业务流程
在上一章节中,我们的sdk组件已经完成了从接口和方法上收集到相关信息的功能。而所有的相关信息都是需要注册到网关注册中心上面去的,本节我们就来补充将相关信息注册到网关注册中心上去的功能。其开发与api-gateway-assist十分类似。
结合着第20章采集到的服务信息,这里把这些服务信息,向网关中心注册。
通常提供服务接口的实现类,只会有一个接口以及对应的实现类。如果有多个接口会抛出异常提醒。
业务实现
实现思路
实现项目结构如下
首先我们在网关注册中心的对于服务端的注册方法接口如下图
可以看到针对网关注册中心针对服务端的注册有系统,接口,方法几个类,每个类的参数情况和,返回结果都很清楚,因此我们的api-gateway-sdk组件在发起注册时,一定要针对其返回结果和参数进行详细的比对。
首先我们已经在上一节中拿到了相关的调用网关注册中心的注册http请求的所有参数,现在我们需要的就是在任意服务项目引入我们的sdk组件后可以某一个时刻,对注册中心发起注册的http请求,根据接收到的返回结果判断注册是否成功。
上一章,已经拿到了请求所需的参数信息,这一章在真正注册时,只需要调用相关的注册方法即可,并接受到请求的响应结果。
具体实现
首先我们需要一个结果包装类用于接受http请求返回的结果,如下
package com.zshunbao.gateway.sdk.common;
/**
* @program: api-gateway-sdk
* @ClassName Result
* @description: 统一返回对象中,Code码、Info描述
* @author: zs宝
* @create: 2025-09-01 17:10
* @Version 1.0
**/
public class Result<T> {
private String code;
private String info;
private T data;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
在有了这个后,我们就需要一个服务类,这个服务类用于向注册中心发起注册请求,并接受结果。GatewayCenterService.java
package com.zshunbao.gateway.sdk.domain.service;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.zshunbao.gateway.sdk.GatewayException;
import com.zshunbao.gateway.sdk.common.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* @program: api-gateway-sdk
* @ClassName GatewayCenterService
* @description: 网关中心服务,向网关注册中心发起注册
* @author: zs宝
* @create: 2025-09-01 17:13
* @Version 1.0
**/
public class GatewayCenterService {
private Logger logger = LoggerFactory.getLogger(GatewayCenterService.class);
/**
* 注册系统
* @param address
* @param systemId
* @param systemName
* @param systemType
* @param systemRegistry
*/
public void doRegisterApplication( String address,String systemId,
String systemName,
String systemType,
String systemRegistry){
Map<String, Object> paramMap=new HashMap<>();
paramMap.put("systemId",systemId);
paramMap.put("systemName",systemName);
paramMap.put("systemType",systemType);
paramMap.put("systemRegistry",systemRegistry);
String resultStr;
try{
resultStr= HttpUtil.post(address+"/wg/admin/register/registerApplication",paramMap,1200);
}catch (Exception e){
logger.error("应用服务接口注册异常,链接资源不可用:{}", address + "/wg/admin/register/registerApplication");
throw e;
}
Result<Boolean> result = JSON.parseObject(resultStr, new TypeReference<Result<Boolean>>() {
});
logger.info("向网关中心注册应用服务 systemId:{} systemName:{} 注册结果:{}", systemId, systemName, resultStr);
if(!result.getCode().equals("0000") && result.getCode().equals("0003")){
throw new GatewayException("注册应用服务异常 [systemId:" + systemId + "] 、[systemRegistry:" + systemRegistry + "]");
}
}
/**
* 注册接口
* @param address
* @param systemId
* @param interfaceId
* @param interfaceName
* @param interfaceVersion
*/
public void doRegisterApplicationInterface(String address,String systemId,
String interfaceId,
String interfaceName,
String interfaceVersion){
Map<String,Object>paramMap=new HashMap<>();
paramMap.put("systemId",systemId);
paramMap.put("interfaceId",interfaceId);
paramMap.put("interfaceName",interfaceName);
paramMap.put("interfaceVersion",interfaceVersion);
String resultStr;
try {
resultStr=HttpUtil.post(address+"/wg/admin/register/registerApplicationInterface",paramMap,1200);
}catch (Exception e){
logger.error("应用服务接口注册异常,链接资源不可用:{}", address + "/wg/admin/register/registerApplicationInterface");
throw e;
}
Result<Boolean> result = JSON.parseObject(resultStr, new TypeReference<Result<Boolean>>() {
});
logger.info("向网关中心注册应用接口服务 systemId:{} interfaceId:{} interfaceName:{} 注册结果:{}", systemId, interfaceId, interfaceName, resultStr);
if (!"0000".equals(result.getCode()) && !"0003".equals(result.getCode()))
throw new GatewayException("向网关中心注册应用接口服务异常 [systemId:" + systemId + "] 、[interfaceId:" + interfaceId + "]");
}
/**
* 注册接口的具体方法
* @param address
* @param systemId
* @param interfaceId
* @param methodId
* @param methodName
* @param parameterType
* @param uri
* @param httpCommandType
* @param auth
*/
public void doRegisterApplicationInterfaceMethod(String address,String systemId,
String interfaceId,
String methodId,
String methodName,
String parameterType,
String uri,
String httpCommandType,
Integer auth){
Map<String,Object>paramMap=new HashMap<>();
paramMap.put("systemId", systemId);
paramMap.put("interfaceId", interfaceId);
paramMap.put("methodId", methodId);
paramMap.put("methodName", methodName);
paramMap.put("parameterType", parameterType);
paramMap.put("uri", uri);
paramMap.put("httpCommandType", httpCommandType);
paramMap.put("auth", auth);
String resultStr;
try {
resultStr=HttpUtil.post(address+"/wg/admin/register/registerApplicationInterfaceMethod",paramMap,1200);
}catch (Exception e){
logger.error("应用服务接口注册方法异常,链接资源不可用:{}", address + "/wg/admin/register/registerApplicationInterfaceMethod");
throw e;
}
Result<Boolean> result = JSON.parseObject(resultStr, new TypeReference<Result<Boolean>>() {
});
logger.info("向网关中心注册应用接口方法服务 systemId:{} interfaceId:{} methodId:{} 注册结果:{}", systemId, interfaceId, methodId, resultStr);
if (!"0000".equals(result.getCode()) && !"0003".equals(result.getCode()))
throw new GatewayException("向网关中心注册应用接口方法服务异常 [systemId:" + systemId + "] 、[interfaceId:" + interfaceId + "]、[methodId:]" + methodId + "]");
}
}
可以看到上述注册方法的大致思路几乎与api-gateway-assist中的网关通信服务注册几乎一致
都需要对应的网关注册中心服务注册的参数以及网关中心的地址。
在有了这个服务之后,我们需要利用spring 实现的Java SPI机制将这个服务构造为bean容器以方便应用使用,即GatewaySDKAutoConfig.java
package com.zshunbao.gateway.sdk.config;
import com.zshunbao.gateway.sdk.application.GatewaySDKApplication;
import com.zshunbao.gateway.sdk.domain.service.GatewayCenterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @program: api-gateway-sdk
* @ClassName GatewaySDKAutoConfig
* @description: 网关sdk配置服务
* @author: zs宝
* @create: 2025-08-29 16:25
* @Version 1.0
**/
@Configuration
@EnableConfigurationProperties(GatewaySDKServiceProperties.class)
public class GatewaySDKAutoConfig {
private Logger logger = LoggerFactory.getLogger(GatewaySDKAutoConfig.class);
@Bean
public GatewayCenterService gatewayCenterService(){
return new GatewayCenterService();
}
@Bean
public GatewaySDKApplication gatewaySDKApplication(GatewaySDKServiceProperties properties,GatewayCenterService gatewayCenterService) {
logger.info("构建 GatewaySDKApplication bean");
return new GatewaySDKApplication(properties,gatewayCenterService);
}
}
利用Java spi的机制构造了服务和应用的bean
最后,我们要在上一章的应用中,一旦需要的信息收集完成,就调用对应的服务方法进行注册GatewaySDKApplication
package com.zshunbao.gateway.sdk.application;
import com.alibaba.fastjson.JSON;
import com.zshunbao.gateway.sdk.GatewayException;
import com.zshunbao.gateway.sdk.annotation.ApiProducerClazz;
import com.zshunbao.gateway.sdk.annotation.ApiProducerMethod;
import com.zshunbao.gateway.sdk.config.GatewaySDKServiceProperties;
import com.zshunbao.gateway.sdk.domain.service.GatewayCenterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.lang.reflect.Method;
/**
* @program: api-gateway-sdk
* @ClassName GatewaySDKApplication
* @description: 应用服务注册
* @author: zs宝
* @create: 2025-08-29 16:24
* @Version 1.0
**/
public class GatewaySDKApplication implements BeanPostProcessor {
private Logger logger = LoggerFactory.getLogger(GatewaySDKApplication.class);
private GatewaySDKServiceProperties properties;
private GatewayCenterService gatewayCenterService;
public GatewaySDKApplication(GatewaySDKServiceProperties properties, GatewayCenterService gatewayCenterService) {
this.properties = properties;
this.gatewayCenterService = gatewayCenterService;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//先查看bean中是否有网关专门标注接口的注解
ApiProducerClazz apiProducerClazz = bean.getClass().getAnnotation(ApiProducerClazz.class);
//如果没有相关注解,直接返回bean即可
if(apiProducerClazz==null){
return bean;
}
//注册系统信息
logger.info("\n应用注册:系统信息 \nsystemId: {} \nsystemName: {} \nsystemType: {} \nsystemRegistry: {}", properties.getSystemId(), properties.getSystemName(), "RPC", properties.getSystemRegistry());
gatewayCenterService.doRegisterApplication(properties.getAddress(),
properties.getSystemId(),
properties.getSystemName(),
"RPC",
properties.getSystemRegistry());
//注册接口信息
Class<?>[] interfaces = bean.getClass().getInterfaces();
if(interfaces.length!=1){
throw new GatewayException(bean.getClass().getName() + "interfaces not one this is " + JSON.toJSONString(interfaces));
}
String interfaceId = interfaces[0].getName();
logger.info("\n应用注册:接口信息 \nsystemId: {} \ninterfaceId: {} \ninterfaceName: {} \ninterfaceVersion: {}", properties.getSystemId(), interfaceId, apiProducerClazz.interfaceName(), apiProducerClazz.interfaceVersion());
gatewayCenterService.doRegisterApplicationInterface(properties.getAddress(),
properties.getSystemId(),
interfaceId,
apiProducerClazz.interfaceName(),
apiProducerClazz.interfaceVersion());
//有网关标注在接口上的注解,那么就一定有相应的标注方法的接口,接下来,我们要将其找到,取出对应的信息
//先从字节码中拿到所有方法
Method[] methods = bean.getClass().getMethods();
//挨个遍历方法寻找有无标注网关在方法上的注解
for(Method method:methods){
ApiProducerMethod apiProducerMethod = method.getAnnotation(ApiProducerMethod.class);
//若没有,则直接下一个
if(apiProducerMethod==null){
continue;
}
//如果有,提取这个方法的相关信息
//但其实这个方法的相关信息,我们在注解中就已近标注完成,只需要从注解中拿就可以了
//但是我们在网关后续利用对RPC进行泛化调用的时候,还需要一个方法中的参数类型信息,这个是没有在注解中标注的
//需要我们动态提取
Class<?>[] parameterTypes = method.getParameterTypes();
StringBuilder parameters = new StringBuilder();
for(Class<?> clazz:parameterTypes){
//将每个参数类型名用字符串存储,中间用","隔开
parameters.append(clazz.getName()).append(",");
}
//将收集到的参数类型名字符串,根据","做拆分(由于最后多了一个","号)
String parameterType = parameters.toString().substring(0, parameters.toString().lastIndexOf(","));
//方法信息
logger.info(
"\n应用注册:方法信息 \nsystemId: {} \ninterfaceId: {} \nmethodId: {} \nmethodName: {} \nparameterType: {} \nuri: {} \nhttpCommandType: {} \nauth: {}",
properties.getSystemId(),
bean.getClass().getName(),
method.getName(),
apiProducerMethod.methodName(),
parameterType,
apiProducerMethod.uri(),
apiProducerMethod.httpCommandType(),
apiProducerMethod.auth()
);
gatewayCenterService.doRegisterApplicationInterfaceMethod(properties.getAddress(),
properties.getSystemId(),
interfaceId,
method.getName(),
apiProducerMethod.methodName(),
parameterType,
apiProducerMethod.uri(),
apiProducerMethod.httpCommandType(),
apiProducerMethod.auth());
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
分别在系统,接口,方法的信息收集完成后,调用了对应的注册服务
测试
最后,这次测试我们可以将我们的组件放于一个真正的PRC服务上进行使用,如下,这个测试项目来源于 api-gateway-test-provider
package cn.bugstack.gateway.interfaces;
import cn.bugstack.gateway.rpc.IActivityBooth;
import cn.bugstack.gateway.rpc.dto.XReq;
import com.alibaba.fastjson.JSON;
import com.zshunbao.gateway.sdk.annotation.ApiProducerClazz;
import com.zshunbao.gateway.sdk.annotation.ApiProducerMethod;
import org.apache.dubbo.config.annotation.Service;
// 前面章节不涉及到上报服务,需要把以下配置注释掉 ApiProducerClazz、ApiProducerMethod 去掉。
@Service(version = "1.0.0")
@ApiProducerClazz(interfaceName = "活动服务", interfaceVersion = "1.0.0")
public class ActivityBooth implements IActivityBooth {
@Override
@ApiProducerMethod(methodName = "探活方法", uri = "/wg/activity/sayHi", httpCommandType = "GET", auth = 0)
public String sayHi(String str) {
return "hi " + str + " by api-gateway-test-provider";
}
@Override
@ApiProducerMethod(methodName = "插入方法", uri = "/wg/activity/insert", httpCommandType = "POST", auth = 1)
public String insert(XReq req) {
return "hi " + JSON.toJSONString(req) + " by api-gateway-test-provider";
}
@Override
@ApiProducerMethod(methodName = "测试方法", uri = "/wg/activity/test", httpCommandType = "POST", auth = 0)
public String test(String str, XReq req) {
return "hi " + str + JSON.toJSONString(req) + " by api-gateway-test-provider";
}
}
注意测试项目的依赖要换成我们自己的
<dependency>
<groupId>com.zshunbao.gateway</groupId>
<artifactId>api-gateway-sdk-02</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
然后在我们启动测试项目之前还要做一些准备
首先是清空 api-gateway-center中有关服务端的数据库表:application_interface,application_interface_method,application_system。
其次我们在第19章中,我们当时由于部署到docker会自动分配IP地址,因此我们在api-gateway-core的netty服务端对于地址端口的绑定代码做了修改,但是由于我们本机使用的校园网,每一次重启电脑都会的IP地址都会改变,这意味每次重启本机都需要根据IP地址进行重新部署(我们是在windows上安装了docker,没有部署到服务器上去),因此这里我们还是直接在本地将多个模块进行联调。由于是在本地,因此第19章中修改的netty服务端通信的代码需要修改,加上IP地址的绑定,即
GatewaySocketServer.java
/**
* @program: api-gateway-core
* @ClassName GatewaySocketServer
* @description: 网关会话服务端
* @author: zs宝
* @create: 2025-07-25 16:48
* @Version 1.0
**/
package com.zshunbao.gateway.core.socket;
import com.zshunbao.gateway.core.session.Configuration;
import com.zshunbao.gateway.core.session.defaults.DefaultGatewaySessionFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.concurrent.Callable;
//实现异步调用接口,使整个会话服务端的过程异步建立
public class GatewaySocketServer implements Callable<Channel> {
private final Logger logger = LoggerFactory.getLogger(GatewaySocketServer.class);
private EventLoopGroup boss;
private EventLoopGroup worker;
private Channel channel;
private DefaultGatewaySessionFactory gatewaySessionFactory;
private final Configuration configuration;
public GatewaySocketServer(Configuration configuration,DefaultGatewaySessionFactory gatewaySessionFactory) {
this.gatewaySessionFactory = gatewaySessionFactory;
this.configuration = configuration;
this.initEventLoopGroup();
}
private void initEventLoopGroup() {
boss=new NioEventLoopGroup(configuration.getBossNThreads());
worker=new NioEventLoopGroup(configuration.getWorkNThreads());
}
@Override
public Channel call() throws Exception {
ChannelFuture channelFuture=null;
try {
ServerBootstrap b=new ServerBootstrap();
b.group(boss,worker)
.channel(NioServerSocketChannel.class)
//设置 TCP 最大连接等待队列
.option(ChannelOption.SO_BACKLOG,128)
.childHandler(new SessionChannelInitializer(gatewaySessionFactory,configuration));
//InetSocketAddress 是 Java 用于表示“IP + 端口”的地址对象
//syncUninterruptibly() 会阻塞直到启动完成(不抛出异常)
// Docker 容器部署会自动分配IP,所以我们只设定端口即可。
channelFuture = b.bind(new InetSocketAddress(configuration.getHostName(), configuration.getPort())).syncUninterruptibly();
//channelFuture = b.bind(configuration.getPort()).syncUninterruptibly();
this.channel=channelFuture.channel();
}catch (Exception e){
logger.error("socket server start error.", e);
}finally {
if(null!=channelFuture && channelFuture.isSuccess()){
logger.info("socket server start done.");
}else{
logger.error("socket server start error.");
}
}
return channel;
}
}
接着本地重新clean,install
api-gateway-core
,api-gateway-assist
这两个模块。最后根据本地情况,在api-gateway-test-provider这个测试项目的application.yml中添加sdk需要读取的系统信息
api-gateway-sdk:
address: http://localhost:8001 # 注册中心;从这里获取接口信息以及完成注册网关操作
systemId: api-gateway-test-provider
systemName: 网关sdk测试工程
systemRegistry: zookeeper://127.0.0.1:2181
启动api-gateway-center,启动api-gateway-assist-00(api-gateway-assist的使用工程),启动测试工程api-gateway-test-provider
网关注册中心日志及数据库
然后我们打开ApiPost,进行测试
bug
在上面测试中,我们包裹一个bug,这个bug数据库什么的信息都正确注册,但是就是在ApiPost进行方法调用的时候报错
我们最开始是以为api-gateway-core中的代码出现了问题,但是仔细想想,我们对于其中的代码只修改了一行,切着修改后的一行在第19章未部署之前一直测试争取,可以被调用,那就说明这不是出现在api-gateway-core代码上的问题,而应该是其它问题。
我们仔细梳理一下,对应http请求到达网关要想进行远程泛化调用走的是api-gateway-core的逻辑,但是这个网关通信模块是被集成到了其starter api-gateway-assist中去了的,因此,我们打开api-gateway-assist的日志,发现
在日志中表示,当网关通信服务向网管注册中心进行配置拉取的时候,拉回来一个空集,
我们仔细查看网管注册中心有关网关服务拉取配置的方法,应该说在网关注册中心的代码我么从来没有修改过,且之前测试都毫无问题,所以逻辑上得处理肯定没有问题,但是这里有一个点就是拉取配置是首先从gateway_distribution表中去进行查询,根据匹配查询结果,再去找服务端相关的配置,然后我们打开gateway_distribution表
发现其中的信息网关服务对应的远程服务为api-gateway-test,与我们测试项目中写的api-gateway-test-provider不同,所以最后查询到的远程服务集合为空,所以调用报错
这里我们修改gateway_distribution表的对应配置即可