feat: migrate IM identity and snowflake primary keys
This commit is contained in:
+2
-2
@@ -395,7 +395,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
- 获取验证码:调用 `POST /v1/im/auth/verification-codes`,必须带 `X-Tinode-APIKey`、`X-Tenant-Code`、`X-IM-Schema: 1`,Body `{"scene":"register","phone":"+86138..."}`;成功返回 `challenge_id`、`expires_in_seconds`、`resend_after_seconds`。
|
||||
- 提交注册:前端校验密码一致和协议后,先调用 `POST /v1/im/auth/verification-codes/{challenge_id}/verify`,Body `{"code":"123456"}`,获得一次性 `verification_token`。
|
||||
- WebSocket:已完成同一企业 `hi` 后发送 `{acc}`。把一次性验证结果放在 `{acc}.cred[0].resp`,它必须是 HTTP verify 返回的 `verification_token`,不是短信验证码 `123456`。不得写入 `desc.private`,也不得在 `{acc}` 顶层传 `tags`。服务端会根据已校验的 `cred` 建立手机号凭证;客户端主动设置 `tags:["tel:..."]` 会被 Tinode 权限策略拒绝并返回 `403 params.what=tags`。
|
||||
- WebSocket:已完成同一企业 `hi` 后发送 `{acc}`。把一次性验证结果放在 `{acc}.cred[0].resp`,它必须是 HTTP verify 返回的 `verification_token`,不是短信验证码 `123456`。不得写入 `desc.private`,也不得在 `{acc}` 顶层传 `tags`。服务端会把已校验手机号写入 `im_users.phone`,并保留验证凭证记录;用户标签已废弃,客户端设置 `tags` 会返回 `403 params.what=tags`。
|
||||
- 手机号格式:HTTP 发码、验证码校验、`{acc}.secret` 中的 username、`cred.val` 必须使用同一个规范化号码,推荐 E.164 格式如 `+86138...`;不要一处使用 `+86138...`、另一处使用 `86138...`。
|
||||
|
||||
```json
|
||||
@@ -1679,7 +1679,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
**状态:Tinode `me` 资料更新已有基础;目标产品接口需改造。**
|
||||
|
||||
- 查询 `GET /v1/im/me/profile` 返回 `avatar_file_id,nickname,birth_date,signature,gender,region,public_account,version,field_rules`。
|
||||
- 查询 `GET /v1/im/me/profile` 返回 `avatar_file_id,nickname,birth_date,signature,gender,region,email,public_account,version,field_rules`;`email` 当前仅为资料字段,不用于登录或找回密码。
|
||||
- 更新统一使用 `PATCH /v1/im/me/profile`,仅提交变更字段并带 `version`;服务端更新后同步 Tinode `me.public` 并推送资料事件。
|
||||
- 头像先以 `purpose=user_avatar` 上传再 PATCH;任何字段的可见性由隐私策略裁剪。
|
||||
|
||||
|
||||
+32
-7
@@ -7,6 +7,7 @@
|
||||
-- 2. Tenant-scoped business tables live in each tenant database and do not include tenant_id.
|
||||
-- 3. This file is for review and manual execution. Existing legacy columns/indexes should be checked before ALTER/DROP.
|
||||
-- 4. Table definitions are aligned with server/db/mysql/adapter.go plus im_verification_codes used by chapter 1 verification APIs.
|
||||
-- 5. Non-auto-increment BIGINT primary keys are generated by the server Snowflake generator.
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
@@ -54,12 +55,14 @@ CREATE TABLE IF NOT EXISTS hotgo.im_tenant_config (
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_user_security (
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
auth_version INT NOT NULL DEFAULT 1,
|
||||
password_updated_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (user_id)
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_user_security_user (user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_verification_codes (
|
||||
@@ -125,6 +128,7 @@ CREATE TABLE IF NOT EXISTS tinode.im_action_tokens (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_youth_mode_settings (
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
purpose VARCHAR(64) NOT NULL,
|
||||
password_hash VARBINARY(255) NOT NULL,
|
||||
@@ -136,7 +140,8 @@ CREATE TABLE IF NOT EXISTS tinode.im_youth_mode_settings (
|
||||
locked_until DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (user_id, purpose)
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_youth_mode_settings_user_purpose (user_id, purpose)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ============================================================
|
||||
@@ -234,6 +239,7 @@ CREATE TABLE IF NOT EXISTS tinode.im_friend_requests (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_friendships (
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
peer_id BIGINT NOT NULL,
|
||||
status VARCHAR(16) NOT NULL,
|
||||
@@ -241,16 +247,19 @@ CREATE TABLE IF NOT EXISTS tinode.im_friendships (
|
||||
topic CHAR(25) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (user_id, peer_id),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_friendships_pair (user_id, peer_id),
|
||||
INDEX idx_im_friendships_peer (peer_id, status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_user_blocks (
|
||||
id BIGINT NOT NULL,
|
||||
blocker_id BIGINT NOT NULL,
|
||||
blocked_id BIGINT NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (blocker_id, blocked_id),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_user_blocks_pair (blocker_id, blocked_id),
|
||||
INDEX idx_im_user_blocks_blocked (blocked_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
@@ -553,6 +562,7 @@ CREATE TABLE IF NOT EXISTS tinode.im_mailbox_submissions (
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_user_profiles (
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
avatar_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
nickname VARCHAR(64) NOT NULL DEFAULT '',
|
||||
@@ -560,14 +570,26 @@ CREATE TABLE IF NOT EXISTS tinode.im_user_profiles (
|
||||
signature VARCHAR(256) NOT NULL DEFAULT '',
|
||||
gender VARCHAR(16) NOT NULL DEFAULT '',
|
||||
region VARCHAR(128) NOT NULL DEFAULT '',
|
||||
email VARCHAR(128) NOT NULL DEFAULT '',
|
||||
public_account VARCHAR(64) NOT NULL DEFAULT '',
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (user_id)
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_user_profiles_user (user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- Existing database migration (the application performs the same migration
|
||||
-- idempotently at startup):
|
||||
-- 1. Add im_users.phone VARCHAR(32) NULL and a UNIQUE index.
|
||||
-- 2. Add im_user_profiles.email VARCHAR(128) NOT NULL DEFAULT ''.
|
||||
-- 3. Backfill phone/email from validated im_credentials first, then use
|
||||
-- tel:/email: rows in im_usertags as a legacy fallback.
|
||||
-- 4. Drop im_usertags and im_users.tags only after the backfill is verified.
|
||||
-- im_topics.tags and im_topictags are topic discovery data and must be retained.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_user_privacy_settings (
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
add_me_methods JSON,
|
||||
profile_visibility VARCHAR(32) NOT NULL DEFAULT 'friends',
|
||||
@@ -576,7 +598,8 @@ CREATE TABLE IF NOT EXISTS tinode.im_user_privacy_settings (
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (user_id)
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_user_privacy_settings_user (user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_app_releases (
|
||||
@@ -599,6 +622,7 @@ CREATE TABLE IF NOT EXISTS tinode.im_app_releases (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tinode.im_device_push_tokens (
|
||||
id BIGINT NOT NULL,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
provider VARCHAR(32) NOT NULL,
|
||||
@@ -607,7 +631,8 @@ CREATE TABLE IF NOT EXISTS tinode.im_device_push_tokens (
|
||||
platform VARCHAR(32) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (device_id),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_device_push_tokens_device (device_id),
|
||||
UNIQUE INDEX uk_im_device_push_tokens_provider_token (provider, token),
|
||||
INDEX idx_im_device_push_tokens_user (user_id, updated_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
9. 好友/黑名单写操作已统一接入 HTTP 幂等、关系事件和必要的对端系统通知。
|
||||
10. 用户搜索和好友申请已执行 `add_me_methods`,手机号搜索不会返回禁止该方式的非好友。
|
||||
11. 好友个人主页已执行 `profile_visibility`。
|
||||
12. 用户搜索已改用昵称、公开账号、登录账号和 `im_users.phone` 等结构化字段,不再依赖用户标签。
|
||||
13. 注册手机号以 `im_users.phone` 为真源,邮箱作为 `im_user_profiles.email` 资料字段;旧 `im_users.tags/im_usertags` 已迁移删除,Topic 标签保留。
|
||||
|
||||
### 5.2 后续增强项
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
-- 适用目标:已有 Tinode MySQL 库,补齐本项目 1-7 章业务层依赖表与关键字段。
|
||||
-- 注意:本稿使用 MySQL 8 functional index 处理空 idempotency_key 唯一性。
|
||||
-- 第一阶段多库方案:本 SQL 的 im_tenant 放 hotgo 公共库;tinode/各租户业务库表暂时保留 tenant_id 字段。
|
||||
-- 本文件中的非自增 BIGINT 主键由服务端雪花算法生成。
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
@@ -79,13 +80,15 @@ CREATE TABLE IF NOT EXISTS im_verification_codes(
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS im_user_security(
|
||||
id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
auth_version INT NOT NULL DEFAULT 1,
|
||||
password_updated_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(tenant_id,user_id),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_user_security_tenant_user(tenant_id,user_id),
|
||||
CONSTRAINT fk_im_user_security_tenant FOREIGN KEY(tenant_id) REFERENCES im_tenant(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
@@ -132,6 +135,7 @@ CREATE TABLE IF NOT EXISTS im_action_tokens(
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS im_youth_mode_settings(
|
||||
id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
purpose VARCHAR(64) NOT NULL,
|
||||
@@ -140,7 +144,8 @@ CREATE TABLE IF NOT EXISTS im_youth_mode_settings(
|
||||
locked_until DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(tenant_id, user_id, purpose),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_youth_settings_tenant_user_purpose(tenant_id, user_id, purpose),
|
||||
CONSTRAINT fk_im_youth_settings_tenant FOREIGN KEY(tenant_id) REFERENCES im_tenant(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
@@ -192,6 +197,7 @@ CREATE UNIQUE INDEX uk_im_friend_requests_idempotency
|
||||
ON im_friend_requests(tenant_id, applicant_id, (NULLIF(idempotency_key, '')));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS im_friendships(
|
||||
id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
peer_id BIGINT NOT NULL,
|
||||
@@ -200,18 +206,21 @@ CREATE TABLE IF NOT EXISTS im_friendships(
|
||||
topic CHAR(25) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(tenant_id, user_id, peer_id),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_friendships_tenant_pair(tenant_id, user_id, peer_id),
|
||||
INDEX idx_im_friendships_peer(tenant_id, peer_id, status),
|
||||
CONSTRAINT fk_im_friendships_tenant FOREIGN KEY(tenant_id) REFERENCES im_tenant(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS im_user_blocks(
|
||||
id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
blocker_id BIGINT NOT NULL,
|
||||
blocked_id BIGINT NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(tenant_id, blocker_id, blocked_id),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_user_blocks_tenant_pair(tenant_id, blocker_id, blocked_id),
|
||||
INDEX idx_im_user_blocks_blocked(tenant_id, blocked_id),
|
||||
CONSTRAINT fk_im_user_blocks_tenant FOREIGN KEY(tenant_id) REFERENCES im_tenant(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
@@ -312,5 +321,6 @@ CREATE TABLE IF NOT EXISTS im_message_revisions(
|
||||
-- ALTER TABLE im_filemsglinks ADD COLUMN userid BIGINT AFTER topic;
|
||||
-- CREATE INDEX im_filemsglinks_tenant_fileid ON im_filemsglinks(tenant_id, fileid);
|
||||
|
||||
-- im_usertags/im_topictags/im_devices/im_auth/im_credentials 也已按租户增加 tenant_id;
|
||||
-- im_usertags 已废弃;im_users.phone 是手机号真源,邮箱存放于 im_user_profiles.email。
|
||||
-- im_topictags/im_devices/im_auth/im_credentials 也已按租户增加 tenant_id;
|
||||
-- 若旧库缺字段,需要参照当前 server/db/mysql/schema.sql 逐项补齐。
|
||||
|
||||
+7
-1
@@ -205,7 +205,7 @@ HTTP API 通用规则:
|
||||
| 模块 | 目标表 | 主要职责 |
|
||||
| --- | --- | --- |
|
||||
| 好友关系 | `im_friendships`、`im_friend_requests`、`im_user_blocks` | 好友申请、同意、拒绝、删除、拉黑 |
|
||||
| 用户资料 | `im_user_profiles`、`im_user_privacy_settings` | 昵称、生日、签名、可见性和添加方式 |
|
||||
| 用户资料 | `im_users`、`im_user_profiles`、`im_user_privacy_settings` | 注册手机号、昵称、邮箱、生日、签名、可见性和添加方式 |
|
||||
| 群治理 | `im_group_settings`、`im_group_join_requests`、`im_group_announcements`、`im_group_mutes`、`im_group_admin_logs` | 入群审批、公告、禁言、管理员、转让群主 |
|
||||
| 群文件 | `im_group_files` | 群文件目录、搜索、删除权限 |
|
||||
| 收藏 | `im_favorites` | 收藏去重、分类、来源、搜索和分享 |
|
||||
@@ -234,6 +234,10 @@ HTTP API 通用规则:
|
||||
| 群成员 | `im_group_members` | 群成员业务状态、角色、入群来源和操作者 |
|
||||
| 消息版本 | `im_message_revisions` | 编辑前内容、版本、操作者和变更原因 |
|
||||
|
||||
MySQL 表主键约定:所有 `im_*` 基础表必须具有独立的 `id` 主键。`im_user_security`、`im_kvmeta`、用户资料/隐私、设备 Push Token、好友/黑名单、青少年模式以及群设置/公告已读/禁言等原先使用自然键或复合主键的表,统一改为服务端雪花算法生成的 `BIGINT id` 主键;原业务键保留唯一索引,查询和幂等语义不变。服务启动时会对当前租户库执行幂等迁移,为存量行补齐雪花 ID 后再替换主键。迁移期间不得同时运行不写入 `id` 的旧版本服务。
|
||||
|
||||
用户身份字段约定:`im_users.phone` 是注册、密码找回和敏感操作验证使用的手机号真源,新注册必须提供已验证手机号;历史无手机号账号迁移后允许暂时为 `NULL`。`im_user_profiles.email` 仅是未验证的资料字段,不参与登录和密码找回。旧 `im_users.tags` 与 `im_usertags` 已废弃,启动迁移会先从已验证凭证和旧标签回填手机号/邮箱,再删除两者。群组发现仍使用 `im_topics.tags` 与 `im_topictags`,不受本次调整影响。
|
||||
|
||||
### 7.3 企业码与登录流程
|
||||
|
||||
1. 未认证客户端先通过 `hi.tenant` 提交企业码;服务端只按 `im_tenant.code` 查找启用租户并把 `TenantID` 固定到 Session。
|
||||
@@ -506,6 +510,8 @@ end_reason, created_at, updated_at
|
||||
| --- | --- |
|
||||
| 头像、昵称、签名 | User Public + `im_user_profiles` |
|
||||
| 生日、地区、性别 | `im_user_profiles`,按隐私设置返回 |
|
||||
| 注册手机号 | `im_users.phone`,仅账号安全流程内部使用 |
|
||||
| 邮箱 | `im_user_profiles.email`,仅作为个人资料,不作为认证凭证 |
|
||||
| 实名姓名、证件号 | 实名表加密存储,不进入 Public |
|
||||
| 群昵称 | `im_group_members`,按需投影到 Subscription Private |
|
||||
|
||||
|
||||
+10
-3
@@ -3,10 +3,12 @@
|
||||
-- Database: the currently selected tenant database.
|
||||
-- Important: one tenant owns one database, so these tables intentionally do not contain tenant_id.
|
||||
-- This SQL is for review first. Do not execute directly in production before confirming indexes and migration order.
|
||||
-- Non-auto-increment BIGINT primary keys are generated by the server Snowflake generator.
|
||||
|
||||
-- 1. Group profile and governance truth.
|
||||
-- Topic/Subscription remains the communication projection; this table is the product business truth.
|
||||
CREATE TABLE IF NOT EXISTS im_group_settings (
|
||||
id BIGINT NOT NULL,
|
||||
topic VARCHAR(32) NOT NULL,
|
||||
owner_user_id BIGINT NOT NULL,
|
||||
name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
@@ -27,7 +29,8 @@ CREATE TABLE IF NOT EXISTS im_group_settings (
|
||||
dissolved_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (topic),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_group_settings_topic (topic),
|
||||
INDEX idx_im_group_settings_owner (owner_user_id, group_status),
|
||||
INDEX idx_im_group_settings_status_updated (group_status, updated_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
@@ -135,19 +138,22 @@ CREATE TABLE IF NOT EXISTS im_group_announcements (
|
||||
|
||||
-- 6. Announcement read receipts.
|
||||
CREATE TABLE IF NOT EXISTS im_group_announcement_reads (
|
||||
id BIGINT NOT NULL,
|
||||
announcement_id VARCHAR(64) NOT NULL,
|
||||
topic VARCHAR(32) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
read_at DATETIME(3) NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (announcement_id, user_id),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_group_announcement_reads_announcement_user (announcement_id, user_id),
|
||||
INDEX idx_im_group_announcement_reads_topic_user (topic, user_id, read_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- 7. Current member mute state.
|
||||
-- Historical changes are stored in im_group_admin_logs.
|
||||
CREATE TABLE IF NOT EXISTS im_group_mutes (
|
||||
id BIGINT NOT NULL,
|
||||
topic VARCHAR(32) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
muted_by BIGINT NOT NULL,
|
||||
@@ -160,7 +166,8 @@ CREATE TABLE IF NOT EXISTS im_group_mutes (
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (topic, user_id),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX uk_im_group_mutes_topic_user (topic, user_id),
|
||||
INDEX idx_im_group_mutes_topic_status (topic, status, expires_at),
|
||||
INDEX idx_im_group_mutes_user_status (user_id, status, expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
> 主要依据:`IM-产品原型需求文档.md`、`IM-前后端对接文档.md`
|
||||
> 多租户依据:`IM-独立库多租户改造方案.md`
|
||||
> 明确排除:已废弃的 `IM-服务端多租户改造方案.md`
|
||||
> 数据库更新:2026-08-18 已完成无独立 `id` 表的雪花主键改造和本地库迁移。
|
||||
|
||||
## 1. 审核说明
|
||||
|
||||
@@ -353,10 +354,13 @@
|
||||
- 好友关系、认证安全、隐私、收藏、通知、会议、报表等业务表已在 MySQL 适配器中出现对应存储实现。
|
||||
- 群业务从 Topic/Subscription 扩展为独立业务表,方向符合复杂群管理需求。
|
||||
- `im_http_idempotency` 和 `im_business_events_outbox` 已纳入应用自动建表与数据库变更汇总脚本。
|
||||
- MySQL 业务表已统一具备独立 `id` 主键;原先使用自然键或复合主键的 11 张表已改用服务端雪花 `BIGINT id`,原业务键保留唯一索引。
|
||||
- 应用启动会在当前租户库幂等补列、回填存量雪花 ID 并切换主键;迁移使用数据库级 advisory lock,独立租户库与残留 `tenant_id` 旧结构均可保留原业务唯一性。
|
||||
- 用户身份字段已结构化:手机号迁入 `im_users.phone`,邮箱迁入 `im_user_profiles.email`;用户 `tags/im_usertags` 在回填后删除,Topic 标签体系继续保留。
|
||||
|
||||
### 需要修正
|
||||
|
||||
1. 建立唯一 schema 版本和迁移入口,禁止“应用启动建部分表 + 手工 SQL 建另一部分表”长期并存。
|
||||
1. 继续把其他历史建表/升级逻辑收敛到唯一 schema 版本和迁移入口;本次雪花主键改造已具备统一自动迁移入口。
|
||||
2. 群业务 SQL 必须参数化目标租户库,禁止硬编码 `tinode`。
|
||||
3. 新租户建库和存量租户升级必须执行同一套 migration。
|
||||
4. 为 Topic、Subscription、群业务表之间的复合写操作提供事务边界。
|
||||
|
||||
Reference in New Issue
Block a user