Files
tencent-im-util/src/main/resources/db/init.sql
T
连龙刚andClaude fdbb7724fa feat(t17): 数据同步——只读拉取群组/群成员/群消息到本地表,按租户查看
主体(T17 数据同步,/admin/sync 三个独立按钮):
- 同步群组:get_appid_group_list 全量 Next 分页 + 逐群 get_group_info → group_mapping
- 同步群成员(→用户):遍历该租户群取 MemberList → user_mapping(腾讯无全量用户API,靠群成员反推)
- 同步群消息:遍历该租户群 getGroupMsg + IsFinished 滚动全量 → im_message(source=SYNC)
- DB: group_mapping 加 name/owner_account/member_count/last_synced_at;user_mapping 加 nick/last_synced_at
  (init.sql 建表 + ADD COLUMN IF NOT EXISTS 老库升级补丁,幂等)
- TencentImClient: 新增 getAppidGroupList(limit,next[,sdkAppId,secretKey])
- 已知限制:C2C单聊无全量会话API;超大群成员需换 get_group_member_info 分页

附带收尾此前未提交的改动:
- 回调字段名修正(FromAccount→From_Account 等腾讯标准字段) + pickMsgRandom/Time/Type 兼容字段差异
- 租户识别重构:前缀经 TenantService.getByPrefixCode 反查 tenantId(主键雪花化与前缀解耦)
- FreeMarker java.time ?string 坑修复(usage/queue Controller 预格式化) + 消息记录"全部"状态修复
- 新增项目 CLAUDE.md + .claude/memory 基建(gitignore 含密钥记忆,不进 git)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-10 08:40:46 +08:00

261 lines
13 KiB
SQL
Raw 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.
-- ======================================================================
-- tencent-im-util 数据库初始化脚本(PostgreSQL
-- 库: tencen_im schema: public
-- 说明: 多租户隔离,所有业务表均带 tenant_id;消息主表按月 RANGE 分区
-- ======================================================================
-- ===== 1. 租户表 =====
CREATE TABLE IF NOT EXISTS tenant (
tenant_id varchar(32) NOT NULL,
tenant_name varchar(64) NOT NULL,
app_key varchar(128) NOT NULL,
app_secret varchar(128) NOT NULL,
prefix_code varchar(16) NOT NULL,
callback_url text,
quota_im_qps int NOT NULL DEFAULT 50,
quota_trtc_concurrent int NOT NULL DEFAULT 100,
status smallint NOT NULL DEFAULT 1, -- 1=启用 0=停用
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_tenant PRIMARY KEY (tenant_id)
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_tenant_app_key ON tenant(app_key);
CREATE UNIQUE INDEX IF NOT EXISTS uk_tenant_prefix ON tenant(prefix_code);
-- ===== 2. 用户映射(业务用户 ↔ IM 用户) =====
CREATE TABLE IF NOT EXISTS user_mapping (
id bigint GENERATED BY DEFAULT AS IDENTITY,
tenant_id varchar(32) NOT NULL,
biz_user_id varchar(128) NOT NULL,
im_user_id varchar(128) NOT NULL,
is_default boolean NOT NULL DEFAULT false,
is_global boolean NOT NULL DEFAULT false,
status smallint NOT NULL DEFAULT 1, -- 1=正常 0=封禁
nick varchar(128), -- 用户昵称(T17 数据同步拉取自群成员资料)
last_synced_at timestamptz, -- 最近一次同步时间(T17
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_user_mapping PRIMARY KEY (id)
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_user_tenant_biz ON user_mapping(tenant_id, biz_user_id);
CREATE UNIQUE INDEX IF NOT EXISTS uk_user_im ON user_mapping(im_user_id);
-- ===== 3. 群映射 =====
CREATE TABLE IF NOT EXISTS group_mapping (
id bigint GENERATED BY DEFAULT AS IDENTITY,
tenant_id varchar(32) NOT NULL,
biz_group_id varchar(128) NOT NULL,
im_group_id varchar(128) NOT NULL,
group_type varchar(16) NOT NULL DEFAULT 'Public',
name varchar(256), -- 群名称(T17 数据同步拉取)
owner_account varchar(128), -- 群主账号(T17 数据同步拉取)
member_count int, -- 群成员数(T17 数据同步拉取)
last_synced_at timestamptz, -- 最近一次同步时间(T17
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_group_mapping PRIMARY KEY (id)
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_group_tenant_biz ON group_mapping(tenant_id, biz_group_id);
CREATE UNIQUE INDEX IF NOT EXISTS uk_group_im ON group_mapping(im_group_id);
-- ===== 4. 消息主表(按月 RANGE 分区) =====
CREATE TABLE IF NOT EXISTS im_message (
msg_key varchar(128) NOT NULL,
tenant_id varchar(32) NOT NULL,
msg_time timestamptz NOT NULL,
conv_type smallint NOT NULL, -- 1=C2C 2=GROUP
conv_id varchar(128) NOT NULL, -- C2C=对端账号 GROUP=群ID
from_account varchar(128),
to_account varchar(128),
group_id varchar(128),
msg_type varchar(32),
msg_body text,
source varchar(16) NOT NULL DEFAULT 'CALLBACK', -- CALLBACK/PULL_BACK/IMPORT
is_cross_tenant boolean NOT NULL DEFAULT false,
dist_status smallint NOT NULL DEFAULT 0, -- 0=待分发 1=已分发 2=失败
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_im_message PRIMARY KEY (msg_key, msg_time)
) PARTITION BY RANGE (msg_time);
-- DEFAULT 分区兜底(应用启动后由 partitionCreateJob 滚动建当月分区)
CREATE TABLE IF NOT EXISTS im_message_default PARTITION OF im_message DEFAULT;
-- 分区索引(建在父表自动传播到所有子分区)
CREATE INDEX IF NOT EXISTS idx_msg_time_brin ON im_message USING BRIN (msg_time);
CREATE INDEX IF NOT EXISTS idx_msg_tenant_conv ON im_message (tenant_id, conv_id, msg_time);
CREATE INDEX IF NOT EXISTS idx_msg_from ON im_message (from_account, msg_time);
CREATE INDEX IF NOT EXISTS idx_msg_dist ON im_message (dist_status) WHERE dist_status = 0;
-- ===== 5. 分发队列(替代 MQFOR UPDATE SKIP LOCKED 抢占消费) =====
CREATE TABLE IF NOT EXISTS dist_queue (
id bigserial,
msg_key varchar(128) NOT NULL,
tenant_id varchar(32) NOT NULL,
conv_id varchar(128),
target_url text,
payload text, -- 分发给业务系统的回调快照
status smallint NOT NULL DEFAULT 0, -- 0=pending 1=processing 2=done 3=dead
retry_count int NOT NULL DEFAULT 0,
next_retry_at timestamptz NOT NULL DEFAULT now(),
locked_by varchar(64),
locked_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_dist_queue PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_dq_pending ON dist_queue (next_retry_at, id) WHERE status = 0;
CREATE INDEX IF NOT EXISTS idx_dq_processing ON dist_queue (locked_at) WHERE status = 1;
CREATE INDEX IF NOT EXISTS idx_dq_tenant ON dist_queue (tenant_id, status);
-- ===== 6. 跨租户授权 =====
CREATE TABLE IF NOT EXISTS cross_tenant_grant (
grant_id bigint GENERATED BY DEFAULT AS IDENTITY,
from_tenant_id varchar(32) NOT NULL,
from_im_user_id varchar(128), -- NULL=该租户任意账户
to_tenant_id varchar(32) NOT NULL,
to_im_user_id varchar(128), -- NULL=目标租户全员
permissions varchar(64) NOT NULL DEFAULT 'send_msg', -- send_msg,add_friend,join_group
direction smallint NOT NULL DEFAULT 0, -- 0=单向 1=双向
start_at timestamptz NOT NULL DEFAULT now(),
end_at timestamptz, -- NULL=长期有效
status smallint NOT NULL DEFAULT 1, -- 1=active 0=revoked 2=expired
approved_by_from varchar(64),
approved_by_to varchar(64),
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_cross_grant PRIMARY KEY (grant_id)
);
CREATE INDEX IF NOT EXISTS idx_grant_from ON cross_tenant_grant (from_tenant_id, from_im_user_id, status);
CREATE INDEX IF NOT EXISTS idx_grant_to ON cross_tenant_grant (to_tenant_id, to_im_user_id, status);
-- ===== 7. 跨租户通讯审计 =====
CREATE TABLE IF NOT EXISTS cross_tenant_audit (
id bigserial,
grant_id bigint,
msg_key varchar(128),
from_im_user_id varchar(128),
to_im_user_id varchar(128),
action_time timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_cross_audit PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_cross_audit_time ON cross_tenant_audit (action_time);
-- ===== 8. TRTC 音视频房间 =====
CREATE TABLE IF NOT EXISTS trtc_room (
room_id bigint NOT NULL, -- 本工具统一分配(雪花)
tenant_id varchar(32) NOT NULL,
biz_room_id varchar(128),
im_group_id varchar(128), -- 关联 IM 群(可选)
status smallint NOT NULL DEFAULT 1, -- 1=进行中 0=已结束
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_trtc_room PRIMARY KEY (room_id)
);
CREATE INDEX IF NOT EXISTS idx_room_tenant_biz ON trtc_room (tenant_id, biz_room_id);
-- ===== 9. 录制文件 =====
CREATE TABLE IF NOT EXISTS recording (
file_id varchar(128) NOT NULL,
tenant_id varchar(32) NOT NULL,
room_id bigint,
cos_path text, -- 按 tenant_id 隔离目录
duration int, -- 秒
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_recording PRIMARY KEY (file_id)
);
CREATE INDEX IF NOT EXISTS idx_rec_tenant_room ON recording (tenant_id, room_id);
-- ===== 10. 用量统计(计费拆账依据) =====
CREATE TABLE IF NOT EXISTS usage_stat (
id bigint GENERATED BY DEFAULT AS IDENTITY,
tenant_id varchar(32) NOT NULL,
stat_time timestamptz NOT NULL,
stat_level smallint NOT NULL DEFAULT 1, -- 1=小时 2=天
im_msg_count bigint NOT NULL DEFAULT 0,
im_dau bigint NOT NULL DEFAULT 0,
trtc_duration_sec bigint NOT NULL DEFAULT 0,
trtc_max_concurrent_room int NOT NULL DEFAULT 0,
api_call_count bigint NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_usage_stat PRIMARY KEY (id)
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_usage ON usage_stat (tenant_id, stat_time, stat_level);
-- ===== 11. API 调用审计 =====
CREATE TABLE IF NOT EXISTS api_call_log (
id bigserial,
tenant_id varchar(32) NOT NULL,
api_name varchar(64) NOT NULL,
params text,
result text,
caller varchar(64),
called_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_api_call_log PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_api_tenant_time ON api_call_log (tenant_id, called_at);
CREATE INDEX IF NOT EXISTS idx_api_time ON api_call_log (called_at);
-- ===== 12. 补拉水位线(PG 持久,Redis 兜底) =====
CREATE TABLE IF NOT EXISTS pull_watermark (
tenant_id varchar(32) NOT NULL,
conv_id varchar(128) NOT NULL,
conv_type smallint NOT NULL,
last_seq bigint NOT NULL DEFAULT 0,
last_time timestamptz,
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_pull_watermark PRIMARY KEY (tenant_id, conv_id)
);
-- ===== 初始化默认租户(示例,可改) =====
INSERT INTO tenant (tenant_id, tenant_name, app_key, app_secret, prefix_code, callback_url)
VALUES ('sa', '系统A', 'sa_app_key_secret', 'sa_app_secret_secret', 'sa', NULL)
ON CONFLICT (tenant_id) DO NOTHING;
-- ===== 13. 管理后台用户(Sa-Token 登录) =====
CREATE TABLE IF NOT EXISTS admin_user (
id bigserial,
username varchar(64) NOT NULL,
password_hash varchar(200) NOT NULL, -- 格式 iterations:salt:hashPBKDF2,见 PasswordUtil
role varchar(16) NOT NULL DEFAULT 'admin',
status smallint NOT NULL DEFAULT 1, -- 1=启用 0=停用
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_admin_user PRIMARY KEY (id),
CONSTRAINT uk_admin_username UNIQUE (username)
);
-- 默认管理员账号由应用首次启动时按 app.yml imutil.admin 配置初始化(不在此写死密码hash)
-- ===== 14. 老应用配置(数据迁移源:每个老应用对应主应用一个租户前缀) =====
CREATE TABLE IF NOT EXISTS source_app (
id bigint GENERATED BY DEFAULT AS IDENTITY,
app_name varchar(64) NOT NULL, -- 老应用名称
sdk_app_id varchar(32) NOT NULL, -- 老应用 SDKAppID
secret_key varchar(128) NOT NULL, -- 老应用密钥(仅本工具持有,迁移拉取用)
tenant_id varchar(32) NOT NULL, -- 对应主应用租户(决定 UserID 前缀),关联 tenant.tenant_id
status smallint NOT NULL DEFAULT 1,-- 1=启用 0=停用
remark text,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_source_app PRIMARY KEY (id)
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_source_app_sdk ON source_app(sdk_app_id);
-- ===== 15. 迁移任务记录(支持断点续传与校验) =====
CREATE TABLE IF NOT EXISTS migrate_task (
id bigint GENERATED BY DEFAULT AS IDENTITY,
source_app_id bigint NOT NULL, -- 关联 source_app.id
object_type varchar(16) NOT NULL, -- USER/GROUP/MSG/ROSTER
status smallint NOT NULL DEFAULT 0, -- 0=pending 1=running 2=paused 3=done 4=failed
total_count bigint NOT NULL DEFAULT 0, -- 应处理总数
processed_count bigint NOT NULL DEFAULT 0, -- 已处理数
pos_cursor varchar(256), -- 断点游标(时间戳/会话+seq,按对象类型语义不同)
verify_result text, -- 校验结果 JSON(老 vs 新 条数对比)
error_msg text, -- 失败原因
started_at timestamptz,
finished_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT pk_migrate_task PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_migrate_task_app ON migrate_task(source_app_id, object_type, status);
-- ===== T17 数据同步:老库升级补丁(ADD COLUMN IF NOT EXISTS,幂等可重跑) =====
ALTER TABLE group_mapping ADD COLUMN IF NOT EXISTS name varchar(256);
ALTER TABLE group_mapping ADD COLUMN IF NOT EXISTS owner_account varchar(128);
ALTER TABLE group_mapping ADD COLUMN IF NOT EXISTS member_count int;
ALTER TABLE group_mapping ADD COLUMN IF NOT EXISTS last_synced_at timestamptz;
ALTER TABLE user_mapping ADD COLUMN IF NOT EXISTS nick varchar(128);
ALTER TABLE user_mapping ADD COLUMN IF NOT EXISTS last_synced_at timestamptz;