在系統編寫的時候,常常會與外圍系統的接口進行對接,或者調用一些子系統的接口,那麼此時會涉及到http請求。
在以往,我沒有采用統一的方式編寫這類程序,會使用hutool、RestTemplate,所以,在此,我想給自己編寫一套統一的請求方式,以便學習。
採用框架:OkHttp3,原因是瞭解到此框架性能良好,安全性好。
需求:
- 普通的get、post請求
- 異步的post請求
OkHttp基礎概念
一、OkHttpClient
此類是框架的核心客户端
二、RequestBody
用於構建不同的請求類型
- application/x-www-form-urlencoded
上手使用
添加Maven依賴
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version> <!-- 2023年最新穩定版 -->
</dependency>
工具類
package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import okio.Buffer;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* 基於okhttp4的HTTP請求工具類
* @author song
*/
public class HttpUtil {
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.build();
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");
private static final MediaType STREAM_MEDIA_TYPE = MediaType.parse("application/octet-stream");
private HttpUtil() {}
/**
* 同步GET請求
*/
public static String get(String url, Map<String, Object> headers, Map<String, Object> queryParams) throws IOException {
HttpUrl httpUrl = buildGetUrl(url, queryParams);
Request request = new Request.Builder()
.url(httpUrl)
.headers(buildHeaders(headers))
.get()
.build();
try (Response response = client.newCall(request).execute()) {
validateResponse(response);
return Objects.requireNonNull(response.body()).string();
}
}
/**
* 同步POST請求(JSON格式)
*/
public static String postJson(String url, Map<String, Object> headers, Object bean) throws IOException {
String json = objectMapper.writeValueAsString(bean);
RequestBody body = RequestBody.create(json, JSON_MEDIA_TYPE);
Request request = new Request.Builder()
.url(url)
.headers(buildHeaders(headers))
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
validateResponse(response);
return Objects.requireNonNull(response.body()).string();
}
}
/**
* 同步POST請求(JSON格式)
*/
public static String postJson(String url, Map<String, Object> headers, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON_MEDIA_TYPE);
Request request = new Request.Builder()
.url(url)
.headers(buildHeaders(headers))
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
validateResponse(response);
return Objects.requireNonNull(response.body()).string();
}
}
/**
* 文件下載方法
* @param savePath 文件保存路徑(包含文件名)
*/
public static void downloadFile(String url, Map<String, Object> headers, String savePath) throws IOException {
Request request = new Request.Builder()
.url(url)
.headers(buildHeaders(headers))
.build();
try (Response response = client.newCall(request).execute()) {
validateResponse(response);
File file = new File(savePath);
try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
sink.writeAll(Objects.requireNonNull(response.body()).source());
}
}
}
public static String uploadFile(String url, Map<String, Object> headers, String fileFieldName,
File file, Consumer<Progress> progressConsumer) throws IOException {
return uploadFile(url, headers, fileFieldName, file, null, progressConsumer);
}
public static String uploadFile(String url, Map<String, Object> headers,
String fieldName, File file,
Map<String, String> formParams,
Consumer<Progress> progressConsumer) throws IOException {
if (file == null) {
throw new IOException("上傳文件不能為null");
}
if (!file.exists() || !file.isFile()) {
throw new IOException("文件不存在或不是有效文件: " + file.getAbsolutePath());
}
RequestBody fileBody = createStreamingBody(file, STREAM_MEDIA_TYPE, progressConsumer);
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(fieldName, file.getName(), fileBody);
// 添加額外的表單參數
if (formParams != null) {
for (Map.Entry<String, String> entry : formParams.entrySet()) {
builder.addFormDataPart(entry.getKey(), entry.getValue());
}
}
MultipartBody body = builder.build();
Request request = new Request.Builder()
.url(url)
.headers(buildHeaders(headers))
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
validateResponse(response);
return Objects.requireNonNull(response.body()).string();
}
}
/**
* 創建流式上傳的請求體(帶進度回調)
*/
private static RequestBody createStreamingBody(final File file, final MediaType mediaType,
final Consumer<Progress> progressConsumer) {
return new RequestBody() {
@Override
public MediaType contentType() {
return mediaType;
}
@Override
public long contentLength() {
return file.length();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
try (Source source = Okio.source(file)) {
long total = file.length();
long uploaded = 0;
Buffer buffer = new Buffer();
long read;
while ((read = source.read(buffer, 8192)) != -1) {
sink.write(buffer, read);
uploaded += read;
buffer.clear();
if (progressConsumer != null && total > 0) {
progressConsumer.accept(new Progress(uploaded, total));
}
}
}
}
};
}
// 構建GET請求URL
private static HttpUrl buildGetUrl(String url, Map<String, Object> params) throws IOException {
if (url == null || url.trim().isEmpty()) {
throw new IOException("請求URL不能為空");
}
HttpUrl baseUrl = Objects.requireNonNull(HttpUrl.parse(url), "無效的URL: " + url);
HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
if (params != null) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
}
return urlBuilder.build();
}
// 構建請求頭
private static Headers buildHeaders(Map<String, Object> headers) {
Headers.Builder builder = new Headers.Builder();
if (headers != null) {
for (Map.Entry<String, Object> entry : headers.entrySet()) {
builder.add(entry.getKey(), String.valueOf(entry.getValue()));
}
}
return builder.build();
}
// 驗證響應狀態
private static void validateResponse(Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("HTTP請求失敗: " + response.code() + " - " + response.message());
}
}
public static class Progress {
private final long current;
private final long total;
public Progress(long current, long total) {
this.current = current;
this.total = total;
}
public long getCurrent() {
return current;
}
public long getTotal() {
return total;
}
public double getPercentage() {
return total > 0 ? (current * 100.0 / total) : 0;
}
}
}