Files
inchstep/docs/数据库设计文档.md
2026-06-25 08:39:47 +08:00

428 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 寸进(InchStep)数据库设计文档
> 文档版本:v1.0
> 数据库:MySQL 8.0
> ORMGORMAutoMigrate
> 字符集: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 | 唯一 IDcuid 风格) |
| 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 | 图片 URLJSON 数组) |
| 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 | 图片 URLJSON 数组) |
| 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 万 |