hyapp-server/server/admin/docs/靓号管理服务端技术文档.md
2026-06-11 01:02:20 +08:00

638 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 靓号管理服务端技术文档
## 当前结论
靓号管理不能只做成后台页面配置。当前仓库里 `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` 能看到“用户管理 > 靓号管理”。