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: 故障排查
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
# 后端服务层
|
||||
|
||||
## 概述
|
||||
|
||||
后端服务层是 PR-Helper 的核心业务逻辑层,位于 handlers 和数据层之间。主要职责包括 Git 操作、LLM 调用、PR 生成和代码审查。
|
||||
|
||||
## 服务架构
|
||||
|
||||
```
|
||||
handlers/
|
||||
↓ 调用
|
||||
services/
|
||||
├── git.go # Git 操作
|
||||
├── llm.go # LLM API 调用
|
||||
├── generate.go # PR 描述生成
|
||||
├── review.go # AI 代码审查
|
||||
├── cache.go # 缓存管理
|
||||
└── notes.go # 审查笔记
|
||||
↓ 使用
|
||||
database/ + filesystem/
|
||||
```
|
||||
|
||||
## Git 服务 (git.go)
|
||||
|
||||
### 核心功能
|
||||
|
||||
#### 克隆仓库
|
||||
|
||||
```go
|
||||
func Clone(opts CloneOptions, progressFn func(event string, data interface{})) (*CloneResult, error)
|
||||
```
|
||||
|
||||
- 支持基本认证(用户名/密码)
|
||||
- 可选深度克隆
|
||||
- 进度回调(SSE 事件)
|
||||
- 返回仓库路径、分支、标签、提交数、大小
|
||||
|
||||
#### 获取引用
|
||||
|
||||
```go
|
||||
func GetRefs(repo *git.Repository) ([]RefInfo, error)
|
||||
```
|
||||
|
||||
- 返回所有分支和标签
|
||||
- 标记 HEAD 分支
|
||||
- 解析注解标签到提交
|
||||
|
||||
#### 获取图形数据
|
||||
|
||||
```go
|
||||
func GetGraph(repo *git.Repository, maxCommits int) (*GraphData, error)
|
||||
```
|
||||
|
||||
- D3.js 兼容格式
|
||||
- 包含提交、引用、边
|
||||
- BFS 遍历,限制最大提交数
|
||||
|
||||
#### 获取差异
|
||||
|
||||
```go
|
||||
func GetDiff(repo *git.Repository, baseRef, headRef string) (string, error)
|
||||
func GetDiffFiles(repo *git.Repository, baseRef, headRef string) ([]FileDiff, error)
|
||||
```
|
||||
|
||||
- 统一差异格式
|
||||
- 按文件分割
|
||||
- 解析变更行数
|
||||
|
||||
#### 获取提交日志
|
||||
|
||||
```go
|
||||
func GetCommitLog(repo *git.Repository, refName string, maxCommits int) ([]CommitInfo, error)
|
||||
```
|
||||
|
||||
- BFS 遍历父提交
|
||||
- 限制最大提交数
|
||||
- 包含作者、时间、父提交
|
||||
|
||||
### 数据结构
|
||||
|
||||
```go
|
||||
type RefInfo struct {
|
||||
Name string `json:"name"`
|
||||
Hash string `json:"hash"`
|
||||
IsHead bool `json:"is_head"`
|
||||
IsTag bool `json:"is_tag"`
|
||||
}
|
||||
|
||||
type CommitInfo struct {
|
||||
Hash string `json:"hash"`
|
||||
ShortHash string `json:"short_hash"`
|
||||
Message string `json:"message"`
|
||||
Author string `json:"author"`
|
||||
Email string `json:"email"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
ParentIDs []string `json:"parent_ids"`
|
||||
}
|
||||
|
||||
type GraphData struct {
|
||||
Commits []CommitInfo `json:"commits"`
|
||||
Refs []RefInfo `json:"refs"`
|
||||
Edges []Edge `json:"edges"`
|
||||
}
|
||||
|
||||
type FileDiff struct {
|
||||
Filename string `json:"filename"`
|
||||
Patch string `json:"patch"`
|
||||
}
|
||||
```
|
||||
|
||||
## LLM 服务 (llm.go)
|
||||
|
||||
### 配置管理
|
||||
|
||||
```go
|
||||
type LLMConfig struct {
|
||||
Endpoint string
|
||||
APIKey string
|
||||
Model string
|
||||
}
|
||||
|
||||
func GetLLMConfig(db *sql.DB, userID int64) (LLMConfig, error)
|
||||
```
|
||||
|
||||
- 从 user_settings 表读取
|
||||
- 每用户独立配置
|
||||
- 默认模型: deepseek-v4-pro
|
||||
|
||||
### 流式调用
|
||||
|
||||
```go
|
||||
type StreamCallback func(event string, data interface{})
|
||||
|
||||
func ChatStream(config LLMConfig, messages []goopenai.ChatCompletionMessage, callback StreamCallback) (string, error)
|
||||
```
|
||||
|
||||
- 使用 go-openai 客户端
|
||||
- SSE 流式响应
|
||||
- 回调函数处理每个 chunk
|
||||
- 返回完整响应文本
|
||||
|
||||
### JSON 提取
|
||||
|
||||
```go
|
||||
func extractJSON(s string) string
|
||||
```
|
||||
|
||||
- 从 Markdown 代码块提取
|
||||
- 从混合文本中提取 JSON 对象/数组
|
||||
- 处理嵌套括号
|
||||
|
||||
## PR 生成服务 (generate.go)
|
||||
|
||||
### 生成流程
|
||||
|
||||
```go
|
||||
func GeneratePR(db *sql.DB, repoPath, base, head string, userID int64, callback StreamCallback) (string, error)
|
||||
```
|
||||
|
||||
1. **读取 LLM 配置**: 从用户设置获取
|
||||
2. **打开仓库**: 使用 go-git
|
||||
3. **获取提交**: 过滤 base 和 head 之间的提交
|
||||
4. **获取差异**: 生成统一差异
|
||||
5. **构建提示**: 包含提交记录和差异
|
||||
6. **调用 LLM**: 流式生成
|
||||
7. **返回结果**: Markdown 格式的 PR 描述
|
||||
|
||||
### 提示模板
|
||||
|
||||
```markdown
|
||||
你是一个专业的技术文档撰写助手。根据以下 Git 变更信息,生成一份 PR 描述。
|
||||
|
||||
## Commit 记录
|
||||
{commits}
|
||||
|
||||
## 代码变更 (Diff)
|
||||
{diff}
|
||||
|
||||
请直接输出 Markdown 格式的 PR 描述,包含以下部分:
|
||||
|
||||
# 标题
|
||||
**类型**: feat|fix|refactor|docs|chore|style|test|perf
|
||||
|
||||
## 概述
|
||||
一段话概述变更内容
|
||||
|
||||
## 详细说明
|
||||
按模块分组的详细变更说明
|
||||
|
||||
## 影响范围
|
||||
影响范围说明
|
||||
```
|
||||
|
||||
### 大型差异处理
|
||||
|
||||
- 截断到 60k 字符
|
||||
- 保留完整提交记录
|
||||
- 标记截断位置
|
||||
|
||||
## 代码审查服务 (review.go)
|
||||
|
||||
### 审查流程
|
||||
|
||||
```go
|
||||
func GenerateReview(db *sql.DB, repoPath, base, head string, topN, concurrency int, userID int64, callback StreamCallback) (*ReviewResult, error)
|
||||
```
|
||||
|
||||
1. **获取差异文件**: 按文件分割
|
||||
2. **排序**: 按变更行数降序
|
||||
3. **Top-N 过滤**: 只分析前 N 个文件
|
||||
4. **并发审查**: 使用信号量控制并发
|
||||
5. **生成汇总**: 聚合所有文件结果
|
||||
6. **返回结果**: 结构化 JSON
|
||||
|
||||
### Top-N 策略
|
||||
|
||||
```go
|
||||
// 按变更行数排序
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return countDiffLines(files[i].Patch) > countDiffLines(files[j].Patch)
|
||||
})
|
||||
|
||||
// 应用 Top-N
|
||||
if topN > 0 && topN < len(files) {
|
||||
files = files[:topN]
|
||||
}
|
||||
```
|
||||
|
||||
- 默认 Top-N: 20
|
||||
- 可通过设置或请求参数调整
|
||||
- 0 表示分析所有文件
|
||||
|
||||
### 并发控制
|
||||
|
||||
```go
|
||||
sem := make(chan struct{}, concurrency)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i, file := range files {
|
||||
wg.Add(1)
|
||||
go func(idx int, f FileDiff) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{} // 获取槽位
|
||||
defer func() { <-sem }() // 释放槽位
|
||||
// ... 审查逻辑
|
||||
}(i, file)
|
||||
}
|
||||
```
|
||||
|
||||
- 默认并发数: 5
|
||||
- 信号量控制最大并发
|
||||
- 线程安全的回调函数
|
||||
|
||||
### 单文件审查提示
|
||||
|
||||
```markdown
|
||||
你是一个资深代码审查专家。请审查以下代码变更,给出专业的 Review 意见。
|
||||
|
||||
## 文件: {filename}
|
||||
## 变更行数: +{additions} / -{deletions}
|
||||
|
||||
## Diff
|
||||
{patch}
|
||||
|
||||
请按以下 JSON 格式输出审查意见(直接输出 JSON 数组,不要包含 markdown 代码块标记):
|
||||
[
|
||||
{
|
||||
"severity": "critical 或 warning 或 info",
|
||||
"description": "问题描述",
|
||||
"suggestion": "建议的修改方案",
|
||||
"code_example": "建议的代码(如有)"
|
||||
}
|
||||
]
|
||||
|
||||
严重程度说明:
|
||||
- critical: 严重问题(安全漏洞、数据丢失风险、崩溃风险)
|
||||
- warning: 建议改进(性能问题、代码规范、可维护性)
|
||||
- info: 提示信息(最佳实践、可选优化)
|
||||
|
||||
如果代码没有问题,输出空数组 []。
|
||||
请用中文回复。
|
||||
```
|
||||
|
||||
### 汇总生成
|
||||
|
||||
```markdown
|
||||
以下是多个文件的代码审查结果,请给出整体评估:
|
||||
|
||||
{reviews}
|
||||
|
||||
请按以下 JSON 格式输出(直接输出 JSON,不要包含 markdown 代码块标记):
|
||||
{
|
||||
"score": 7,
|
||||
"overall": "总体评价(2-3 句话)",
|
||||
"findings": "按严重程度排序的主要发现汇总",
|
||||
"recommendations": "改进建议,用换行分隔多条建议"
|
||||
}
|
||||
```
|
||||
|
||||
### 数据结构
|
||||
|
||||
```go
|
||||
type ReviewSuggestion struct {
|
||||
Severity string `json:"severity"`
|
||||
Description string `json:"description"`
|
||||
Suggestion string `json:"suggestion"`
|
||||
CodeExample string `json:"code_example,omitempty"`
|
||||
}
|
||||
|
||||
type FileReview struct {
|
||||
FileName string `json:"file_name"`
|
||||
ChangeLines int `json:"change_lines"`
|
||||
Suggestions []ReviewSuggestion `json:"suggestions"`
|
||||
RawReview string `json:"raw_review"`
|
||||
}
|
||||
|
||||
type ReviewSummary struct {
|
||||
Score int `json:"score"`
|
||||
Overall string `json:"overall"`
|
||||
Findings string `json:"findings"`
|
||||
Recommendations string `json:"recommendations"`
|
||||
}
|
||||
|
||||
type ReviewResult struct {
|
||||
FileReviews []FileReview `json:"file_reviews"`
|
||||
Summary ReviewSummary `json:"summary"`
|
||||
TopN int `json:"top_n"`
|
||||
}
|
||||
```
|
||||
|
||||
## SSE 事件格式
|
||||
|
||||
### PR 生成事件
|
||||
|
||||
| 事件 | 数据 | 说明 |
|
||||
|------|------|------|
|
||||
| `content` | `{content: string}` | LLM 输出的 Markdown 片段 |
|
||||
| `done` | `{content: ""}` | 生成完成 |
|
||||
|
||||
### 代码审查事件
|
||||
|
||||
| 事件 | 数据 | 说明 |
|
||||
|------|------|------|
|
||||
| `start` | `{total_files, reviewed_files, top_n}` | 审查开始 |
|
||||
| `file_start` | `{file, index, total}` | 开始审查文件 |
|
||||
| `content` | `{content: string}` | LLM 输出片段 |
|
||||
| `suggestion` | `{file, severity, content}` | 审查建议 |
|
||||
| `file_end` | `{file}` | 文件审查完成 |
|
||||
| `summary` | `{score, overall, findings, recommendations}` | 汇总结果 |
|
||||
| `progress` | `{step: string}` | 进度更新 |
|
||||
| `error` | `{message: string}` | 错误信息 |
|
||||
| `done` | `{content: ""}` | 审查完成 |
|
||||
|
||||
## 缓存服务 (cache.go)
|
||||
|
||||
### 仓库缓存
|
||||
|
||||
- 存储路径: `data/repos/`
|
||||
- 命名规则: `{url}_{timestamp}`
|
||||
- 过期清理: 可配置天数(默认 7 大)
|
||||
- 大小限制: 可配置最大大小(默认 5GB)
|
||||
|
||||
### 缓存生命周期
|
||||
|
||||
1. **克隆**: 首次访问时克隆
|
||||
2. **使用**: 更新 last_used 时间
|
||||
3. **过期**: 定期清理过期仓库
|
||||
4. **删除**: 手动或自动删除
|
||||
|
||||
## 笔记服务 (notes.go)
|
||||
|
||||
### 笔记操作
|
||||
|
||||
```go
|
||||
func SaveNote(db *sql.DB, analysisID int64, scope, scopeKey, content string) (*models.ReviewNote, error)
|
||||
func GetNotes(db *sql.DB, analysisID int64, scope string) ([]models.ReviewNote, error)
|
||||
```
|
||||
|
||||
### 作用域类型
|
||||
|
||||
- `overall`: 整体审查笔记
|
||||
- `file`: 文件级别笔记(scope_key = 文件名)
|
||||
- `suggestion`: 建议级别笔记(scope_key = 建议 ID)
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 错误传播
|
||||
|
||||
- 服务层返回 error
|
||||
- 处理器层转换为 HTTP 状态码
|
||||
- SSE 端点通过 error 事件发送
|
||||
|
||||
### 常见错误
|
||||
|
||||
- 仓库不存在
|
||||
- LLM API 密钥未配置
|
||||
- LLM 调用失败
|
||||
- 差异过大
|
||||
- 数据库操作失败
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 并发审查
|
||||
|
||||
- 信号量控制并发数
|
||||
- 线程安全的回调
|
||||
- 避免数据竞争
|
||||
|
||||
### 差异截断
|
||||
|
||||
- 单文件: 30k 字符
|
||||
- PR 生成: 60k 字符
|
||||
- 避免 token 超限
|
||||
|
||||
### 增量渲染
|
||||
|
||||
- 按文件分批
|
||||
- 异步处理
|
||||
- 进度反馈
|
||||
Reference in New Issue
Block a user