Skip to content

会员体系升级 - 开发规划(第一期)

状态:第一期全部完成待部署(User 标签/分组、Finance AssetType 平台化、User GradeRight、User 登录记录扩展) 日期:2026-07-21 范围:hiapi-cloud-finance(AssetType 平台化)、hiapi-cloud-user(会员权益/标签分组/登录记录) 前置讨论:见 [[project_user_module]]、[[project_account_system]]


一、总览

背景:用户管理功能盘点后发现四块系统性缺口——资产类型全靠商户手填导致新商户"装了用不了"、会员等级没有可执行权益、无法按人群做精细化触达、缺登录安全审计。经过讨论收敛出第一期范围,原则是:能复用现成基础设施的优先做(积分挂 AssetType、登录记录挂 UserLogs),规则引擎/自动化类的先把数据接口留好、执行逻辑推后

本期做什么(详见二~四节):

  1. Finance:AssetType 平台化(固定 3 类型常量 + 商户注册自动开通 + 内部 Feign 开放 + 商户后台锁定手动创建)
  2. User:会员权益 GradeRight(结构化,同步 Feign 查询 + 异步事件通知,跨子应用不强依赖)
  3. User:自定义标签/分组/群(手动打标,数据接口预留自动规则位)
  4. User:登录记录扩展(复用 UserLogs,不新建表)

本期明确不做(汇总于第六节,含未来低优先级项):积分规则引擎/消耗场景/过期机制、商户自助开通引导流程、自动标签规则引擎/动态分组、生命周期分层看板、异地登录预警、实名认证、运营效率(批量导入导出/账号合并)、优惠券/卡券。


二、Finance:AssetType 平台化

2.1 问题

AssetType.type 是商户自由文本创建,新商户注册后 0 个资产类型,余额/积分等能力形同虚设直到管理员手动配置;且不同商户可能给同一概念起不同 type 值,平台层无法统一识别。

2.2 方案

固定类型常量(FinanceConst 新增):

常量type 值中文名(默认)scalerechargepaytransferwithdrawal
ASSET_TYPE_BALANCEBALANCE余额2falsefalsefalsefalse
ASSET_TYPE_POINTSPOINTS积分0falsefalsefalsefalse
ASSET_TYPE_SHOPPING_CREDITSHOPPING_CREDIT购物金2falsefalsefalsefalse

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 个默认类型。

商户注册时机自动开通(事件驱动,不做同步跨服务调用——createMerchanthiapi-cloud-public 的本地事务,Finance 是另一个服务,不能把跨库调用揉进同一个 @Transactional):

  1. hiapi-core-event-shared 新增 cn.hiapi.event.shared.events.merchant.MerchantRegisterEvent(mid, name, mobile, created)
  2. MsgRoutingKey 新增 MERCHANT_REGISTER = "merchant.register"
  3. 补全 hiapi-cloud-public-appMerchantManagerLogic.createMerchant() 里那行已经写了一半的注释 //this.dispatchContext.getServiceOne(EventPublish);,改为在 SubscriptionApp 保存之后真正 publish(MERCHANT_REGISTER, ...)
  4. 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:

字段类型说明
idbigint主键
midbigint所属商户
gradeIdbigint关联 Grade
rightTypevarchar枚举:DISCOUNT折扣/FREE_SHIPPING免邮/POINTS_MULTIPLIER积分倍率/GIFT礼包/CUSTOM_SERVICE专属客服
rightValuetext(JSON)按 rightType 自定义结构,如 DISCOUNT→{"rate":0.9},GIFT→{"pointsAmount":100}
statusint1启用/0禁用
sortint排序
created/updatedbigint

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
autoRuletext(JSON),预留字段,本期不使用
status1/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
ruletext(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/sendreceiverIds,不改动已上线的消息中心后端代码

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暂缓
优惠券/卡券完全不考虑,不在本期设计范围

七、建议开发顺序

三块工作互相独立、可并行,建议顺序(按见效速度和依赖关系):

  1. User 标签/分组 —— 直接放大已经上线的 Message 中心(发送页加"选标签/选分组"),见效最快,和其他两块无依赖
  2. Finance AssetType 平台化(含 MerchantRegisterEvent)—— 地基类工作,完成后"新商户注册即有可用资产类型"这个体验问题才算解决
  3. User GradeRight —— 和标签分组互不依赖,可穿插进行
  4. User 登录记录扩展 —— 改动最小(一个字段+一个筛选),放最后顺手做

八、需要确认的实现细节

写这份文档过程中做了几个默认判断,如果和预期不符请指出:

  1. AssetType 三个默认类型的权限开关默认值 已确认(2026-07-21):三个都全部关闭,见 §2.2。
  2. AssetsTypeController.add() 创建分支直接报错拒绝 已确认(2026-07-22):维持硬拒绝,不加内部开关退路。
  3. 登录记录的"异地登录预警"和"实名认证"被我列进了 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 新增可空 device text 字段(JSON:{ua, platform, deviceId})
  • UserAccountService.login()LoginRequest.data.device 取值写入(未改 DTO 结构,data 本来就是自由 JSON 字段)
  • 会员详情页"用户日志"Tab 新增"仅看登录记录"勾选框(action=login 筛选,复用已有 UserLogsQuery.action)+ 登录设备列展示

待部署验证:四块全部代码完成、各服务 mvn compile/hiapi-cloud-admin-tsvue-tsc --noEmit 均已验证通过,尚未做建表后的端到端联调(MQ 队列自动建、新商户注册流程、会员打标/圈人/发消息、等级权益 Feign 查询)。