基于 Spring Framework 6 的跨服务请求客户端 - HTTP Interface
从 这里好像没有讨论 java 技术的 继续讨论。
在前段时间公司要自己搞个电商平台,首选开发语言用 java, 架子用 springboot,我作为研发负责人。
在立项初期我准备上 cloud, 但是老板没同意,说后面再迭代,我因为刚来这家公司我也就没好说什么,单体就单体呗。
在后面的开发过程中产品提出要增加一个供应商服务端,两端会有频繁的数据交互,我思考了半天有几个方案,要么是谷歌的 grpc, 要么是阿里的 dubbo, 或者是 openfeign, 不过 grpc 要写 protobuf,dubbo 以前用的 2.8 版本遇到兼容性问题现在好多年不用而且时间给的少我怕来不及,openfeign 又没脱离 cloud 依赖,需要引入一堆 maven 依赖,思来想去总不能直接 http 吧,这不胶水架构了么。
最后吧,还是用了胶水架构,不过是好用一点的,这就是 HTTP Interface 啦,因为当时是我负责搭建架子的,所以我采用了基于 jdk17 的 boot3.5, 正好是基于 Spring Framework 6 的。
这个东西用起来门槛非常低,只需要一个配置:
@Configuration @RequiredArgsConstructor @Slf4j public class HttpInterfaceConfig {
private final TenantPropagator tenantPropagator;
@Value("${service.auth.secret-key}") private String secretKey;
private WebClient.Builder createWebClientBuilder(String baseUrl) {
return WebClient.builder()
.baseUrl(baseUrl)
.filter(this::addAuthHeaders) // 添加认证过滤器
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
}
/**
* 为每次请求动态添加认证头
*/ private Mono<ClientResponse> addAuthHeaders(ClientRequest request, ExchangeFunction next) {
// 每次请求时动态生成时间戳、随机数和签名 long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
String signature = HmacSignUtils.generateTimestampSignature(timestamp, nonce, secretKey);
log.debug("🔐 动态生成认证头 - timestamp: {}, nonce: {}, signature: {}",
timestamp, nonce, signature);
// 创建新的请求,添加认证头 ClientRequest newRequest = ClientRequest.from(request)
.header("X-Internal-Service", "true")
.header("X-Service-Timestamp", String.valueOf(timestamp))
.header("X-Service-Nonce", nonce)
.header("X-Service-Signature", signature)
.build();
return next.exchange(newRequest);
}
@Bean public OperationPlatFormApiClient operationPlatformApiClient(
@Value("${service.operationPlatform.url}") String baseUrl) {
log.info("=== operationPlatformApiClient 配置信息 ===");
log.info("baseUrl: {}", baseUrl);
log.info("secretKey: {}", secretKey != null ? "已配置" : "未配置");
// 创建基础的 WebClient Builder
WebClient.Builder webClientBuilder = createWebClientBuilder(baseUrl);
// 传播租户信息
webClientBuilder = tenantPropagator.propagateTenant(webClientBuilder);
WebClient webClient = webClientBuilder.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(adapter)
.build();
OperationPlatFormApiClient client = factory.createClient(OperationPlatFormApiClient.class);
log.info("✅ operationPlatformApiClient 创建成功");
return client;
}
@Bean public SupplierApiClient supplierApiClient(
@Value("${service.supplier.url}") String baseUrl) {
log.info("=== SupplierApiClient 配置信息 ===");
log.info("baseUrl: {}", baseUrl);
log.info("secretKey: {}", secretKey != null ? "已配置" : "未配置");
// 创建基础的 WebClient Builder
WebClient.Builder webClientBuilder = createWebClientBuilder(baseUrl);
// 传播租户信息
webClientBuilder = tenantPropagator.propagateTenant(webClientBuilder);
WebClient webClient = webClientBuilder.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(adapter)
.build();
SupplierApiClient client = factory.createClient(SupplierApiClient.class);
log.info("✅ SupplierApiClient 创建成功");
return client;
}
@Bean public CrowdsourcingApiClient crowdsourcingApiClient(
@Value("${service.crowdsourcing.url}") String baseUrl) {
log.info("=== CrowdsourcingApiClient 配置信息 ===");
log.info("baseUrl: {}", baseUrl);
// 创建基础的 WebClient Builder
WebClient.Builder webClientBuilder = createWebClientBuilder(baseUrl);
// 传播租户信息
webClientBuilder = tenantPropagator.propagateTenant(webClientBuilder);
WebClient webClient = webClientBuilder.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
return HttpServiceProxyFactory
.builderFor(adapter)
.build()
.createClient(CrowdsourcingApiClient.class);
}
}
就可以了,然后就能像写 openfeign 那样:
@Resource private SupplierApiClient supplierApiClient;
@PostMapping("/createSupplier") @Operation(summary = "新增供应商用户") public Mono<CommonResult<Integer>> createSupplier(@Valid @RequestBody SuppLierUsersVO reqVO) {
return ReactiveApiHandler.processReactive(supplierApiClient.createAccount(reqVO), "创建供应商账户");
}
要使用它只需要有 spring-webflux 就可以了,好像是 Spring Framework 6 自带的。
ps:
这东西有个坑,就是你的客户端里的接口越多,启动的时候就越慢,所以一般推荐做个懒加载,不然你客户端写上百个接口,启动就要占用你一分钟的时间。