From 2b40f2e13b3adfd24603cc39b1292f33cfef2ae3 Mon Sep 17 00:00:00 2001 From: wzy-warehouse <18135009705@163.com> Date: Tue, 5 May 2026 19:22:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=B4=E9=9B=A8=E5=9B=BE=E5=B1=82=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/AlgorithmServerProperties.java | 19 ++ .../controller/AlgorithmProxyController.java | 222 ++++++++++++++++++ src/main/resources/application-dev.yml | 10 +- src/main/resources/application-prod.yml | 10 +- 4 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/gis/xian/config/AlgorithmServerProperties.java create mode 100644 src/main/java/com/gis/xian/controller/AlgorithmProxyController.java diff --git a/src/main/java/com/gis/xian/config/AlgorithmServerProperties.java b/src/main/java/com/gis/xian/config/AlgorithmServerProperties.java new file mode 100644 index 0000000..395011e --- /dev/null +++ b/src/main/java/com/gis/xian/config/AlgorithmServerProperties.java @@ -0,0 +1,19 @@ +package com.gis.xian.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 算法服务器配置属性 + */ +@Data +@Component +@ConfigurationProperties(prefix = "algorithm.server") +public class AlgorithmServerProperties { + + /** + * 算法服务器地址 + */ + private String url; +} diff --git a/src/main/java/com/gis/xian/controller/AlgorithmProxyController.java b/src/main/java/com/gis/xian/controller/AlgorithmProxyController.java new file mode 100644 index 0000000..9d405d9 --- /dev/null +++ b/src/main/java/com/gis/xian/controller/AlgorithmProxyController.java @@ -0,0 +1,222 @@ +package com.gis.xian.controller; + +import com.alibaba.fastjson2.JSON; +import com.gis.xian.config.AlgorithmServerProperties; +import com.gis.xian.domain.ApiResponse; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.Map; + +/** + * 算法代理控制器 + * 所有以 /algorithm-api 开头的请求都会转发到算法服务器 + */ +@RestController +@RequestMapping("/algorithm-api") +@Slf4j +public class AlgorithmProxyController extends BaseController { + + @Resource + private AlgorithmServerProperties algorithmServerProperties; + + private final RestTemplate restTemplate = new RestTemplate(); + + /** + * 处理所有 GET 请求 + */ + @GetMapping("/**") + public ApiResponse proxyGet(HttpServletRequest request) { + return proxyRequest(request, HttpMethod.GET); + } + + /** + * 处理所有 POST 请求 + */ + @PostMapping("/**") + public ApiResponse proxyPost(HttpServletRequest request) throws IOException { + return proxyRequest(request, HttpMethod.POST); + } + + /** + * 处理所有 PUT 请求 + */ + @PutMapping("/**") + public ApiResponse proxyPut(HttpServletRequest request) throws IOException { + return proxyRequest(request, HttpMethod.PUT); + } + + /** + * 处理所有 DELETE 请求 + */ + @DeleteMapping("/**") + public ApiResponse proxyDelete(HttpServletRequest request) { + return proxyRequest(request, HttpMethod.DELETE); + } + + /** + * 处理所有 PATCH 请求 + */ + @PatchMapping("/**") + public ApiResponse proxyPatch(HttpServletRequest request) throws IOException { + return proxyRequest(request, HttpMethod.PATCH); + } + + /** + * 通用的请求代理方法 + */ + @SuppressWarnings("unchecked") + private ApiResponse proxyRequest(HttpServletRequest request, HttpMethod httpMethod) { + long startTime = System.currentTimeMillis(); + + try { + // 构建目标 URL + String targetUrl = buildTargetUrl(request); + log.info("代理请求: {} -> {}", request.getRequestURI(), targetUrl); + + // 构建请求头 + HttpHeaders headers = buildRequestHeaders(request); + + // 构建请求体 + Object requestBody = null; + if (httpMethod == HttpMethod.POST || httpMethod == HttpMethod.PUT || httpMethod == HttpMethod.PATCH) { + String contentType = request.getContentType(); + if (contentType != null && !contentType.isEmpty()) { + headers.setContentType(MediaType.parseMediaType(contentType)); + + // 读取请求体 + byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream()); + if (bodyBytes.length > 0) { + // 尝试解析为 JSON,如果不是 JSON 则直接使用字节数组 + if (contentType.contains("application/json")) { + String jsonBody = new String(bodyBytes, StandardCharsets.UTF_8); + try { + requestBody = JSON.parse(jsonBody); + } catch (Exception e) { + requestBody = jsonBody; + } + } else { + requestBody = bodyBytes; + } + } + } + } + + // 创建 HTTP 实体 + HttpEntity entity = new HttpEntity<>(requestBody, headers); + + // 发送请求到算法服务器 + ResponseEntity response = restTemplate.exchange( + targetUrl, + httpMethod, + entity, + String.class + ); + + // 解析响应 + long endTime = System.currentTimeMillis(); + log.info("代理请求完成: {} -> {}, 耗时: {}ms, 状态码: {}", + request.getRequestURI(), targetUrl, (endTime - startTime), response.getStatusCode()); + + // 尝试将响应解析为 ApiResponse + String responseBody = response.getBody(); + if (responseBody != null && !responseBody.isEmpty()) { + try { + // 尝试解析为 ApiResponse 格式 + Map responseMap = JSON.parseObject(responseBody, Map.class); + if (responseMap.containsKey("code") && responseMap.containsKey("message")) { + // 如果已经是 ApiResponse 格式,直接返回 + Integer code = (Integer) responseMap.get("code"); + String message = (String) responseMap.get("message"); + Object data = responseMap.get("data"); + return new ApiResponse<>(code, message, data); + } + } catch (Exception e) { + log.debug("响应不是标准 ApiResponse 格式,将作为数据返回"); + } + + // 如果不是 ApiResponse 格式,将整个响应作为 data 返回 + return ApiResponse.ok((Object) responseBody); + } + + return ApiResponse.ok((Object) null); + + } catch (Exception e) { + long endTime = System.currentTimeMillis(); + log.error("代理请求失败: {} , 耗时: {}ms, 错误: {}", + request.getRequestURI(), (endTime - startTime), e.getMessage(), e); + return ApiResponse.error(502, "算法服务调用失败: " + e.getMessage(), null); + } + } + + /** + * 构建目标 URL + */ + private String buildTargetUrl(HttpServletRequest request) { + // 获取原始请求路径,去掉 /algorithm-api 前缀 + String requestUri = request.getRequestURI(); + String path = requestUri.substring("/algorithm-api".length()); + + // 确保路径以 / 开头 + if (!path.startsWith("/")) { + path = "/" + path; + } + + // 拼接算法服务器地址和路径 + String baseUrl = algorithmServerProperties.getUrl(); + if (baseUrl.endsWith("/") && path.startsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + + String targetUrl = baseUrl + path; + + // 添加查询参数 + String queryString = request.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + targetUrl += "?" + queryString; + } + + return targetUrl; + } + + /** + * 构建请求头 + */ + private HttpHeaders buildRequestHeaders(HttpServletRequest request) { + HttpHeaders headers = new HttpHeaders(); + + // 复制所有请求头(排除一些特定的头) + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + // 排除一些不应该转发的头 + if (!shouldExcludeHeader(headerName)) { + Enumeration headerValues = request.getHeaders(headerName); + while (headerValues.hasMoreElements()) { + headers.add(headerName, headerValues.nextElement()); + } + } + } + + return headers; + } + + /** + * 判断是否应该排除该请求头 + */ + private boolean shouldExcludeHeader(String headerName) { + String lowerName = headerName.toLowerCase(); + // 排除 Host、Content-Length 等头 + return lowerName.equals("host") || + lowerName.equals("content-length") || + lowerName.equals("transfer-encoding"); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index ee1759e..c094695 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -41,7 +41,15 @@ safety: no-encrypt-paths: - /crypto/sm2/public-key - /druid + - /algorithm-api/rainfall/grid # 请求无需解密的路径 no-decrypt-paths: - /crypto/sm2/public-key - - /druid \ No newline at end of file + - /druid + - /algorithm-api/rainfall/grid + +# 算法服务器配置 +algorithm: + server: + # 开发环境算法服务器地址 + url: http://localhost:8082 \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 0912dfa..c233141 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -41,6 +41,14 @@ safety: # 响应无需加密的路径 no-encrypt-paths: - /crypto/sm2/public-key + - /algorithm-api/rainfall/grid # 请求无需解密的路径 no-decrypt-paths: - - /crypto/sm2/public-key \ No newline at end of file + - /crypto/sm2/public-key + - /algorithm-api/rainfall/grid + +# 算法服务器配置 +algorithm: + server: + # 生产环境算法服务器地址 + url: http://localhost:8081 \ No newline at end of file