feat: 实现IP白名单严格拦截、三方Webhook多租户事件订阅与MCP专线业务调用

This commit is contained in:
wjh
2026-10-09 11:51:23 +08:00
parent c6bd736e7f
commit 00a9dcaf2f
11 changed files with 603 additions and 58 deletions
@@ -0,0 +1,102 @@
package com.iot.gateway.common;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 网关与核心业务后端 (IOT-backend) 的专线 HTTP 桥接客户端
* <p>
* 自动向核心后端传递 X-Internal-Secret 与 X-Tenant-Id,
* 触发核心后端切入指定租户的独立物理数据库。
*/
@Slf4j
@Component
public class BackendInternalClient {
@Value("${gateway.backend.base-url:http://localhost:8080}")
private String backendBaseUrl;
@Value("${gateway.backend.internal-secret:iot-internal-open-gateway-secret-key-20261009-xyz}")
private String internalSecret;
private final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 内部专线 GET 请求
*/
public JsonNode get(String path, Long tenantId, Map<String, String> queryParams) {
StringBuilder urlBuilder = new StringBuilder(backendBaseUrl).append(path);
if (queryParams != null && !queryParams.isEmpty()) {
urlBuilder.append("?");
queryParams.forEach((k, v) -> {
if (v != null && !v.isEmpty()) {
urlBuilder.append(k).append("=").append(v).append("&");
}
});
urlBuilder.deleteCharAt(urlBuilder.length() - 1);
}
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(urlBuilder.toString()))
.header("X-Internal-Secret", internalSecret)
.header("X-Tenant-Id", String.valueOf(tenantId != null ? tenantId : 1L))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(6))
.GET()
.build();
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() == 200) {
return objectMapper.readTree(resp.body());
} else {
log.warn("核心后端内部专线响应非 200: status={}, body={}", resp.statusCode(), resp.body());
}
} catch (Exception e) {
log.warn("调用核心后端内部接口异常: url={}, tenantId={}, err={}", urlBuilder, tenantId, e.getMessage());
}
return null;
}
/**
* 内部专线 POST 请求
*/
public JsonNode post(String path, Long tenantId, Map<String, Object> body) {
String url = backendBaseUrl + path;
try {
String jsonStr = objectMapper.writeValueAsString(body != null ? body : new HashMap<>());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("X-Internal-Secret", internalSecret)
.header("X-Tenant-Id", String.valueOf(tenantId != null ? tenantId : 1L))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(6))
.POST(HttpRequest.BodyPublishers.ofString(jsonStr))
.build();
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() == 200) {
return objectMapper.readTree(resp.body());
} else {
log.warn("核心后端内部专线响应非 200: status={}, body={}", resp.statusCode(), resp.body());
}
} catch (Exception e) {
log.warn("调用核心后端内部 POST 异常: url={}, tenantId={}, err={}", url, tenantId, e.getMessage());
}
return null;
}
}