feat: update Primary Key
This commit is contained in:
parent
bebf76e582
commit
f79211ddac
@ -58,13 +58,12 @@ Do **not** move `infrastructure` into `pkg` (or a public repo-root `pkg/`).
|
||||
|
||||
## IDs
|
||||
|
||||
Not every PK is Snowflake. Do not AUTO_INCREMENT natural keys.
|
||||
Not every PK is Snowflake. Do not AUTO_INCREMENT Snowflake business ids (`users.id`, `messages.id`, `conversations.id`).
|
||||
|
||||
| Kind | Examples | Rule |
|
||||
|------|----------|------|
|
||||
| Snowflake | `users.id` (uid), `messages.id` (MsgId), `conversations.id` | Time-ordered business IDs. One `id.Generator` per process; `snowflake_worker_id` unique per process. Clock rewind fails issuance for the whole process. |
|
||||
| AUTO_INCREMENT | `user_devices.id`, `groups.id` (群号), `friend_relations.id` | Internal row id, or sequential public 群号. Friend edges are directed (`user_id`→`friend_id`); UNIQUE `(user_id, friend_id)`. Business chat cursor is still Snowflake `conversations.id`. |
|
||||
| Natural key | `user_emails.email`, P2P `(user_small, user_big)`, members, sync cursors | Composite / natural PK. No extra surrogate id. |
|
||||
| AUTO_INCREMENT | `groups.id` (群号); row id on `user_devices`, `friend_relations`, `user_login_logs`, `user_emails`, `p2p_conversations`, `conversation_members`, `group_members`, `device_sync_cursors`, `message_deletes`, `moment_audience`, `moment_likes`, `moment_settings`, `moment_hides` | Table-local row id. Business uniqueness stays UNIQUE (email, P2P pair, members, cursors). Do not AUTO_INCREMENT Snowflake columns. |
|
||||
|
||||
`device_id` is **client-generated**, stored on the device, written to JWT `did`. The server does not allocate it. `uid` in JSON **is** `users.id`; do not add a second uid column.
|
||||
|
||||
|
||||
@ -128,6 +128,7 @@ func ignorableDDL(err error) bool {
|
||||
1051, // unknown table
|
||||
1060, // duplicate column
|
||||
1061, // duplicate key name
|
||||
1091, // can't drop; check that column/key exists
|
||||
1146: // table doesn't exist
|
||||
return true
|
||||
default:
|
||||
|
||||
@ -441,20 +441,22 @@ CREATE TABLE conversations (
|
||||
|
||||
-- 两人永远同一条会话,学 BFF 小 uid : 大 uid
|
||||
CREATE TABLE p2p_conversations (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
user_small BIGINT NOT NULL,
|
||||
user_big BIGINT NOT NULL,
|
||||
conversation_id BIGINT NOT NULL,
|
||||
PRIMARY KEY (user_small, user_big),
|
||||
UNIQUE KEY uk_p2p_users (user_small, user_big),
|
||||
UNIQUE KEY uk_conversation (conversation_id)
|
||||
);
|
||||
|
||||
CREATE TABLE conversation_members (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
last_read_msg_id BIGINT NOT NULL DEFAULT 0,
|
||||
joined_at DATETIME(3) NOT NULL,
|
||||
muted TINYINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (conversation_id, user_id),
|
||||
UNIQUE KEY uk_conversation_members (conversation_id, user_id),
|
||||
KEY idx_user_id (user_id)
|
||||
);
|
||||
```
|
||||
@ -481,12 +483,13 @@ CREATE TABLE mailbox_events (
|
||||
);
|
||||
|
||||
CREATE TABLE device_sync_cursors (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
device_id BIGINT NOT NULL,
|
||||
mailbox_seq BIGINT NOT NULL DEFAULT 0,
|
||||
dialog_score BIGINT NOT NULL DEFAULT 0, -- Redis ZSET score 游标
|
||||
updated_at DATETIME(3) NOT NULL,
|
||||
PRIMARY KEY (user_id, device_id)
|
||||
UNIQUE KEY uk_user_device (user_id, device_id)
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@ -10,8 +10,9 @@ import (
|
||||
|
||||
// ConversationMember maps table conversation_members.
|
||||
type ConversationMember struct {
|
||||
ConversationID int64 `gorm:"column:conversation_id;primaryKey;comment:会话 id"`
|
||||
UserID int64 `gorm:"column:user_id;primaryKey;comment:成员 user_id"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (conversation_id, user_id)"`
|
||||
ConversationID int64 `gorm:"column:conversation_id;comment:会话 id"`
|
||||
UserID int64 `gorm:"column:user_id;comment:成员 user_id"`
|
||||
LastReadMsgID int64 `gorm:"column:last_read_msg_id;comment:该成员已读到的最大消息 id,未读真相;Redis 丢了可从此重建"`
|
||||
JoinedAt time.Time `gorm:"column:joined_at;comment:加入会话时间"`
|
||||
Muted int `gorm:"column:muted;comment:0=未静音 1=免打扰"`
|
||||
|
||||
@ -2,8 +2,9 @@ package model
|
||||
|
||||
// P2PConversation maps table p2p_conversations.
|
||||
type P2PConversation struct {
|
||||
UserSmall int64 `gorm:"column:user_small;primaryKey;comment:两人中较小的 user_id,保证同一对用户只有一行"`
|
||||
UserBig int64 `gorm:"column:user_big;primaryKey;comment:两人中较大的 user_id"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (user_small, user_big)"`
|
||||
UserSmall int64 `gorm:"column:user_small;comment:两人中较小的 user_id,保证同一对用户只有一行"`
|
||||
UserBig int64 `gorm:"column:user_big;comment:两人中较大的 user_id"`
|
||||
ConversationID int64 `gorm:"column:conversation_id;comment:对应 conversations.id"`
|
||||
}
|
||||
|
||||
|
||||
@ -10,8 +10,9 @@ import (
|
||||
|
||||
// GroupMember maps table group_members.
|
||||
type GroupMember struct {
|
||||
GroupID int64 `gorm:"column:group_id;primaryKey;comment:群号,groups.id"`
|
||||
UserID int64 `gorm:"column:user_id;primaryKey;comment:成员 user_id"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (group_id, user_id)"`
|
||||
GroupID int64 `gorm:"column:group_id;comment:群号,groups.id"`
|
||||
UserID int64 `gorm:"column:user_id;comment:成员 user_id"`
|
||||
Role int `gorm:"column:role;comment:1=群主 2=管理员 3=普通成员"`
|
||||
Nickname string `gorm:"column:nickname;comment:群内昵称,空则用用户昵称"`
|
||||
Remark string `gorm:"column:remark;comment:成员对该群的备注,仅自己可见"`
|
||||
|
||||
@ -2,10 +2,11 @@ package model
|
||||
|
||||
import "time"
|
||||
|
||||
// MessageDelete maps table message_deletes. PK is (user_id, msg_id).
|
||||
// MessageDelete maps table message_deletes. Business unique key is (user_id, msg_id).
|
||||
type MessageDelete struct {
|
||||
UserID int64 `gorm:"column:user_id;primaryKey;comment:删除者 user_id,仅该用户历史不可见"`
|
||||
MsgID int64 `gorm:"column:msg_id;primaryKey;comment:被删除的 Snowflake MsgId"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (user_id, msg_id)"`
|
||||
UserID int64 `gorm:"column:user_id;comment:删除者 user_id,仅该用户历史不可见"`
|
||||
MsgID int64 `gorm:"column:msg_id;comment:被删除的 Snowflake MsgId"`
|
||||
ConversationID int64 `gorm:"column:conversation_id;comment:所属会话,便于按会话过滤"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;comment:删除时间"`
|
||||
}
|
||||
|
||||
@ -56,16 +56,18 @@ func postFrom(p *entity.Post) *Post {
|
||||
|
||||
// Audience maps table moment_audience.
|
||||
type Audience struct {
|
||||
PostID int64 `gorm:"column:post_id;primaryKey;comment:帖 id"`
|
||||
PeerID int64 `gorm:"column:peer_id;primaryKey;comment:白名单能看 / 黑名单不能看 的好友 uid"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (post_id, peer_id)"`
|
||||
PostID int64 `gorm:"column:post_id;comment:帖 id"`
|
||||
PeerID int64 `gorm:"column:peer_id;comment:白名单能看 / 黑名单不能看 的好友 uid"`
|
||||
}
|
||||
|
||||
func (Audience) TableName() string { return "moment_audience" }
|
||||
|
||||
// Like maps table moment_likes.
|
||||
type Like struct {
|
||||
PostID int64 `gorm:"column:post_id;primaryKey;comment:帖 id"`
|
||||
UserID int64 `gorm:"column:user_id;primaryKey;comment:点赞人"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (post_id, user_id)"`
|
||||
PostID int64 `gorm:"column:post_id;comment:帖 id"`
|
||||
UserID int64 `gorm:"column:user_id;comment:点赞人"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;comment:点赞时间"`
|
||||
}
|
||||
|
||||
@ -146,7 +148,8 @@ func (r *Notice) Entity() *entity.Notice {
|
||||
|
||||
// Setting maps table moment_settings.
|
||||
type Setting struct {
|
||||
UserID int64 `gorm:"column:user_id;primaryKey;comment:用户 users.id"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 user_id"`
|
||||
UserID int64 `gorm:"column:user_id;comment:用户 users.id"`
|
||||
CoverKey string `gorm:"column:cover_key;comment:封面 object_key,空表示未设"`
|
||||
VisibleDays int16 `gorm:"column:visible_days;comment:允许朋友查看范围:0全部 3/30/180 天"`
|
||||
LastReadPostID int64 `gorm:"column:last_read_post_id;comment:发现页红点水位,只增不减"`
|
||||
@ -180,8 +183,9 @@ func settingFrom(s *entity.Settings) *Setting {
|
||||
|
||||
// Hide maps table moment_hides.
|
||||
type Hide struct {
|
||||
UserID int64 `gorm:"column:user_id;primaryKey;comment:设置方"`
|
||||
PeerID int64 `gorm:"column:peer_id;primaryKey;comment:对方"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (user_id, peer_id)"`
|
||||
UserID int64 `gorm:"column:user_id;comment:设置方"`
|
||||
PeerID int64 `gorm:"column:peer_id;comment:对方"`
|
||||
HideHim bool `gorm:"column:hide_him;comment:1=不看他(Feed 去掉对方)"`
|
||||
HideMe bool `gorm:"column:hide_me;comment:1=不让他看"`
|
||||
}
|
||||
|
||||
@ -79,7 +79,10 @@ func (m *Repo) AddLike(ctx context.Context, like *entity.Like) (bool, error) {
|
||||
}
|
||||
created := false
|
||||
err = db.Transaction(func(tx *gorm.DB) error {
|
||||
res := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&row)
|
||||
res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "post_id"}, {Name: "user_id"}},
|
||||
DoNothing: true,
|
||||
}).Create(&row)
|
||||
if res.Error != nil {
|
||||
return mapDBErr(res.Error)
|
||||
}
|
||||
@ -197,7 +200,12 @@ func (m *Repo) UpsertSettings(ctx context.Context, s *entity.Settings) error {
|
||||
if row.UpdatedAt.IsZero() {
|
||||
row.UpdatedAt = time.Now()
|
||||
}
|
||||
return mapDBErr(db.Save(row).Error)
|
||||
return mapDBErr(db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{
|
||||
"cover_key", "visible_days", "last_read_post_id", "updated_at",
|
||||
}),
|
||||
}).Create(row).Error)
|
||||
}
|
||||
|
||||
func (m *Repo) AdvanceRead(ctx context.Context, userID, postID int64) error {
|
||||
|
||||
@ -4,8 +4,9 @@ import "time"
|
||||
|
||||
// DeviceSyncCursor maps table device_sync_cursors.
|
||||
type DeviceSyncCursor struct {
|
||||
UserID int64 `gorm:"column:user_id;primaryKey;comment:用户 id"`
|
||||
DeviceID int64 `gorm:"column:device_id;primaryKey;comment:设备 id"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)"`
|
||||
UserID int64 `gorm:"column:user_id;comment:用户 id"`
|
||||
DeviceID int64 `gorm:"column:device_id;comment:设备 id"`
|
||||
MailboxSeq int64 `gorm:"column:mailbox_seq;comment:该设备已同步到的 mailbox_events.seq"`
|
||||
DialogScore int64 `gorm:"column:dialog_score;comment:会话列表 Redis ZSET 游标"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;comment:游标更新时间"`
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
mysqldriver "github.com/go-sql-driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"spark-server/internal/module/sync/entity"
|
||||
"spark-server/internal/pkg/errs"
|
||||
@ -119,5 +120,8 @@ func (s *Store) SaveCursor(ctx context.Context, userID int64, deviceID uint64, m
|
||||
UserID: userID, DeviceID: int64(deviceID),
|
||||
MailboxSeq: mailboxSeq, DialogScore: dialogScore, UpdatedAt: time.Now(),
|
||||
}
|
||||
return mapDBErr(db.Save(&row).Error)
|
||||
return mapDBErr(db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}, {Name: "device_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"mailbox_seq", "dialog_score", "updated_at"}),
|
||||
}).Create(&row).Error)
|
||||
}
|
||||
|
||||
@ -11,7 +11,8 @@ import (
|
||||
|
||||
// UserEmail maps table user_emails: one normalized email to one user.
|
||||
type UserEmail struct {
|
||||
Email string `gorm:"column:email;primaryKey;comment:规范化后的小写邮箱,登录账号"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 email"`
|
||||
Email string `gorm:"column:email;comment:规范化后的小写邮箱,登录账号"`
|
||||
UserID int64 `gorm:"column:user_id;comment:绑定的 users.id,一对一"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;comment:绑定时间"`
|
||||
}
|
||||
|
||||
63
migrations/0016_row_autoincrement_id.sql
Normal file
63
migrations/0016_row_autoincrement_id.sql
Normal file
@ -0,0 +1,63 @@
|
||||
-- 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.
|
||||
|
||||
ALTER TABLE 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
DROP PRIMARY KEY,
|
||||
ADD COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '行 id,表内自增,仅内部用;业务唯一键是 (user_id, peer_id)' FIRST,
|
||||
ADD PRIMARY KEY (id),
|
||||
ADD UNIQUE KEY uk_user_peer (user_id, peer_id);
|
||||
Loading…
Reference in New Issue
Block a user