hyapp-server/docs/用户ID技术设计.md

27 KiB
Raw Permalink Blame History

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 当前展示号来源,defaultpretty 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

  1. HTTP、客户端 JSON 和 JWT claims 中的 user_id 一律返回数值型用户 ID 的字符串形式。
  2. 跨服务通信一律传数值型 user_id
  3. 数据库主关联一律存数值型 user_id,并用 app_code 做租户作用域。
  4. outbox、审计、账单、房间状态、IM session、活动记录一律存数值型 user_id
  5. display_user_id 只允许出现在展示、搜索、分享、登录名、客服查询和运营配置入口。
  6. 任何通过 display_user_id 发起的请求,都必须先在 user-service 解析成内部 user_id,后续链路只传内部 user_id
  7. display_user_id 可修改,所以不能作为幂等键、外键、账务主体或事件主体。
  8. 临时靓号不能修改 user_id,也不能删除或转移用户的默认短号。
  9. 当前展示号的真实来源必须可解释:默认短号来自永久归属,临时靓号来自有效租约。
  10. ResolveDisplayUserID、密码登录和用户资料查询都必须先处理过期靓号,再返回当前有效短号。
  11. 靓号过期恢复必须事务化,不能只靠缓存 TTL。

Ownership

user-serviceuser_iddisplay_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_idgateway 校验当前用户是 active coin_sellerwallet-service 扣减该用户 COIN_SELLER_COIN 并给目标用户增加普通 COINdisplay_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 运营阻断,不允许分配

生成列用于模拟部分唯一索引:同一个短号不能同时处于 activeheldreservedblocked;同一个用户只能有一个 active 当前展示号。历史 expiredreleased 记录可以保留多条。

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_iddefault_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=prettydisplay_user_id_expires_at_ms
  • 密码登录使用当前靓号,默认短号暂时不能作为登录名。

Expire Pretty Display User ID Flow

靓号过期有两条触发路径:

Path Rule
定时任务 扫描 pretty_display_user_id_leases(status=active, expires_at_ms<=now)
懒过期 ResolveDisplayUserIDGetUserIdentity、密码登录发现快照过期时触发

过期恢复必须走同一段事务逻辑:

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_kinddisplay_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