COE 系统开发规范
基于 BFON(信贷产品运营平台)、BLMS(名单管理系统)、BNBS(产品域编排与资金批扣平台)、BCPS(信用核心平台)四个仓库的代码结构分析,整理出的 COE 体系开发规范。
本文档保留 BFON / BLMS 的基础型规范,同时合并 BNBS / BCPS 的重型系统规范,按“统一原则 + 仓库差异”组织。
目录
- 总体架构原则
- 项目目录结构规范
- Maven 多模块规范
- Java 包结构与分层规范
- 命名规范
- 配置管理规范
- 数据库规范
- 服务间通信规范
- 请求处理与路由规范
- 数据模型与传输对象规范
- 异常处理规范
- 日志与交易追踪规范
- 国际化(i18n)规范
- 测试规范
- 构建与部署规范
- 代码风格与工具约定
- 附录:四仓开发规范对照
- 规范执行检查清单
1. 总体架构原则
1.1 技术栈
| 层面 | 技术选型 |
|---|---|
| 语言 | Java 21 / Java 8 并存,以各系统 parent POM 版本为准 |
| 框架 | Spring Boot(基于 zcnc-sdk-starter-parent) |
| ORM | MyBatis / MyBatis-Plus |
| 消息总线 | RMB(WeBank Reliable Message Bus) |
| 数据库 | TDSQL(腾讯分布式 SQL,MySQL 兼容) |
| 连接池 | Apache DBCP / Druid(由 SDK 自动配置) |
| 构建工具 | Maven(多模块 reactor) |
| JSON | Jackson(主)+ Fastjson(兼容遗留) |
| 工具库 | Lombok、Guava、Apache Commons |
1.2 架构风格
- 微服务架构:每个系统是独立部署的 Spring Boot 应用
- 消息驱动:服务间以 RMB 异步/同步消息通信为主
- 分层架构:Controller → Service → DAO → Database,严格单向依赖
- 无状态服务:服务实例无本地状态,通过 ThreadLocal 管理请求上下文
- SDK 驱动:核心能力(数据库、消息、缓存、序列号、日志等)由
zcnc-sdk-starter-*统一提供
1.3 设计原则
| 原则 | 说明 |
|---|---|
| 关注点分离 | 每个模块职责单一:common(共享模型)、service(业务逻辑)、batch(批处理) |
| 模板方法模式 | 基类定义处理骨架(参数校验 → 业务处理 → 后置处理),子类实现具体逻辑 |
| 策略模式 | 通过 Spring Bean 注册 + ServiceCode 路由实现服务的动态分发 |
| 配置外部化 | 业务配置与代码分离,conf/(生产)与 conf-local/(开发)独立管理 |
| 向前兼容 | JSON 反序列化使用 @JsonIgnoreProperties(ignoreUnknown = true),避免字段增删导致兼容性问题 |
2. 项目目录结构规范
每个 COE 系统必须遵循以下统一顶层目录布局:
{system-name}/
├── pom.xml # 父 POM(Maven reactor 聚合)
├── README.md # 项目说明(中文)
├── .gitignore # Git 忽略规则
│
├── {system}-service/ # 主服务模块(必须有)
├── {system}-common/ # 共享模块(可选,按需拆分)
├── {system}-batch/ # 批处理模块(可选,有批处理需求时)
├── {system}-dao/ # 数据访问模块(可选,按需拆分)
├── ...其他业务模块
│
├── bin/ # 运维部署脚本(必须)
├── conf/ # 生产环境配置(必须)
├── conf-local/ # 本地开发配置(推荐)
├── dbScript/ # 数据库变更脚本(必须)
└── metadata/ # 表结构元数据定义(推荐)2.1 各目录用途说明
| 目录 | 必须 | 说明 |
|---|---|---|
bin/ | ✅ | 部署脚本:start.sh、stop.sh、deploy.sh、cncboot.sh |
conf/ | ✅ | 生产环境配置文件,按模块分子目录 |
conf-local/ | 推荐 | 本地/SIT 开发环境配置,结构与 conf/ 镜像 |
dbScript/ | ✅ | 数据库增量脚本,按版本日期组织 |
metadata/ | 推荐 | 最新完整表结构 DDL/DML 定义 |
pom.xml | ✅ | Maven 父 POM |
2.2 bin/ 脚本规范
bin/
├── {system}-service/
│ ├── start.sh # 启动脚本(设置 systemId、AppPort、jmxPort)
│ ├── stop.sh # 停止脚本
│ ├── deploy.sh # 部署脚本
│ ├── cncboot.sh # CNC SDK 引导脚本(通用)
│ ├── docker-run.sh # Docker 启动(可选)
│ └── docker-stop.sh # Docker 停止(可选)
└── {system}-batch/
├── start.sh
├── stop.sh
├── deploy.sh
└── cncboot.shstart.sh 必须定义的环境变量:
systemId=U543 # 系统编号(全局唯一)
AppPort=6885 # 应用端口
jmxPort=26885 # JMX 监控端口(= 26000 + 应用端口后两位)2.3 conf/ / conf-local/ 配置目录规范
conf/
├── {system}-service/ # 按可部署模块分目录
│ ├── application.properties # Spring Boot 主配置
│ ├── {module}.properties # 模块业务配置
│ ├── {module}-env.properties # 环境变量配置
│ ├── {module}-nodes.env # 节点拓扑配置
│ ├── rmb-client.properties # RMB 客户端配置
│ ├── gns-api.properties # GNS 全局命名服务配置
│ ├── log4j2.xml # 日志配置
│ └── weservice.properties # WeService 框架配置
└── {system}-batch/
└── (同上结构)关键规则:
conf/存放生产环境配置(使用模板变量[@...])conf-local/存放本地/SIT 开发配置(使用实际值)- 两者目录结构和文件名必须完全一致
2.4 四仓顶层目录模式
上面的目录结构可以视为 COE 的基础模板,但四个仓库并不完全同构,开发时要按实际模式落位:
| 仓库 | 顶层结构主模式 | 开发规范重点 |
|---|---|---|
BFON | service / batch / common | 标准三段式,REST + 批处理并存 |
BLMS | service 单主模块 | 轻量消息驱动,围绕 @RmbMap 和 ServiceCode 组织 |
BNBS | cgi / service-adm / service-dcn / mid / batch-dcn / union-app | 按运行角色拆模块,允许联合部署壳与中台模块并存 |
BCPS | online / online-dcn / batch-dcn / core-* / service-api / facility / infrastructure | 按接入层、核心域、基础设施分层,模块数多且层次深 |
落地规则:
BFON、BLMS可以直接套用本节基础模板。BNBS、BCPS不应被强行压缩成service/common/batch三段式,而应按“运行入口 + 领域核心 + 基础设施”拆分。union-app、core-*、facility、infrastructure这类模块属于 COE 合法结构,不算偏离规范。
3. Maven 多模块规范
3.1 父 POM 规范
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.webank.cnc.sdk</groupId>
<artifactId>zcnc-sdk-parent-for-coe</artifactId>
<!-- 或 zcnc-sdk-starter-parent -->
<version>{sdk-version}</version>
</parent>
<groupId>cn.webank.cnc</groupId>
<artifactId>{system-name}</artifactId>
<version>{version}-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>{system}-common</module>
<module>{system}-service</module>
<module>{system}-batch</module> <!-- 按需 -->
</modules>
</project>3.2 模块职责定义
| 模块类型 | 打包方式 | 职责 | Spring Boot 插件 |
|---|---|---|---|
*-common | jar | 共享模型、工具类、常量、枚举 | ❌ 不使用 |
*-service | jar (可执行) | 主业务服务:Controller、Service、RMB Listener | ✅ spring-boot-maven-plugin (repackage) |
*-batch | jar (可执行) | 批处理任务:Scheduler、Job Config | ✅ spring-boot-maven-plugin (repackage) |
*-dao | jar | 数据访问层:DAO 接口、MyBatis Mapper | ❌ 不使用 |
3.3 模块 POM 配置要点
服务模块必须指定主类:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>cn.webank.cnc.{abbrev}.service.{System}ServiceApplication</mainClass>
</configuration>
<executions>
<execution>
<goals><goal>repackage</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>依赖版本管理原则:
- SDK 相关依赖版本由 parent POM 统一管理,不单独声明版本
- 第三方依赖版本在父 POM
<dependencyManagement>中统一声明 - 禁止在子模块中硬编码依赖版本号
3.4 四仓模块编排差异
| 仓库 | Reactor 组织方式 | 规范要点 |
|---|---|---|
BFON | 父 POM 聚合 common/service/batch | 适合按共享能力和部署单元拆分 |
BLMS | 父 POM 下单主服务模块 | 模块少,但消息服务类与工具类分层要清晰 |
BNBS | 父 POM 聚合多个业务层与多个 union-app 启动壳 | 启动壳只做装配,不承载核心业务逻辑 |
BCPS | 父 POM 聚合接入层、核心层、基础层、批量层 | online 与 core-* 依赖方向必须单向,从接入层指向核心层 |
额外规则:
BNBS的union-app模块负责把多个业务子模块组装成可运行单元,公共逻辑应下沉到业务模块或facility。BCPS的service-api、facility、infrastructure应保持稳定接口,避免反向依赖online层。
4. Java 包结构与分层规范
4.1 基础包名
以下为多数系统采用的业务包名前缀。历史系统可能存在兼容包根或更深一层的实际顶层包,例如 BGLP 常见业务包为 cn.webank.cnc.glp.core,部分启动类位于 cn.webank.cnc。
cn.webank.cnc.{system-abbrev}| 系统 | 缩写 | 完整包名 |
|---|---|---|
| BFON | bfon | cn.webank.cnc.bfon |
| BLMS | blms | cn.webank.cnc.blms |
| BCDPS | bcdps | cn.webank.cnc.bcdps |
| BCRC-TCS | crc | cn.webank.cnc.crc |
| BCVS | cvs | cn.webank.cnc.cvs |
| BFPS-AP | bfpsap | cn.webank.cnc.bfpsap |
| BGLP | glp | cn.webank.cnc.glp.core(常见业务包) |
4.2 模块内包分层结构
cn.webank.cnc.{abbrev}.service/
├── {System}ServiceApplication.java # 启动类(在 service 包根)
│
├── controller/ # 控制器层(Web API)
│ └── {Feature}Controller.java
│ └── {Feature}ControllerV1.java # 版本化控制器
│
├── service/ # 业务服务层
│ ├── {Feature}Service.java # 业务接口
│ ├── impl/ # 实现类(可选,按复杂度决定)
│ │ ├── {Feature}ServiceImpl.java
│ │ ├── api/ # API 服务实现(BLMS 模式)
│ │ │ └── SyncServiceS0001.java
│ │ └── enhancer/ # 消息增强器(策略模式)
│ ├── common/
│ │ └── AbstractBaseService.java # 模板方法基类
│ ├── ServiceHandler.java # 服务处理器接口
│ └── ServiceHandlerHolder.java # 服务注册/路由器
│
├── dao/ # 数据访问层
│ ├── {Entity}Dao.java # DAO 接口
│ └── (无 Impl,由 MyBatis XML 实现代理)
│
├── model/ # 数据模型
│ ├── {Entity}.java # 实体类
│ ├── {Entity}DTO.java # 数据传输对象
│ ├── request/ # 请求模型(BFON 模式)
│ │ ├── {TransactionId}CmdReq.java
│ │ └── {TransactionId}CmdRsp.java
│ └── exceptions/ # 自定义异常
│ └── {ErrorType}Exception.java
│
├── enums/ # 枚举定义
│ ├── {Concept}Enum.java
│ └── {Status}Enum.java
│
├── constants/ # 常量定义
│ ├── CommonValues.java
│ └── CmdNoConstants.java
│
├── config/ # 配置类
│ └── {Feature}Config.java
│
├── rmb/ # RMB 消息端点定义
│ └── MyRmbService.java
│
├── rmbhandler/ # RMB 消息处理器
│ └── Sync{Feature}Handler.java
│
├── rmb/action/notice/ # RMB 异步通知监听(BFON 模式)
│ └── SyncRmbListener.java
│
├── task/ # 异步任务
│ └── {Feature}TaskHandler.java
│
├── batch/ # 批处理(可选模块)
│ ├── config/
│ │ └── {System}BatchConfig.java
│ └── scheduler/
│ └── {Feature}Scheduler.java
│
├── annotation/ # 自定义注解
│ └── Attribute.java
│
├── util/ # 工具类
│ ├── {Purpose}Util.java
│ ├── BeanUtil.java # Spring 上下文持有者
│ ├── LogUtil.java # 日志工具
│ ├── RmbUtil.java # RMB 消息构造
│ ├── ThreadContext.java # 线程上下文
│ ├── ParamCheckUtil.java # 参数校验
│ └── FpsAgent.java # 文件平台服务
│
└── protocol/ # 协议定义(BFON 模式)
├── {Feature}Req.java
├── {Feature}Rsp.java
└── dto/
└── {Feature}Dto.java4.3 分层调用规则
┌──────────────────────────────────────────────┐
│ Controller / RMB Listener │ 入口层
│ ↓ 调用 │
│ Service │ 业务层
│ ↓ 调用 │
│ DAO / DynamicProxyGate / RmbUtil │ 数据层 / 集成层
│ ↓ 访问 │
│ Database / External Service │ 持久层
└──────────────────────────────────────────────┘严格规则:
- Controller 只能调用 Service,禁止直接访问 DAO
- Service 之间可以相互调用,但避免循环依赖
- DAO 接口由 MyBatis 代理实现,不需要手写实现类
- 工具类(
util/)是静态方法,无状态,可被任意层调用
4.4 四仓典型包结构差异
| 仓库 | 典型包结构 | 开发规范重点 |
|---|---|---|
BFON | controller / service / dao / model / protocol / rmb | 强类型 DTO、REST 控制器、远程代理调用清晰分层 |
BLMS | rmb / service / service.impl.api / util / annotation | Map 报文、模板方法基类、ServiceCode 路由 |
BNBS | context / state / action / flowAction / facility / remote | 以状态机编排为主,链路围绕上下文与动作流转展开 |
BCPS | online / subserver / facade / core / repository / facility | 适配层、Facade、核心域分层明确,不跨层直连 |
BNBS 推荐链路:
RMB / CGI 入口
-> 公共服务入口
-> Context 构建
-> State / Action / FlowAction
-> DAO / Remote / FacilityBCPS 推荐链路:
RmbService
-> OnlineServiceImpl
-> subserver / IBiz
-> OnlineFacadeService
-> CoreOnlineFacade
-> Core Domain / Repository5. 命名规范
5.1 文件命名
| 类型 | 命名模式 | 示例 |
|---|---|---|
| 启动类 | {System}{Module}Application.java | BfonServiceApplication.java |
| 控制器 | {Feature}Controller.java 或 {Feature}ControllerV{N}.java | LoanInfoControllerV1.java |
| 服务接口 | {Feature}Service.java | CustomerInfoBfonService.java |
| 服务实现 | {Feature}ServiceImpl.java 或 {Type}Service{Code}.java | SyncServiceS0001.java |
| DAO 接口 | {Entity}Dao.java | BlkListDao.java |
| 模型/实体 | {Entity}.java | BlkList.java |
| DTO | {Feature}Dto.java 或 {Feature}DTO.java | WhiteListDTO.java |
| 请求对象 | {TransactionId}CmdReq.java 或 {Feature}Req.java | C215003CmdReq.java |
| 响应对象 | {TransactionId}CmdRsp.java 或 {Feature}Rsp.java | C215005CmdRsp.java |
| 枚举 | {Concept}Enum.java | RspCodeEnum.java |
| 常量 | {Purpose}Constants.java 或 CommonValues.java | CmdNoConstants.java |
| 配置类 | {Feature}Config.java | BfonBatchConfig.java |
| 工具类 | {Purpose}Util.java | RmbUtil.java |
| 异常 | {ErrorType}Exception.java | ServiceNotExistException.java |
| 测试 | {ClassUnderTest}Test.java | BlkListDaoTest.java |
| MyBatis Mapper | {DaoName}.map.xml | BlkListDao.map.xml |
5.2 数据库对象命名
| 对象 | 命名模式 | 示例 |
|---|---|---|
| 业务表 | TM_{ENTITY_NAME} | TM_BLK_LIST、TM_PARA_DICT |
| 批次文件表 | YAK_{PURPOSE} | YAK_FILE_IMPORT |
| Spring Batch 表 | BATCH_{TABLE_NAME} | BATCH_JOB_EXECUTION |
| 序列表 | TM_SEC_SEQ | TM_SEC_SEQ |
| DDL 脚本 | {N}_ddl_{database}_create.sql | 001_ddl_blmsdb_create.sql |
| DML 脚本 | {N}_dml_{TABLE}_{action}.sql | 001_dml_TM_PARA_DICT_insert.sql |
| 单表 DDL | {TABLE_NAME}.sql | TM_PARA_DICT.sql |
5.3 配置键命名
| 类型 | 命名模式 | 示例 |
|---|---|---|
| 应用配置 | {module}.{feature}.{property} | bfon-service.properties → spring.datasource.url |
| i18n 键(系统级) | sys_{system}_{module}_i18n_msg_{N} | sys_bfon_bfon-service_i18n_msg_67 |
| i18n 键(应用级) | app_{system}_{module}_user_i18_msg_{N} | app_bfon_bfon-service_user_i18_msg_7 |
| i18n 键(数据字典) | db_{db}_{TABLE}|{COLUMN}|{value} | db_bfondb_TM_PARA_DICT|DICT_VALUE|{entry} |
5.4 命名风格总览
| 类型 | 风格 | 示例 |
|---|---|---|
| Java 类名 | PascalCase | LoanInfoControllerV1 |
| Java 方法名 | camelCase | queryLoanListInfo |
| Java 常量 | UPPER_SNAKE_CASE | ECIF_APP_TYPE |
| Java 包名 | 全小写,点分隔 | cn.webank.cnc.bfon.service |
| 数据库表名 | UPPER_SNAKE_CASE | TM_BLK_LIST |
| 数据库列名 | UPPER_SNAKE_CASE | LIST_TYPE |
| URL 路径 | 小写,连字符分隔 | /rfon/web/loan/action |
| URL 端点 | camelCase | /query/loanListInfo |
| Maven artifactId | 小写,连字符分隔 | bfon-service |
| Properties 键 | 小写,点分隔 | rmb.280016.serviceId |
6. 配置管理规范
6.1 配置文件清单
每个可部署模块必须在 conf/ 和 conf-local/ 下包含以下配置文件:
| 文件 | 必须 | 说明 |
|---|---|---|
application.properties | ✅ | Spring Boot 主配置:端口、数据源、日志 |
{module}.properties | ✅ | 模块业务配置:实例ID、系统ID、服务参数 |
{module}-env.properties | ✅ | 环境变量:DCN、数据库连接串、中间件地址 |
{module}-nodes.env | ✅ | 节点拓扑:JAVA_HOME、DCN、IDC、IP 列表 |
rmb-client.properties | ✅ | RMB 客户端:系统ID、DCN、WeMQ 配置 |
gns-api.properties | ✅ | GNS 全局命名服务:超时配置 |
log4j2.xml | ✅ | 日志配置:appender、滚动策略 |
weservice.properties | 推荐 | WeService 框架配置 |
sso.client.properties | 按需 | SSO/CAS 单点登录配置(仅 Web 服务) |
6.2 application.properties 核心配置项
# 服务配置
server.port=6885
server.servlet.contextPath=/b-rfon-web
# 数据源(由 SDK 自动配置,此处仅覆盖)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 缓存
spring.cache.type=SIMPLE
# MyBatis
mybatis-plus.mapper-locations=classpath*:mapper/**/*.map.xml
# 日志
logging.config=classpath:log4j2.xml6.3 环境变量模板
生产配置使用 [@...] 模板占位符,由部署系统在运行时替换:
# conf/ (生产)
spring.datasource.url=jdbc:mysql://[@DB_HOST]:[@DB_PORT]/[@DB_NAME]
spring.datasource.password=[@DB_PASSWORD]# conf-local/ (开发)
spring.datasource.url=jdbc:mysql://localhost:3306/bfondb
spring.datasource.password=dev_password_1236.4 日志配置规范(log4j2.xml)
必须配置三类日志 Appender:
| Appender | 用途 | 滚动策略 |
|---|---|---|
| AppLog | 应用日志(INFO+) | 按天 + 500MB |
| TradeLog | 交易日志(接口调用追踪) | 按天 + 500MB |
| RmbLog | RMB 消息日志 | 按天 + 500MB |
7. 数据库规范
7.1 脚本组织
dbScript/
├── {version-date}/ # 按发版日期组织
│ └── tdsql/adm/{database-name}/
│ ├── ddl/
│ │ ├── 001_ddl_{db}_create.sql # 增量 DDL
│ │ └── 002_ddl_{db}_alter.sql
│ └── dml/
│ ├── 001_dml_{TABLE}_insert.sql # 种子数据/初始数据
│ └── 002_dml_{TABLE}_update.sql7.2 元数据管理
metadata/
└── tdsql/adm/{database-name}/
├── ddl/ # 完整表结构(最新全量)
│ ├── TM_{TABLE_A}.sql # 每个表一个文件
│ ├── TM_{TABLE_B}.sql
│ └── ...
└── dml/ # 种子数据
└── {N}_dml_{TABLE}_{action}.sql规则:
dbScript/存放增量变更,metadata/存放全量最新定义- 每次发版在
dbScript/下新建日期目录 metadata/应始终反映最新完整 schema- 表结构 DDL 文件名与表名一致
7.3 MyBatis Mapper 规范
- Mapper XML 文件名:
{DaoName}.map.xml - 存放位置:
src/main/resources/mapper/ - 使用
<foreach>进行批量操作 - MySQL 特性:使用
ON DUPLICATE KEY UPDATE实现 upsert - DAO 接口不需要实现类,由 MyBatis 动态代理
7.4 表设计约定
| 约定 | 说明 |
|---|---|
| 表名前缀 | TM_(主表)、YAK_(批处理文件表)、BATCH_(框架表) |
| 字符集 | utf8 / utf8mb3,排序规则 utf8_bin |
| 主键 | 自增 ID 或业务唯一键 |
| 状态字段 | 使用枚举值(SUBMIT、PASS、REJECT 等) |
| 时间字段 | CREATE_TIME、UPDATE_TIME,格式 yyyy-MM-dd HH:mm:ss |
7.5 四仓数据库落位差异
| 仓库 | 数据库模式 | 开发规范重点 |
|---|---|---|
BFON | 单业务库模式(如 bfondb) | 增量脚本与 metadata 配套维护 |
BLMS | 单业务库模式(blmsdb),dbScript 中保留 lmsdb 历史命名 | 文档与连接配置以真实运行库名为准 |
BNBS | 按产品域、中台、批量能力拆分模块,数据库对象也随模块分域 | 先定位模块归属,再定位脚本目录与 Mapper |
BCPS | 运行库 + 核心库双层模式(bcpsdb、bcpscoredb) | 运行态数据与核心账务/借据域分开维护,不混用 Mapper |
8. 服务间通信规范
8.1 通信方式总览
| 方式 | 方向 | 适用场景 | 实现技术 |
|---|---|---|---|
| DynamicProxyGate | 同步出站 | 调用其他 COE 系统的远程服务 | BFON 主要方式 |
| RMB 同步调用 | 同步出站 | 调用 RMB 注册的服务 | BLMS 主要方式 |
| RMB 异步通知 | 异步入站 | 接收上游系统的消息通知 | @RmbMap 注解 |
| FPS 文件传输 | 异步 | 批量文件上传/下载 | FpsClientUtil |
| REST API | 同步入站 | 对前端/运营平台暴露 HTTP 接口 | Spring MVC @RequestMapping |
8.2 DynamicProxyGate 模式(BFON)
通过动态代理网关调用远程服务:
// 1. 定义枚举标识远程服务
public enum RemoteBizServiceEnum {
LOAN_LIST_QUERY,
LOAN_DETAIL_QUERY,
CUSTOMER_INFO_QUERY,
// ...
}
// 2. 在 Service 中调用
RemoteServiceResp resp = (RemoteServiceResp) dynamicProxyGate.call(
RemoteBizServiceEnum.LOAN_LIST_QUERY.name(), // 服务标识
"COE", // 目标系统标识
request // 请求参数
);8.3 RMB 消息模式(BLMS / 通用)
入站端点定义:
@Component
public class MyRmbService {
@RmbMap(serviceId = "${rmb.sync.serviceId}", scenario = "${rmb.sync.scenario}")
public Message syncList(Message request) {
// 处理请求并返回响应
return SyncListHandler.handle(request);
}
}出站调用:
// 同步调用
Message response = RmbUtil.syncSend(requestMsg, targetServiceId);
// 或使用 SDK 工具
RmbClientUtil.sendRequest(message, serviceId, scenario);8.4 REST API 模式
URL 路径规范:
/{system-prefix}/web/{feature}/action/{operation}| 示例 | 说明 |
|---|---|
/rfon/web/loan/action/query/loanListInfo | 贷款列表查询 |
/rfon/web/customer/action/query/custInfo | 客户信息查询 |
/rfon/web/txn/action/query/txnRecordList | 交易记录查询 |
/web/common/settings/query | 公共设置查询 |
Controller 规范:
@RestController
@RequestMapping("/rfon/web/loan/action")
public class LoanInfoControllerV1 {
@Autowired
private LoanInfoService loanInfoService;
@PostMapping("/query/loanListInfo")
public RespBase queryLoanListInfo(@RequestBody LoanListReq req) {
return loanInfoService.queryLoanList(req);
}
}8.5 四仓通信模式对照
| 仓库 | 主要入站 | 主要出站 | 开发规范重点 |
|---|---|---|---|
BFON | REST API、部分 RMB 通知 | DynamicProxyGate、文件平台 | 面向前台/运营接口,控制器协议要稳定 |
BLMS | @RmbMap 消息入口 | RmbUtil.syncSend | 围绕消息报文和路由码组织,不引入多余 HTTP 抽象 |
BNBS | RMB 主入口,部分 CGI 渠道入口 | RMB、remote / facility 封装 | 入口多,但业务编排应统一收敛到上下文状态机 |
BCPS | RMB 主入口、批量/DCN 入口 | 核心域 Facade、仓库层 | 外层协议与核心域解耦,交易复用通过 Facade 实现 |
9. 请求处理与路由规范
9.1 模板方法模式(BLMS 推荐)
使用 AbstractBaseService 抽象基类定义处理骨架:
public abstract class AbstractBaseService implements ServiceHandler {
@Override
public final void handle(Map<String, Object> request, Map<String, Object> response) {
// 1. 参数校验
checkParam(request);
// 2. 业务处理
doService(request, response);
// 3. 后置处理
postProcess(request, response);
}
protected abstract void checkParam(Map<String, Object> request);
protected abstract void doService(Map<String, Object> request, Map<String, Object> response);
protected void postProcess(Map<String, Object> request, Map<String, Object> response) {
// 默认空实现,子类可覆盖
}
}9.2 ServiceCode 路由模式(BLMS)
通过 ServiceCode 实现 Bean 名称路由:
// 1. 定义 ServiceCode 枚举
public enum ServiceCodeEnum {
S001("S001", "SyncServiceS0001"), // 公共名单查询
S002("S002", "SyncServiceS0002"), // 黑名单查询
S003("S003", "SyncServiceS0003"), // 在线黑名单新增
;
private static final Map<String, ServiceCodeEnum> CODE_MAP = new HashMap<>();
static {
for (ServiceCodeEnum e : values()) {
CODE_MAP.put(e.code, e);
}
}
public static ServiceCodeEnum fromCode(String code) {
return CODE_MAP.get(code);
}
public String getBeanName() { return beanName; }
}
// 2. 路由器
@Component
public class ServiceHandlerHolder {
@Autowired
private ApplicationContext context;
public ServiceHandler getHandler(String serviceCode) {
ServiceCodeEnum codeEnum = ServiceCodeEnum.fromCode(serviceCode);
return context.getBean(codeEnum.getBeanName(), ServiceHandler.class);
}
}
// 3. 消息分发器
public class SyncListHandler {
public Message handle(Message request) {
Map<String, Object> body = parseBody(request);
String serviceCode = (String) body.get("serviceCode");
ServiceHandler handler = handlerHolder.getHandler(serviceCode);
handler.handle(requestMap, responseMap);
return buildResponse(responseMap);
}
}9.3 服务实现命名规范(BLMS 模式)
| 命名模式 | 类型 | 示例 |
|---|---|---|
SyncServiceS{NNNN} | 同步服务 | SyncServiceS0001 |
AsyncServiceA{NNNN} | 异步服务 | AsyncServiceA0001 |
FileServiceS{NNNN} | 文件处理服务 | FileServiceS0004 |
9.4 四仓路由风格归纳
| 仓库 | 路由主模式 | 推荐开发规范 |
|---|---|---|
BFON | Controller -> Service -> DAO / Remote | 以交易语义划分接口,DTO 与远程服务枚举成对维护 |
BLMS | @RmbMap -> Handler -> ServiceCode Bean | 新交易优先复用模板方法骨架,避免散落式 if/else |
BNBS | 统一入口 -> Context -> State / Action / FlowAction | 新流程优先扩展动作节点,不在入口类堆叠分支 |
BCPS | RmbService -> OnlineServiceImpl -> subserver -> Facade -> Core | 新交易优先接入 subserver 与 Facade,不直接下钻核心仓库层 |
10. 数据模型与传输对象规范
10.1 实体类(Model)
使用 Lombok(推荐,BFON 风格):
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class LoanListInfoDto {
@JsonProperty("loan_no")
private String loanNo;
@JsonProperty("cust_name")
private String custName;
@JsonProperty("loan_amount")
private BigDecimal loanAmount;
}使用自定义注解(BLMS 风格):
public class BlkList {
@Attribute("LIST_TYPE")
private String listType;
@Attribute("CERT_TYPE")
private String certType;
// 手动 getter/setter
}10.2 请求/响应对象
BFON 模式 — 按交易码命名:
@Data
public class C215003CmdReq {
private AppAcctInfo appAcctInfo;
private QueryInfo queryInfo;
@Data
public static class AppAcctInfo {
private String certType;
private String certNo;
}
@Data
public static class QueryInfo {
private String acctType;
private String acctNo;
}
}BLMS 模式 — 使用 Map:
// 请求和响应均为 Map<String, Object>
public void doService(Map<String, Object> request, Map<String, Object> response) {
String listType = (String) request.get("listType");
// ...
response.put("resultCode", "0000");
response.put("resultMsg", "success");
}10.3 统一响应封装
// BFON 模式
public class RespBase {
private String retCode;
private String retMessage;
private Object retData;
}
// 成功
return RespBaseUtil.returnSuccessResp(data);
// 失败
return RespBaseUtil.returnErrorResp(exception);
// BLMS 模式(Map 内嵌)
response.put("rspCode", RspCodeEnum.SUCCESS.getCode());
response.put("rspMsg", RspCodeEnum.SUCCESS.getMsg());10.4 分页模式
// 请求分页参数
@Data
public static class Page {
private Integer pageStart; // 起始页码
private Integer pageSize; // 每页条数
}
// 响应分页封装
public class PageContainer<T> {
private List<T> rows; // 数据列表
private Integer total; // 总条数
}10.5 内部静态类嵌套
复杂请求对象使用内部静态类组织嵌套结构:
@Data
public class C222323CmdReq {
private String loanNo;
private Control control;
@Data
public static class Control {
private String channelType;
private String operatorId;
}
}11. 异常处理规范
11.1 自定义异常
// 业务异常
public class ServiceNotExistException extends Exception {
public ServiceNotExistException(String message) {
super(message);
}
}
// 序列化异常
public class JsonSerializeException extends Exception { ... }
public class JsonReSerializeException extends Exception { ... }11.2 异常处理模式
Controller 层(BFON):
@PostMapping("/query/loanListInfo")
public RespBase queryLoanListInfo(@RequestBody LoanListReq req) {
try {
return loanInfoService.queryLoanList(req);
} catch (Exception e) {
log.error("借据列表查询异常", e);
return RespBaseUtil.returnErrorResp(e);
}
}Service 层(BLMS 模板方法):
@Override
protected void doService(Map<String, Object> request, Map<String, Object> response) {
try {
// 业务逻辑
} catch (ServiceNotExistException e) {
response.put("rspCode", RspCodeEnum.SERVICE_NOT_EXIST.getCode());
response.put("rspMsg", e.getMessage());
} catch (Exception e) {
response.put("rspCode", RspCodeEnum.SYSTEM_ERROR.getCode());
response.put("rspMsg", "系统异常");
LogUtil.error(e, "业务处理异常");
}
}11.3 响应码枚举规范
public enum RspCodeEnum {
SUCCESS("0000", "成功"),
PARAM_ERROR("1001", "参数错误"),
SERVICE_NOT_EXIST("2001", "服务不存在"),
SYSTEM_ERROR("9999", "系统异常"),
;
private final String code;
private final String msg;
}12. 日志与交易追踪规范
12.1 日志工具封装
// 使用 SDK 封装的 LogUtil
LogUtil.info("开始查询借据列表, loanNo={}", loanNo);
LogUtil.error(e, "借据列表查询异常, loanNo={}", loanNo);
// 或使用 SLF4J(推荐)
private static final Logger log = LoggerFactory.getLogger(LoanInfoService.class);
log.info("查询客户信息, certType={}, certNo={}", certType, certNo);12.2 交易日志
通过 MyTradeLogUtil 记录接口调用的完整追踪信息:
MyTradeLogUtil.logSyncCall(
interfaceDesc, // 接口描述
serviceCode, // 服务码
requestTime, // 请求时间
responseTime, // 响应时间
rspCode, // 响应码
bizSeqNo // 业务流水号
);12.3 线程上下文追踪
使用 ThreadContext 维护请求级元数据:
// 设置上下文
ThreadContext.setBizSeqNo("202606050001");
ThreadContext.setConsumerId("BLMS");
ThreadContext.setOrgSysId("BFON");
// 获取上下文
String bizSeqNo = ThreadContext.getBizSeqNo();
// 请求结束后清理(防止 ThreadLocal 泄漏)
ThreadContext.clear();13. 国际化(i18n)规范
13.1 资源文件组织
src/main/resources/messages/
├── messages_app_en.properties # 应用级英文
├── messages_app_zh_CN.properties # 应用级中文
├── messages_sys_en.properties # 系统级英文
└── messages_sys_zh_CN.properties # 系统级中文13.2 Key 命名规范
# 系统级消息
sys_{system}_{module}_i18n_msg_{N}
# 示例
sys_bfon_bfon-service_i18n_msg_67=Loan list query error
# 应用级消息
app_{system}_{module}_user_i18_msg_{N}
# 示例
app_bfon_bfon-service_user_i18_msg_7=User not found
# 数据字典
db_{db}_{TABLE}|{COLUMN}|{value}
# 示例
db_bfondb_TM_PARA_DICT|DICT_VALUE|LOAN_TYPE=Loan Type13.3 代码调用方式
// 使用 I18nUtil 获取国际化消息,第二个参数为中文默认值
String message = I18nUtil.getMsgValue(
"sys_bfon_bfon-service_i18n_msg_67",
"借据列表查询异常"
);14. 测试规范
14.1 测试框架
| 依赖 | 版本参考 | 用途 |
|---|---|---|
| JUnit 4 | 4.x | 测试框架 |
| Spring Boot Test | 随 Boot 版本 | 集成测试 |
| Mockito | 3.9.0 | Mock 框架 |
| PowerMock | 2.0.9 | 静态方法 Mock |
| H2 Database | 2.2.220 | 内存数据库(替代 TDSQL) |
| JaCoCo | Maven 插件 | 代码覆盖率 |
14.2 测试文件组织
src/test/
├── java/cn/webank/cnc/{abbrev}/
│ ├── {ClassUnderTest}Test.java # 单元测试
│ └── dao/
│ └── {Entity}DaoTest.java # DAO 集成测试
└── resources/
├── log4j2.xml # 测试日志配置
├── rmb-client.properties # 测试 RMB 配置
├── service-context-test-dao.xml # 测试 Spring 上下文
└── {system}-service/
├── {module}.properties # 测试应用配置
└── {module}-env.properties # 测试环境配置14.3 测试编写规范
// 使用自定义 Runner(含日志初始化)
@RunWith(Junit4ClassRunnerWithLog.class)
@ContextConfiguration(locations = {"classpath:service-context-test-dao.xml"})
public class BlkListDaoTest {
@Autowired
private BlkListDao blkListDao;
@Test
// @Ignore // 需要数据库连接时默认跳过
public void testQueryByCertNo() {
BlkList result = blkListDao.queryByCertNo("123456");
assertNotNull(result);
}
}14.4 代码覆盖率配置
JaCoCo 插件排除规则:
- 排除
model/、constants/、enums/包 - 排除
*Application.java启动类 - 排除
config/配置类 - 目标覆盖率:≥ 60%(行覆盖)
14.5 架构测试(ArchUnit)
- 配置文件:
META-INF/zcnc-sdk/archRule.properties - 忽略模式:
archunit_ignore_patterns.txt - 在 CI 环境自动执行
15. 构建与部署规范
15.1 本地构建
# 完整构建
mvn clean install -DskipTests
# 仅构建特定模块
mvn clean install -pl {system}-service -am
# 运行测试
mvn test
# 生成覆盖率报告
mvn jacoco:report15.2 产出物
| 模块 | 产出文件 | 说明 |
|---|---|---|
*-service | {system}-service.jar | Spring Boot 可执行 JAR |
*-batch | {system}-batch.jar | Spring Boot 可执行 JAR |
*-common | {system}-common.jar | 普通 JAR(依赖库) |
*-dao | {system}-dao.jar | 普通 JAR(依赖库) |
15.3 部署目录结构
{deploy-root}/
├── bin/ # 部署脚本
├── conf/ # 配置文件
├── lib/ # 应用 JAR
└── logs/ # 日志输出15.4 JVM 参数规范
# 标准参数(start.sh 中定义)
java -jar {system}-service.jar \
-Xms1024m -Xmx1024m \
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \
-XX:+UseConcMarkSweepGC \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=./logs/heapdump.hprof \
-Xloggc:./logs/gc.log \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=${jmxPort} \
--spring.config.location=file:./conf/ \
-Denv.root=./15.5 Docker 部署(可选)
# docker-run.sh
docker run -d \
--name {system}-service \
-p ${AppPort}:${AppPort} \
-v ./conf:/app/conf \
-v ./logs:/app/logs \
{registry}/{system}-service:{version}15.6 本地运行
// 启动类中使用 LocalRun 工具
public static void main(String[] args) {
SpringApplication.run(
BfonServiceApplication.class,
LocalRun.prepareLocalRun(args, "bfon-service")
);
}LocalRun.prepareLocalRun() 自动设置:
- 从
conf-local/加载配置 - 设置本地环境变量
- 覆盖数据源为本地开发库
16. 代码风格与工具约定
16.1 Lombok 使用规范
| 注解 | 使用场景 |
|---|---|
@Data | 所有 DTO、Request、Response 类 |
@Slf4j | 需要日志的类(替代手写 LoggerFactory.getLogger()) |
@Builder | 构建复杂对象(可选) |
@NoArgsConstructor / @AllArgsConstructor | 与 @Data 配合使用(可选) |
16.2 Spring 注解规范
| 注解 | 使用场景 |
|---|---|
@SpringBootApplication | 启动类,配合 scanBasePackages |
@EnableTransactionManagement | 事务管理(mode = AdviceMode.ASPECTJ) |
@EnableCaching | 缓存(mode = AdviceMode.ASPECTJ) |
@EnableAspectJAutoProxy | AOP 代理 |
@EnableBatchProcessing | Spring Batch(modular = true) |
@EnableScheduling | 定时任务 |
@MapperScans | MyBatis Mapper 扫描 |
@RestController / @Controller | Web 控制器 |
@Component | Spring Bean 注册 |
@Autowired | 依赖注入 |
16.3 Jackson 注解规范
@Data
@JsonIgnoreProperties(ignoreUnknown = true) // 前向兼容
public class SomeDto {
@JsonProperty("snake_case_field") // 映射蛇形命名字段
private String camelCaseField;
}16.4 AspectJ 与 Lombok 兼容
在 POM 中配置 aspectj-maven-plugin 以解决 Lombok 与 AspectJ 的兼容性问题(仅当使用 @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) 时需要)。
16.5 Spring Boot 启动类模板
@SpringBootApplication(
exclude = JpaRepositoriesAutoConfiguration.class,
scanBasePackages = {
"cn.webank.cnc.fct", // FCT 基础框架
"cn.webank.cnc.{parent}", // 父系统包(如 rfon)
"cn.webank.cnc.ccl.midweb.**", // CCL 中间件
"cn.webank.cnc.{abbrev}" // 当前系统
}
)
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@EnableCaching(mode = AdviceMode.ASPECTJ)
@EnableAspectJAutoProxy
@EnableBatchProcessing(modular = true) // 按需
@EnableScheduling // 按需
@MapperScans({
@MapperScan("cn.webank.cnc.{abbrev}.dao"),
@MapperScan("cn.webank.cnc.fct.dao") // 共享 DAO
})
public class {System}ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(
{System}ServiceApplication.class,
LocalRun.prepareLocalRun(args, "{system}-service")
);
}
}17. 附录:四仓开发规范对照
17.1 四仓总览
| 维度 | BFON | BLMS | BNBS | BCPS |
|---|---|---|---|---|
| 定位 | 信贷产品运营平台 | 名单管理系统 | 产品域编排与资金批扣平台 | 信用核心平台 |
| 基础包 | cn.webank.cnc.bfon | cn.webank.cnc.blms | cn.webank.cnc.bnbs | cn.webank.cnc.bcps |
| 顶层结构 | service / batch / common | service 单主模块 | cgi / adm / dcn / mid / batch / union-app | online / batch-dcn / core-* / service-api / facility / infrastructure |
| 主要入站 | REST | RMB | RMB + CGI | RMB + 批量/DCN |
| 主要出站 | DynamicProxyGate | RMB 同步调用 | RMB + remote/facility | Facade + Core Domain |
| 数据传输 | 强类型 DTO | Map<String, Object> | Context 驱动对象 | 交易报文 + Facade 对象 |
| 数据库模式 | 单业务库 | 单业务库 | 分域拆分 | 运行库 + 核心库 |
17.2 BFON 开发规范
- 以
REST Controller + Service + DAO为主链路,接口边界清晰,适合前台与运营场景。 - 新功能优先落在
service模块;批量、调度、文件作业落在batch模块;共享模型和工具下沉到common。 - 外部系统调用优先通过
DynamicProxyGate或统一代理封装,避免把远程协议散落到业务类。 - 请求和响应对象推荐强类型 DTO,命名可按交易码或业务语义组织。
- 如果新增异步通知链路,可放在
rmb/action/notice一类目录,但不要反向污染 REST 主链路。
17.3 BLMS 开发规范
- 以
@RmbMap为统一入站,围绕ServiceCode做消息分发。 - 业务实现类优先继承模板方法基类,把参数校验、业务处理、后置处理拆清楚。
- 报文主体通常使用
Map<String, Object>,开发时要集中封装取值、判空、响应码处理。 service.impl.api一类目录是核心交易落点,新增交易应遵循SyncServiceS{NNNN}这类命名模式。- 该仓库更轻量,避免为了“看起来统一”而额外引入复杂分层。
17.4 BNBS 开发规范
- 不要把
BNBS误读成普通三段式服务,它更像“渠道网关 + 产品编排 + 资金/批扣中台”的组合平台。 - 开发时优先按运行角色识别模块:
cgi面向渠道接入,service-adm/service-dcn面向业务编排,mid面向中台能力,union-app面向联合部署。 - 主链路推荐围绕
context / state / action / flowAction扩展,新流程优先增加动作节点或状态节点,不在入口类堆叠大量条件分支。 - 远程调用、公共能力、数据访问应分别下沉到
remote、facility、DAO/Mapper 层,避免把集成逻辑写进action。 - 多个启动壳并存是正常模式,启动壳负责组装,不承载复杂业务。
17.5 BCPS 开发规范
BCPS是重型信用核心,规范核心不在“模块少”,而在“接入层、Facade 层、核心域层边界清楚”。- 新交易一般沿
RmbService -> OnlineServiceImpl -> subserver -> OnlineFacadeService -> CoreOnlineFacade -> Core Domain链路接入。 subserver层负责交易适配和路由,Facade 负责交易归并与领域编排,核心域负责借据、额度、入账、重组等真实业务。service-api、facility、infrastructure要保持基础能力定位,不能被online反向污染。- 数据库开发要区分运行库与核心库,Mapper、事务和领域对象不要跨库混杂。
17.6 什么时候套用哪套规范
| 场景 | 优先参考仓库 |
|---|---|
| 新增前台/运营 HTTP 接口 | BFON |
| 新增名单查询、名单上报、消息型服务 | BLMS |
| 新增产品流程编排、状态机节点、资金批扣链路 | BNBS |
| 新增核心授信、借据、入账、重组、额度交易 | BCPS |
17.7 四仓共同规范
| 维度 | 共同点 |
|---|---|
| 技术栈 | Spring Boot + MyBatis + RMB + TDSQL |
| 构建 | Maven 多模块 + zcnc-sdk-starter-parent |
| 部署 | bin/ 脚本 + conf/ 外部化配置 |
| 数据库 | dbScript/(增量) + metadata/(全量) |
| 日志 | Log4j2 + 交易日志 |
| 包结构 | cn.webank.cnc.{abbrev}.{layer} |
| 文件命名 | {Feature}{Type}.java(PascalCase) |
| 依赖 | Guava、Lombok、Jackson、RMB SDK |
18. 规范执行检查清单
新系统或新模块开发时,请逐项检查:
项目结构
- [ ] 顶层目录包含
bin/、conf/、conf-local/、dbScript/、metadata/ - [ ] Maven 模块按职责拆分(common / service / batch / dao)
- [ ] 父 POM 继承自
zcnc-sdk-starter-parent - [ ] 每个 Spring Boot 模块有且仅有一个
*Application.java
代码规范
- [ ] Java 包名遵循
cn.webank.cnc.{abbrev}.{layer} - [ ] 类名遵循 PascalCase,使用正确的后缀(Controller / Service / Dao / Enum / Util)
- [ ] DTO 类使用
@Data+@JsonIgnoreProperties(ignoreUnknown = true) - [ ] 常量类使用
UPPER_SNAKE_CASE - [ ] 枚举类后缀
Enum
配置规范
- [ ]
conf/和conf-local/结构镜像一致 - [ ] 生产配置使用
[@...]模板变量 - [ ] 包含
application.properties、rmb-client.properties、log4j2.xml - [ ] 日志配置包含 AppLog + TradeLog + RmbLog 三个 Appender
数据库规范
- [ ] 增量脚本放
dbScript/{version-date}/ - [ ] 全量表结构放
metadata/ - [ ] 表名使用
TM_前缀 - [ ] MyBatis Mapper 命名为
*Dao.map.xml
通信规范
- [ ] 入站接口定义在
controller/或rmb/ - [ ] 出站调用使用
DynamicProxyGate或RmbUtil - [ ] 文件传输使用
FpsClientUtil - [ ] 响应使用统一封装(
RespBase或 Map + RspCodeEnum)
重型系统补充检查
- [ ]
BNBS新流程优先扩展context / state / action / flowAction,而不是在入口类堆分支 - [ ]
BNBS的union-app仅做装配,不承载核心业务 - [ ]
BCPS新交易按subserver -> Facade -> Core链路接入 - [ ]
BCPS的online、core-*、facility、infrastructure依赖方向保持单向 - [ ] 双库系统明确区分运行库与核心库,不混用 Mapper 与事务边界
部署规范
- [ ]
start.sh定义systemId、AppPort、jmxPort - [ ] JVM 参数包含
-Xms、-Xmx、GC 配置、HeapDump - [ ] 使用
LocalRun.prepareLocalRun()支持本地运行