package io.dataease.utils; import io.dataease.exception.DEException; import lombok.Data; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.entity.EntityBuilder; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.*; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static io.dataease.result.ResultCode.SYSTEM_INNER_ERROR; public class HttpClientUtil { private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class); private static final String HTTPS = "https"; /** * 根据url构建HttpClient(区分http和https) * * @param url 请求地址 * @return CloseableHttpClient实例 */ private static CloseableHttpClient buildHttpClient(String url) { if (StringUtils.isEmpty(url)) { throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: url 不能为空!"); } try { if (url.startsWith(HTTPS)) { return buildHttpClient(true); } else { // http return HttpClientBuilder.create().build(); } } catch (Exception e) { throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } } private static CloseableHttpClient buildHttpClient(boolean ssl) { try { if (ssl) { SSLContextBuilder builder = new SSLContextBuilder(); builder.loadTrustMaterial(null, (X509Certificate[] x509Certificates, String s) -> true); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(builder.build(), new String[]{"TLSv1.1", "TLSv1.2", "SSLv3"}, null, NoopHostnameVerifier.INSTANCE); Registry registry = RegistryBuilder.create() .register("http", new PlainConnectionSocketFactory()) .register("https", socketFactory).build(); HttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(registry); return HttpClients.custom().setConnectionManager(connManager).build(); } else { // http return HttpClientBuilder.create().build(); } } catch (Exception e) { throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } } public static boolean validateUrl(String url, HttpClientConfig config) { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(url); HttpGet httpGet = new HttpGet(url); if (config == null) { config = new HttpClientConfig(); } httpGet.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpGet.addHeader(key, header.get(key)); } HttpResponse response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() >= 400) { String msg = EntityUtils.toString(response.getEntity(), config.getCharset()); if (StringUtils.isEmpty(msg)) { msg = "StatusCode: " + response.getStatusLine().getStatusCode(); } throw new Exception(msg); } return true; } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { if (httpClient != null) { httpClient.close(); } } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } /** * Get http请求 * * @param url 请求地址 * @param config 配置项,如果null则使用默认配置 * @return 响应结果字符串 */ public static String get(String url, HttpClientConfig config) { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(url); HttpGet httpGet = new HttpGet(url); if (config == null) { config = new HttpClientConfig(); } httpGet.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpGet.addHeader(key, header.get(key)); } HttpResponse response = httpClient.execute(httpGet); return getResponseStr(response, config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { if (httpClient != null) { httpClient.close(); } } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } public static String patch(String url, String json, HttpClientConfig config) { CloseableHttpClient httpClient = buildHttpClient(url); HttpPatch httpPatch = new HttpPatch(url); config = config == null ? new HttpClientConfig() : config; try { httpPatch.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpPatch.addHeader(key, header.get(key)); } EntityBuilder entityBuilder = EntityBuilder.create(); entityBuilder.setText(json); entityBuilder.setContentType(ContentType.APPLICATION_JSON); HttpEntity requestEntity = entityBuilder.build(); httpPatch.setEntity(requestEntity); HttpResponse response = httpClient.execute(httpPatch); return getResponseStr(response, config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { httpClient.close(); } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } /** * Post请求,请求内容必须为JSON格式的字符串 * * @param url 请求地址 * @param config 配置项,如果null则使用默认配置 * @param json JSON格式的字符串 * @return 响应结果字符串 */ public static String post(String url, String json, HttpClientConfig config) { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(url); HttpPost httpPost = new HttpPost(url); if (config == null) { config = new HttpClientConfig(); } httpPost.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpPost.addHeader(key, header.get(key)); } EntityBuilder entityBuilder = EntityBuilder.create(); entityBuilder.setText(json); entityBuilder.setContentType(ContentType.APPLICATION_JSON); HttpEntity requestEntity = entityBuilder.build(); httpPost.setEntity(requestEntity); HttpResponse response = httpClient.execute(httpPost); return getResponseStr(response, config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { if (httpClient != null) { httpClient.close(); } } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } public static HttpResponse postWithHeaders(String url, String json, HttpClientConfig config) { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(url); HttpPost httpPost = new HttpPost(url); if (config == null) { config = new HttpClientConfig(); } httpPost.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpPost.addHeader(key, header.get(key)); } EntityBuilder entityBuilder = EntityBuilder.create(); entityBuilder.setText(json); entityBuilder.setContentType(ContentType.APPLICATION_JSON); HttpEntity requestEntity = entityBuilder.build(); httpPost.setEntity(requestEntity); HttpResponse response = httpClient.execute(httpPost); return response; } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { if (httpClient != null) { httpClient.close(); } } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } public static String put(String url, String json, HttpClientConfig config) { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(url); HttpPut httpPut = new HttpPut(url); if (config == null) { config = new HttpClientConfig(); } httpPut.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpPut.addHeader(key, header.get(key)); } EntityBuilder entityBuilder = EntityBuilder.create(); entityBuilder.setText(json); entityBuilder.setContentType(ContentType.APPLICATION_JSON); HttpEntity requestEntity = entityBuilder.build(); httpPut.setEntity(requestEntity); HttpResponse response = httpClient.execute(httpPut); return getResponseStr(response, config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { if (httpClient != null) { httpClient.close(); } } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } /** * Post请求,请求内容必须为JSON格式的字符串 * * @param url 请求地址 * @param json JSON格式的字符串 * @return 响应结果字符串 */ public static String post(String url, String json) { return HttpClientUtil.post(url, json, null); } /** * Post请求,请求内容必须为键值对参数 * * @param url 请求地址 * @param config 配置项,如果null则使用默认配置 * @param body 请求内容键值对参数 * @return 响应结果字符串 */ public static String post(String url, Map body, HttpClientConfig config) { try (CloseableHttpClient httpClient = buildHttpClient(url)) { HttpPost httpPost = new HttpPost(url); if (config == null) { config = new HttpClientConfig(); } httpPost.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpPost.addHeader(key, header.get(key)); } if (body != null && body.size() > 0) { List nvps = new ArrayList<>(); for (String key : body.keySet()) { nvps.add(new BasicNameValuePair(key, body.get(key))); } try { httpPost.setEntity(new UrlEncodedFormEntity(nvps, config.getCharset())); } catch (Exception e) { logger.error("HttpClient转换编码错误", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient转换编码错误: " + e.getMessage()); } } HttpResponse response = httpClient.execute(httpPost); return getResponseStr(response, config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } } private static String getResponseStr(HttpResponse response, HttpClientConfig config) throws Exception { if (response.getStatusLine().getStatusCode() >= 400) { String msg = EntityUtils.toString(response.getEntity(), config.getCharset()); if (StringUtils.isEmpty(msg)) { msg = "StatusCode: " + response.getStatusLine().getStatusCode(); } throw new Exception(msg); } return EntityUtils.toString(response.getEntity(), config.getCharset()); } public static Map downloadFile(String url, HttpClientConfig config, String path) { String encodeUIl = url; Map name = new HashMap<>(); if (!url.contains("%")) { String[] http = url.split("://"); String[] server = http[1].split("/"); encodeUIl = http[0] + "://" + server[0] + "/" + URLEncoder.encode(http[1].substring(server[0].length() + 1, http[1].length())); } try (CloseableHttpClient httpClient = buildHttpClient(encodeUIl.replace("+", "%20"))) { HttpGet httpGet = new HttpGet(encodeUIl.replace("+", "%20")); // 设置请求配置 httpGet.setConfig(config.buildRequestConfig()); // 设置请求头 config.getHeader().forEach(httpGet::addHeader); HttpResponse response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() >= 400) { String msg = EntityUtils.toString(response.getEntity(), config.getCharset()); if (StringUtils.isEmpty(msg)) { msg = "StatusCode: " + response.getStatusLine().getStatusCode(); } throw new Exception(msg); } String fileName = extractFileName(response, url); String suffix = fileName.substring(fileName.lastIndexOf(".") + 1); String tranName = UUID.randomUUID().toString() + "." + suffix; name.put("fileName", fileName); name.put("tranName", tranName); File localFile = new File(path + tranName); try (InputStream is = response.getEntity().getContent(); FileOutputStream outputStream = new FileOutputStream(localFile)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new RuntimeException("HttpClient查询失败: " + e.getMessage(), e); } return name; } private static String extractFileName(HttpResponse response, String url) { url = URLDecoder.decode(url); String fileName = ""; String disposition = response.getHeaders("Content-Disposition").toString(); if (disposition != null) { int filenameIndex = disposition.indexOf("filename="); if (filenameIndex > 0) { fileName = disposition.substring(filenameIndex + 9) .replaceAll("\"", "") // 去除引号 .trim(); } } if (fileName.isEmpty()) { url = url.split("\\?")[0]; fileName = url.contains("/") ? url.substring(url.lastIndexOf('/') + 1) : "download_" + System.currentTimeMillis(); } if (fileName.trim().isEmpty()) { fileName = "download_" + System.currentTimeMillis(); } return fileName; } public static byte[] downloadBytes(String url) { HttpClientConfig config = new HttpClientConfig(); return HttpClientUtil.downFromRemote(url, config); } public static byte[] downFromRemote(String url, HttpClientConfig config) { try (CloseableHttpClient httpClient = buildHttpClient(url)) { HttpGet httpGet = new HttpGet(url); // 设置请求配置 httpGet.setConfig(config.buildRequestConfig()); // 设置请求头 config.getHeader().forEach(httpGet::addHeader); HttpResponse response = httpClient.execute(httpGet); try (InputStream inputStream = response.getEntity().getContent(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; // 读取响应内容并写入输出流 while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } return outputStream.toByteArray(); } } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new RuntimeException("HttpClient查询失败: " + e.getMessage(), e); } } public static String postFile(String fileServer, byte[] bytes, String fileName, Map param, HttpClientConfig config) { CloseableHttpClient httpClient = buildHttpClient(fileServer); HttpPost postRequest = new HttpPost(fileServer); if (config == null) { config = new HttpClientConfig(); } postRequest.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); if (MapUtils.isNotEmpty(header)) { Iterator var8 = header.keySet().iterator(); while (var8.hasNext()) { String key = (String) var8.next(); postRequest.addHeader(key, (String) header.get(key)); } } MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setCharset(StandardCharsets.UTF_8); builder.addBinaryBody("image", bytes, ContentType.DEFAULT_BINARY, fileName); if (param != null) { Iterator var13 = param.entrySet().iterator(); while (var13.hasNext()) { Map.Entry entry = (Map.Entry) var13.next(); builder.addTextBody((String) entry.getKey(), (String) entry.getValue()); } } try { postRequest.setEntity((HttpEntity) builder.build()); return getResponseStr(httpClient.execute(postRequest), config); } catch (Exception var11) { logger.error("HttpClient查询失败", var11); throw new RuntimeException("HttpClient查询失败: " + var11.getMessage()); } } public static String upload(String url, byte[] bytes, String name, Map paramMap, Map headMap) { HttpClientConfig config = new HttpClientConfig(); addHead(config, headMap); return HttpClientUtil.postFile(url, bytes, name, paramMap, config); } public static String upload(String url, File file, String name) { HttpClientConfig config = new HttpClientConfig(); Map param = new HashMap<>(); param.put("fileFlag", "media"); param.put("fileName", name); return HttpClientUtil.postFile(url, file, param, config); } public static String postFile(String fileServer, File file, Map param, HttpClientConfig config) { CloseableHttpClient httpClient = buildHttpClient(fileServer); HttpPost postRequest = new HttpPost(fileServer); if (config == null) { config = new HttpClientConfig(); } postRequest.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); String fileFlag = param.get("fileFlag"); String fileName = param.get("fileName"); param.remove("fileFlag"); param.remove("fileName"); if (MapUtils.isNotEmpty(header)) { for (String key : header.keySet()) { postRequest.addHeader(key, header.get(key)); } } postRequest.setHeader("Content-Type", "multipart/form-data"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setCharset(StandardCharsets.UTF_8); builder.addBinaryBody(StringUtils.isNotBlank(fileFlag) ? fileFlag : "file", file, ContentType.APPLICATION_OCTET_STREAM, StringUtils.isNotBlank(fileName) ? fileName : file.getName()); if (MapUtils.isNotEmpty(param)) { for (Map.Entry entry : param.entrySet()) { StringBody stringBody = new StringBody(entry.getValue(), ContentType.TEXT_PLAIN.withCharset("utf-8")); builder.addPart(entry.getKey(), stringBody); } } try { postRequest.setEntity(builder.build()); return getResponseStr(httpClient.execute(postRequest), config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new RuntimeException("HttpClient查询失败: " + e.getMessage()); } } private static void addHead(HttpClientConfig config, Map headMap) { if (MapUtils.isEmpty(headMap)) return; for (Map.Entry entry : headMap.entrySet()) { config.addHeader(entry.getKey(), entry.getValue().toString()); } } public static String delete(String url, HttpClientConfig config) { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(url); HttpDelete httpDelete = new HttpDelete(url); if (config == null) { config = new HttpClientConfig(); } httpDelete.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpDelete.addHeader(key, header.get(key)); } HttpResponse response = httpClient.execute(httpDelete); return getResponseStr(response, config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { if (httpClient != null) { httpClient.close(); } } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } public static boolean isURLReachable(String urlString, Map head) { try { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(5000); // 设置连接超时时间,单位为毫秒 connection.setReadTimeout(5000); // 设置读取超时时间,单位为毫秒 if (MapUtils.isNotEmpty(head)) { for (Map.Entry entry : head.entrySet()) { connection.addRequestProperty(entry.getKey(), entry.getValue()); } } int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { return true; // 状态码200表示URL可达 } else if (StringUtils.equalsIgnoreCase("Unauthorized", connection.getResponseMessage())) { LogUtil.error("apisix key error [failed to check token]"); } } catch (IOException e) { return false; } return false; // 如果发生异常或状态码不是200,则URL不可达 } public static String postWebhook(String url, String contentType, Map param, boolean ssl, HttpClientConfig config) { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(ssl); HttpPost httpPost = new HttpPost(url); if (ObjectUtils.isEmpty(config)) { config = new HttpClientConfig(); } httpPost.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpPost.addHeader(key, header.get(key)); } if (StringUtils.equalsIgnoreCase(contentType, ContentType.APPLICATION_JSON.getMimeType())) { EntityBuilder entityBuilder = EntityBuilder.create(); if (MapUtils.isNotEmpty(param)) { String json = JsonUtil.toJSONString(param).toString(); entityBuilder.setText(json); } entityBuilder.setContentType(ContentType.APPLICATION_JSON); HttpEntity requestEntity = entityBuilder.build(); httpPost.setEntity(requestEntity); } else { List nvps = param.entrySet().stream().map(entry -> new BasicNameValuePair(entry.getKey(), ObjectUtils.isEmpty(entry.getValue()) ? null : entry.getValue().toString())).collect(Collectors.toList()); try { UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nvps, config.getCharset()); httpPost.setEntity(entity); } catch (Exception e) { logger.error("HttpClient转换编码错误", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient转换编码错误: " + e.getMessage()); } } HttpResponse response = httpClient.execute(httpPost); return getResponseStr(response, config); } catch (Exception e) { logger.error("HttpClient查询失败", e); throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage()); } finally { try { if (httpClient != null) { httpClient.close(); } } catch (Exception e) { logger.error("HttpClient关闭连接失败", e); } } } public static MultipartResponse postForScreenshot( String url, Map body, HttpClientConfig config) throws IOException { CloseableHttpClient httpClient = null; try { httpClient = buildHttpClient(url); HttpPost httpPost = new HttpPost(url); if (config == null) { config = new HttpClientConfig(); } httpPost.setConfig(config.buildRequestConfig()); Map header = config.getHeader(); for (String key : header.keySet()) { httpPost.addHeader(key, header.get(key)); } EntityBuilder entityBuilder = EntityBuilder.create(); String json = JsonUtil.toJSONString(body).toString(); entityBuilder.setText(json); entityBuilder.setContentType(ContentType.APPLICATION_JSON); HttpEntity requestEntity = entityBuilder.build(); httpPost.setEntity(requestEntity); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new DEException(response.getStatusLine().getStatusCode(), response.toString()); } HttpEntity entity = response.getEntity(); byte[] bytes = EntityUtils.toByteArray(entity); // raw bytes String contentType = response.getFirstHeader("Content-Type").getValue(); if (contentType.startsWith("multipart/")) { return MultipartParser.parse(bytes, contentType); // see util below } else { throw new IOException("unexpected response: " + contentType); } } } catch (IOException e) { throw new RuntimeException(e); } } @Data public static class MultipartResponse { Map metadata; byte[] image; } public class MultipartParser { public static MultipartResponse parse(byte[] body, String contentType) throws IOException { String boundary = extractBoundary(contentType); String delim = "--" + boundary; byte[] delimBytes = delim.getBytes(); MultipartResponse resp = new MultipartResponse(); resp.metadata = new HashMap<>(); int idx = 0; while (idx < body.length) { int start = indexOf(body, delimBytes, idx); if (start < 0) break; idx = start + delimBytes.length; if (idx + 1 < body.length && body[idx] == '-' && body[idx+1] == '-') break; // final boundary // skip CRLF if (body[idx] == '\r' && body[idx+1] == '\n') idx += 2; // read headers int headerEnd = indexOf(body, "\r\n\r\n".getBytes(), idx); String headers = new String(body, idx, headerEnd - idx); idx = headerEnd + 4; boolean isImage = headers.contains("name=\"image\""); int nextBoundary = indexOf(body, delimBytes, idx); if (nextBoundary < 0) break; byte[] part = new byte[nextBoundary - idx - 2]; // strip trailing CRLF System.arraycopy(body, idx, part, 0, part.length); if (isImage) { resp.image = part; } else { String json = new String(part); // 最简单把整个 JSON 字符串放到 metadata map; // 你也可以用 Jackson/Gson 解析成具体字段 resp.metadata = JsonUtil.parseObject(json, Map.class); } idx = nextBoundary; } return resp; } private static String extractBoundary(String contentType) { Pattern p = Pattern.compile("boundary=(.*)"); Matcher m = p.matcher(contentType); if (m.find()) { return m.group(1); } throw new IllegalArgumentException("No boundary in content-type"); } private static int indexOf(byte[] array, byte[] target, int start) { outer: for (int i = start; i <= array.length - target.length; i++) { for (int j = 0; j < target.length; j++) { if (array[i+j] != target[j]) continue outer; } return i; } return -1; } } }