30分钟
云交易系统开发手册
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中文字符集的作用。
- show full columns from
-
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})
-
-
使用SocketChannel 连接服务端进行测试,并发写入需要控制写入速率,通过 Thread.sleep(20l)可实现安全写入。
-
引入 ringbuffer 模块后,报错显示无法强制类型转换为指定的 springContext 容器:
Caused by: java.lang.ClassCastException: class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext cannot be cast to class org.springframework.context.annotation.AnnotationConfigApplicationContext (org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext and org.springframework.context.annotation.AnnotationConfigApplicationContext are in unnamed module of loader 'app') at com.huang.buffer.handler.MessageMetaDataHandler.setApplicationContext(MessageMetaDataHandler.java:62) ~[classes/:na] at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:112) ~[spring-context-6.0.8.jar:6.0.8] at org.springframework.context.support.ApplicationContextAwareProcessor.postProcessBeforeInitialization(ApplicationContextAwareProcessor.java:87) ~[spring-context-6.0.8.jar:6.0.8] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:419) ~[spring-beans-6.0.8.jar:6.0.8] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1762) ~[spring-beans-6.0.8.jar:6.0.8] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.0.8.jar:6.0.8] ... 15 common frames omitted
-
这是因为业务子系统使用的AnnotationConfigServletWebServerApplicationContext进行托管,将 ringbuffer 中实现了 ApplicationContextAware 接口的 bean 所包含的成员变量全部改为对应的上层抽象接口ConfigurableApplicationContext即可进行强转,可参考:
@Component public class MessageMetaDataHandler extends ChannelInboundHandlerAdapter implements InitializingBean, ApplicationContextAware { private static final Logger log = LoggerFactory.getLogger(MessageMetaDataHandler.class); private static final String BUSINESS_FLAG_KEY = "businessFlag"; private ConfigurableApplicationContext applicationContext; private static Map<Integer,BusinessHandler> handlers; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ... } @Override public void afterPropertiesSet() throws Exception { // 从容器中获取 BusinessHandler 接口的所有实现类 Map<String, BusinessHandler> candidateBeans = this.applicationContext.getBeansOfType(BusinessHandler.class); handlers = candidateBeans.values().stream().collect(Collectors.toMap(BusinessHandler::businessFlag, Function.identity())); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = (ConfigurableApplicationContext) applicationContext; } }
-
-
如果业务子系统引入 Ringbuffer 模块后,无法找到 ringbuffer 模块下要求运行时托管给 Spring 容器管理的 bean,是因为默认 Spring 只扫描启动类所在目录下的所有 component、service、controller,需要手动通过注解引入 ringbuffer 模块下的 bean,可参考信用交易子系统启动类上的注解:
/** * @description: 信用竞价交易子系统启动类 * @author: huang.zh * @create: 2023-11-16 21:12 **/ @EnableApiDoc @EnableSecurityAuthorization @EnableGlobalExceptionHandler @SpringBootApplication @EnableNativeHints @EnableDiscoveryClient @EnableFeignClients @MapperScan(basePackages = "com.huang.credit.mapper",sqlSessionTemplateRef = "sqlSessionTemplate") // 引入 ringbuffer 模块下的 bean @Import(value = {NettyStartListener.class, ServerProperty.class, SocketServer.class, GenerateMessageDataHandler.class, MessageMetaDataHandler.class}) public class CreditTradeApplication { public static void main(String[] args) { SpringApplication.run(CreditTradeApplication.class,args); } }
- 也可以直接使用 ringbuffer 模块下的@EnableRingBuffer 注解,它会帮业务系统自动引入启用环形队列处理消息时所有需要托管的 bean:
/** * @description: 信用竞价交易子系统启动类 * @author: huang.zh * @create: 2023-11-16 21:12 **/ @EnableApiDoc @EnableSecurityAuthorization @EnableGlobalExceptionHandler @SpringBootApplication @EnableNativeHints @EnableDiscoveryClient @EnableFeignClients @MapperScan(basePackages = "com.huang.credit.mapper",sqlSessionTemplateRef = "sqlSessionTemplate") @EnableRingBuffer public class CreditTradeApplication { public static void main(String[] args) { SpringApplication.run(CreditTradeApplication.class,args); } }
-
如果在 gateway 模块下的 globalfilter 相关实现类中,直接通过 autowired 注入OpenFeign 的请求借口 FeignClient,会引起循环依赖报错:
Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | partitionNoCheckFilter (field private com.huang.UserSystemFeignClient com.huang.PartitionNoCheckFilter.userSystemFeignClient) ↑ ↓ | com.huang.UserSystemFeignClient ↑ ↓ | corsGatewayFilterApplicationListener defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class] ↑ ↓ | routePredicateHandlerMapping defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class] ↑ ↓ | filteringWebHandler defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class] └─────┘ Action: Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
-
需要考虑引入 lazy 注解,在第一次实际使用该FeignClient 时才进行初始化:
@Lazy @Autowired private UserSystemFeignClient userSystemFeignClient;
-
同时,需要注意 Spring Cloud Gateway中,不支持动态路由,需要将配置项中对应 serviceId 的 url 改成具体地址:
openfeign: client: refresh-enabled: false config: user-system: url: http://127.0.0.1:8880 quota-manager-system: url: http://127.0.0.1:8881 credit-trade-system: url: http://127.0.0.1:8883
-
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 的消息,自测完成。 |
2024.10.08 | LemonBuffer 模块扩展 BusinessHandler,作为所有业务消息处理子类的抽象,并托管给 Spring 容器处理。 |
2024.10.09 | 1、消息处理器MessageMetaDataHandler 具备发现 businessHandler 的能力,并能够根据消息中业务标志采用具体的消息业务处理器处理消息。 2、完善单元测试类,各业务子系统可参考SocketServerTest 和 TestBusinessHandler 实现对应的消息处理器。 |
2024.10.10 | 考虑如何实现消息处理类绑定 LemonBuffer 实现异步处理消息。 |
2024.10.11 | 信用交易子系统引入 ringbuffer 模块,解决报错,成功运行。 |
2024.10.12 | 新增注解@EnableRingBuffer,供业务系统使用,集成环形队列模块时通过该注解引入所有需要托管给 Spring 容器的 bean。 |
2024.10.14 | 构建回报异常体系,Ringbuffer 执行回报报错时统一捕获异常。 |
2024.10.15 | 融资买入回报模块流程功能编码,完成委托状态计算。 |
2024.10.18 | 1、合约表扩展合约成交数量字段,委托回报时更新该字段。 2、回报流程中新增更新合约方法,支持更新合约成交数量和融资买入在途委托金额。 3、支持回报更新合约汇总,并记录合约汇总流水。 |
2024.10.19 | 1、回报扩充 isCancelOrder 字段,用于判断撤单委托和原委托,对应处理不同的流程。 2、BusinessHandler 接口扩展 doRevert 方法,各实现类实现后,用于正常回报流程处理出错时触发回滚操作。 3、融资买入回报处理器支持废单处理流程。 |
2024.10.22 | 新增撤单委托接口,同时新增撤单委托专用的业务检查 specification,用于撤单时间检查、原委托状态校验。 2、新增撤单委托专用时间检查处理器。 |
2024.10.24 | 1、融资买入成交回报测试完成,修复 bug。 2、废单场景回报修正合约汇总表在途金额更新错误。 |
2024.10.25 | 1、信用交易委托表扩展原委托字段,用于撤单委托时记录原委托编号,定位原委托信息。 2、完成撤单委托时原委托状态检查。 |
2024.10.26 | 撤单委托完成编码,支持内部撤单和外部撤单,并支持内部撤单直接作废融资合约,待自测。 |
2024.10.28 | 1、修复内部撤单合约汇总在途金额更新不正确的问题。 2、新增CreditEntrustBackWriteHandler,并集成 lemonBuffer,用于异步处理交易所回写请求。 |
2024.10.29 | 1、支持撤单成交委托状态计算,支持原委托部分成交后撤单和整笔全部撤单。 2、修复撤单委托下单后,因为委托类型不正确而引起的撤单成交回报时委托状态不能正确计算的问题。 3、内部撤单支持回冲头寸资金和前后台费用。 4、撤单成交回报支持回冲头寸资金(包括原委托部分成交后在途金额+后台的回冲)。 5、撤单成交回报支持根据本次回报成交的金额计算后台费用,并累加至清算金额 clearBalance。 |
2024.10.30 | 1、支持撤单委托废单回报处理。 2、原委托在撤单委托废单回报时修正为撤单前的状态。 3、修正融资买入委托成交回报时费用计算不准确,支持委托全部成交的情况时,清算金额计算考虑前台费用。 |
2024.10.31 | 1、修复融资买入委托成交回报时,未正确计算合约汇总表成交数量的问题。 |
2024.11.01 | 新增信用客户持仓表,并支持更新或新增一条指定代码的持仓记录。 |
2024.11.02 | 1、新增信用客户持仓流水表,支持在更新信用客户持仓流水的同时记录流水。 2、全局流水序列号获取类 SerialCounterNoTccService,支持不开启分布式事务时使用,走常规RPC 获取最新序列号。 |
2024.11.04 | 1、修复融资买入内部撤单时,回冲头寸多扣减费用的问题。 2、融资买入成交回报处理,支持在更新委托状态前更新持仓数据。 |
2024.11.05 | 1、修复回写时往队列发布回写消息,对应的 handler 不处理的问题。 2、新增内部回写测试接口,供内部处理回写请求的转换。 |
2024.11.06 | 1、新增内部回报测试接口,供内部处理回报请求的转换。 |
2024.11.07 | 1、支持系统初始化校验,交易日期非当日的情况直接报错返回。 2、新增卖券还款业务检查责任链。 |
2024.11.19 | 1、修复融资买入成交回报持仓更新不准确的bug。 2、卖券还款业务检查编码进度 20%。 |
2024.11.20 | 时间检查方法抽象至公共类 TradeDateTimeCheckHandler,方便各业务检查处理类复用。 |
2025.03.03 | 资产账号表新增 partition_no 字段,用于后续多分片水平扩展。 |
2025.03.04 | gateway 配置文件中的静态路由维护多分片模式配置。 |
2025.03.05 | 使用 discovery 注册分片号,在 gateway 获取serviceId 对应的分片号。 |
2025.03.07 | 新增 dynamic-routes模块,具备动态路由缓存,运行时注入容器上下文。 |
2025.03.08 | 1、新增 updateDynamicRoutes方法,用于 DynamicRoutesCache 运行时更新动态路由缓存。 2、新增PartitionNoCheckFilter,运行时可根据请求找到对应的分片,并转发至对应的分片核心。 |
2025.03.09 | PartitionNoCheckFilter逻辑优化,支持根据路由配置中的多分片开启模式开关,来动态获取路由。若多分片模式未开启,则直接放行。 |
2025.03.10 | PartitionNoCheckFilter逻辑优化,如果请求头不带有分片号,则从表单域中获取 fundAccount,如果获取不到则报错返回。 |
2025.03.12 | 1、解决PartitionNoCheckFilter中,注入 FeignClient 导致循环引用报错的问题。 2、账户系统支持与网关交互,向网关提供分片号和账号区间的关联信息。 |
2025.03.13 | PartitionNoCheckFilter支持通过定时触发异步线程的模式请求分片和账号的关联信息。 |
2025.03.14 | 1、PartitionNoCheckFilter支持通过新增 partitionNoCache 维护资产账号和分片的信息。 2、DynamicRoutesCache要求传入拼接好的 cacheKey 作为命中缓存的入参。 3、PartitionNoCheckFilter增加多分片模式元数据异常拦截报错的逻辑,多分片模式下没有 serviceId 以及 通过账户未落在缓存中的分片账号区间段内,均报错返回。 |
2025.03.15 | DynamicRoutesCache 引入 Nacos NamingService,成功解析网关配置文件中配置的spring.cloud.gateway.routes配置项,后续将通过在初始化阶段注册配置文件中配置的所有 serviceId 的状态监听器 |
2025.03.17 | DynamicRoutesCache 改用 NotifyCenter 的抽象类 Subscriber 监听服务分片的状态,后续需要为每一个配置了多分片模式的serviceId 实现对应的Subscriber,做到松耦合。 |
2025.03.18 | DynamicRoutesCache 成功订阅多分片服务信息,具备根据 serviceId#paritionNo的模式维护分片的URI信息。 |
2025.03.20 | DynamicRoutesCache 具备根据监听事件,自动剔除下线分片的逻辑。 |
2025.03.21 | 1、修复DynamicRoutesCache根据分片信息获取路由缓存时cacheKey 拼接错误导致路由失败的问题。 2、修复分片-资产账号区间段解析错误导致缓存不更新的问题。 3、优化分片-资产账号区间段缓存维护的 log提示日志。 |
2025.03.22 | 需要手动实现spring-cloud-loadbalancer 对应的负载均衡策略,否则无法转发至准确的分片。决定手动实现ReactorServiceInstanceLoadBalancer,来代替框架中的 RoundRobbinLoadBalancer,编码进度20% |
2025.03.24 | DynamicRoutesCache 处理路由变更监听事件时,忽略使用动态路由缓存的应用程序自身上下线事件。 |
2025.03.25 | 更换gateway中动态路由转发的技术路线,将 ServerWebExchange 重定向的动作前移,拟用实现AbstractGatewayFilterFactory工厂类直接改写初始 exchange请求,不在 GlobalFilter 中做改写动作。该技术路线已具备可行性,待迁移PartitionNoCheckFilter 的逻辑至AbstractGatewayFilterFactory实现类的 apply 方法中。 |
2025.03.29 | 1、去除原来的 ParitionNoCheckFilter,迁移节点定位的逻辑至AbstractGatewayFilterFactory的实现类DynamicRoutesGatewayFilterFactory中,编码完成,自测完成,根据分片定位路由,转发至指定分片成功。 2、调整部分错误枚举的提示信息。 3、网关启动时支持立即刷新分片-资产账号区间缓存,防止启动后因为定时任务尚未执行而导致资产账号定位时请求获取不到具体的分片返回报错。 4、业务子系统支持接入 DynamicRoutes,维护当前子系统下当前分片的所有路由,后续可用于主备选举。编码进度 30% |
2025.03.31 | 1、DynamicRoutes维护当前子系统下当前分片的所有路由,后续可用于主备选举。编码进度 40% |
2025.04.01 | 1、使用 BIFunction 来获取需要更新的分片缓存路由。2、规范化代码关键注释。 |
2025.04.02 | 1、DynamicRoutes维护当前子系统下当前分片的所有路由,后续可用于主备选举。编码完成,自测完成。 2、新建动态选主类,通过注解引入,后续将通过探测主节点是否存活的动作来发起主备选举动作,编码进度 20%。 |
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_trade_time;
truncate table credit.credit_serial_counter_record;
truncate table credit.credit_stock_real_jour;
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;
-- 重置专项头寸业务资金流水表回滚的计数器号段长度
update quota.serial_no_counter set step = 1 where serial_counter_no = 3;
-- 重置信用客户持仓流水表的计数器号段长度
update quota.serial_no_counter set step = 1 where serial_counter_no = 4;
-- 插入信用交易时间表
INSERT INTO credit.credit_trade_time (id, time_kind, init_date, begin_time, end_time, remark, withdraw_flag) VALUES (1, '0', 0, 0, 0, '0-交易系统是否初始化', '0');
INSERT INTO credit.credit_trade_time (id, time_kind, init_date, begin_time, end_time, remark, withdraw_flag) VALUES (2, '3', 20231130, 0, 99999999, '信用交易时间', '1');
INSERT INTO credit.credit_trade_time (id, time_kind, init_date, begin_time, end_time, remark, withdraw_flag) VALUES (3, '4', 20241028, 0, 99999999, '信用委托撤单时间', '1');
存储过程
-- 检查资产账号表是否存在分片号的存储过程
-- 信用交易账号,默认 52 分片
drop procedure if exists checkPartitionNo;
delimiter //
create procedure checkPartitionNo()
begin
select count(1) into @row_num from information_schema.COLUMNS
where TABLE_SCHEMA = 'user'
and TABLE_NAME = 'fund_account' and COLUMN_NAME = 'partition_no';
if @row_num = 0 then alter table user.fund_account add column partition_no int default 52 comment '分片号';
end if;
end; //
delimiter ;
call checkPartitionNo();
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 | 委托编号 | 不涉及 |
origOrderId | bigint | Long | 原委托编号,撤单委托记录要撤单的委托号,普通委托记录自身的委托号 | 不涉及 |
businessAmount | int | int | 成交数量 | 不涉及 |
businessPrice | decimal(20,2) | BigDecimal | 成交价格 | 不涉及 |
businessBalance | decimal(20,2) | BigDecimal | 成交金额,不包含前后台费用 | 不涉及 |
stockCode | varchar(10) | String | 证券代码 | 不涉及 |
realBuyAmount | bigint | Long | 回报买入数量 | 不涉及 |
realBuyBalance | decimal(20,2) | BigDecimal | 回报买入金额 | 不涉及 |
realSellAmount | bigint | Long | 回报卖出数量 | 不涉及 |
realSellBalance | decimal(20,2) | BigDecimal | 回报卖出金额 | 不涉及 |
frozenAmount | bigint | Long | 当日买入冻结数量 | 不涉及 |
unfrozenAmount | bigint | Long | 当日卖出解冻数量 | 不涉及 |
currentAmount | bigint | Long | 当前数量(包含不可卖的证券数量) | 不涉及 |
enableAmount | bigint | Long | 可用数量(可进行卖出操作的证券数量) | 不涉及 |
8. todo list
- ✅
流水计数器中的缓存功能,需要维护每一种计数器种类的号段起始值,否则存在多种计数器种类缓存无法击中的风险。 - ✅
专项头寸资金发生变化,流水序列号使用带缓存的统一流水计数器工具类。 - ✅
流水计数器缓存,需要判断新申请号段的情况下才更新缓存中号段的起始值。 - ✅
构造成交回报报文,并解析报文。 - ✅
统一报文反序列化处理器和消息元数据处理器托管给 Spring 容器管理生命周期,支持嵌入各业务系统。 - ✅
ringbuffer 模块需要自定义一个注解来实现业务子系统集成该模块后能扫描到该模块下的 bean。 - ✅
搭建回报模块,执行回报流程。 - ✅
实现委托撤单。 - ✅
融资买入回报业务处理器,需要实现 doRevert,并在 doHandle 中触发。 - 🔴融资买入回报业务处理器,需要集成 RingBuffer ,实现高性能异步队列处理回报请求。
- 🔴统一流水计数器考虑抽象成公共组件,供各业务子系统使用。
- 🔴普通业务头寸资金流水表,需要通过统一流水计数器获取序列号,同时考虑性能,序列号递增的写库操作考虑引入异步队列触发。
9. 流水计数器种类
计数器编号 | 对应数据表名 | 单次申请步长 |
---|---|---|
0 | 头寸账户资金流水表 | 1 |
1 | 信用交易委托表 | 100 |
2 | 客户专项头寸资金表 | 100 |
3 | 客户专项头寸资金表回滚专用 | 1 |
4 | 信用客户持仓流水表 | 1 |