428 lines
15 KiB
Markdown
428 lines
15 KiB
Markdown
# 寸进(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 万 |
|