27 KiB
User ID Technical Design
本文档定义数值型用户主键、默认短号、临时靓号的职责边界、数据库模型、跨服务通信规则和修改流程。核心原则:user_id 是唯一用户主键,后端和客户端响应都使用它;display_user_id 只做展示、搜索、登录名和运营入口。
Terminology
| Field | Meaning | Mutable | Unique | Used By Internal Services |
|---|---|---|---|---|
user_id |
数值型用户 ID,服务间主关联和客户端响应统一使用 | No | Yes | Yes |
default_display_user_id |
用户本身的默认短号 | Yes by explicit change | Yes while owned | No |
current_display_user_id |
当前对外展示、搜索、登录的短号 | Yes | Yes while effective | No |
pretty_display_user_id |
用户申请的临时靓号 | Yes by lease | Yes while leased | No |
display_user_id |
API 中返回的当前展示号,等同 current_display_user_id |
Yes | Yes while effective | No |
display_user_id_kind |
当前展示号来源,default 或 pretty |
Yes | No | No |
display_user_id_expires_at_ms |
当前靓号过期时间;默认短号为空 | Yes | No | No |
示例:
user_id: 918274650129384448
default_display_user_id: 163000
current_display_user_id: 888888
display_user_id_kind: pretty
display_user_id_expires_at_ms: 1777000000000
user_id 是内部关系主键,也可以作为接口响应中的用户 ID。default_display_user_id 是用户本身的号。临时靓号有效期内会覆盖当前展示号;过期后必须恢复到 default_display_user_id。
所有展示号唯一性都限定在 app_code 内。不同 App 可以使用同一个 display_user_id,但同一个 App 内只能有一个当前有效归属。
Design Principles
- HTTP、客户端 JSON 和 JWT claims 中的
user_id一律返回数值型用户 ID 的字符串形式。 - 跨服务通信一律传数值型
user_id。 - 数据库主关联一律存数值型
user_id,并用app_code做租户作用域。 - outbox、审计、账单、房间状态、IM session、活动记录一律存数值型
user_id。 display_user_id只允许出现在展示、搜索、分享、登录名、客服查询和运营配置入口。- 任何通过
display_user_id发起的请求,都必须先在user-service解析成内部user_id,后续链路只传内部user_id。 display_user_id可修改,所以不能作为幂等键、外键、账务主体或事件主体。- 临时靓号不能修改
user_id,也不能删除或转移用户的默认短号。 - 当前展示号的真实来源必须可解释:默认短号来自永久归属,临时靓号来自有效租约。
ResolveDisplayUserID、密码登录和用户资料查询都必须先处理过期靓号,再返回当前有效短号。- 靓号过期恢复必须事务化,不能只靠缓存 TTL。
Ownership
user-service 是 user_id 和 display_user_id 的唯一 owner。
| Capability | Owner | Rule |
|---|---|---|
生成 user_id |
user-service |
注册或三方首次登录时生成 |
| 分配默认短号 | user-service |
新用户创建时按 App 序列分配,首号 163000 |
| 修改默认短号 | user-service |
事务内检查唯一性和冷却期 |
| 申请临时靓号 | user-service |
创建靓号租约并覆盖当前展示号 |
| 过期临时靓号 | user-service |
恢复当前展示号为默认短号 |
解析 display_user_id |
user-service |
只返回当前有效归属 |
| 房间状态 | room-service |
只保存 user_id |
| 钱包账务 | wallet-service |
只保存 user_id |
| IM 连接 | 腾讯云 IM | identifier 使用内部长 user_id 的十进制字符串 |
| 活动结算 | activity-service |
只保存 user_id |
其他服务不能自己缓存长期 display_user_id -> user_id 绑定作为事实来源。短期缓存必须有 TTL,并允许 user-service 失效或回源。
Role Identity Facts
用户 ID 和短号不表达业务身份。主播、Agency、BD、BD Leader、coin_seller 都是挂在同一个 user_id 下的业务事实,不应该把身份塞进 display_user_id 或 JWT 文案里。
| Identity | Table | Can Coexist | Account Boundary |
|---|---|---|---|
host |
host_profiles |
可和 BD、BD Leader、coin_seller 并存 | 主播收益余额在 wallet-service |
agency owner |
agencies + owner membership |
可和 BD、BD Leader、coin_seller 并存 | Agency 工资结算后入钱包 |
bd |
bd_profiles(role='bd') |
可和 host、Agency、coin_seller 并存 | BD 工资结算后入钱包 |
bd_leader |
bd_profiles(role='bd_leader') |
可和 host、Agency、coin_seller 并存 | BD Leader 工资结算后入钱包 |
coin_seller |
coin_seller_profiles |
可和 Agency、BD、BD Leader 并存 | 币商专用资产 COIN_SELLER_COIN |
coin_seller 是币商身份,拥有独立于玩家普通 COIN 的币商金币账户。币商给玩家转金币时,内部链路只能使用数值型 user_id:gateway 校验当前用户是 active coin_seller,wallet-service 扣减该用户 COIN_SELLER_COIN 并给目标用户增加普通 COIN。display_user_id 只可用于后台或客户端搜索目标用户,解析后必须转换成内部 user_id 再进入账务。
Database Model
users
用户主表保存不可变主键、默认短号、当前展示号快照,以及注册资料和来源快照。
CREATE TABLE users (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu',
user_id BIGINT NOT NULL PRIMARY KEY,
default_display_user_id VARCHAR(32) NOT NULL,
current_display_user_id VARCHAR(32) NOT NULL,
current_display_user_id_kind VARCHAR(32) NOT NULL,
current_display_user_id_expires_at_ms BIGINT NULL,
username VARCHAR(64) NULL,
gender VARCHAR(32) NULL,
country VARCHAR(64) NULL,
invite_code VARCHAR(64) NULL,
register_ip VARCHAR(64) NULL,
register_user_agent VARCHAR(512) NULL,
country_by_ip VARCHAR(64) NULL,
register_device_id VARCHAR(128) NULL,
register_device VARCHAR(128) NULL,
os_version VARCHAR(64) NULL,
avatar VARCHAR(512) NULL,
birth_date DATE NULL,
app_version VARCHAR(64) NULL,
build_number VARCHAR(64) NULL,
source VARCHAR(64) NULL,
install_channel VARCHAR(64) NULL,
campaign VARCHAR(128) NULL,
platform VARCHAR(16) NULL,
language VARCHAR(32) NULL,
timezone VARCHAR(64) NULL,
status VARCHAR(32) NOT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
UNIQUE KEY uk_users_default_display_user_id (app_code, default_display_user_id),
UNIQUE KEY uk_users_current_display_user_id (app_code, current_display_user_id)
);
current_display_user_id 是读优化快照,不是变更历史来源。它必须和 user_display_user_ids 的当前有效记录保持一致。
快照规则:
| Case | current_display_user_id |
current_display_user_id_kind |
current_display_user_id_expires_at_ms |
|---|---|---|---|
| 没有靓号 | default_display_user_id |
default |
NULL |
| 靓号有效 | 靓号 | pretty |
靓号过期时间 |
| 靓号过期后 | default_display_user_id |
default |
NULL |
user_display_user_ids
短号归属历史表。默认短号和临时靓号都进入这张表。
CREATE TABLE user_display_user_ids (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu',
id BIGINT NOT NULL PRIMARY KEY,
display_user_id VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
display_user_id_kind VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL,
assigned_at_ms BIGINT NOT NULL,
activated_at_ms BIGINT NULL,
expires_at_ms BIGINT NULL,
released_at_ms BIGINT NULL,
release_reason VARCHAR(64) NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
live_display_user_id VARCHAR(32)
GENERATED ALWAYS AS (
CASE
WHEN status IN ('active', 'held', 'reserved', 'blocked') THEN display_user_id
ELSE NULL
END
) STORED,
active_user_id BIGINT
GENERATED ALWAYS AS (
CASE WHEN status = 'active' THEN user_id ELSE NULL END
) STORED,
UNIQUE KEY uk_user_display_user_ids_live (app_code, live_display_user_id),
UNIQUE KEY uk_user_display_user_ids_active_user (app_code, active_user_id),
KEY idx_user_display_user_ids_user_id (app_code, user_id)
);
status 建议:
active
held
expired
released
reserved
blocked
状态含义:
| Status | Meaning |
|---|---|
active |
当前可被普通业务解析的展示号 |
held |
默认短号仍归用户所有,但被临时靓号覆盖,普通业务不解析 |
expired |
临时靓号到期 |
released |
曾经归属用户,已经释放 |
reserved |
系统预留,不允许普通用户申请 |
blocked |
运营阻断,不允许分配 |
生成列用于模拟部分唯一索引:同一个短号不能同时处于 active、held、reserved 或 blocked;同一个用户只能有一个 active 当前展示号。历史 expired、released 记录可以保留多条。
pretty_display_user_id_leases
靓号租约表保存临时覆盖关系。它是“过期恢复默认短号”的事实来源。
CREATE TABLE pretty_display_user_id_leases (
lease_id VARCHAR(64) NOT NULL PRIMARY KEY,
display_user_id VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
status VARCHAR(32) NOT NULL,
starts_at_ms BIGINT NOT NULL,
expires_at_ms BIGINT NOT NULL,
payment_receipt_id VARCHAR(64) NULL,
source VARCHAR(32) NOT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
active_display_user_id VARCHAR(32)
GENERATED ALWAYS AS (
CASE WHEN status = 'active' THEN display_user_id ELSE NULL END
) STORED,
active_user_id BIGINT
GENERATED ALWAYS AS (
CASE WHEN status = 'active' THEN user_id ELSE NULL END
) STORED,
UNIQUE KEY uk_pretty_display_user_id_active (active_display_user_id),
UNIQUE KEY uk_pretty_display_user_id_active_user (active_user_id),
KEY idx_pretty_display_user_id_leases_user (user_id, status),
KEY idx_pretty_display_user_id_leases_expire (status, expires_at_ms)
);
status 建议:
active
expired
cancelled
refunded
首版规则:
- 一个用户同一时间只能有一个 active 靓号租约。
- 一个靓号同一时间只能租给一个用户。
- 申请新靓号不会改变
default_display_user_id。 - 靓号有效期内,默认短号处于
held,不能被其他用户申请。 - 靓号过期后,必须把
users.current_display_user_id恢复为users.default_display_user_id。
display_user_id_change_logs
短号修改审计表。
CREATE TABLE display_user_id_change_logs (
id BIGINT NOT NULL PRIMARY KEY,
user_id BIGINT NOT NULL,
old_display_user_id VARCHAR(32) NOT NULL,
new_display_user_id VARCHAR(32) NOT NULL,
old_display_user_id_kind VARCHAR(32) NOT NULL,
new_display_user_id_kind VARCHAR(32) NOT NULL,
change_type VARCHAR(32) NOT NULL,
lease_id VARCHAR(64) NULL,
reason VARCHAR(64) NOT NULL,
operator_user_id BIGINT NULL,
request_id VARCHAR(64) NOT NULL,
created_at_ms BIGINT NOT NULL,
KEY idx_display_user_id_change_logs_user_id (user_id),
KEY idx_display_user_id_change_logs_request_id (request_id)
);
修改短号必须写审计。审计失败不能静默忽略;如果审计和主变更不能同事务完成,需要进入 user-service outbox 补偿。
user_country_change_logs
国家修改日志只服务资料审计和冷却期,不参与 display_user_id 解析。
CREATE TABLE user_country_change_logs (
id BIGINT NOT NULL PRIMARY KEY,
user_id BIGINT NOT NULL,
old_country VARCHAR(64) NULL,
new_country VARCHAR(64) NOT NULL,
request_id VARCHAR(96) NOT NULL,
changed_at_ms BIGINT NOT NULL,
KEY idx_user_country_change_logs_user_changed (user_id, changed_at_ms),
KEY idx_user_country_change_logs_request_id (request_id)
);
同一用户修改国家按滚动 30 天冷却。冷却期检查必须在锁定 users 行后的事务内读取最新日志,不能只依赖 gateway 侧判断。
ID Generation
user_id
user_id 使用 int64,在服务间 RPC、数据库关系、客户端响应和内部审计中统一使用。人工输入、搜索、分享和登录名使用 display_user_id。
要求:
user_id全局唯一。- 不可变。
- 客户端不能自报或修改
user_id。 user_id不依赖数据库自增暴露业务规模。
可选实现:
Snowflake-like ID
Sonyflake-like ID
database segment allocator
当前实现由 user-service 创建 user_id。
display_user_id
display_user_id 推荐用字符串保存,即使首版只允许数字。
原因:
- 后续可能支持靓号、带前缀号、运营号段。
- 字符串可以保留前导零。
- 可以统一处理大小写和格式归一化。
首版格式可以定为:
^[1-9][0-9]{5,10}$
格式规则必须由 user-service 校验。客户端校验只做体验,不作为安全边界。
Pretty Display User ID Model
临时靓号是短号的一种,但它不是永久改号。它只是在有效期内覆盖当前展示号。
有效展示号计算规则:
if active_pretty_lease exists and now < expires_at_ms:
display_user_id = pretty_display_user_id
display_user_id_kind = pretty
display_user_id_expires_at_ms = expires_at_ms
else:
display_user_id = default_display_user_id
display_user_id_kind = default
display_user_id_expires_at_ms = null
首版不做多个靓号叠加。用户已有 active 靓号时,再申请其他靓号返回 DISPLAY_USER_ID_PRETTY_ACTIVE。如果后续要支持续期,只允许对同一个靓号延长 expires_at_ms,不能生成第二条 active 租约。
默认短号和临时靓号的关系:
| Scenario | Behavior |
|---|---|
| 新用户注册 | 分配默认短号,当前展示号等于默认短号 |
| 申请靓号成功 | 当前展示号改为靓号,默认短号变为 held |
| 靓号有效期内搜索 | 只能通过靓号解析到用户 |
| 靓号有效期内密码登录 | 只能通过当前靓号登录 |
| 靓号过期 | 当前展示号恢复默认短号 |
| 靓号过期后搜索 | 靓号不再解析,默认短号重新解析 |
默认短号在 held 期间仍属于原用户,不能被其他用户申请、租赁或永久占用。
Cross-Service Contract
内部 proto 字段统一使用:
int64 user_id = 1;
禁止在内部主链路使用:
string display_user_id = 1;
允许 user-service 暴露解析接口:
service UserIdentityService {
rpc ResolveDisplayUserID(ResolveDisplayUserIDRequest) returns (ResolveDisplayUserIDResponse);
rpc GetUserIdentity(GetUserIdentityRequest) returns (GetUserIdentityResponse);
rpc ApplyPrettyDisplayUserID(ApplyPrettyDisplayUserIDRequest) returns (ApplyPrettyDisplayUserIDResponse);
}
message ResolveDisplayUserIDRequest {
string display_user_id = 1;
}
message ResolveDisplayUserIDResponse {
int64 user_id = 1;
string display_user_id = 2;
string status = 3;
string default_display_user_id = 4;
string display_user_id_kind = 5;
int64 display_user_id_expires_at_ms = 6;
}
message GetUserIdentityRequest {
int64 user_id = 1;
}
message GetUserIdentityResponse {
int64 user_id = 1;
string display_user_id = 2;
string status = 3;
string default_display_user_id = 4;
string display_user_id_kind = 5;
int64 display_user_id_expires_at_ms = 6;
}
message ApplyPrettyDisplayUserIDRequest {
int64 user_id = 1;
string pretty_display_user_id = 2;
int64 lease_duration_ms = 3;
string payment_receipt_id = 4;
}
message ApplyPrettyDisplayUserIDResponse {
int64 user_id = 1;
string display_user_id = 2;
string default_display_user_id = 3;
string display_user_id_kind = 4;
int64 display_user_id_expires_at_ms = 5;
string lease_id = 6;
}
ResolveDisplayUserID 只能返回当前有效归属。默认短号被靓号覆盖时处于 held,普通解析接口不能返回它。历史归属查询必须使用运营或审计接口,不能给普通业务链路使用。
Service Usage Rules
gateway-service
gateway 可以接收客户端传入的 display_user_id,但只能用于调用 user-service.ResolveDisplayUserID。
解析后:
display_user_id -> user_id -> downstream RPC
gateway 不缓存永久映射。
密码登录入口也按当前展示号解析。用户有 active 靓号时,默认短号暂时不能登录;靓号过期后,默认短号恢复登录能力。
room-service
Room Cell 中只保存:
OwnerID int64
HostID int64
MicSeat.UserID int64
OnlineUsers map[int64]
AdminUsers map[int64]bool
BanUsers map[int64]bool
MuteUsers map[int64]bool
房间状态、snapshot、command log、outbox 事件都不能保存 display_user_id 作为身份字段。
如果房间广播需要展示短号,由客户端或网关资料接口通过 user-service 补全;Room Cell 不承担资料拼装。
Tencent Cloud IM
腾讯云 IM identifier 只使用不可变长 user_id 的十进制字符串:
user_id
单聊消息、系统通知、群消息和后续回调校验都使用 user_id。
客户端通过短号发起私聊时,必须先解析成 user_id。
wallet-service
账务主体只允许是:
user_id
账单、ledger、幂等记录、余额账户都不能用 display_user_id。
activity-service
活动积分、奖励、风控、排行榜内部主体只用 user_id。
活动榜单展示短号时,展示层再补资料。活动结算不能依赖短号。
User Creation Flow
新用户创建时必须同时分配 user_id 和 default_display_user_id。创建完成后,current_display_user_id 等于默认短号。
sequenceDiagram
participant U as user-service
participant DB as MySQL
U->>U: generate user_id
U->>U: allocate default_display_user_id from 163000 sequence
U->>DB: begin transaction
U->>DB: insert users(default=current)
U->>DB: insert user_display_user_ids(default,active)
U->>DB: commit
如果默认短号唯一冲突,重新生成候选短号后重试。重试次数必须有限,超过后返回 DISPLAY_USER_ID_ALLOCATE_FAILED。
Change Default Display User ID Flow
修改默认短号必须事务化。首版建议禁止在 active 靓号期间修改默认短号,避免“过期恢复哪个号”的用户理解成本;如果业务必须支持,需要在变更日志中明确记录新的恢复目标。
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
participant DB as MySQL
C->>G: ChangeDisplayUserID
G->>U: ChangeDisplayUserID(user_id,new_default_display_user_id)
U->>U: validate format and cooldown
U->>DB: begin transaction
U->>DB: lock user row
U->>DB: reject if active pretty lease exists
U->>DB: lock current default display_user_id
U->>DB: check new display_user_id uniqueness
U->>DB: mark old default display_user_id released
U->>DB: insert new default display_user_id active
U->>DB: update users.default_display_user_id
U->>DB: update users.current_display_user_id
U->>DB: insert display_user_id_change_logs
U->>DB: commit
修改成功后,user-service 可以发布用户资料变更事件,供客户端资料缓存或服务端资料缓存刷新。腾讯云 IM 的 identifier 不随短号变化。
Apply Pretty Display User ID Flow
申请临时靓号必须先确认靓号可用、租期合法、支付结果有效。金币扣费或订单支付归属账务服务;user-service 只消费可信的 payment_receipt_id 或运营授权结果。
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
participant DB as MySQL
C->>G: ApplyPrettyDisplayUserID
G->>U: ApplyPrettyDisplayUserID(user_id,pretty_display_user_id,duration,receipt)
U->>U: validate format, duration, receipt
U->>DB: begin transaction
U->>DB: lock user row
U->>DB: reject if user already has active pretty lease
U->>DB: check pretty display_user_id availability
U->>DB: mark default display_user_id held
U->>DB: insert pretty_display_user_id_leases(active)
U->>DB: insert user_display_user_ids(pretty,active,expires_at_ms)
U->>DB: update users.current_display_user_id=pretty
U->>DB: insert display_user_id_change_logs(change_type=pretty_apply)
U->>DB: commit
申请成功后:
ResolveDisplayUserID(pretty)返回该用户。ResolveDisplayUserID(default)不返回该用户。GetUserIdentity(user_id)返回display_user_id_kind=pretty和display_user_id_expires_at_ms。- 密码登录使用当前靓号,默认短号暂时不能作为登录名。
Expire Pretty Display User ID Flow
靓号过期有两条触发路径:
| Path | Rule |
|---|---|
| 定时任务 | 扫描 pretty_display_user_id_leases(status=active, expires_at_ms<=now) |
| 懒过期 | ResolveDisplayUserID、GetUserIdentity、密码登录发现快照过期时触发 |
过期恢复必须走同一段事务逻辑:
sequenceDiagram
participant U as user-service
participant DB as MySQL
U->>DB: begin transaction
U->>DB: lock user row
U->>DB: lock active pretty lease
U->>DB: mark pretty lease expired
U->>DB: mark pretty display_user_id expired
U->>DB: mark default display_user_id active
U->>DB: update users.current_display_user_id=default
U->>DB: clear users.current_display_user_id_expires_at_ms
U->>DB: insert display_user_id_change_logs(change_type=pretty_expire)
U->>DB: commit
过期成功后:
- 旧靓号不能继续登录、搜索或分享解析。
- 默认短号恢复为当前展示号。
- user-service 必须失效
user_id -> display_user_id和相关display_user_id -> user_id缓存。 - 如果过期事务失败,不能只删缓存;必须重试事务或告警。
Display User ID Reuse
默认建议:短号释放或靓号过期后进入冷却期,不立即复用。
状态流转:
default: active -> held -> active
default change: active -> released -> reserved -> active
pretty lease: active -> expired -> reserved -> active
首版可以先不复用已释放默认短号;靓号是否可再次出租由运营库存规则决定,但必须有冷却期和历史归属审计。
如果后续要复用:
- 需要冷却期。
- 需要运营规则。
- 需要历史归属审计。
- 普通业务查询只能返回当前有效归属。
API And Proto Additions
建议在:
api/proto/user/v1/user.proto
增加:
message UserIdentity {
int64 user_id = 1;
string display_user_id = 2;
string status = 3;
string default_display_user_id = 4;
string display_user_id_kind = 5;
int64 display_user_id_expires_at_ms = 6;
}
message GetUserIdentityRequest {
int64 user_id = 1;
}
message GetUserIdentityResponse {
UserIdentity identity = 1;
}
message ResolveDisplayUserIDRequest {
string display_user_id = 1;
}
message ResolveDisplayUserIDResponse {
UserIdentity identity = 1;
}
message ChangeDisplayUserIDRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string new_display_user_id = 3;
}
message ChangeDisplayUserIDResponse {
UserIdentity identity = 1;
}
message ApplyPrettyDisplayUserIDRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string pretty_display_user_id = 3;
int64 lease_duration_ms = 4;
string payment_receipt_id = 5;
}
message ApplyPrettyDisplayUserIDResponse {
UserIdentity identity = 1;
string lease_id = 2;
}
message ExpirePrettyDisplayUserIDRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string lease_id = 3;
}
message ExpirePrettyDisplayUserIDResponse {
UserIdentity identity = 1;
}
HTTP 外部接口由 gateway 暴露:
GET /api/v1/users/by-display-user-id/{display_user_id}
GET /api/v1/users/{user_id}/identity
POST /api/v1/users/me/display-id/change
POST /api/v1/users/me/display-id/pretty/apply
具体路径可以后续和 OpenAPI 一起定,但内部 proto 必须先保证主链路只用 user_id。
Error Reasons
user-service 需要增加这些 reason:
| Reason | gRPC Code | Meaning |
|---|---|---|
DISPLAY_USER_ID_INVALID |
InvalidArgument |
短号格式非法 |
DISPLAY_USER_ID_EXISTS |
AlreadyExists |
短号已被当前有效、持有、预留或阻断 |
DISPLAY_USER_ID_COOLDOWN |
FailedPrecondition |
修改短号冷却期未结束 |
COUNTRY_CHANGE_COOLDOWN |
FailedPrecondition |
修改国家冷却期未结束 |
DISPLAY_USER_ID_NOT_FOUND |
NotFound |
当前有效短号不存在 |
DISPLAY_USER_ID_ALLOCATE_FAILED |
Internal |
默认短号分配失败 |
DISPLAY_USER_ID_PRETTY_NOT_AVAILABLE |
FailedPrecondition |
靓号不可申请、已被租用或被预留 |
DISPLAY_USER_ID_PRETTY_ACTIVE |
FailedPrecondition |
用户已有 active 靓号租约 |
DISPLAY_USER_ID_LEASE_EXPIRED |
FailedPrecondition |
靓号租约已经过期 |
DISPLAY_USER_ID_PAYMENT_REQUIRED |
FailedPrecondition |
申请靓号缺少有效支付凭证 |
gateway 映射到 HTTP envelope 时沿用相同 code。
Caching Rules
允许缓存:
user_id -> display_user_id
display_user_id -> user_id
限制:
display_user_id -> user_id必须短 TTL。- 靓号缓存 TTL 不能超过
display_user_id_expires_at_ms - now。 - 申请靓号后必须失效默认短号和新靓号相关缓存。
- 靓号过期后必须失效旧靓号、默认短号和用户身份缓存。
- 修改默认短号后必须主动失效相关缓存。
- 账务、房间、活动不能依赖缓存中的短号做身份判断。
- 缓存 miss 必须回源 user-service。
- 缓存命中也必须尊重
expires_at_ms,不能返回已过期靓号。
Migration Impact
后续修改现有 proto 时:
- 所有跨服务用户字段保持
int64 user_id。 - 如果要额外返回短号,只能追加
string display_user_id,不能替换user_id。 - 如果返回当前展示号,必须同时考虑追加
display_user_id_kind和display_user_id_expires_at_ms,避免客户端把临时靓号误认为永久短号。 - 历史 outbox 事件不补写短号。
- 客户端显示短号时通过用户资料接口或缓存补全。
Test Matrix
必须覆盖:
- 新用户创建时生成唯一
user_id和默认display_user_id。 - 默认短号冲突时可重试。
- 通过当前有效
display_user_id解析到正确user_id。 - 不存在的短号返回
DISPLAY_USER_ID_NOT_FOUND。 - 修改短号成功后旧短号不再解析到用户。
- 修改短号遇到已占用短号返回
DISPLAY_USER_ID_EXISTS。 - 冷却期内修改返回
DISPLAY_USER_ID_COOLDOWN。 - 申请靓号成功后,当前展示号变为靓号,默认短号进入
held。 - 靓号有效期内,默认短号不能被普通解析接口解析。
- 靓号有效期内,密码登录使用当前靓号。
- 用户已有 active 靓号时再次申请其他靓号返回
DISPLAY_USER_ID_PRETTY_ACTIVE。 - 靓号过期后,当前展示号恢复默认短号。
- 靓号过期后,旧靓号不再解析到用户。
- 懒过期和定时过期并发执行时,只能有一个事务成功。
- 房间、钱包、IM、活动 proto 和存储仍只使用
user_id作为主体。
Final Rule
user_id 是系统身份,default_display_user_id 是用户本身的门牌号,临时靓号只是限时覆盖当前门牌号。
服务之间传 user_id。客户端展示、搜索、分享可以用 display_user_id。