24 KiB
成就和徽章体系架构
本文单独定义成就、徽章、长徽章、大徽章、系统赠送徽章和资料页展示的产品/后端边界。等级体系、VIP 体系和资源组体系已有独立文档,本文只说明它们和徽章展示的衔接方式。
目标
- 成就可以由服务端任务完成、事实指标达标或活动条件达成后自动解锁。
- 成就解锁后可以发放徽章,也可以只记录成就不发徽章。
- 系统可以直接赠送徽章,不要求生成一个伪造成就。
- 徽章分为长徽章和大徽章:
- 长徽章用于身份、等级、VIP、机构、职业、官方认证等横向标识。
- 大徽章用于成就、活动、荣誉墙等方形或近方形展示。
- App 可以在资料页顶部展示多个长徽章,在 Honor/荣誉区域展示多个大徽章。
- 房间资料、麦位、公屏和用户主页都只能读取服务端返回的徽章投影,客户端不能自报徽章。
- 后台可以配置成就条件、成就奖励徽章、徽章样式、徽章有效期、系统赠送和展示优先级。
非目标
- 不把成就体系和每日任务体系合并。每日任务是短周期任务和领奖;成就是长期或活动荣誉。
- 不把徽章配置写散到用户资料表。徽章仍然是
wallet-service的资源权益。 - 不允许客户端提交“我完成了某成就”或“我拥有某徽章”。
- 不让
room-service、wallet-service、user-service或game-service在主链路同步判断成就。 - 首版不做徽章交易、徽章合成、徽章回收兑换。
核心概念
| 概念 | 含义 | Owner |
|---|---|---|
成就 achievement |
一套服务端条件和解锁事实,例如完成指定任务、送礼达到 10 万、活动内抽奖 10 次 | activity-service |
徽章资源 badge resource |
用户可以持有、过期、展示或佩戴的资源,resources.resource_type=badge |
wallet-service |
长徽章 strip badge |
横向胶囊/铭牌样式,用于等级、VIP、身份、机构、认证等 | wallet-service 配置,来源状态由对应服务提供 |
大徽章 tile badge |
方形或近方形荣誉图标,用于成就、活动赠送、系统荣誉 | wallet-service 配置,成就来源由 activity-service 触发 |
徽章持有 entitlement |
用户已经获得某个徽章资源,可能永久,也可能限时 | wallet-service |
徽章展示 display slot |
用户资料页、荣誉墙、房间资料等位置上展示哪些徽章和排序 | wallet-service 或展示投影 |
关键关系:
- 成就解锁不等于徽章持有。成就是荣誉事实,徽章是资源权益。
- 徽章持有不等于正在展示。用户可以拥有很多大徽章,但资料页只展示前几个。
- 系统赠送徽章不等于成就。运营活动可以直接发一个大徽章,不必污染成就列表。
- 长徽章优先表达“当前身份状态”,大徽章优先表达“历史荣誉沉淀”。
徽章分类
按获取来源
| 来源 | source_type | 示例 | 是否生成成就 |
|---|---|---|---|
| 成就奖励 | achievement |
完成送礼达人成就后获得方形徽章 | 是 |
| 系统赠送 | system_grant |
运营活动、节日、补偿、人工授予徽章 | 否 |
| 等级身份 | level |
财富 M16、游戏 53、魅力 18 | 不一定,通常由等级投影计算 |
| VIP 身份 | vip |
VIP1/VIP7 长徽章 | 否,由 VIP 状态决定 |
| 官方身份 | identity |
主播、公会、代理、币商、官方认证 | 否,由对应身份系统决定 |
| 关系身份 | relationship |
CP、守护、家族关系等 | 可选,通常由关系事实决定 |
按展示形态
| 形态 | badge_form | 展示位置 | 设计规则 |
|---|---|---|---|
| 长徽章 | strip |
昵称下方、资料卡顶部、房间小卡、麦位名称旁 | 横向,图标 + 短文本或等级数字,优先级排序,数量受限 |
| 大徽章 | tile |
Honor 页、荣誉墙、资料页精选荣誉 | 方形/近方形,强调图形资产,可被用户精选展示 |
长徽章不建议放进 Honor 网格里堆叠;大徽章不建议塞到昵称行里,否则会挤压昵称、短号和国家标识。
总体架构
flowchart LR
Task["activity task completed"] --> AchievementConsumer["activity achievement consumer"]
WalletFact["wallet/user/room/game facts"] --> AchievementConsumer
AchievementConsumer --> AchievementDB[("activity achievement_*")]
AchievementDB --> RewardJob["achievement reward job"]
RewardJob --> WalletGrant["wallet-service GrantResourceGroup"]
Admin["server/admin"] --> SystemGrant["wallet-service GrantResource/Group"]
WalletGrant --> BadgeEntitlement[("user_resource_entitlements")]
SystemGrant --> BadgeEntitlement
BadgeEntitlement --> BadgeDisplay["badge display slots / projection"]
Level["activity level read model"] --> BadgeDisplay
VIP["wallet VIP state"] --> BadgeDisplay
Identity["user/host/agency identity"] --> BadgeDisplay
App["App profile / Honor"] --> Gateway["gateway-service"]
Gateway --> BadgeDisplay
处理原则:
- 成就只消费服务端事实事件或服务端任务完成事件。
- 徽章发放统一走
wallet-service资源或资源组发放能力。 - 长徽章里的等级/VIP/身份可以是“计算型展示”,不一定要求每次状态变化都发一个新资源。
- 大徽章必须有明确的徽章资源和持有记录,方便 Honor 页展示、过期过滤和后台审计。
- 高频房间资料展示需要轻量投影,不能实时扫用户全部资源和全部成就。
成就设计
成就分两类:
| 类型 | achievement_type | 示例 | 周期 |
|---|---|---|---|
| 普通成就 | ordinary |
完成 10 个每日任务、累计送礼 10 万、累计上麦 100 小时 | lifetime |
| 活动成就 | activity_limited |
活动期内完成指定游戏挑战、送指定礼物、登录满 7 天 | activity:{collection_id} |
成就触发来源:
| 触发来源 | metric_type 示例 | 说明 |
|---|---|---|
| 每日/专属任务完成 | task_completed_count、specific_task_completed |
task module 写出 TaskCompleted 服务端事件,成就系统消费 |
| 送礼/收礼 | gift_spend_coin、gift_received_value |
以 wallet 账务事实为准 |
| 游戏 | game_spend_coin、game_win_coin、game_round_count |
以游戏结算或钱包扣费事实为准 |
| 关系/社交 | follow_count、followed_count、relationship_days |
以 user-service 关系事实为准 |
| 活跃 | active_day_count、consecutive_active_days |
以服务端活跃事实为准 |
成就解锁规则
- 单条件成就:一个指标达到目标值后解锁。
- 多条件成就:多个条件全部达到后解锁,首版只支持 AND。
- 活动成就只统计
occurred_at_ms >= start_ms && occurred_at_ms < end_ms的事件。 - 普通成就
cycle_key=lifetime;活动成就cycle_key=activity:{collection_id}。 - 退款、撤销、解除关系等首版不自动回滚已解锁成就,后续通过风控或后台补偿处理。
成就奖励徽章
成就定义里不直接写图片 URL,只绑定资源组或主徽章资源:
| 字段 | 说明 |
|---|---|
primary_badge_resource_id |
该成就主展示徽章,通常是大徽章 |
reward_resource_group_id |
解锁后发放的资源组,可以包含徽章、头像框、金币等 |
auto_pin_policy |
是否自动放入资料页精选展示,例如 none / if_empty / always |
honor_visibility |
是否进入 Honor 列表,默认进入 |
如果 reward_resource_group_id 内包含大徽章,必须保证对应资源的 badge_form=tile。如果成就只用于记录不发徽章,primary_badge_resource_id=0 且 reward_resource_group_id=0。
系统赠送徽章
系统赠送用于运营或后台直接授予徽章:
| 场景 | 推荐方式 |
|---|---|
| 活动参与奖 | 后台批量赠送资源组,grant_source=activity_campaign |
| 官方认证 | 授予长徽章,grant_source=identity_verify,通常限时或直到身份取消 |
| 补偿/人工荣誉 | 后台赠送大徽章,grant_source=admin |
| 节日限定 | 赠送限时大徽章,资源组 item 配置 duration_ms |
系统赠送只写资源授予事实,不写 user_achievement_unlocks。如果产品希望用户在 Honor 页看到“活动赠送”的记录,可以从 wallet 的 ResourceGroupGranted/UserResourceChanged 事件生成 user_badge_grants 投影,来源标记为 system_grant。
长徽章设计
长徽章用于资料头部的身份行,类似截图中昵称下方的 M16、等级数字、身份标识、VIP 等。
长徽章来源
| 类型 | 示例 | 展示来源 |
|---|---|---|
| 等级长徽章 | 财富 M16、游戏 53、魅力 18 | activity-service 等级摘要 |
| VIP 长徽章 | VIP1/VIP7 | wallet-service VIP 当前状态 |
| 身份长徽章 | 主播、代理、币商、官方、家族 | 对应身份系统或后台授予 |
| 关系长徽章 | CP 天数、守护关系 | user-service 关系事实 |
长徽章有两种实现方式:
| 方式 | 使用场景 | 说明 |
|---|---|---|
| 计算型长徽章 | 等级、VIP 这类随状态变化的标识 | 不需要每级发一个资源,查询时根据状态和配置生成展示项 |
| 资源型长徽章 | 官方认证、代理、币商等运营授予标识 | 通过 badge 资源发放,有有效期和审计 |
等级和 VIP 不建议每次升级都发一个新长徽章资源,否则会产生大量过期或废弃权益。更稳妥的做法是保存等级/VIP 状态,按 badge_display_specs 的模板渲染,例如 M{level}、VIP{level}。
展示规则
- 长徽章展示槽位建议为
profile_strip。 - 默认最多展示 6 个,超过数量按
priority和业务权威排序截断。 - 推荐排序:官方/身份认证 > VIP > 等级 > 关系 > 活动临时身份。
- 长徽章文字必须短,避免昵称区域换行挤压。
- 可过期长徽章在
expires_at_ms <= now后自动从展示投影移除。 - 用户不应能手动伪造等级/VIP/认证长徽章,只能在允许的资源型长徽章中选择是否展示。
大徽章设计
大徽章用于 Honor 页和资料页精选荣誉,类似截图中一排方形金色徽章。
大徽章来源
| 类型 | 示例 | 获取 |
|---|---|---|
| 成就大徽章 | 送礼达人、麦上之星、连续活跃 30 天 | 成就解锁后发放 |
| 活动大徽章 | 生日活动、节日活动、游戏赛季 | 活动成就或系统赠送 |
| 官方大徽章 | 官方精选、赛事冠军、年度荣誉 | 系统赠送 |
展示规则
- 大徽章展示槽位建议为
profile_tile和honor_wall。 - Honor 页展示用户拥有的全部有效大徽章,按获得时间倒序或后台排序展示。
- 资料页顶部精选只展示 3 到 6 个,用户可以手动置顶;未设置时按稀有度、优先级、获得时间自动选择。
- 已过期的大徽章默认不展示;如果产品要展示历史已过期活动徽章,需要给资源配置
historical_visible=true,但不能再作为有效权益使用。 - 大徽章点击后可以进入详情页,展示来源、获得时间、有效期、对应成就进度或系统赠送原因。
数据模型
钱包资源侧
徽章本体继续复用 resources:
resources(
app_code, resource_id, resource_code, resource_type,
name, status, grantable, grant_strategy,
asset_url, preview_url, animation_url, metadata_json,
created_at_ms, updated_at_ms
);
约束:
- 徽章资源必须
resource_type=badge。 - 大徽章推荐
grant_strategy=set_active_flag,永久持有。 - 限时活动徽章可以使用
expires_at_ms控制有效期。 - 徽章图片、动画和客户端样式从资源库读取,不允许成就配置直接填散落 URL。
建议新增徽章展示规格表:
badge_display_specs(
app_code VARCHAR(32) NOT NULL,
resource_id BIGINT NOT NULL,
badge_form VARCHAR(32) NOT NULL, -- strip / tile
badge_kind VARCHAR(32) NOT NULL, -- level / vip / achievement / system / identity
default_slot VARCHAR(32) NOT NULL, -- profile_strip / profile_tile / honor_wall
display_name VARCHAR(128) NOT NULL,
priority INT NOT NULL DEFAULT 0,
rarity VARCHAR(32) NOT NULL DEFAULT '',
text_template VARCHAR(128) NOT NULL DEFAULT '',
display_config_json JSON NULL,
status VARCHAR(32) NOT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY(app_code, resource_id),
KEY idx_badge_specs_form(app_code, badge_form, status, priority)
);
如果首版不想加表,可以先把这些字段放进 resources.metadata_json;但后台要按长徽章/大徽章筛选、排序和校验时,独立表更清晰。
成就侧
achievement_collections(
app_code, collection_id, collection_type,
title, status, effective_from_ms, effective_to_ms,
sort_order, display_config_json,
created_at_ms, updated_at_ms
);
achievement_definitions(
app_code, achievement_id, collection_id,
achievement_type, title, description,
status, version,
primary_badge_resource_id,
reward_resource_group_id,
auto_pin_policy,
honor_visibility,
effective_from_ms, effective_to_ms,
sort_order,
created_by_admin_id, updated_by_admin_id,
created_at_ms, updated_at_ms
);
achievement_conditions(
app_code, condition_id, achievement_id, version,
metric_type, target_value, target_unit,
dimension_filter_json,
created_at_ms
);
user_achievement_progress(
app_code, user_id, achievement_id, condition_id, cycle_key,
progress_value, target_value, status,
updated_at_ms,
PRIMARY KEY(app_code, user_id, achievement_id, condition_id, cycle_key)
);
user_achievement_unlocks(
app_code, user_id, achievement_id, cycle_key,
version, status, unlocked_at_ms,
reward_job_id,
created_at_ms, updated_at_ms,
PRIMARY KEY(app_code, user_id, achievement_id, cycle_key)
);
徽章展示槽位
现有 user_resource_equipment 对 resource_type=badge 只能保存一个装备项,无法满足多个长徽章和多个大徽章同时展示。建议不要直接复用它做徽章墙,而是新增多槽位表:
user_badge_display_slots(
app_code VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
slot VARCHAR(32) NOT NULL, -- profile_strip / profile_tile / honor_wall / room_card
position INT NOT NULL,
badge_form VARCHAR(32) NOT NULL, -- strip / tile
resource_id BIGINT NOT NULL,
entitlement_id VARCHAR(96) NOT NULL DEFAULT '',
source_type VARCHAR(32) NOT NULL, -- achievement / system_grant / level / vip / identity
source_id VARCHAR(128) NOT NULL DEFAULT '',
pin_mode VARCHAR(32) NOT NULL DEFAULT '', -- auto / user / system
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY(app_code, user_id, slot, position),
KEY idx_badge_display_user(app_code, user_id, slot, updated_at_ms),
KEY idx_badge_display_resource(app_code, resource_id)
);
校验:
profile_strip只允许badge_form=strip。profile_tile和honor_wall只允许badge_form=tile。- 资源型徽章必须校验用户持有有效
user_resource_entitlements。 - 计算型长徽章可以
entitlement_id='',但必须能从等级/VIP/身份状态验证。 - 同一槽位同一
resource_id不应重复展示。
资料展示投影
高频资料展示建议维护轻量投影:
user_badge_display_profiles(
app_code VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
strip_badges_json JSON NOT NULL,
tile_badges_json JSON NOT NULL,
honor_summary_json JSON NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY(app_code, user_id)
);
投影内容只放展示所需字段,例如 resource_id/name/badge_form/source_type/asset_url/text/level/expires_at_ms/priority。完整详情仍回源到成就和资源接口。
发放流程
成就获得大徽章
sequenceDiagram
autonumber
participant Source as Task/Wallet/User/Game Fact
participant Act as activity-service
participant DB as activity MySQL
participant Wallet as wallet-service
participant App as App
Source->>Act: TaskCompleted or metric event
Act->>DB: update achievement progress idempotently
Act->>DB: insert user_achievement_unlocks
Act->>DB: insert reward job
Act->>Wallet: GrantResourceGroup(command_id)
Wallet->>Wallet: create badge entitlement
Wallet-->>Act: grant_id
Act->>DB: mark reward granted
App->>Act: query achievements / badges
幂等键建议:
- 成就消费:
source_event_id:achievement:{achievement_id}:{user_id}:{cycle_key} - 奖励发放:
achievement_reward:{user_id}:{achievement_id}:{cycle_key} - 钱包命令:
wallet_grant:{reward_job_id}
系统直接赠送徽章
sequenceDiagram
autonumber
participant Admin as server/admin
participant Wallet as wallet-service
participant Projection as badge projection
Admin->>Wallet: GrantResource/GrantResourceGroup(command_id, badge resource)
Wallet->>Wallet: write resource_grants and user_resource_entitlements
Wallet->>Projection: UserResourceChanged outbox
Projection->>Projection: update user_badge_display_profiles
系统赠送必须保留 grant_source、操作人、原因、目标用户、有效期和资源快照,方便客服和审计。
长徽章刷新
长徽章不一定经过资源发放:
flowchart LR
LevelChanged["GrowthLevelChanged"] --> Projection["badge display projection"]
VipChanged["VipMembershipChanged"] --> Projection
IdentityChanged["Host/Agency/Verify Changed"] --> Projection
Projection --> Profile["profile strip badges"]
例如用户财富等级从 M15 升到 M16,只需要等级投影变化后刷新 strip_badges_json 中的文本和样式,不需要给用户发一个新的 M16 徽章资源。
App 接口
查询我的成就
GET /api/v1/achievements?collection_type=ordinary
GET /api/v1/achievements?collection_id=act_202605
每个成就返回:
| 字段 | 说明 |
|---|---|
achievement_id |
成就 ID |
title / description |
展示文案 |
status |
locked / in_progress / unlocked / expired |
progress_value / target_value |
当前进度和目标 |
unlocked_at_ms |
解锁时间 |
primary_badge |
主大徽章资源摘要 |
reward_status |
none / pending / granted / failed |
effective_to_ms |
活动成就结束时间 |
查询我的徽章
GET /api/v1/badges/me
返回:
{
"strip_badges": [],
"profile_tile_badges": [],
"honor_badges": [],
"server_time_ms": 1777996800000
}
strip_badges 是资料顶部长徽章;profile_tile_badges 是资料页精选大徽章;honor_badges 是 Honor 页全部大徽章。
设置资料页精选大徽章
POST /api/v1/badges/display
请求:
{
"slot": "profile_tile",
"items": [
{ "resource_id": 101, "entitlement_id": "ent_1", "position": 1 },
{ "resource_id": 102, "entitlement_id": "ent_2", "position": 2 }
],
"command_id": "cmd_badge_display_550e8400"
}
规则:
- 只能设置当前用户自己有效持有的大徽章。
profile_tile最多 6 个,具体数量由 App 设计配置决定。- 长徽章中的等级、VIP、认证类标识不允许用户伪造或任意排序;只允许隐藏非强制展示的资源型长徽章。
- 重复
command_id+ 相同 payload 返回同一结果;不同 payload 返回幂等冲突。
查询他人荣誉
GET /api/v1/users/{user_id}/honors
返回用户公开可见的大徽章、成就摘要和精选展示。隐私或风控隐藏的徽章不返回。
房间资料展示
现有接口可继续承载:
GET /api/v1/users/room-display-profiles:batch?user_ids=10001,10002
badges 字段建议返回已裁剪的展示数组,不返回用户全部徽章:
{
"badges": [
{ "badge_form": "strip", "source_type": "vip", "text": "VIP7" },
{ "badge_form": "strip", "source_type": "level", "text": "M16" },
{ "badge_form": "tile", "source_type": "achievement", "resource_id": 101 }
]
}
后台管理
后台建议放在活动管理和资源管理两个区域:
活动管理
- 成就配置
- 活动成就
资源管理
- 徽章资源
- 徽章赠送
- 徽章展示配置
成就配置
字段:
| 字段 | 说明 |
|---|---|
| 成就名称、描述 | App 展示 |
| 成就类型 | 普通 / 活动限时 |
| 条件类型 | 指定任务完成 / 指标累计 / 多条件 AND |
| 指标和目标值 | 例如 task_completed_count >= 10 |
| 活动窗口 | 活动成就必填 |
| 主徽章资源 | 一般选择大徽章 |
| 奖励资源组 | 可选,解锁后发放 |
| 排序和状态 | draft / active / paused / archived |
徽章资源配置
字段:
| 字段 | 说明 |
|---|---|
| 徽章形态 | 长徽章 strip / 大徽章 tile |
| 徽章来源提示 | achievement / system / level / vip / identity |
| 资源素材 | icon、preview、animation |
| 文本模板 | 长徽章可用,例如 M{level}、VIP{level} |
| 默认槽位 | profile_strip / profile_tile / honor_wall |
| 展示优先级 | 数值越高越靠前 |
| 稀有度 | common / rare / epic / legendary 等展示字段 |
| 有效期策略 | 永久、固定天数、跟随 VIP/身份有效期 |
系统赠送
字段:
| 字段 | 说明 |
|---|---|
| 目标用户 | 单个用户或批量导入 |
| 徽章或资源组 | 从资源库选择 |
| 有效期 | 永久或指定天数 |
| 赠送原因 | 必填,进入审计 |
| grant_source | admin / activity_campaign / identity_verify |
| command_id | 前端按提交动作生成,重试复用 |
展示建议
资料页顶部可以按这个结构展示:
- 头像、昵称、国家和基础身份。
- 长徽章行:VIP、等级、身份、机构等,最多 6 个。
- 精选大徽章行:用户置顶或系统自动挑选 3 到 6 个。
- 数据行:访客、关注、粉丝等。
- Tab:Honor / Relationship / Moment。
- Honor 页:按分类展示全部大徽章和成就详情。
展示策略:
- 长徽章越靠近昵称,越应该是强身份和实时状态。
- 大徽章越靠近 Honor,越应该是历史荣誉和活动纪念。
- 资料页首屏只展示精选,不展示全部,避免像资源背包一样拥挤。
- Honor 页要能区分“成就获得”和“系统赠送”,否则用户不知道徽章来源。
异常与补偿
- 重复事件:成就进度和解锁必须用派生事件 ID 幂等。
- 钱包发放失败:成就仍保持已解锁,奖励任务重试,后台可手动补发。
- 徽章过期:展示投影按有效期过滤,不删除历史授予记录。
- 系统赠送撤销:首版建议新增后台禁用/撤销权益命令,不直接删表。
- 成就配置修改:active 成就修改条件必须生成新版本;历史是否重算走后台 rebuild job。
- 展示投影延迟:房间资料允许短暂延迟,但不能返回客户端自报徽章。
- 多 App 隔离:所有成就、资源、展示槽位必须带
app_code。
落地顺序
- 在资源后台为 badge 增加
badge_form、badge_kind、默认槽位和优先级配置。 - 在
activity-service增加 achievement domain:定义、条件、进度、解锁、奖励任务。 - 扩展 task module 事件,完成任务后写出
TaskCompleted服务端事实,供成就消费。 - 用
wallet-service.GrantResourceGroup发放成就徽章和系统徽章。 - 新增
user_badge_display_slots,支持多个长徽章和多个大徽章展示。 - 新增 App 接口:成就列表、我的徽章、设置精选大徽章、他人荣誉。
- 接入
room-display-profiles:batch,返回裁剪后的长徽章和精选大徽章。 - 补后台:成就配置、徽章资源配置、系统赠送、授予记录查询。
验收点
- 用户完成指定任务后,成就只解锁一次,徽章奖励只发放一次。
- 系统直接赠送大徽章后,用户 Honor 页能看到该徽章,但成就列表不会多出伪造成就。
- 同一用户可以同时展示多个长徽章和多个大徽章。
- 等级/VIP 变化后,长徽章展示自动变化,不产生无意义的旧等级徽章堆积。
- 用户不能把未持有、已过期或形态不匹配的徽章设置到资料页。
- 资料页和房间资料只返回服务端投影,客户端不能自报徽章。
- 徽章资源、成就定义、系统赠送和展示设置都按
app_code隔离。