Some checks failed
Build and Deploy Log Center / build-and-deploy (push) Failing after 1m55s
1021 lines
36 KiB
Markdown
1021 lines
36 KiB
Markdown
# Bug 自动修复工作流优化方案
|
||
|
||
> 文档版本: v1.0
|
||
> 创建日期: 2026-02-25
|
||
> 状态: 待实施
|
||
|
||
---
|
||
|
||
## 📋 目录
|
||
|
||
- [背景](#背景)
|
||
- [当前问题](#当前问题)
|
||
- [优化方案](#优化方案)
|
||
- [状态设计](#状态设计)
|
||
- [完整工作流程](#完整工作流程)
|
||
- [Web 界面设计](#web-界面设计)
|
||
- [技术实施](#技术实施)
|
||
- [实施计划](#实施计划)
|
||
- [决策清单](#决策清单)
|
||
|
||
---
|
||
|
||
## 背景
|
||
|
||
### 现状
|
||
|
||
当前 Repair Agent 实现了 Bug 的自动修复功能,但存在以下问题:
|
||
|
||
1. **Git 工作流不规范**:修复后直接在本地合并到 main 分支,无法在 Gitea 追溯
|
||
2. **缺少人工审核**:AI 修复的代码直接进入主分支,存在风险
|
||
3. **状态过于复杂**:9 个状态流转复杂,难以维护
|
||
4. **Web 界面功能缺失**:无法在日志中台操作 PR 合并
|
||
|
||
### 目标
|
||
|
||
- ✅ 规范 Git 工作流,通过 PR 合并代码
|
||
- ✅ 增加人工审核环节,保障代码质量
|
||
- ✅ 简化 Bug 状态,优化状态流转
|
||
- ✅ 完善 Web 界面,提供便捷的操作入口
|
||
|
||
---
|
||
|
||
## 当前问题
|
||
|
||
### 问题 1:本地合并无法追溯
|
||
|
||
**当前流程:**
|
||
```bash
|
||
git checkout main
|
||
git merge fix/auto-xxx # ← 本地合并
|
||
git push origin main
|
||
git branch -d fix/auto-xxx
|
||
```
|
||
|
||
**问题:**
|
||
- ❌ Gitea 仓库里没有 PR 记录
|
||
- ❌ 无法在 Gitea UI 追溯这个修复来自哪个分支
|
||
- ❌ 团队成员不知道这个变更是怎么来的
|
||
- ❌ 无法进行代码审核和讨论
|
||
|
||
### 问题 2:状态过多且流转复杂
|
||
|
||
**当前状态(9 个):**
|
||
```python
|
||
NEW → VERIFYING → CANNOT_REPRODUCE
|
||
↓
|
||
PENDING_FIX → FIXING → FIXED → VERIFIED → DEPLOYED
|
||
↓
|
||
FIX_FAILED
|
||
```
|
||
|
||
**问题:**
|
||
- ❌ `VERIFYING` 和 `PENDING_FIX` 功能重叠
|
||
- ❌ `FIXED` 和 `VERIFIED` 区分不明确
|
||
- ❌ `CANNOT_REPRODUCE` 和 `FIX_FAILED` 都表示失败
|
||
|
||
### 问题 3:缺少人工审核环节
|
||
|
||
**当前流程:**
|
||
```
|
||
AI 修复 → commit → push → 本地 merge → push main
|
||
```
|
||
|
||
- ❌ 没有审核环节
|
||
- ❌ AI 改错了直接污染主分支
|
||
- ❌ 无法事前检查代码质量
|
||
|
||
---
|
||
|
||
## 优化方案
|
||
|
||
### 核心改进
|
||
|
||
1. **通过 PR 合并代码**:AI 修复后自动创建 PR,保留审核环节
|
||
2. **简化 Bug 状态**:从 9 个减少到 6 个,流转更清晰
|
||
3. **完善 Web 界面**:提供 PR 审核入口
|
||
|
||
### 关键对比
|
||
|
||
| 维度 | 当前方式(本地合并) | 优化方式(PR 合并) |
|
||
|------|---------------------|-------------------|
|
||
| **追溯性** | ❌ 无 PR 记录 | ✅ Gitea 有完整记录 |
|
||
| **审核** | ❌ 无审核环节 | ✅ 人工审核 |
|
||
| **安全性** | ⚠️ 直接影响 main | ✅ 合并前可检查 |
|
||
| **协作** | ❌ 团队不可见 | ✅ Gitea UI 可见 |
|
||
| **回滚** | 需要 revert | 关闭 PR 即可 |
|
||
|
||
---
|
||
|
||
## 状态设计
|
||
|
||
### 优化后的状态(6 个)
|
||
|
||
```python
|
||
class BugStatus(str, Enum):
|
||
"""优化后的 Bug 状态"""
|
||
NEW = "NEW" # 新发现的 Bug
|
||
FIXING = "FIXING" # AI 正在修复
|
||
PENDING_REVIEW = "PENDING_REVIEW" # 已修复,等待人工审核(有 PR)
|
||
MERGED = "MERGED" # PR 已合并到 main 分支
|
||
DEPLOYED = "DEPLOYED" # 已部署到生产环境
|
||
FAILED = "FAILED" # 修复失败或无法修复
|
||
```
|
||
|
||
### 状态流转图
|
||
|
||
```
|
||
┌─────┐ Agent扫描 ┌────────┐ AI修复成功 ┌──────────────┐
|
||
│ NEW │ ───────────▶ │ FIXING │ ────────────▶ │PENDING_REVIEW│
|
||
└─────┘ └────────┘ └──────────────┘
|
||
│ │
|
||
AI修复失败 人工审核合并
|
||
↓ ↓
|
||
┌────────┐ ┌────────┐
|
||
│ FAILED │ │ MERGED │
|
||
└────────┘ └────────┘
|
||
│
|
||
CI/CD部署
|
||
↓
|
||
┌──────────┐
|
||
│ DEPLOYED │
|
||
└──────────┘
|
||
```
|
||
|
||
### 状态说明
|
||
|
||
| 状态 | 说明 | 触发条件 | 后续操作 |
|
||
|------|------|---------|---------|
|
||
| **NEW** | 新发现的 Bug | Log Center 接收到错误日志 | Agent 定时扫描并修复 |
|
||
| **FIXING** | AI 正在修复 | Agent 开始执行修复任务 | AI 修复完成或失败 |
|
||
| **PENDING_REVIEW** | 等待审核 | AI 修复成功,PR 已创建 | 人工在 Gitea 审核 |
|
||
| **MERGED** | 已合并 | PR 在 Gitea 被合并 | CI/CD 自动部署 |
|
||
| **DEPLOYED** | 已部署 | 部署到生产环境成功 | 结束 |
|
||
| **FAILED** | 修复失败 | AI 无法修复或测试失败 | 人工介入 |
|
||
|
||
---
|
||
|
||
## 完整工作流程
|
||
|
||
### 阶段 1:自动修复(Repair Agent)
|
||
|
||
```python
|
||
1. 扫描 NEW 状态的 Bug
|
||
2. 更新状态: NEW → FIXING
|
||
3. 调用 Claude Code CLI 修复代码
|
||
4. 运行测试验证修复
|
||
5. commit + push fix 分支到远程
|
||
6. 调用 Gitea API 创建 PR
|
||
7. 更新状态: FIXING → PENDING_REVIEW
|
||
8. 上传修复报告(包含 PR 链接)
|
||
```
|
||
|
||
**关键数据结构:**
|
||
```json
|
||
{
|
||
"bug_id": 123,
|
||
"status": "PENDING_REVIEW",
|
||
"repair_report": {
|
||
"pr_number": 45,
|
||
"pr_url": "https://gitea.xxx/owner/repo/pulls/45",
|
||
"branch_name": "fix/auto-20260225-1430",
|
||
"modified_files": ["app/views.py", "app/models.py"],
|
||
"diff": "...",
|
||
"test_passed": true,
|
||
"test_output": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 阶段 2:人工审核(Web 界面)
|
||
|
||
#### 方案 A:跳转到 Gitea 审核(推荐)
|
||
|
||
**优势:**
|
||
- ✅ 实现简单,只需提供跳转链接
|
||
- ✅ 利用 Gitea 原生 PR 功能
|
||
- ✅ 支持完整的代码审核流程
|
||
|
||
**用户操作流程:**
|
||
```
|
||
1. 打开日志中台 Bug 列表
|
||
2. 筛选 "PENDING_REVIEW" 状态
|
||
3. 点击 Bug 查看详情
|
||
4. 查看修复报告(AI 分析、修改文件、测试结果)
|
||
5. 点击 "前往 Gitea 审核 PR" 按钮 → 新标签页打开
|
||
6. 在 Gitea 查看完整 diff
|
||
7. 确认无误 → 点击 "Merge" 按钮
|
||
或发现问题 → 点击 "Close" 并标记为 FAILED
|
||
```
|
||
|
||
#### 方案 B:在日志中台内审核(高级)
|
||
|
||
**优势:**
|
||
- ✅ 无需跳转,统一操作界面
|
||
- ✅ 可定制审核流程
|
||
|
||
**劣势:**
|
||
- ⚠️ 需要开发 diff viewer
|
||
- ⚠️ 需要对接 Gitea API
|
||
|
||
**用户操作流程:**
|
||
```
|
||
1. 在日志中台 Bug 详情页
|
||
2. 查看嵌入的代码 diff
|
||
3. 点击 "批准并合并" 按钮
|
||
4. 后端调用 Gitea API 合并 PR
|
||
```
|
||
|
||
---
|
||
|
||
### 阶段 3:自动部署(CI/CD + Webhook)
|
||
|
||
#### 3.1 Gitea Webhook 通知
|
||
|
||
**Gitea 配置:**
|
||
```
|
||
Settings → Webhooks → Add Webhook
|
||
- URL: https://your-log-center.com/api/webhooks/gitea
|
||
- Events: Pull Requests (merged)
|
||
- Secret: <配置密钥>
|
||
```
|
||
|
||
**日志中台接收:**
|
||
```python
|
||
@router.post("/webhooks/gitea")
|
||
async def gitea_webhook(payload: dict):
|
||
"""接收 Gitea PR 合并事件"""
|
||
if payload["action"] == "merged":
|
||
pr_number = payload["pull_request"]["number"]
|
||
|
||
# 通过 PR 号找到对应的 Bug
|
||
bug = await find_bug_by_pr_number(pr_number)
|
||
|
||
if bug and bug.status == BugStatus.PENDING_REVIEW:
|
||
# 更新状态: PENDING_REVIEW → MERGED
|
||
await update_bug_status(bug.id, BugStatus.MERGED)
|
||
logger.info(f"Bug #{bug.id} PR 已合并,等待部署")
|
||
```
|
||
|
||
#### 3.2 部署成功通知
|
||
|
||
**CI/CD Pipeline 配置:**
|
||
```yaml
|
||
# .gitlab-ci.yml 或 Jenkinsfile
|
||
deploy:
|
||
stage: deploy
|
||
script:
|
||
- deploy_to_production.sh
|
||
- |
|
||
# 部署成功后通知日志中台
|
||
curl -X POST https://your-log-center.com/api/webhooks/deployment \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-Secret: ${WEBHOOK_SECRET}" \
|
||
-d '{
|
||
"project_id": "rtc_backend",
|
||
"status": "success",
|
||
"commit": "'$CI_COMMIT_SHA'"
|
||
}'
|
||
```
|
||
|
||
**日志中台更新状态:**
|
||
```python
|
||
@router.post("/webhooks/deployment")
|
||
async def deployment_webhook(payload: dict):
|
||
"""接收部署成功通知"""
|
||
if payload["status"] == "success":
|
||
commit = payload["commit"]
|
||
|
||
# 找到该 commit 关联的 Bug(MERGED 状态)
|
||
bugs = await find_bugs_by_commit(commit, status=BugStatus.MERGED)
|
||
|
||
for bug in bugs:
|
||
# 更新状态: MERGED → DEPLOYED
|
||
await update_bug_status(bug.id, BugStatus.DEPLOYED)
|
||
logger.info(f"Bug #{bug.id} 已部署到生产环境")
|
||
```
|
||
|
||
---
|
||
|
||
## Web 界面设计
|
||
|
||
### 1. Bug 列表页
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ Bug 自动修复看板 🔍 [搜索框] [筛选▼] │
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 📊 今日统计 │
|
||
│ ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │
|
||
│ │ 🆕 新发现 │ 🔧 修复中 │ 🟡 待审核 │ ✅ 已合并 │ 🚀 已部署 │ │
|
||
│ │ 12 │ 3 │ 5 │ 8 │ 15 │ │
|
||
│ └──────────┴──────────┴──────────┴──────────┴──────────┘ │
|
||
│ │
|
||
│ 状态筛选: [ 全部 ] [ 🟡 待审核 ] [ 🔧 修复中 ] [ ❌ 失败 ] │
|
||
│ │
|
||
│ 🟡 待审核 (需要你的操作) │
|
||
│ ┌──────────────────────────────────────────────────────┐ │
|
||
│ │ #123 │ TypeError in user_login │ 🟡 待审核 │ 详情 │ │
|
||
│ │ │ rtc_backend | 2h ago │ │ → │ │
|
||
│ ├──────┼──────────────────────────────┼──────────┼─────┤ │
|
||
│ │ #124 │ NullPointerException │ 🟡 待审核 │ 详情 │ │
|
||
│ │ │ rtc_backend | 1h ago │ │ → │ │
|
||
│ ├──────┼──────────────────────────────┼──────────┼─────┤ │
|
||
│ │ #125 │ DB connection timeout │ 🟡 待审核 │ 详情 │ │
|
||
│ │ │ rtc_web | 30m ago │ │ → │ │
|
||
│ └──────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 🔧 修复中 │
|
||
│ ┌──────────────────────────────────────────────────────┐ │
|
||
│ │ #126 │ API endpoint error │ 🔧 修复中 │ ... │ │
|
||
│ └──────────────────────────────────────────────────────┘ │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Bug 详情页(方案 A:跳转到 Gitea)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ← 返回列表 Bug #123 │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 📌 基本信息 │
|
||
│ ┌───────────────────────────────────────────────────────┐ │
|
||
│ │ 标题: TypeError: 'NoneType' object is not iterable │ │
|
||
│ │ 项目: rtc_backend │ │
|
||
│ │ 状态: 🟡 PENDING_REVIEW (待审核) │ │
|
||
│ │ 环境: production │ │
|
||
│ │ 发现时间: 2026-02-25 12:30:15 │ │
|
||
│ │ 修复时间: 2026-02-25 14:30:45 │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 🐛 错误详情 │
|
||
│ ┌───────────────────────────────────────────────────────┐ │
|
||
│ │ 文件: app/views.py │ │
|
||
│ │ 行号: 24 │ │
|
||
│ │ 错误: TypeError: 'NoneType' object is not iterable │ │
|
||
│ │ │ │
|
||
│ │ 堆栈: │ │
|
||
│ │ File "app/views.py", line 24, in user_login │ │
|
||
│ │ for item in request.user: │ │
|
||
│ │ TypeError: 'NoneType' object is not iterable │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 📋 修复报告 │
|
||
│ ┌───────────────────────────────────────────────────────┐ │
|
||
│ │ AI 分析: │ │
|
||
│ │ request.user 可能为 None,需要添加 null 检查 │ │
|
||
│ │ │ │
|
||
│ │ 修改文件: 2 个 │ │
|
||
│ │ - app/views.py (+3, -1) │ │
|
||
│ │ - tests/test_views.py (+12, -0) │ │
|
||
│ │ │ │
|
||
│ │ 测试结果: ✅ 通过 (2 tests passed) │ │
|
||
│ │ │ │
|
||
│ │ 修复分支: fix/auto-20260225-1430 │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 🔗 Pull Request │
|
||
│ ┌───────────────────────────────────────────────────────┐ │
|
||
│ │ PR #45: fix: auto repair bug #123 │ │
|
||
│ │ fix/auto-20260225-1430 → main │ │
|
||
│ │ │ │
|
||
│ │ 修改: 2 files changed (+15, -1) │ │
|
||
│ │ 状态: 🟡 Open (等待审核) │ │
|
||
│ │ │ │
|
||
│ │ ┌──────────────────────────────┐ │ │
|
||
│ │ │ 🔍 前往 Gitea 审核 PR → │ ← 主要操作 │ │
|
||
│ │ └──────────────────────────────┘ │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 📄 代码变更预览 │
|
||
│ ┌───────────────────────────────────────────────────────┐ │
|
||
│ │ app/views.py │ │
|
||
│ │ ───────────────────────────────────── │ │
|
||
│ │ 23 │ def user_login(request): │ │
|
||
│ │ -24 │ for item in request.user: │ │ ← 删除
|
||
│ │ +24 │ if request.user is not None: │ │ ← 新增
|
||
│ │ +25 │ for item in request.user: │ │ ← 新增
|
||
│ │ 26 │ # ... rest of code │ │
|
||
│ │ │ │
|
||
│ │ [查看完整 diff →] │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 📝 操作日志 │
|
||
│ ┌───────────────────────────────────────────────────────┐ │
|
||
│ │ 2026-02-25 14:30 状态更新: FIXING → PENDING_REVIEW │ │
|
||
│ │ 2026-02-25 14:28 AI 修复完成,PR 已创建 │ │
|
||
│ │ 2026-02-25 14:25 开始自动修复 │ │
|
||
│ │ 2026-02-25 12:30 Bug 已记录 │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 统计看板
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 📊 Bug 修复统计 时间范围: [最近 7 天 ▼]│
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 整体指标 │
|
||
│ ┌──────────┬──────────┬──────────┬──────────┐ │
|
||
│ │ 总计发现 │ 自动修复 │ 修复成功率│ 平均耗时 │ │
|
||
│ │ 156 │ 142 │ 91.0% │ 12 min │ │
|
||
│ └──────────┴──────────┴──────────┴──────────┘ │
|
||
│ │
|
||
│ 每日趋势 │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ 40 ┤ │ │
|
||
│ │ 30 ┤ ● ● │ │
|
||
│ │ 20 ┤ ● ● ● ● │ │
|
||
│ │ 10 ┤ ● ● ● ● ● ● │ │
|
||
│ │ 0 └────────────────────────────────────── │ │
|
||
│ │ Mon Tue Wed Thu Fri Sat Sun │ │
|
||
│ │ │ │
|
||
│ │ ■ 新发现 ■ 自动修复 ■ 修复失败 │ │
|
||
│ └─────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 项目分布 │
|
||
│ ┌──────────────┬───────────┬───────────┬─────────┐ │
|
||
│ │ 项目 │ 待审核 │ 已合并 │ 失败 │ │
|
||
│ ├──────────────┼───────────┼───────────┼─────────┤ │
|
||
│ │ rtc_backend │ 3 │ 12 │ 1 │ │
|
||
│ │ rtc_web │ 2 │ 8 │ 0 │ │
|
||
│ │ airhub_app │ 0 │ 5 │ 2 │ │
|
||
│ └──────────────┴───────────┴───────────┴─────────┘ │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 技术实施
|
||
|
||
### 修改清单
|
||
|
||
#### 1. Repair Agent 端
|
||
|
||
**文件:`log_center/repair_agent/agent/git_manager.py`**
|
||
|
||
```python
|
||
def create_pull_request(
|
||
self,
|
||
title: str,
|
||
description: str,
|
||
gitea_url: str,
|
||
gitea_token: str,
|
||
) -> tuple[bool, str]:
|
||
"""
|
||
调用 Gitea API 创建 Pull Request
|
||
|
||
Args:
|
||
title: PR 标题
|
||
description: PR 描述
|
||
gitea_url: Gitea 服务器地址
|
||
gitea_token: Gitea API Token
|
||
|
||
Returns:
|
||
(是否成功, PR URL 或错误信息)
|
||
"""
|
||
if not self.repo or not self.github_repo:
|
||
return False, "未配置 Git 仓库"
|
||
|
||
try:
|
||
# 解析仓库信息
|
||
# github_repo 格式: https://gitea.xxx/owner/repo.git
|
||
import re
|
||
match = re.search(r'([^/]+)/([^/]+?)(?:\.git)?$', self.github_repo)
|
||
if not match:
|
||
return False, "无法解析仓库信息"
|
||
|
||
owner, repo = match.groups()
|
||
current_branch = self.repo.active_branch.name
|
||
base_branch = "main" if "main" in self.repo.heads else "master"
|
||
|
||
# 调用 Gitea API
|
||
import httpx
|
||
api_url = f"{gitea_url}/api/v1/repos/{owner}/{repo}/pulls"
|
||
headers = {
|
||
"Authorization": f"token {gitea_token}",
|
||
"Content-Type": "application/json",
|
||
}
|
||
payload = {
|
||
"title": title,
|
||
"head": current_branch,
|
||
"base": base_branch,
|
||
"body": description,
|
||
}
|
||
|
||
response = httpx.post(api_url, json=payload, headers=headers, timeout=30)
|
||
response.raise_for_status()
|
||
|
||
data = response.json()
|
||
pr_url = data.get("html_url", "")
|
||
pr_number = data.get("number", 0)
|
||
|
||
logger.info(f"PR 创建成功: #{pr_number} - {pr_url}")
|
||
return True, pr_url
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建 PR 失败: {e}")
|
||
return False, str(e)
|
||
```
|
||
|
||
**文件:`log_center/repair_agent/agent/core.py`**
|
||
|
||
修改自动提交部分(Line 256-267):
|
||
|
||
```python
|
||
# 自动提交并创建 PR(仅在 Git 启用时)
|
||
if git_enabled and auto_commit and modified_files and git_manager:
|
||
bug_ids = ", ".join([f"#{b.id}" for b in bugs])
|
||
commit_msg = f"fix: auto repair bugs {bug_ids}"
|
||
|
||
# Step 1: commit 代码
|
||
git_manager.commit(commit_msg)
|
||
logger.info("代码已提交")
|
||
|
||
# Step 2: push fix 分支
|
||
git_manager.push()
|
||
logger.info("fix 分支已推送")
|
||
|
||
# Step 3: 创建 PR(替代原来的 merge_to_main_and_push)
|
||
success, pr_url = git_manager.create_pull_request(
|
||
title=commit_msg,
|
||
description=self._generate_pr_description(bugs, output, modified_files),
|
||
gitea_url=settings.gitea_url,
|
||
gitea_token=settings.gitea_token,
|
||
)
|
||
|
||
if success:
|
||
logger.info(f"PR 已创建: {pr_url}")
|
||
# 更新修复报告,添加 PR 信息
|
||
for bug in bugs:
|
||
self._update_pr_info(bug.id, pr_url)
|
||
else:
|
||
logger.warning(f"PR 创建失败: {pr_url},请手动创建")
|
||
elif not git_enabled and auto_commit:
|
||
logger.info("未配置 GitHub 仓库,跳过自动提交")
|
||
```
|
||
|
||
新增辅助方法:
|
||
|
||
```python
|
||
def _generate_pr_description(
|
||
self,
|
||
bugs: list[Bug],
|
||
ai_output: str,
|
||
modified_files: list[str],
|
||
) -> str:
|
||
"""生成 PR 描述"""
|
||
lines = [
|
||
f"## 🤖 AI 自动修复",
|
||
"",
|
||
f"本 PR 修复了 {len(bugs)} 个 Bug:",
|
||
"",
|
||
]
|
||
|
||
for bug in bugs:
|
||
lines.append(f"- Bug #{bug.id}: {bug.error.type} - {bug.error.message[:50]}")
|
||
|
||
lines.extend([
|
||
"",
|
||
"## 📝 修复说明",
|
||
"",
|
||
ai_output[:500], # 截取前 500 字符
|
||
"",
|
||
"## 📄 修改文件",
|
||
"",
|
||
])
|
||
|
||
for file in modified_files[:10]: # 最多显示 10 个文件
|
||
lines.append(f"- `{file}`")
|
||
|
||
lines.extend([
|
||
"",
|
||
"---",
|
||
"",
|
||
"⚠️ **请仔细审核代码变更后再合并**",
|
||
])
|
||
|
||
return "\n".join(lines)
|
||
|
||
def _update_pr_info(self, bug_id: int, pr_url: str):
|
||
"""更新 Bug 的 PR 信息"""
|
||
try:
|
||
# 从 PR URL 提取 PR 号
|
||
import re
|
||
match = re.search(r'/pulls/(\d+)', pr_url)
|
||
pr_number = int(match.group(1)) if match else 0
|
||
|
||
# 调用 TaskManager 更新
|
||
self.task_manager.update_pr_info(bug_id, pr_number, pr_url)
|
||
except Exception as e:
|
||
logger.error(f"更新 PR 信息失败: {e}")
|
||
```
|
||
|
||
**文件:`log_center/repair_agent/models/bug.py`**
|
||
|
||
简化状态枚举:
|
||
|
||
```python
|
||
class BugStatus(str, Enum):
|
||
"""Bug 状态"""
|
||
NEW = "NEW" # 新发现
|
||
FIXING = "FIXING" # 修复中
|
||
PENDING_REVIEW = "PENDING_REVIEW" # 待审核(有 PR)
|
||
MERGED = "MERGED" # 已合并
|
||
DEPLOYED = "DEPLOYED" # 已部署
|
||
FAILED = "FAILED" # 失败
|
||
```
|
||
|
||
**文件:`log_center/repair_agent/config/settings.py`**
|
||
|
||
新增配置项:
|
||
|
||
```python
|
||
class Settings(BaseSettings):
|
||
# ... 现有配置 ...
|
||
|
||
# Gitea 配置
|
||
gitea_url: str = Field(
|
||
default="https://gitea.airlabs.art",
|
||
description="Gitea 服务器地址"
|
||
)
|
||
gitea_token: str = Field(
|
||
default="",
|
||
description="Gitea API Token"
|
||
)
|
||
|
||
# PR 配置
|
||
auto_create_pr: bool = Field(
|
||
default=True,
|
||
description="是否自动创建 PR"
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
#### 2. Log Center 端
|
||
|
||
**文件:`log_center/app/models.py`**
|
||
|
||
更新 Bug 模型:
|
||
|
||
```python
|
||
class ErrorLog(Base):
|
||
# ... 现有字段 ...
|
||
|
||
# 新增字段
|
||
pr_number = Column(Integer, nullable=True, comment="PR 编号")
|
||
pr_url = Column(String(500), nullable=True, comment="PR 链接")
|
||
branch_name = Column(String(200), nullable=True, comment="修复分支名")
|
||
```
|
||
|
||
**文件:`log_center/app/api/webhooks.py`**
|
||
|
||
新增 Gitea Webhook 接收端点:
|
||
|
||
```python
|
||
@router.post("/webhooks/gitea")
|
||
async def gitea_webhook(
|
||
request: Request,
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""接收 Gitea PR 合并事件"""
|
||
try:
|
||
payload = await request.json()
|
||
|
||
# 验证签名(可选)
|
||
# verify_gitea_signature(request.headers, payload)
|
||
|
||
action = payload.get("action")
|
||
|
||
if action == "merged":
|
||
# PR 已合并
|
||
pr_number = payload["pull_request"]["number"]
|
||
repo_name = payload["repository"]["name"]
|
||
|
||
# 通过 PR 号和项目名找到对应的 Bug
|
||
bug = db.query(ErrorLog).filter(
|
||
ErrorLog.pr_number == pr_number,
|
||
ErrorLog.project_id.like(f"%{repo_name}%"),
|
||
ErrorLog.status == BugStatus.PENDING_REVIEW.value,
|
||
).first()
|
||
|
||
if bug:
|
||
# 更新状态
|
||
bug.status = BugStatus.MERGED.value
|
||
bug.updated_at = datetime.now()
|
||
db.commit()
|
||
|
||
logger.info(f"Bug #{bug.id} PR #{pr_number} 已合并")
|
||
|
||
return {"message": "Status updated"}
|
||
|
||
return {"message": "Event ignored"}
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理 Gitea Webhook 失败: {e}")
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
|
||
@router.post("/webhooks/deployment")
|
||
async def deployment_webhook(
|
||
payload: dict,
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""接收部署成功通知"""
|
||
try:
|
||
if payload.get("status") == "success":
|
||
commit_sha = payload["commit"]
|
||
project_id = payload["project_id"]
|
||
|
||
# 找到该 commit 关联的 Bug(MERGED 状态)
|
||
bugs = db.query(ErrorLog).filter(
|
||
ErrorLog.project_id == project_id,
|
||
ErrorLog.status == BugStatus.MERGED.value,
|
||
).all()
|
||
|
||
# TODO: 通过 commit SHA 精确匹配(需要记录 commit)
|
||
|
||
for bug in bugs:
|
||
bug.status = BugStatus.DEPLOYED.value
|
||
bug.deployed_at = datetime.now()
|
||
|
||
db.commit()
|
||
logger.info(f"已更新 {len(bugs)} 个 Bug 状态为 DEPLOYED")
|
||
|
||
return {"message": f"Updated {len(bugs)} bugs"}
|
||
|
||
return {"message": "Not a success deployment"}
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理部署 Webhook 失败: {e}")
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
```
|
||
|
||
**文件:`log_center/app/api/tasks.py`**
|
||
|
||
新增接口:
|
||
|
||
```python
|
||
@router.put("/bugs/{bug_id}/pr-info")
|
||
async def update_pr_info(
|
||
bug_id: int,
|
||
pr_number: int,
|
||
pr_url: str,
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""更新 Bug 的 PR 信息"""
|
||
bug = db.query(ErrorLog).filter(ErrorLog.id == bug_id).first()
|
||
if not bug:
|
||
raise HTTPException(status_code=404, detail="Bug not found")
|
||
|
||
bug.pr_number = pr_number
|
||
bug.pr_url = pr_url
|
||
db.commit()
|
||
|
||
return {"message": "PR info updated"}
|
||
```
|
||
|
||
**文件:`log_center/web/src/pages/BugDetail.tsx`**
|
||
|
||
添加 PR 信息展示:
|
||
|
||
```tsx
|
||
{bug.status === 'PENDING_REVIEW' && bug.pr_url && (
|
||
<Card title="Pull Request" style={{ marginTop: 16 }}>
|
||
<Space direction="vertical" style={{ width: '100%' }}>
|
||
<div>
|
||
<Tag color="orange">待审核</Tag>
|
||
<Text strong>PR #{bug.pr_number}</Text>: {bug.branch_name} → main
|
||
</div>
|
||
|
||
<Statistic
|
||
title="修改文件"
|
||
value={bug.repair_report?.modified_files?.length || 0}
|
||
suffix="个"
|
||
/>
|
||
|
||
<Button
|
||
type="primary"
|
||
icon={<LinkOutlined />}
|
||
href={bug.pr_url}
|
||
target="_blank"
|
||
>
|
||
前往 Gitea 审核 PR
|
||
</Button>
|
||
</Space>
|
||
</Card>
|
||
)}
|
||
```
|
||
|
||
---
|
||
|
||
## 实施计划
|
||
|
||
### Phase 1:核心功能(必须实施)
|
||
|
||
**时间:2-3 天**
|
||
|
||
#### 任务清单
|
||
|
||
- [ ] **Repair Agent 端**
|
||
- [ ] 新增 `create_pull_request()` 方法
|
||
- [ ] 修改 `fix_project()` 逻辑,移除 `merge_to_main_and_push()`
|
||
- [ ] 状态更新为 `PENDING_REVIEW`
|
||
- [ ] 修复报告包含 PR 信息
|
||
- [ ] 配置文件添加 Gitea 相关参数
|
||
|
||
- [ ] **Log Center 端**
|
||
- [ ] 数据库添加 `pr_number`, `pr_url`, `branch_name` 字段
|
||
- [ ] 简化 Bug 状态枚举(6 个)
|
||
- [ ] 新增 Gitea Webhook 接收端点
|
||
- [ ] Bug 详情页显示 PR 信息
|
||
- [ ] "前往 Gitea 审核" 按钮
|
||
|
||
- [ ] **Gitea 配置**
|
||
- [ ] 创建 API Token
|
||
- [ ] 配置 Webhook(PR 合并事件)
|
||
|
||
- [ ] **测试验证**
|
||
- [ ] 端到端测试完整流程
|
||
- [ ] 验证 PR 创建成功
|
||
- [ ] 验证状态自动更新
|
||
|
||
#### 预期效果
|
||
|
||
```
|
||
NEW → FIXING → PENDING_REVIEW (有 PR) → [人工审核] → MERGED
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 2:优化体验(可选,后续迭代)
|
||
|
||
**时间:1 周**
|
||
|
||
#### 任务清单
|
||
|
||
- [ ] **Web 界面优化**
|
||
- [ ] 嵌入 diff viewer 组件
|
||
- [ ] 在日志中台直接批准/拒绝 PR
|
||
- [ ] 批量审核功能
|
||
- [ ] 统计看板优化
|
||
|
||
- [ ] **通知系统**
|
||
- [ ] 邮件通知(PR 创建、合并)
|
||
- [ ] 钉钉/企业微信通知
|
||
- [ ] 消息中心
|
||
|
||
- [ ] **高级功能**
|
||
- [ ] PR 自动审批(测试通过 + 低风险)
|
||
- [ ] 审批流程配置(指定审批人)
|
||
- [ ] PR 评论同步到日志中台
|
||
|
||
---
|
||
|
||
## 决策清单
|
||
|
||
### 需要确认的事项
|
||
|
||
- [ ] **1. 状态简化方案**
|
||
- ✅ 采用 6 个状态(NEW, FIXING, PENDING_REVIEW, MERGED, DEPLOYED, FAILED)
|
||
- ❌ 保留现有 9 个状态
|
||
|
||
- [ ] **2. 审核方式**
|
||
- ✅ **方案 A**:跳转到 Gitea 审核(推荐,简单快速)
|
||
- ❌ **方案 B**:在日志中台内审核(高级,需要更多开发)
|
||
|
||
- [ ] **3. 实施阶段**
|
||
- ✅ **Phase 1**:核心功能(必须,2-3 天)
|
||
- ⚠️ **Phase 2**:优化体验(可选,1 周)
|
||
|
||
- [ ] **4. Gitea 配置信息**
|
||
- [ ] Gitea 服务器地址:`https://gitea.xxx.com`
|
||
- [ ] Gitea Token:已生成并配置
|
||
- [ ] Webhook Secret:已配置
|
||
|
||
---
|
||
|
||
## 附录
|
||
|
||
### A. 数据库迁移脚本
|
||
|
||
```python
|
||
# alembic/versions/xxx_add_pr_info.py
|
||
def upgrade():
|
||
op.add_column('error_logs', sa.Column('pr_number', sa.Integer(), nullable=True))
|
||
op.add_column('error_logs', sa.Column('pr_url', sa.String(500), nullable=True))
|
||
op.add_column('error_logs', sa.Column('branch_name', sa.String(200), nullable=True))
|
||
|
||
# 迁移旧状态到新状态
|
||
op.execute("""
|
||
UPDATE error_logs
|
||
SET status = 'FAILED'
|
||
WHERE status IN ('FIX_FAILED', 'CANNOT_REPRODUCE')
|
||
""")
|
||
|
||
op.execute("""
|
||
UPDATE error_logs
|
||
SET status = 'NEW'
|
||
WHERE status IN ('VERIFYING', 'PENDING_FIX')
|
||
""")
|
||
|
||
def downgrade():
|
||
op.drop_column('error_logs', 'branch_name')
|
||
op.drop_column('error_logs', 'pr_url')
|
||
op.drop_column('error_logs', 'pr_number')
|
||
```
|
||
|
||
---
|
||
|
||
### B. Gitea Webhook 配置示例
|
||
|
||
**Webhook URL:**
|
||
```
|
||
https://your-log-center.com/api/webhooks/gitea
|
||
```
|
||
|
||
**Webhook Events:**
|
||
- ✅ Pull Requests (merged)
|
||
|
||
**Payload 示例:**
|
||
```json
|
||
{
|
||
"action": "merged",
|
||
"number": 45,
|
||
"pull_request": {
|
||
"id": 45,
|
||
"number": 45,
|
||
"title": "fix: auto repair bugs #123",
|
||
"head": {
|
||
"ref": "fix/auto-20260225-1430"
|
||
},
|
||
"base": {
|
||
"ref": "main"
|
||
},
|
||
"merged": true,
|
||
"merged_at": "2026-02-25T15:30:00Z"
|
||
},
|
||
"repository": {
|
||
"name": "rtc_backend",
|
||
"full_name": "airlabs/rtc_backend"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### C. CI/CD 部署通知配置
|
||
|
||
**.gitlab-ci.yml 示例:**
|
||
```yaml
|
||
stages:
|
||
- test
|
||
- build
|
||
- deploy
|
||
|
||
deploy:production:
|
||
stage: deploy
|
||
only:
|
||
- main
|
||
script:
|
||
- echo "Deploying to production..."
|
||
- ./deploy.sh
|
||
- |
|
||
# 通知日志中台部署成功
|
||
curl -X POST https://your-log-center.com/api/webhooks/deployment \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-Secret: ${WEBHOOK_SECRET}" \
|
||
-d '{
|
||
"project_id": "rtc_backend",
|
||
"status": "success",
|
||
"commit": "'${CI_COMMIT_SHA}'",
|
||
"deployed_at": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
## 参考资料
|
||
|
||
- [Gitea API 文档](https://docs.gitea.io/en-us/api-usage/)
|
||
- [Gitea Webhooks](https://docs.gitea.io/en-us/webhooks/)
|
||
- [Git 工作流最佳实践](https://www.atlassian.com/git/tutorials/comparing-workflows)
|
||
|
||
---
|
||
|
||
**文档维护:**
|
||
- 创建:2026-02-25
|
||
- 更新:待更新
|
||
- 负责人:待指定
|