灰度切流策略框架设计
一套基于策略模式 + 配置驱动的通用灰度发布框架,支持白名单、黑名单、百分比、组合策略等多种切流模式,配置热更新零代码侵入。
1. 背景与问题
在大型业务系统中,新功能上线通常不能一蹴而就地全量放开,而是需要经历"内部验证 → 小范围灰度 → 逐步放量 → 全量切流"的渐进式发布过程。传统的 if-else 硬编码方式存在以下问题:
| 问题 | 描述 |
|---|---|
| 代码侵入强 | 每个灰度场景都需要写一套判断逻辑,散落在各业务代码中 |
| 调整需发版 | 修改白名单或百分比需要重新发布应用 |
| 缺乏一致性 | 不同场景的灰度实现方式五花八门,难以统一管控 |
| 无法一键熔断 | 发现问题时无法快速关闭灰度开关 |
| 难以组合 | 无法灵活组合多种灰度策略(如"白名单 + 百分比") |
本框架的核心目标:一行代码接入、配置动态生效、策略可组合、一键可熔断。
2. 整体架构
┌─────────────────────────────────────────────────────────────┐ │ 业务调用方 │ │ grayStrategyService.isPassGrayStrategy( │ │ "YOUR_BIZ_CODE", targetId, params) │ └────────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ GrayStrategyServiceImpl │ │ │ │ ① 根据 bizCode 查询远程配置中心,获取 GrayStrategyConfig │ │ ② 检查 switchOn 总开关 │ │ ③ 反序列化 modeList,逐个执行策略 Handler │ │ ④ all pass → true ; any fail → false │ │ ⑤ try-finally 打印决策 digest 日志 │ └────────────┬────────────┬────────────┬──────────────────────┘ │ │ │ ┌────────▼──┐ ┌──────▼───┐ ┌────▼──────────┐ │ Handler A │ │Handler B │ │ Handler C │ ... │ (白名单) │ │(百分比) │ │(黑名单等) │ └───────────┘ └──────────┘ └───────────────┘核心决策流程:
- 业务方传入
bizCode(灰度业务码)、targetId(通常是商户 ID)、params(维度参数) - 根据
bizCode从配置中心加载GrayStrategyConfig - 如果配置不存在或
switchOn=false,直接返回默认结果 - 遍历
modeList中的策略模式,逐个执行对应的Handler - 任意一个 Handler 不通过,整体不通过(AND 语义)
- 全部通过,返回
true
3. 核心组件详解
3.1 配置模型 —— GrayStrategyConfig
每一条灰度配置包含 5 个字段:
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
bizCode | String | 灰度业务标识,定位到具体业务场景 | "SUPPLY_B2B_MEMBER_WITH_REGISTER_FROM" |
switchOn | String | 总开关,控制该场景灰度是否启用 | "true"/"false" |
modeList | String | JSON 数组,策略模式列表,支持多策略组合 | ["WHITE_LIST","UID_PERCENTAGE"] |
ruleMap | String | JSON 对象,白名单/黑名单的维度规则 | {"userId":["uid1","uid2"],"instCode":["Z05"]} |
cutPercentage | String | 百分比切流比例(0-100) | "10"表示 10% |
关键设计 —— modeList 是数组:同一个业务场景可以叠加多种策略模式。比如同时配置["WHITE_LIST","UID_PERCENTAGE"],表示先过白名单、再过百分比,全部满足才算通过。
配置存储在远程配置中心(如 ComPara),修改后即时生效,无需发版。
3.2 策略模式枚举 —— GrayStrategyModeEnum
将每种灰度策略封装为枚举值 + Handler的形式:
┌─────────────────────────────┬───────────────────────────────────────────────────┐ │ 枚举值 │ 描述 │ ├─────────────────────────────┼───────────────────────────────────────────────────┤ │ ALL_PASS │ 全量放行模式 │ │ WHITE_LIST │ 白名单模式(所有维度必须同时命中,AND 语义) │ │ BLACK_LIST │ 黑名单模式(任意维度命中即拦截,OR 语义) │ │ UID_PERCENTAGE │ UID 分表位百分比切流(确定性,同一用户结果恒定) │ │ REFERENCE_UID_PERCENTAGE │ 引用 UID 百分比切流(hashCode 分桶,非标准 UID 用) │ │ WHITE_LIST_ANY_MATCH_ENOUGH │ 宽松白名单(任意一个维度命中即通过,OR 语义) │ │ WHITE_LIST_WITH_PERCENTAGE │ 白名单+百分比(核心维度直通,非核心维度再过百分比) │ └─────────────────────────────┴───────────────────────────────────────────────────┘设计亮点 —— 枚举即路由表:枚举的构造函数第三个参数直接持有对应的 Handler 实例:
WHITE_LIST("WHITE_LIST","白名单模式",newWhiteListStrategyHandler()),调用时通过GrayStrategyModeEnum.getByCode(mode).getHandler()即可获取处理器,省去了工厂类或 Map 映射。
3.3 策略处理器接口 —— GrayStrategyHandler
publicinterfaceGrayStrategyHandler{intPERCENTAGE_0=0;intPERCENTAGE_100=100;/** * @param config 灰度策略配置(含 ruleMap、cutPercentage 等) * @param targetId 切流目标 ID(通常是商户 userId) * @param param 运行时维度参数(如 userId、instCode、label 等) * @return true: 通过灰度决策 | false: 不通过 */booleanhandle(GrayStrategyConfigconfig,StringtargetId,Map<String,String>param);}三个参数的职责明确:
- config:该场景的完整灰度配置,Handler 按需读取 ruleMap、cutPercentage 等字段
- targetId:切流目标,通常传商户 userId
- param:运行时维度参数,与 ruleMap 中的 key 对应用于匹配
3.4 调用方接口 —— GrayStrategyService
提供两个重载方法和一系列参数构建工具:
// 核心方法(defaultResult 默认 false)booleanisPassGrayStrategy(StringbizCode,StringtargetId,Map<String,String>params);// 支持自定义默认结果的方法booleanisPassGrayStrategy(StringbizCode,StringtargetId,Map<String,String>params,booleandefaultResult);// 参数构建工具方法(static)staticMap<String,String>buildUidParamMap(StringuserId);staticMap<String,String>buildLabelParamMap(Stringlabel);staticMap<String,String>buildBizSceneParamMap(StringbizScene);staticMap<String,String>buildInstCodeParamMap(StringinstCode);staticMap<String,String>buildParamMap(Stringkey,Stringvalue);defaultResult** 参数的含义:** 当配置不存在(即配置中心没有该 bizCode 的记录)时,返回什么默认值。一般传false(保守,走老流程),也可以按业务需要传true。
4. 七种策略模式详解
4.1 ALL_PASS —— 全量放行
语义:无条件放行,所有请求都通过。
适用场景:灰度验证已结束,需要全量上线时,将 modeList 改为["ALL_PASS"]即可。
实现:直接返回true。
4.2 WHITE_LIST —— 严格白名单(AND 语义)
语义:ruleMap 中所有维度都必须命中白名单,才算通过。
决策流程:
传入参数: {userId: "A", instCode: "Z05"} 配置: ruleMap = {userId: ["A","B"], instCode: ["Z05"]} 遍历 ruleMap: ① userId 维度: param["userId"]="A" 在白名单 ["A","B"] 中 → 通过 ② instCode 维度: param["instCode"]="Z05" 在白名单 ["Z05"] 中 → 通过 所有维度通过 → 返回 true传入参数: {userId: "A", instCode: "Z99"} 配置: ruleMap = {userId: ["A","B"], instCode: ["Z05"]} 遍历 ruleMap: ① userId 维度: 通过 ② instCode 维度: param["instCode"]="Z99" 不在白名单 ["Z05"] 中 → 不通过 存在维度不通过 → 返回 false注意:如果传入参数中缺少某个维度的值,直接返回false。白名单是"证明你行"的逻辑,缺少证据即为不通过。
适用场景:精确控制特定商户,需要同时满足多个维度条件。
4.3 BLACK_LIST —— 黑名单(OR 语义)
语义:ruleMap 中任意一个维度命中黑名单,即被拦截。
决策流程:
传入参数: {userId: "A", instCode: "Z05"} 配置: ruleMap = {userId: ["C","D"], instCode: ["Z99"]} 遍历 ruleMap: ① userId 维度: param["userId"]="A" 不在黑名单 ["C","D"] 中 → 不拦截 ② instCode 维度: param["instCode"]="Z05" 不在黑名单 ["Z99"] 中 → 不拦截 全部未命中 → 返回 true(放行)关键设计差异:如果传入参数中缺少某个维度的值,黑名单模式选择continue(跳过,不拦截),而非返回false。这与白名单相反:黑名单是"证明你不行",缺少证据就不拦截。
适用场景:排除特定商户,如已知有问题的商户 ID 需要暂时屏蔽新流程。
4.4 UID_PERCENTAGE —— UID 分表位百分比切流
语义:根据 userId 的分表位(0-99)判断是否命中灰度比例。
决策流程:
1. 从配置读取 cutPercentage(如 "10") 2. 通过 DBPrimaryKeyUtil.getUserPartitionByUserId(userId) 获取分表位 3. 判断: userPartition < cutPercentage → 通过设计亮点 —— 确定性:使用分表位而不是Random.nextInt(),保证了同一用户多次请求的结果恒定。这是灰度切流的关键要求——用户不能一会儿走新流程一会儿走老流程。
分表位算法内部还兼容了压测 UID(倒数第二位可能是字母 A-J),会做字母→数字的替换处理。
适用场景:按比例灰度放量,如先放量 5%,观察后逐步提升到 10%、50%、100%。
4.5 REFERENCE_UID_PERCENTAGE —— 引用 UID 百分比切流
语义:与 UID_PERCENTAGE 类似,但分桶算法不同。
关键区别:
| 模式 | 分桶算法 | 适用场景 |
|---|---|---|
| UID_PERCENTAGE | 分表位(DB 内部 ID 格式) | 标准 ipay 内部 UID |
| REFERENCE_UID_PERCENTAGE | hashCode() % 100 | 非 ipay 格式的引用 ID(如平台商的 seller ID) |
设计亮点 —— 模板方法模式:继承PercentageStrategyHandler,只覆写getUserPartitionByUserId方法:
publicclassReferenceUidPercentageStrategyHandlerextendsPercentageStrategyHandler{@OverrideprotectedintgetUserPartitionByUserId(StringreferenceUserId){returnMath.abs(referenceUserId.hashCode()%100);}}父类定义了百分比判定的骨架流程,子类只需替换分桶算法。
4.6 WHITE_LIST_ANY_MATCH_ENOUGH —— 宽松白名单(OR 语义)
语义:ruleMap 中任意一个维度命中白名单,即算通过。
决策流程:
传入参数: {userId: "A", instCode: "Z99"} 配置: ruleMap = {userId: ["A","B"], instCode: ["Z05"]} 遍历 ruleMap: ① userId 维度: param["userId"]="A" 在白名单 ["A","B"] 中 → 命中! 任意一条命中 → 返回 true(无需继续检查 instCode)额外能力 —— 通配符*:如果某个维度的白名单列表包含"*",则该维度无条件通过:
配置: ruleMap = {instCode: ["*"]} 传入参数: {instCode: "任意值"} → 命中通配符,直接通过与严格白名单的对比:
| 对比维度 | WHITE_LIST(严格) | WHITE_LIST_ANY_MATCH_ENOUGH(宽松) |
|---|---|---|
| 语义 | 所有维度都要命中 | 任意维度命中即可 |
| 逻辑 | AND | OR |
| 缺少维度参数 | 返回 false | 不影响(其他维度命中即可) |
| 通配符支持 | 无 | 支持* |
钩子方法设计:hintPercentageAfterMatch()是一个钩子,当前实现直接返回true,但为子类留了扩展点。
4.7 WHITE_LIST_WITH_PERCENTAGE —— 白名单 + 百分比
语义:先做白名单匹配(OR 语义),命中后根据维度类型决定是否再做百分比淘汰。
核心逻辑(覆写钩子方法):
白名单命中后: ├── 命中的维度是核心维度 (userId / maInnerId) │ → 直接放行,不走百分比 │ └── 命中的维度是非核心维度 (instCode / label 等) → 还需要过百分比随机淘汰 → RandomUtils.nextInt(0, 101) <= cutPercentage → 通过设计智慧 —— 维度优先级区分:
- 核心维度(userId、maInnerId):白名单精确到具体商户,确定性高,不需要百分比。配置了某个 userId 就一定放行该用户。
- 非核心维度(instCode、label 等):白名单覆盖范围大(如某个 instCode 下可能有大量商户),需要百分比来渐进放量,避免一次性放开太多。
百分比注意点:这里的百分比用的是RandomUtils.nextInt()(随机),与UID_PERCENTAGE的分表位(确定性)不同。这是有意为之——白名单阶段已经做了维度筛选,百分比阶段是对非核心维度的补充过滤,随机性可以接受。
5. 策略组合机制
modeList支持配置多个策略模式,实现灵活组合:
5.1 多策略 AND 语义
配置示例:"modeList": ["WHITE_LIST","UID_PERCENTAGE"]
决策过程:先过白名单 → 再过百分比 → 全部通过才算通过
请求进入 │ ▼ WHITE_LIST Handler ├── 不通过 → 返回 false └── 通过 ↓ ▼ UID_PERCENTAGE Handler ├── 不通过 → 返回 false └── 通过 → 返回 true5.2 单策略场景
配置示例:"modeList": ["WHITE_LIST_ANY_MATCH_ENOUGH"]
只走一种策略模式,通过即通过。
5.3 全量切流
配置示例:"modeList": ["ALL_PASS"]
等价于关闭灰度,所有请求都走新流程。也可以直接将switchOn设为"true"配合ALL_PASS。
6. 核心决策引擎流程
privatebooleandoGrayDecide(StringbizCode,StringuserId,Map<String,String>params,booleandefaultResult){// 1. bizCode 为空 → 返回 defaultResultif(Objects.isNull(bizCode)){returndefaultResult;}// 2. 从配置中心查询 GrayStrategyConfigGrayStrategyConfigstrategyConfig=queryGrayStrategyConfig(bizCode);// 3. 配置不存在 → 返回 defaultResultif(Objects.isNull(strategyConfig)){returndefaultResult;}// 4. 总开关关闭 → 返回 false(一键熔断)if(!Boolean.parseBoolean(strategyConfig.getSwitchOn())){returnfalse;}// 5. 解析 modeListList<String>grayModes=JSONObject.parseObject(strategyConfig.getModeList(),newTypeReference<List<String>>(){});if(CollectionUtils.isEmpty(grayModes)){returnfalse;}// 6. 遍历执行每个策略 Handler(AND 语义)for(StringgrayMode:grayModes){GrayStrategyModeEnummodeEnum=GrayStrategyModeEnum.getByCode(grayMode);if(Objects.isNull(modeEnum)){returnfalse;// 不支持的模式 → 不通过}if(!modeEnum.getHandler().handle(strategyConfig,userId,params)){returnfalse;// 任一策略不通过 → 整体不通过}}returntrue;// 所有策略都通过}关键设计决策总结:
| 步骤 | 条件 | 结果 | 设计意图 |
|---|---|---|---|
| bizCode 为空 | 无法决策 | 返回defaultResult | 兜底安全,由调用方决定 |
| 配置不存在 | 未配置灰度规则 | 返回defaultResult | 未配置 != 拦截,尊重业务方的默认倾向 |
| switchOn=false | 主动关闭 | 返回false | 一键熔断,紧急情况下快速回滚 |
| modeList 为空 | 无策略 | 返回false | 有配置但无策略,保守处理 |
| 不支持的模式 | 配置错误 | 返回false | 防止配置错误导致误放行 |
| Handler 不通过 | 策略拦截 | 返回false | AND 语义,任意失败即终止 |
7. 配置示例
示例 1:简单白名单(仅允许指定用户)
{"grayBizCode":"MY_FEATURE_SWITCH","switchOn":"true","modeList":"[\"WHITE_LIST\"]","ruleMap":"{\"userId\":[\"2088123456\",\"2088789012\"]}","cutPercentage":"0"}调用方式:
grayStrategyService.isPassGrayStrategy("MY_FEATURE_SWITCH",userId,GrayStrategyService.buildUidParamMap(userId));示例 2:白名单 + 百分比组合(白名单用户直通,非白名单用户按 instCode 10% 放量)
{"grayBizCode":"MY_FEATURE_SWITCH","switchOn":"true","modeList":"[\"WHITE_LIST_WITH_PERCENTAGE\"]","ruleMap":"{\"userId\":[\"2088123456\"],\"instCode\":[\"Z05\"]}","cutPercentage":"10"}调用方式:
Map<String,String>params=newHashMap<>();params.put("userId",userId);params.put("instCode",instCode);grayStrategyService.isPassGrayStrategy("MY_FEATURE_SWITCH",userId,params);- userId 命中白名单 → 核心维度,直通
- instCode 命中白名单但 userId 未命中 → 非核心维度,10% 随机放量
- 都未命中 → 不通过
示例 3:全量切流
{"grayBizCode":"MY_FEATURE_SWITCH","switchOn":"true","modeList":"[\"ALL_PASS\"]","ruleMap":"{}","cutPercentage":"0"}示例 4:黑名单排除(排除特定商户)
{"grayBizCode":"MY_FEATURE_SWITCH","switchOn":"true","modeList":"[\"BLACK_LIST\"]","ruleMap":"{\"userId\":[\"2088problem1\",\"2088problem2\"]}","cutPercentage":"0"}示例 5:多策略组合(先过白名单,再过百分比)
{"grayBizCode":"MY_FEATURE_SWITCH","switchOn":"true","modeList":"[\"WHITE_LIST\",\"UID_PERCENTAGE\"]","ruleMap":"{\"instCode\":[\"Z05\"]}","cutPercentage":"30"}- instCode 必须是 Z05(白名单 AND 语义)
- 且分表位 < 30(百分比切流)
- 两者都满足才通过
8. 灰度生命周期管理
一个完整的灰度切流应经历以下阶段:
阶段 1: 内部验证 配置: modeList=["WHITE_LIST"], ruleMap={userId: ["内部测试UID"]} 目标: 仅对内部用户开放 ↓ 验证通过 阶段 2: 小范围灰度 配置: modeList=["WHITE_LIST_ANY_MATCH_ENOUGH"], ruleMap={instCode: ["Z05"]} 目标: 指定 instCode 下的商户开放 ↓ 观察稳定 阶段 3: 渐进放量 配置: modeList=["WHITE_LIST_WITH_PERCENTAGE"], ruleMap={instCode: ["Z05"]}, cutPercentage="10" 目标: 非核心维度命中后 10% 放量 ↓ 逐步提升 cutPercentage: 10 → 30 → 50 → 80 阶段 4: 全量切流 配置: modeList=["ALL_PASS"] 目标: 全量开放 ↓ 运行稳定 阶段 5: 下线灰度代码 目标: 移除灰度判断逻辑,删除 ComPar配置,清理 GrayBizCodeEnum重要提醒:阶段 5 常常被忽略。灰度配置不是永久的,切流完成后要及时下线灰度代码和配置,否则代码中会残留大量无用的灰度判断逻辑,增加维护成本。
9. 设计模式总结
| 设计模式 | 应用位置 | 效果 |
|---|---|---|
| 策略模式 | GrayStrategyHandler+ 7 种实现 | 不同灰度判定算法可独立变化,互不影响 |
| 模板方法模式 | PercentageStrategyHandler→ReferenceUidPercentageStrategyHandler | 复用百分比切流骨架,只替换分桶算法 |
| 模板方法模式 | AnyMatchEnoughWhiteListStrategyHandler→AnyMatchEnoughWhiteListAndPercentageStrategyHandler | 复用白名单匹配逻辑,只扩展命中后的百分比判断 |
| 枚举即路由表 | GrayStrategyModeEnum持有 Handler 实例 | 省去工厂类,枚举值 = 策略码 + 处理器 |
| 配置驱动 | 配置中心存储 GrayStrategyConfig | 策略规则热更新,无需发版 |
| 组合模式 | modeList 数组 | 多个策略 AND 组合,实现精细化切流控制 |
10. 接入指南
10.1 新增灰度场景
Step 1:在GrayBizCodeEnum中添加业务码枚举(可选,建议添加以获得编译期检查):
publicenumGrayBizCodeEnum{// .../** 你的业务场景描述 */YOUR_BIZ_SCENE_CODE,// ...}Step 2:在配置中心(ComPara)添加iexpmprodGrayStrategyConfig配置项,grayBizCode填写你的业务码。
Step 3:在业务代码中调用:
@SofaReferenceprivateGrayStrategyServicegrayStrategyService;publicvoidyourBusinessMethod(){StringuserId=getCurrentUserId();booleanisNewFlow=grayStrategyService.isPassGrayStrategy(GrayBizCodeEnum.YOUR_BIZ_SCENE_CODE.name(),userId,GrayStrategyService.buildUidParamMap(userId),false// 配置不存在时默认走老流程);if(isNewFlow){// 新流程}else{// 老流程}}10.2 新增灰度模式
如果现有 7 种模式不满足需求,扩展步骤如下:
Step 1:实现GrayStrategyHandler接口:
publicclassYourCustomHandlerimplementsGrayStrategyHandler{@Overridepublicbooleanhandle(GrayStrategyConfigconfig,StringtargetId,Map<String,String>param){// 你的灰度判定逻辑returntrue;// or false}}Step 2:在GrayStrategyModeEnum中添加枚举值:
YOUR_MODE("YOUR_MODE","你的模式描述",newYourCustomHandler()),Step 3:在配置中心的 modeList 中使用"YOUR_MODE"即可。
10.3 参数构建速查
| 场景 | 构建方法 |
|---|---|
| 按 userId 灰度 | GrayStrategyService.buildUidParamMap(userId) |
| 按 instCode 灰度 | GrayStrategyService.buildInstCodeParamMap(instCode) |
| 按 label 灰度 | GrayStrategyService.buildLabelParamMap(label) |
| 按 bizScene 灰度 | GrayStrategyService.buildBizSceneParamMap(bizScene) |
| 按 loginId 灰度 | GrayStrategyService.buildLoginIdParamMap(loginId) |
| 按 salePlanCode 灰度 | GrayStrategyService.buildSalePlanCodeParamMap(salePlanCode) |
| 自定义维度 | GrayStrategyService.buildParamMap("yourKey", "yourValue") |
| 多维度组合 | 自行构建 Map 并 put 多个 key-value |
11. 最佳实践
11.1 命名规范
灰度业务码应清晰表达场景语义,推荐格式:{业务域}_{功能描述}_{灰度目的}
✅ SUPPLY_B2B_MEMBER_WITH_REGISTER_FROM (绑定会员指定 registerFrom) ✅ PORTAL_ONBOARD_STRUCTURE_CHANGE_ISNTCODE (门户入驻解耦-主体维度) ✅ MERCHANT_TRANSACTION_RISK_LIMIT (商户交易限制) ✅ ONBOARD_HK_Z05_SWITCH (香港商户切流到 Z05)11.2 defaultResult 的选择
- 保守策略(推荐):传
false,配置不存在时走老流程。适用于新功能上线初期。 - 激进策略:传
true,配置不存在时走新流程。适用于老流程即将下线的场景。
11.3 日志可观测
每次灰度决策都有 digest 日志输出:
[GrayStrategyService-digest]bizCode=YOUR_BIZ_CODE,targetId=2088xxx,params={userId=2088xxx},isPassGrayStrategy=true.利用这个日志可以进行线上灰度效果统计和问题排查。
11.4 切流节奏建议
1% → 观察 2-3 天 → 5% → 观察 2-3 天 → 10% → 30% → 50% → 80% → 100% → ALL_PASS每次调整百分比只需修改配置中心的cutPercentage值,即时生效。
12. 类图总览
GrayStrategyService (接口) │ └── GrayStrategyServiceImpl (决策引擎) │ ├── GrayStrategyConfig (配置模型) │ bizCode / switchOn / modeList / ruleMap / cutPercentage │ ├── GrayStrategyModeEnum (策略路由) │ ALL_PASS ────→ DefaultStrategyHandler │ WHITE_LIST ──→ WhiteListStrategyHandler │ BLACK_LIST ──→ BlackListStrategyHandler │ UID_PERCENTAGE → PercentageStrategyHandler │ └── ReferenceUidPercentageStrategyHandler │ WHITE_LIST_ANY_MATCH_ENOUGH → AnyMatchEnoughWhiteListStrategyHandler │ └── AnyMatchEnoughWhiteListAndPercentageStrategyHandler │ WHITE_LIST_WITH_PERCENTAGE ──→ (同上) │ └── GrayBizCodeEnum (业务码) FIX_ALI_TRIP_SETTLE_CHANNEL SUPPLY_B2B_MEMBER_WITH_REGISTER_FROM PORTAL_ONBOARD_STRUCTURE_CHANGE_ISNTCODE ... (40+ 业务码)