首次提交

This commit is contained in:
zeronline
2026-06-25 08:39:47 +08:00
parent 20a93c703a
commit c5cccd346b
169 changed files with 24720 additions and 0 deletions
+427
View File
@@ -0,0 +1,427 @@
# 寸进(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 万 |