feat: table add prefix

This commit is contained in:
zhr 2026-09-23 17:11:56 +08:00
parent ce2cdf6bf3
commit f724f66d1e
55 changed files with 338 additions and 306 deletions

View File

@ -141,6 +141,6 @@ func skipFriendDirected(gdb *gorm.DB, path string) bool {
return false return false
} }
var n int64 var n int64
err := gdb.Raw(`SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'friend_relations' AND column_name = 'user_id'`).Scan(&n).Error err := gdb.Raw(`SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 't_friend_relations' AND column_name = 'user_id'`).Scan(&n).Error
return err == nil && n > 0 return err == nil && n > 0
} }

View File

@ -379,8 +379,8 @@ TaskPool:
2. 权限(见 9.4),失败不写库 2. 权限(见 9.4),失败不写库
3. MsgId = 请求带来的或 Snowflake 3. MsgId = 请求带来的或 Snowflake
4. BEGIN 4. BEGIN
INSERT messages INSERT t_messages
UPDATE conversations.last_msg_id / last_msg_at UPDATE t_conversations.last_msg_id / last_msg_at
COMMIT COMMIT
5. DeliverQueue.Push // 可靠入队,失败则重试有限次 5. DeliverQueue.Push // 可靠入队,失败则重试有限次
6. 仍失败 → 不 SEND_ACK,客户端用 client_msg_id 重试 6. 仍失败 → 不 SEND_ACK,客户端用 client_msg_id 重试
@ -406,7 +406,7 @@ P2P 好友关系、未拉黑、对方未禁你
## 10.1 messages ## 10.1 messages
```sql ```sql
CREATE TABLE messages ( CREATE TABLE t_messages (
id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属会话 id', conversation_id BIGINT NOT NULL COMMENT '所属会话 id',
sender_id BIGINT NOT NULL COMMENT '发送者 user_id', sender_id BIGINT NOT NULL COMMENT '发送者 user_id',
@ -423,7 +423,7 @@ CREATE TABLE messages (
`message_type` 与 `content` 内 JSON 的 `t` 必须一致。正文用 `json.Marshal` 得到的 `[]byte` 写入 BLOB,不要把 Go `string` 当列类型。图片/语音/GIF/文件只存对象存储 key。完整 COMMENT 见 `migrations/0001_init.sql`。 `message_type` 与 `content` 内 JSON 的 `t` 必须一致。正文用 `json.Marshal` 得到的 `[]byte` 写入 BLOB,不要把 Go `string` 当列类型。图片/语音/GIF/文件只存对象存储 key。完整 COMMENT 见 `migrations/0001_init.sql`。
```sql ```sql
SELECT * FROM messages SELECT * FROM t_messages
WHERE conversation_id = ? AND id > ? WHERE conversation_id = ? AND id > ?
ORDER BY id ASC LIMIT 200; ORDER BY id ASC LIMIT 200;
``` ```
@ -431,7 +431,7 @@ SELECT * FROM messages
## 10.2 conversations / P2P 唯一 / members ## 10.2 conversations / P2P 唯一 / members
```sql ```sql
CREATE TABLE conversations ( CREATE TABLE t_conversations (
id BIGINT PRIMARY KEY, id BIGINT PRIMARY KEY,
type TINYINT NOT NULL, -- 1=P2P 2=GROUP type TINYINT NOT NULL, -- 1=P2P 2=GROUP
last_msg_id BIGINT NOT NULL DEFAULT 0, last_msg_id BIGINT NOT NULL DEFAULT 0,
@ -440,7 +440,7 @@ CREATE TABLE conversations (
); );
-- 两人永远同一条会话,学 BFF 小 uid : 大 uid -- 两人永远同一条会话,学 BFF 小 uid : 大 uid
CREATE TABLE p2p_conversations ( CREATE TABLE t_p2p_conversations (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_small BIGINT NOT NULL, user_small BIGINT NOT NULL,
user_big BIGINT NOT NULL, user_big BIGINT NOT NULL,
@ -449,7 +449,7 @@ CREATE TABLE p2p_conversations (
UNIQUE KEY uk_conversation (conversation_id) UNIQUE KEY uk_conversation (conversation_id)
); );
CREATE TABLE conversation_members ( CREATE TABLE t_conversation_members (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
conversation_id BIGINT NOT NULL, conversation_id BIGINT NOT NULL,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
@ -469,7 +469,7 @@ CREATE TABLE conversation_members (
## 10.3 mailbox_events(失败邮箱 + 控制事件) ## 10.3 mailbox_events(失败邮箱 + 控制事件)
```sql ```sql
CREATE TABLE mailbox_events ( CREATE TABLE t_mailbox_events (
id BIGINT PRIMARY KEY, id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
device_id BIGINT NOT NULL, -- 0 = 该用户所有设备 device_id BIGINT NOT NULL, -- 0 = 该用户所有设备
@ -482,7 +482,7 @@ CREATE TABLE mailbox_events (
KEY idx_user_device (user_id, device_id, seq) KEY idx_user_device (user_id, device_id, seq)
); );
CREATE TABLE device_sync_cursors ( CREATE TABLE t_device_sync_cursors (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
device_id BIGINT NOT NULL, device_id BIGINT NOT NULL,
@ -817,7 +817,7 @@ Client TLS :443
Nginx SNI im.example.com + PROXY Nginx SNI im.example.com + PROXY
gnet 读真实 IP → TLS → Frame gnet 读真实 IP → TLS → Frame
TaskPool TaskPool
权限 → INSERT messages → RPUSH 可靠队列 → SEND_ACK 权限 → INSERT t_messages → RPUSH 可靠队列 → SEND_ACK
Deliver Worker Deliver Worker
幂等 → ZSET 列表 → 在线 MESSAGE 幂等 → ZSET 列表 → 在线 MESSAGE
失败 → mailbox + 可选系统推送 失败 → mailbox + 可选系统推送

View File

@ -65,7 +65,7 @@ device_id = uint64(SHA256(fingerprint_hash)[0:8]),非 0
## 表:`devices` ## 表:`devices`
```sql ```sql
CREATE TABLE devices ( CREATE TABLE t_devices (
device_id BIGINT UNSIGNED NOT NULL COMMENT 'JWT did;多 App 同机共用', device_id BIGINT UNSIGNED NOT NULL COMMENT 'JWT did;多 App 同机共用',
fingerprint_hash CHAR(64) NOT NULL COMMENT '物理指纹,UNIQUE,不含 app_id', fingerprint_hash CHAR(64) NOT NULL COMMENT '物理指纹,UNIQUE,不含 app_id',
device_type TINYINT UNSIGNED NOT NULL COMMENT 'device_type 枚举', device_type TINYINT UNSIGNED NOT NULL COMMENT 'device_type 枚举',
@ -85,10 +85,10 @@ CREATE TABLE devices (
## 表:`user_devices`(无 `app_id`) ## 表:`user_devices`(无 `app_id`)
```sql ```sql
CREATE TABLE user_devices ( CREATE TABLE t_user_devices (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增', id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增',
user_id BIGINT NOT NULL COMMENT '用户 uid', user_id BIGINT NOT NULL COMMENT '用户 uid',
device_id BIGINT UNSIGNED NOT NULL COMMENT 'devices.device_id', device_id BIGINT UNSIGNED NOT NULL COMMENT 't_devices.device_id',
last_login_at DATETIME(3) NOT NULL COMMENT '该用户在此设备最近登录成功', last_login_at DATETIME(3) NOT NULL COMMENT '该用户在此设备最近登录成功',
last_active_at DATETIME(3) NULL COMMENT '该用户在此设备最近活跃', last_active_at DATETIME(3) NULL COMMENT '该用户在此设备最近活跃',
created_at DATETIME(3) NOT NULL COMMENT '该用户首次在此设备绑定', created_at DATETIME(3) NOT NULL COMMENT '该用户首次在此设备绑定',
@ -110,7 +110,7 @@ CREATE TABLE user_devices (
## 表:`user_login_logs` ## 表:`user_login_logs`
```sql ```sql
ALTER TABLE user_login_logs ALTER TABLE t_user_login_logs
ADD COLUMN app_id TINYINT UNSIGNED NOT NULL DEFAULT 0 ADD COLUMN app_id TINYINT UNSIGNED NOT NULL DEFAULT 0
COMMENT '当次登录 App,见 meta app_id' AFTER device_id; COMMENT '当次登录 App,见 meta app_id' AFTER device_id;
``` ```
@ -169,9 +169,9 @@ ALTER TABLE user_login_logs
1. 校验 Body **`app_id`**、OTP 等。 1. 校验 Body **`app_id`**、OTP 等。
2. **email** 查/建用户。 2. **email** 查/建用户。
3. 指纹 → **`device_id`** → UPSERT **`devices`**。 3. 指纹 → **`device_id`** → UPSERT **`t_devices`**。
4. UPSERT **`user_devices(user_id, device_id)`**。 4. UPSERT **`t_user_devices(user_id, device_id)`**。
5. INSERT **`user_login_logs`**(含当次 **`app_id`**)。 5. INSERT **`t_user_login_logs`**(含当次 **`app_id`**)。
6. **`Issue(user_id, device_id, app_id)`**(Body `app_id` → JWT `aid`;PairStore 按 **`pair_key_mode`**)。 6. **`Issue(user_id, device_id, app_id)`**(Body `app_id` → JWT `aid`;PairStore 按 **`pair_key_mode`**)。
--- ---

View File

@ -27,9 +27,9 @@ func (m *Repo) ListComplaints(ctx context.Context, limit int) ([]ComplaintRow, e
err = db.Raw(` err = db.Raw(`
SELECT r.id, r.user_id, r.target_id, r.reason, r.detail, r.created_at, SELECT r.id, r.user_id, r.target_id, r.reason, r.detail, r.created_at,
COALESCE(e.email,'') AS email, COALESCE(te.email,'') AS target_email COALESCE(e.email,'') AS email, COALESCE(te.email,'') AS target_email
FROM friend_reports r FROM t_friend_reports r
LEFT JOIN user_emails e ON e.user_id = r.user_id LEFT JOIN t_user_emails e ON e.user_id = r.user_id
LEFT JOIN user_emails te ON te.user_id = r.target_id LEFT JOIN t_user_emails te ON te.user_id = r.target_id
ORDER BY r.id DESC ORDER BY r.id DESC
LIMIT ?`, limit).Scan(&rows).Error LIMIT ?`, limit).Scan(&rows).Error
if err != nil { if err != nil {
@ -53,6 +53,6 @@ func (m *Repo) InsertComplaint(ctx context.Context, r *friendentity.Report) erro
if err != nil { if err != nil {
return err return err
} }
return mapDBErr(db.Exec(`INSERT INTO friend_reports (id, user_id, target_id, reason, detail, created_at) VALUES (?,?,?,?,?,?)`, return mapDBErr(db.Exec(`INSERT INTO t_friend_reports (id, user_id, target_id, reason, detail, created_at) VALUES (?,?,?,?,?,?)`,
r.ID, r.UserID, r.TargetID, int(r.Reason), r.Detail, r.CreatedAt).Error) r.ID, r.UserID, r.TargetID, int(r.Reason), r.Detail, r.CreatedAt).Error)
} }

View File

@ -36,7 +36,7 @@ func (m *Repo) ListGroups(ctx context.Context, q string) ([]GroupRow, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
query := db.Table("`groups`").Where("status = ?", int(groupentity.StatusActive)) query := db.Table("`t_groups`").Where("status = ?", int(groupentity.StatusActive))
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
if q != "" { if q != "" {
like := "%" + q + "%" like := "%" + q + "%"
@ -63,7 +63,7 @@ func (m *Repo) GroupByID(ctx context.Context, groupID int64) (*GroupRow, error)
return nil, err return nil, err
} }
var r groupScanRow var r groupScanRow
if err := db.Table("`groups`").Where("id = ?", groupID).Take(&r).Error; err != nil { if err := db.Table("`t_groups`").Where("id = ?", groupID).Take(&r).Error; err != nil {
return nil, mapDBErr(err) return nil, mapDBErr(err)
} }
row := groupScanToRow(r) row := groupScanToRow(r)
@ -78,7 +78,7 @@ func (m *Repo) ActiveGroupOwner(ctx context.Context, groupID int64) (int64, erro
var r struct { var r struct {
OwnerID int64 `gorm:"column:owner_id"` OwnerID int64 `gorm:"column:owner_id"`
} }
err = db.Table("`groups`").Select("owner_id"). err = db.Table("`t_groups`").Select("owner_id").
Where("id = ? AND status = ?", groupID, int(groupentity.StatusActive)). Where("id = ? AND status = ?", groupID, int(groupentity.StatusActive)).
Take(&r).Error Take(&r).Error
if err != nil { if err != nil {
@ -105,9 +105,9 @@ func (m *Repo) ListGroupMembers(ctx context.Context, groupID int64) ([]GroupMemb
var rows []scanRow var rows []scanRow
err = db.Raw(` err = db.Raw(`
SELECT m.user_id, m.role, m.nickname, COALESCE(e.email,'') AS email, COALESCE(u.nickname,'') AS u_nickname SELECT m.user_id, m.role, m.nickname, COALESCE(e.email,'') AS email, COALESCE(u.nickname,'') AS u_nickname
FROM group_members m FROM t_group_members m
LEFT JOIN users u ON u.id = m.user_id LEFT JOIN t_users u ON u.id = m.user_id
LEFT JOIN user_emails e ON e.user_id = m.user_id LEFT JOIN t_user_emails e ON e.user_id = m.user_id
WHERE m.group_id = ? WHERE m.group_id = ?
ORDER BY m.role ASC, m.joined_at ASC ORDER BY m.role ASC, m.joined_at ASC
LIMIT 500`, groupID).Scan(&rows).Error LIMIT 500`, groupID).Scan(&rows).Error

View File

@ -15,14 +15,14 @@ func (m *Repo) ImStats(ctx context.Context) (*adminentity.ImStats, error) {
} }
out := &adminentity.ImStats{} out := &adminentity.ImStats{}
var priv, grp, revokedPriv, revokedGrp int64 var priv, grp, revokedPriv, revokedGrp int64
_ = db.Table("private_message").Count(&priv).Error _ = db.Table("t_private_message").Count(&priv).Error
_ = db.Table("group_message").Count(&grp).Error _ = db.Table("t_group_message").Count(&grp).Error
_ = db.Table("private_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedPriv).Error _ = db.Table("t_private_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedPriv).Error
_ = db.Table("group_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedGrp).Error _ = db.Table("t_group_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedGrp).Error
out.MessageCount = priv + grp out.MessageCount = priv + grp
out.RevokedCount = revokedPriv + revokedGrp out.RevokedCount = revokedPriv + revokedGrp
_ = db.Table("conversations").Count(&out.ConversationCount).Error _ = db.Table("t_conversations").Count(&out.ConversationCount).Error
_ = db.Table("`groups`").Where("status = ?", 1).Count(&out.GroupCount).Error _ = db.Table("`t_groups`").Where("status = ?", 1).Count(&out.GroupCount).Error
return out, nil return out, nil
} }
@ -33,10 +33,10 @@ func (m *Repo) ListRecentMessages(ctx context.Context, fetchLimit int) ([]Messag
} }
sql := ` sql := `
SELECT id, conversation_id, sender_id, receiver_id, 0 AS group_id, client_msg_id, message_type, content, status, created_at, 'private' AS chat_type SELECT id, conversation_id, sender_id, receiver_id, 0 AS group_id, client_msg_id, message_type, content, status, created_at, 'private' AS chat_type
FROM private_message FROM t_private_message
UNION ALL UNION ALL
SELECT id, conversation_id, sender_id, 0 AS receiver_id, group_id, client_msg_id, message_type, content, status, created_at, 'group' AS chat_type SELECT id, conversation_id, sender_id, 0 AS receiver_id, group_id, client_msg_id, message_type, content, status, created_at, 'group' AS chat_type
FROM group_message` FROM t_group_message`
type scanRow struct { type scanRow struct {
ID int64 `gorm:"column:id"` ID int64 `gorm:"column:id"`
ConversationID int64 `gorm:"column:conversation_id"` ConversationID int64 `gorm:"column:conversation_id"`
@ -87,10 +87,10 @@ func (m *Repo) ListUserConversations(ctx context.Context, userID int64) ([]Conve
SELECT c.id, c.type, c.last_msg_id, c.last_msg_at, SELECT c.id, c.type, c.last_msg_id, c.last_msg_at,
COALESCE(p.user_small, 0) AS user_small, COALESCE(p.user_big, 0) AS user_big, COALESCE(p.user_small, 0) AS user_small, COALESCE(p.user_big, 0) AS user_big,
COALESCE(g.id, 0) AS group_id, COALESCE(g.name, '') AS group_name COALESCE(g.id, 0) AS group_id, COALESCE(g.name, '') AS group_name
FROM conversation_members cm FROM t_conversation_members cm
INNER JOIN conversations c ON c.id = cm.conversation_id INNER JOIN t_conversations c ON c.id = cm.conversation_id
LEFT JOIN p2p_conversations p ON p.conversation_id = c.id LEFT JOIN t_p2p_conversations p ON p.conversation_id = c.id
LEFT JOIN `+"`groups`"+` g ON g.conversation_id = c.id LEFT JOIN `+"`t_groups`"+` g ON g.conversation_id = c.id
WHERE cm.user_id = ? WHERE cm.user_id = ?
ORDER BY c.last_msg_at DESC ORDER BY c.last_msg_at DESC
LIMIT 200`, userID).Scan(&rows).Error LIMIT 200`, userID).Scan(&rows).Error
@ -127,7 +127,7 @@ func (m *Repo) ListGroupMessages(ctx context.Context, groupID int64, limit int)
var rows []scanRow var rows []scanRow
err = db.Raw(` err = db.Raw(`
SELECT id, conversation_id, sender_id, group_id, client_msg_id, message_type, content, status, created_at, 'group' AS chat_type SELECT id, conversation_id, sender_id, group_id, client_msg_id, message_type, content, status, created_at, 'group' AS chat_type
FROM group_message WHERE group_id = ? ORDER BY created_at DESC LIMIT ?`, groupID, limit).Scan(&rows).Error FROM t_group_message WHERE group_id = ? ORDER BY created_at DESC LIMIT ?`, groupID, limit).Scan(&rows).Error
if err != nil { if err != nil {
return nil, mapDBErr(err) return nil, mapDBErr(err)
} }

View File

@ -14,7 +14,7 @@ func (m *Repo) ListUsers(ctx context.Context, emailLike string, page, size int)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
base := db.Table("users AS u").Joins("LEFT JOIN user_emails e ON e.user_id = u.id") base := db.Table("t_users AS u").Joins("LEFT JOIN t_user_emails e ON e.user_id = u.id")
emailLike = strings.TrimSpace(emailLike) emailLike = strings.TrimSpace(emailLike)
if emailLike != "" { if emailLike != "" {
base = base.Where("e.email LIKE ?", "%"+emailLike+"%") base = base.Where("e.email LIKE ?", "%"+emailLike+"%")
@ -30,8 +30,8 @@ func (m *Repo) ListUsers(ctx context.Context, emailLike string, page, size int)
const userListSelect = `u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at, const userListSelect = `u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, COALESCE(e.email,'') AS email,
COALESCE((SELECT d.device_id FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id, COALESCE((SELECT d.device_id FROM t_user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id,
COALESCE((SELECT d.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform` COALESCE((SELECT CAST(dev.device_type AS CHAR) FROM t_user_devices d INNER JOIN t_devices dev ON dev.device_id = d.device_id WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform`
func (m *Repo) UsersByEmail(ctx context.Context, email string) ([]UserRow, error) { func (m *Repo) UsersByEmail(ctx context.Context, email string) ([]UserRow, error) {
db, err := m.session(ctx) db, err := m.session(ctx)
@ -42,10 +42,10 @@ func (m *Repo) UsersByEmail(ctx context.Context, email string) ([]UserRow, error
err = db.Raw(` err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at, SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
e.email, e.email,
COALESCE((SELECT d.device_id FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id, COALESCE((SELECT d.device_id FROM t_user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id,
COALESCE((SELECT d.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform COALESCE((SELECT CAST(dev.device_type AS CHAR) FROM t_user_devices d INNER JOIN t_devices dev ON dev.device_id = d.device_id WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform
FROM user_emails e FROM t_user_emails e
INNER JOIN users u ON u.id = e.user_id INNER JOIN t_users u ON u.id = e.user_id
WHERE e.email = ?`, email).Scan(&rows).Error WHERE e.email = ?`, email).Scan(&rows).Error
if err != nil { if err != nil {
return nil, mapDBErr(err) return nil, mapDBErr(err)
@ -62,10 +62,10 @@ func (m *Repo) UserByID(ctx context.Context, id int64) (*UserRow, error) {
err = db.Raw(` err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at, SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, COALESCE(e.email,'') AS email,
COALESCE((SELECT d.device_id FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id, COALESCE((SELECT d.device_id FROM t_user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id,
COALESCE((SELECT d.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform COALESCE((SELECT CAST(dev.device_type AS CHAR) FROM t_user_devices d INNER JOIN t_devices dev ON dev.device_id = d.device_id WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform
FROM users u FROM t_users u
LEFT JOIN user_emails e ON e.user_id = u.id LEFT JOIN t_user_emails e ON e.user_id = u.id
WHERE u.id = ?`, id).Scan(&rows).Error WHERE u.id = ?`, id).Scan(&rows).Error
if err != nil { if err != nil {
return nil, mapDBErr(err) return nil, mapDBErr(err)
@ -86,8 +86,8 @@ func (m *Repo) UsersByNickname(ctx context.Context, nickname string) ([]UserRow,
err = db.Raw(` err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at, SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, 0 AS device_id, '' AS platform COALESCE(e.email,'') AS email, 0 AS device_id, '' AS platform
FROM users u FROM t_users u
LEFT JOIN user_emails e ON e.user_id = u.id LEFT JOIN t_user_emails e ON e.user_id = u.id
WHERE u.nickname LIKE ? WHERE u.nickname LIKE ?
ORDER BY u.id DESC LIMIT 50`, "%"+strings.TrimSpace(nickname)+"%").Scan(&rows).Error ORDER BY u.id DESC LIMIT 50`, "%"+strings.TrimSpace(nickname)+"%").Scan(&rows).Error
if err != nil { if err != nil {
@ -104,10 +104,11 @@ func (m *Repo) UsersByDevice(ctx context.Context, deviceID string, deviceIDNum u
var rows []userScanRow var rows []userScanRow
err = db.Raw(` err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at, SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, d.device_id, d.platform COALESCE(e.email,'') AS email, d.device_id, CAST(dev.device_type AS CHAR) AS platform
FROM user_devices d FROM t_user_devices d
INNER JOIN users u ON u.id = d.user_id INNER JOIN t_devices dev ON dev.device_id = d.device_id
LEFT JOIN user_emails e ON e.user_id = u.id INNER JOIN t_users u ON u.id = d.user_id
LEFT JOIN t_user_emails e ON e.user_id = u.id
WHERE CAST(d.device_id AS CHAR) = ? OR d.device_id = ? WHERE CAST(d.device_id AS CHAR) = ? OR d.device_id = ?
ORDER BY d.last_login_at DESC LIMIT 50`, deviceID, deviceIDNum).Scan(&rows).Error ORDER BY d.last_login_at DESC LIMIT 50`, deviceID, deviceIDNum).Scan(&rows).Error
if err != nil { if err != nil {
@ -124,11 +125,12 @@ func (m *Repo) UsersByIP(ctx context.Context, ip string) ([]UserRow, error) {
var rows []userScanRow var rows []userScanRow
err = db.Raw(` err = db.Raw(`
SELECT DISTINCT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at, SELECT DISTINCT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, d.device_id, d.platform COALESCE(e.email,'') AS email, d.device_id, CAST(dev.device_type AS CHAR) AS platform
FROM user_devices d FROM t_user_devices d
INNER JOIN users u ON u.id = d.user_id INNER JOIN t_devices dev ON dev.device_id = d.device_id
LEFT JOIN user_emails e ON e.user_id = u.id INNER JOIN t_users u ON u.id = d.user_id
WHERE d.last_ip = ? LEFT JOIN t_user_emails e ON e.user_id = u.id
WHERE dev.last_client_ip = ?
ORDER BY u.id DESC LIMIT 100`, ip).Scan(&rows).Error ORDER BY u.id DESC LIMIT 100`, ip).Scan(&rows).Error
if err != nil { if err != nil {
return nil, mapDBErr(err) return nil, mapDBErr(err)
@ -142,8 +144,8 @@ func (m *Repo) SetUserStatusByEmail(ctx context.Context, email string, status us
return err return err
} }
res := db.Exec(` res := db.Exec(`
UPDATE users u UPDATE t_users u
INNER JOIN user_emails e ON e.user_id = u.id INNER JOIN t_user_emails e ON e.user_id = u.id
SET u.status = ?, u.updated_at = ? SET u.status = ?, u.updated_at = ?
WHERE e.email = ?`, int(status), time.Now(), email) WHERE e.email = ?`, int(status), time.Now(), email)
if res.Error != nil { if res.Error != nil {
@ -161,8 +163,8 @@ func (m *Repo) SetNicknameByEmail(ctx context.Context, email, nickname string) e
return err return err
} }
res := db.Exec(` res := db.Exec(`
UPDATE users u UPDATE t_users u
INNER JOIN user_emails e ON e.user_id = u.id INNER JOIN t_user_emails e ON e.user_id = u.id
SET u.nickname = ?, u.updated_at = ? SET u.nickname = ?, u.updated_at = ?
WHERE e.email = ?`, nickname, time.Now(), email) WHERE e.email = ?`, nickname, time.Now(), email)
if res.Error != nil { if res.Error != nil {
@ -180,8 +182,8 @@ func (m *Repo) ClearAvatarByEmail(ctx context.Context, email string) error {
return err return err
} }
res := db.Exec(` res := db.Exec(`
UPDATE users u UPDATE t_users u
INNER JOIN user_emails e ON e.user_id = u.id INNER JOIN t_user_emails e ON e.user_id = u.id
SET u.avatar = '', u.updated_at = ? SET u.avatar = '', u.updated_at = ?
WHERE e.email = ?`, time.Now(), email) WHERE e.email = ?`, time.Now(), email)
if res.Error != nil { if res.Error != nil {
@ -210,10 +212,11 @@ func (m *Repo) SameIPRank(ctx context.Context, limit int) ([]SameIPRankRow, erro
DeviceCount int64 `gorm:"column:device_count"` DeviceCount int64 `gorm:"column:device_count"`
} }
err = db.Raw(` err = db.Raw(`
SELECT last_ip AS ip, COUNT(DISTINCT user_id) AS user_count, COUNT(*) AS device_count SELECT dev.last_client_ip AS ip, COUNT(DISTINCT d.user_id) AS user_count, COUNT(*) AS device_count
FROM user_devices FROM t_user_devices d
WHERE last_ip IS NOT NULL AND last_ip <> '' AND last_ip NOT LIKE '127.%' AND last_ip <> '::1' INNER JOIN t_devices dev ON dev.device_id = d.device_id
GROUP BY last_ip WHERE dev.last_client_ip IS NOT NULL AND dev.last_client_ip <> '' AND dev.last_client_ip NOT LIKE '127.%' AND dev.last_client_ip <> '::1'
GROUP BY dev.last_client_ip
ORDER BY user_count DESC ORDER BY user_count DESC
LIMIT ?`, limit).Scan(&rows).Error LIMIT ?`, limit).Scan(&rows).Error
if err != nil { if err != nil {
@ -232,7 +235,7 @@ func (m *Repo) LookupUserIDByEmail(ctx context.Context, email string) (int64, er
return 0, err return 0, err
} }
var id int64 var id int64
err = db.Table("user_emails").Select("user_id").Where("email = ?", email).Scan(&id).Error err = db.Table("t_user_emails").Select("user_id").Where("email = ?", email).Scan(&id).Error
return id, mapDBErr(err) return id, mapDBErr(err)
} }

View File

@ -12,4 +12,4 @@ type Conversation struct {
CreatedAt time.Time `gorm:"column:created_at;comment:会话创建时间"` CreatedAt time.Time `gorm:"column:created_at;comment:会话创建时间"`
} }
func (Conversation) TableName() string { return "conversations" } func (Conversation) TableName() string { return "t_conversations" }

View File

@ -18,7 +18,7 @@ type ConversationMember struct {
Muted int `gorm:"column:muted;comment:0=未静音 1=免打扰"` Muted int `gorm:"column:muted;comment:0=未静音 1=免打扰"`
} }
func (ConversationMember) TableName() string { return "conversation_members" } func (ConversationMember) TableName() string { return "t_conversation_members" }
func (r *ConversationMember) entity() *entity.Member { func (r *ConversationMember) entity() *entity.Member {
if r == nil { if r == nil {

View File

@ -8,4 +8,4 @@ type P2PConversation struct {
ConversationID int64 `gorm:"column:conversation_id;comment:对应 conversations.id"` ConversationID int64 `gorm:"column:conversation_id;comment:对应 conversations.id"`
} }
func (P2PConversation) TableName() string { return "p2p_conversations" } func (P2PConversation) TableName() string { return "t_p2p_conversations" }

View File

@ -25,7 +25,7 @@ type DeviceRow struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:最近 touch"` UpdatedAt time.Time `gorm:"column:updated_at;comment:最近 touch"`
} }
func (DeviceRow) TableName() string { return "devices" } func (DeviceRow) TableName() string { return "t_devices" }
type RegistryRepo struct { type RegistryRepo struct {
gdb *gorm.DB gdb *gorm.DB

View File

@ -17,7 +17,7 @@ type Favorite struct {
CreatedAt time.Time `gorm:"column:created_at;comment:收藏时间"` CreatedAt time.Time `gorm:"column:created_at;comment:收藏时间"`
} }
func (Favorite) TableName() string { return "favorites" } func (Favorite) TableName() string { return "t_favorites" }
func (r *Favorite) Entity() *entity.Favorite { func (r *Favorite) Entity() *entity.Favorite {
if r == nil { if r == nil {

View File

@ -25,7 +25,7 @@ type FriendRelation struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:最近更新时间"` UpdatedAt time.Time `gorm:"column:updated_at;comment:最近更新时间"`
} }
func (FriendRelation) TableName() string { return "friend_relations" } func (FriendRelation) TableName() string { return "t_friend_relations" }
func (r *FriendRelation) entity() *entity.Relation { func (r *FriendRelation) entity() *entity.Relation {
if r == nil { if r == nil {

View File

@ -18,7 +18,7 @@ type FriendReport struct {
CreatedAt time.Time `gorm:"column:created_at;comment:投诉时间"` CreatedAt time.Time `gorm:"column:created_at;comment:投诉时间"`
} }
func (FriendReport) TableName() string { return "friend_reports" } func (FriendReport) TableName() string { return "t_friend_reports" }
func (m *Repo) CreateReport(ctx context.Context, r *entity.Report) error { func (m *Repo) CreateReport(ctx context.Context, r *entity.Report) error {
if r == nil || r.UserID <= 0 || r.TargetID <= 0 { if r == nil || r.UserID <= 0 || r.TargetID <= 0 {

View File

@ -24,7 +24,7 @@ type FriendRequest struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:最近一次申请或处理时间"` UpdatedAt time.Time `gorm:"column:updated_at;comment:最近一次申请或处理时间"`
} }
func (FriendRequest) TableName() string { return "friend_requests" } func (FriendRequest) TableName() string { return "t_friend_requests" }
func (r *FriendRequest) entity() *entity.Request { func (r *FriendRequest) entity() *entity.Request {
if r == nil { if r == nil {

View File

@ -17,7 +17,7 @@ func (m *Repo) FindByOwnerAndName(ctx context.Context, ownerID int64, name strin
return nil, err return nil, err
} }
var row Group var row Group
err = db.Table("`groups`"). err = db.Table("`t_groups`").
Where("owner_id = ? AND name = ? AND status = ?", ownerID, name, entity.StatusActive). Where("owner_id = ? AND name = ? AND status = ?", ownerID, name, entity.StatusActive).
Order("id ASC").Limit(1).Take(&row).Error Order("id ASC").Limit(1).Take(&row).Error
if err != nil { if err != nil {

View File

@ -8,10 +8,10 @@ import (
"spark-server/internal/pkg/errs" "spark-server/internal/pkg/errs"
) )
// Group maps table groups. // Group maps table t_groups.
type Group struct { type Group struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:群号,表内自增,对外群 ID"` ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:群号,表内自增,对外群 ID"`
ConversationID int64 `gorm:"column:conversation_id;uniqueIndex;comment:对应 conversations.id(Snowflake),发消息与同步用"` ConversationID int64 `gorm:"column:conversation_id;uniqueIndex;comment:对应 t_conversations.id(Snowflake),发消息与同步用"`
Name string `gorm:"column:name;comment:群名称"` Name string `gorm:"column:name;comment:群名称"`
Avatar string `gorm:"column:avatar;comment:群头像对象存储 key,空表示未设置"` Avatar string `gorm:"column:avatar;comment:群头像对象存储 key,空表示未设置"`
Announcement string `gorm:"column:announcement;comment:群公告,仅群主/管理员可改"` Announcement string `gorm:"column:announcement;comment:群公告,仅群主/管理员可改"`
@ -23,7 +23,7 @@ type Group struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:资料或成员变更时间"` UpdatedAt time.Time `gorm:"column:updated_at;comment:资料或成员变更时间"`
} }
func (Group) TableName() string { return "groups" } func (Group) TableName() string { return "t_groups" }
func (r *Group) entity() *entity.Group { func (r *Group) entity() *entity.Group {
if r == nil { if r == nil {
@ -98,8 +98,8 @@ func (m *Repo) ListByUser(ctx context.Context, userID int64, limit int) ([]*enti
} }
limit = entity.ClampLimit(limit) limit = entity.ClampLimit(limit)
var rows []Group var rows []Group
err = db.Table("`groups` AS g").Select("g.*"). err = db.Table("`t_groups` AS g").Select("g.*").
Joins("INNER JOIN group_members m ON m.group_id = g.id"). Joins("INNER JOIN t_group_members m ON m.group_id = g.id").
Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive). Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive).
Order("g.updated_at DESC").Limit(limit).Find(&rows).Error Order("g.updated_at DESC").Limit(limit).Find(&rows).Error
if err != nil { if err != nil {
@ -123,8 +123,8 @@ func (m *Repo) ListAllActiveByUser(ctx context.Context, userID int64) ([]*entity
return nil, err return nil, err
} }
var rows []Group var rows []Group
err = db.Table("`groups` AS g").Select("g.*"). err = db.Table("`t_groups` AS g").Select("g.*").
Joins("INNER JOIN group_members m ON m.group_id = g.id"). Joins("INNER JOIN t_group_members m ON m.group_id = g.id").
Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive). Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive).
Order("g.updated_at DESC").Limit(purgeListCap).Find(&rows).Error Order("g.updated_at DESC").Limit(purgeListCap).Find(&rows).Error
if err != nil { if err != nil {
@ -166,7 +166,7 @@ func (m *Repo) UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS in
if err != nil { if err != nil {
return err return err
} }
res := db.Table("conversations").Where("id = ?", conversationID).Updates(map[string]any{ res := db.Table("t_conversations").Where("id = ?", conversationID).Updates(map[string]any{
"last_msg_id": msgID, "last_msg_id": msgID,
"last_msg_at": msTime(atMS), "last_msg_at": msTime(atMS),
}) })

View File

@ -20,7 +20,7 @@ type GroupMember struct {
JoinedAt time.Time `gorm:"column:joined_at;comment:入群时间"` JoinedAt time.Time `gorm:"column:joined_at;comment:入群时间"`
} }
func (GroupMember) TableName() string { return "group_members" } func (GroupMember) TableName() string { return "t_group_members" }
func (r *GroupMember) entity() *entity.Member { func (r *GroupMember) entity() *entity.Member {
if r == nil { if r == nil {

View File

@ -11,4 +11,4 @@ type MessageDelete struct {
CreatedAt time.Time `gorm:"column:created_at;comment:删除时间"` CreatedAt time.Time `gorm:"column:created_at;comment:删除时间"`
} }
func (MessageDelete) TableName() string { return "message_deletes" } func (MessageDelete) TableName() string { return "t_message_deletes" }

View File

@ -13,7 +13,7 @@ import (
"spark-server/internal/pkg/errs" "spark-server/internal/pkg/errs"
) )
// Repo is MySQL persistence for private_message and group_message. // Repo is MySQL persistence for t_private_message and t_group_message.
type Repo struct { type Repo struct {
gdb *gorm.DB gdb *gorm.DB
} }
@ -45,7 +45,7 @@ func mapDBErr(err error) error {
func (m *Repo) lookupKind(db *gorm.DB, conversationID int64) (entity.Kind, error) { func (m *Repo) lookupKind(db *gorm.DB, conversationID int64) (entity.Kind, error) {
var row struct{ Type int } var row struct{ Type int }
if err := db.Table("conversations").Select("type").Where("id = ?", conversationID).Take(&row).Error; err != nil { if err := db.Table("t_conversations").Select("type").Where("id = ?", conversationID).Take(&row).Error; err != nil {
return 0, mapDBErr(err) return 0, mapDBErr(err)
} }
if conventity.Type(row.Type) == conventity.TypeGroup { if conventity.Type(row.Type) == conventity.TypeGroup {
@ -154,7 +154,7 @@ func (m *Repo) CreateAndBump(ctx context.Context, message *entity.Message) error
if err := m.insertBody(tx, message, row); err != nil { if err := m.insertBody(tx, message, row); err != nil {
return err return err
} }
res := tx.Table("conversations").Where("id = ?", message.ConversationID).Updates(map[string]any{ res := tx.Table("t_conversations").Where("id = ?", message.ConversationID).Updates(map[string]any{
"last_msg_id": message.ID, "last_msg_id": message.ID,
"last_msg_at": row.CreatedAt, "last_msg_at": row.CreatedAt,
}) })
@ -197,7 +197,7 @@ func (m *Repo) fillGroupID(db *gorm.DB, message *entity.Message) error {
return nil return nil
} }
var gid int64 var gid int64
if err := db.Table("`groups`").Select("id").Where("conversation_id = ?", message.ConversationID).Scan(&gid).Error; err != nil { if err := db.Table("`t_groups`").Select("id").Where("conversation_id = ?", message.ConversationID).Scan(&gid).Error; err != nil {
return mapDBErr(err) return mapDBErr(err)
} }
if gid <= 0 { if gid <= 0 {
@ -212,7 +212,7 @@ func (m *Repo) fillReceiverID(db *gorm.DB, message *entity.Message) error {
return nil return nil
} }
var peer int64 var peer int64
err := db.Table("conversation_members").Select("user_id"). err := db.Table("t_conversation_members").Select("user_id").
Where("conversation_id = ? AND user_id <> ?", message.ConversationID, message.SenderID). Where("conversation_id = ? AND user_id <> ?", message.ConversationID, message.SenderID).
Limit(1).Scan(&peer).Error Limit(1).Scan(&peer).Error
if err != nil { if err != nil {

View File

@ -7,8 +7,8 @@ import (
) )
const ( const (
tablePrivateMessage = "private_message" tablePrivateMessage = "t_private_message"
tableGroupMessage = "group_message" tableGroupMessage = "t_group_message"
) )
// MessageRow is shared columns for private_message and group_message. Must be exported so GORM can embed it. // MessageRow is shared columns for private_message and group_message. Must be exported so GORM can embed it.

View File

@ -66,16 +66,16 @@ func purgeMomentUser(tx *gorm.DB, userID int64) error {
} }
func bumpOffUserLikes(tx *gorm.DB, userID int64) error { func bumpOffUserLikes(tx *gorm.DB, userID int64) error {
sql := `UPDATE moment_posts p sql := `UPDATE t_moment_posts p
INNER JOIN (SELECT post_id, COUNT(*) AS n FROM moment_likes WHERE user_id = ? GROUP BY post_id) x ON p.id = x.post_id INNER JOIN (SELECT post_id, COUNT(*) AS n FROM t_moment_likes WHERE user_id = ? GROUP BY post_id) x ON p.id = x.post_id
SET p.like_count = CASE WHEN p.like_count < x.n THEN 0 ELSE p.like_count - x.n END SET p.like_count = CASE WHEN p.like_count < x.n THEN 0 ELSE p.like_count - x.n END
WHERE p.user_id <> ?` WHERE p.user_id <> ?`
return mapDBErr(tx.Exec(sql, userID, userID).Error) return mapDBErr(tx.Exec(sql, userID, userID).Error)
} }
func bumpOffUserComments(tx *gorm.DB, userID int64) error { func bumpOffUserComments(tx *gorm.DB, userID int64) error {
sql := `UPDATE moment_posts p sql := `UPDATE t_moment_posts p
INNER JOIN (SELECT post_id, COUNT(*) AS n FROM moment_comments WHERE user_id = ? AND status = ? GROUP BY post_id) x ON p.id = x.post_id INNER JOIN (SELECT post_id, COUNT(*) AS n FROM t_moment_comments WHERE user_id = ? AND status = ? GROUP BY post_id) x ON p.id = x.post_id
SET p.comment_count = CASE WHEN p.comment_count < x.n THEN 0 ELSE p.comment_count - x.n END SET p.comment_count = CASE WHEN p.comment_count < x.n THEN 0 ELSE p.comment_count - x.n END
WHERE p.user_id <> ?` WHERE p.user_id <> ?`
return mapDBErr(tx.Exec(sql, userID, entity.StatusNormal, userID).Error) return mapDBErr(tx.Exec(sql, userID, entity.StatusNormal, userID).Error)

View File

@ -24,7 +24,7 @@ type Post struct {
CreatedAt time.Time `gorm:"column:created_at;comment:发帖时间,visible_days 按此裁剪"` CreatedAt time.Time `gorm:"column:created_at;comment:发帖时间,visible_days 按此裁剪"`
} }
func (Post) TableName() string { return "moment_posts" } func (Post) TableName() string { return "t_moment_posts" }
func (r *Post) Entity() *entity.Post { func (r *Post) Entity() *entity.Post {
if r == nil { if r == nil {
@ -61,7 +61,7 @@ type Audience struct {
PeerID int64 `gorm:"column:peer_id;comment:白名单能看 / 黑名单不能看 的好友 uid"` PeerID int64 `gorm:"column:peer_id;comment:白名单能看 / 黑名单不能看 的好友 uid"`
} }
func (Audience) TableName() string { return "moment_audience" } func (Audience) TableName() string { return "t_moment_audience" }
// Like maps table moment_likes. // Like maps table moment_likes.
type Like struct { type Like struct {
@ -71,7 +71,7 @@ type Like struct {
CreatedAt time.Time `gorm:"column:created_at;comment:点赞时间"` CreatedAt time.Time `gorm:"column:created_at;comment:点赞时间"`
} }
func (Like) TableName() string { return "moment_likes" } func (Like) TableName() string { return "t_moment_likes" }
func (r *Like) Entity() *entity.Like { func (r *Like) Entity() *entity.Like {
if r == nil { if r == nil {
@ -92,7 +92,7 @@ type Comment struct {
CreatedAt time.Time `gorm:"column:created_at;comment:评论时间"` CreatedAt time.Time `gorm:"column:created_at;comment:评论时间"`
} }
func (Comment) TableName() string { return "moment_comments" } func (Comment) TableName() string { return "t_moment_comments" }
func (r *Comment) Entity() *entity.Comment { func (r *Comment) Entity() *entity.Comment {
if r == nil { if r == nil {
@ -130,7 +130,7 @@ type Notice struct {
CreatedAt time.Time `gorm:"column:created_at;comment:通知时间"` CreatedAt time.Time `gorm:"column:created_at;comment:通知时间"`
} }
func (Notice) TableName() string { return "moment_notices" } func (Notice) TableName() string { return "t_moment_notices" }
func (r *Notice) Entity() *entity.Notice { func (r *Notice) Entity() *entity.Notice {
if r == nil { if r == nil {
@ -156,7 +156,7 @@ type Setting struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:最近更新时间"` UpdatedAt time.Time `gorm:"column:updated_at;comment:最近更新时间"`
} }
func (Setting) TableName() string { return "moment_settings" } func (Setting) TableName() string { return "t_moment_settings" }
func (r *Setting) Entity() *entity.Settings { func (r *Setting) Entity() *entity.Settings {
if r == nil { if r == nil {
@ -190,7 +190,7 @@ type Hide struct {
HideMe bool `gorm:"column:hide_me;comment:1=不让他看"` HideMe bool `gorm:"column:hide_me;comment:1=不让他看"`
} }
func (Hide) TableName() string { return "moment_hides" } func (Hide) TableName() string { return "t_moment_hides" }
func (r *Hide) Entity() *entity.Hide { func (r *Hide) Entity() *entity.Hide {
if r == nil { if r == nil {

View File

@ -40,7 +40,7 @@ type trackRow struct {
UpdatedAt time.Time `gorm:"column:updated_at"` UpdatedAt time.Time `gorm:"column:updated_at"`
} }
func (trackRow) TableName() string { return "music_tracks" } func (trackRow) TableName() string { return "t_music_tracks" }
type settingRow struct { type settingRow struct {
SettingKey string `gorm:"column:setting_key;primaryKey"` SettingKey string `gorm:"column:setting_key;primaryKey"`
@ -48,7 +48,7 @@ type settingRow struct {
UpdatedAt time.Time `gorm:"column:updated_at"` UpdatedAt time.Time `gorm:"column:updated_at"`
} }
func (settingRow) TableName() string { return "music_settings" } func (settingRow) TableName() string { return "t_music_settings" }
func (r *Repo) ListAll(ctx context.Context) ([]entity.Track, error) { func (r *Repo) ListAll(ctx context.Context) ([]entity.Track, error) {
if !r.Ready() { if !r.Ready() {

View File

@ -27,7 +27,7 @@ type Post struct {
CreatedAt time.Time `gorm:"column:created_at;comment:发帖时间"` CreatedAt time.Time `gorm:"column:created_at;comment:发帖时间"`
} }
func (Post) TableName() string { return "square_posts" } func (Post) TableName() string { return "t_square_posts" }
func (r *Post) Entity() *entity.Post { func (r *Post) Entity() *entity.Post {
if r == nil { if r == nil {
@ -72,7 +72,7 @@ type Audience struct {
PeerID int64 `gorm:"column:peer_id;primaryKey;comment:好友 uid"` PeerID int64 `gorm:"column:peer_id;primaryKey;comment:好友 uid"`
} }
func (Audience) TableName() string { return "square_audience" } func (Audience) TableName() string { return "t_square_audience" }
// Like maps square_likes. // Like maps square_likes.
type Like struct { type Like struct {
@ -81,7 +81,7 @@ type Like struct {
CreatedAt time.Time `gorm:"column:created_at;comment:点赞时间"` CreatedAt time.Time `gorm:"column:created_at;comment:点赞时间"`
} }
func (Like) TableName() string { return "square_likes" } func (Like) TableName() string { return "t_square_likes" }
func (r *Like) Entity() *entity.Like { func (r *Like) Entity() *entity.Like {
if r == nil { if r == nil {
@ -102,7 +102,7 @@ type Comment struct {
CreatedAt time.Time `gorm:"column:created_at;comment:评论时间"` CreatedAt time.Time `gorm:"column:created_at;comment:评论时间"`
} }
func (Comment) TableName() string { return "square_comments" } func (Comment) TableName() string { return "t_square_comments" }
func (r *Comment) Entity() *entity.Comment { func (r *Comment) Entity() *entity.Comment {
if r == nil { if r == nil {
@ -140,7 +140,7 @@ type Notice struct {
CreatedAt time.Time `gorm:"column:created_at;comment:通知时间"` CreatedAt time.Time `gorm:"column:created_at;comment:通知时间"`
} }
func (Notice) TableName() string { return "square_notices" } func (Notice) TableName() string { return "t_square_notices" }
func (r *Notice) Entity() *entity.Notice { func (r *Notice) Entity() *entity.Notice {
if r == nil { if r == nil {
@ -163,7 +163,7 @@ type NoticeState struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:更新时间"` UpdatedAt time.Time `gorm:"column:updated_at;comment:更新时间"`
} }
func (NoticeState) TableName() string { return "square_notice_state" } func (NoticeState) TableName() string { return "t_square_notice_state" }
func (r *NoticeState) Entity() *entity.NoticeState { func (r *NoticeState) Entity() *entity.NoticeState {
if r == nil { if r == nil {

View File

@ -63,16 +63,16 @@ func purgeSquareUser(tx *gorm.DB, userID int64) error {
} }
func bumpOffUserLikes(tx *gorm.DB, userID int64) error { func bumpOffUserLikes(tx *gorm.DB, userID int64) error {
sql := `UPDATE square_posts p sql := `UPDATE t_square_posts p
INNER JOIN (SELECT post_id, COUNT(*) AS n FROM square_likes WHERE user_id = ? GROUP BY post_id) x ON p.id = x.post_id INNER JOIN (SELECT post_id, COUNT(*) AS n FROM t_square_likes WHERE user_id = ? GROUP BY post_id) x ON p.id = x.post_id
SET p.like_count = CASE WHEN p.like_count < x.n THEN 0 ELSE p.like_count - x.n END SET p.like_count = CASE WHEN p.like_count < x.n THEN 0 ELSE p.like_count - x.n END
WHERE p.user_id <> ?` WHERE p.user_id <> ?`
return mapDBErr(tx.Exec(sql, userID, userID).Error) return mapDBErr(tx.Exec(sql, userID, userID).Error)
} }
func bumpOffUserComments(tx *gorm.DB, userID int64) error { func bumpOffUserComments(tx *gorm.DB, userID int64) error {
sql := `UPDATE square_posts p sql := `UPDATE t_square_posts p
INNER JOIN (SELECT post_id, COUNT(*) AS n FROM square_comments WHERE user_id = ? AND status = ? GROUP BY post_id) x ON p.id = x.post_id INNER JOIN (SELECT post_id, COUNT(*) AS n FROM t_square_comments WHERE user_id = ? AND status = ? GROUP BY post_id) x ON p.id = x.post_id
SET p.comment_count = CASE WHEN p.comment_count < x.n THEN 0 ELSE p.comment_count - x.n END SET p.comment_count = CASE WHEN p.comment_count < x.n THEN 0 ELSE p.comment_count - x.n END
WHERE p.user_id <> ?` WHERE p.user_id <> ?`
return mapDBErr(tx.Exec(sql, userID, entity.StatusNormal, userID).Error) return mapDBErr(tx.Exec(sql, userID, entity.StatusNormal, userID).Error)

View File

@ -12,4 +12,4 @@ type DeviceSyncCursor struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:游标更新时间"` UpdatedAt time.Time `gorm:"column:updated_at;comment:游标更新时间"`
} }
func (DeviceSyncCursor) TableName() string { return "device_sync_cursors" } func (DeviceSyncCursor) TableName() string { return "t_device_sync_cursors" }

View File

@ -15,4 +15,4 @@ type MailboxEvent struct {
CreatedAt time.Time `gorm:"column:created_at;comment:写入时间"` CreatedAt time.Time `gorm:"column:created_at;comment:写入时间"`
} }
func (MailboxEvent) TableName() string { return "mailbox_events" } func (MailboxEvent) TableName() string { return "t_mailbox_events" }

View File

@ -22,9 +22,9 @@ func (m *Repo) ListAccounts(ctx context.Context) ([]Account, error) {
User User
Email string `gorm:"column:email"` Email string `gorm:"column:email"`
} }
err = db.Table("users AS u"). err = db.Table("t_users AS u").
Select("u.*, e.email"). Select("u.*, e.email").
Joins("INNER JOIN user_emails e ON e.user_id = u.id"). Joins("INNER JOIN t_user_emails e ON e.user_id = u.id").
Order("u.id ASC"). Order("u.id ASC").
Find(&rows).Error Find(&rows).Error
if err != nil { if err != nil {

View File

@ -24,7 +24,7 @@ type User struct {
UpdatedAt time.Time `gorm:"column:updated_at;comment:资料最近更新时间(update_time)"` UpdatedAt time.Time `gorm:"column:updated_at;comment:资料最近更新时间(update_time)"`
} }
func (User) TableName() string { return "users" } func (User) TableName() string { return "t_users" }
func (r *User) entity() *entity.User { func (r *User) entity() *entity.User {
if r == nil { if r == nil {

View File

@ -28,7 +28,7 @@ type UserDelete struct {
DeletedAt time.Time `gorm:"column:deleted_at;comment:注销时间"` DeletedAt time.Time `gorm:"column:deleted_at;comment:注销时间"`
} }
func (UserDelete) TableName() string { return "user_delete" } func (UserDelete) TableName() string { return "t_user_delete" }
func (r *UserDelete) entity() *entity.Deleted { func (r *UserDelete) entity() *entity.Deleted {
if r == nil { if r == nil {

View File

@ -18,7 +18,7 @@ type UserDevice struct {
CreatedAt time.Time `gorm:"column:created_at;comment:该用户首次在此设备绑定"` CreatedAt time.Time `gorm:"column:created_at;comment:该用户首次在此设备绑定"`
} }
func (UserDevice) TableName() string { return "user_devices" } func (UserDevice) TableName() string { return "t_user_devices" }
func (row *UserDevice) entity() *entity.UserDeviceLink { func (row *UserDevice) entity() *entity.UserDeviceLink {
if row == nil { if row == nil {

View File

@ -17,7 +17,7 @@ type UserEmail struct {
CreatedAt time.Time `gorm:"column:created_at;comment:绑定时间"` CreatedAt time.Time `gorm:"column:created_at;comment:绑定时间"`
} }
func (UserEmail) TableName() string { return "user_emails" } func (UserEmail) TableName() string { return "t_user_emails" }
func (m *Repo) GetByEmail(ctx context.Context, email string) (*entity.User, error) { func (m *Repo) GetByEmail(ctx context.Context, email string) (*entity.User, error) {
db, err := m.session(ctx) db, err := m.session(ctx)

View File

@ -21,7 +21,7 @@ type UserLoginLog struct {
CreatedAt time.Time `gorm:"column:created_at;comment:本次登录时间"` CreatedAt time.Time `gorm:"column:created_at;comment:本次登录时间"`
} }
func (UserLoginLog) TableName() string { return "user_login_logs" } func (UserLoginLog) TableName() string { return "t_user_login_logs" }
func (m *Repo) InsertLoginLog(ctx context.Context, log *entity.LoginLog) error { func (m *Repo) InsertLoginLog(ctx context.Context, log *entity.LoginLog) error {
if log == nil { if log == nil {

View File

@ -0,0 +1,39 @@
-- Legacy databases: rename unprefixed tables to t_* (must run before 0001+ CREATE t_*).
-- No-op when tables already use t_ prefix (unknown source table → ignorable DDL).
RENAME TABLE users TO t_users;
RENAME TABLE user_emails TO t_user_emails;
RENAME TABLE user_devices TO t_user_devices;
RENAME TABLE user_login_logs TO t_user_login_logs;
RENAME TABLE user_delete TO t_user_delete;
RENAME TABLE devices TO t_devices;
RENAME TABLE conversations TO t_conversations;
RENAME TABLE p2p_conversations TO t_p2p_conversations;
RENAME TABLE conversation_members TO t_conversation_members;
RENAME TABLE mailbox_events TO t_mailbox_events;
RENAME TABLE device_sync_cursors TO t_device_sync_cursors;
RENAME TABLE messages TO t_messages;
RENAME TABLE private_message TO t_private_message;
RENAME TABLE group_message TO t_group_message;
RENAME TABLE message_deletes TO t_message_deletes;
RENAME TABLE friend_relations TO t_friend_relations;
RENAME TABLE friend_requests TO t_friend_requests;
RENAME TABLE friend_reports TO t_friend_reports;
RENAME TABLE favorites TO t_favorites;
RENAME TABLE `groups` TO `t_groups`;
RENAME TABLE group_members TO t_group_members;
RENAME TABLE moment_posts TO t_moment_posts;
RENAME TABLE moment_audience TO t_moment_audience;
RENAME TABLE moment_likes TO t_moment_likes;
RENAME TABLE moment_comments TO t_moment_comments;
RENAME TABLE moment_notices TO t_moment_notices;
RENAME TABLE moment_settings TO t_moment_settings;
RENAME TABLE moment_hides TO t_moment_hides;
RENAME TABLE square_posts TO t_square_posts;
RENAME TABLE square_audience TO t_square_audience;
RENAME TABLE square_likes TO t_square_likes;
RENAME TABLE square_comments TO t_square_comments;
RENAME TABLE square_notices TO t_square_notices;
RENAME TABLE square_notice_state TO t_square_notice_state;
RENAME TABLE music_tracks TO t_music_tracks;
RENAME TABLE music_settings TO t_music_settings;

View File

@ -1,6 +1,6 @@
-- Spark IM V3 initial schema. Message body lives in MySQL; dialog list lives in Redis. -- Spark IM V3 initial schema. Message body lives in MySQL; dialog list lives in Redis.
CREATE TABLE IF NOT EXISTS conversations ( CREATE TABLE IF NOT EXISTS t_conversations (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 会话 id,时间有序主键', id BIGINT PRIMARY KEY COMMENT 'Snowflake 会话 id,时间有序主键',
type TINYINT NOT NULL COMMENT '会话类型:1=单聊 2=群聊', type TINYINT NOT NULL COMMENT '会话类型:1=单聊 2=群聊',
last_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '最新消息 Snowflake id,0 表示尚无消息', last_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '最新消息 Snowflake id,0 表示尚无消息',
@ -8,15 +8,15 @@ CREATE TABLE IF NOT EXISTS conversations (
created_at DATETIME(3) NOT NULL COMMENT '会话创建时间' created_at DATETIME(3) NOT NULL COMMENT '会话创建时间'
) COMMENT='会话。单聊/群聊共用一张表;列表与未读在 Redis,不在此表热更新'; ) COMMENT='会话。单聊/群聊共用一张表;列表与未读在 Redis,不在此表热更新';
CREATE TABLE IF NOT EXISTS p2p_conversations ( CREATE TABLE IF NOT EXISTS t_p2p_conversations (
user_small BIGINT NOT NULL COMMENT '两人中较小的 user_id,保证同一对用户只有一行', user_small BIGINT NOT NULL COMMENT '两人中较小的 user_id,保证同一对用户只有一行',
user_big BIGINT NOT NULL COMMENT '两人中较大的 user_id', user_big BIGINT NOT NULL COMMENT '两人中较大的 user_id',
conversation_id BIGINT NOT NULL COMMENT '对应 conversations.id', conversation_id BIGINT NOT NULL COMMENT '对应 t_conversations.id',
PRIMARY KEY (user_small, user_big), PRIMARY KEY (user_small, user_big),
UNIQUE KEY uk_conversation (conversation_id) UNIQUE KEY uk_conversation (conversation_id)
) COMMENT='单聊唯一索引。按 (较小 uid, 较大 uid) 定位会话,避免 A-B / B-A 两条记录'; ) COMMENT='单聊唯一索引。按 (较小 uid, 较大 uid) 定位会话,避免 A-B / B-A 两条记录';
CREATE TABLE IF NOT EXISTS conversation_members ( CREATE TABLE IF NOT EXISTS t_conversation_members (
conversation_id BIGINT NOT NULL COMMENT '会话 id', conversation_id BIGINT NOT NULL COMMENT '会话 id',
user_id BIGINT NOT NULL COMMENT '成员 user_id', user_id BIGINT NOT NULL COMMENT '成员 user_id',
last_read_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '该成员已读到的最大消息 id,未读真相;Redis 丢了可从此重建', last_read_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '该成员已读到的最大消息 id,未读真相;Redis 丢了可从此重建',
@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS conversation_members (
KEY idx_user_id (user_id) KEY idx_user_id (user_id)
) COMMENT='会话成员。热路径会话列表不扫此表;last_read_msg_id 是已读持久化'; ) COMMENT='会话成员。热路径会话列表不扫此表;last_read_msg_id 是已读持久化';
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS t_messages (
id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属会话 id', conversation_id BIGINT NOT NULL COMMENT '所属会话 id',
sender_id BIGINT NOT NULL COMMENT '发送者 user_id', sender_id BIGINT NOT NULL COMMENT '发送者 user_id',
@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS messages (
KEY idx_conversation_id (conversation_id, id) KEY idx_conversation_id (conversation_id, id)
) COMMENT='消息正文。PK=MsgId;content 为序列化 BLOB,不存媒体二进制'; ) COMMENT='消息正文。PK=MsgId;content 为序列化 BLOB,不存媒体二进制';
CREATE TABLE IF NOT EXISTS mailbox_events ( CREATE TABLE IF NOT EXISTS t_mailbox_events (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 事件 id', id BIGINT PRIMARY KEY COMMENT 'Snowflake 事件 id',
user_id BIGINT NOT NULL COMMENT '接收用户', user_id BIGINT NOT NULL COMMENT '接收用户',
device_id BIGINT NOT NULL COMMENT '目标设备;0 表示该用户全部设备', device_id BIGINT NOT NULL COMMENT '目标设备;0 表示该用户全部设备',
@ -52,10 +52,10 @@ CREATE TABLE IF NOT EXISTS mailbox_events (
KEY idx_user_device (user_id, device_id, seq) KEY idx_user_device (user_id, device_id, seq)
) COMMENT='失败邮箱与控制事件。成功在线推送的新消息不写此表'; ) COMMENT='失败邮箱与控制事件。成功在线推送的新消息不写此表';
CREATE TABLE IF NOT EXISTS device_sync_cursors ( CREATE TABLE IF NOT EXISTS t_device_sync_cursors (
user_id BIGINT NOT NULL COMMENT '用户 id', user_id BIGINT NOT NULL COMMENT '用户 id',
device_id BIGINT NOT NULL COMMENT '设备 id', device_id BIGINT NOT NULL COMMENT '设备 id',
mailbox_seq BIGINT NOT NULL DEFAULT 0 COMMENT '该设备已同步到的 mailbox_events.seq', mailbox_seq BIGINT NOT NULL DEFAULT 0 COMMENT '该设备已同步到的 t_mailbox_events.seq',
dialog_score BIGINT NOT NULL DEFAULT 0 COMMENT '会话列表 Redis ZSET 游标', dialog_score BIGINT NOT NULL DEFAULT 0 COMMENT '会话列表 Redis ZSET 游标',
updated_at DATETIME(3) NOT NULL COMMENT '游标更新时间', updated_at DATETIME(3) NOT NULL COMMENT '游标更新时间',
PRIMARY KEY (user_id, device_id) PRIMARY KEY (user_id, device_id)

View File

@ -1,6 +1,6 @@
-- Users: Snowflake PK (not AUTO_INCREMENT), public spark_id, email map, login devices. -- Users: Snowflake PK (not AUTO_INCREMENT), public spark_id, email map, login t_devices.
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS t_users (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 用户 id(uid),时间有序,非自增', id BIGINT PRIMARY KEY COMMENT 'Snowflake 用户 id(uid),时间有序,非自增',
spark_id VARCHAR(32) NOT NULL COMMENT '对外展示的账号,用户可改,全局唯一', spark_id VARCHAR(32) NOT NULL COMMENT '对外展示的账号,用户可改,全局唯一',
nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
@ -14,28 +14,25 @@ CREATE TABLE IF NOT EXISTS users (
created_at DATETIME(3) NOT NULL COMMENT '注册时间(create_time)', created_at DATETIME(3) NOT NULL COMMENT '注册时间(create_time)',
updated_at DATETIME(3) NOT NULL COMMENT '资料最近更新时间(update_time)', updated_at DATETIME(3) NOT NULL COMMENT '资料最近更新时间(update_time)',
UNIQUE KEY uk_spark_id (spark_id) UNIQUE KEY uk_spark_id (spark_id)
) COMMENT='用户账号。主键 Snowflake 即 uid;登录身份在 user_emails,设备在 user_devices'; ) COMMENT='用户账号。主键 Snowflake 即 uid;登录身份在 t_user_emails,设备在 t_user_devices';
CREATE TABLE IF NOT EXISTS user_emails ( CREATE TABLE IF NOT EXISTS t_user_emails (
email VARCHAR(255) NOT NULL COMMENT '规范化后的小写邮箱,登录账号', email VARCHAR(255) NOT NULL COMMENT '规范化后的小写邮箱,登录账号',
user_id BIGINT NOT NULL COMMENT '绑定的 users.id,一对一', user_id BIGINT NOT NULL COMMENT '绑定的 t_users.id,一对一',
created_at DATETIME(3) NOT NULL COMMENT '绑定时间', created_at DATETIME(3) NOT NULL COMMENT '绑定时间',
PRIMARY KEY (email), PRIMARY KEY (email),
UNIQUE KEY uk_user_id (user_id) UNIQUE KEY uk_user_id (user_id)
) COMMENT='邮箱与用户的一对一映射。登录用邮箱查此表再读 users'; ) COMMENT='邮箱与用户的一对一映射。登录用邮箱查此表再读 t_users';
CREATE TABLE IF NOT EXISTS user_devices ( CREATE TABLE IF NOT EXISTS t_user_devices (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)', id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)',
user_id BIGINT NOT NULL COMMENT '所属用户', user_id BIGINT NOT NULL COMMENT '所属用户',
device_id BIGINT UNSIGNED NOT NULL COMMENT '客户端设备 id,写入 JWT did', device_id BIGINT UNSIGNED NOT NULL COMMENT 't_devices.device_id,JWT did',
platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'ios/android/web 等', last_login_at DATETIME(3) NOT NULL COMMENT '该用户在此设备最近登录成功',
device_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '设备展示名', last_active_at DATETIME(3) NULL COMMENT '该用户在此设备最近活跃',
app_version VARCHAR(32) NOT NULL DEFAULT '' COMMENT '最近一次登录的客户端版本', created_at DATETIME(3) NOT NULL COMMENT '该用户首次在此设备绑定',
language VARCHAR(16) NOT NULL DEFAULT '' COMMENT '最近一次登录语言,如 zh-CN',
last_ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次登录的 PROXY RealIP,禁止用环回地址',
last_login_at DATETIME(3) NOT NULL COMMENT '最近一次登录时间',
created_at DATETIME(3) NOT NULL COMMENT '该设备首次登录时间',
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE KEY uk_user_device (user_id, device_id), UNIQUE KEY uk_user_device (user_id, device_id),
KEY idx_user_id (user_id) KEY idx_user_id (user_id),
) COMMENT='登录过的设备。同 user+device 覆盖更新;踢人按 device_id'; KEY idx_device_id (device_id)
) COMMENT='用户与设备关系;设备属性见 t_devices,当前 App 见 JWT aid';

View File

@ -1,29 +1,29 @@
-- Add table/column comments for databases created before 0001/0002 carried COMMENT clauses. -- Add table/column comments for databases created before 0001/0002 carried COMMENT clauses.
ALTER TABLE conversations COMMENT = '会话。单聊/群聊共用一张表;列表与未读在 Redis,不在此表热更新'; ALTER TABLE t_conversations COMMENT = '会话。单聊/群聊共用一张表;列表与未读在 Redis,不在此表热更新';
ALTER TABLE conversations ALTER TABLE t_conversations
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 会话 id,时间有序主键', MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 会话 id,时间有序主键',
MODIFY COLUMN type TINYINT NOT NULL COMMENT '会话类型:1=单聊 2=群聊', MODIFY COLUMN type TINYINT NOT NULL COMMENT '会话类型:1=单聊 2=群聊',
MODIFY COLUMN last_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '最新消息 Snowflake id,0 表示尚无消息', MODIFY COLUMN last_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '最新消息 Snowflake id,0 表示尚无消息',
MODIFY COLUMN last_msg_at DATETIME(3) NOT NULL COMMENT '最新消息时间(毫秒精度)', MODIFY COLUMN last_msg_at DATETIME(3) NOT NULL COMMENT '最新消息时间(毫秒精度)',
MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '会话创建时间'; MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '会话创建时间';
ALTER TABLE p2p_conversations COMMENT = '单聊唯一索引。按 (较小 uid, 较大 uid) 定位会话,避免 A-B / B-A 两条记录'; ALTER TABLE t_p2p_conversations COMMENT = '单聊唯一索引。按 (较小 uid, 较大 uid) 定位会话,避免 A-B / B-A 两条记录';
ALTER TABLE p2p_conversations ALTER TABLE t_p2p_conversations
MODIFY COLUMN user_small BIGINT NOT NULL COMMENT '两人中较小的 user_id,保证同一对用户只有一行', MODIFY COLUMN user_small BIGINT NOT NULL COMMENT '两人中较小的 user_id,保证同一对用户只有一行',
MODIFY COLUMN user_big BIGINT NOT NULL COMMENT '两人中较大的 user_id', MODIFY COLUMN user_big BIGINT NOT NULL COMMENT '两人中较大的 user_id',
MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '对应 conversations.id'; MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '对应 t_conversations.id';
ALTER TABLE conversation_members COMMENT = '会话成员。热路径会话列表不扫此表;last_read_msg_id 是已读持久化'; ALTER TABLE t_conversation_members COMMENT = '会话成员。热路径会话列表不扫此表;last_read_msg_id 是已读持久化';
ALTER TABLE conversation_members ALTER TABLE t_conversation_members
MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '会话 id', MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '会话 id',
MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '成员 user_id', MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '成员 user_id',
MODIFY COLUMN last_read_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '该成员已读到的最大消息 id,未读真相;Redis 丢了可从此重建', MODIFY COLUMN last_read_msg_id BIGINT NOT NULL DEFAULT 0 COMMENT '该成员已读到的最大消息 id,未读真相;Redis 丢了可从此重建',
MODIFY COLUMN joined_at DATETIME(3) NOT NULL COMMENT '加入会话时间', MODIFY COLUMN joined_at DATETIME(3) NOT NULL COMMENT '加入会话时间',
MODIFY COLUMN muted TINYINT NOT NULL DEFAULT 0 COMMENT '0=未静音 1=免打扰'; MODIFY COLUMN muted TINYINT NOT NULL DEFAULT 0 COMMENT '0=未静音 1=免打扰';
ALTER TABLE messages COMMENT = '消息正文。PK=MsgId;content 为序列化 BLOB,不存媒体二进制'; ALTER TABLE t_messages COMMENT = '消息正文。PK=MsgId;content 为序列化 BLOB,不存媒体二进制';
ALTER TABLE messages ALTER TABLE t_messages
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake MsgId,主键、排序键、同步游标', MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '所属会话 id', MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '所属会话 id',
MODIFY COLUMN sender_id BIGINT NOT NULL COMMENT '发送者 user_id', MODIFY COLUMN sender_id BIGINT NOT NULL COMMENT '发送者 user_id',
@ -33,8 +33,8 @@ ALTER TABLE messages
MODIFY COLUMN status TINYINT NOT NULL DEFAULT 1 COMMENT '1=正常 2=已撤回 3=审核隐藏', MODIFY COLUMN status TINYINT NOT NULL DEFAULT 1 COMMENT '1=正常 2=已撤回 3=审核隐藏',
MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '服务端入库时间'; MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '服务端入库时间';
ALTER TABLE mailbox_events COMMENT = '失败邮箱与控制事件。成功在线推送的新消息不写此表'; ALTER TABLE t_mailbox_events COMMENT = '失败邮箱与控制事件。成功在线推送的新消息不写此表';
ALTER TABLE mailbox_events ALTER TABLE t_mailbox_events
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 事件 id', MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 事件 id',
MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '接收用户', MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '接收用户',
MODIFY COLUMN device_id BIGINT NOT NULL COMMENT '目标设备;0 表示该用户全部设备', MODIFY COLUMN device_id BIGINT NOT NULL COMMENT '目标设备;0 表示该用户全部设备',
@ -44,16 +44,16 @@ ALTER TABLE mailbox_events
MODIFY COLUMN payload VARBINARY(4096) COMMENT '控制事件载荷(会话 id+消息 id 等),不含消息正文', MODIFY COLUMN payload VARBINARY(4096) COMMENT '控制事件载荷(会话 id+消息 id 等),不含消息正文',
MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '写入时间'; MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '写入时间';
ALTER TABLE device_sync_cursors COMMENT = '每设备同步游标。mailbox_seq 对邮箱,dialog_score 对会话列表'; ALTER TABLE t_device_sync_cursors COMMENT = '每设备同步游标。mailbox_seq 对邮箱,dialog_score 对会话列表';
ALTER TABLE device_sync_cursors ALTER TABLE t_device_sync_cursors
MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '用户 id', MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '用户 id',
MODIFY COLUMN device_id BIGINT NOT NULL COMMENT '设备 id', MODIFY COLUMN device_id BIGINT NOT NULL COMMENT '设备 id',
MODIFY COLUMN mailbox_seq BIGINT NOT NULL DEFAULT 0 COMMENT '该设备已同步到的 mailbox_events.seq', MODIFY COLUMN mailbox_seq BIGINT NOT NULL DEFAULT 0 COMMENT '该设备已同步到的 t_mailbox_events.seq',
MODIFY COLUMN dialog_score BIGINT NOT NULL DEFAULT 0 COMMENT '会话列表 Redis ZSET 游标', MODIFY COLUMN dialog_score BIGINT NOT NULL DEFAULT 0 COMMENT '会话列表 Redis ZSET 游标',
MODIFY COLUMN updated_at DATETIME(3) NOT NULL COMMENT '游标更新时间'; MODIFY COLUMN updated_at DATETIME(3) NOT NULL COMMENT '游标更新时间';
ALTER TABLE users COMMENT = '用户账号。主键 Snowflake;登录身份在 user_emails,设备在 user_devices'; ALTER TABLE t_users COMMENT = '用户账号。主键 Snowflake;登录身份在 t_user_emails,设备在 t_user_devices';
ALTER TABLE users ALTER TABLE t_users
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 用户 id(uid),时间有序,非自增', MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 用户 id(uid),时间有序,非自增',
MODIFY COLUMN spark_id VARCHAR(32) NOT NULL COMMENT '对外展示的账号,用户可改,全局唯一', MODIFY COLUMN spark_id VARCHAR(32) NOT NULL COMMENT '对外展示的账号,用户可改,全局唯一',
MODIFY COLUMN nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', MODIFY COLUMN nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
@ -62,21 +62,17 @@ ALTER TABLE users
MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '注册时间', MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '注册时间',
MODIFY COLUMN updated_at DATETIME(3) NOT NULL COMMENT '资料最近更新时间'; MODIFY COLUMN updated_at DATETIME(3) NOT NULL COMMENT '资料最近更新时间';
ALTER TABLE user_emails COMMENT = '邮箱与用户的一对一映射。登录用邮箱查此表再读 users'; ALTER TABLE t_user_emails COMMENT = '邮箱与用户的一对一映射。登录用邮箱查此表再读 t_users';
ALTER TABLE user_emails ALTER TABLE t_user_emails
MODIFY COLUMN email VARCHAR(255) NOT NULL COMMENT '规范化后的小写邮箱,登录账号', MODIFY COLUMN email VARCHAR(255) NOT NULL COMMENT '规范化后的小写邮箱,登录账号',
MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '绑定的 users.id,一对一', MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '绑定的 t_users.id,一对一',
MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '绑定时间'; MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '绑定时间';
ALTER TABLE user_devices COMMENT = '登录过的设备。同 user+device 覆盖更新;踢人按 device_id'; ALTER TABLE t_user_devices COMMENT = '用户与设备关系;设备属性见 t_devices,当前 App 见 JWT aid';
ALTER TABLE user_devices ALTER TABLE t_user_devices
MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)', MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)',
MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '所属用户', MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '所属用户',
MODIFY COLUMN device_id BIGINT UNSIGNED NOT NULL COMMENT '客户端设备 id,写入 JWT did', MODIFY COLUMN device_id BIGINT UNSIGNED NOT NULL COMMENT 't_devices.device_id,JWT did',
MODIFY COLUMN platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'ios/android/web 等', MODIFY COLUMN last_login_at DATETIME(3) NOT NULL COMMENT '该用户在此设备最近登录成功',
MODIFY COLUMN device_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '设备展示名', MODIFY COLUMN last_active_at DATETIME(3) NULL COMMENT '该用户在此设备最近活跃',
MODIFY COLUMN app_version VARCHAR(32) NOT NULL DEFAULT '' COMMENT '最近一次登录的客户端版本', MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '该用户首次在此设备绑定';
MODIFY COLUMN language VARCHAR(16) NOT NULL DEFAULT '' COMMENT '最近一次登录语言,如 zh-CN',
MODIFY COLUMN last_ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次登录的 PROXY RealIP,禁止用环回地址',
MODIFY COLUMN last_login_at DATETIME(3) NOT NULL COMMENT '最近一次登录时间',
MODIFY COLUMN created_at DATETIME(3) NOT NULL COMMENT '该设备首次登录时间';

View File

@ -1,5 +1,5 @@
-- user_devices.id is a table-local AUTO_INCREMENT row key, not Snowflake. -- t_user_devices.id is a table-local AUTO_INCREMENT row key, not Snowflake.
-- Business identity remains (user_id, device_id). Existing Snowflake values stay; new rows continue from MAX(id)+1. -- Business identity remains (user_id, device_id). Existing Snowflake values stay; new rows continue from MAX(id)+1.
ALTER TABLE user_devices ALTER TABLE t_user_devices
MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)'; MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)';

View File

@ -1,9 +1,9 @@
-- Profile fields on users. One ADD per statement (stock MySQL has no ADD COLUMN IF NOT EXISTS). -- Profile fields on t_users. One ADD per statement (stock MySQL has no ADD COLUMN IF NOT EXISTS).
-- Re-running migrate ignores duplicate-column errors. -- Re-running migrate ignores duplicate-column errors.
ALTER TABLE users ADD COLUMN qrcode VARCHAR(512) NOT NULL DEFAULT '' COMMENT '个人二维码对象存储 key 或 URL,不存图片二进制'; ALTER TABLE t_users ADD COLUMN qrcode VARCHAR(512) NOT NULL DEFAULT '' COMMENT '个人二维码对象存储 key 或 URL,不存图片二进制';
ALTER TABLE users ADD COLUMN birthday DATE NULL COMMENT '生日,未填为空'; ALTER TABLE t_users ADD COLUMN birthday DATE NULL COMMENT '生日,未填为空';
ALTER TABLE users ADD COLUMN gender TINYINT NOT NULL DEFAULT 0 COMMENT '0=未知 1=男 2=女'; ALTER TABLE t_users ADD COLUMN gender TINYINT NOT NULL DEFAULT 0 COMMENT '0=未知 1=男 2=女';
ALTER TABLE users ADD COLUMN last_login_at DATETIME(3) NULL COMMENT '最近一次登录时间'; ALTER TABLE t_users ADD COLUMN last_login_at DATETIME(3) NULL COMMENT '最近一次登录时间';
ALTER TABLE users ADD COLUMN last_active_at DATETIME(3) NULL COMMENT '最近一次活跃时间(登录、心跳、发消息等)'; ALTER TABLE t_users ADD COLUMN last_active_at DATETIME(3) NULL COMMENT '最近一次活跃时间(登录、心跳、发消息等)';
ALTER TABLE users COMMENT = '用户账号。主键 Snowflake 即 uid;登录身份在 user_emails,设备在 user_devices'; ALTER TABLE t_users COMMENT = '用户账号。主键 Snowflake 即 uid;登录身份在 t_user_emails,设备在 t_user_devices';

View File

@ -1,6 +1,6 @@
-- user_login_logs: one row per successful login. user_devices.platform is the device type. -- t_user_login_logs: one row per successful login. Device type lives on t_devices.
CREATE TABLE IF NOT EXISTS user_login_logs ( CREATE TABLE IF NOT EXISTS t_user_login_logs (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增', id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增',
user_id BIGINT NOT NULL COMMENT '登录用户', user_id BIGINT NOT NULL COMMENT '登录用户',
device_id BIGINT UNSIGNED NOT NULL COMMENT '客户端设备 id', device_id BIGINT UNSIGNED NOT NULL COMMENT '客户端设备 id',
@ -12,7 +12,4 @@ CREATE TABLE IF NOT EXISTS user_login_logs (
created_at DATETIME(3) NOT NULL COMMENT '本次登录时间', created_at DATETIME(3) NOT NULL COMMENT '本次登录时间',
PRIMARY KEY (id), PRIMARY KEY (id),
KEY idx_user_created (user_id, created_at) KEY idx_user_created (user_id, created_at)
) COMMENT='用户登录记录。每次成功登录追加一行;当前设备以 user_devices 为准'; ) COMMENT='用户登录记录。每次成功登录追加一行;当次 App 见 app_id';
ALTER TABLE user_devices
MODIFY COLUMN platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT '设备类型:ios/android/harmony/windows/macos/web';

View File

@ -1,6 +1,6 @@
-- Bidirectional friends as two directed rows, one pending request per pair, reports. -- Bidirectional friends as two directed rows, one pending request per pair, reports.
CREATE TABLE IF NOT EXISTS friend_relations ( CREATE TABLE IF NOT EXISTS t_friend_relations (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增', id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增',
user_id BIGINT NOT NULL COMMENT '关系所属用户', user_id BIGINT NOT NULL COMMENT '关系所属用户',
friend_id BIGINT NOT NULL COMMENT '对方 user_id', friend_id BIGINT NOT NULL COMMENT '对方 user_id',
@ -16,7 +16,7 @@ CREATE TABLE IF NOT EXISTS friend_relations (
KEY idx_user_status (user_id, status) KEY idx_user_status (user_id, status)
) COMMENT='单向好友边。A 与 B 互为好友时存两行,各自维护备注、拉黑、置顶'; ) COMMENT='单向好友边。A 与 B 互为好友时存两行,各自维护备注、拉黑、置顶';
CREATE TABLE IF NOT EXISTS friend_requests ( CREATE TABLE IF NOT EXISTS t_friend_requests (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 申请 id', id BIGINT PRIMARY KEY COMMENT 'Snowflake 申请 id',
from_user BIGINT NOT NULL COMMENT '申请人 user_id', from_user BIGINT NOT NULL COMMENT '申请人 user_id',
to_user BIGINT NOT NULL COMMENT '被申请人 user_id', to_user BIGINT NOT NULL COMMENT '被申请人 user_id',
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS friend_requests (
KEY idx_to_status (to_user, status, updated_at) KEY idx_to_status (to_user, status, updated_at)
) COMMENT='好友申请。同一对 from→to 只保留一行,重复发起覆盖理由与备注'; ) COMMENT='好友申请。同一对 from→to 只保留一行,重复发起覆盖理由与备注';
CREATE TABLE IF NOT EXISTS friend_reports ( CREATE TABLE IF NOT EXISTS t_friend_reports (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 投诉 id', id BIGINT PRIMARY KEY COMMENT 'Snowflake 投诉 id',
user_id BIGINT NOT NULL COMMENT '投诉人 user_id', user_id BIGINT NOT NULL COMMENT '投诉人 user_id',
target_id BIGINT NOT NULL COMMENT '被投诉人 user_id', target_id BIGINT NOT NULL COMMENT '被投诉人 user_id',

View File

@ -1,11 +1,11 @@
-- 用户收藏。媒体对象复制到 perm/fav,不引用 chat/ 原 key;源对象过期则无法再收藏。 -- 用户收藏。媒体对象复制到 perm/fav,不引用 chat/ 原 key;源对象过期则无法再收藏。
CREATE TABLE IF NOT EXISTS favorites ( CREATE TABLE IF NOT EXISTS t_favorites (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 收藏 id', id BIGINT PRIMARY KEY COMMENT 'Snowflake 收藏 id',
user_id BIGINT NOT NULL COMMENT '收藏者 user_id', user_id BIGINT NOT NULL COMMENT '收藏者 user_id',
msg_id BIGINT NOT NULL COMMENT '原消息 Snowflake id,仅作来源;撤回不影响收藏', msg_id BIGINT NOT NULL COMMENT '原消息 Snowflake id,仅作来源;撤回不影响收藏',
conversation_id BIGINT NOT NULL COMMENT '原会话 id,仅作来源', conversation_id BIGINT NOT NULL COMMENT '原会话 id,仅作来源',
message_type SMALLINT NOT NULL COMMENT '消息类型,与 messages.message_type 一致', message_type SMALLINT NOT NULL COMMENT '消息类型,与 t_messages.message_type 一致',
content BLOB NOT NULL COMMENT '收藏快照(JSON 字节);媒体 key 已复制到 perm/fav/', content BLOB NOT NULL COMMENT '收藏快照(JSON 字节);媒体 key 已复制到 perm/fav/',
created_at DATETIME(3) NOT NULL COMMENT '收藏时间', created_at DATETIME(3) NOT NULL COMMENT '收藏时间',
UNIQUE KEY uk_user_msg (user_id, msg_id), UNIQUE KEY uk_user_msg (user_id, msg_id),

View File

@ -1,8 +1,8 @@
-- Group id is a table-local AUTO_INCREMENT 群号. Chat / sync still use Snowflake conversations.id. -- Group id is a table-local AUTO_INCREMENT 群号. Chat / sync still use Snowflake t_conversations.id.
CREATE TABLE IF NOT EXISTS `groups` ( CREATE TABLE IF NOT EXISTS `t_groups` (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '群号,表内自增,对外群 ID', id BIGINT NOT NULL AUTO_INCREMENT COMMENT '群号,表内自增,对外群 ID',
conversation_id BIGINT NOT NULL COMMENT '对应 conversations.id(Snowflake),发消息与同步用', conversation_id BIGINT NOT NULL COMMENT '对应 t_conversations.id(Snowflake),发消息与同步用',
name VARCHAR(32) NOT NULL COMMENT '群名称', name VARCHAR(32) NOT NULL COMMENT '群名称',
avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '群头像对象存储 key,空表示未设置', avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '群头像对象存储 key,空表示未设置',
owner_id BIGINT NOT NULL COMMENT '群主 user_id', owner_id BIGINT NOT NULL COMMENT '群主 user_id',
@ -17,8 +17,8 @@ CREATE TABLE IF NOT EXISTS `groups` (
KEY idx_status_updated (status, updated_at) KEY idx_status_updated (status, updated_at)
) COMMENT='群资料。id 是自增群号;会话列表与未读仍按 conversation_id 走 Redis'; ) COMMENT='群资料。id 是自增群号;会话列表与未读仍按 conversation_id 走 Redis';
CREATE TABLE IF NOT EXISTS group_members ( CREATE TABLE IF NOT EXISTS t_group_members (
group_id BIGINT NOT NULL COMMENT '群号,groups.id', group_id BIGINT NOT NULL COMMENT '群号,t_groups.id',
user_id BIGINT NOT NULL COMMENT '成员 user_id', user_id BIGINT NOT NULL COMMENT '成员 user_id',
role TINYINT NOT NULL DEFAULT 3 COMMENT '1=群主 2=管理员 3=普通成员', role TINYINT NOT NULL DEFAULT 3 COMMENT '1=群主 2=管理员 3=普通成员',
nickname VARCHAR(32) NOT NULL DEFAULT '' COMMENT '群内昵称,空则用用户昵称', nickname VARCHAR(32) NOT NULL DEFAULT '' COMMENT '群内昵称,空则用用户昵称',
@ -26,4 +26,4 @@ CREATE TABLE IF NOT EXISTS group_members (
joined_at DATETIME(3) NOT NULL COMMENT '入群时间', joined_at DATETIME(3) NOT NULL COMMENT '入群时间',
PRIMARY KEY (group_id, user_id), PRIMARY KEY (group_id, user_id),
KEY idx_user_id (user_id) KEY idx_user_id (user_id)
) COMMENT='群成员与角色。热路径 MemberIDs 可短缓存;免打扰在 conversation_members'; ) COMMENT='群成员与角色。热路径 MemberIDs 可短缓存;免打扰在 t_conversation_members';

View File

@ -1,10 +1,10 @@
-- Per-user delete (hide). Does not change messages.status or the peer's history. -- Per-user delete (hide). Does not change t_messages.status or the peer's history.
CREATE TABLE IF NOT EXISTS message_deletes ( CREATE TABLE IF NOT EXISTS t_message_deletes (
user_id BIGINT NOT NULL COMMENT '删除者 user_id,仅该用户历史不可见', user_id BIGINT NOT NULL COMMENT '删除者 user_id,仅该用户历史不可见',
msg_id BIGINT NOT NULL COMMENT '被删除的 Snowflake MsgId', msg_id BIGINT NOT NULL COMMENT '被删除的 Snowflake MsgId',
conversation_id BIGINT NOT NULL COMMENT '所属会话,便于按会话过滤', conversation_id BIGINT NOT NULL COMMENT '所属会话,便于按会话过滤',
created_at DATETIME(3) NOT NULL COMMENT '删除时间', created_at DATETIME(3) NOT NULL COMMENT '删除时间',
PRIMARY KEY (user_id, msg_id), PRIMARY KEY (user_id, msg_id),
KEY idx_user_conv_msg (user_id, conversation_id, msg_id) KEY idx_user_conv_msg (user_id, conversation_id, msg_id)
) COMMENT='用户删除消息(只对自己隐藏)。对端仍可见;全员撤回走 messages.status=2'; ) COMMENT='用户删除消息(只对自己隐藏)。对端仍可见;全员撤回走 t_messages.status=2';

View File

@ -1,6 +1,6 @@
-- Split message body by conversation type. Snowflake MsgId remains PK / sort / sync cursor. -- Split message body by conversation type. Snowflake MsgId remains PK / sort / sync cursor.
CREATE TABLE IF NOT EXISTS private_message ( CREATE TABLE IF NOT EXISTS t_private_message (
id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属单聊会话 id', conversation_id BIGINT NOT NULL COMMENT '所属单聊会话 id',
sender_id BIGINT NOT NULL COMMENT '发送者 user_id;系统消息为 0', sender_id BIGINT NOT NULL COMMENT '发送者 user_id;系统消息为 0',
@ -13,12 +13,12 @@ CREATE TABLE IF NOT EXISTS private_message (
UNIQUE KEY uk_sender_conv_client_msg (sender_id, conversation_id, client_msg_id), UNIQUE KEY uk_sender_conv_client_msg (sender_id, conversation_id, client_msg_id),
KEY idx_conversation_id (conversation_id, id), KEY idx_conversation_id (conversation_id, id),
KEY idx_receiver_id (receiver_id, id) KEY idx_receiver_id (receiver_id, id)
) COMMENT='单聊消息正文。PK=MsgId;与 group_message 分表存储'; ) COMMENT='单聊消息正文。PK=MsgId;与 t_group_message 分表存储';
CREATE TABLE IF NOT EXISTS group_message ( CREATE TABLE IF NOT EXISTS t_group_message (
id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属群会话 id', conversation_id BIGINT NOT NULL COMMENT '所属群会话 id',
group_id BIGINT NOT NULL DEFAULT 0 COMMENT '群号 groups.id,便于按群查询', group_id BIGINT NOT NULL DEFAULT 0 COMMENT '群号 t_groups.id,便于按群查询',
sender_id BIGINT NOT NULL COMMENT '发送者 user_id;系统消息为 0', sender_id BIGINT NOT NULL COMMENT '发送者 user_id;系统消息为 0',
client_msg_id VARCHAR(64) NOT NULL COMMENT '客户端消息 id,与 sender+会话 组成幂等键', client_msg_id VARCHAR(64) NOT NULL COMMENT '客户端消息 id,与 sender+会话 组成幂等键',
message_type SMALLINT NOT NULL COMMENT '消息类型:1文本 2图片 3语音 4视频 5表情 6GIF 7文件 8位置 9系统', message_type SMALLINT NOT NULL COMMENT '消息类型:1文本 2图片 3语音 4视频 5表情 6GIF 7文件 8位置 9系统',
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS group_message (
KEY idx_group_id (group_id, id) KEY idx_group_id (group_id, id)
) COMMENT='群聊消息正文。PK=MsgId;群热路径不写 MySQL inbox 行'; ) COMMENT='群聊消息正文。PK=MsgId;群热路径不写 MySQL inbox 行';
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS t_messages (
id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属会话 id', conversation_id BIGINT NOT NULL COMMENT '所属会话 id',
sender_id BIGINT NOT NULL COMMENT '发送者 user_id', sender_id BIGINT NOT NULL COMMENT '发送者 user_id',
@ -41,22 +41,22 @@ CREATE TABLE IF NOT EXISTS messages (
created_at DATETIME(3) NOT NULL COMMENT '服务端入库时间', created_at DATETIME(3) NOT NULL COMMENT '服务端入库时间',
UNIQUE KEY uk_sender_conv_client_msg (sender_id, conversation_id, client_msg_id), UNIQUE KEY uk_sender_conv_client_msg (sender_id, conversation_id, client_msg_id),
KEY idx_conversation_id (conversation_id, id) KEY idx_conversation_id (conversation_id, id)
) COMMENT='迁移中转表。0011 拷贝到 private_message / group_message 后删除'; ) COMMENT='迁移中转表。0011 拷贝到 t_private_message / t_group_message 后删除';
INSERT IGNORE INTO private_message (id, conversation_id, sender_id, client_msg_id, message_type, content, status, created_at) INSERT IGNORE INTO t_private_message (id, conversation_id, sender_id, client_msg_id, message_type, content, status, created_at)
SELECT m.id, m.conversation_id, m.sender_id, m.client_msg_id, m.message_type, m.content, m.status, m.created_at SELECT m.id, m.conversation_id, m.sender_id, m.client_msg_id, m.message_type, m.content, m.status, m.created_at
FROM messages m FROM t_messages m
INNER JOIN conversations c ON c.id = m.conversation_id AND c.type = 1; INNER JOIN t_conversations c ON c.id = m.conversation_id AND c.type = 1;
INSERT IGNORE INTO group_message (id, conversation_id, sender_id, client_msg_id, message_type, content, status, created_at) INSERT IGNORE INTO t_group_message (id, conversation_id, sender_id, client_msg_id, message_type, content, status, created_at)
SELECT m.id, m.conversation_id, m.sender_id, m.client_msg_id, m.message_type, m.content, m.status, m.created_at SELECT m.id, m.conversation_id, m.sender_id, m.client_msg_id, m.message_type, m.content, m.status, m.created_at
FROM messages m FROM t_messages m
INNER JOIN conversations c ON c.id = m.conversation_id AND c.type = 2; INNER JOIN t_conversations c ON c.id = m.conversation_id AND c.type = 2;
INSERT IGNORE INTO private_message (id, conversation_id, sender_id, client_msg_id, message_type, content, status, created_at) INSERT IGNORE INTO t_private_message (id, conversation_id, sender_id, client_msg_id, message_type, content, status, created_at)
SELECT m.id, m.conversation_id, m.sender_id, m.client_msg_id, m.message_type, m.content, m.status, m.created_at SELECT m.id, m.conversation_id, m.sender_id, m.client_msg_id, m.message_type, m.content, m.status, m.created_at
FROM messages m FROM t_messages m
LEFT JOIN conversations c ON c.id = m.conversation_id LEFT JOIN t_conversations c ON c.id = m.conversation_id
WHERE c.id IS NULL; WHERE c.id IS NULL;
DROP TABLE IF EXISTS messages; DROP TABLE IF EXISTS t_messages;

View File

@ -1,7 +1,7 @@
-- Group announcement (owner/admin) and per-member remark (self only). -- Group announcement (owner/admin) and per-member remark (self only).
ALTER TABLE `groups` ALTER TABLE `t_groups`
ADD COLUMN announcement VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '群公告,仅群主/管理员可改' AFTER avatar; ADD COLUMN announcement VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '群公告,仅群主/管理员可改' AFTER avatar;
ALTER TABLE group_members ALTER TABLE t_group_members
ADD COLUMN remark VARCHAR(32) NOT NULL DEFAULT '' COMMENT '成员对该群的备注,仅自己可见' AFTER nickname; ADD COLUMN remark VARCHAR(32) NOT NULL DEFAULT '' COMMENT '成员对该群的备注,仅自己可见' AFTER nickname;

View File

@ -1,7 +1,7 @@
-- Pair-shaped friend_relations (user_small, user_big) → two directed rows. -- Pair-shaped t_friend_relations (user_small, user_big) → two directed rows.
-- Skipped by cmd/migrate when friend_relations.user_id already exists. -- Skipped by cmd/migrate when t_friend_relations.user_id already exists.
CREATE TABLE IF NOT EXISTS friend_relations_new ( CREATE TABLE IF NOT EXISTS t_friend_relations_new (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增', id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增',
user_id BIGINT NOT NULL COMMENT '关系所属用户', user_id BIGINT NOT NULL COMMENT '关系所属用户',
friend_id BIGINT NOT NULL COMMENT '对方 user_id', friend_id BIGINT NOT NULL COMMENT '对方 user_id',
@ -17,22 +17,22 @@ CREATE TABLE IF NOT EXISTS friend_relations_new (
KEY idx_user_status (user_id, status) KEY idx_user_status (user_id, status)
) COMMENT='单向好友边。A 与 B 互为好友时存两行,各自维护备注、拉黑、置顶'; ) COMMENT='单向好友边。A 与 B 互为好友时存两行,各自维护备注、拉黑、置顶';
INSERT IGNORE INTO friend_relations_new (user_id, friend_id, status, remark, is_blocked, is_pinned, source, created_at, updated_at) INSERT IGNORE INTO t_friend_relations_new (user_id, friend_id, status, remark, is_blocked, is_pinned, source, created_at, updated_at)
SELECT r.user_small, r.user_big, 1, IFNULL(n.remark, ''), 0, 0, 0, r.created_at, r.created_at SELECT r.user_small, r.user_big, 1, IFNULL(n.remark, ''), 0, 0, 0, r.created_at, r.created_at
FROM friend_relations r FROM t_friend_relations r
LEFT JOIN friend_remarks n ON n.user_id = r.user_small AND n.friend_id = r.user_big; LEFT JOIN t_friend_remarks n ON n.user_id = r.user_small AND n.friend_id = r.user_big;
INSERT IGNORE INTO friend_relations_new (user_id, friend_id, status, remark, is_blocked, is_pinned, source, created_at, updated_at) INSERT IGNORE INTO t_friend_relations_new (user_id, friend_id, status, remark, is_blocked, is_pinned, source, created_at, updated_at)
SELECT r.user_big, r.user_small, 1, IFNULL(n.remark, ''), 0, 0, 0, r.created_at, r.created_at SELECT r.user_big, r.user_small, 1, IFNULL(n.remark, ''), 0, 0, 0, r.created_at, r.created_at
FROM friend_relations r FROM t_friend_relations r
LEFT JOIN friend_remarks n ON n.user_id = r.user_big AND n.friend_id = r.user_small; LEFT JOIN t_friend_remarks n ON n.user_id = r.user_big AND n.friend_id = r.user_small;
INSERT INTO friend_relations_new (user_id, friend_id, status, remark, is_blocked, is_pinned, source, created_at, updated_at) INSERT INTO t_friend_relations_new (user_id, friend_id, status, remark, is_blocked, is_pinned, source, created_at, updated_at)
SELECT b.user_id, b.blocked_id, 0, '', 1, 0, 0, b.created_at, b.created_at SELECT b.user_id, b.blocked_id, 0, '', 1, 0, 0, b.created_at, b.created_at
FROM user_blocks b FROM t_user_blocks b
ON DUPLICATE KEY UPDATE is_blocked = 1, updated_at = VALUES(updated_at); ON DUPLICATE KEY UPDATE is_blocked = 1, updated_at = VALUES(updated_at);
DROP TABLE IF EXISTS friend_remarks; DROP TABLE IF EXISTS t_friend_remarks;
DROP TABLE IF EXISTS user_blocks; DROP TABLE IF EXISTS t_user_blocks;
DROP TABLE IF EXISTS friend_relations; DROP TABLE IF EXISTS t_friend_relations;
RENAME TABLE friend_relations_new TO friend_relations; RENAME TABLE t_friend_relations_new TO t_friend_relations;

View File

@ -1,23 +1,23 @@
-- Add denormalized query keys. Ignored when 0011 already created the columns. -- Add denormalized query keys. Ignored when 0011 already created the columns.
ALTER TABLE private_message ALTER TABLE t_private_message
ADD COLUMN receiver_id BIGINT NOT NULL DEFAULT 0 COMMENT '接收者 user_id;系统消息为 0'; ADD COLUMN receiver_id BIGINT NOT NULL DEFAULT 0 COMMENT '接收者 user_id;系统消息为 0';
ALTER TABLE private_message ALTER TABLE t_private_message
ADD KEY idx_receiver_id (receiver_id, id); ADD KEY idx_receiver_id (receiver_id, id);
UPDATE private_message pm UPDATE t_private_message pm
INNER JOIN p2p_conversations p ON p.conversation_id = pm.conversation_id INNER JOIN t_p2p_conversations p ON p.conversation_id = pm.conversation_id
SET pm.receiver_id = IF(pm.sender_id = p.user_small, p.user_big, IF(pm.sender_id = p.user_big, p.user_small, 0)) SET pm.receiver_id = IF(pm.sender_id = p.user_small, p.user_big, IF(pm.sender_id = p.user_big, p.user_small, 0))
WHERE pm.receiver_id = 0 AND pm.sender_id <> 0; WHERE pm.receiver_id = 0 AND pm.sender_id <> 0;
ALTER TABLE group_message ALTER TABLE t_group_message
ADD COLUMN group_id BIGINT NOT NULL DEFAULT 0 COMMENT '群号 groups.id,便于按群查询'; ADD COLUMN group_id BIGINT NOT NULL DEFAULT 0 COMMENT '群号 t_groups.id,便于按群查询';
ALTER TABLE group_message ALTER TABLE t_group_message
ADD KEY idx_group_id (group_id, id); ADD KEY idx_group_id (group_id, id);
UPDATE group_message gm UPDATE t_group_message gm
INNER JOIN `groups` g ON g.conversation_id = gm.conversation_id INNER JOIN `t_groups` g ON g.conversation_id = gm.conversation_id
SET gm.group_id = g.id SET gm.group_id = g.id
WHERE gm.group_id = 0; WHERE gm.group_id = 0;

View File

@ -1,8 +1,8 @@
-- 朋友圈。独立于 messages;Snowflake post_id 是主键、排序和游标。媒体在 perm/moment/,不过期,删帖时删对象。 -- 朋友圈。独立于 t_messages;Snowflake post_id 是主键、排序和游标。媒体在 perm/moment/,不过期,删帖时删对象。
CREATE TABLE IF NOT EXISTS moment_posts ( CREATE TABLE IF NOT EXISTS t_moment_posts (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 帖 id,Feed/相册游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake 帖 id,Feed/相册游标',
user_id BIGINT NOT NULL COMMENT '作者 users.id', user_id BIGINT NOT NULL COMMENT '作者 t_users.id',
kind SMALLINT NOT NULL COMMENT '1纯文 2图 3视频', kind SMALLINT NOT NULL COMMENT '1纯文 2图 3视频',
text VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正文,可空;与媒体至少有一项', text VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正文,可空;与媒体至少有一项',
content BLOB NOT NULL COMMENT '图/视频 JSON 字节,不存媒体二进制', content BLOB NOT NULL COMMENT '图/视频 JSON 字节,不存媒体二进制',
@ -12,26 +12,26 @@ CREATE TABLE IF NOT EXISTS moment_posts (
poi_address VARCHAR(256) NOT NULL DEFAULT '' COMMENT '地点地址', poi_address VARCHAR(256) NOT NULL DEFAULT '' COMMENT '地点地址',
visibility SMALLINT NOT NULL DEFAULT 0 COMMENT '0好友公开 1私密 2白名单 3黑名单', visibility SMALLINT NOT NULL DEFAULT 0 COMMENT '0好友公开 1私密 2白名单 3黑名单',
status SMALLINT NOT NULL DEFAULT 0 COMMENT '0正常 1已删', status SMALLINT NOT NULL DEFAULT 0 COMMENT '0正常 1已删',
like_count INT NOT NULL DEFAULT 0 COMMENT '点赞数,与 moment_likes 同事务维护', like_count INT NOT NULL DEFAULT 0 COMMENT '点赞数,与 t_moment_likes 同事务维护',
comment_count INT NOT NULL DEFAULT 0 COMMENT '未删评论数,与 moment_comments 同事务维护', comment_count INT NOT NULL DEFAULT 0 COMMENT '未删评论数,与 t_moment_comments 同事务维护',
created_at DATETIME(3) NOT NULL COMMENT '发帖时间,visible_days 按此裁剪', created_at DATETIME(3) NOT NULL COMMENT '发帖时间,visible_days 按此裁剪',
KEY idx_user_id (user_id, id) KEY idx_user_id (user_id, id)
) COMMENT='朋友圈帖。读时按好友+可见性过滤,不写每好友 inbox'; ) COMMENT='朋友圈帖。读时按好友+可见性过滤,不写每好友 inbox';
CREATE TABLE IF NOT EXISTS moment_audience ( CREATE TABLE IF NOT EXISTS t_moment_audience (
post_id BIGINT NOT NULL COMMENT '帖 id', post_id BIGINT NOT NULL COMMENT '帖 id',
peer_id BIGINT NOT NULL COMMENT '白名单能看 / 黑名单不能看 的好友 uid', peer_id BIGINT NOT NULL COMMENT '白名单能看 / 黑名单不能看 的好友 uid',
PRIMARY KEY (post_id, peer_id) PRIMARY KEY (post_id, peer_id)
) COMMENT='部分可见名单。仅 visibility=2/3 有行'; ) COMMENT='部分可见名单。仅 visibility=2/3 有行';
CREATE TABLE IF NOT EXISTS moment_likes ( CREATE TABLE IF NOT EXISTS t_moment_likes (
post_id BIGINT NOT NULL COMMENT '帖 id', post_id BIGINT NOT NULL COMMENT '帖 id',
user_id BIGINT NOT NULL COMMENT '点赞人', user_id BIGINT NOT NULL COMMENT '点赞人',
created_at DATETIME(3) NOT NULL COMMENT '点赞时间', created_at DATETIME(3) NOT NULL COMMENT '点赞时间',
PRIMARY KEY (post_id, user_id) PRIMARY KEY (post_id, user_id)
) COMMENT='朋友圈点赞。重复 PUT 幂等'; ) COMMENT='朋友圈点赞。重复 PUT 幂等';
CREATE TABLE IF NOT EXISTS moment_comments ( CREATE TABLE IF NOT EXISTS t_moment_comments (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 评论 id', id BIGINT PRIMARY KEY COMMENT 'Snowflake 评论 id',
post_id BIGINT NOT NULL COMMENT '帖 id', post_id BIGINT NOT NULL COMMENT '帖 id',
user_id BIGINT NOT NULL COMMENT '评论人', user_id BIGINT NOT NULL COMMENT '评论人',
@ -43,7 +43,7 @@ CREATE TABLE IF NOT EXISTS moment_comments (
KEY idx_post (post_id, id) KEY idx_post (post_id, id)
) COMMENT='朋友圈评论。展示按共同好友过滤'; ) COMMENT='朋友圈评论。展示按共同好友过滤';
CREATE TABLE IF NOT EXISTS moment_notices ( CREATE TABLE IF NOT EXISTS t_moment_notices (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 通知 id,列表游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake 通知 id,列表游标',
to_user_id BIGINT NOT NULL COMMENT '接收人', to_user_id BIGINT NOT NULL COMMENT '接收人',
from_user_id BIGINT NOT NULL COMMENT '动作发起人', from_user_id BIGINT NOT NULL COMMENT '动作发起人',
@ -55,15 +55,15 @@ CREATE TABLE IF NOT EXISTS moment_notices (
KEY idx_to_user (to_user_id, id) KEY idx_to_user (to_user_id, id)
) COMMENT='朋友圈赞评通知。不进会话 mailbox'; ) COMMENT='朋友圈赞评通知。不进会话 mailbox';
CREATE TABLE IF NOT EXISTS moment_settings ( CREATE TABLE IF NOT EXISTS t_moment_settings (
user_id BIGINT PRIMARY KEY COMMENT '用户 users.id', user_id BIGINT PRIMARY KEY COMMENT '用户 t_users.id',
cover_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 object_key,空表示未设', cover_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 object_key,空表示未设',
visible_days SMALLINT NOT NULL DEFAULT 0 COMMENT '允许朋友查看范围:0全部 3/30/180 天', visible_days SMALLINT NOT NULL DEFAULT 0 COMMENT '允许朋友查看范围:0全部 3/30/180 天',
last_read_post_id BIGINT NOT NULL DEFAULT 0 COMMENT '发现页红点水位,只增不减', last_read_post_id BIGINT NOT NULL DEFAULT 0 COMMENT '发现页红点水位,只增不减',
updated_at DATETIME(3) NOT NULL COMMENT '最近更新时间' updated_at DATETIME(3) NOT NULL COMMENT '最近更新时间'
) COMMENT='朋友圈个人设置。无行则封面空、范围全部、水位 0'; ) COMMENT='朋友圈个人设置。无行则封面空、范围全部、水位 0';
CREATE TABLE IF NOT EXISTS moment_hides ( CREATE TABLE IF NOT EXISTS t_moment_hides (
user_id BIGINT NOT NULL COMMENT '设置方', user_id BIGINT NOT NULL COMMENT '设置方',
peer_id BIGINT NOT NULL COMMENT '对方', peer_id BIGINT NOT NULL COMMENT '对方',
hide_him TINYINT NOT NULL DEFAULT 0 COMMENT '1=不看他(Feed 去掉对方)', hide_him TINYINT NOT NULL DEFAULT 0 COMMENT '1=不看他(Feed 去掉对方)',

View File

@ -1,62 +1,62 @@
-- Junction / mapping tables get a table-local AUTO_INCREMENT row id. -- Junction / mapping tables get a table-local AUTO_INCREMENT row id.
-- Business uniqueness stays UNIQUE (same columns as the old composite PK). -- Business uniqueness stays UNIQUE (same columns as the old composite PK).
-- Snowflake PKs (users / messages / conversations / moments posts) are unchanged. -- Snowflake PKs (t_users / t_messages / t_conversations / moments posts) are unchanged.
ALTER TABLE user_emails ALTER TABLE t_user_emails
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 email' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 email' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_email (email); ADD UNIQUE KEY uk_email (email);
ALTER TABLE p2p_conversations ALTER TABLE t_p2p_conversations
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_small, user_big)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_small, user_big)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_p2p_users (user_small, user_big); ADD UNIQUE KEY uk_p2p_users (user_small, user_big);
ALTER TABLE conversation_members ALTER TABLE t_conversation_members
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (conversation_id, user_id)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (conversation_id, user_id)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_conversation_members (conversation_id, user_id); ADD UNIQUE KEY uk_conversation_members (conversation_id, user_id);
ALTER TABLE device_sync_cursors ALTER TABLE t_device_sync_cursors
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_user_device (user_id, device_id); ADD UNIQUE KEY uk_user_device (user_id, device_id);
ALTER TABLE group_members ALTER TABLE t_group_members
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (group_id, user_id)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (group_id, user_id)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_group_members (group_id, user_id); ADD UNIQUE KEY uk_group_members (group_id, user_id);
ALTER TABLE message_deletes ALTER TABLE t_message_deletes
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, msg_id)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, msg_id)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_user_msg (user_id, msg_id); ADD UNIQUE KEY uk_user_msg (user_id, msg_id);
ALTER TABLE moment_audience ALTER TABLE t_moment_audience
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (post_id, peer_id)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (post_id, peer_id)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_post_peer (post_id, peer_id); ADD UNIQUE KEY uk_post_peer (post_id, peer_id);
ALTER TABLE moment_likes ALTER TABLE t_moment_likes
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (post_id, user_id)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (post_id, user_id)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_post_user (post_id, user_id); ADD UNIQUE KEY uk_post_user (post_id, user_id);
ALTER TABLE moment_settings ALTER TABLE t_moment_settings
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 user_id' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 user_id' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_user_id (user_id); ADD UNIQUE KEY uk_user_id (user_id);
ALTER TABLE moment_hides ALTER TABLE t_moment_hides
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, peer_id)' FIRST, ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, peer_id)' FIRST,
ADD PRIMARY KEY (id), ADD PRIMARY KEY (id),

View File

@ -1,7 +1,7 @@
-- Archived accounts. Live rows stay in users; cancellation copies then deletes. -- Archived accounts. Live rows stay in t_users; cancellation copies then deletes.
CREATE TABLE IF NOT EXISTS user_delete ( CREATE TABLE IF NOT EXISTS t_user_delete (
id BIGINT PRIMARY KEY COMMENT '原 users.id(uid),Snowflake,不复用', id BIGINT PRIMARY KEY COMMENT '原 t_users.id(uid),Snowflake,不复用',
spark_id VARCHAR(32) NOT NULL COMMENT '注销时的 Spark 账号', spark_id VARCHAR(32) NOT NULL COMMENT '注销时的 Spark 账号',
nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '注销时的昵称', nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '注销时的昵称',
avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '注销时的头像 object_key', avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '注销时的头像 object_key',
@ -18,4 +18,4 @@ CREATE TABLE IF NOT EXISTS user_delete (
KEY idx_email (email), KEY idx_email (email),
KEY idx_spark_id (spark_id), KEY idx_spark_id (spark_id),
KEY idx_deleted_at (deleted_at) KEY idx_deleted_at (deleted_at)
) COMMENT='已注销用户档案。从 users 迁入后删除原行,邮箱与 spark_id 可重新注册'; ) COMMENT='已注销用户档案。从 t_users 迁入后删除原行,邮箱与 spark_id 可重新注册';

View File

@ -1,8 +1,8 @@
-- 广场。Snowflake post_id;content 为 VARBINARY 序列化 JSON 字节。 -- 广场。Snowflake post_id;content 为 VARBINARY 序列化 JSON 字节。
CREATE TABLE IF NOT EXISTS square_posts ( CREATE TABLE IF NOT EXISTS t_square_posts (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 帖 id,Feed 游标', id BIGINT PRIMARY KEY COMMENT 'Snowflake 帖 id,Feed 游标',
user_id BIGINT NOT NULL COMMENT '作者 users.id', user_id BIGINT NOT NULL COMMENT '作者 t_users.id',
kind SMALLINT NOT NULL COMMENT '1纯文 2图 3视频', kind SMALLINT NOT NULL COMMENT '1纯文 2图 3视频',
text VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正文,可空;与媒体至少有一项', text VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正文,可空;与媒体至少有一项',
content VARBINARY(16384) NOT NULL COMMENT '序列化 JSON 字节(json.Marshal),图/视频只存 object_key', content VARBINARY(16384) NOT NULL COMMENT '序列化 JSON 字节(json.Marshal),图/视频只存 object_key',
@ -22,20 +22,20 @@ CREATE TABLE IF NOT EXISTS square_posts (
KEY idx_user (user_id, id) KEY idx_user (user_id, id)
) COMMENT='广场帖'; ) COMMENT='广场帖';
CREATE TABLE IF NOT EXISTS square_audience ( CREATE TABLE IF NOT EXISTS t_square_audience (
post_id BIGINT NOT NULL COMMENT '帖 id', post_id BIGINT NOT NULL COMMENT '帖 id',
peer_id BIGINT NOT NULL COMMENT '白名单能看 / 黑名单不能看 的好友 uid', peer_id BIGINT NOT NULL COMMENT '白名单能看 / 黑名单不能看 的好友 uid',
PRIMARY KEY (post_id, peer_id) PRIMARY KEY (post_id, peer_id)
) COMMENT='广场部分可见名单'; ) COMMENT='广场部分可见名单';
CREATE TABLE IF NOT EXISTS square_likes ( CREATE TABLE IF NOT EXISTS t_square_likes (
post_id BIGINT NOT NULL COMMENT '帖 id', post_id BIGINT NOT NULL COMMENT '帖 id',
user_id BIGINT NOT NULL COMMENT '点赞人', user_id BIGINT NOT NULL COMMENT '点赞人',
created_at DATETIME(3) NOT NULL COMMENT '点赞时间', created_at DATETIME(3) NOT NULL COMMENT '点赞时间',
PRIMARY KEY (post_id, user_id) PRIMARY KEY (post_id, user_id)
) COMMENT='广场点赞'; ) COMMENT='广场点赞';
CREATE TABLE IF NOT EXISTS square_comments ( CREATE TABLE IF NOT EXISTS t_square_comments (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 评论 id', id BIGINT PRIMARY KEY COMMENT 'Snowflake 评论 id',
post_id BIGINT NOT NULL COMMENT '帖 id', post_id BIGINT NOT NULL COMMENT '帖 id',
user_id BIGINT NOT NULL COMMENT '评论人', user_id BIGINT NOT NULL COMMENT '评论人',
@ -47,7 +47,7 @@ CREATE TABLE IF NOT EXISTS square_comments (
KEY idx_post (post_id, id) KEY idx_post (post_id, id)
) COMMENT='广场评论'; ) COMMENT='广场评论';
CREATE TABLE IF NOT EXISTS square_notices ( CREATE TABLE IF NOT EXISTS t_square_notices (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 通知 id', id BIGINT PRIMARY KEY COMMENT 'Snowflake 通知 id',
to_user_id BIGINT NOT NULL COMMENT '接收人', to_user_id BIGINT NOT NULL COMMENT '接收人',
from_user_id BIGINT NOT NULL COMMENT '动作发起人', from_user_id BIGINT NOT NULL COMMENT '动作发起人',
@ -59,8 +59,8 @@ CREATE TABLE IF NOT EXISTS square_notices (
KEY idx_to_user (to_user_id, id) KEY idx_to_user (to_user_id, id)
) COMMENT='广场赞评通知'; ) COMMENT='广场赞评通知';
CREATE TABLE IF NOT EXISTS square_notice_state ( CREATE TABLE IF NOT EXISTS t_square_notice_state (
user_id BIGINT PRIMARY KEY COMMENT '用户 users.id', user_id BIGINT PRIMARY KEY COMMENT '用户 t_users.id',
last_read_notice_id BIGINT NOT NULL DEFAULT 0 COMMENT '互动通知已读水位', last_read_notice_id BIGINT NOT NULL DEFAULT 0 COMMENT '互动通知已读水位',
updated_at DATETIME(3) NOT NULL COMMENT '更新时间' updated_at DATETIME(3) NOT NULL COMMENT '更新时间'
) COMMENT='广场通知未读水位'; ) COMMENT='广场通知未读水位';

View File

@ -1,6 +1,6 @@
-- Physical devices registry + slim user_devices + login log app_id. -- Physical t_devices registry + slim t_user_devices + login log app_id.
CREATE TABLE IF NOT EXISTS devices ( CREATE TABLE IF NOT EXISTS t_devices (
device_id BIGINT UNSIGNED NOT NULL COMMENT 'JWT did;多 App 同机共用', device_id BIGINT UNSIGNED NOT NULL COMMENT 'JWT did;多 App 同机共用',
fingerprint_hash CHAR(64) NOT NULL COMMENT '物理指纹 SHA256 hex,不含 app_id', fingerprint_hash CHAR(64) NOT NULL COMMENT '物理指纹 SHA256 hex,不含 app_id',
device_type TINYINT UNSIGNED NOT NULL COMMENT 'device_type 枚举 1=ios 2=android …', device_type TINYINT UNSIGNED NOT NULL COMMENT 'device_type 枚举 1=ios 2=android …',
@ -14,15 +14,15 @@ CREATE TABLE IF NOT EXISTS devices (
UNIQUE KEY uk_fingerprint (fingerprint_hash) UNIQUE KEY uk_fingerprint (fingerprint_hash)
) COMMENT='物理设备;与用户、App 无关'; ) COMMENT='物理设备;与用户、App 无关';
ALTER TABLE user_login_logs ALTER TABLE t_user_login_logs
ADD COLUMN app_id TINYINT UNSIGNED NOT NULL DEFAULT 0 ADD COLUMN app_id TINYINT UNSIGNED NOT NULL DEFAULT 0
COMMENT '当次登录 app_id' AFTER device_id; COMMENT '当次登录 app_id' AFTER device_id;
ALTER TABLE user_devices ALTER TABLE t_user_devices
ADD COLUMN last_active_at DATETIME(3) NULL COMMENT '该用户在此设备最近活跃' AFTER last_login_at; ADD COLUMN last_active_at DATETIME(3) NULL COMMENT '该用户在此设备最近活跃' AFTER last_login_at;
ALTER TABLE user_devices DROP COLUMN platform; ALTER TABLE t_user_devices DROP COLUMN platform;
ALTER TABLE user_devices DROP COLUMN device_name; ALTER TABLE t_user_devices DROP COLUMN device_name;
ALTER TABLE user_devices DROP COLUMN app_version; ALTER TABLE t_user_devices DROP COLUMN app_version;
ALTER TABLE user_devices DROP COLUMN language; ALTER TABLE t_user_devices DROP COLUMN language;
ALTER TABLE user_devices DROP COLUMN last_ip; ALTER TABLE t_user_devices DROP COLUMN last_ip;

View File

@ -1,6 +1,6 @@
-- 运营音乐列表 + 全局会员试听时长。 -- 运营音乐列表 + 全局会员试听时长。
CREATE TABLE IF NOT EXISTS music_tracks ( CREATE TABLE IF NOT EXISTS t_music_tracks (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '曲目自增 id', id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '曲目自增 id',
title VARCHAR(256) NOT NULL DEFAULT '' COMMENT '曲名', title VARCHAR(256) NOT NULL DEFAULT '' COMMENT '曲名',
cover_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 object_key(sparkr)', cover_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 object_key(sparkr)',
@ -15,12 +15,12 @@ CREATE TABLE IF NOT EXISTS music_tracks (
KEY idx_music_list (enabled, sort_order, id) KEY idx_music_list (enabled, sort_order, id)
) COMMENT='运营音乐曲目'; ) COMMENT='运营音乐曲目';
CREATE TABLE IF NOT EXISTS music_settings ( CREATE TABLE IF NOT EXISTS t_music_settings (
setting_key VARCHAR(64) PRIMARY KEY COMMENT '配置键', setting_key VARCHAR(64) PRIMARY KEY COMMENT '配置键',
setting_value VARCHAR(256) NOT NULL DEFAULT '' COMMENT '配置值', setting_value VARCHAR(256) NOT NULL DEFAULT '' COMMENT '配置值',
updated_at DATETIME(3) NOT NULL COMMENT '更新时间' updated_at DATETIME(3) NOT NULL COMMENT '更新时间'
) COMMENT='音乐全局配置(如会员免费试听秒数)'; ) COMMENT='音乐全局配置(如会员免费试听秒数)';
INSERT INTO music_settings (setting_key, setting_value, updated_at) INSERT INTO t_music_settings (setting_key, setting_value, updated_at)
VALUES ('member_preview_seconds', '30', CURRENT_TIMESTAMP(3)) VALUES ('member_preview_seconds', '30', CURRENT_TIMESTAMP(3))
ON DUPLICATE KEY UPDATE setting_key = setting_key; ON DUPLICATE KEY UPDATE setting_key = setting_key;