hyapp-server/docs/VIP系统架构.md

12 KiB
Raw Blame History

可配置 VIP 系统

目标与边界

VIP 是 wallet-serviceapp_code 隔离的会员与权益域。每个 App 通过一份 vip_program_configs 选择等级数量、购买有效期策略、后台发放模式和体验卡开关gateway、room、admin 和客户端只能消费这份显式配置,不得通过 app_code 写第二套状态机。

  • wallet-service:会员、体验卡、用户功能开关、权益矩阵、购买扣费、每日返现、资源发放、历史和 outbox 的 owner。
  • gateway-serviceApp HTTP 鉴权、协议转换和钱包 gRPC 编排。
  • room-service:只执行房间权限并通过腾讯云 IM 投递当前房间事件,不能拥有会员状态。
  • server/admin:配置 program、等级和每级完整权益发放真实 VIP 或体验卡。
  • Flutter展示后端状态、操作背包佩戴并处理客户端生命周期效果不参与购买规则计算。

全链路业务时间使用 UTC epoch milliseconds。每日结算或刷新按 UTC 0 点切日;用户本地时间只用于展示。

当前 App 策略

配置 Lalu Fami
program_type legacy_timed tiered_privilege_v1
level_count 10 9
同级购买 从当前截止时间续期 从当前截止时间续期
高级升级 保留剩余时间后追加新时长 立即替换为高级,从购买时刻重新计时,旧等级剩余时间丢弃
低级购买 拒绝 拒绝
后台发放 direct_membership,直接写真实 VIP trial_card,发到背包后由用户佩戴
体验卡 关闭 开启
权益集合 每级显式保存 每级显式保存完整集合,不按 unlock_level 自动继承

Fami 的 VIP1VIP9 初始全部为 disabled。30 天只是不产生零时长脏配置的占位值;价格和资源组保持 0运营完成配置并启用前不可购买或发卡。

数据模型

App 级 program

vip_program_configs 是行为规则的唯一来源:

  • program_type
  • level_count
  • same_level_expiry_policy
  • upgrade_expiry_policy
  • downgrade_purchase_policy
  • benefit_inheritance_policy,当前固定 target_only
  • grant_mode
  • trial_card_enabled
  • status
  • config_version

后台修改 program、等级或权益时递增 config_version。会员、订单和体验卡保留创建时的 program/version 快照,当前权限判断使用最新有效 program 和等级显式权益集合。

等级和权益

  • vip_levels:等级名称、状态、金币价格、时长、可选资源组和排序。
  • vip_level_benefits:以 (app_code, level, benefit_code) 保存该等级最终完整权益集合。

unlock_level 只记录 P1 初始化来源,运行时和后台保存都不能据此给高等级自动补权益。纯功能权益的 resource_id=0;装扮权益可以绑定同 App 的实际资源。

付费会员

user_vip_memberships 保存用户付费会员事实。有效条件是 level > 0 && expires_at_ms > now_ms;不依赖异步过期任务才能失效。

购买事务同时完成:

  1. 锁定 program、目标等级和当前会员
  2. 根据 program 计算有效期,拒绝降级;
  3. 锁定 COIN 账户并扣费;
  4. 发放目标资源组;
  5. 更新会员,写订单、历史和 wallet outbox
  6. 一次事务提交。

command_id 是购买幂等键,request_id 仅用于链路追踪。

vip_command_locks 在任何会员、账户或资源业务锁之前串行化 (app_code, command_id)。购买、直接会员赠送和体验卡发放共享该守卫空间;同命令并发重试会等待原事务提交后读取稳定回执,三种业务也不能交叉复用同一命令号。

体验卡

体验卡使用三层事实:

  • resources(resource_type=vip_trial_card):卡面与等级元数据;
  • user_resource_entitlements:背包实例和绝对有效期;
  • user_resource_equipment:当前单选佩戴指针;
  • user_vip_trial_cards:等级、来源、时长和 program/version 快照。

发卡时即写入 effective_at_msexpires_at_ms,但不自动佩戴。切换卡片只替换 user_resource_equipment 指针,不修改、删除、暂停或延长任一卡片。

例如 A=14 天、B=20 天、C=30 天:佩戴 A 后立刻切 BB 生效A 仍保留原绝对到期时间;之后只要 A 未过期即可重新佩戴。体验卡等级不受当前付费 VIP 或上一张体验卡等级限制。

用户功能开关

user_vip_settings(app_code,user_id) 保存两个用户偏好:

  • room_entry_notice_enabled:进房 VIP 高亮通知;
  • online_global_notice_enabled:上线 VIP 全服飘屏。

无记录时两项默认为 true,首次修改才写行。偏好不授予权益:无 VIP 或对应等级没有权益时,即使开关为开也不会放行。用户可在无 VIP、VIP 过期或切换体验卡时保留偏好Fami 与 Lalu 互不影响。

每日金币返现

daily_coin_rebatemetadata_json.coin_amount 是该等级每日可领金币正整数。只有启用的付费 VIP 参与,体验卡即使历史配置误开 trial_enabled 也会被 wallet 二次排除。

每个 App 每个 UTC 日期只有一个 vip_daily_coin_rebate_runs

  1. 首个成功批次固化当时的 config_version 和等级金额矩阵;后续分页不重读新配置。
  2. wallet 根据会员历史还原 UTC 0 点时的付费 VIP0 点后才购买或升级的状态从次日起参与。
  3. 生成 vip_daily_coin_rebates 资格,领取窗口固定为 [UTC 当日0点,UTC 次日0点)
  4. 同事务写 VipDailyCoinRebateAvailable outboxactivity-service 幂等投影为“VIP金币返现”系统消息。
  5. 用户手动领取时返现行锁、COIN 余额、交易、分录和 WalletBalanceChanged outbox 在一个事务内提交。

“日切资格”严格按 UTC 0 点;“金额配置”是当日首个成功 run 的快照,不伪造系统尚未保存的 0 点历史配置。正常情况下 cron 每分钟触发;如零点后 wallet/cron 长时间不可用,恢复前的后台金额修改会进入当日快照。运营若要保证修改次日生效,应在当日 run 已创建后修改。

最终生效状态

所有调用方统一消费 VipState

  • paid_vip:真实购买会员;
  • equipped_trial_card:当前有效且已佩戴的体验卡;
  • effective_vip:最终展示和权限使用的 VIP
  • effective_sourcepaidtrialnone
  • effective_benefits:最终身份拥有的权益资格;可关闭的通知类权益还要与 user_settings 做 AND
  • program_config:当前 App 规则;
  • user_settings:当前 App 下的进房/上线通知偏好。

program 启用时的优先级固定为:trial_card_enabled=true 且体验卡有效并已佩戴时覆盖展示;否则使用有效付费会员;二者都无效则为 none。关闭体验卡开关只停止它参与最终权限合并,背包和佩戴原始事实仍保留;关闭整个 program 时付费与卡片原始事实仍可查询,但最终身份为 none、不下发权益。

钱包只读取 effective_vip.level 对应的显式权益行。来源为 trial 时,再移除 trial_enabled=false 的权益;当前 daily_coin_rebate 不对体验卡开放。owner service 必须按 benefit_code 判断,不能改回 level >= N;进房和上线通知的执行方必须使用 wallet 已合并用户开关的结果。

P1 权益编码

等级 本级新增权益
VIP1 vip_badge_identityvip_medalavatar_framevip_title
VIP2 chat_bubblevip_giftentry_effectvisitor_history
VIP3 voice_waveprofile_cardcustom_room_backgroundroom_image_message
VIP4 animated_avataranimated_room_covermic_skinroom_bordervehicle
VIP5 colored_room_namecolored_nicknamecolored_idgift_tray_skin
VIP6 hide_profile_datacustom_avatar_frameleaderboard_invisibledaily_coin_rebate
VIP7 custom_profile_cardanonymous_profile_visitcustom_gift
VIP8 room_entry_noticecustom_pretty_idcustom_vehicle
VIP9 anti_kickanti_muteonline_global_notice

初始化时高等级行已展开为完整集合,但这是 seed 结果,不是运行时继承规则。后台可以显式移除任一等级的某项权益。

房间权限和 IM

room-service 在需要房间权限时读取 wallet 的 GetMyVip.state

  • custom_room_backgroundP1 program 的房主保存或切换自定义背景前强校验Lalu 保持原逻辑。
  • anti_kick:普通房主/管理员踢人时拒绝操作。
  • anti_mute:普通房主/管理员新增禁言时拒绝操作。
  • 平台封禁、风控、超级管理员使用独立系统治理入口,不经过上述普通房管守卫。
  • room_entry_notice:权益有效且用户开关开启时,随 JoinRoomResponseRoomUserJoined 和腾讯云 IM 当前房间系统消息携带;只在用户进入的当前房间展示,不做全服广播。

online_global_noticePOST /vip/online-notice 触发gateway 先调用 wallet CheckVipBenefit,同时校验权益资格和用户开关;然后从 user-service 取服务端用户资料,最后写 activity-service 全局播报 outbox。腾讯云 IM 使用 TIMCustomElemExt=im_broadcastDesc=vip_online_noticebroadcast_type=vip_online_noticescope=global。Flutter 在进程内按 UTC 日控制触发:同一进程每天最多触发一次,不持久化该标记;杀进程重启后允许再次触发。服务端不做用户日级去重,但相同 command_id 的 HTTP 重试映射到同一 event_id。Flutter 不能直接向全服群发消息。

App HTTP API

统一前缀 /api/v1,响应使用 {code,message,request_id,data}

  • GET /vip/packagesprogram、可购买等级、每级权益和完整 state。
  • GET /vip/me:付费、体验卡、最终 VIP、权益资格和用户功能开关。
  • POST /vip/purchase:请求 {command_id,level};价格、时长和资源由服务端读取。
  • GET|PATCH /vip/settings:读取或局部修改进房/上线通知偏好;空 PATCH 拒绝。
  • POST /vip/online-notice:请求 {command_id},服务端校验权益、开关和资料后写全服播报 outbox。
  • GET /vip/coin-rebates/current:返回当前 UTC 日返现资格;无资格或 cron 尚未生成时 found=false
  • POST /vip/coin-rebates/statuses:按最多 100 个 rebate_ids 恢复系统消息真实状态,或用受限分页查历史。
  • POST /vip/coin-rebates/{rebate_id}/claim:请求 {command_id},金额、归属、过期和入账全由 wallet 裁决。
  • GET /users/me/resources?resource_type=vip_trial_card&active_only=true:查询背包体验卡资源实例。
  • POST /vip/trial-cards/{entitlement_id}/equip:佩戴指定背包实例。
  • DELETE /vip/trial-cards/equipped:卸下当前体验卡。
  • 历史通用背包装备/卸下接口命中 vip_trial_card 时由 wallet service 委托上述专用状态机禁止仓储层直接改装备槽Flutter VIP 流程仍使用专用接口以一次取得完整 VipState
  • POST /manager-center/vip-grantsgateway 按当前 program 自动选择直接会员或 30 天体验卡发放Fami 发卡不自动佩戴。

后台接口:

  • GET|PUT /v1/admin/activity/vip-program
  • GET|PUT /v1/admin/activity/vip-levels
  • POST /v1/admin/activity/vip-trial-card-grants
  • POST /v1/admin/activity/vip-grants,仅用于 direct_membership program

权益落地边界

当前服务端已强制执行购买有效期、体验卡状态合并、用户通知开关、每日金币返现资格/消息/手动领取入账、自定义房间背景、防踢、防禁言、房内进场通知和上线全服通知。装扮类权益由已绑定的钱包资源与 Flutter 展示链路执行。

以下权益仍只有稳定编码和资格下发,不能仅凭本架构宣称业务已完整闭环:访客匿名/隐藏、榜单隐身、房间发图、动态媒体、彩色样式和礼物托盘等。各 owner 实现时必须调用 CheckVipBenefit 或消费 effective_benefits,并补自己的存储、审计、风控和边界测试。