十二章 网关注册服务接口领域服务实现

在网关注册中心提供服务接口的注册服务,便于后续服务启动后向网关中心注册信息。

设计

12-设计

十一章我们实现了网关的服务注册,接下来我们需要提供用于 RPC 服务注册的接口。在一个 RPC 的服务注册中,需要包括三个部分:RPC 服务系统信息、这个服务下的所有接口信息、接口下的所有方法信息。分批的向网关中心完成注册操作。所有信息注册完成后,才能让网关算力服务 core 与 RPC 的接口进行关联,也就是把 RPC 接口分配到处理的网关服务上去。

  • 网关中心维护 RPC 服务注册的库表:

    • application_ system 维护 RPC 服务的系统信息,如系统名称、注册中心等。
    • application_ interface 维护每个 RPC 服务下所有的接口信息,如接口地址、接口名称等。
    • application_interface_method 维护 RPC 服务下每个接口下的方法信息,如方法名称、入参类型、HTTP 请求类型、uri 以及是否鉴权等。
  • 那么本章需要开发这样一块的功能接口,允许外部通过 HTTP 接口进行注册 RPC 服务接口。

实现

【infrastructure层】接口注册仓储服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package cn.ray.gateway.center.infrastructure.repository;

import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceMethodVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationSystemVO;
import cn.ray.gateway.center.domain.register.repository.IRegisterManageRepository;
import cn.ray.gateway.center.infrastructure.dao.IApplicationInterfaceDao;
import cn.ray.gateway.center.infrastructure.dao.IApplicationInterfaceMethodDao;
import cn.ray.gateway.center.infrastructure.dao.IApplicationSystemDao;
import cn.ray.gateway.center.infrastructure.pojo.ApplicationInterface;
import cn.ray.gateway.center.infrastructure.pojo.ApplicationInterfaceMethod;
import cn.ray.gateway.center.infrastructure.pojo.ApplicationSystem;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
* @author Ray
* @date 2023/5/24 15:06
* @description 接口注册仓储服务
*/
@Repository
public class RegisterManageRepository implements IRegisterManageRepository {

@Resource
private IApplicationSystemDao applicationSystemDao;

@Resource
private IApplicationInterfaceDao applicationInterfaceDao;

@Resource
private IApplicationInterfaceMethodDao applicationInterfaceMethodDao;

@Override
public void registerApplication(ApplicationSystemVO applicationSystemVO) {
ApplicationSystem applicationSystem = new ApplicationSystem();
applicationSystem.setSystemId(applicationSystemVO.getSystemId());
applicationSystem.setSystemName(applicationSystemVO.getSystemName());
applicationSystem.setSystemType(applicationSystemVO.getSystemType());
applicationSystem.setSystemRegistry(applicationSystemVO.getSystemRegistry());
applicationSystemDao.insert(applicationSystem);
}

@Override
public void registerApplicationInterface(ApplicationInterfaceVO applicationInterfaceVO) {
ApplicationInterface applicationInterface = new ApplicationInterface();
applicationInterface.setSystemId(applicationInterfaceVO.getSystemId());
applicationInterface.setInterfaceId(applicationInterfaceVO.getInterfaceId());
applicationInterface.setInterfaceName(applicationInterfaceVO.getInterfaceName());
applicationInterface.setInterfaceVersion(applicationInterfaceVO.getInterfaceVersion());
applicationInterfaceDao.insert(applicationInterface);
}

@Override
public void registerApplicationInterfaceMethod(ApplicationInterfaceMethodVO applicationInterfaceMethodVO) {
ApplicationInterfaceMethod applicationInterfaceMethod = new ApplicationInterfaceMethod();
applicationInterfaceMethod.setSystemId(applicationInterfaceMethodVO.getSystemId());
applicationInterfaceMethod.setInterfaceId(applicationInterfaceMethodVO.getInterfaceId());
applicationInterfaceMethod.setMethodId(applicationInterfaceMethodVO.getMethodId());
applicationInterfaceMethod.setMethodName(applicationInterfaceMethodVO.getMethodName());
applicationInterfaceMethod.setParameterType(applicationInterfaceMethodVO.getParameterType());
applicationInterfaceMethod.setUri(applicationInterfaceMethodVO.getUri());
applicationInterfaceMethod.setHttpCommandType(applicationInterfaceMethodVO.getHttpCommandType());
applicationInterfaceMethod.setAuth(applicationInterfaceMethodVO.getAuth());
applicationInterfaceMethodDao.insert(applicationInterfaceMethod);
}
}

【domain层】接口注册服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package cn.ray.gateway.center.domain.register.service;

import cn.ray.gateway.center.application.IRegisterManageService;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceMethodVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationSystemVO;
import cn.ray.gateway.center.domain.register.repository.IRegisterManageRepository;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
* @author Ray
* @date 2023/5/24 14:59
* @description 接口注册服务
*/
@Service
public class RegisterManageService implements IRegisterManageService {

@Resource
private IRegisterManageRepository registerManageRepository;

@Override
public void registerApplication(ApplicationSystemVO applicationSystemVO) {
registerManageRepository.registerApplication(applicationSystemVO);
}

@Override
public void registerApplicationInterface(ApplicationInterfaceVO applicationInterfaceVO) {
registerManageRepository.registerApplicationInterface(applicationInterfaceVO);
}

@Override
public void registerApplicationInterfaceMethod(ApplicationInterfaceMethodVO applicationInterfaceMethodVO) {
registerManageRepository.registerApplicationInterfaceMethod(applicationInterfaceMethodVO);
}
}

【interfaces层】RPC 服务注册管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package cn.ray.gateway.center.interfaces;

import cn.ray.gateway.center.application.IRegisterManageService;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceMethodVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationSystemVO;
import cn.ray.gateway.center.infrastructure.common.Constants;
import cn.ray.gateway.center.infrastructure.common.ResponseCode;
import cn.ray.gateway.center.infrastructure.common.Result;
import cn.ray.gateway.center.infrastructure.pojo.ApplicationSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
* @author Ray
* @date 2023/5/24 15:17
* @description RPC 服务注册管理
*/
@RestController
@RequestMapping("/wg/admin/register")
public class RPCRegisterManage {

private Logger logger = LoggerFactory.getLogger(RPCRegisterManage.class);

@Resource
private IRegisterManageService registerManageService;

@PostMapping(value = "registerApplication", produces = "application/json;charset=utf-8")
public Result<Boolean> registerApplication(@RequestParam String systemId,
@RequestParam String systemName,
@RequestParam String systemType,
@RequestParam String systemRegistry) {
try {
logger.info("注册应用服务 systemId:{}", systemId);
ApplicationSystemVO applicationSystemVO = new ApplicationSystemVO();
applicationSystemVO.setSystemId(systemId);
applicationSystemVO.setSystemName(systemName);
applicationSystemVO.setSystemType(systemType);
applicationSystemVO.setSystemRegistry(systemRegistry);
registerManageService.registerApplication(applicationSystemVO);
return new Result<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getInfo(), true);
} catch (DuplicateKeyException e) {
logger.warn("注册应用服务重复 systemId:{}", systemId, e);
return new Result<>(ResponseCode.INDEX_DUP.getCode(), e.getMessage(), true);
} catch (Exception e) {
logger.error("注册应用服务失败 systemId:{}", systemId, e);
return new Result<>(ResponseCode.UN_ERROR.getCode(), e.getMessage(), false);
}
}

@PostMapping(value = "registerApplicationInterface", produces = "application/json;charset=utf-8")
public Result<Boolean> registerApplicationInterface(@RequestParam String systemId,
@RequestParam String interfaceId,
@RequestParam String interfaceName,
@RequestParam String interfaceVersion) {
try {
logger.info("注册应用接口 systemId:{} interfaceId:{}", systemId, interfaceId);
ApplicationInterfaceVO applicationInterfaceVO = new ApplicationInterfaceVO();
applicationInterfaceVO.setSystemId(systemId);
applicationInterfaceVO.setInterfaceId(interfaceId);
applicationInterfaceVO.setInterfaceName(interfaceName);
applicationInterfaceVO.setInterfaceVersion(interfaceVersion);
registerManageService.registerApplicationInterface(applicationInterfaceVO);
return new Result<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getInfo(), true);
} catch (DuplicateKeyException e) {
logger.warn("注册应用接口重复 systemId:{} interfaceId:{}", systemId, interfaceId);
return new Result<>(ResponseCode.INDEX_DUP.getCode(), e.getMessage(), true);
} catch (Exception e) {
logger.error("注册应用接口失败 systemId:{} interfaceId:{}", systemId, interfaceId, e);
return new Result<>(ResponseCode.UN_ERROR.getCode(), e.getMessage(), false);
}
}

@PostMapping(value = "registerApplicationInterfaceMethod", produces = "application/json;charset=utf-8")
public Result<Boolean> registerApplicationInterfaceMethod(@RequestParam String systemId,
@RequestParam String interfaceId,
@RequestParam String methodId,
@RequestParam String methodName,
@RequestParam String parameterType,
@RequestParam String uri,
@RequestParam String httpCommandType,
@RequestParam Integer auth) {
try {
logger.info("注册应用接口方法 systemId:{} interfaceId:{} methodId:{}", systemId, interfaceId, methodId);
ApplicationInterfaceMethodVO applicationInterfaceMethodVO = new ApplicationInterfaceMethodVO();
applicationInterfaceMethodVO.setSystemId(systemId);
applicationInterfaceMethodVO.setInterfaceId(interfaceId);
applicationInterfaceMethodVO.setMethodId(methodId);
applicationInterfaceMethodVO.setMethodName(methodName);
applicationInterfaceMethodVO.setParameterType(parameterType);
applicationInterfaceMethodVO.setUri(uri);
applicationInterfaceMethodVO.setHttpCommandType(httpCommandType);
applicationInterfaceMethodVO.setAuth(auth);
registerManageService.registerApplicationInterfaceMethod(applicationInterfaceMethodVO);
return new Result<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getInfo(), true);
} catch (DuplicateKeyException e) {
logger.warn("注册应用接口方法重复 systemId:{} interfaceId:{} methodId:{}", systemId, interfaceId, methodId);
return new Result<>(ResponseCode.INDEX_DUP.getCode(), e.getMessage(), true);
} catch (Exception e) {
logger.error("注册应用接口方法 systemId:{} interfaceId:{} methodId:{}", systemId, interfaceId, methodId, e);
return new Result<>(ResponseCode.UN_ERROR.getCode(), e.getMessage(), false);
}
}
}

测试

ApiTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package cn.ray.gateway.center.test;

import cn.ray.gateway.center.application.IConfigManageService;
import cn.ray.gateway.center.application.IRegisterManageService;
import cn.ray.gateway.center.domain.manage.model.vo.GatewayServerVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceMethodVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationInterfaceVO;
import cn.ray.gateway.center.domain.register.model.vo.ApplicationSystemVO;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.List;

/**
* @author Ray
* @date 2023/5/21 16:15
* @description
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {

private Logger logger = LoggerFactory.getLogger(ApiTest.class);

@Resource
private IConfigManageService configManageService;

@Resource
private IRegisterManageService registerManageService;

@Test
public void test_queryGatewayServerList() {
List<GatewayServerVO> gatewayServerVOS = configManageService.queryGatewayServerList();
logger.info("测试结果:{}", JSON.toJSONString(gatewayServerVOS));
}

@Test
public void test_registerGatewayServerNode() {
configManageService.registerGatewayServerNode("10001", "api-gateway-g1", "电商支付网关", "127.0.0.196");
configManageService.registerGatewayServerNode("10001", "api-gateway-g2", "电商支付网关", "127.0.0.197");
configManageService.registerGatewayServerNode("10001", "api-gateway-g3", "电商配送网关", "127.0.0.198");
}

@Test
public void test_registerApplication() {
ApplicationSystemVO applicationSystemVO = new ApplicationSystemVO();
applicationSystemVO.setSystemId("api-gateway-test");
applicationSystemVO.setSystemName("网关测试系统");
applicationSystemVO.setSystemType("RPC");
applicationSystemVO.setSystemRegistry("127.0.0.1");
registerManageService.registerApplication(applicationSystemVO);
}

@Test
public void test_registerApplicationInterface() {
ApplicationInterfaceVO applicationInterfaceVO = new ApplicationInterfaceVO();
applicationInterfaceVO.setSystemId("api-gateway-test");
applicationInterfaceVO.setInterfaceId("cn.ray.gateway.rpc.IActivityBooth");
applicationInterfaceVO.setInterfaceName("活动平台");
applicationInterfaceVO.setInterfaceVersion("v1.0.0");
registerManageService.registerApplicationInterface(applicationInterfaceVO);
}

@Test
public void test_registerApplicationInterfaceMethod() {
ApplicationInterfaceMethodVO applicationInterfaceVO01 = new ApplicationInterfaceMethodVO();
applicationInterfaceVO01.setSystemId("api-gateway-test");
applicationInterfaceVO01.setInterfaceId("cn.ray.gateway.rpc.IActivityBooth");
applicationInterfaceVO01.setMethodId("sayHi");
applicationInterfaceVO01.setMethodName("测试方法");
applicationInterfaceVO01.setParameterType("java.lang.String");
applicationInterfaceVO01.setUri("/wg/activity/sayHi");
applicationInterfaceVO01.setHttpCommandType("GET");
applicationInterfaceVO01.setAuth(0);
registerManageService.registerApplicationInterfaceMethod(applicationInterfaceVO01);

ApplicationInterfaceMethodVO applicationInterfaceVO02 = new ApplicationInterfaceMethodVO();
applicationInterfaceVO02.setSystemId("api-gateway-test");
applicationInterfaceVO02.setInterfaceId("cn.ray.gateway.rpc.IActivityBooth");
applicationInterfaceVO02.setMethodId("insert");
applicationInterfaceVO02.setMethodName("插入方法");
applicationInterfaceVO02.setParameterType("cn.ray.gateway.rpc.dto.XReq");
applicationInterfaceVO02.setUri("/wg/activity/insert");
applicationInterfaceVO02.setHttpCommandType("POST");
applicationInterfaceVO02.setAuth(1);
registerManageService.registerApplicationInterfaceMethod(applicationInterfaceVO02);
}
}

测试目的在于单元测试中模拟RPC注册信息,记录信息到数据库。如果注册信息已存在,则会返回幂等。这里可以先做一次查询处理,减少撞库(通过唯一索引查询,如查询结果为null再插入,防止重复注册)。但为了幂等体现,暂时先不做插入数据库的查询处理。

思考

  1. 唯一索引设置
    1. application_system 唯一索引设置为 system_id
    2. application_interface 唯一索引设置为 联合索引(system_id, interface_id)
    3. application_interface_method 唯一索引设置为 联合索引(system_id, interface_id, method_id)
  2. 接囗幂等性
    • 用户对同一操作发起了一次或多次请求的对数据的影响是一致不变的,不会因为多次的请求而产生副作用。
    • 前端重复提交表单、接口超时重写提交请求、消息重写消费等都会导致幂等性问题
  3. 保证接口幂等性的方案
    1. 分布式锁
      1. 设置分布式锁Redission
      2. 占用锁
      3. 执行业务
      4. 释放锁
    2. 乐观锁
      1. 在对应的数据表中多添加一个版本字段version,作为当前数据的版本标识
      2. 每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个更新条件
    3. 设置数据库表唯一索引
      1. 可以规定一些从业务上唯一的字段(比如:身份证号、分布式主键 ID),为这些字段建立一个唯一索引
    4. Redis + token
      1. 客户端调用服务端的生成 token 接口,将生成的 token 作为 key 存入 Redis。需要确保 token 唯一性,可以是序列号、分布式ID、UUID 等。
      2. 同时将 token 返回给客户端,客户端发起请求时将 token 作为请求头一起提交。
      3. 服务端接收请求后从 headers 中取出 token,并根据 token 查询Redis 中该key是否存在。
      4. 如果存在,就删除该 key (token),执行业务逻辑。
      5. 如果不存在,则说明客户端是连续请求,因为一个 token 只能被使用一次。
      6. 感觉这个增加了系统的复杂性,需要考虑 Redis 是否可用,以及每次请求都需要从服务端拿到token,性能也有所损耗?用户体验可能也不太好,等待请求返回结果比之前的几种方案都要慢?