免增加出参名单配置 · 设计文档

分支 iteration/v2.57_产品合规改造 缓存 Key: AGG_SPECIAL_LIST

合规改造 · 免增加出参名单

为金融机构调用的单一数据源产品增加朴道侧评分/画像出参场景下,提供一个免增加出参名单配置能力:命中名单的账户,其出参不增加合规用固定出参。

📊 数据表 agg_special_list ⚡ 缓存 R2MHashCache 🔑 Key AGG_SPECIAL_LIST 🔄 版本号驱动刷新 🧩 5 个前端接口

1需求概述

合规改造增加特殊名单。命中名单的账户,出参不增加合规用固定出参。名单以账户为粒度,支持启用/停用状态切换,停用不参与命中。

分支iteration/v2.57_产品合规改造_20260810172346

缓存 KeyAGG_SPECIAL_LIST(命中则该账户出参不增加合规用固定出参)

2数据库设计

2.1 建表 DDL

🗄️agg_special_list免增加出参名单(聚合特殊名单)
字段类型键/约束默认值含义
idbigint🔑 PK AUTO_INCREMENT-自增主键(即规则编号)
account_idbigintNOT NULL UNIQUE-客户账户标识
statusvarchar(10)NOT NULL'1'状态:1=启用,0=停用(字典 JG0000019)
creatorvarchar(128)NULL创建人(username)
created_timedatetimeNULL创建时间
modifiervarchar(128)NULL修改人(username)
modified_timedatetimeNOT NULLCURRENT_TIMESTAMP更新时间,ON UPDATE CURRENT_TIMESTAMP
SQL · DDL
CREATE TABLE `agg_special_list` (
  `id`            bigint        NOT NULL AUTO_INCREMENT  COMMENT '自增主键(即规则编号)',
  `account_id`    bigint        NOT NULL                 COMMENT '客户账户标识',
  `status`        varchar(10)   NOT NULL DEFAULT '1'   COMMENT '状态:1=启用,0=停用(字典JG0000019)',
  `creator`       varchar(128)           DEFAULT NULL   COMMENT '创建人(username)',
  `created_time`  datetime                DEFAULT NULL   COMMENT '创建时间',
  `modifier`      varchar(128)  DEFAULT NULL                  COMMENT '修改人(username)',
  `modified_time` datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_account_id` (`account_id`)
) ENGINE=InnoDB COMMENT='免增加出参名单(聚合特殊名单)';

2.2 字段说明

字段类型默认值含义
idbigint AUTO_INCREMENT-自增主键,即规则编号。启用/停用不改编号
account_idbigint-客户账户标识,关联 account_info.id
statusvarchar(10)'1'状态(字典 JG0000019):'1'=启用,'0'=停用。停用不参与命中(不入缓存)
creatorvarchar(128)NULL创建人,MetaObjectHandler 插入时填充
created_timedatetimeNULL创建时间,MetaObjectHandler 插入时填充
modifiervarchar(128)NULL修改人,MetaObjectHandler 插入/更新时填充
modified_timedatetime NOT NULLCURRENT_TIMESTAMP更新时间,MetaObjectHandler 填充;ON UPDATE CURRENT_TIMESTAMP 兜底非 MP 的直接 SQL

2.3 设计说明

account_id 唯一索引
同一账户仅一条记录(启用/停用状态切换),account_id 加唯一索引 uk_account_id,数据库层保障一账户一记录。新增时若 account_id 已存在(任意状态),代码预校验 count(account_id=?) > 0 提示「该账户已在免增加出参名单中」并阻止;并发插入由唯一索引兜底
id 自增即规则编号
id 为数据库自增,直接作为列表"规则编号"展示。启用/停用为同记录状态切换,不新增/删除行,编号天然不变

3缓存设计

3.1 缓存结构

agg_special_list
status='1' 启用记录
loadAll
CacheLoader
AggSpecialListCacheLoader
写入
R2MHashCache
Key: AGG_SPECIAL_LIST
Field: accountId
Value: "1"
查询命中
出参组装
命中 → 不增加合规用固定出参
  • 类型R2MHashCache(Redis Hash,主 R2M 集群)
  • Redis KeyAGG_SPECIAL_LIST(常量 R2MConstant.AGG_SPECIAL_LIST
  • Hash FieldaccountId.toString(),Value:"1"(仅作命中标志,无业务含义,命中 Field 即表示该账户在名单中)
  • 业务语义:按 accountId 查缓存,命中则该账户出参不增加合规用固定出参

3.2 缓存加载

  • 加载器AggSpecialListCacheLoader extends AbstractCacheVersionLoader<String, String>,构造器传业务类型 VersionBusinessTypeEnum.AGG_SPECIAL_LIST.getType()
  • 实现 loadAll():全量加载 status='1'(启用)记录,按 accountId 建映射;停用记录不入缓存
  • 加载时机:启动加载 + 定时任务检测版本号变化刷新

3.3 版本号

  • 版本号由 BusinessVersionAspect 切面维护:对 AggSpecialList 实体的 save/update/remove/delete 操作,切面自动递增 OperationBusinessVersion 表中业务类型 aggSpecialList 的 version(version = 当前时间戳)
  • getLatestGlobalVersion()AbstractCacheVersionLoader 父类实现,查 OperationBusinessVersion 表取 version,子类无需覆盖
  • 新增/启用/停用操作均会推高 version,定时任务检测到变化即触发全量重载

3.4 刷新机制

1
写操作
add / changeStatus
只更新数据库
2
BusinessVersionAspect
切面自动递增
version = 时间戳
3
AggSpecialListCacheTask
EasyJob 定时任务
检测版本号变化
4
全量重载缓存
loadAll(status='1')
下一周期感知
  • 缓存刷新统一由定时任务 AggSpecialListCacheTask(EasyJob)负责:检测 OperationBusinessVersion 版本号变化,变化时触发全量重载
  • 写操作(add / changeStatus)只更新数据库,不主动调缓存刷新方法;版本号递增由 BusinessVersionAspect 切面在写操作后自动完成,定时任务下一周期感知并刷新
  • 刷新延迟:取决于定时任务执行周期(配置表低频写入,延迟可接受)

3.5 注册

  • CacheConfig 新增 @Bean:返回 AbstractCache<String, String>,实例化 new R2MHashCache(R2MConstant.AGG_SPECIAL_LIST, aggSpecialListCacheLoader, null, r2mClusterClient, null, null, false, false)(参照现有 R2MHashCache 注册)
  • R2MConstant 新增常量 AGG_SPECIAL_LIST

4分层实现

4.1 包结构与文件清单

service/aggSpecialList/ dao/model/AggSpecialList.java # 主表实体, extends BaseEntity dao/mapper/AggSpecialListMapper.java # 含 selectPage 自定义分页 buss/bo/AggSpecialListBo.java # 新增 BO buss/bo/AggSpecialListPageBo.java # 分页查询 BO buss/repository/AggSpecialListRepository.java # 继承 IService + selectPage buss/repository/impl/AggSpecialListRepositoryImpl.java buss/service/IAggSpecialListService.java buss/service/impl/AggSpecialListServiceImpl.java
mvc/aggSpecialList/ controller/AggSpecialListController.java dto/AggSpecialListPageDto.java # 继承项目分页基类 dto/AggSpecialListAddDto.java dto/AggSpecialListChangeStatusDto.java vo/AggSpecialListPageVo.java vo/AggSpecialListAccountInfoVo.java common/AggSpecialListConvertBasic.java # MapStruct, DTO->BO
cache/loader/AggSpecialListCacheLoader.java # extends AbstractCacheVersionLoader task/AggSpecialListCacheTask.java config/CacheConfig.java # 追加 @Bean(非新建文件) common/constants/R2MConstant.java # 追加常量(非新建文件) common/constants/Enum/VersionBusinessTypeEnum.java # 追加枚举值 AGG_SPECIAL_LIST(非新建文件) resources/mapper/aggSpecialList/AggSpecialListMapper.xml

实体统一放 service/aggSpecialList/dao/model/ 下,不沿用历史分包

4.2 实体 / BO / DTO / VO / 转换

4.2 实体类 AggSpecialList

extends BaseEntity,字段:id@TableId(type=IdType.AUTO))、accountIdstatus;审计字段(creator/createdTime/modifier/modifiedTime)继承自 BaseEntity,由 MetaObjectHandler 自动填充。

4.3 BO

BO字段用途
AggSpecialListBoaccountId新增用
AggSpecialListPageBoorgNomerchantNameaccountIdstatus分页查询用

4.4 DTO

DTO字段校验
AggSpecialListPageDtopageNum/pageSize(继承)+ orgNo/merchantName/accountId/status-
AggSpecialListAddDtoaccountId@NotNull
AggSpecialListChangeStatusDtoidstatus@NotNull

4.5 VO

  • AggSpecialListPageVo:见 5.1 返回字段表。creator/modifier@GenerateStrField(type=TranslateType.USER, suffix="Name"),时间字段加 @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
  • AggSpecialListAccountInfoVoaccountId/orgNo/fullName/shortName/merchantName/accountName/status

4.6 对象转换

AggSpecialListConvertBasic@Mapper(componentModel="spring")):PageDto→PageBoAddDto→Bo。Entity→VO 在 Service 手动组装(需关联查询客户信息)。

4.7 Service 方法

方法说明
page(pageNum, pageSize, PageBo)分页查询,返回 IPage<PageVo>
add(Bo)账户有效性 + 唯一性校验后 insert,不刷缓存
changeStatus(id, status)校验 status 合法后 update,不刷缓存
checkAccount(accountId)count 查询返回 Boolean
accountInfo(accountId)查 account_info + t_crm_org_base_info 组装 VO

缓存刷新由定时任务 AggSpecialListCacheTask 统一负责;Service 写操作不主动刷缓存,版本号递增由 BusinessVersionAspect 切面自动完成。

5接口说明(前端)

5.0 接口总览

#1POST
规则列表分页
/aggSpecialList/page
查询名单(启用+停用)
#2POST
新增配置
/aggSpecialList/add
新增一条启用规则
#3POST
启用/停用
/aggSpecialList/changeStatus
切换启用/停用状态
#4GET
校验账户是否已有生效规则
/aggSpecialList/checkAccount
新增弹窗选账户时实时校验
#5GET
按账户标识查客户信息
/aggSpecialList/accountInfo
新增弹窗选账户后带出客户信息

权限说明:本功能接口均为 NORMAL(走菜单按钮鉴权)。

统一返回包装 Result<T>,分页返回 Result<PageResult<T>>

5.1 规则列表分页

POST /aggSpecialList/page 规则列表分页 权限 NORMAL
入参
字段类型必填说明
pageNumint页码
pageSizeint每页条数(10/20/50/100)
orgNostring客户编号,模糊匹配
merchantNamestring客户全称/简称,模糊匹配(任一命中)
accountIdlong客户账户标识,精确匹配
statusstring状态筛选:'1'=启用,'0'=停用
返回 Result<PageResult<AggSpecialListPageVo>>
字段类型说明
idlong规则编号(自增,启用/停用不改编号)
accountIdlong客户账户标识
orgNostring客户编号
merchantNamestring客户名称,格式:客户全称(客户简称)
accountNamestring客户账户名称
statusstring状态:'1'/'0'
statusStrstring状态文案:启用 / 停用
creatorstring创建人(username,鼠标悬浮展示)
creatorNamestring创建人中文名(直接展示)
createdTimestring创建时间(yyyy-MM-dd HH:mm:ss)
modifierstring修改人(username)
modifierNamestring修改人中文名
modifiedTimestring修改时间(yyyy-MM-dd HH:mm:ss)
📌 排序:modified_time DESC, id DESC(编辑时间倒序,相同时规则编号倒序)|规则总数:PageResult.total,随查询/状态切换自动更新|停用行:前端文字弱化展示

5.2 新增配置

POST /aggSpecialList/add 新增配置 权限 NORMAL
入参
字段类型必填说明
accountIdlong客户账户标识(输入+下拉精确选择生效中账户)
校验(阻止保存并提示)
  1. accountId 不能为空,且须为系统中有效启用账户,否则提示并阻止
  2. 若该账户已存在记录(任意状态),提示「该账户已在免增加出参名单中」并阻止(唯一索引兜底
返回 Result<?>
✅ 成功后:自增生成规则编号;状态置启用;审计字段由 MetaObjectHandler 自动填充(缓存由定时任务刷新);前端刷新清单页。

5.3 启用/停用

POST /aggSpecialList/changeStatus 启用/停用 权限 NORMAL
入参(@RequestBody JSON)
字段类型必填说明
idlong规则编号
statusstring目标状态:'1'=启用,'0'=停用
🖱️ 交互:启用行展示「停用」按钮(传 status='0'),停用行展示「启用」按钮(传 status='1')。
⚙️ 逻辑:校验记录存在且 status 值合法后,update set status=?(modifier/modified_time 由 MetaObjectHandler 自动填充)。停用后该 accountId 不再入缓存、不参与命中;启用后重新入缓存(缓存由定时任务刷新)。
返回 Result<?>

5.4 校验账户是否已有生效规则

GET /aggSpecialList/checkAccount?accountId={accountId} 校验账户 权限 NORMAL
入参
字段类型必填说明
accountIdlong客户账户标识
返回 Result<Boolean>
返回值含义
true该账户已有记录(前端提示「该账户已在免增加出参名单中」,阻止继续)
false可新增
💡 用途:新增弹窗选择账户后实时校验。

5.5 按账户标识查客户信息

GET /aggSpecialList/accountInfo?accountId={accountId} 查客户信息 权限 NORMAL
入参
字段类型必填说明
accountIdlong客户账户标识
返回 Result<AggSpecialListAccountInfoVo>
字段类型说明
accountIdlong客户账户标识
orgNostring客户编号
fullNamestring客户全称
shortNamestring客户简称
merchantNamestring客户全称(客户简称) 展示格式
accountNamestring客户账户名称
statusstring账户状态:'1'=已启用
💡 用途:新增弹窗选择账户后,自动带出客户编号、客户名称、账户名称(只读)。

5.6 字段翻译约定

  • creator/modifier:后端用 @GenerateStrField(type=TranslateType.USER) 注解,序列化时自动追加 creatorName/modifierName(中文名)。前端直接展示 xxxName,鼠标悬浮展示 xxx(username)。
  • 时间字段@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")

6关键业务逻辑

6.1 分页查询

单表查询 + 关联账户/客户信息,因多表 JOIN 采用自定义 XML 分页selectPage)。

SQL · selectPage XML
SELECT
    t.id,
    t.account_id,
    t.status,
    t.creator,
    t.created_time,
    t.modifier,
    t.modified_time,
    obi.org_no,
    ai.account_name,
    CASE
        WHEN obi.short_name IS NOT NULL AND obi.short_name != ''
        THEN CONCAT(obi.full_name, '(', obi.short_name, ')')
        ELSE obi.full_name
    END AS merchantName
FROM agg_special_list t
LEFT JOIN account_info ai ON ai.id = t.account_id
LEFT JOIN t_crm_org_base_info obi ON obi.org_no = ai.org_no
<where>
    <if test="orgNo != null and orgNo != ''">
        AND obi.org_no LIKE CONCAT('%', #{orgNo}, '%')
    </if>
    <if test="merchantName != null and merchantName != ''">
        AND (instr(fullwidth_to_halfwidth(obi.full_name), fullwidth_to_halfwidth(#{merchantName})) != 0
          OR instr(fullwidth_to_halfwidth(obi.short_name), fullwidth_to_halfwidth(#{merchantName})) != 0)
    </if>
    <if test="accountId != null">
        AND t.account_id = #{accountId}
    </if>
    <if test="status != null and status != ''">
        AND t.status = #{status}
    </if>
</where>
ORDER BY t.modified_time DESC, t.id DESC
匹配规则
客户编号:模糊匹配(LIKE)|客户全称/简称:fullwidth_to_halfwidth(项目已有函数)全半角统一后 instr 匹配,full_name / short_name 任一命中|账户标识、状态:精确匹配。
排序与翻译
排序:编辑时间倒序,相同时规则编号倒序|statusStr 翻译:'1'→生效中,'0'→已删除。

6.2 新增配置校验

1
账户有效性
account_info 记录存在
且 status='1'(已启用)
否则提示并阻止
2
唯一性校验
count(account_id=?) > 0
(不限状态)
提示「该账户已在名单中」
唯一索引兜底并发
3
insert
status='1'(启用)
审计字段 MetaObjectHandler 填充
缓存由定时任务刷新

6.3 启用/停用(changeStatus)

1
校验记录存在
2
校验 status 合法
'1'=启用 / '0'=停用
3
update set status
where id=?
modifier/modified_time
MetaObjectHandler 自动填充
4
定时任务刷缓存
停用→不再入缓存、不命中
启用→重新入缓存

6.4 校验账户是否已有生效规则(checkAccount)

  • count(account_id=?) > 0(不限状态)返回 true(该账户已有记录)
  • 用途:新增弹窗选中账户后实时校验,提示「该账户已在免增加出参名单中」

6.5 按账户标识查客户信息(accountInfo)

1
查 account_info
account_name / org_no / status
2
查 t_crm_org_base_info
按 org_no
full_name / short_name
3
组装 AccountInfoVo
merchantName = CONCAT(full_name,'(',short_name,')')
short_name 空时取 full_name
status = 账户启用状态

💡 用途:新增弹窗选中账户后带出客户编号、客户名称、账户名称(只读)。

7后续事项

  • 🗄️
    建表执行按 2.1 DDL 在对应环境建表。
  • 🏷️
    版本类型注册VersionBusinessTypeEnum 新增 AGG_SPECIAL_LIST 枚举值(type=aggSpecialList,关联 AggSpecialList 实体),供 BusinessVersionAspect 切面识别。
  • 🌱
    业务版本初始化OperationBusinessVersion 表插入一行 buss_type='aggSpecialList' 初始记录(version 初始值),否则 getLatestGlobalVersion() 查无数据会报错。
  • 定时任务配置AggSpecialListCacheTask 需在 EasyJob 调度平台注册任务并配置执行周期(配置表低频写入,周期可适当放宽)。
  • 🤝
    缓存契约对齐与出参组装方约定 AGG_SPECIAL_LIST 的 Key(accountId)与命中语义,确认消费方按约定读取。
  • 🔑
    resourceId 配置暂不考虑,后续前端联调时补充菜单与权限码映射。