Skip to content

COE 系统开发规范

基于 BFON(信贷产品运营平台)、BLMS(名单管理系统)、BNBS(产品域编排与资金批扣平台)、BCPS(信用核心平台)四个仓库的代码结构分析,整理出的 COE 体系开发规范。

本文档保留 BFON / BLMS 的基础型规范,同时合并 BNBS / BCPS 的重型系统规范,按“统一原则 + 仓库差异”组织。

目录

  1. 总体架构原则
  2. 项目目录结构规范
  3. Maven 多模块规范
  4. Java 包结构与分层规范
  5. 命名规范
  6. 配置管理规范
  7. 数据库规范
  8. 服务间通信规范
  9. 请求处理与路由规范
  10. 数据模型与传输对象规范
  11. 异常处理规范
  12. 日志与交易追踪规范
  13. 国际化(i18n)规范
  14. 测试规范
  15. 构建与部署规范
  16. 代码风格与工具约定
  17. 附录:四仓开发规范对照
  18. 规范执行检查清单

1. 总体架构原则

1.1 技术栈

层面技术选型
语言Java 21 / Java 8 并存,以各系统 parent POM 版本为准
框架Spring Boot(基于 zcnc-sdk-starter-parent
ORMMyBatis / MyBatis-Plus
消息总线RMB(WeBank Reliable Message Bus)
数据库TDSQL(腾讯分布式 SQL,MySQL 兼容)
连接池Apache DBCP / Druid(由 SDK 自动配置)
构建工具Maven(多模块 reactor)
JSONJackson(主)+ 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 系统必须遵循以下统一顶层目录布局:

text
{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.shstop.shdeploy.shcncboot.sh
conf/生产环境配置文件,按模块分子目录
conf-local/推荐本地/SIT 开发环境配置,结构与 conf/ 镜像
dbScript/数据库增量脚本,按版本日期组织
metadata/推荐最新完整表结构 DDL/DML 定义
pom.xmlMaven 父 POM

2.2 bin/ 脚本规范

text
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.sh

start.sh 必须定义的环境变量

bash
systemId=U543           # 系统编号(全局唯一)
AppPort=6885            # 应用端口
jmxPort=26885           # JMX 监控端口(= 26000 + 应用端口后两位)

2.3 conf/ / conf-local/ 配置目录规范

text
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 的基础模板,但四个仓库并不完全同构,开发时要按实际模式落位:

仓库顶层结构主模式开发规范重点
BFONservice / batch / common标准三段式,REST + 批处理并存
BLMSservice 单主模块轻量消息驱动,围绕 @RmbMapServiceCode 组织
BNBScgi / service-adm / service-dcn / mid / batch-dcn / union-app按运行角色拆模块,允许联合部署壳与中台模块并存
BCPSonline / online-dcn / batch-dcn / core-* / service-api / facility / infrastructure按接入层、核心域、基础设施分层,模块数多且层次深

落地规则

  • BFONBLMS 可以直接套用本节基础模板。
  • BNBSBCPS 不应被强行压缩成 service/common/batch 三段式,而应按“运行入口 + 领域核心 + 基础设施”拆分。
  • union-appcore-*facilityinfrastructure 这类模块属于 COE 合法结构,不算偏离规范。

3. Maven 多模块规范

3.1 父 POM 规范

xml
<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 插件
*-commonjar共享模型、工具类、常量、枚举❌ 不使用
*-servicejar (可执行)主业务服务:Controller、Service、RMB Listenerspring-boot-maven-plugin (repackage)
*-batchjar (可执行)批处理任务:Scheduler、Job Configspring-boot-maven-plugin (repackage)
*-daojar数据访问层:DAO 接口、MyBatis Mapper❌ 不使用

3.3 模块 POM 配置要点

服务模块必须指定主类

xml
<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 聚合接入层、核心层、基础层、批量层onlinecore-* 依赖方向必须单向,从接入层指向核心层

额外规则

  • BNBSunion-app 模块负责把多个业务子模块组装成可运行单元,公共逻辑应下沉到业务模块或 facility
  • BCPSservice-apifacilityinfrastructure 应保持稳定接口,避免反向依赖 online 层。

4. Java 包结构与分层规范

4.1 基础包名

以下为多数系统采用的业务包名前缀。历史系统可能存在兼容包根或更深一层的实际顶层包,例如 BGLP 常见业务包为 cn.webank.cnc.glp.core,部分启动类位于 cn.webank.cnc

text
cn.webank.cnc.{system-abbrev}
系统缩写完整包名
BFONbfoncn.webank.cnc.bfon
BLMSblmscn.webank.cnc.blms
BCDPSbcdpscn.webank.cnc.bcdps
BCRC-TCScrccn.webank.cnc.crc
BCVScvscn.webank.cnc.cvs
BFPS-APbfpsapcn.webank.cnc.bfpsap
BGLPglpcn.webank.cnc.glp.core(常见业务包)

4.2 模块内包分层结构

text
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.java

4.3 分层调用规则

text
┌──────────────────────────────────────────────┐
│  Controller / RMB Listener                    │  入口层
│  ↓ 调用                                       │
│  Service                                       │  业务层
│  ↓ 调用                                        │
│  DAO / DynamicProxyGate / RmbUtil              │  数据层 / 集成层
│  ↓ 访问                                        │
│  Database / External Service                   │  持久层
└──────────────────────────────────────────────┘

严格规则

  • Controller 只能调用 Service,禁止直接访问 DAO
  • Service 之间可以相互调用,但避免循环依赖
  • DAO 接口由 MyBatis 代理实现,不需要手写实现类
  • 工具类(util/)是静态方法,无状态,可被任意层调用

4.4 四仓典型包结构差异

仓库典型包结构开发规范重点
BFONcontroller / service / dao / model / protocol / rmb强类型 DTO、REST 控制器、远程代理调用清晰分层
BLMSrmb / service / service.impl.api / util / annotationMap 报文、模板方法基类、ServiceCode 路由
BNBScontext / state / action / flowAction / facility / remote以状态机编排为主,链路围绕上下文与动作流转展开
BCPSonline / subserver / facade / core / repository / facility适配层、Facade、核心域分层明确,不跨层直连

BNBS 推荐链路

text
RMB / CGI 入口
-> 公共服务入口
-> Context 构建
-> State / Action / FlowAction
-> DAO / Remote / Facility

BCPS 推荐链路

text
RmbService
-> OnlineServiceImpl
-> subserver / IBiz
-> OnlineFacadeService
-> CoreOnlineFacade
-> Core Domain / Repository

5. 命名规范

5.1 文件命名

类型命名模式示例
启动类{System}{Module}Application.javaBfonServiceApplication.java
控制器{Feature}Controller.java{Feature}ControllerV{N}.javaLoanInfoControllerV1.java
服务接口{Feature}Service.javaCustomerInfoBfonService.java
服务实现{Feature}ServiceImpl.java{Type}Service{Code}.javaSyncServiceS0001.java
DAO 接口{Entity}Dao.javaBlkListDao.java
模型/实体{Entity}.javaBlkList.java
DTO{Feature}Dto.java{Feature}DTO.javaWhiteListDTO.java
请求对象{TransactionId}CmdReq.java{Feature}Req.javaC215003CmdReq.java
响应对象{TransactionId}CmdRsp.java{Feature}Rsp.javaC215005CmdRsp.java
枚举{Concept}Enum.javaRspCodeEnum.java
常量{Purpose}Constants.javaCommonValues.javaCmdNoConstants.java
配置类{Feature}Config.javaBfonBatchConfig.java
工具类{Purpose}Util.javaRmbUtil.java
异常{ErrorType}Exception.javaServiceNotExistException.java
测试{ClassUnderTest}Test.javaBlkListDaoTest.java
MyBatis Mapper{DaoName}.map.xmlBlkListDao.map.xml

5.2 数据库对象命名

对象命名模式示例
业务表TM_{ENTITY_NAME}TM_BLK_LISTTM_PARA_DICT
批次文件表YAK_{PURPOSE}YAK_FILE_IMPORT
Spring Batch 表BATCH_{TABLE_NAME}BATCH_JOB_EXECUTION
序列表TM_SEC_SEQTM_SEC_SEQ
DDL 脚本{N}_ddl_{database}_create.sql001_ddl_blmsdb_create.sql
DML 脚本{N}_dml_{TABLE}_{action}.sql001_dml_TM_PARA_DICT_insert.sql
单表 DDL{TABLE_NAME}.sqlTM_PARA_DICT.sql

5.3 配置键命名

类型命名模式示例
应用配置{module}.{feature}.{property}bfon-service.propertiesspring.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 类名PascalCaseLoanInfoControllerV1
Java 方法名camelCasequeryLoanListInfo
Java 常量UPPER_SNAKE_CASEECIF_APP_TYPE
Java 包名全小写,点分隔cn.webank.cnc.bfon.service
数据库表名UPPER_SNAKE_CASETM_BLK_LIST
数据库列名UPPER_SNAKE_CASELIST_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.propertiesSpring Boot 主配置:端口、数据源、日志
{module}.properties模块业务配置:实例ID、系统ID、服务参数
{module}-env.properties环境变量:DCN、数据库连接串、中间件地址
{module}-nodes.env节点拓扑:JAVA_HOME、DCN、IDC、IP 列表
rmb-client.propertiesRMB 客户端:系统ID、DCN、WeMQ 配置
gns-api.propertiesGNS 全局命名服务:超时配置
log4j2.xml日志配置:appender、滚动策略
weservice.properties推荐WeService 框架配置
sso.client.properties按需SSO/CAS 单点登录配置(仅 Web 服务)

6.2 application.properties 核心配置项

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.xml

6.3 环境变量模板

生产配置使用 [@...] 模板占位符,由部署系统在运行时替换:

properties
# conf/ (生产)
spring.datasource.url=jdbc:mysql://[@DB_HOST]:[@DB_PORT]/[@DB_NAME]
spring.datasource.password=[@DB_PASSWORD]
properties
# conf-local/ (开发)
spring.datasource.url=jdbc:mysql://localhost:3306/bfondb
spring.datasource.password=dev_password_123

6.4 日志配置规范(log4j2.xml)

必须配置三类日志 Appender:

Appender用途滚动策略
AppLog应用日志(INFO+)按天 + 500MB
TradeLog交易日志(接口调用追踪)按天 + 500MB
RmbLogRMB 消息日志按天 + 500MB

7. 数据库规范

7.1 脚本组织

text
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.sql

7.2 元数据管理

text
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 或业务唯一键
状态字段使用枚举值(SUBMITPASSREJECT 等)
时间字段CREATE_TIMEUPDATE_TIME,格式 yyyy-MM-dd HH:mm:ss

7.5 四仓数据库落位差异

仓库数据库模式开发规范重点
BFON单业务库模式(如 bfondb增量脚本与 metadata 配套维护
BLMS单业务库模式(blmsdb),dbScript 中保留 lmsdb 历史命名文档与连接配置以真实运行库名为准
BNBS按产品域、中台、批量能力拆分模块,数据库对象也随模块分域先定位模块归属,再定位脚本目录与 Mapper
BCPS运行库 + 核心库双层模式(bcpsdbbcpscoredb运行态数据与核心账务/借据域分开维护,不混用 Mapper

8. 服务间通信规范

8.1 通信方式总览

方式方向适用场景实现技术
DynamicProxyGate同步出站调用其他 COE 系统的远程服务BFON 主要方式
RMB 同步调用同步出站调用 RMB 注册的服务BLMS 主要方式
RMB 异步通知异步入站接收上游系统的消息通知@RmbMap 注解
FPS 文件传输异步批量文件上传/下载FpsClientUtil
REST API同步入站对前端/运营平台暴露 HTTP 接口Spring MVC @RequestMapping

8.2 DynamicProxyGate 模式(BFON)

通过动态代理网关调用远程服务:

java
// 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 / 通用)

入站端点定义

java
@Component
public class MyRmbService {
    @RmbMap(serviceId = "${rmb.sync.serviceId}", scenario = "${rmb.sync.scenario}")
    public Message syncList(Message request) {
        // 处理请求并返回响应
        return SyncListHandler.handle(request);
    }
}

出站调用

java
// 同步调用
Message response = RmbUtil.syncSend(requestMsg, targetServiceId);

// 或使用 SDK 工具
RmbClientUtil.sendRequest(message, serviceId, scenario);

8.4 REST API 模式

URL 路径规范

text
/{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 规范

java
@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 四仓通信模式对照

仓库主要入站主要出站开发规范重点
BFONREST API、部分 RMB 通知DynamicProxyGate、文件平台面向前台/运营接口,控制器协议要稳定
BLMS@RmbMap 消息入口RmbUtil.syncSend围绕消息报文和路由码组织,不引入多余 HTTP 抽象
BNBSRMB 主入口,部分 CGI 渠道入口RMB、remote / facility 封装入口多,但业务编排应统一收敛到上下文状态机
BCPSRMB 主入口、批量/DCN 入口核心域 Facade、仓库层外层协议与核心域解耦,交易复用通过 Facade 实现

9. 请求处理与路由规范

9.1 模板方法模式(BLMS 推荐)

使用 AbstractBaseService 抽象基类定义处理骨架:

java
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 名称路由:

java
// 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 四仓路由风格归纳

仓库路由主模式推荐开发规范
BFONController -> Service -> DAO / Remote以交易语义划分接口,DTO 与远程服务枚举成对维护
BLMS@RmbMap -> Handler -> ServiceCode Bean新交易优先复用模板方法骨架,避免散落式 if/else
BNBS统一入口 -> Context -> State / Action / FlowAction新流程优先扩展动作节点,不在入口类堆叠分支
BCPSRmbService -> OnlineServiceImpl -> subserver -> Facade -> Core新交易优先接入 subserver 与 Facade,不直接下钻核心仓库层

10. 数据模型与传输对象规范

10.1 实体类(Model)

使用 Lombok(推荐,BFON 风格)

java
@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 风格)

java
public class BlkList {
    @Attribute("LIST_TYPE")
    private String listType;

    @Attribute("CERT_TYPE")
    private String certType;

    // 手动 getter/setter
}

10.2 请求/响应对象

BFON 模式 — 按交易码命名

java
@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

java
// 请求和响应均为 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 统一响应封装

java
// 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 分页模式

java
// 请求分页参数
@Data
public static class Page {
    private Integer pageStart;    // 起始页码
    private Integer pageSize;     // 每页条数
}

// 响应分页封装
public class PageContainer<T> {
    private List<T> rows;         // 数据列表
    private Integer total;        // 总条数
}

10.5 内部静态类嵌套

复杂请求对象使用内部静态类组织嵌套结构:

java
@Data
public class C222323CmdReq {
    private String loanNo;
    private Control control;

    @Data
    public static class Control {
        private String channelType;
        private String operatorId;
    }
}

11. 异常处理规范

11.1 自定义异常

java
// 业务异常
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):

java
@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 模板方法):

java
@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 响应码枚举规范

java
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 日志工具封装

java
// 使用 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 记录接口调用的完整追踪信息:

java
MyTradeLogUtil.logSyncCall(
    interfaceDesc,    // 接口描述
    serviceCode,      // 服务码
    requestTime,      // 请求时间
    responseTime,     // 响应时间
    rspCode,          // 响应码
    bizSeqNo          // 业务流水号
);

12.3 线程上下文追踪

使用 ThreadContext 维护请求级元数据:

java
// 设置上下文
ThreadContext.setBizSeqNo("202606050001");
ThreadContext.setConsumerId("BLMS");
ThreadContext.setOrgSysId("BFON");

// 获取上下文
String bizSeqNo = ThreadContext.getBizSeqNo();

// 请求结束后清理(防止 ThreadLocal 泄漏)
ThreadContext.clear();

13. 国际化(i18n)规范

13.1 资源文件组织

text
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 命名规范

text
# 系统级消息
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 Type

13.3 代码调用方式

java
// 使用 I18nUtil 获取国际化消息,第二个参数为中文默认值
String message = I18nUtil.getMsgValue(
    "sys_bfon_bfon-service_i18n_msg_67",
    "借据列表查询异常"
);

14. 测试规范

14.1 测试框架

依赖版本参考用途
JUnit 44.x测试框架
Spring Boot Test随 Boot 版本集成测试
Mockito3.9.0Mock 框架
PowerMock2.0.9静态方法 Mock
H2 Database2.2.220内存数据库(替代 TDSQL)
JaCoCoMaven 插件代码覆盖率

14.2 测试文件组织

text
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 测试编写规范

java
// 使用自定义 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 本地构建

bash
# 完整构建
mvn clean install -DskipTests

# 仅构建特定模块
mvn clean install -pl {system}-service -am

# 运行测试
mvn test

# 生成覆盖率报告
mvn jacoco:report

15.2 产出物

模块产出文件说明
*-service{system}-service.jarSpring Boot 可执行 JAR
*-batch{system}-batch.jarSpring Boot 可执行 JAR
*-common{system}-common.jar普通 JAR(依赖库)
*-dao{system}-dao.jar普通 JAR(依赖库)

15.3 部署目录结构

text
{deploy-root}/
├── bin/                # 部署脚本
├── conf/               # 配置文件
├── lib/                # 应用 JAR
└── logs/               # 日志输出

15.4 JVM 参数规范

bash
# 标准参数(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 部署(可选)

bash
# 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 本地运行

java
// 启动类中使用 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
@EnableAspectJAutoProxyAOP 代理
@EnableBatchProcessingSpring Batch(modular = true
@EnableScheduling定时任务
@MapperScansMyBatis Mapper 扫描
@RestController / @ControllerWeb 控制器
@ComponentSpring Bean 注册
@Autowired依赖注入

16.3 Jackson 注解规范

java
@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 启动类模板

java
@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 四仓总览

维度BFONBLMSBNBSBCPS
定位信贷产品运营平台名单管理系统产品域编排与资金批扣平台信用核心平台
基础包cn.webank.cnc.bfoncn.webank.cnc.blmscn.webank.cnc.bnbscn.webank.cnc.bcps
顶层结构service / batch / commonservice 单主模块cgi / adm / dcn / mid / batch / union-apponline / batch-dcn / core-* / service-api / facility / infrastructure
主要入站RESTRMBRMB + CGIRMB + 批量/DCN
主要出站DynamicProxyGateRMB 同步调用RMB + remote/facilityFacade + Core Domain
数据传输强类型 DTOMap<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 扩展,新流程优先增加动作节点或状态节点,不在入口类堆叠大量条件分支。
  • 远程调用、公共能力、数据访问应分别下沉到 remotefacility、DAO/Mapper 层,避免把集成逻辑写进 action
  • 多个启动壳并存是正常模式,启动壳负责组装,不承载复杂业务。

17.5 BCPS 开发规范

  • BCPS 是重型信用核心,规范核心不在“模块少”,而在“接入层、Facade 层、核心域层边界清楚”。
  • 新交易一般沿 RmbService -> OnlineServiceImpl -> subserver -> OnlineFacadeService -> CoreOnlineFacade -> Core Domain 链路接入。
  • subserver 层负责交易适配和路由,Facade 负责交易归并与领域编排,核心域负责借据、额度、入账、重组等真实业务。
  • service-apifacilityinfrastructure 要保持基础能力定位,不能被 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.propertiesrmb-client.propertieslog4j2.xml
  • [ ] 日志配置包含 AppLog + TradeLog + RmbLog 三个 Appender

数据库规范

  • [ ] 增量脚本放 dbScript/{version-date}/
  • [ ] 全量表结构放 metadata/
  • [ ] 表名使用 TM_ 前缀
  • [ ] MyBatis Mapper 命名为 *Dao.map.xml

通信规范

  • [ ] 入站接口定义在 controller/rmb/
  • [ ] 出站调用使用 DynamicProxyGateRmbUtil
  • [ ] 文件传输使用 FpsClientUtil
  • [ ] 响应使用统一封装(RespBase 或 Map + RspCodeEnum)

重型系统补充检查

  • [ ] BNBS 新流程优先扩展 context / state / action / flowAction,而不是在入口类堆分支
  • [ ] BNBSunion-app 仅做装配,不承载核心业务
  • [ ] BCPS 新交易按 subserver -> Facade -> Core 链路接入
  • [ ] BCPSonlinecore-*facilityinfrastructure 依赖方向保持单向
  • [ ] 双库系统明确区分运行库与核心库,不混用 Mapper 与事务边界

部署规范

  • [ ] start.sh 定义 systemIdAppPortjmxPort
  • [ ] JVM 参数包含 -Xms-Xmx、GC 配置、HeapDump
  • [ ] 使用 LocalRun.prepareLocalRun() 支持本地运行