Skip to content

TSID 字段约定

Last Updated: 2026-04-11
Status: 长期协议契约文档
Scope: 所有 REST API 与 WebSocket API 中 TSID 字段的传输格式约定


1. 什么是 TSID

TSID(Time-Sorted ID)是 Imboy 使用的分布式 ID 生成方案,取代原先的 BIGSERIAL 自增 ID。

位布局(64 位,无符号,但存储为有符号 BIGINT):

[sign:1=0][timestamp_ms:42][node_id:10][sequence:11]
字段位数说明
符号位1始终为 0(正整数)
时间戳42相对于纪元(2025-01-01 00:00:00 UTC)的毫秒数
节点 ID103 位数据中心 + 7 位节点
序列号11每毫秒每节点最多 2048 个 ID

特性

  • 纪元:2025-01-01 00:00:00 UTC
  • 时间跨度:139.5 年(2025 → 2164)
  • 生成吞吐量:单节点 200 万+ ID/秒
  • 数据库类型:PostgreSQL BIGINT

2. API 传输格式

后端以 JSON integer(number)返回 TSID 字段

json
{
  "code": 0,
  "msg": "success.",
  "payload": {
    "id": 84442613760000001,
    "from_id": 84442613760000002,
    "to_id": 84442613760000003
  }
}

客户端精度处理

JavaScript Number 最大安全整数为 2^53 - 1 = 9,007,199,254,740,991(约 9×10^15),
而当前 TSID 值(从 2025-01-01 起约 400+ 天后)已超过此上限(约 8×10^16)。

前端(imboy-admin-frontend)解决方案axios 客户端通过 transformResponse 中的
safeParseBigIntJson 函数,在 JSON 解析阶段将 16 位及以上整数自动转为字符串:

typescript
// client.ts — safeParseBigIntJson
const safeText = trimmed.replace(
  /(?<=[:,[\s])(-?\d{16,})(?=[,\]}\s])/g,
  '"$1"'
)
return JSON.parse(safeText)

因此,前端 TypeScript 中所有 TSID 字段类型为 EntityId = string

Flutter(imboyapp)解决方案:Dart int 为 64 位,可直接承载 TSID,无精度丢失。
从 API 响应解析时使用 int.parse(json['id'].toString()) 兼容 int/string 两种格式。


3. 哪些字段是 TSID

以下字段在 API 响应中为 TSID(JSON integer,前端接收为 string):

字段名含义所在接口
id资源主键所有资源接口
uid用户 ID用户、消息、群组相关接口
from_id消息发送方 ID消息接口
to_id消息接收方 ID消息接口
peer_id会话对端 ID会话接口
group_id群组 ID群组、消息接口
user_id用户 ID(别名)群成员、设备接口
owner_uid群主 ID群组接口
creator_uid创建者 ID群组接口
mentioned_uid被 @ 用户 ID提及接口
from_uid消息发送方 ID(别名)提及接口
key_id合规密钥 ID合规密钥接口
conversation_id会话 ID会话接口
last_msg_id最后消息 ID会话接口
channel_id频道 ID频道接口
author_id作者 ID频道消息接口

不是 TSID 的 ID 字段

字段名含义类型
device_id设备标识符string(UUID 或自定义)
did设备 ID(别名)string
role_id角色 IDnumber(小整数)

4. 前端规范

TypeScript(imboy-admin-frontend)

typescript
// src/types/common.ts
export type EntityId = string  // TSID 经 safeParseBigIntJson 转换后的 string

// 使用示例
interface User {
  id: EntityId       // ✅ 使用 EntityId,不直接写 string
  account: string    // ✅ 非 TSID 字段直接写 string
}

规则

  • 所有 TSID 字段必须使用 EntityId 类型,不直接写 string
  • 不要用 Number(id)parseInt(id) 处理 TSID(会丢失精度)
  • 在 UI 展示时直接渲染字符串,不做数值运算

Dart/Flutter(imboyapp)

dart
// TSID 解析:兼容 int 和 string 两种 JSON 格式
int parseId(dynamic value) {
  if (value is int) return value;
  return int.parse(value.toString());
}

// 存储:SQLite INTEGER 列,Dart int 类型
final int id;
final int fromId;
final int toId;

5. 后端规范(Erlang)

erlang
%% 生成 TSID(Repo 层 insert 时)
Id = elib_tsid:generate(table_name).

%% 客户端输入:binary → integer
UidInt = ec_cnv:to_integer(UidBin).

%% 客户端输出:直接返回 integer(JSON number)
%% 不做 integer_to_binary 转换
Payload = #{<<"id">> => Id, <<"from_id">> => FromId}.

6. 相关文档

  • docs/reference/rest-api.md — REST API 通用约定
  • docs/reference/websocket-api-2.md — WebSocket 消息协议
  • src/lib/elib_tsid.erl — TSID 生成器实现
  • imboy-admin-frontend/src/services/api/client.ts — safeParseBigIntJson 实现
  • imboy-admin-frontend/src/types/common.ts — EntityId 类型定义

IMBoy — 企业私有化即时通讯平台