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