1. 依赖和中间件

  • open-jdk 17.0.7
  • graalvm 22.3.2
  • Maven 3.6.3
  • nacos 2.2.3
  • Redis 6.0
  • Mysql 8.0
  • Springboot 3.0.6
  • SpringCloud Alibaba 2022.0.0.0
  • mybatis-plus 3.5.3
  • Seata 1.7.0
  • Netty 4.1.99
  • Disruptor 4.0.0

2. 模块总览

模块名 中文名 备注
native-cloud-user-system 交易账户系统 用于维护证券交易账户数据,负责客户开户、适当性、风险评估等常规运营操作。
native-cloud-qms-system 交易额度系统 用于维护日间交易信用业务头寸、申报流水号区间等交易关键辅助信息。
native-cloud-compact 信用合约系统 用于维护日间信用交易产生的合约信息,提供对合约进行清算、展期、结息罚息等操作。
native-cloud-credit-trade 信用交易系统 提供日间融资融券业务入口,供信用客户进行融资融券日间交易。
native-cloud-gateway 交易网关 所有日间交易、运营业务的入口,后续将建设包含根据具体账户定位至具体交易核心的功能,实现多分片定位。
native-cloud-system-api 交易交互组件 包含各交易子系统相互调用的api、数据载体。
native-cloud-common 交易通用组件 包含交易子系统公用工具类、配置类等关键可插拔功能的启动项。

2.1 模块简介

todo…

3. 部署事项

3.1 部署命令:

  • 建议每个模块都在idea中添加两个启动配置项

    • Native-cloud-xxx-debug:用于本地idea调试功能使用
    • Native-cloud-xxx-preCompile:用于Native打包镜像,需要添加VM参数命令:
      • -agentlib:native-image-agent=config-output-dir=/你的idea workspace目录/native-cloud-system/native-cloud-xxx-system/src/main/resources/META-INF/native-image/native-cloud-xxx-system
      • 添加VM参数启动后,随机通过调试请求工具(如postman)访问该模块子系统几个功能(也可以不做这一步,只是为了确保native-image打包时所有代码组件均能够被链接),然后停止该模块,会在该模块的src/main/resources/META-INF/native-image/native-cloud-xxx-system目录下生成打包所需的json配置文件,这些文件中指定了源代码中通过反射、代理等途径创建的对象,只有成功生成这些配置文件,native-image打包成本地镜像才不会报错。
    • native-image执行打包命令:
      • mvn -e -Pnative -DskipTests=true native:compile
  • 建议在部署模块之前,先通过mvn clean install将本地所有模块的改动都打入本地maven repo中去。

  • nacos本地启动命令:

    • sudo sh ./startup.sh -m standalone
      
  • seata本地启动命令:

    • sh ./seata-server.sh
      
    • 同时注意,seata的端口配置默认为7091,如果要更改默认启动端口,要在seata/conf/application.yml中打开如下配置:

      • server:
          port: 8091
        

4. 部署常见问题

  • 镜像打包成功,但Mybatis相关类报错:ClassNotFound

    • 检查该模块启动类上是否有注解@EnableNativeHints,只有引入了该注解,才能让mybatis-plus在打包成本地镜像后正常工作。
  • 镜像打包成功,但Jwt相关类报错:DefaultJwtParser:ClassNotFound

    • 检查该模块对应打包配置目录下的proxy-config.json文件中有没有指定DefaultJwtParser,若没有指定则说明该类和其相关的模块均未被链接,需要使用对应模块的preCompile启动项启动该类,调用一次流程中使用了DefaultJwtParser的功能,再停止该模块,能够使proxy-config文件写入该类,再重新执行打包命令即可。
  • 执行native-image打包命令过程中报错:nosuchmethoderror

    • 如果是在一个之前能够正常工作的class中添加了新方法,那么需要让maven重新生成该类的安装包,执行mvn clean install即可。
    • 如果是javabean中不存在getXXX或setXXX方法,检查是否使用lombok的@data、@AllArgsConstructor等注解,另外,不建议在项目中使用lombok。
  • gateway无法转发请求到正确的子模块:

    • 检查gateway下的配置文件routes节点中是否已添加子模块的路由。
    • 如果是新加的模块,由于AOT提前编译的局限性,需要在gateway目录下增加一个对应模块的OpenFeignClient,这样才能确保OpenFeign相关的配置类提前被链接。
  • mybatis-plus使用lambda查询方式报错:

  • 目前mybatis官方暂未出补丁修复AOT模式下lambda查询失效的问题,虽然github上issue已经存在,建议避开wrapper查询,自行在mapper中增加查询方法进行业务上的适配。

  • 打包成native-image执行时,插入到mysql的中文字段乱码:

    • show full columns from .,确保数据库表字段的字符集是UTF8编码。
    • 检查配置文件中jdbc-url配置,由于使用了hikariCP作为数据源,characterEncoding=uft8才能起到使用UTF8中文字符集的作用。
  • native镜像RPC报错: java.lang.IllegalArgumentException: Object of class [org.springframework.context.support.GenericApplicationContext] must be an instance of interface org.springframework.context.annotation.AnnotationConfigRegistry] with root cause

    • 在对应的调用方模块配置文件中加入openfeign相关AOT配置,以qms-system调用user-system为例,需在qms-system模块的配置文件中加入如下内容:

      • spring:
          cloud:
            refresh:
              enabled: false
            openfeign:
              # 支持AOT编译openfeign调用配置项 begin
              client:
                refresh-enabled: false
                config:
                  user-system:
                    urls: http://127.0.0.1:8880
              lazy-attributes-resolution: false
            loadbalancer:
              ribbon:
                enabled: false
              nacos:
                enabled: true
              eager-load:
                clients:
                  - user-system
              cache:
                enabled: true
              # 支持AOT编译openfeign调用配置项 end
        
      • 如果下游服务(上述例子user-system)为集群部署,由于AOT需要提前加载指定订阅服务的列表,所以user-system.urls中需要配置集群中所有节点的ip:port。

  • 全局错误处理失败,报错:No acceptable representation

    • 检查返回的result实现类是否有成员变量的get\set方法。
  • 集成seata之后,使用nacos作为配置中心,控制台报错,nacos-server端登录错误,完整错误如下:

    2023-12-09T13:14:38.423+08:00 ERROR 2971 --- [s.client.Worker] c.a.n.c.a.i.process.HttpLoginProcessor   : login failed: {"code":500,"message":"caused: Cannot invoke \"com.alibaba.nacos.plugin.auth.impl.jwt.NacosJwtParser.getExpireTimeInSeconds(String)\" because \"this.jwtParser\" is null;","header":{"header":{"Accept-Charset":"UTF-8","Authorization":"Bearer","Connection":"close","Content-Length":"142","Content-Security-Policy":"script-src 'self'","Content-Type":"text/html;charset=UTF-8","Date":"Sat, 09 Dec 2023 05:14:38 GMT","Vary":"Access-Control-Request-Headers"},"originalResponseHeader":{"Authorization":["Bearer"],"Connection":["close"],"Content-Length":["142"],"Content-Security-Policy":["script-src 'self'"],"Content-Type":["text/html;charset=UTF-8"],"Date":["Sat, 09 Dec 2023 05:14:38 GMT"],"Vary":["Access-Control-Request-Headers","Access-Control-Request-Method","Origin"]},"charset":"UTF-8"}}
    
    
    • 原因是nacos使用的2.2.3版本默认取消了登录验证,所以注释掉工程对应的配置文件中的username和password配置项,需要注意的是seata.config.nacos和seata.config.registry均需要注释用户名和密码。
  • 集成seata,使用nacos做配置中心,控制台报错:service.vgroupMapping.native-cloud-system-group配置为空:

    io.seata.config.exception.ConfigNotFoundException: service.vgroupMapping.native-cloud-system-group configuration item is required
    
    • 先检查工程模块下seata.config.nacos.group的值,发现配置项中定义去group为SEATA_GROUP的组中去获取配置:

      seata:
        tx-service-group: native-cloud-system-group
        service:
          tx-service-group: native-cloud-system-group
          vgroup-mapping:
            native-cloud-system-group: native
        config:
          type: nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
      
    • 再检查nacos dashboard中,namespace为public下(因为seata.config.nacos.namespace未配置,所以采用默认命名空间)是否添加了DataId为service.vgroupMapping.native-cloud-system-group且group为SEATA_GROUP的配置,如果没有,按照上述的条件添加一条配置,配置的值为:SEATA_GROUP。

  • 集成seata,集群订阅报错:

    [timeoutChecker_1_1] ERROR i.s.c.r.n.NettyClientChannelManager:188 --> no available service found in cluster 'default', please make sure registry config correct and keep your seata server running
    
    • 确认seata-server端配置文件中seata.registry.nacos.cluster的值,和客户端从nacos拉取的配置文件中dataID为service.vgroupMapping.native-cloud-system-group中的值相等。
  • @GlobalTransactional,TCC模式只执行一阶段try的逻辑,不执行二阶段提交:

    • 断点打入GlobalTransactionalInterceptor中invoke方法,重点调试GlobalTransactional注解获取的过程:

      final GlobalTransactional globalTransactionalAnnotation = getAnnotation(method, targetClass, GlobalTransactional.class);
      

      查看method变量的方法名是否和业务代码中GlobalTransactional注解标记的方法名一致。

  • 集成seata,客户端启动后报错:RM can not reconnect server

    • 用下面的命令指定ip和端口重启seata-server:

      ./seata-server.sh -h 127.0.0.1 -p 8091
      
  • 集成seata后,分布式事务执行过程中报错:

    java.sql.SQLSyntaxErrorException: Table 'credit.undo_log' doesn't exist
    
    • 发生在向数据库表进行insert记录的动作中,检查执行sql insert操作的方法是否已实现本地TccService,由于使用了seata代理了Datasource,不实现TccService情况下则会在插入时使用mysql默认的undolog表记录日志,而代理后的数据源无法获取mysql自带undolog,将执行insert语句的方法实现TccService即可,可参考下面委托表对应的TccService:

      @LocalTCC
      public interface CreditEntrustTccService {
      
          @TwoPhaseBusinessAction(name = "generateCreditEntrust", commitMethod = "generateCreditEntrustCommit", rollbackMethod = "generateCreditEntrustRollback")
          Long generateCreditEntrust(@BusinessActionContextParameter(paramName = "creditEntrust") CreditEntrust creditEntrust,
                                   @BusinessActionContextParameter(paramName = "entrustProperty") EntrustProperty entrustProperty);
      
          Boolean generateCreditEntrustCommit(BusinessActionContext businessActionContext);
      
          Boolean generateCreditEntrustRollback(BusinessActionContext businessActionContext);
      }
      
  • OpenFeign报错feign.RetryableException: too many bytes written executing

    • RPC传对象的时候造成请求体序列化后的字节数过多,需要取消openFeign拦截器中从前端请求带来的content-length限制,在项目下找到FeignBasicAuthRequestInterceptor并在遍历请求header循环中加入如下代码:

      if (name.equals("content-length")){
        	continue;
      }
      
  • jdk17 使用annotation processor处理编译期注解,mvn install 注解处理器模块报错:

    服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider com.huang.ast.processor.LemonCompileProcessor not found时抛出异常错误
    
    
    • 确保处理器模块maven的配置文件中加入如下配置项

      		<build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <configuration>
                        	<!-- 此项必须 -->
                          <proc>none</proc>
                          <compilerArgs>
                            	<!-- 下面四项arg也建议加上,这样执行mvn compile命令会加上下面的参数,确保字节码增强所依赖的javac模块不会无法访问-->
                              <arg>--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
                              <arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
                              <arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
                              <arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
                          </compilerArgs>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      
  • jdk17 使用annotation processor处理编译期注解,mvn install 注解处理器模块时代码构建不通过,报如下错误:

    com.sun.tools.javac.api不可见
    com.sun.tools.javac.processing不可见
    com.sun.tools.javac.tree不可见
    com.sun.tools.javac.util不可见
    
    • 由于jdk9 引入了jigsaw特性,原本javac所在的tools.jar被模块化,需要在idea设置中找到 Build,Execution,Deployment –> Compiler –> Java Complier –> override complier parameters per module,确保处理器模块export如下jdk自带模块:

      -parameters
      --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
      --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
      --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
      --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
      
  • 使用注解处理器的业务模块 执行mvn compile/install 报错:

    class com.huang.ast.processor.LemonCompileProcessor (in unnamed module @0x3ac3f6f) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module @0x3ac3f6f
    
    
    • 使用了idea自带的maven进行打包,所以不会读取配置在pom文件中的compilerArgs,需要手动为idea自带的maven指定解析时可访问的模块,打开settings –> Build,Execution,Deployment –> build tools–> maven –> runner,设置maven运行时的vm option:

      --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
      --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
      --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
      --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
      
    • 同时,建议在业务模块pom文件中加上如下配置,使得命令行打包时能够读取配置项,使得编译期注解处理器能够正常被依赖:

      		<build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <configuration>
                          <target>17</target>
                          <source>17</source>
                          <annotationProcessors>
                              <annotationProcessor>com.huang.ast.processor.LemonCompileProcessor</annotationProcessor>
                          </annotationProcessors>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      
  • build modules或者执行mvn clean compile报如下错误:

    java.lang.AssertionError
    	at jdk.compiler/com.sun.tools.javac.util.Assert.error(Assert.java:155)
    	at jdk.compiler/com.sun.tools.javac.util.Assert.check(Assert.java:46)
    	at jdk.compiler/com.sun.tools.javac.util.Bits.incl(Bits.java:186)
    	at jdk.compiler/com.sun.tools.javac.comp.Flow$AssignAnalyzer.initParam(Flow.java:2232)
    	at jdk.compiler/com.sun.tools.javac.comp.Flow$AssignAnalyzer.visitMethodDef(Flow.java:2156)
    	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:921)
    	at jdk.compiler/com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:49)
    	at jdk.compiler/com.sun.tools.javac.comp.Flow$BaseAnalyzer.scan(Flow.java:444)
    	at jdk.compiler/com.sun.tools.javac.comp.Flow$AssignAnalyzer.scan(Flow.java:1724)
    	at jdk.compiler/com.sun.tools.javac.comp.Flow$AssignAnalyzer.visitClassDef(Flow.java:2098)
    	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:819)
    	at jdk.compiler/com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:49)
    
    • 需要将treeMaker的节点指针定位至当前元素对应语法树的节点指针,在获取当前元素的语法树之后加入如下代码:

      JCTree jcTree = this.trees.getTree(element);
      treeMaker.pos = jcTree.pos;
      
  • 调用inner接口时报错:403 Forbidden

    feign.FeignException$Forbidden: [403] during [GET] to [http://user-system/inner/user/getUserDetail/] [UserClient#getUserDetail(String)]: []
    
    • 原因是feign client发起RPC的时候,如果方法是在path后紧跟入参,比如/inner/user/getUserDetail/{userId},需要入参和对应的innerController完全保持一致,检查InnerController被@PathVariable注解标记的方法入参,在对应的feign client中也存在@PathVariable:

      InnerController:
      @OpenApi
      @GetMapping("/user/getUserDetail/{userId}")
      public T getUserDetail(@PathVariable String userId){
        return  userDetailsService.loadUserByUsername(userId);
      }
      
      Feign Client:
      @FeignClient(name = "user-system",value = "user-system",contextId = "userSystem")
      public interface UserClient {
      
      		... // 其他方法
      
          @GetMapping("/inner/user/getUserDetail/{userId}")
          T getUserDetail(@PathVariable String userId);
      }
      
  • 集成native-cloud-common-auth,通过@EnableSecurityAuthorization开启全局用户鉴权后,在进入controller方法之前,模块应该实现spring-security要求的userServiceImpl,否则请求该子系统任意接口(标记了openApi的白名单接口除外)都会返回403Forbidden错误,可参考交易模块已实现的UserServiceImpl:

    /**
     * @description: 信用交易系统用户权限认证实现
     * @author: huang.zh
     * @create: 2024-09-15 08:09
     **/
    @Service
    @Slf4j
    public class UserDetailServiceImpl implements UserDetailsService {
    
    
        private UserClient userClient;
    
        public UserDetailServiceImpl(@Autowired UserClient userClient) {
            this.userClient = userClient;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 注意此时传入的username为jwtAuthUtil解析的userId
          	// 发起RPC至账户子系统,请求用户的权限等运行时数据
            UserMetadata userMetadata = userClient.getUserDetail(username);
            String userId = userMetadata.getUserId();
          	// 当前系统需要鉴权,将UserMetadata转换为符合Spring-security权限校验体系的实体类User
            List<SimpleGrantedAuthority> authorities = userMetadata.getPermissions().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            return new User(userId,userId,authorities);
        }
    }
    
    • 注意,通过OpenFeign发起RPC请求,至账户子系统请求用户元数据,要求下游账户系统不能直接返回spring security的UserDetails接口及其任意一个实现类,是因为这些实现类没有默认无参构造函数,而OpenFeign解析response时使用jackson进行反序列化,要求响应类实体要有无参构造函数,这里可以使用native-cloud-common-auth模块下的UserMetadata,各上游系统获取到用户元数据后如果需要鉴权,可参考信用交易子系统将UserMetadata转换为符合Spring-security权限校验体系的实体类User:

      /**
       * @description: 用户元数据运行时包装类,维护从账户系统获取的用户数据(userId,permissions...)
       * @author: huang.zh
       * @create: 2024-09-15 20:47
       **/
      public class UserMetadata {
      
          private String userId;
      
          private List<String> permissions;
      
          public UserMetadata() {
          }
      
          public UserMetadata(String userId, List<String> permissions) {
              this.userId = userId;
              this.permissions = permissions;
          }
      
          public String getUserId() {
              return userId;
          }
      
          public void setUserId(String userId) {
              this.userId = userId;
          }
      
          public List<String> getPermissions() {
              return permissions;
          }
      
          public void setPermissions(List<String> permissions) {
              this.permissions = permissions;
          }
      }
      
  • 单元测试中,将 类通过 @component 注解标记,尝试托管给 spring 容器,但单元测试方法报错:

    No qualifying bean of type 'com.huang.buffer.handler.GenerateMessageDataHandler' available
    
    • 这是因为测试类继承自 TestApplication,在 TestApplication 中 通过注解配置了指定单元测试时允许容器托管的类:

      @SpringBootTest(classes = {TestApplication.class, NettyStartListener.class, ServerProperty.class, SocketServer.class})
      

      在 SpringBootTest注解 的 classes 属性中补齐单元测试中需要 Spring容器托管的类即可正常托管对应的 bean:

      @SpringBootTest(classes = {TestApplication.class, NettyStartListener.class, ServerProperty.class, SocketServer.class, GenerateMessageDataHandler.class, MessageMetaDataHandler.class})
      

5.开发日志

日期 内容
2023.07.08 项目基本依赖引入,完成框架搭建。
2023.07.15 账户系统完成基础功能:账户查询、营业部树查询。
2023.07.18 账户系统权限系统表结构建立完成,基于RBAC模型。
2023.07.20 账户系统权限体系编码完成,自测完成,实现用户粒度角色权限筛选与聚合。
2023.07.24 jwt+security依赖引入,登录+鉴权系统组合完成,实现登录赋权和api粒度权限校验。
2023.07.25 部分查询调整为mybatis-plus提供的lambda查询模式,减少代码中sql出现的频次。
2023.07.26 引入nacos,账户子系统具备服务注册功能,同时引入open-feign,提供基于http协议的RPC。
2023.07.31 native-image引入,改造部分类的加载方式,兼容native镜像打包。
2023.08.03 Graal-sdk依赖升级为22.3.2,解决native-image在AOT阶段的报错问题。
2023.08.09 添加账户子系统AOT脚本,确保所有模块能够在编译时期准确链接。
2023.08.10 新增common模块,用于放置公共配置类和工具类。
2023.08.12 抽取账户配置引导类(security、mybatis-plus、swagger-api…)至common模块下,启动、打包成功。
2023.08.15 新增common-api模块,移动公用entity至该模块路径,供后续子系统间基于公用entity传输数据。
2023.08.18 新增额度子系统,完成框架搭建和数据流通、子系统间通信测试。
2023.08.23 额度子系统完成头寸体系建设,提供头寸划转资金、证券功能,编码完成,自测完成。
2023.08.29 新增自定义注解openApi,配合鉴权体系实现白名单配置机制。
2023.08.31 子系统全部增加domain域,所有服务不再直接访问dao层查询,统一由domain域提供数据访问和操作的途径。
2023.09.10 新增gateway模块,实现统一网关分发路由。
2023.09.16 调整open-feign相关AOT配置,解决gateway和open-feign兼容问题导致的native程序请求分发失败。
2023.09.20 gateway模块增加open-feign子系统代理类,能够使代理类被正确链接,解决AOT报错。
2023.09.24 新增common-util模块,放置常量类和工具类。
2023.09.29 新增common-result模块,提供子系统对外统一结果封装。
2023.09.31 异常体系建设完成,可自定义异常种类、异常处理类,具备统一捕捉业务异常的能力。
2023.10.07 业务检查模板体系设计完成、编码完成,提供高度抽象的模板检查策略,易于扩展。
2023.10.15 账户子系统、额度子系统业务完成业务检查模板改造,各类业务适配业务模板,编码完成、自测完成。
2023.10.20 提供接口api文档配置化注解,实现可插拔。
2023.10.26 新增大量枚举,优化业务检查代码,使用枚举代替所有魔法值。
2023.10.30 新建信用交易子系统,完成框架搭建。
2023.11.03 交易时间体系建设完成,具备不同业务交易时间检查能力。
2023.11.11 融资买入参数检查编码完成、自测完成。
2023.11.13 竞价业务证券代码检查体系建设完成,统一提供基础证券检查功能,并允许子类自行扩展。
2023.11.23 融资买入委托数据检查编码完成,自测完成。
2024.02.26 完成RPC调用融资日间实时合约生成逻辑,并基于TCC实现分布式事务补偿机制,自测完成。
2024.02.27 取消信用委托表order_id索引唯一属性,使分布式事务补偿后出现同一订单流水号能够存在多条废单委托,且仅存在一条有效委托,自测完成。
2024.02.29 补全合约系统日间实时合约流水和合约汇总流水记录逻辑,结合融资买入业务自测通过。
2024.03.01 引入javac-annotation processor编译期注解处理器,并解决JDK17模块化引起的maven compile以及idea build module报错,编译期处理器组件框架搭建完成,待实现具体注解处理器。
2024.03.02 调整日间合约汇总表索引,增加init_date字段并取消流水表索引unique属性,分布式事务代码配套修改。
2024.03.03 编译期注解框架完成get和set方法生成,值拷贝语句生成编码进度50%。
2024.03.04 值拷贝语句编码完成,自测通过,待结合业务逻辑进行测试。
2024.03.06 搭建环形队列模块,引入ringbuffer,并结合netty实现高性能收取报文处理数据。
2024.03.07 自定义简要二进制通信协议,编码50%。
2024.03.08 自定义简要二进制通信协议,编码完成,待业务核心模块引入该组件测试报文接收效果。
2024.03.10 ringbuffer模块,netty接收二进制数据根据报文规则解析字节代码修改,测试完成。
2024.03.12 ringbuffer模块引入disruptor,编写通用api,自测完成,压测结果支持16400 QPS。
2024.08.30-2024.08.31 设计信用交易后台费用表结构,完成费用预算业务组件编写,自测完成,待业务模块引入使用。
2024.09.03 委托表扩充费用和金额字段,在委托过程中增加费用和发生金额赋值,待自测。
2024.09.04 资产账号表扩充费用属性串字段,增加交易公用费用属性获取工具,拟在委托过程中获取费用属性以计算费用,待自测。
2024.09.05 证券交易费用模块结合融资买入业务自测后修复bug,完成业务流程中费用预算的开发。
2024.09.06 费用预算引入低费用标志,当标志开启时,委托发生费用小于最低费用时不收取最低佣金,结合账户系统费用串自测完成。
2024.09.09 新建专项业务头寸账户表和专项业务资金表,支持融资买入检查客户专项头寸账户和专项头寸可用资金,待自测。
2024.09.10 支持融资买入更新专项头寸可用资金,待自测。
2024.09.11 普通业务头寸资金流水表扩充发生标志和交易日期字段,并在融资买入委托支持更新,待自测。
2024.09.12 自测过程中发现专项头寸更新未实现TccService,需要实现TccService以便支持分布式事务。
2024.09.13 实现专项头寸分布式事务TccService,代替原有的专项头寸domain进行头寸资金更新,头寸资金更新模块自测完成。
2024.09.15 信用交易系统接入全局账户鉴权功能,结合融资买入业务自测完成,完成Api粒度鉴权
2024.09.16 梳理模块总览,编写各模块职责。
2024.09.18 新增缓存管理类,用于缓存运行时RPC获取的用户元数据信息,减少RPC至账户系统的开销,自测完成。
2024.09.19 1、修复用户元数据缓存未能命中的bug,自测完成。
2、缓存支持可重入锁,以应对并发更新。
2024.09.20 信用交易核心获取序列号使用缓存双写模式,自测完成
2024.09.21 1、额度流水计数器分片表扩充step步长字段,支持交易核心批量申请序列号,编码完成,自测完成。
2、专项业务头寸资金流水表的计数器类型和普通业务头寸资金流水表计数器独立,防止数据库行锁资源频繁竞争,自测完成。
2024.09.23 修复bug,当前缓存号段无序列号可用时,更新该号段数据库表中可用状态为不可用。
2024.09.24 流水计数器分布式事务实现类支持缓存号段数据上下场的能力,避免发生故障停机后无法恢复缓存数据,造成号段资源浪费。自测完成。
2024.09.26 流水计数器获取类改造,可缓存多种计数器种类的号段数据,并具备计数器号段数据上场能力,自测完成。
2024.09.27 流水计数器获取类改造,使同一种计数器号段能够连续申请,自测完成。
2024.09.28 信用交易专项头寸资金流水表,生成序列号改造:使用带缓存功能的统一流水计数器工具类,自测完成。
2024.09.30 统一流水计数器缓存扩展申请号段标志,只在申请号段时才更新当前计数器种类的号段起始值缓存,自测完成。
2024.10.01 构造回报报文,并解析成功,成功反序列化成MessageMetaData,供业务流程使用,自测完成。
2024.10.02 编写生成字节报文工具,ByteTypeUtil.generateBytes,支持根据传入任意 map 生成字节报文,自测完成。
2024.10.03 LemonBufferTest 单元测试, 65535 个线程同时通过ByteTypeUtil.generateBytes生成字节报文并打入队列,总消耗时间 814ms,性能 ok。
2024.10.05 LemonBuffer 模块新增消息处理器,并将模块下的处理器全部托管给 spring 容器,过滤不包含businessFlag 的消息,自测完成。

6.初始化脚本

系统初始化脚本,打入后,交易回到最初数据状态:

truncate table compact.compact;

truncate table compact.compact_jour;

truncate table compact.compact_summary;

truncate table compact.compact_summary_jour;

truncate table credit.credit_entrust;

truncate table credit.credit_serial_counter_record;

truncate table credit.special_cash_group_fund_jour;

truncate table quota.cash_group_fund_jour;

-- 初始化普通业务头寸资金表
update quota.cash_group_fund set financing_total_balance = 10000000000.00,enable_balance = 10000000000.00,frozen_balance = 0.00,financing_used_balance = 0.00 where 1=1;

-- 初始化专项业务头寸资金表
update credit.special_cash_group_fund set fin_total_balance = 10000000000.00,enable_balance = 10000000000.00,fin_used_balance = 0.0,ref_due_balance = 0.0,frozen_balance = 0.0,cash_interest = 0.0 where 1=1;

-- 重置流水号额度表各业务种类的计数器为初始值
update quota.serial_no_counter set serial_counter_value = 1000000 where 1=1 ;

-- 重置普通头寸业务资金流水表的计数器号段长度
update quota.serial_no_counter set step = 1 where serial_counter_no = 0;

-- 重置信用交易委托表的计数器号段长度
update quota.serial_no_counter set step = 100 where serial_counter_no = 1;

-- 重置专项头寸业务资金流水表的计数器号段长度
update quota.serial_no_counter set step = 100 where serial_counter_no = 2;

7.字段释义

字段名 mysql类型 java类型 备注 枚举
userId varchar(64) String 用户id(客户层id) 不涉及
fundAccount bigint long 资产账号 不涉及
stockAccount varchar(64) String 股东账号 不涉及
cashGroupAccount bigint Long 头寸账号 不涉及
branchNo int int 营业部编号 不涉及
accountStatus int int 账号状态 不涉及
realAction tinyint int 发生动作,常用在记录资金流水、股份流水时,表明是成交回报流程或是委托流程发生的资金、股份变更 0,委托过程中记录流水
1,回报过程中记录流水
entrustStatus tinyint int 委托状态,表明一笔委托当前在业务系统中的流转状态 0:未报
1:待报
2:已报
6:已撤
8:已成
compactStatus char(1) char 合约状态,表明信用交易产生合约的当前状态 0:未了结
1:部分了结
2:已了结
3:作废
compactType char(1) char 合约类型,融资或者融券合约 1:融资合约
2:融券合约
exchangeType varchar(10) String 交易市场 1:上海市场
2:深圳市场
3:北交所
fareProp char(1) String 费用属性,只有前台/后台费用 0:前台费用
1:后台费用
fareType char(1) String 费用类型 0-佣金
1-印花税
2-过户费
3-其他费用
x-其他费用
fundAccountStatus int int 资产账号状态 0:正常
1:休眠
2:冻结
4:销户
5:未知
seatProp smallInt int 席位属性 0:普通席位
7:信用席位
stockAccountStatus smallint int 证券账号状态 0:正常
1:休眠
2:未知
accountType char char 证券账号类型 0:普通证券账号
1:信用证券账号
stockType char(2) String 证券类型 0:股票
4:新股
G:新债
entrustPrice decimal(20,2) BigDecimal 委托价格 不涉及
entrustAmount int int 委托数量 不涉及
entrustBalance decimal(20,2) BigDecimal 委托总金额,不包含前后台费用 不涉及
prevBalance decimal(20,2) BigDecimal 委托发生金额,委托下单时落表,包含前后台费用 不涉及
clearBalance decimal(20,2) BigDecimal 清算金额,回报时更新,包含当前成交数量对应的前后台费用 不涉及
orderId bigint Long 委托编号 不涉及
businessAmount int int 成交数量 不涉及
businessPrice decimal(20,2) BigDecimal 成交价格 不涉及
businessBalance decimal(20,2) BigDecimal 成交金额,不包含前后台费用 不涉及

8. todo list

  • 流水计数器中的缓存功能,需要维护每一种计数器种类的号段起始值,否则存在多种计数器种类缓存无法击中的风险。
  • 专项头寸资金发生变化,流水序列号使用带缓存的统一流水计数器工具类。
  • 流水计数器缓存,需要判断新申请号段的情况下才更新缓存中号段的起始值。
  • 构造成交回报报文,并解析报文。
  • 统一报文反序列化处理器和消息元数据处理器托管给 Spring 容器管理生命周期,支持嵌入各业务系统。
  • 🔴搭建回报模块,执行回报流程
  • 🔴统一流水计数器考虑抽象成公共组件,供各业务子系统使用。
  • 🔴普通业务头寸资金流水表,需要通过统一流水计数器获取序列号,同时考虑性能,序列号递增的写库操作考虑引入异步队列触发。