# 寸进(InchStep)数据库设计文档 > 文档版本:v1.0 > 数据库:MySQL 8.0 > ORM:GORM(AutoMigrate) > 字符集:utf8mb4 / utf8mb4_unicode_ci > 更新日期:2026-06-24 --- ## 目录 1. 文档概述 2. 数据库全局配置 3. 数据模型详情 4. ER 关系图 5. 索引策略 6. 迁移说明 7. 查询场景与优化 --- ## 1. 文档概述 本文档定义寸进平台 v1.0 的全部数据模型、表结构、索引策略和迁移策略。数据模型映射自 server/internal/models/ 中的 Go 结构体,通过 GORM AutoMigrate 自动管理表结构。 --- ## 2. 数据库全局配置 `ini # 连接参数 DB_HOST=127.0.0.1 DB_PORT=13306 DB_USER=root DB_PASS=123456 DB_NAME=inchstep CHARSET=utf8mb4 COLLATION=utf8mb4_unicode_ci LOC=Asia/Shanghai ` ### 2.1 GORM 连接配置 `go dsn := "root:123456@tcp(127.0.0.1:13306)/inchstep?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" // 关键配置: // - parseTime=true: 将 MySQL DATETIME 映射为 Go time.Time // - loc=Asia/Shanghai: 时区感知,影响连续天数计算 ` --- ## 3. 数据模型详情 ### 3.1 users(用户表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | varchar(32) | PK | 唯一 ID(cuid 风格) | | phone | varchar(20) | UNIQUE, NULL | 手机号 | | nickname | varchar(50) | NULL | 昵称 | | avatar | varchar(255) | NULL | 头像 URL | | bio | varchar(100) | NULL | 个性签名 | | motto | varchar(50) | NULL | 个人目标简介 | | privacy | int | DEFAULT 2 | 1=公开 2=好友 3=私密 | | points | int | DEFAULT 0 | 总积分 | | created_at | datetime | NOT NULL | 创建时间 | | updated_at | datetime | NOT NULL | 更新时间 | **GORM 映射:** `go type User struct { ID string gorm:"primaryKey;type:varchar(32)" json:"id" Phone *string gorm:"uniqueIndex;type:varchar(20)" json:"phone,omitempty" Nickname *string gorm:"type:varchar(50)" json:"nickname,omitempty" Avatar *string gorm:"type:varchar(255)" json:"avatar,omitempty" Bio *string gorm:"type:varchar(100)" json:"bio,omitempty" Motto *string gorm:"type:varchar(50)" json:"motto,omitempty" Privacy PrivacyLevel gorm:"default:2" json:"privacy" Points int gorm:"default:0" json:"points" CreatedAt time.Time gorm:"column:created_at" json:"createdAt" UpdatedAt time.Time gorm:"column:updated_at" json:"updatedAt" } ` **索引**: - PRIMARY KEY (id) - UNIQUE INDEX idx_phone (phone) ### 3.2 checkin_items(打卡项表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | varchar(32) | PK | 唯一 ID | | user_id | varchar(32) | FK, NOT NULL, INDEX | 所属用户 | | name | varchar(100) | NOT NULL | 打卡项名称 | | icon | varchar(50) | NULL | 图标 | | category | varchar(20) | NOT NULL | 分类:study/health/habit/hobby/goal | | daily_goal | varchar(200) | NULL | 每日目标 | | cycle_type | varchar(20) | DEFAULT 'daily' | 周期:daily/weekly/every_other_day/custom | | privacy | int | DEFAULT 2 | 隐私级别 | | status | varchar(20) | DEFAULT 'active' | 状态:active/paused/archived/deleted | | created_at | datetime | NOT NULL | 创建时间 | **GORM 映射:** `go type CheckInItem struct { ID string gorm:"primaryKey;type:varchar(32)" json:"id" UserID string gorm:"column:user_id;type:varchar(32);not null;index" json:"userId" Name string gorm:"type:varchar(100);not null" json:"name" Icon *string gorm:"type:varchar(50)" json:"icon,omitempty" Category CheckInCategory gorm:"type:varchar(20);not null" json:"category" DailyGoal *string gorm:"column:daily_goal;type:varchar(200)" json:"dailyGoal,omitempty" CycleType CycleType gorm:"column:cycle_type;default:daily;type:varchar(20)" json:"cycleType" Privacy PrivacyLevel gorm:"default:2" json:"privacy" Status ItemStatus gorm:"default:active;type:varchar(20)" json:"status" CreatedAt time.Time gorm:"column:created_at" json:"createdAt" } ` **索引**: - PRIMARY KEY (id) - INDEX idx_user_id (user_id) ### 3.3 checkin_records(打卡记录表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | varchar(32) | PK | 唯一 ID | | item_id | varchar(32) | FK, NOT NULL, INDEX | 所属打卡项 | | user_id | varchar(32) | FK, NOT NULL, INDEX | 所属用户 | | check_date | datetime | NOT NULL, INDEX | 打卡日期 | | status | varchar(20) | DEFAULT 'normal' | normal/makeup | | note | varchar(500) | NULL | 备注文字 | | images | text | NULL | 图片 URL(JSON 数组) | | created_at | datetime | NOT NULL | 记录创建时间 | **GORM 映射:** `go type CheckInRecord struct { ID string gorm:"primaryKey;type:varchar(32)" json:"id" ItemID string gorm:"column:item_id;type:varchar(32);not null;index" json:"itemId" UserID string gorm:"column:user_id;type:varchar(32);not null;index" json:"userId" CheckDate time.Time gorm:"column:check_date;not null;index" json:"checkDate" Status RecordStatus gorm:"default:normal;type:varchar(20)" json:"status" Note *string gorm:"type:varchar(500)" json:"note,omitempty" Images *string gorm:"type:text" json:"images,omitempty" CreatedAt time.Time gorm:"column:created_at" json:"createdAt" } ` **索引**: - PRIMARY KEY (id) - INDEX idx_user_id (user_id) - INDEX idx_item_id (item_id) - INDEX idx_check_date (check_date) - COMPOSITE INDEX idx_user_date (user_id, check_date)(查询推荐) ### 3.4 achievements(成就定义表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | varchar(50) | PK | 成就 ID(如 irst_checkin) | | name | varchar(100) | NOT NULL | 成就名称 | | description | varchar(200) | NULL | 描述 | | icon | varchar(50) | NULL | 图标 | | rarity | varchar(20) | DEFAULT 'bronze' | bronze/silver/gold | | condition | varchar(200) | NULL | 达成条件描述 | **GORM 映射:** `go type Achievement struct { ID string gorm:"primaryKey;type:varchar(50)" json:"id" Name string gorm:"type:varchar(100);not null" json:"name" Description *string gorm:"type:varchar(200)" json:"description,omitempty" Icon *string gorm:"type:varchar(50)" json:"icon,omitempty" Rarity string gorm:"default:bronze;type:varchar(20)" json:"rarity" Condition *string gorm:"type:varchar(200)" json:"condition,omitempty" } ` ### 3.5 user_achievements(用户成就关联表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | user_id | varchar(32) | PK, FK | 用户 ID | | achievement_id | varchar(50) | PK, FK | 成就 ID | | unlocked_at | datetime | NOT NULL | 解锁时间 | **联合主键**:(user_id, achievement_id) ### 3.6 posts(动态表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | varchar(32) | PK | 唯一 ID | | user_id | varchar(32) | FK, NOT NULL, INDEX | 发布者 | | content | text | NULL | 文字内容 | | images | text | NULL | 图片 URL(JSON 数组) | | privacy | int | DEFAULT 1 | 1=公开 2=好友 3=私密 | | likes | int | DEFAULT 0 | 点赞数(计数器) | | comments | int | DEFAULT 0 | 评论数(计数器) | | created_at | datetime | NOT NULL | 发布时间 | **索引**: - PRIMARY KEY (id) - INDEX idx_created_at (created_at)(广场排序) - INDEX idx_user_id (user_id) ### 3.7 friends(好友关系表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | user_id | varchar(32) | PK, FK | 用户 A | | friend_id | varchar(32) | PK, FK | 用户 B | | status | varchar(20) | DEFAULT 'pending' | pending/accepted | | created_at | datetime | NOT NULL | 请求时间 | **联合主键**:(user_id, friend_id)(双向好友关系需两条记录) ### 3.8 groups(打卡小组表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | varchar(32) | PK | 唯一 ID | | name | varchar(20) | NOT NULL | 小组名称 | | cover | varchar(255) | NULL | 封面图片 | | intro | varchar(200) | NULL | 小组简介 | | type | varchar(20) | DEFAULT 'public' | public/private | | max_member | int | DEFAULT 50 | 人数上限 | | created_by | varchar(32) | NOT NULL | 创建者 | | created_at | datetime | NOT NULL | 创建时间 | ### 3.9 group_members(小组成员表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | group_id | varchar(32) | PK, FK | 小组 ID | | user_id | varchar(32) | PK, FK | 用户 ID | | role | varchar(20) | DEFAULT 'member' | admin/member | | joined_at | datetime | NOT NULL | 加入时间 | **联合主键**:(group_id, user_id) --- ## 4. ER 关系图 ` ┌─────────┐ ┌──────────────┐ ┌────────────────┐ │ users │1───N→│ checkin_items│1───N→│ checkin_records│ └─────────┘ └──────────────┘ └────────────────┘ │ 1 │ │ │ │ 1 N │ │ ├──────────────┐ │ │ 1 │ 1 │ ▼ ▼ ▼ ┌──────────┐ ┌──────────────┐ │ friends │ │ posts │ └──────────┘ └──────────────┘ │ │ N ▼ ┌────────────────┐ ┌──────────────────────┐ ┌──────────────┐ │ groups │1───N→│ group_members │N───1│ users │ └────────────────┘ └──────────────────────┘ └──────────────┘ ┌──────────────┐ ┌──────────────────────┐ ┌──────────────┐ │ achievements │1───N→│ user_achievements │N───1│ users │ └──────────────┘ └──────────────────────┘ └──────────────┘ ` ### 关系说明 | 左表 | 关系 | 右表 | 说明 | |------|------|------|------| | users | 1 → N | checkin_items | 一个用户可创建多个打卡项 | | checkin_items | 1 → N | checkin_records | 一个打卡项有多条打卡记录 | | users | 1 → N | checkin_records | 一个用户有多条打卡记录 | | users | 1 → N | friends | 一个用户可有多个好友 | | users | 1 → N | posts | 一个用户可发布多条动态 | | users | 1 → N | user_achievements | 一个用户可解锁多个成就 | | groups | 1 → N | group_members | 一个小组有多个成员 | | achievements | 1 → N | user_achievements | 一个成就可被多个用户解锁 | --- ## 5. 索引策略 ### 5.1 高频查询与索引覆盖 | 查询场景 | 涉及表 | 使用索引 | 优化建议 | |----------|--------|----------|----------| | 用户登录(手机号) | users | idx_phone | — | | 用户打卡项列表 | checkin_items | idx_user_id | — | | 打卡项打卡记录 | checkin_records | idx_item_id, idx_check_date | 复合索引 (item_id, check_date) | | 连续天数计算 | checkin_records | idx_user_id, idx_check_date | 必须按 check_date DESC | | 月度统计 | checkin_records | idx_user_id, idx_check_date | 复合索引 (user_id, check_date) | | 动态广场排序 | posts | idx_created_at | 只查 privacy=1 | | 好友列表 | friends | idx_user_id | — | | 小组成员 | group_members | idx_group_id | — | ### 5.2 索引定义(迁移推荐) `sql -- 已有索引(由 GORM 自动创建) -- users: PRIMARY(id), UNIQUE(phone) -- checkin_items: PRIMARY(id), INDEX(user_id) -- checkin_records: PRIMARY(id), INDEX(item_id), INDEX(user_id), INDEX(check_date) -- posts: PRIMARY(id), INDEX(user_id), INDEX(created_at) -- 推荐手动添加的复合索引 CREATE INDEX idx_records_user_date ON checkin_records(user_id, check_date); CREATE INDEX idx_records_item_date ON checkin_records(item_id, check_date); ` --- ## 6. 迁移说明 ### 6.1 开发环境(AutoMigrate) 开发环境使用 GORM AutoMigrate 自动创建/更新表结构。服务启动时执行: `go db.AutoMigrate( &models.User{}, &models.CheckInItem{}, &models.CheckInRecord{}, &models.Achievement{}, &models.UserAchievement{}, &models.Post{}, &models.Friend{}, &models.Group{}, &models.GroupMember{}, ) ` ### 6.2 生产环境 生产环境建议: 1. **开发阶段**:使用 AutoMigrate 快速迭代 2. **预发布阶段**:生成 SQL 迁移脚本,人工审核后执行 3. **生产阶段**:使用版本化迁移工具(如 golang-migrate) ### 6.3 初始化数据 服务启动时自动初始化成就数据(10 个预定义成就徽章): `go func (s *PointsService) InitAchievements() error { for _, a := range predefinedAchievements { s.db.Where("id = ?", a.ID).FirstOrCreate(&a) } return nil } ` --- ## 7. 查询场景与优化 ### 7.1 连续天数计算 `sql -- 查询逻辑:按日期降序获取某打卡项的所有记录 SELECT check_date FROM checkin_records WHERE item_id = ? AND user_id = ? ORDER BY check_date DESC; ` **优化策略**: - (item_id, user_id, check_date) 复合索引 - Redis 缓存结果,打卡时更新 ### 7.2 动态广场分页 `sql SELECT p.*, u.nickname, u.avatar FROM posts p LEFT JOIN users u ON p.user_id = u.id WHERE p.privacy = 1 ORDER BY p.created_at DESC LIMIT 20 OFFSET 0; ` ### 7.3 月度统计 `sql SELECT COUNT(*) FROM checkin_records WHERE user_id = ? AND check_date >= ? AND check_date < ?; SELECT category, COUNT(*) as count FROM checkin_items WHERE user_id = ? AND status = 'active' GROUP BY category; ` ### 7.4 好友列表 `sql SELECT friend_id FROM friends WHERE user_id = ? AND status = 'accepted'; ` --- ## 附录:表总览 | # | 表名 | 说明 | 行数预估(万 DAU) | |---|------|------|-------------------| | 1 | users | 用户 | 5 万 | | 2 | checkin_items | 打卡项 | 20 万(人均 4 项) | | 3 | checkin_records | 打卡记录 | 5000 万(日均 50 万) | | 4 | achievements | 成就定义 | 10 条(静态) | | 5 | user_achievements | 用户成就 | 10 万 | | 6 | posts | 动态 | 100 万 | | 7 | friends | 好友关系 | 20 万 | | 8 | groups | 打卡小组 | 1 万 | | 9 | group_members | 小组成员 | 10 万 |