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

View File

@ -379,8 +379,8 @@ TaskPool:
2. 权限(见 9.4),失败不写库
3. MsgId = 请求带来的或 Snowflake
4. BEGIN
INSERT messages
UPDATE conversations.last_msg_id / last_msg_at
INSERT t_messages
UPDATE t_conversations.last_msg_id / last_msg_at
COMMIT
5. DeliverQueue.Push // 可靠入队,失败则重试有限次
6. 仍失败 → 不 SEND_ACK,客户端用 client_msg_id 重试
@ -406,7 +406,7 @@ P2P 好友关系、未拉黑、对方未禁你
## 10.1 messages
```sql
CREATE TABLE messages (
CREATE TABLE t_messages (
id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属会话 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`。
```sql
SELECT * FROM messages
SELECT * FROM t_messages
WHERE conversation_id = ? AND id > ?
ORDER BY id ASC LIMIT 200;
```
@ -431,7 +431,7 @@ SELECT * FROM messages
## 10.2 conversations / P2P 唯一 / members
```sql
CREATE TABLE conversations (
CREATE TABLE t_conversations (
id BIGINT PRIMARY KEY,
type TINYINT NOT NULL, -- 1=P2P 2=GROUP
last_msg_id BIGINT NOT NULL DEFAULT 0,
@ -440,7 +440,7 @@ CREATE TABLE conversations (
);
-- 两人永远同一条会话,学 BFF 小 uid : 大 uid
CREATE TABLE p2p_conversations (
CREATE TABLE t_p2p_conversations (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_small BIGINT NOT NULL,
user_big BIGINT NOT NULL,
@ -449,7 +449,7 @@ CREATE TABLE p2p_conversations (
UNIQUE KEY uk_conversation (conversation_id)
);
CREATE TABLE conversation_members (
CREATE TABLE t_conversation_members (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
conversation_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
@ -469,7 +469,7 @@ CREATE TABLE conversation_members (
## 10.3 mailbox_events(失败邮箱 + 控制事件)
```sql
CREATE TABLE mailbox_events (
CREATE TABLE t_mailbox_events (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
device_id BIGINT NOT NULL, -- 0 = 该用户所有设备
@ -482,7 +482,7 @@ CREATE TABLE mailbox_events (
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,
user_id BIGINT NOT NULL,
device_id BIGINT NOT NULL,
@ -817,7 +817,7 @@ Client TLS :443
Nginx SNI im.example.com + PROXY
gnet 读真实 IP → TLS → Frame
TaskPool
权限 → INSERT messages → RPUSH 可靠队列 → SEND_ACK
权限 → INSERT t_messages → RPUSH 可靠队列 → SEND_ACK
Deliver Worker
幂等 → ZSET 列表 → 在线 MESSAGE
失败 → mailbox + 可选系统推送

View File

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

View File

@ -27,9 +27,9 @@ func (m *Repo) ListComplaints(ctx context.Context, limit int) ([]ComplaintRow, e
err = db.Raw(`
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
FROM friend_reports r
LEFT JOIN user_emails e ON e.user_id = r.user_id
LEFT JOIN user_emails te ON te.user_id = r.target_id
FROM t_friend_reports r
LEFT JOIN t_user_emails e ON e.user_id = r.user_id
LEFT JOIN t_user_emails te ON te.user_id = r.target_id
ORDER BY r.id DESC
LIMIT ?`, limit).Scan(&rows).Error
if err != nil {
@ -53,6 +53,6 @@ func (m *Repo) InsertComplaint(ctx context.Context, r *friendentity.Report) erro
if err != nil {
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)
}

View File

@ -36,7 +36,7 @@ func (m *Repo) ListGroups(ctx context.Context, q string) ([]GroupRow, error) {
if err != nil {
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)
if q != "" {
like := "%" + q + "%"
@ -63,7 +63,7 @@ func (m *Repo) GroupByID(ctx context.Context, groupID int64) (*GroupRow, error)
return nil, err
}
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)
}
row := groupScanToRow(r)
@ -78,7 +78,7 @@ func (m *Repo) ActiveGroupOwner(ctx context.Context, groupID int64) (int64, erro
var r struct {
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)).
Take(&r).Error
if err != nil {
@ -105,9 +105,9 @@ func (m *Repo) ListGroupMembers(ctx context.Context, groupID int64) ([]GroupMemb
var rows []scanRow
err = db.Raw(`
SELECT m.user_id, m.role, m.nickname, COALESCE(e.email,'') AS email, COALESCE(u.nickname,'') AS u_nickname
FROM group_members m
LEFT JOIN users u ON u.id = m.user_id
LEFT JOIN user_emails e ON e.user_id = m.user_id
FROM t_group_members m
LEFT JOIN t_users u ON u.id = m.user_id
LEFT JOIN t_user_emails e ON e.user_id = m.user_id
WHERE m.group_id = ?
ORDER BY m.role ASC, m.joined_at ASC
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{}
var priv, grp, revokedPriv, revokedGrp int64
_ = db.Table("private_message").Count(&priv).Error
_ = db.Table("group_message").Count(&grp).Error
_ = db.Table("private_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedPriv).Error
_ = db.Table("group_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedGrp).Error
_ = db.Table("t_private_message").Count(&priv).Error
_ = db.Table("t_group_message").Count(&grp).Error
_ = db.Table("t_private_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedPriv).Error
_ = db.Table("t_group_message").Where("status = ?", msgentity.StatusRecalled).Count(&revokedGrp).Error
out.MessageCount = priv + grp
out.RevokedCount = revokedPriv + revokedGrp
_ = db.Table("conversations").Count(&out.ConversationCount).Error
_ = db.Table("`groups`").Where("status = ?", 1).Count(&out.GroupCount).Error
_ = db.Table("t_conversations").Count(&out.ConversationCount).Error
_ = db.Table("`t_groups`").Where("status = ?", 1).Count(&out.GroupCount).Error
return out, nil
}
@ -33,10 +33,10 @@ func (m *Repo) ListRecentMessages(ctx context.Context, fetchLimit int) ([]Messag
}
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
FROM private_message
FROM t_private_message
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
FROM group_message`
FROM t_group_message`
type scanRow struct {
ID int64 `gorm:"column: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,
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
FROM conversation_members cm
INNER JOIN conversations c ON c.id = cm.conversation_id
LEFT JOIN p2p_conversations p ON p.conversation_id = c.id
LEFT JOIN `+"`groups`"+` g ON g.conversation_id = c.id
FROM t_conversation_members cm
INNER JOIN t_conversations c ON c.id = cm.conversation_id
LEFT JOIN t_p2p_conversations p ON p.conversation_id = c.id
LEFT JOIN `+"`t_groups`"+` g ON g.conversation_id = c.id
WHERE cm.user_id = ?
ORDER BY c.last_msg_at DESC
LIMIT 200`, userID).Scan(&rows).Error
@ -127,7 +127,7 @@ func (m *Repo) ListGroupMessages(ctx context.Context, groupID int64, limit int)
var rows []scanRow
err = db.Raw(`
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 {
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 {
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)
if 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,
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.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform`
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 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) {
db, err := m.session(ctx)
@ -42,10 +42,10 @@ func (m *Repo) UsersByEmail(ctx context.Context, email string) ([]UserRow, error
err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
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.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform
FROM user_emails e
INNER JOIN users u ON u.id = e.user_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 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 t_user_emails e
INNER JOIN t_users u ON u.id = e.user_id
WHERE e.email = ?`, email).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
@ -62,10 +62,10 @@ func (m *Repo) UserByID(ctx context.Context, id int64) (*UserRow, error) {
err = db.Raw(`
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((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.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform
FROM users u
LEFT JOIN user_emails e ON e.user_id = u.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 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 t_users u
LEFT JOIN t_user_emails e ON e.user_id = u.id
WHERE u.id = ?`, id).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
@ -86,8 +86,8 @@ func (m *Repo) UsersByNickname(ctx context.Context, nickname string) ([]UserRow,
err = db.Raw(`
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
FROM users u
LEFT JOIN user_emails e ON e.user_id = u.id
FROM t_users u
LEFT JOIN t_user_emails e ON e.user_id = u.id
WHERE u.nickname LIKE ?
ORDER BY u.id DESC LIMIT 50`, "%"+strings.TrimSpace(nickname)+"%").Scan(&rows).Error
if err != nil {
@ -104,10 +104,11 @@ func (m *Repo) UsersByDevice(ctx context.Context, deviceID string, deviceIDNum u
var rows []userScanRow
err = db.Raw(`
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
FROM user_devices d
INNER JOIN users u ON u.id = d.user_id
LEFT JOIN user_emails e ON e.user_id = u.id
COALESCE(e.email,'') AS email, d.device_id, CAST(dev.device_type AS CHAR) AS platform
FROM t_user_devices d
INNER JOIN t_devices dev ON dev.device_id = d.device_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 = ?
ORDER BY d.last_login_at DESC LIMIT 50`, deviceID, deviceIDNum).Scan(&rows).Error
if err != nil {
@ -124,11 +125,12 @@ func (m *Repo) UsersByIP(ctx context.Context, ip string) ([]UserRow, error) {
var rows []userScanRow
err = db.Raw(`
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
FROM user_devices d
INNER JOIN users u ON u.id = d.user_id
LEFT JOIN user_emails e ON e.user_id = u.id
WHERE d.last_ip = ?
COALESCE(e.email,'') AS email, d.device_id, CAST(dev.device_type AS CHAR) AS platform
FROM t_user_devices d
INNER JOIN t_devices dev ON dev.device_id = d.device_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 dev.last_client_ip = ?
ORDER BY u.id DESC LIMIT 100`, ip).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
@ -142,8 +144,8 @@ func (m *Repo) SetUserStatusByEmail(ctx context.Context, email string, status us
return err
}
res := db.Exec(`
UPDATE users u
INNER JOIN user_emails e ON e.user_id = u.id
UPDATE t_users u
INNER JOIN t_user_emails e ON e.user_id = u.id
SET u.status = ?, u.updated_at = ?
WHERE e.email = ?`, int(status), time.Now(), email)
if res.Error != nil {
@ -161,8 +163,8 @@ func (m *Repo) SetNicknameByEmail(ctx context.Context, email, nickname string) e
return err
}
res := db.Exec(`
UPDATE users u
INNER JOIN user_emails e ON e.user_id = u.id
UPDATE t_users u
INNER JOIN t_user_emails e ON e.user_id = u.id
SET u.nickname = ?, u.updated_at = ?
WHERE e.email = ?`, nickname, time.Now(), email)
if res.Error != nil {
@ -180,8 +182,8 @@ func (m *Repo) ClearAvatarByEmail(ctx context.Context, email string) error {
return err
}
res := db.Exec(`
UPDATE users u
INNER JOIN user_emails e ON e.user_id = u.id
UPDATE t_users u
INNER JOIN t_user_emails e ON e.user_id = u.id
SET u.avatar = '', u.updated_at = ?
WHERE e.email = ?`, time.Now(), email)
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"`
}
err = db.Raw(`
SELECT last_ip AS ip, COUNT(DISTINCT user_id) AS user_count, COUNT(*) AS device_count
FROM user_devices
WHERE last_ip IS NOT NULL AND last_ip <> '' AND last_ip NOT LIKE '127.%' AND last_ip <> '::1'
GROUP BY last_ip
SELECT dev.last_client_ip AS ip, COUNT(DISTINCT d.user_id) AS user_count, COUNT(*) AS device_count
FROM t_user_devices d
INNER JOIN t_devices dev ON dev.device_id = d.device_id
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
LIMIT ?`, limit).Scan(&rows).Error
if err != nil {
@ -232,7 +235,7 @@ func (m *Repo) LookupUserIDByEmail(ctx context.Context, email string) (int64, er
return 0, err
}
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)
}

View File

@ -12,4 +12,4 @@ type Conversation struct {
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=免打扰"`
}
func (ConversationMember) TableName() string { return "conversation_members" }
func (ConversationMember) TableName() string { return "t_conversation_members" }
func (r *ConversationMember) entity() *entity.Member {
if r == nil {

View File

@ -8,4 +8,4 @@ type P2PConversation struct {
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"`
}
func (DeviceRow) TableName() string { return "devices" }
func (DeviceRow) TableName() string { return "t_devices" }
type RegistryRepo struct {
gdb *gorm.DB

View File

@ -17,7 +17,7 @@ type Favorite struct {
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 {
if r == nil {

View File

@ -25,7 +25,7 @@ type FriendRelation struct {
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 {
if r == nil {

View File

@ -18,7 +18,7 @@ type FriendReport struct {
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 {
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:最近一次申请或处理时间"`
}
func (FriendRequest) TableName() string { return "friend_requests" }
func (FriendRequest) TableName() string { return "t_friend_requests" }
func (r *FriendRequest) entity() *entity.Request {
if r == nil {

View File

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

View File

@ -8,10 +8,10 @@ import (
"spark-server/internal/pkg/errs"
)
// Group maps table groups.
// Group maps table t_groups.
type Group struct {
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:群名称"`
Avatar string `gorm:"column:avatar;comment:群头像对象存储 key,空表示未设置"`
Announcement string `gorm:"column:announcement;comment:群公告,仅群主/管理员可改"`
@ -23,7 +23,7 @@ type Group struct {
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 {
if r == nil {
@ -98,8 +98,8 @@ func (m *Repo) ListByUser(ctx context.Context, userID int64, limit int) ([]*enti
}
limit = entity.ClampLimit(limit)
var rows []Group
err = db.Table("`groups` AS g").Select("g.*").
Joins("INNER JOIN group_members m ON m.group_id = g.id").
err = db.Table("`t_groups` AS g").Select("g.*").
Joins("INNER JOIN t_group_members m ON m.group_id = g.id").
Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive).
Order("g.updated_at DESC").Limit(limit).Find(&rows).Error
if err != nil {
@ -123,8 +123,8 @@ func (m *Repo) ListAllActiveByUser(ctx context.Context, userID int64) ([]*entity
return nil, err
}
var rows []Group
err = db.Table("`groups` AS g").Select("g.*").
Joins("INNER JOIN group_members m ON m.group_id = g.id").
err = db.Table("`t_groups` AS g").Select("g.*").
Joins("INNER JOIN t_group_members m ON m.group_id = g.id").
Where("m.user_id = ? AND g.status = ?", userID, entity.StatusActive).
Order("g.updated_at DESC").Limit(purgeListCap).Find(&rows).Error
if err != nil {
@ -166,7 +166,7 @@ func (m *Repo) UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS in
if err != nil {
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_at": msTime(atMS),
})

View File

@ -20,7 +20,7 @@ type GroupMember struct {
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 {
if r == nil {

View File

@ -11,4 +11,4 @@ type MessageDelete struct {
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"
)
// 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 {
gdb *gorm.DB
}
@ -45,7 +45,7 @@ func mapDBErr(err error) error {
func (m *Repo) lookupKind(db *gorm.DB, conversationID int64) (entity.Kind, error) {
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)
}
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 {
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_at": row.CreatedAt,
})
@ -197,7 +197,7 @@ func (m *Repo) fillGroupID(db *gorm.DB, message *entity.Message) error {
return nil
}
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)
}
if gid <= 0 {
@ -212,7 +212,7 @@ func (m *Repo) fillReceiverID(db *gorm.DB, message *entity.Message) error {
return nil
}
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).
Limit(1).Scan(&peer).Error
if err != nil {

View File

@ -7,8 +7,8 @@ import (
)
const (
tablePrivateMessage = "private_message"
tableGroupMessage = "group_message"
tablePrivateMessage = "t_private_message"
tableGroupMessage = "t_group_message"
)
// 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 {
sql := `UPDATE 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
sql := `UPDATE t_moment_posts p
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
WHERE p.user_id <> ?`
return mapDBErr(tx.Exec(sql, userID, userID).Error)
}
func bumpOffUserComments(tx *gorm.DB, userID int64) error {
sql := `UPDATE 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
sql := `UPDATE t_moment_posts p
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
WHERE p.user_id <> ?`
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 按此裁剪"`
}
func (Post) TableName() string { return "moment_posts" }
func (Post) TableName() string { return "t_moment_posts" }
func (r *Post) Entity() *entity.Post {
if r == nil {
@ -61,7 +61,7 @@ type Audience struct {
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.
type Like struct {
@ -71,7 +71,7 @@ type Like struct {
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 {
if r == nil {
@ -92,7 +92,7 @@ type Comment struct {
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 {
if r == nil {
@ -130,7 +130,7 @@ type Notice struct {
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 {
if r == nil {
@ -156,7 +156,7 @@ type Setting struct {
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 {
if r == nil {
@ -190,7 +190,7 @@ type Hide struct {
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 {
if r == nil {

View File

@ -40,7 +40,7 @@ type trackRow struct {
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 {
SettingKey string `gorm:"column:setting_key;primaryKey"`
@ -48,7 +48,7 @@ type settingRow struct {
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) {
if !r.Ready() {

View File

@ -27,7 +27,7 @@ type Post struct {
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 {
if r == nil {
@ -72,7 +72,7 @@ type Audience struct {
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.
type Like struct {
@ -81,7 +81,7 @@ type Like struct {
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 {
if r == nil {
@ -102,7 +102,7 @@ type Comment struct {
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 {
if r == nil {
@ -140,7 +140,7 @@ type Notice struct {
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 {
if r == nil {
@ -163,7 +163,7 @@ type NoticeState struct {
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 {
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 {
sql := `UPDATE 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
sql := `UPDATE t_square_posts p
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
WHERE p.user_id <> ?`
return mapDBErr(tx.Exec(sql, userID, userID).Error)
}
func bumpOffUserComments(tx *gorm.DB, userID int64) error {
sql := `UPDATE 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
sql := `UPDATE t_square_posts p
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
WHERE p.user_id <> ?`
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:游标更新时间"`
}
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:写入时间"`
}
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
Email string `gorm:"column:email"`
}
err = db.Table("users AS u").
err = db.Table("t_users AS u").
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").
Find(&rows).Error
if err != nil {

View File

@ -24,7 +24,7 @@ type User struct {
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 {
if r == nil {

View File

@ -28,7 +28,7 @@ type UserDelete struct {
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 {
if r == nil {

View File

@ -18,7 +18,7 @@ type UserDevice struct {
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 {
if row == nil {

View File

@ -17,7 +17,7 @@ type UserEmail struct {
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) {
db, err := m.session(ctx)

View File

@ -21,7 +21,7 @@ type UserLoginLog struct {
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 {
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.
CREATE TABLE IF NOT EXISTS conversations (
CREATE TABLE IF NOT EXISTS t_conversations (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 会话 id,时间有序主键',
type TINYINT NOT NULL COMMENT '会话类型:1=单聊 2=群聊',
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 '会话创建时间'
) 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_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),
UNIQUE KEY uk_conversation (conversation_id)
) 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',
user_id BIGINT NOT NULL COMMENT '成员 user_id',
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)
) 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,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属会话 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)
) 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',
user_id BIGINT NOT NULL COMMENT '接收用户',
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)
) 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',
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 游标',
updated_at DATETIME(3) NOT NULL COMMENT '游标更新时间',
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),时间有序,非自增',
spark_id VARCHAR(32) NOT NULL 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)',
updated_at DATETIME(3) NOT NULL COMMENT '资料最近更新时间(update_time)',
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 '规范化后的小写邮箱,登录账号',
user_id BIGINT NOT NULL COMMENT '绑定的 users.id,一对一',
user_id BIGINT NOT NULL COMMENT '绑定的 t_users.id,一对一',
created_at DATETIME(3) NOT NULL COMMENT '绑定时间',
PRIMARY KEY (email),
UNIQUE KEY uk_user_id (user_id)
) COMMENT='邮箱与用户的一对一映射。登录用邮箱查此表再读 users';
) COMMENT='邮箱与用户的一对一映射。登录用邮箱查此表再读 t_users';
CREATE TABLE IF NOT EXISTS user_devices (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)',
user_id BIGINT NOT NULL COMMENT '所属用户',
device_id BIGINT UNSIGNED NOT NULL COMMENT '客户端设备 id,写入 JWT did',
platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'ios/android/web 等',
device_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '设备展示名',
app_version VARCHAR(32) NOT NULL DEFAULT '' 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 '该设备首次登录时间',
CREATE TABLE IF NOT EXISTS t_user_devices (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)',
user_id BIGINT NOT NULL COMMENT '所属用户',
device_id BIGINT UNSIGNED NOT NULL COMMENT 't_devices.device_id,JWT did',
last_login_at DATETIME(3) NOT NULL COMMENT '该用户在此设备最近登录成功',
last_active_at DATETIME(3) NULL COMMENT '该用户在此设备最近活跃',
created_at DATETIME(3) NOT NULL COMMENT '该用户首次在此设备绑定',
PRIMARY KEY (id),
UNIQUE KEY uk_user_device (user_id, device_id),
KEY idx_user_id (user_id)
) COMMENT='登录过的设备。同 user+device 覆盖更新;踢人按 device_id';
KEY idx_user_id (user_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.
ALTER TABLE conversations COMMENT = '会话。单聊/群聊共用一张表;列表与未读在 Redis,不在此表热更新';
ALTER TABLE conversations
ALTER TABLE t_conversations COMMENT = '会话。单聊/群聊共用一张表;列表与未读在 Redis,不在此表热更新';
ALTER TABLE t_conversations
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 会话 id,时间有序主键',
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_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 p2p_conversations
ALTER TABLE t_p2p_conversations COMMENT = '单聊唯一索引。按 (较小 uid, 较大 uid) 定位会话,避免 A-B / B-A 两条记录';
ALTER TABLE t_p2p_conversations
MODIFY COLUMN user_small 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 conversation_members
ALTER TABLE t_conversation_members COMMENT = '会话成员。热路径会话列表不扫此表;last_read_msg_id 是已读持久化';
ALTER TABLE t_conversation_members
MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '会话 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 joined_at DATETIME(3) NOT NULL COMMENT '加入会话时间',
MODIFY COLUMN muted TINYINT NOT NULL DEFAULT 0 COMMENT '0=未静音 1=免打扰';
ALTER TABLE messages COMMENT = '消息正文。PK=MsgId;content 为序列化 BLOB,不存媒体二进制';
ALTER TABLE messages
ALTER TABLE t_messages COMMENT = '消息正文。PK=MsgId;content 为序列化 BLOB,不存媒体二进制';
ALTER TABLE t_messages
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
MODIFY COLUMN conversation_id BIGINT NOT NULL COMMENT '所属会话 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 created_at DATETIME(3) NOT NULL COMMENT '服务端入库时间';
ALTER TABLE mailbox_events COMMENT = '失败邮箱与控制事件。成功在线推送的新消息不写此表';
ALTER TABLE mailbox_events
ALTER TABLE t_mailbox_events COMMENT = '失败邮箱与控制事件。成功在线推送的新消息不写此表';
ALTER TABLE t_mailbox_events
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 事件 id',
MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '接收用户',
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 created_at DATETIME(3) NOT NULL COMMENT '写入时间';
ALTER TABLE device_sync_cursors COMMENT = '每设备同步游标。mailbox_seq 对邮箱,dialog_score 对会话列表';
ALTER TABLE device_sync_cursors
ALTER TABLE t_device_sync_cursors COMMENT = '每设备同步游标。mailbox_seq 对邮箱,dialog_score 对会话列表';
ALTER TABLE t_device_sync_cursors
MODIFY COLUMN user_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 updated_at DATETIME(3) NOT NULL COMMENT '游标更新时间';
ALTER TABLE users COMMENT = '用户账号。主键 Snowflake;登录身份在 user_emails,设备在 user_devices';
ALTER TABLE users
ALTER TABLE t_users COMMENT = '用户账号。主键 Snowflake;登录身份在 t_user_emails,设备在 t_user_devices';
ALTER TABLE t_users
MODIFY COLUMN id BIGINT NOT NULL COMMENT 'Snowflake 用户 id(uid),时间有序,非自增',
MODIFY COLUMN spark_id VARCHAR(32) NOT NULL 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 updated_at DATETIME(3) NOT NULL COMMENT '资料最近更新时间';
ALTER TABLE user_emails COMMENT = '邮箱与用户的一对一映射。登录用邮箱查此表再读 users';
ALTER TABLE user_emails
ALTER TABLE t_user_emails COMMENT = '邮箱与用户的一对一映射。登录用邮箱查此表再读 t_users';
ALTER TABLE t_user_emails
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 '绑定时间';
ALTER TABLE user_devices COMMENT = '登录过的设备。同 user+device 覆盖更新;踢人按 device_id';
ALTER TABLE user_devices
ALTER TABLE t_user_devices COMMENT = '用户与设备关系;设备属性见 t_devices,当前 App 见 JWT aid';
ALTER TABLE t_user_devices
MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)',
MODIFY COLUMN user_id BIGINT NOT NULL COMMENT '所属用户',
MODIFY COLUMN device_id BIGINT UNSIGNED NOT NULL COMMENT '客户端设备 id,写入 JWT did',
MODIFY COLUMN platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'ios/android/web 等',
MODIFY COLUMN device_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '设备展示名',
MODIFY COLUMN app_version VARCHAR(32) NOT NULL DEFAULT '' 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 '该设备首次登录时间';
MODIFY COLUMN device_id BIGINT UNSIGNED NOT NULL COMMENT 't_devices.device_id,JWT did',
MODIFY COLUMN last_login_at DATETIME(3) NOT NULL COMMENT '该用户在此设备最近登录成功',
MODIFY COLUMN last_active_at DATETIME(3) 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.
ALTER TABLE user_devices
ALTER TABLE t_user_devices
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.
ALTER TABLE users ADD COLUMN qrcode VARCHAR(512) NOT NULL DEFAULT '' COMMENT '个人二维码对象存储 key 或 URL,不存图片二进制';
ALTER TABLE users ADD COLUMN birthday DATE NULL COMMENT '生日,未填为空';
ALTER TABLE 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 users ADD COLUMN last_active_at DATETIME(3) NULL COMMENT '最近一次活跃时间(登录、心跳、发消息等)';
ALTER TABLE users COMMENT = '用户账号。主键 Snowflake 即 uid;登录身份在 user_emails,设备在 user_devices';
ALTER TABLE t_users ADD COLUMN qrcode VARCHAR(512) NOT NULL DEFAULT '' COMMENT '个人二维码对象存储 key 或 URL,不存图片二进制';
ALTER TABLE t_users ADD COLUMN birthday DATE NULL COMMENT '生日,未填为空';
ALTER TABLE t_users ADD COLUMN gender TINYINT NOT NULL DEFAULT 0 COMMENT '0=未知 1=男 2=女';
ALTER TABLE t_users ADD COLUMN last_login_at DATETIME(3) NULL COMMENT '最近一次登录时间';
ALTER TABLE t_users ADD COLUMN last_active_at DATETIME(3) NULL COMMENT '最近一次活跃时间(登录、心跳、发消息等)';
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,表内自增',
user_id BIGINT NOT NULL COMMENT '登录用户',
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 '本次登录时间',
PRIMARY KEY (id),
KEY idx_user_created (user_id, created_at)
) COMMENT='用户登录记录。每次成功登录追加一行;当前设备以 user_devices 为准';
ALTER TABLE user_devices
MODIFY COLUMN platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT '设备类型:ios/android/harmony/windows/macos/web';
) COMMENT='用户登录记录。每次成功登录追加一行;当次 App 见 app_id';

View File

@ -1,6 +1,6 @@
-- 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,表内自增',
user_id BIGINT NOT NULL COMMENT '关系所属用户',
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)
) 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',
from_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)
) 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',
user_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;源对象过期则无法再收藏。
CREATE TABLE IF NOT EXISTS favorites (
CREATE TABLE IF NOT EXISTS t_favorites (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 收藏 id',
user_id BIGINT NOT NULL COMMENT '收藏者 user_id',
msg_id BIGINT NOT NULL COMMENT '原消息 Snowflake 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/',
created_at DATETIME(3) NOT NULL COMMENT '收藏时间',
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',
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 '群名称',
avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '群头像对象存储 key,空表示未设置',
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)
) COMMENT='群资料。id 是自增群号;会话列表与未读仍按 conversation_id 走 Redis';
CREATE TABLE IF NOT EXISTS group_members (
group_id BIGINT NOT NULL COMMENT '群号,groups.id',
CREATE TABLE IF NOT EXISTS t_group_members (
group_id BIGINT NOT NULL COMMENT '群号,t_groups.id',
user_id BIGINT NOT NULL COMMENT '成员 user_id',
role TINYINT NOT NULL DEFAULT 3 COMMENT '1=群主 2=管理员 3=普通成员',
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 '入群时间',
PRIMARY KEY (group_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,仅该用户历史不可见',
msg_id BIGINT NOT NULL COMMENT '被删除的 Snowflake MsgId',
conversation_id BIGINT NOT NULL COMMENT '所属会话,便于按会话过滤',
created_at DATETIME(3) NOT NULL COMMENT '删除时间',
PRIMARY KEY (user_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.
CREATE TABLE IF NOT EXISTS private_message (
CREATE TABLE IF NOT EXISTS t_private_message (
id BIGINT PRIMARY KEY COMMENT 'Snowflake MsgId,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属单聊会话 id',
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),
KEY idx_conversation_id (conversation_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,主键、排序键、同步游标',
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',
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系统',
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS group_message (
KEY idx_group_id (group_id, id)
) 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,主键、排序键、同步游标',
conversation_id BIGINT NOT NULL COMMENT '所属会话 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 '服务端入库时间',
UNIQUE KEY uk_sender_conv_client_msg (sender_id, conversation_id, client_msg_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
FROM messages m
INNER JOIN conversations c ON c.id = m.conversation_id AND c.type = 1;
FROM t_messages m
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
FROM messages m
INNER JOIN conversations c ON c.id = m.conversation_id AND c.type = 2;
FROM t_messages m
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
FROM messages m
LEFT JOIN conversations c ON c.id = m.conversation_id
FROM t_messages m
LEFT JOIN t_conversations c ON c.id = m.conversation_id
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).
ALTER TABLE `groups`
ALTER TABLE `t_groups`
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;

View File

@ -1,7 +1,7 @@
-- Pair-shaped friend_relations (user_small, user_big) → two directed rows.
-- Skipped by cmd/migrate when friend_relations.user_id already exists.
-- Pair-shaped t_friend_relations (user_small, user_big) → two directed rows.
-- 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,表内自增',
user_id BIGINT NOT NULL COMMENT '关系所属用户',
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)
) 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
FROM friend_relations r
LEFT JOIN friend_remarks n ON n.user_id = r.user_small AND n.friend_id = r.user_big;
FROM t_friend_relations r
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
FROM friend_relations r
LEFT JOIN friend_remarks n ON n.user_id = r.user_big AND n.friend_id = r.user_small;
FROM t_friend_relations r
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
FROM user_blocks b
FROM t_user_blocks b
ON DUPLICATE KEY UPDATE is_blocked = 1, updated_at = VALUES(updated_at);
DROP TABLE IF EXISTS friend_remarks;
DROP TABLE IF EXISTS user_blocks;
DROP TABLE IF EXISTS friend_relations;
RENAME TABLE friend_relations_new TO friend_relations;
DROP TABLE IF EXISTS t_friend_remarks;
DROP TABLE IF EXISTS t_user_blocks;
DROP TABLE IF EXISTS t_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.
ALTER TABLE private_message
ALTER TABLE t_private_message
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);
UPDATE private_message pm
INNER JOIN p2p_conversations p ON p.conversation_id = pm.conversation_id
UPDATE t_private_message pm
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))
WHERE pm.receiver_id = 0 AND pm.sender_id <> 0;
ALTER TABLE group_message
ADD COLUMN group_id BIGINT NOT NULL DEFAULT 0 COMMENT '群号 groups.id,便于按群查询';
ALTER TABLE t_group_message
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);
UPDATE group_message gm
INNER JOIN `groups` g ON g.conversation_id = gm.conversation_id
UPDATE t_group_message gm
INNER JOIN `t_groups` g ON g.conversation_id = gm.conversation_id
SET gm.group_id = g.id
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/相册游标',
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视频',
text VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正文,可空;与媒体至少有一项',
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 '地点地址',
visibility SMALLINT NOT NULL DEFAULT 0 COMMENT '0好友公开 1私密 2白名单 3黑名单',
status SMALLINT NOT NULL DEFAULT 0 COMMENT '0正常 1已删',
like_count INT NOT NULL DEFAULT 0 COMMENT '点赞数,与 moment_likes 同事务维护',
comment_count INT NOT NULL DEFAULT 0 COMMENT '未删评论数,与 moment_comments 同事务维护',
like_count INT NOT NULL DEFAULT 0 COMMENT '点赞数,与 t_moment_likes 同事务维护',
comment_count INT NOT NULL DEFAULT 0 COMMENT '未删评论数,与 t_moment_comments 同事务维护',
created_at DATETIME(3) NOT NULL COMMENT '发帖时间,visible_days 按此裁剪',
KEY idx_user_id (user_id, id)
) COMMENT='朋友圈帖。读时按好友+可见性过滤,不写每好友 inbox';
CREATE TABLE IF NOT EXISTS moment_audience (
CREATE TABLE IF NOT EXISTS t_moment_audience (
post_id BIGINT NOT NULL COMMENT '帖 id',
peer_id BIGINT NOT NULL COMMENT '白名单能看 / 黑名单不能看 的好友 uid',
PRIMARY KEY (post_id, peer_id)
) 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',
user_id BIGINT NOT NULL COMMENT '点赞人',
created_at DATETIME(3) NOT NULL COMMENT '点赞时间',
PRIMARY KEY (post_id, user_id)
) COMMENT='朋友圈点赞。重复 PUT 幂等';
CREATE TABLE IF NOT EXISTS moment_comments (
CREATE TABLE IF NOT EXISTS t_moment_comments (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 评论 id',
post_id BIGINT NOT NULL COMMENT '帖 id',
user_id BIGINT NOT NULL COMMENT '评论人',
@ -43,7 +43,7 @@ CREATE TABLE IF NOT EXISTS moment_comments (
KEY idx_post (post_id, id)
) COMMENT='朋友圈评论。展示按共同好友过滤';
CREATE TABLE IF NOT EXISTS moment_notices (
CREATE TABLE IF NOT EXISTS t_moment_notices (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 通知 id,列表游标',
to_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)
) COMMENT='朋友圈赞评通知。不进会话 mailbox';
CREATE TABLE IF NOT EXISTS moment_settings (
user_id BIGINT PRIMARY KEY COMMENT '用户 users.id',
CREATE TABLE IF NOT EXISTS t_moment_settings (
user_id BIGINT PRIMARY KEY COMMENT '用户 t_users.id',
cover_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 object_key,空表示未设',
visible_days SMALLINT NOT NULL DEFAULT 0 COMMENT '允许朋友查看范围:0全部 3/30/180 天',
last_read_post_id BIGINT NOT NULL DEFAULT 0 COMMENT '发现页红点水位,只增不减',
updated_at DATETIME(3) NOT NULL COMMENT '最近更新时间'
) COMMENT='朋友圈个人设置。无行则封面空、范围全部、水位 0';
CREATE TABLE IF NOT EXISTS moment_hides (
CREATE TABLE IF NOT EXISTS t_moment_hides (
user_id BIGINT NOT NULL COMMENT '设置方',
peer_id BIGINT NOT NULL COMMENT '对方',
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.
-- 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,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 email' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_email (email);
ALTER TABLE p2p_conversations
ALTER TABLE t_p2p_conversations
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_small, user_big)' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_p2p_users (user_small, user_big);
ALTER TABLE conversation_members
ALTER TABLE t_conversation_members
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (conversation_id, user_id)' FIRST,
ADD PRIMARY KEY (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,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_user_device (user_id, device_id);
ALTER TABLE group_members
ALTER TABLE t_group_members
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (group_id, user_id)' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_group_members (group_id, user_id);
ALTER TABLE message_deletes
ALTER TABLE t_message_deletes
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, msg_id)' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_user_msg (user_id, msg_id);
ALTER TABLE moment_audience
ALTER TABLE t_moment_audience
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (post_id, peer_id)' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_post_peer (post_id, peer_id);
ALTER TABLE moment_likes
ALTER TABLE t_moment_likes
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (post_id, user_id)' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_post_user (post_id, user_id);
ALTER TABLE moment_settings
ALTER TABLE t_moment_settings
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 user_id' FIRST,
ADD PRIMARY KEY (id),
ADD UNIQUE KEY uk_user_id (user_id);
ALTER TABLE moment_hides
ALTER TABLE t_moment_hides
DROP PRIMARY KEY,
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, peer_id)' FIRST,
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 (
id BIGINT PRIMARY KEY COMMENT '原 users.id(uid),Snowflake,不复用',
CREATE TABLE IF NOT EXISTS t_user_delete (
id BIGINT PRIMARY KEY COMMENT '原 t_users.id(uid),Snowflake,不复用',
spark_id VARCHAR(32) NOT NULL COMMENT '注销时的 Spark 账号',
nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '注销时的昵称',
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_spark_id (spark_id),
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 字节。
CREATE TABLE IF NOT EXISTS square_posts (
CREATE TABLE IF NOT EXISTS t_square_posts (
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视频',
text VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正文,可空;与媒体至少有一项',
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)
) COMMENT='广场帖';
CREATE TABLE IF NOT EXISTS square_audience (
CREATE TABLE IF NOT EXISTS t_square_audience (
post_id BIGINT NOT NULL COMMENT '帖 id',
peer_id BIGINT NOT NULL COMMENT '白名单能看 / 黑名单不能看 的好友 uid',
PRIMARY KEY (post_id, peer_id)
) COMMENT='广场部分可见名单';
CREATE TABLE IF NOT EXISTS square_likes (
CREATE TABLE IF NOT EXISTS t_square_likes (
post_id BIGINT NOT NULL COMMENT '帖 id',
user_id BIGINT NOT NULL COMMENT '点赞人',
created_at DATETIME(3) NOT NULL COMMENT '点赞时间',
PRIMARY KEY (post_id, user_id)
) COMMENT='广场点赞';
CREATE TABLE IF NOT EXISTS square_comments (
CREATE TABLE IF NOT EXISTS t_square_comments (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 评论 id',
post_id BIGINT NOT NULL COMMENT '帖 id',
user_id BIGINT NOT NULL COMMENT '评论人',
@ -47,7 +47,7 @@ CREATE TABLE IF NOT EXISTS square_comments (
KEY idx_post (post_id, id)
) COMMENT='广场评论';
CREATE TABLE IF NOT EXISTS square_notices (
CREATE TABLE IF NOT EXISTS t_square_notices (
id BIGINT PRIMARY KEY COMMENT 'Snowflake 通知 id',
to_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)
) COMMENT='广场赞评通知';
CREATE TABLE IF NOT EXISTS square_notice_state (
user_id BIGINT PRIMARY KEY COMMENT '用户 users.id',
CREATE TABLE IF NOT EXISTS t_square_notice_state (
user_id BIGINT PRIMARY KEY COMMENT '用户 t_users.id',
last_read_notice_id BIGINT NOT NULL DEFAULT 0 COMMENT '互动通知已读水位',
updated_at DATETIME(3) NOT NULL 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 同机共用',
fingerprint_hash CHAR(64) NOT NULL COMMENT '物理指纹 SHA256 hex,不含 app_id',
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)
) COMMENT='物理设备;与用户、App 无关';
ALTER TABLE user_login_logs
ALTER TABLE t_user_login_logs
ADD COLUMN app_id TINYINT UNSIGNED NOT NULL DEFAULT 0
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;
ALTER TABLE user_devices DROP COLUMN platform;
ALTER TABLE user_devices DROP COLUMN device_name;
ALTER TABLE user_devices DROP COLUMN app_version;
ALTER TABLE user_devices DROP COLUMN language;
ALTER TABLE user_devices DROP COLUMN last_ip;
ALTER TABLE t_user_devices DROP COLUMN platform;
ALTER TABLE t_user_devices DROP COLUMN device_name;
ALTER TABLE t_user_devices DROP COLUMN app_version;
ALTER TABLE t_user_devices DROP COLUMN language;
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',
title VARCHAR(256) NOT NULL DEFAULT '' COMMENT '曲名',
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)
) COMMENT='运营音乐曲目';
CREATE TABLE IF NOT EXISTS music_settings (
CREATE TABLE IF NOT EXISTS t_music_settings (
setting_key VARCHAR(64) PRIMARY KEY COMMENT '配置键',
setting_value VARCHAR(256) NOT NULL DEFAULT '' COMMENT '配置值',
updated_at DATETIME(3) NOT NULL 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))
ON DUPLICATE KEY UPDATE setting_key = setting_key;