Appearance
会员体系升级 - 开发规划(第一期)
状态:第一期全部完成待部署(User 标签/分组、Finance AssetType 平台化、User GradeRight、User 登录记录扩展) 日期:2026-07-21 范围:
hiapi-cloud-finance(AssetType 平台化)、hiapi-cloud-user(会员权益/标签分组/登录记录) 前置讨论:见 [[project_user_module]]、[[project_account_system]]
一、总览
背景:用户管理功能盘点后发现四块系统性缺口——资产类型全靠商户手填导致新商户"装了用不了"、会员等级没有可执行权益、无法按人群做精细化触达、缺登录安全审计。经过讨论收敛出第一期范围,原则是:能复用现成基础设施的优先做(积分挂 AssetType、登录记录挂 UserLogs),规则引擎/自动化类的先把数据接口留好、执行逻辑推后。
本期做什么(详见二~四节):
- Finance:AssetType 平台化(固定 3 类型常量 + 商户注册自动开通 + 内部 Feign 开放 + 商户后台锁定手动创建)
- User:会员权益
GradeRight(结构化,同步 Feign 查询 + 异步事件通知,跨子应用不强依赖) - User:自定义标签/分组/群(手动打标,数据接口预留自动规则位)
- User:登录记录扩展(复用 UserLogs,不新建表)
本期明确不做(汇总于第六节,含未来低优先级项):积分规则引擎/消耗场景/过期机制、商户自助开通引导流程、自动标签规则引擎/动态分组、生命周期分层看板、异地登录预警、实名认证、运营效率(批量导入导出/账号合并)、优惠券/卡券。
二、Finance:AssetType 平台化
2.1 问题
AssetType.type 是商户自由文本创建,新商户注册后 0 个资产类型,余额/积分等能力形同虚设直到管理员手动配置;且不同商户可能给同一概念起不同 type 值,平台层无法统一识别。
2.2 方案
固定类型常量(FinanceConst 新增):
| 常量 | type 值 | 中文名(默认) | scale | recharge | pay | transfer | withdrawal |
|---|---|---|---|---|---|---|---|
ASSET_TYPE_BALANCE | BALANCE | 余额 | 2 | false | false | false | false |
ASSET_TYPE_POINTS | POINTS | 积分 | 0 | false | false | false | false |
ASSET_TYPE_SHOPPING_CREDIT | SHOPPING_CREDIT | 购物金 | 2 | false | false | false | false |
2026-07-21 定案:三个默认类型权限开关全部关闭,商户后台按需手动开启(比最初草案"余额默认开充值/支付/提现"更保守,避免刚注册就暴露可用的资金能力带来风控隐患)。
幂等创建方法:AssetTypeService.ensureAssetType(mid, type, name, icon, scale, recharge, pay, transfer, withdrawal) —— 内部先 getByType(mid, type) 查一次,存在则跳过,不存在则创建。AssetTypeService.ensureDefaultAssetTypes(mid) 封装"开通以上 3 个默认类型"这组固定调用,是商户注册自动开通和下面兜底逻辑的唯一入口,避免两处重复维护默认值。
MQ 兜底(2026-07-21 补充):商户注册自动开通走 MQ 异步,消息可能丢失或还没被消费,导致商户打开资产类型页面时是空的。AssetsTypeController.listAssetsType() 在返回空列表时,同步兜底调用一次 ensureDefaultAssetTypes(mid) 再查一次——不管 MQ 有没有正常投递,商户只要打开这个页面就一定能看到 3 个默认类型。
商户注册时机自动开通(事件驱动,不做同步跨服务调用——createMerchant 是 hiapi-cloud-public 的本地事务,Finance 是另一个服务,不能把跨库调用揉进同一个 @Transactional):
hiapi-core-event-shared新增cn.hiapi.event.shared.events.merchant.MerchantRegisterEvent(mid, name, mobile, created)MsgRoutingKey新增MERCHANT_REGISTER = "merchant.register"- 补全
hiapi-cloud-public-app的MerchantManagerLogic.createMerchant()里那行已经写了一半的注释//this.dispatchContext.getServiceOne(EventPublish);,改为在SubscriptionApp保存之后真正publish(MERCHANT_REGISTER, ...) - Finance 新增
EventMerchantRegisterListener implements IEventMessageListener<MerchantRegisterEvent>(放hiapi-finance-event-message,参照现有EventFinanceAssetsReceipt风格),命中后依次ensureAssetType三次(余额/积分/购物金)
内部子应用开放 Feign(供 VEM/Shop 等需要自定义资产类型——如"设备积分""游戏币"——的场景使用):
hiapi-cloud-finance-feign新增FeignAssetType:POST /feign/finance/asset-type/ensure,请求体{mid, type, name, icon, scale, recharge, pay, transfer, withdrawal},幂等,内部调用同一个ensureAssetType- Finance 侧新增对应内部 Controller 实现该接口
商户后台锁定手动创建:
- 后端
AssetsTypeController.add()的id==null(创建分支)直接返回错误:"资产类型由平台统一预置或通过内部服务开通,如需自定义类型请联系平台";id!=null(编辑名称/图标/开关)分支保留不变 - admin-ts
views/finance/assets-type隐藏"新增"按钮,列表/编辑功能保留
开发完成后:把 FeignAssetType 契约和 ensureAssetType 的行为记录进 hiapi-docs(接口文档 + EOLINK 导入),供其他子应用开发者查阅。
2.3 本节不做(低优先级 backlog)
- 商户自助开通引导流程:未来用一个"新手引导"步骤让商户自己走完系统基础配置(含资产类型),替代/补充现在的"自动开通 3 个默认类型"这种一刀切方案。当前先用固定 3 类型顶上,这个引导流程记为低优先级待开发。
- 积分规则引擎(签到/消费得分等触发规则)
- 积分消耗场景(积分商城/抵现——这本来就该由消费方 App 自己实现,不属于 Finance)
- 积分过期机制
三、User:会员权益 GradeRight
3.1 设计原则
User 只做"声明",不做"执行"。消费/发放动作发生在其他子应用里,GradeRight 的存在与否不依赖任何其他子应用是否安装——纯定制客户没装 shop,也可以在 User 后台配置好权益声明,不会报错、不会依赖外部服务。
3.2 数据模型
新表 hiapi_core_user_grade_right:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| mid | bigint | 所属商户 |
| gradeId | bigint | 关联 Grade |
| rightType | varchar | 枚举:DISCOUNT折扣/FREE_SHIPPING免邮/POINTS_MULTIPLIER积分倍率/GIFT礼包/CUSTOM_SERVICE专属客服 |
| rightValue | text(JSON) | 按 rightType 自定义结构,如 DISCOUNT→{"rate":0.9},GIFT→{"pointsAmount":100} |
| status | int | 1启用/0禁用 |
| sort | int | 排序 |
| created/updated | bigint | — |
3.3 接口
同步查询(适合折扣率这类下单时必须实时用到的权益):
hiapi-cloud-user-feign新增FeignGradeRight:GET /feign/user/grade-right/list?mid=&uid=—— 内部先查GradeInfo拿当前 gradeId,再查该等级全部生效中的权益,返回给调用方(shop/vem 等)自行解析应用
异步事件(适合升级礼包这类一次性、允许延迟的动作):
- User 等级变更时发布
UserGradeChangeEvent(mid, uid, oldGradeId, newGradeId) - 谁想接就订阅:Finance 可订阅它调用
changeAssets发放升级积分礼包,Message 中心可订阅它发条祝贺消息——不订阅则什么都不发生,不影响主流程
3.4 后台
/user/grade 编辑弹窗新增"权益配置"区块,增删改权益项(按 rightType 出不同的表单)。
四、User:自定义标签/分组/群
4.1 范围
本期只做手动打标签、手动圈定分组成员。自动规则(按行为/属性自动打标、动态规则圈人)的数据接口这次就设计好(字段建好、类型定义好),但执行规则的定时任务不做——将来补规则引擎只需要新增一个 job 读现成的字段,不需要再改表结构。
4.2 数据模型
hiapi_core_user_tag(标签定义):
| 字段 | 说明 |
|---|---|
| id, mid | — |
| name | 标签名 |
| color | 展示色 |
| type | 枚举 MANUAL/AUTO,本期只启用 MANUAL |
| autoRule | text(JSON),预留字段,本期不使用 |
| status | 1/0 |
| created | — |
hiapi_core_user_tag_relation(用户-标签关系,多对多):id, mid, uid, tagId, created,唯一键 (mid, uid, tagId)
hiapi_core_user_group(分组/人群包):
| 字段 | 说明 |
|---|---|
| id, mid, name, remark | — |
| ruleType | 枚举 STATIC/DYNAMIC,本期只启用 STATIC |
| rule | text(JSON),预留字段,DYNAMIC 暂不使用 |
| memberCount | 缓存的成员数,新增/移除成员时同步更新 |
| status, created | — |
hiapi_core_user_group_member(STATIC 分组的静态成员):id, mid, groupId, uid, created,唯一键 (mid, groupId, uid)
4.3 后台(要求:标签/分组要容易搜索查看)
- 「标签管理」页:标签列表(增删改)+ 会员列表页新增"按标签筛选"header + 会员详情页可直接打标签/摘标签
- 「分组管理」页:分组列表 + 新建(手动选人,或从会员列表筛选结果批量加入)+ 成员管理面板,成员列表支持按昵称/手机号搜索,分组列表支持按名称搜索
- Message 中心发送页升级:接收人来源从"手动勾选"新增"选标签"/"选分组"入口——后端新增两个解析接口:
GET /merchant/user-tag/resolve-uids?tagId=GET /merchant/user-group/resolve-uids?groupId=返回 uid 列表后,前端拼进现有POST /merchant/message/notification/send的receiverIds,不改动已上线的消息中心后端代码
4.4 本节不做(低优先级 backlog)
- 自动标签规则引擎(定时任务按
autoRule打标/摘标) - 动态分组规则计算(按
rule实时圈人) - 生命周期分层看板(新客/活跃/沉默/流失——本质是自动标签的一个应用场景,依赖规则引擎先落地,暂不做)
五、User:安全与信任(登录记录)
5.1 现状
C 端登录(UserAccountService)已经在写 UserLogs(action=login,含 ip),不是从零开始。缺口只是没有设备信息、后台也没有专门的"登录记录"筛选视图。
5.2 方案(不新建表,复用 UserLogs)
UserLogs新增一个可空字段device(text,JSON:{ua, platform, deviceId})- 登录请求本身不用改 DTO 结构——
BasicLoginRequest.data本来就是自由 JSON 字段,约定前端把设备信息放进data.device,登录逻辑落库时取出写进UserLogs.device - 会员详情页"用户日志"Tab(阶段一已做)新增一个"仅看登录记录"筛选(
action=login),即视为"登录设备/登录历史"能力,不新建页面
5.3 本节不做/需另行确认(低优先级 backlog)
- 异地登录预警:比对本次/上次登录 IP 地理位置,差异大则通过 Message 中心发条 SECURITY 分类站内信。技术上可复用 VEM 已验证的 IP 地理解析方案([[project_vem_device_ip_geo]]),但本期不做。
- 实名认证 KYC:涉及合规,建议先确认核验强度要求(人工审核 vs 三方 SDK 自动核验)再单独排期,本期不做。
- 风控规则引擎(异常登录/频繁改密等规则)
六、本期完全不做的功能汇总
| 功能 | 归属 | 备注 |
|---|---|---|
| 积分规则引擎/消耗场景/过期机制 | Finance | 见 §2.3 |
| 商户自助开通引导流程 | Finance | 低优先级,见 §2.3 |
| 自动标签规则引擎/动态分组 | User | 数据接口已预留,见 §4.4 |
| 生命周期分层看板 | User | 依赖自动标签,见 §4.4 |
| 异地登录预警 / 实名认证 / 风控规则 | User | 见 §5.3 |
| 运营效率(批量导入导出、账号合并) | User | 暂缓 |
| 优惠券/卡券 | — | 完全不考虑,不在本期设计范围 |
七、建议开发顺序
三块工作互相独立、可并行,建议顺序(按见效速度和依赖关系):
- User 标签/分组 —— 直接放大已经上线的 Message 中心(发送页加"选标签/选分组"),见效最快,和其他两块无依赖
- Finance AssetType 平台化(含
MerchantRegisterEvent)—— 地基类工作,完成后"新商户注册即有可用资产类型"这个体验问题才算解决 - User GradeRight —— 和标签分组互不依赖,可穿插进行
- User 登录记录扩展 —— 改动最小(一个字段+一个筛选),放最后顺手做
八、需要确认的实现细节
写这份文档过程中做了几个默认判断,如果和预期不符请指出:
AssetType 三个默认类型的权限开关默认值已确认(2026-07-21):三个都全部关闭,见 §2.2。已确认(2026-07-22):维持硬拒绝,不加内部开关退路。AssetsTypeController.add()创建分支直接报错拒绝- 登录记录的"异地登录预警"和"实名认证"被我列进了 backlog(未明确否定但你也没明确肯定),不是本期范围——确认这个理解没错
九、进展记录
User 标签/分组/群(§四,已完成待部署)
后端(hiapi-cloud-user,mvn compile 通过):
- 新表
hiapi_core_user_tag/hiapi_core_user_tag_relation/hiapi_core_user_group/hiapi_core_user_group_member(hiapi-user-entity),关系表用@Table(uniqueConstraints=...)唯一键保证幂等,写入沿用MsgAnnouncementCursorService.touch的"先查后插,唯一键冲突转忽略"模式 UserTagController(/merchant/user-tag)、UserGroupController(/merchant/user-group):标准 CRUD +attach/detach(打标/摘标签)+member/add/member/remove/member/query(圈人/移除/按昵称手机号账号搜成员)+resolve-uids(与文档 §4.3 约定路径一致)UserQuery新增ids: Long[]过滤字段(供"按标签筛选会员列表"复用,不是消息中心专用)
前端(hiapi-cloud-admin-ts,vue-tsc --noEmit 通过):
- 新页面「标签管理」(
/user/tag)、「分组管理」(/user/group,含成员管理抽屉 + 选人对话框),菜单50505000/50506000 - 会员列表页(
views/user/index.vue)新增"按标签筛选" header;会员详情页新增标签展示/打标/摘标签 - 会员群发消息对话框新增"接收人来源"单选(手动勾选/按标签/按分组),按标签/分组时前端先调
resolve-uids解析出 uid 列表再拼进已上线的POST /merchant/message/notification/send,未改动消息中心后端代码
待部署验证:建表(ddl-auto: update 自动建,无需手动 SQL)、前后端联调。
Finance AssetType 平台化(§二,已完成待部署)
框架层(hiapi-fast-frame,mvn install 已跑,downstream 服务已拿到新类):
hiapi-core-event-shared新增cn.hiapi.event.shared.events.merchant.MerchantRegisterEvent(mid/name/mobile/created)MsgRoutingKey新增MERCHANT_REGISTER = "merchant.register"
Finance(hiapi-cloud-finance,mvn compile 通过):
FinanceConst新增ASSET_TYPE_BALANCE/ASSET_TYPE_POINTS/ASSET_TYPE_SHOPPING_CREDIT三个常量AssetTypeService.ensureAssetType(...)幂等开通方法(先getByType查一次,存在则跳过)EventMerchantRegisterListener(hiapi-finance-event-message,风格照抄EventFinanceAssetsReceipt)订阅MERCHANT_REGISTER,命中后依次开通余额(scale2,recharge/pay/withdrawal开,transfer关)/积分(scale0,仅pay)/购物金(scale2,仅pay)AssetTypeApi(契约,hiapi-finance-contract)+AssetTypeFeignClient(hiapi-finance-contract-feign)+ 内部FeignAssetTypeController(hiapi-finance-merchant-api)
(写这段时它还叫FeignAssetType、放在已归档的hiapi-basic-feign;T1 契约归位后是现在这个位置)(/feign/finance/asset-type/ensure,风格照抄FeignPaymentController,请求体走hiapi-finance-feign-data内部 DTO,不直接依赖客户端模块类型)—— 目前还没有任何调用方接入,是给未来 VEM/Shop 等子应用自定义资产类型预留的入口AssetsTypeController.add()创建分支(id==null)直接返回错误拒绝;编辑分支不变
Public(hiapi-cloud-public,mvn compile 通过):
- 补全
MerchantManagerLogic.createMerchant()里原本注释掉的事件发布,SubscriptionApp保存之后真正publish(MERCHANT_REGISTER, ...),appId复用CloudConst.APP_ID_CLOUD_PLATFORM(与同方法里SubscriptionApp.appId保持一致,该模块没有专属 appId 常量)
admin-ts(vue-tsc --noEmit 通过):
views/finance/assets-type隐藏"新增"入口(header 置空),编辑功能不变
待部署验证:新商户注册后经 MQ 异步开通 3 个默认资产类型,需要联调验证(队列自动建、ensureAssetType 未加数据库唯一约束,理论上重复投递可能产生重复行,注册场景概率极低,先不处理,见§八item2 一类的判断)。
User GradeRight(§三,已完成待部署)
后端(hiapi-cloud-user,mvn compile 通过):
- 新表
hiapi_core_user_grade_right(mid/gradeId/rightType/rightValue json/status/sort),GradeRightController(/merchant/user/grade-right)标准 CRUD,parseData/buildFields校验 gradeId 属于当前商户 - 同步查询:
GradeRightApi(契约,hiapi-user-contract)+GradeRightFeignClient(hiapi-user-contract-feign)+FeignGradeRightController(hiapi-user-merchant-api),GET /feign/user/grade-right/list?mid=&uid=
(写这段时它还叫FeignGradeRight、放在已归档的hiapi-basic-feign),内部FeignGradeRightController直接读User.gradeId(不是GradeInfo——后台改等级走的是User.gradeId字段,GradeInfo表当前后台没在维护,踩坑记录见下)查该等级生效中的权益 - 异步事件:框架层新增
UserGradeChangeEvent(mid/uid/oldGradeId/newGradeId)+MsgRoutingKey.USER_GRADE_CHANGE,UserController.editGrade()改完等级后发布,目前零订阅方,是纯预留(谁想接谁订阅,不订阅不影响主流程)
admin-ts(vue-tsc --noEmit 通过):
/user/grade编辑抽屉新增"权益配置"区块(仅编辑已保存等级时可见),表格+弹窗增删改权益项,按rightType出不同表单(折扣率/积分倍率/礼包积分数/说明)
踩坑记录:User 实体注释写着"等级id 不做线上保存,通过 GradeInfo 获取",但实际 UserController.editGrade() 改的是 User.gradeId 字段,GradeInfo 表并未同步写入——这是当前代码库里已存在的不一致,本次 FeignGradeRight 按"实际生效路径"读 User.gradeId,没有去动 GradeInfo,以后如果要修这个不一致记得两处都要看。
User 登录记录扩展(§五,已完成待部署)
UserLogs新增可空devicetext 字段(JSON:{ua, platform, deviceId})UserAccountService.login()从LoginRequest.data.device取值写入(未改 DTO 结构,data本来就是自由 JSON 字段)- 会员详情页"用户日志"Tab 新增"仅看登录记录"勾选框(
action=login筛选,复用已有UserLogsQuery.action)+ 登录设备列展示
待部署验证:四块全部代码完成、各服务 mvn compile/hiapi-cloud-admin-ts 的 vue-tsc --noEmit 均已验证通过,尚未做建表后的端到端联调(MQ 队列自动建、新商户注册流程、会员打标/圈人/发消息、等级权益 Feign 查询)。