# 靓号管理服务端技术文档 ## 当前结论 靓号管理不能只做成后台页面配置。当前仓库里 `user-service` 已经是 `user_id`、`display_user_id` 和临时靓号租约的 owner,`server/admin` 只能做后台 HTTP 入口、权限、审计和管理适配,App 入口继续由 `gateway-service` 暴露。 本需求新增的是“靓号池申请 + 后台发放靓号 + 资料返回靓号字段”,要在现有 `display_user_id` 体系上扩展,不重新引入一套用户 ID。 ## 已有事实 - `users.user_id` 是内部不可变用户主键。 - `users.current_display_user_id` 是当前对外展示短号。 - `users.current_display_user_id_kind` 当前支持 `default` / `pretty`。 - `users.current_display_user_id_expires_at_ms` 为 `0` 或空时表示当前展示号不自动过期。 - `user_display_user_ids` 已保存默认短号和靓号的实时归属。 - `pretty_display_user_id_leases` 已保存靓号覆盖关系。 - 当前默认短号和旧自助靓号校验是 5 到 11 位、不能 0 开头的纯数字。 - 本需求的靓号池批量生成默认仍可使用数字规则;后台发放靓号不能复用纯数字限制,需要支持阿拉伯语、英文和数字组合。 - 当前 App 端已有 `/api/v1/users/me/display-id/pretty/apply`,但参数是 `pretty_display_user_id + lease_duration_ms + payment_receipt_id`,不是靓号池申请要的 `pretty_id`。 ## 目标 - 后台在“用户管理”下新增二级菜单“靓号管理”。 - 后台可以配置靓号池的等级轨道、等级区间、生成规则和生成数量。 - 后台批量生成靓号时,不能和当前库里的 `users.user_id`、默认短号、当前短号、历史有效短号、已有靓号池号码冲突。 - 每个靓号池号码都有一个稳定 `pretty_id`,App 靓号池申请时只传 `pretty_id`。 - 靓号池申请默认长期持有;用户已有靓号时,新靓号直接覆盖旧靓号,旧靓号释放。 - 后台发放靓号可以直接输入靓号内容,可以填写有效期;不填有效期时默认长期持有。 - 后台发放靓号内容不限定纯数字,需要支持阿拉伯语、英文和数字组合。 - App 用户申请靓号池号码时,服务端根据靓号池配置校验用户是否在对应等级区间内,在区间内才允许占用。 - 用户拥有靓号后,个人信息、个人资料卡、房间个人资料卡、房间详情等资料返回都带当前靓号字段。 ## 非目标 - 不把靓号作为内部主键使用,所有业务关系仍然使用 `user_id`。 - 不让 `room-service` 保存靓号状态。房间只保存房间状态和用户 `user_id`,展示资料由 gateway 汇总用户资料。 - 不让 `server/admin` 直接 import app 后端 `services/*` 或 `pkg/*`。 - 首版不做等级下降后的自动回收。用户申请时校验等级区间;后续如果需要降级回收,再新增独立回收规则和定时任务。 - 后台发放不需要等级校验,管理员只受后台权限和审计约束。 - 首版不对外推送 IM。靓号申请成功后,客户端以接口返回和下次资料刷新为准。 ## 服务边界 | 模块 | 责任 | | --- | --- | | `user-service` | 靓号池、靓号项、后台发放、替换释放事务、短号唯一性、当前展示号覆盖、资料字段 | | `activity-service` | 提供用户等级展示投影,至少能按 `user_id` 返回 `wealth/game/charm` 当前等级 | | `gateway-service` | App HTTP 入口,按 token 用户申请靓号,返回统一 envelope | | `server/admin` | 后台 HTTP API、菜单、权限、审计,调用 `hyapp.local/api` protobuf client | | `hyapp-admin-platform` | 用户管理下新增“靓号管理”页面,消费后台 API | ## 等级区间规则 当前等级体系有三条轨道: | track | 含义 | | --- | --- | | `wealth` | 财富等级 | | `game` | 游戏等级 | | `charm` | 魅力等级 | 靓号池配置必须保存 `level_track`,不能在服务端猜默认轨道。后台页面可以默认选中 `wealth`,但请求和数据库必须明确保存轨道。 靓号池申请时: 1. 根据 `pretty_id` 锁定靓号项和所属靓号池。 2. 读取用户当前等级,使用池子的 `level_track`。 3. 判断 `min_level <= current_level <= max_level`。 4. 通过后进入靓号占用事务。 ## 数据库设计 新增表归属 `hyapp_user`,因为靓号和短号唯一性属于 `user-service`。 ### 现有短号表扩展 后台发放的靓号不一定是数字,现有短号相关字段需要从数字短号能力扩展成“展示号字符串”能力: ```sql ALTER TABLE users MODIFY COLUMN current_display_user_id VARCHAR(64) NOT NULL, MODIFY COLUMN default_display_user_id VARCHAR(64) NOT NULL; ALTER TABLE user_display_user_ids MODIFY COLUMN display_user_id VARCHAR(64) NOT NULL, MODIFY COLUMN live_display_user_id VARCHAR(64) GENERATED ALWAYS AS ( CASE WHEN status IN ('active', 'held', 'reserved', 'blocked') THEN display_user_id ELSE NULL END ) STORED; ALTER TABLE pretty_display_user_id_leases MODIFY COLUMN display_user_id VARCHAR(64) NOT NULL, MODIFY COLUMN active_display_user_id VARCHAR(64) GENERATED ALWAYS AS ( CASE WHEN status = 'active' THEN display_user_id ELSE NULL END ) STORED, ADD COLUMN released_at_ms BIGINT NOT NULL DEFAULT 0 AFTER expires_at_ms, ADD COLUMN release_reason VARCHAR(64) NOT NULL DEFAULT '' AFTER released_at_ms; ALTER TABLE display_user_id_change_logs MODIFY COLUMN old_display_user_id VARCHAR(64) NOT NULL, MODIFY COLUMN new_display_user_id VARCHAR(64) NOT NULL; ``` 规则: - 默认短号仍然按现有数字规则生成。 - 靓号池批量生成默认仍然按数字规则生成。 - 后台发放靓号允许 1 到 64 个字符。 - 后台发放靓号允许 Unicode 字母、Unicode 数字和组合标记,用于支持阿拉伯语、英文和数字。 - 后台发放靓号不允许空白字符、控制字符、emoji 和标点符号。 - 唯一性仍按 `app_code + display_user_id` 判断;如果产品要求大小写敏感,相关唯一列需要改成 `utf8mb4_bin` 或在写入前做明确大小写归一。 ### `pretty_display_id_pools` 保存后台配置的等级区间和默认生成规则。 ```sql CREATE TABLE pretty_display_id_pools ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', pool_id VARCHAR(64) NOT NULL PRIMARY KEY, name VARCHAR(128) NOT NULL, level_track VARCHAR(32) NOT NULL, min_level INT NOT NULL, max_level INT NOT NULL, rule_type VARCHAR(32) NOT NULL, rule_config_json JSON NULL, status VARCHAR(32) NOT NULL, sort_order INT NOT NULL DEFAULT 0, created_by_admin_id BIGINT NOT NULL DEFAULT 0, updated_by_admin_id BIGINT NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, UNIQUE KEY uk_pretty_pool_range_rule (app_code, level_track, min_level, max_level, name), KEY idx_pretty_pool_status (app_code, status, sort_order) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池配置表'; ``` 字段说明: | 字段 | 说明 | | --- | --- | | `pool_id` | 靓号池 ID | | `level_track` | `wealth` / `game` / `charm` | | `min_level` | 可申请最小等级,包含 | | `max_level` | 可申请最大等级,包含 | | `rule_type` | 生成规则,例如 `aabbcc` | | `rule_config_json` | 长度、号段、排除数字等扩展配置 | | `status` | `active` / `disabled` | 约束: - `min_level` 不能大于 `max_level`。 - `level_track` 必须是已有等级轨道。 - `active` 池才允许 App 用户申请。 ### `pretty_display_ids` 保存每一个可申请靓号。每个靓号都有自己的 `pretty_id`。 ```sql CREATE TABLE pretty_display_ids ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', pretty_id VARCHAR(64) NOT NULL PRIMARY KEY, pool_id VARCHAR(64) NOT NULL DEFAULT '', source VARCHAR(32) NOT NULL DEFAULT 'pool', display_user_id VARCHAR(64) NOT NULL, status VARCHAR(32) NOT NULL, assigned_user_id BIGINT NOT NULL DEFAULT 0, assigned_lease_id VARCHAR(64) NOT NULL DEFAULT '', assigned_at_ms BIGINT NOT NULL DEFAULT 0, released_at_ms BIGINT NOT NULL DEFAULT 0, release_reason VARCHAR(64) NOT NULL DEFAULT '', generated_batch_id VARCHAR(64) NOT NULL DEFAULT '', created_by_admin_id BIGINT NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, live_display_user_id VARCHAR(64) GENERATED ALWAYS AS ( CASE WHEN status IN ('available', 'assigned', 'disabled', 'reserved') THEN display_user_id ELSE NULL END ) STORED, UNIQUE KEY uk_pretty_display_ids_live (app_code, live_display_user_id), KEY idx_pretty_display_ids_pool_status (app_code, pool_id, status), KEY idx_pretty_display_ids_assigned_user (app_code, assigned_user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池号码表'; ``` 状态说明: | status | 说明 | | --- | --- | | `available` | 可申请 | | `assigned` | 已被用户占用 | | `disabled` | 后台下架,不允许申请 | | `reserved` | 系统预留,不允许普通用户申请 | | `released` | 曾经发放或占用,已释放,不参与可申请列表 | | `deleted` | 软删除,只保留审计 | `source` 说明: | source | 说明 | | --- | --- | | `pool` | 靓号池生成,释放后可以回到 `available` | | `admin_grant` | 后台直接发放,释放后默认进入 `released` | ### `pretty_display_id_generation_batches` 保存批量生成记录,方便后台展示和排查。 ```sql CREATE TABLE pretty_display_id_generation_batches ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', batch_id VARCHAR(64) NOT NULL PRIMARY KEY, pool_id VARCHAR(64) NOT NULL, rule_type VARCHAR(32) NOT NULL, rule_config_json JSON NULL, requested_count INT NOT NULL, generated_count INT NOT NULL, skipped_conflict_count INT NOT NULL, status VARCHAR(32) NOT NULL, operator_admin_id BIGINT NOT NULL, request_id VARCHAR(64) NOT NULL DEFAULT '', created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, KEY idx_pretty_batch_pool (app_code, pool_id, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号批量生成记录表'; ``` ## 靓号池生成规则 靓号池批量生成支持数字规则、日期规则、寓意号和自定义规则。数字规则允许前导 0,号码以字符串保存。 | rule_type | 说明 | | --- | --- | | `aa`、`aaa`、`aaaa`、`aaaaa`、`aaaaaa`、`aaaaaaa` | 2A 到 7A 连号 | | `aabb` | 两组对子 | | `aabbcc` | 三组重复数字 | | `aabbccdd` | 四组对子 | | `abab`、`ababab` | 交替号 | | `abba`、`abcba`、`abccba` | 回文号 | | `aaab`、`aaaab`、`aaaaab` | 拖一号 | | `aaabbb`、`aaaabbbb` | 双组号 | | `abc`、`abcd`、`abcde`、`abcdef` | 顺子号 | | `cba`、`dcba`、`edcba`、`fedcba` | 倒顺号 | | `abcabc` | 顺子重复号 | | `date_yyyymmdd`、`date_yymmdd` | 日期号 | | `love` | 520、521、1314 等寓意号 | | `custom_values` | 使用 `rule_config_json.values` 固定候选 | | `custom_pattern` | 使用 `rule_config_json.pattern` 自定义模式 | `rule_config_json` 可选字段: | 字段 | 说明 | | --- | --- | | `digits` | 限制可用数字,例如 `["6","8","9"]` | | `exclude_digits` | 排除数字,例如 `["4"]` | | `prefix`、`suffix` | 给生成候选加前缀或后缀 | | `values` | `custom_values` 的候选数组 | | `pattern` | `custom_pattern` 的模式,例如 `AAAABBBB` | | `year_from`、`year_to` | 日期号年份范围 | | `start_date`、`end_date` | 日期号精确范围,格式 `yyyy-mm-dd` | 后台直接发放靓号不受本节数字生成规则限制,按“现有短号表扩展”里的后台发放字符规则校验。 ## 批量生成事务 批量生成时由 `user-service` 完成冲突检查和入库。 冲突范围: - `CAST(users.user_id AS CHAR)`。 - `users.default_display_user_id`。 - `users.current_display_user_id`。 - `user_display_user_ids` 中 `status IN ('active','held','reserved','blocked')` 的号码。 - `pretty_display_ids` 中 `status IN ('available','assigned','disabled','reserved')` 的号码。 流程: 1. 后台提交 `pool_id + rule_type + rule_config + count`。 2. `user-service` 生成候选号码。 3. 每个候选号码先做数字短号格式校验。 4. 批量查询冲突号码,过滤已冲突项。 5. 插入 `pretty_display_ids`,唯一键兜底处理并发冲突。 6. 写 `pretty_display_id_generation_batches`。 7. 返回本次生成数量和跳过冲突数量。 为了避免生成器死循环,服务端需要设置最大尝试次数,例如 `count * 20`,仍不足时返回实际生成数量和 `skipped_conflict_count`。 ## 通用替换释放事务 靓号池申请和后台发放都复用同一个“替换当前靓号”事务。 如果用户没有 active pretty 展示号: 1. 默认短号从 `active` 改为 `held`。 2. 新靓号写入 `pretty_display_user_id_leases`。 3. 新靓号写入 `user_display_user_ids`,`display_user_id_kind='pretty'`。 4. 更新 `users.current_display_user_id`、`current_display_user_id_kind`、`current_display_user_id_expires_at_ms`。 5. 写 `display_user_id_change_logs`。 如果用户已有 active pretty 展示号: 1. 锁定当前用户行和旧 active lease。 2. 旧 `pretty_display_user_id_leases.status` 改为 `cancelled`,`release_reason='replaced_by_new_pretty'`。 3. 旧 `user_display_user_ids.status` 改为 `released`,记录 `released_at_ms`。 4. 如果旧靓号来自靓号池,旧 `pretty_display_ids.status` 改回 `available`,清空 `assigned_user_id`、`assigned_lease_id`、`assigned_at_ms`。 5. 如果旧靓号来自后台发放,旧 `pretty_display_ids.status` 改为 `released`,保留历史。 6. 写旧靓号释放日志。 7. 再按新靓号写入当前 active 展示号。 这个规则意味着:用户申请或被后台发放新靓号时,不返回“已有靓号”冲突,而是直接覆盖旧靓号。 ## 靓号池申请事务 App 靓号池申请只接受 `pretty_id`,不接受客户端自报号码、等级、用户 ID 或租期。 事务流程: 1. 从 access token 读取当前 `user_id`。 2. `SELECT ... FOR UPDATE` 锁定 `pretty_display_ids`。 3. 校验靓号项为 `available` 且 `source='pool'`。 4. 锁定所属 `pretty_display_id_pools`,校验为 `active`。 5. 调用 `activity-service` 读取当前用户在 `level_track` 下的等级。 6. 校验用户等级在 `[min_level, max_level]`。 7. 进入通用替换释放事务。 8. 写新 `pretty_display_user_id_leases`,`source='level_pool'`,`expires_at_ms=0`。 9. 更新新 `pretty_display_ids.status='assigned'`,写入 `assigned_user_id` 和 `assigned_lease_id`。 10. 返回当前身份投影。 靓号池申请默认长期持有: - `pretty_display_user_id_leases.expires_at_ms = 0`。 - `users.current_display_user_id_expires_at_ms = 0`。 - 不由定时任务自动过期。 ## 后台发放事务 后台发放不依赖靓号池,也不校验用户等级。 输入: - `target_user_id`:目标 App 用户。 - `display_user_id`:管理员输入的靓号内容。 - `duration_ms`:可选;大于 0 表示限时靓号,不传或传 0 表示长期。 - `reason`:发放原因,写入审计和 change log。 事务流程: 1. 校验管理员有 `pretty-id:grant` 权限。 2. 校验 `display_user_id` 符合后台发放字符规则。 3. 检查 `display_user_id` 不与当前有效 `users.user_id` 字符串、默认短号、当前短号、历史有效短号、已有靓号冲突。 4. 锁定目标用户行。 5. 进入通用替换释放事务。 6. 生成新的 `pretty_id` 和 `lease_id`。 7. 写 `pretty_display_ids`,`source='admin_grant'`,`status='assigned'`。 8. 写 `pretty_display_user_id_leases`,`source='admin_grant'`。 9. 如果 `duration_ms > 0`,`expires_at_ms = now_ms + duration_ms`;否则 `expires_at_ms = 0`。 10. 更新 `users.current_display_user_id_expires_at_ms` 为同一个过期时间。 11. 返回当前身份投影。 限时后台靓号: - `expires_at_ms > 0` 时继续复用现有靓号过期恢复逻辑。 - 到期后恢复默认短号,不回到靓号池。 - 如果到期前又发放或申请了新靓号,旧限时靓号按通用替换释放事务取消。 ## Proto 变更 在 `api/proto/user/v1/user.proto` 中扩展或新增 identity 管理 RPC。 新增 App 申请 RPC: ```proto message ApplyPrettyDisplayIDFromPoolRequest { RequestMeta meta = 1; int64 user_id = 2; string pretty_id = 3; } message ApplyPrettyDisplayIDFromPoolResponse { UserIdentity identity = 1; string pretty_id = 2; string lease_id = 3; } ``` 新增后台管理 RPC: ```proto message AdminGrantPrettyDisplayIDRequest { RequestMeta meta = 1; int64 target_user_id = 2; string display_user_id = 3; int64 duration_ms = 4; string reason = 5; int64 operator_admin_id = 6; } message AdminGrantPrettyDisplayIDResponse { UserIdentity identity = 1; string pretty_id = 2; string lease_id = 3; } service UserPrettyDisplayIDAdminService { rpc ListPrettyDisplayIDPools(ListPrettyDisplayIDPoolsRequest) returns (ListPrettyDisplayIDPoolsResponse); rpc CreatePrettyDisplayIDPool(CreatePrettyDisplayIDPoolRequest) returns (PrettyDisplayIDPoolResponse); rpc UpdatePrettyDisplayIDPool(UpdatePrettyDisplayIDPoolRequest) returns (PrettyDisplayIDPoolResponse); rpc GeneratePrettyDisplayIDs(GeneratePrettyDisplayIDsRequest) returns (GeneratePrettyDisplayIDsResponse); rpc ListPrettyDisplayIDs(ListPrettyDisplayIDsRequest) returns (ListPrettyDisplayIDsResponse); rpc SetPrettyDisplayIDStatus(SetPrettyDisplayIDStatusRequest) returns (PrettyDisplayIDResponse); rpc AdminGrantPrettyDisplayID(AdminGrantPrettyDisplayIDRequest) returns (AdminGrantPrettyDisplayIDResponse); } ``` 资料投影建议增加字段: ```proto message User { // existing fields... string pretty_id = 24; string pretty_display_user_id = 25; } message UserIdentity { // existing fields... string pretty_id = 8; string pretty_display_user_id = 9; } ``` 字段规则: | 字段 | 说明 | | --- | --- | | `display_user_id` | 当前对外展示号;有靓号时等于靓号 | | `display_user_id_kind` | `default` / `pretty` | | `display_user_id_expires_at_ms` | 长期持有为 `0` | | `pretty_id` | 当前占用的靓号池记录 ID,没有则为空 | | `pretty_display_user_id` | 当前占用的靓号号码,没有则为空 | ## 后台 HTTP 接口 后台接口归属 `server/admin/internal/modules/prettyid`,路径放在用户管理域。 ### 靓号池列表 | 项 | 内容 | | --- | --- | | 地址 | `GET /api/v1/admin/users/pretty-id-pools` | | 参数 | `status`、`level_track`、`page`、`page_size` | | 返回值 | `items[]`、`total`,每项包含 `pool_id`、`name`、`level_track`、`min_level`、`max_level`、`rule_type`、`status` | | 相关 IM | 无 | ### 创建靓号池 | 项 | 内容 | | --- | --- | | 地址 | `POST /api/v1/admin/users/pretty-id-pools` | | 参数 | `name`、`level_track`、`min_level`、`max_level`、`rule_type`、`rule_config_json`、`status` | | 返回值 | 靓号池详情 | | 相关 IM | 无 | ### 更新靓号池 | 项 | 内容 | | --- | --- | | 地址 | `PUT /api/v1/admin/users/pretty-id-pools/{pool_id}` | | 参数 | `name`、`level_track`、`min_level`、`max_level`、`rule_type`、`rule_config_json`、`status` | | 返回值 | 靓号池详情 | | 相关 IM | 无 | ### 批量生成靓号 | 项 | 内容 | | --- | --- | | 地址 | `POST /api/v1/admin/users/pretty-id-pools/{pool_id}/generate` | | 参数 | `rule_type`、`rule_config_json`、`count` | | 返回值 | `batch_id`、`requested_count`、`generated_count`、`skipped_conflict_count` | | 相关 IM | 无 | ### 靓号列表 | 项 | 内容 | | --- | --- | | 地址 | `GET /api/v1/admin/users/pretty-ids` | | 参数 | `pool_id`、`source`、`status`、`keyword`、`assigned_user_id`、`page`、`page_size` | | 返回值 | `items[]`、`total`,每项包含 `pretty_id`、`source`、`display_user_id`、`pool`、`status`、`assigned_user_id`、`assigned_lease_id` | | 相关 IM | 无 | ### 后台发放靓号 | 项 | 内容 | | --- | --- | | 地址 | `POST /api/v1/admin/users/pretty-ids/grant` | | 参数 | `target_user_id`、`display_user_id`、`duration_ms`、`reason`;`duration_ms` 不传或为 `0` 表示长期 | | 返回值 | `user_id`、`display_user_id`、`display_user_id_kind`、`display_user_id_expires_at_ms`、`pretty_id`、`pretty_display_user_id`、`lease_id` | | 相关 IM | 无 | ### 靓号状态修改 | 项 | 内容 | | --- | --- | | 地址 | `POST /api/v1/admin/users/pretty-ids/{pretty_id}/status` | | 参数 | `status`,只允许未占用的靓号在 `available`、`disabled`、`reserved` 之间切换 | | 返回值 | 靓号详情 | | 相关 IM | 无 | ## App HTTP 接口 ### 可申请靓号列表 | 项 | 内容 | | --- | --- | | 地址 | `GET /api/v1/users/pretty-ids/available` | | 参数 | `page`、`page_size` | | 返回值 | `items[]`、`total`,每项包含 `pretty_id`、`display_user_id`、`level_track`、`min_level`、`max_level` | | 相关 IM | 无 | 返回只包含当前用户等级满足条件的 `available` 靓号。 ### 靓号池申请 | 项 | 内容 | | --- | --- | | 地址 | `POST /api/v1/users/me/display-id/pretty/apply` | | 参数 | `pretty_id` | | 返回值 | `user_id`、`display_user_id`、`display_user_id_kind`、`display_user_id_expires_at_ms`、`pretty_id`、`pretty_display_user_id`、`lease_id` | | 相关 IM | 无 | 说明: - 保留当前地址,避免前端路由重复;请求体改为优先读取 `pretty_id`。 - 靓号池申请默认长期持有,`display_user_id_expires_at_ms=0`。 - 用户已有靓号时,新靓号直接覆盖旧靓号,旧靓号释放。 - 如果要兼容旧付费参数,服务端可以临时保留 `pretty_display_user_id + lease_duration_ms + payment_receipt_id` 分支,但新 App 只使用 `pretty_id`。 ## 资料返回改造 所有资料返回统一使用同一套字段。 | 场景 | 当前链路 | 改造点 | | --- | --- | --- | | 个人信息 | `GET /api/v1/users/me` -> `user-service.GetUser` | `User` proto 增加 `pretty_id`、`pretty_display_user_id` | | 个人资料卡 | 用户资料批量接口或资料详情 | 复用 `User` 投影字段 | | 房间个人资料卡 | `GET /api/v1/users/room-display-profiles:batch` | `roomDisplayProfileData` 增加靓号字段 | | 房间详情 | `room detail` 中的 `profiles[]` | 来自同一批 `roomDisplayProfileData` | | 登录 / 刷新 token | `AuthToken` 已有 display 字段 | 如客户端需要启动后立即知道靓号,补 `pretty_id`、`pretty_display_user_id` | 响应规则: - 用户没有靓号:`display_user_id` 返回默认短号,`display_user_id_kind='default'`,`pretty_id=''`,`pretty_display_user_id=''`。 - 用户有靓号:`display_user_id` 返回靓号,`display_user_id_kind='pretty'`,`pretty_id` 返回池记录 ID,`pretty_display_user_id` 返回靓号号码。 - 长期持有靓号:`display_user_id_expires_at_ms=0`。 - 后台限时发放靓号:`display_user_id_expires_at_ms` 返回实际过期时间。 ## 后台菜单和权限 新增权限: | 权限码 | kind | 说明 | | --- | --- | --- | | `pretty-id:view` | `menu` | 查看靓号管理 | | `pretty-id:update` | `button` | 新增、编辑、上下架靓号池和靓号 | | `pretty-id:generate` | `button` | 批量生成靓号 | | `pretty-id:grant` | `button` | 后台直接发放靓号 | 新增菜单: | 字段 | 值 | | --- | --- | | parent | `app-users` | | title | `靓号管理` | | code | `app-user-pretty-ids` | | path | `/app/users/pretty-ids` | | icon | `badge` | | permission_code | `pretty-id:view` | | sort | `65` | 需要同步: - `server/admin/migrations/044_pretty_id_management.sql`。 - `server/admin/internal/repository/seed.go` 默认权限和菜单。 - `platform-admin` 默认绑定全部新增权限。 - 只读角色只绑定 `pretty-id:view`。 ## 代码落点 | 位置 | 改动 | | --- | --- | | `api/proto/user/v1/user.proto` | 增加靓号池 RPC、后台发放 RPC、资料字段 | | `services/user-service/internal/domain/user` | 增加 pool、pretty item、申请命令、后台发放命令和非数字靓号校验 | | `services/user-service/internal/storage/mysql/identity` | 增加靓号池、生成、申请、后台发放、替换释放事务 | | `services/user-service/deploy/mysql/initdb/001_user_service.sql` | 增加新表并扩展短号字段长度 | | `services/user-service/migrations` | 增加线上迁移 | | `services/gateway-service/internal/transport/http/userapi` | 改造靓号池申请接口、可申请列表、资料字段 | | `services/gateway-service/internal/transport/http/roomapi` | 房间资料结构补靓号字段 | | `server/admin/internal/integration/userclient` | 增加后台靓号管理 client | | `server/admin/internal/modules/prettyid` | 新增后台模块 | | `server/admin/internal/router/router.go` | 注册后台模块 | | `server/admin/migrations` | 菜单和权限迁移 | ## 错误码建议 | code | 场景 | | --- | --- | | `PRETTY_ID_NOT_FOUND` | `pretty_id` 不存在 | | `PRETTY_ID_NOT_AVAILABLE` | 靓号不可申请或已被占用 | | `PRETTY_ID_LEVEL_NOT_MATCH` | 用户等级不在池区间内 | | `PRETTY_DISPLAY_ID_INVALID` | 后台发放的靓号内容不符合字符规则 | | `DISPLAY_USER_ID_CONFLICT` | 生成或申请时发现短号冲突 | ## 验证项 必须验证: - `make proto` 成功,生成 `.pb.go` 和 `_grpc.pb.go`。 - `go test ./...` 在 `hyapp-server` 根目录通过。 - `go test ./...` 在 `hyapp-server/server/admin` 通过。 - 批量生成 1000 个 `aabbcc` 靓号时,不插入与 `users.user_id`、默认短号、当前短号、已有靓号冲突的号码。 - 用户等级低于池区间时,申请返回 `PRETTY_ID_LEVEL_NOT_MATCH`。 - 用户等级在池区间内时,申请成功并更新: - `pretty_display_ids.status='assigned'` - `pretty_display_user_id_leases.source='pool'` - `user_display_user_ids.display_user_id_kind='pretty'` - `users.current_display_user_id_kind='pretty'` - 用户已有靓号时,再申请另一个靓号会覆盖旧靓号,旧靓号释放;旧池靓号回到 `available`,旧后台发放靓号进入 `released`。 - 后台发放 `VIP2026`、`ملك123` 这类英文数字或阿拉伯语数字组合可以成功。 - 后台发放不传 `duration_ms` 时,返回 `display_user_id_expires_at_ms=0`。 - 后台发放传 `duration_ms` 时,返回实际 `display_user_id_expires_at_ms`,到期后恢复默认短号。 - `GET /api/v1/users/me` 返回靓号字段。 - `GET /api/v1/users/room-display-profiles:batch` 返回靓号字段。 - 房间详情 `profiles[]` 返回靓号字段。 - 后台 `/api/v1/navigation/menus` 能看到“用户管理 > 靓号管理”。