Files
PR-Helper/docs/04-database-design.md
wonder 275e5cc886 docs: 添加 10 份技术文档,README 改为中文
- 01-architecture.md: 架构概览
- 02-backend-services.md: 后端服务层
- 03-frontend-interaction.md: 前端交互设计
- 04-database-design.md: 数据库设计
- 05-api-reference.md: API 接口文档
- 06-sse-streaming.md: SSE 流式传输
- 07-llm-integration.md: LLM 集成
- 08-deployment.md: 部署运维
- 09-development-guide.md: 开发指南
- 10-troubleshooting.md: 故障排查
2026-06-23 22:38:43 +08:00

395 lines
8.8 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.
# 数据库设计
## 概述
PR-Helper 使用 MySQL 作为主数据库,存储用户信息、仓库元数据、分析结果和审查笔记。
## 数据库配置
### 连接参数
```env
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=
MYSQL_DATABASE=pr_helper
```
### DSN 格式
```
{user}:{password}@tcp({host}:{port})/{database}?charset=utf8mb4&parseTime=True&loc=Local
```
## 表结构
### 1. settings 表
全局键值存储,保存系统配置。
```sql
CREATE TABLE IF NOT EXISTS settings (
`key` VARCHAR(255) PRIMARY KEY,
value TEXT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
| 字段 | 类型 | 说明 |
|------|------|------|
| key | VARCHAR(255) | 配置键名(主键) |
| value | TEXT | 配置值 |
#### 默认设置
```go
var DefaultSettings = map[string]string{
"llm.endpoint": "https://api.deepseek.com",
"llm.api_key": "",
"llm.model": "deepseek-v4-pro",
"review.top_n": "20",
"review.concurrency": "5",
"cache.max_age_days": "7",
"cache.max_size_mb": "5000",
}
```
### 2. users 表
用户账户信息。
```sql
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 用户 ID(主键,自增) |
| email | VARCHAR(255) | 邮箱(唯一) |
| password_hash | TEXT | 密码哈希 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
### 3. user_settings 表
用户个人设置,覆盖全局默认值。
```sql
CREATE TABLE IF NOT EXISTS user_settings (
user_id BIGINT NOT NULL,
`key` VARCHAR(255) NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (user_id, `key`),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
| 字段 | 类型 | 说明 |
|------|------|------|
| user_id | BIGINT | 用户 ID(外键) |
| key | VARCHAR(255) | 配置键名 |
| value | TEXT | 配置值 |
#### 常用配置键
| 键名 | 说明 | 默认值 |
|------|------|--------|
| llm.endpoint | LLM API 端点 | https://api.deepseek.com |
| llm.api_key | LLM API 密钥 | (空) |
| llm.model | LLM 模型名称 | deepseek-v4-pro |
| review.top_n | 审查文件数上限 | 20 |
| review.concurrency | 审查并发数 | 5 |
### 4. repositories 表
克隆的仓库元数据。
```sql
CREATE TABLE IF NOT EXISTS repositories (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
url TEXT NOT NULL,
local_path TEXT NOT NULL,
size_bytes BIGINT DEFAULT 0,
cloned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_used DATETIME DEFAULT CURRENT_TIMESTAMP,
auth_type VARCHAR(20) NOT NULL DEFAULT 'none',
credential TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 仓库 ID(主键,自增) |
| user_id | BIGINT | 所有者用户 ID(外键) |
| url | TEXT | 仓库 URL |
| local_path | TEXT | 本地存储路径 |
| size_bytes | BIGINT | 仓库大小(字节) |
| cloned_at | DATETIME | 克隆时间 |
| last_used | DATETIME | 最后使用时间 |
| auth_type | VARCHAR(20) | 认证类型(none/https/ssh) |
| credential | TEXT | 认证凭证(不明文返回) |
### 5. analyses 表
分析结果存储(PR 描述和代码审查)。
```sql
CREATE TABLE IF NOT EXISTS analyses (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
repo_id BIGINT,
type VARCHAR(50) NOT NULL,
base_ref TEXT NOT NULL,
head_ref TEXT NOT NULL,
result LONGTEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (repo_id) REFERENCES repositories(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 分析 ID(主键,自增) |
| user_id | BIGINT | 用户 ID(外键) |
| repo_id | BIGINT | 仓库 ID(外键) |
| type | VARCHAR(50) | 类型(pr_description/code_review) |
| base_ref | TEXT | 基准分支 |
| head_ref | TEXT | 目标分支 |
| result | LONGTEXT | 结果 JSON |
| created_at | DATETIME | 创建时间 |
#### result 字段格式
**PR 描述类型**:
```json
{
"content": "# 标题\n\n## 概述\n..."
}
```
**代码审查类型**:
```json
{
"file_reviews": [
{
"file_name": "main.go",
"change_lines": 42,
"suggestions": [
{
"severity": "warning",
"description": "...",
"suggestion": "...",
"code_example": "..."
}
],
"raw_review": "..."
}
],
"summary": {
"score": 7,
"overall": "...",
"findings": "...",
"recommendations": "..."
},
"top_n": 20
}
```
### 6. review_notes 表
用户添加的审查笔记。
```sql
CREATE TABLE IF NOT EXISTS review_notes (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
analysis_id BIGINT,
scope VARCHAR(50) NOT NULL,
scope_key TEXT NOT NULL,
content TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (analysis_id) REFERENCES analyses(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 笔记 ID(主键,自增) |
| user_id | BIGINT | 用户 ID(外键) |
| analysis_id | BIGINT | 分析 ID(外键) |
| scope | VARCHAR(50) | 作用域类型 |
| scope_key | TEXT | 作用域键 |
| content | TEXT | 笔记内容 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
#### scope 类型
| scope | scope_key | 说明 |
|-------|-----------|------|
| overall | (空) | 整体审查笔记 |
| file | 文件名 | 文件级别笔记 |
| suggestion | 建议 ID | 建议级别笔记 |
## 索引设计
### 主键索引
- 所有表使用 BIGINT 自增主键
- settings 表使用 key 作为主键
### 唯一索引
- users.email: 唯一约束
### 复合主键
- user_settings: (user_id, key)
### 外键索引
- user_settings.user_id → users.id
- repositories.user_id → users.id
- analyses.user_id → users.id
- analyses.repo_id → repositories.id
- review_notes.user_id → users.id
- review_notes.analysis_id → analyses.id
## 迁移策略
### 自动迁移
```go
func (db *DB) migrate() error {
stmts := []string{
"CREATE TABLE IF NOT EXISTS ...",
// ...
}
for _, s := range stmts {
if _, err := db.conn.Exec(s); err != nil {
return err
}
}
return nil
}
```
### 增量迁移
```go
alterStmts := []string{
"ALTER TABLE repositories ADD COLUMN auth_type VARCHAR(20) NOT NULL DEFAULT 'none'",
"ALTER TABLE repositories ADD COLUMN credential TEXT",
}
for _, s := range alterStmts {
db.conn.Exec(s) // 忽略错误(列已存在)
}
```
- 使用 `IF NOT EXISTS` 避免重复创建
- 忽略 "duplicate column" 错误实现幂等性
### 默认数据初始化
```go
func (db *DB) seedDefaults() error {
for key, val := range models.DefaultSettings {
_, err := db.conn.Exec(
"INSERT IGNORE INTO settings (`key`, value) VALUES (?, ?)", key, val,
)
// ...
}
return nil
}
```
- 使用 `INSERT IGNORE` 避免重复插入
## 数据完整性
### 外键约束
- 级联删除(ON DELETE CASCADE)
- 删除用户时自动清理相关数据
### 数据验证
- 应用层验证(handler 层)
- 数据库约束(NOT NULL, UNIQUE)
## 性能优化
### 查询优化
- 使用参数化查询避免 SQL 注入
- 避免 SELECT *,只查询需要的字段
- 合理使用索引
### 连接管理
- 使用 database/sql 连接池
- 设置合理的连接超时
- 及时关闭连接
### 大字段处理
- result 使用 LONGTEXT
- 考虑分表或外部存储(未来优化)
## 备份策略
### 逻辑备份
```bash
mysqldump -u root -p pr_helper > backup.sql
```
### 恢复
```bash
mysql -u root -p pr_helper < backup.sql
```
### 定期备份
- 建议每日备份
- 保留最近 7 天备份
- 异地备份(生产环境)
## 监控
### 关键指标
- 连接数
- 查询耗时
- 慢查询
- 表大小
### 命令
```sql
-- 查看连接数
SHOW STATUS LIKE 'Threads_connected';
-- 查看慢查询
SHOW VARIABLES LIKE 'slow_query_log';
-- 查看表大小
SELECT table_name, round(((data_length + index_length) / 1024 / 1024), 2) AS "Size (MB)"
FROM information_schema.tables
WHERE table_schema = 'pr_helper';
```