EvoCode API 契约(详细版)¶
对外 REST(
/api/v1)+ analyzer 内部 API(/analyze/v1)+ SSE 协议 版本:v1.0 · 2026-08-10 · 对应架构修订(05 审查 P0-1/P0-3/P1-1/2/3/4 已落实)使用说明:本文档是接口实现的唯一契约来源。字段名与结构变更必须先改本文档,再同步三端代码。LLM 出口唯一在 analyzer(backend 永不直连 LLM)。
目录¶
- 通用约定
- 错误码全表
- 对外 API(按资源分组,含请求/响应示例)
- SSE 事件协议(AI 医生)
- Analyzer 内部 API
- 分页与排序规范
- 契约变更流程
1. 通用约定¶
| 项 | 约定 |
|---|---|
| Base URL | 对外 /api/v1;内部 /analyze/v1(仅 127.0.0.1) |
| 响应包装 | {"code": 0, "message": "ok", "data": ...};code=0 成功 |
| 时间 | ISO-8601 UTC:2026-08-10T10:00:00Z |
| 字段 | JSON camelCase;枚举值全大写 |
| 分页 | 查询参数 page(从1) size(默认10, 上限100),响应 data 内 {total, page, size, items} |
| 上传 | multipart/form-data |
| 流式 | text/event-stream,UTF-8,无 gzip(见 §4) |
| 鉴权 | v1.0 前无(单用户本地部署);预留 Authorization: Bearer 解析位,不校验 |
2. 错误码全表¶
2.1 HTTP 状态码¶
| HTTP | 场景 |
|---|---|
| 200 | 成功 |
| 201 | 创建成功(项目) |
| 202 | 任务已接受(异步) |
| 400 | 参数/请求体错误(业务 code 1xxx/2xxx) |
| 404 | 资源不存在(2001 项目不存在等) |
| 502 | analyzer 不可达(3001) |
| 500 | 系统错误(5xxx) |
HTTP 映射约定(GlobalExceptionHandler 统一实现):1xxx/2xxx → 400;3xxx → 502;5xxx → 500;业务 code 始终在 body 中携带。
2.2 业务错误码(body.code)¶
| 段 | code | 含义 | 触发点 |
|---|---|---|---|
| 1xxx 参数 | 1001 | 参数缺失 | 校验注解 |
| 1002 | 参数格式错误 | 校验注解 | |
| 1003 | 分页参数非法 | 控制器 | |
| 2xxx 业务 | 2001 | 项目不存在 | 详情/分析/删除 |
| 2002 | 该项目已有运行中分析任务 | 发起分析 | |
| 2003 | 上传文件非法(zip 损坏/超限/恶意路径) | 上传 | |
| 2004 | 项目状态不允许该操作(如分析中删除→按 7.5 流程不返回此码,走 CANCELLED) | — | |
| 2005 | 文件内容越权或超限 | content 接口 | |
| 2006 | 会话不存在 | chat | |
| 2007 | 发送过于频繁/重复提交(AI 医生) | chat(同内容 2s 内重复) | |
| 2008 | 该分析正在重新生成报告中 | regenerate | |
| 2009 | 仓库克隆失败(不存在/私有/超时) | 创建项目-GIT | |
| 2010 | 该项目尚无架构分析(含 analysisId 指定的分析无架构数据) | 架构查询 | |
| 2011 | 技术债状态迁移非法或必填字段缺失 | 技术债 | |
| 2012 | 技术债不存在 | 技术债 | |
| 2013 | 文档不存在 | 文档 | |
| 2014 | 文档已被人工编辑,重新生成需确认(force) | 文档 regenerate | |
| 2015 | 分析不存在或无报告(审查修复:此前误用 2001「项目不存在」) | 报告查询/regenerate/status | |
| 3xxx 分析器 | 3001 | analyzer 不可达/内部错误 | AnalyzerClient |
| 3002 | 扫描超时(部分结果已保留) | AnalysisAsyncRunner | |
| 3003 | LLM 失败(已降级,此码仅在降级也失败时返回) | report 流程 | |
| 5xxx 系统 | 5001 | 磁盘空间不足 | 上传/解压 |
| 5002 | 数据库异常 | 兜底 | |
| 5003 | 服务重启导致任务中断 | 启动恢复 |
注意:
LLM_NO_KEY/LLM_FAILED通常不以错误形式返回(触发规则版降级,报告 source=RULES);只有降级路径本身失败才返回 3003。
3. 对外 API(详细)¶
3.0 健康检查¶
GET /api/v1/health¶
// 200:{ "code": 0, "data": { "service": "evocode-backend", "status": "UP",
// "version": "0.1.0-SNAPSHOT", "time": "2026-08-10T10:00:00" } }
// 说明:三端冒烟(smoke.ps1)与 start-dev.bat 健康检查依赖。
3.1 创建项目¶
POST /api/v1/projects¶
方式 A:zip 上传(multipart/form-data)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | string | 是 | 项目名(≤100,去首尾空白) |
| description | string | 否 | 描述 |
| file | file | 是 | .zip,≤200MB,解压后 ≤500MB |
// 201 响应
{ "code": 0, "message": "ok",
"data": { "id": 1, "name": "Chatez", "sourceType": "ZIP", "status": "CREATED",
"storagePath": "data/projects/1", "langStats": null, "locTotal": 0,
"fileCount": 0, "frameworkTags": [], "lastAnalyzedAt": null,
"createdAt": "2026-08-10T10:00:00Z" } }
// 400:{ "code": 1001, "message": "name 不能为空" }
// 400:{ "code": 2003, "message": "上传文件非法:zip 内存在非法路径(../)" }
校验顺序(实现必须遵循):① 扩展名 .zip → ② 大小 ≤200MB → ③ 解压至临时目录(逐文件路径校验)→ ④ 解压体积/文件数校验 → ⑤ 原子移入 data/projects/{id} → ⑥ 触发快扫(异步,3 分钟内出档案)。任何一步失败:清理临时目录,返回 2003。
方式 B:GitHub 仓库
// Content-Type: application/json
// req
{ "name": "Chatez", "repoUrl": "https://github.com/xxx/chatez", "cloneDepth": 1 }
// 201 响应同方式 A(sourceType=GIT,repoUrl 回显)
// 400:{ "code": 1002, "message": "repoUrl 格式非法" }
// 400:{ "code": 2009, "message": "仓库克隆失败:仓库不存在或为私有" }
规则:默认 --depth 1;cloneDepth=0 全量克隆(供演化分析 FR-8.4);超时 5 分钟;支持 GIT_PROXY 配置。
3.2 项目列表¶
GET /api/v1/projects¶
| 参数 | 类型 | 说明 |
|---|---|---|
| page / size | int | 分页(size ≤100) |
| keyword | string | 名称模糊匹配 |
| language | string | 语言筛选(lang_stats 键) |
| status | string | CREATED/ANALYZING/READY/FAILED |
| sort | string | createdAt(默认) / lastAnalyzedAt / locTotal / name |
| order | string | asc / desc |
{ "code": 0, "data": { "total": 5, "page": 1, "size": 10, "items": [
{ "id": 1, "name": "Chatez", "sourceType": "GIT",
"langStats": { "Java": 61.2, "JavaScript": 30.4, "OTHER": 8.4 },
"frameworkTags": ["Spring Boot", "Vue", "Electron"],
"locTotal": 20431, "fileCount": 412, "status": "READY",
"healthScore": 82, "lastAnalyzedAt": "2026-08-10T10:00:00Z",
"createdAt": "2026-08-01T08:00:00Z" } ] } }
healthScore取该项目最近一次成功分析的报告值(JOIN 子查询,避免全表扫描)。
3.3 项目详情¶
GET /api/v1/projects/{id}¶
{ "code": 0, "data": {
"id": 1, "name": "Chatez", "description": null, "sourceType": "GIT",
"repoUrl": "https://github.com/xxx/chatez", "status": "READY",
"langStats": { "Java": 61.2, "JavaScript": 30.4, "OTHER": 8.4 },
"frameworkTags": ["Spring Boot", "Vue", "Electron"],
"locTotal": 20431, "fileCount": 412, "ignoredCount": 88,
"lastAnalyzedAt": "2026-08-10T10:00:00Z",
"latestAnalysis": { "id": 10, "status": "SUCCEEDED", "reportJson": { …见 3.5 } },
"createdAt": "2026-08-01T08:00:00Z" } }
// 404:{ "code": 2001, "message": "项目不存在" }
latestAnalysis.reportJson体积 ≤1MB 时内联返回;超过则只返回{analysisId},前端单独拉报告接口(防列表页卡顿)。
3.3.1 更新项目(P9b)¶
PATCH /api/v1/projects/{id}¶
// req: { "name": "新名称", "description": "新描述" } // 字段可选,至少提供一个
// 200:{ "code": 0, "data": { "id": 1, "name": "新名称", "description": "新描述", … } }
// 400:{ "code": 1001, "message": "至少提供一个更新字段(name 或 description)" }
// { "code": 1002, "message": "name 不能为空 / name 长度不能超过 100" }
// 404:{ "code": 2001, "message": "项目不存在" }
3.3.2 报告导出(P9b)¶
GET /api/v1/projects/{id}/report/export¶
// 200:text/markdown; charset=utf-8 纯文本下载
// Content-Disposition: attachment; filename="evocode-report-{id}.md"
// body:Markdown 格式最新报告(无 {code:0} JSON 包装,前端按 blob 下载)
// 404:{ "code": 2015, "message": "该分析不存在或无报告" }(无 SUCCEEDED 报告时)
3.4 删除项目¶
DELETE /api/v1/projects/{id}¶
// 200:{ "code": 0, "message": "ok", "data": null }
删除时序(对应架构 §7.5):① 存在 RUNNING 任务 → 置 CANCELLED → ② 等执行线程退出(≤60s)→ ③ 事务级联清库(chat_message→chat_session→knowledge_chunk→tech_debt→generated_doc→commit_stat/file_change_stat/hotspot→arch_*→quality_issue/dependency→file_node→analysis→project)→ ④ 删磁盘目录(失败仅记日志)。
3.5 发起分析¶
POST /api/v1/projects/{id}/analyses¶
// req: { "type": "FULL" } // v0.1 仅 FULL;P3+ 增加 QUALITY/ARCH/EVOLUTION
// 202 响应
{ "code": 0, "message": "ok",
"data": { "id": 10, "projectId": 1, "type": "FULL", "status": "PENDING",
"progress": 0, "stage": "QUEUED", "createdAt": "2026-08-10T10:00:00Z" } }
// 400:{ "code": 2002, "message": "该项目已有运行中的分析任务" }
// 404:{ "code": 2001, "message": "项目不存在" }
3.6 分析历史与状态轮询¶
GET /api/v1/projects/{id}/analyses¶
{ "code": 0, "data": { "total": 3, "items": [
{ "id": 10, "type": "FULL", "status": "SUCCEEDED", "progress": 100,
"stage": "DONE", "errorMessage": null, "startedAt": "...", "finishedAt": "...",
"source": "LLM", "healthScore": 82 },
{ "id": 9, "type": "FULL", "status": "FAILED", "progress": 70, "stage": "REPORT",
"errorMessage": "LLM 调用失败且降级也失败", ... } ] } }
GET /api/v1/analyses/{id}(轮询,2s 间隔)¶
{ "code": 0, "data": { "id": 10, "status": "RUNNING", "progress": 45,
"stage": "SCAN", "errorMessage": null } }
状态机与进度映射:
| 阶段 | 进度 | stage 值 | 说明 |
|---|---|---|---|
| 排队 | 0 | QUEUED | |
| 扫描 | 5→70 | SCAN | 调 /analyze/scan;超时保留部分 |
| 扫描落库 | 70 | SCAN_DONE | |
| 报告 | 75→95 | REPORT | 调 /analyze/report(LLM 或规则版) |
| 完成 | 100 | DONE | |
| 失败 | — | — | status=FAILED + errorMessage |
3.7 报告¶
GET /api/v1/analyses/{id}/report¶
{ "code": 0, "data": {
"analysisId": 10, "generatedAt": "2026-08-10T10:01:00Z",
"source": "LLM", // LLM / RULES
"promptVersion": "report-1.2",
"report": {
"healthScore": 82, "level": "GOOD",
"summary": "整体结构清晰,但 User 模块耦合度偏高、存在 EOL 依赖,较上期 -3 分。",
"techStack": { "languages": {"Java": 61.2, "JavaScript": 30.4},
"frameworks": ["Spring Boot", "Vue", "Electron"] },
"dimensions": [
{ "key": "quality", "score": 76, "stars": 4, "summary": "超长方法 23 处" },
{ "key": "structure", "score": 88, "stars": 4, "summary": "分层规范" },
{ "key": "dependency", "score": 55, "stars": 3, "summary": "Spring Boot 2.5 已 EOL" },
{ "key": "scale", "score": 90, "stars": 5, "summary": "评估充分" } ],
"risks": [
{ "level": "HIGH", "title": "Spring Boot 2.5 已停止官方支持",
"detail": "pom.xml 使用 2.5.14,OSS 支持已于 2023-11 结束",
"suggestion": "升级至 3.5,注意 javax→jakarta 迁移",
"references": [{ "file": "pom.xml", "line": 3 }] } ],
"recommendations": [
{ "phase": "第一阶段", "items": ["升级 Spring Boot 3.x", "拆分 UserService"] } ] } } }
// 404:{ "code": 2015, "message": "该分析不存在或无报告" }
POST /api/v1/analyses/{id}/report/regenerate(P2 起)¶
// 不重扫:仅重跑 LLM 报告步骤(用 analysis 已存的扫描摘要)
// 前置:analysis.status = SUCCEEDED 且 report_json 非空;否则 404/2001
// 状态转移:SUCCEEDED → RUNNING(stage=REPORT) → SUCCEEDED(覆盖 report_json,记录 regeneratedAt)
// 202:{ "code": 0, "data": { "id": 10, "status": "RUNNING", "stage": "REPORT", "progress": 75 } }
// 完成后由前端轮询 GET /analyses/{id} 与 /report
// 400:{ "code": 2008, "message": "该分析正在重新生成报告中" }
GET /api/v1/projects/{id}/report/history(P9c)¶
// 报告历史:聚合 SUCCEEDED + report_json 非空的分析摘要(按 id 倒序,最新在前)
// 参数:limit(默认 10,上限 20;非法 → 400/1003)
// 200:{ "code": 0, "data": { "items": [
// { "analysisId": 27, "createdAt": "2026-08-10T10:01:00Z", "healthScore": 82, "level": "GOOD",
// "dimensions": [ { "key": "quality", "score": 76, "stars": 4 }, … ],
// "risks": [ { "level": "HIGH", "title": "Spring Boot 2.5 已停止官方支持" }, … ],
// "source": "RULES" }, … ] } }
// 404:{ "code": 2001, "message": "项目不存在" };无 SUCCEEDED 分析 → items: []
// 注:healthScore 有数值防御(非数字历史脏数据 → null,前端显示 —);risks 为摘要(不含 detail/引用),供两期 diff
3.8 项目地图与文件内容¶
GET /api/v1/projects/{id}/files¶
| 参数 | 说明 |
|---|---|
| page/size | 分页 |
| language | 语言筛选 |
| keyword | 路径模糊匹配 |
| sort | path(默认) / loc / sizeBytes |
{ "code": 0, "data": { "total": 412, "items": [
{ "path": "src/main/java/com/chatez/service/UserService.java",
"language": "Java", "loc": 180, "sizeBytes": 4210 } ] } }
GET /api/v1/projects/{id}/files/content?path=<相对路径>¶
// 200:{ "code": 0, "data": { "path": "src/.../UserService.java",
// "language": "Java", "loc": 180, "content": "package com.chatez...", "truncated": false } }
// 400:{ "code": 2005, "message": "文件越权或超限" } // ../、绝对路径、非 file_node 路径、>2MB、二进制
安全实现(强制):① path 必须存在于该项目的 file_node 表(白名单)→ ② 规范化后必须位于 data/projects/{id}/ 内 → ③ 大小 ≤2MB → ④ 二进制检测(前 512 字节含 NUL → 拒绝)→ ⑤ 读全文 UTF-8,失败返回 2005。该接口是 FR-6.3 引用预览的唯一数据来源。
3.9 依赖(P3)¶
GET /api/v1/projects/{id}/dependencies?riskLevel=¶
{ "code": 0, "data": { "total": 34, "items": [
{ "ecosystem": "maven", "name": "spring-boot-starter-parent", "version": "2.5.14",
"latestVersion": "3.5.0", "riskLevel": "HIGH", "isEol": true,
"riskReason": "2.x 已结束 OSS 支持(2023-11)",
"suggestion": "升级 3.x,注意 javax→jakarta 迁移",
"aiAdvice": { "impact": "…", "steps": ["…"], "risks": ["…"], "estimate": "0.5 人日" } } ] } }
aiAdvice 为异步生成(ai_status 字段约定:NONE=未生成 / PENDING / DONE / FAILED,与 07 §4 枚举一致)。
3.10 质量(P3)¶
GET /api/v1/projects/{id}/quality-issues?severity=&kind=&status=¶
{ "code": 0, "data": {
"metrics": { "bugs": 23, "vulnerabilities": 5, "codeSmells": 88,
"duplicationRate": 12.4, "coverageRate": null, "complexity": 3.2,
"available": true, "comparedWithLast": { "bugs": 5, "vulnerabilities": 0 } },
"total": 116, "items": [
{ "id": 501, "severity": "CRITICAL", "kind": "BUG", "ruleKey": "s3776",
"filePath": "src/.../UserService.java", "line": 88,
"message": "Method too long",
"aiExplanation": "该函数同时承担用户验证、数据库操作与邮件发送…",
"aiSuggestion": "拆分为 UserAuthService / UserQueryService / UserMailService",
"aiStatus": "DONE", "status": "OPEN" } ] } }
metrics.available=false 表示 Sonar 未启用(质量维度 N/A)。
POST /api/v1/quality-issues/{id}/explain(重新解释)¶
// 200:{ "code": 0, "data": { "id": 501, "aiStatus": "DONE" } }
// 说明:同步调 analyzer /analyze/v1/explain(规则版 + LLM 增强),成功回填 ai_* 并置
// aiStatus=DONE;analyzer 不可达时置 FAILED(可重试)。首次解释随分析自动触发;
// 此接口用于失败重试或用户主动要求。issue 不存在 → 2015。
3.11 架构(P4)¶
GET /api/v1/projects/{id}/architecture?analysisId=¶
{ "code": 0, "data": {
"nodes": [ { "id": 11, "nodeKey": "com.chatez.controller.UserController",
"name": "UserController", "nodeType": "CONTROLLER",
"filePath": "src/.../UserController.java", "metrics": { "outDegree": 3, "inDegree": 0 } } ],
"edges": [ { "id": 21, "sourceNodeId": 11, "targetNodeId": 12,
"relation": "CALL" } ],
"violations": [ { "id": 31, "violationType": "LAYER_VIOLATION",
"description": "Controller 直接调用 Repository(违反分层)",
"sourceNodeId": 11, "targetNodeId": 13,
"severity": "HIGH",
"suggestion": "将数据访问迁移到 Service 层",
"aiNote": "该违规使 Controller 承担数据访问职责…" } ] } }
// 404:{ "code": 2010, "message": "该项目尚无架构分析" }(analysisId 缺省取最新一次架构分析)
3.12 技术债(P7)¶
// GET /api/v1/projects/{id}/tech-debts?status=
{ "code": 0, "data": { "total": 7, "items": [
{ "id": 101, "source": "ARCH", "title": "支付模块耦合严重",
"level": "HIGH", "description": "…", "suggestion": "拆分 PaymentService",
"status": "OPEN", "refAnalysisId": 10,
"createdAt": "...", "resolvedAt": null } ] } }
// POST /api/v1/tech-debts/{id}/status 更新状态
// req: { "status": "DOING" | "DONE" | "WONTFIX",
// "resolveNote": "已拆分验证…", // DONE 必填
// "wonfixReason": "优先级低…" } // WONTFIX 必填
// 200:{ "code": 0, "data": null }
// POST /api/v1/projects/{id}/tech-debts 手动/AI 登记(TD-04)
// req: { "source": "MANUAL" | "AI_DOCTOR", // 手动登记 / AI 医生确认
// "title": "…", // 必填 ≤200
// "level": "HIGH"|"MEDIUM"|"LOW", // 缺省 MEDIUM
// "description": "…", "suggestion": "…" }
// 201:{ "code": 0, "data": { "id": 102, "source": "MANUAL", "title": "…",
// "level": "HIGH", "status": "OPEN", "refAnalysisId": null, … } }
// 400:1001 title 必填;1002 source 仅支持 MANUAL/AI_DOCTOR 或 title 超长;404 2001 项目不存在
状态机约束:仅允许 OPEN→DOING、OPEN→DONE、OPEN→WONTFIX、DOING→DONE;非法迁移返回 2xxx。
3.13 演化(P5)¶
GET /api/v1/projects/{id}/evolution?range=30d¶
{ "code": 0, "data": {
"range": "30d",
"trend": [ { "week": "2026-07-20", "commits": 12, "linesAdded": 3200, "linesRemoved": 800 } ],
"topFiles": [ { "filePath": "src/.../UserService.java", "commitCount": 45,
"linesAdded": 12000, "linesRemoved": 3000 } ],
"authors": [ { "authorName": "李工", "commits": 87, "linesAdded": 15000 } ],
"hotspots": [ { "module": "User", "riskLevel": "HIGH",
"evidence": ["变更 45 次", "新增 12000 行", "耦合度全项目最高"],
"aiConclusion": "User 模块正在成为风险中心,建议优先重构" } ] } }
// 200 + available=false:非 Git 来源 / 无提交历史项目(P5 决策:不返回 404)
// 404:{ "code": 2001, "message": "项目不存在" }
// 注:topFiles 为最新一次分析的全量 TOP-N(落库窗口 365d),不受 range 过滤
3.14 依赖(P9d)¶
GET /api/v1/projects/{id}/dependencies¶
{ "code": 0, "data": {
"available": true,
"dependencies": [
{ "name": "org.springframework.boot:spring-boot-starter-web", "version": "2.5.14",
"type": "MAVEN", "file": "pom.xml", "risk": "HIGH",
"reason": "Spring Boot 2.5 已 EOL(OSS 支持 2023-11 结束)", "latest": "3.2+", "isEol": true },
{ "name": "vue", "version": "2.6.14", "type": "NPM", "file": "package.json",
"risk": "HIGH", "reason": "Vue 2 已 EOL(2023-12-31 结束支持)", "latest": "3.x", "isEol": true },
{ "name": "axios", "version": "1.7.0", "type": "NPM", "file": "package.json",
"risk": null, "reason": null, "latest": null, "isEol": false } ] } }
// 200 + available=false:无 Maven/npm 依赖文件(缺 pom.xml/package.json)→ 非错误
// 404:{ "code": 2001, "message": "项目不存在" }
// 注:risk=null 表示未命中 EOL 规则表("未知版本,建议人工确认",不误报);
// 数据来源 dependency 表(FULL 分析落库,07 §3.4),取最新分析
3.15 文档(P7)¶
// GET /api/v1/projects/{id}/docs?type=README|ARCH|API
// type 可选筛选;data 为列表(README/ARCH/API 三类各至多一条)
{ "code": 0, "data": [ { "id": 201, "docType": "README", "title": "Chatez 项目说明",
"content": "# Chatez\n…", "version": 1, "edited": false,
"createdAt": "..." } ] }
// POST /api/v1/projects/{id}/docs/{docType}/generate?force=false
// 生成/重新生成(同步调 analyzer,耗时 10-30s)
// 200:{ "code": 0, "data": { "id": 201, "docType": "README", "title": "…", "version": 2, … } }
// 400:{ "code": 2014, "message": "文档已被人工编辑,重新生成需确认(force)" }
// (edited=true 且未带 force=true 时拒绝覆盖)
// 502:{ "code": 3001, "message": "文档服务不可达…" }
// 400:{ "code": 3004, "message": "LLM 未配置…" }(analyzer 返回 LLM_NO_KEY 时)
// POST /api/v1/docs/{id}/edit
// req: { "content": "# 人工修改后的 README…" }
// 200:{ "code": 0, "data": { "id": 201, "version": 2, "edited": true } }
3.16 会话管理(AI 医生,P6)¶
// GET /api/v1/projects/{id}/chats (按 lastMessageAt desc)
{ "code": 0, "data": { "total": 3, "items": [
{ "id": 81, "title": "为什么这个项目难维护", "messageCount": 4,
"createdAt": "...", "lastMessageAt": "..." } ] } }
// POST /api/v1/projects/{id}/chats → 建会话(title 占位"新会话",首条消息后自动生成)
// 201:{ "code": 0, "data": { "id": 81, "title": "新会话", "messageCount": 0, "createdAt": "..." } }
// DELETE /api/v1/chats/{id} → 级联删除该会话全部消息
// 200:{ "code": 0, "data": null }
### 3.17 分析进度 SSE(P9e)
#### GET /api/v1/projects/{id}/analyses/events
```jsonc
// 200 text/event-stream(复用 §4 SSE 帧格式;event 名 = analysis-progress)
// data 载荷(AnalysisProgressPublisher.ProgressEvent):
// { "analysisId": 26, "status": "RUNNING", "stage": "SCAN", "progress": 30,
// "message": "扫描中…" }
// 触发点:AnalysisRunner 状态机变更——RUNNING(SCAN,5) / RUNNING(SCAN_DONE,70) /
// RUNNING(REPORT,75) / SUCCEEDED(DONE,100) / FAILED
// 断线:不重放历史(前端回退 GET /analyses/{id} 2s 轮询);无订阅者时 no-op
// 用途:详情页 EventSource 实时进度条 + 完成/失败 Toast
4. SSE 事件协议(AI 医生)¶
4.1 发起¶
POST /api/v1/chats/{id}/messages
Content-Type: application/json
Accept: text/event-stream
{ "content": "为什么这个项目维护困难?", "fileRef": "src/.../UserService.java" }
fileRef可选:用户显式 @ 的文件(走"全文发送"开关后携带内容,默认省略)- 响应:
200 text/event-stream,连接保持至done或错误
4.2 事件类型¶
| event | data 格式 | 语义 |
|---|---|---|
delta |
{"content":"…"} |
增量文本(前端追加) |
citations |
{"items":[{"file":"…","line":12,"excerpt":"…"}]} |
引用列表(流结束前一次性发送;前端校验存在性后渲染) |
done |
{"messageId": 9527} |
正常结束;后端已落库 |
error |
{"code":"LLM_FAILED","message":"…"} |
异常结束;后端落库失败消息(role=ASSISTANT, content=错误说明) |
4.3 完整示例流¶
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no ← 关键:禁用中间层缓冲
event: delta
data: {"content":"根据最近一次分析,主要原因有:\n\n1. "}
event: delta
data: {"content":"**依赖风险**:pom.xml:3 使用 Spring Boot 2.5…"}
event: citations
data: {"items":[{"file":"pom.xml","line":3,"excerpt":"<parent>…2.5.14…"},
{"file":"src/.../UserService.java","line":1,"excerpt":"public class UserService…"}]}
event: done
data: {"messageId": 9527}
4.4 前端处理规则¶
- 用 fetch + ReadableStream 逐行解析
event:/data:,不做 EventSource(需自定义 headers) - 断线/超时(>180s 无事件)→ 提示"连接中断",可重发(同 content 2s 内重复提交返回 2007)
citations到齐后渲染引用卡片,点击 → 调GET /files/content?path=→ Monaco 定位行号error事件到达后关闭流,展示错误横幅
4.5 后端与 analyzer 转发约定¶
- backend → analyzer
POST /analyze/v1/chat(同结构 SSE,透传不缓冲) - analyzer 断流 → backend 向客户端发
error事件并落库 - 超时:LLM 首 token 60s / 总时长 180s
5. Analyzer 内部 API(仅 127.0.0.1:8091)¶
统一错误体(非 200):
{"error": {"code": "…", "message": "…"}},映射规则见 §2/开发指导 §6.2。
5.1 POST /analyze/v1/scan¶
// req
{ "projectId": 1, "codeDir": "data/projects/1" }
// 200(超时/超限时加字段 truncated: true / skippedBigFiles: N)
{ "languages": {"Java": 61.2, "JavaScript": 30.4, "OTHER": 8.4},
"locTotal": 20431, "fileCount": 412, "ignoredCount": 88,
"frameworks": ["Spring Boot", "Vue", "Electron"],
"hasBackend": true, "hasFrontend": true, "dbHint": ["MySQL"],
"files": [ { "path": "src/.../UserService.java", "language": "Java",
"loc": 180, "sizeBytes": 4210 } ],
"skippedBigFiles": 1 }
5.2 POST /analyze/v1/report¶
// req
{ "projectId": 1, "scan": {…}, "quality": null, "arch": null, "evolution": null,
"historyReports": [ { "healthScore": 85, "summary": "…" } ],
"regenerate": false }
// 200
{ "source": "LLM" | "RULES",
"promptVersion": "report-1.2",
"report": { "healthScore": 82, "level": "GOOD", "summary": "…",
"dimensions": […], "risks": […], "recommendations": […] } }
// LLM 无 Key/失败 → 内部降级:source=RULES(HTTP 仍 200,不视为失败)
5.3 POST /analyze/v1/quality¶
// req { "projectId": 1, "codeDir": "data/projects/1" }
// 200 { "metrics": {…}, "issues": [ { "ruleKey","severity","kind","filePath","line","message" } ] }
// Sonar 不可达 → 200 + { "available": false }(非错误)
5.4 POST /analyze/v1/explain¶
// req { "issue": { "ruleKey","severity","message","filePath","line" },
// "fileSnippet": "…上下文…" }
// 200 { "explanation": "…", "suggestion": "…", "codeExample": "…" }
5.5 POST /analyze/v1/architecture¶
// req { "projectId": 1, "codeDir": "data/projects/1", "languages": ["java"] }
// 200 { "nodes": [ { "nodeKey","name","nodeType","filePath","metrics" } ],
// "edges": [ { "sourceNodeKey","targetNodeKey","relation" } ],
// "violations": [ { "violationType","description","sourceNodeKey","targetNodeKey",
// "severity","suggestion" } ] }
5.6 POST /analyze/v1/evolution¶
// req { "projectId": 1, "gitDir": "data/projects/1", "rangeDays": 30 }
// 200 { "commits": [ { "hash","authorName","committedAt","linesAdded","linesRemoved",
// "filesChanged","message" } ],
// "trend": [ { "week","commits","linesAdded","linesRemoved" } ],
// "topFiles": [ { "filePath","commitCount","linesAdded","linesRemoved" } ],
// "authors": [ { "authorName","commits","linesAdded" } ],
// "hotspots": [ { "module","riskLevel","evidence" } ] }
// 非 git 目录 / git 执行失败 → 200 + { "available": false };空仓库(有 .git 无提交)→ available=true + 空数组
5.7 POST /analyze/v1/chat(SSE 生成端,P6)¶
// req(stream 输入:JSON body 一次提交)
{ "projectId": 1,
"systemContext": { "projectSummary": "…", "latestReportSummary": "…" },
"history": [ { "role": "user"|"assistant", "content": "…" } ], // 已截断:≤6轮+摘要
"query": "为什么这个项目维护困难?",
"fileRef": null | { "path": "…", "content": "…" } }
// 响应:SSE 事件流(delta/citations/done/error),协议与 §4 一致
5.8 POST /analyze/v1/rag/index · /analyze/v1/rag/search(P6)¶
// index: req { "projectId": 1, "codeDir": "data/projects/1", "languages": ["java","python"] }
// analyzer 直读磁盘切片(不经 HTTP 传文件内容,避免大内存与扩大隐私面)
// → 200 { "chunks": 312, "embeddingModel": "bge-m3" }
// search: req { "projectId": 1, "query": "…", "topK": 8 }
// → 200 { "chunks": [ { "file","chunkIndex","content","meta","score" } ] }
// 错误:PG 未配置/宕机 → 503 {"error":{"code":"RAG_UNAVAILABLE","message":"…"}}
5.9 POST /analyze/v1/doc(P7b 契约新增)¶
// req(JSON body 一次提交)
{ "projectId": 1, "docType": "README" | "ARCH" | "API",
"scan": {…}|null, // README/API 用(backend 由 file_node 重建)
"arch": {…}|null, // ARCH 用(backend 取最新 analysis 架构)
"projectInfo": { "name": "…", "description": "…" },
"codeDir": "data/projects/1"|null } // API 用:analyzer 直读磁盘扫描 controller
// → 200 { "docType": "README", "title": "…", "content": "markdown…" }
// 错误(统一错误体):
// 400 {"error":{"code":"LLM_NO_KEY","message":"文档生成失败:…"}} 无 LLM key
// 502 {"error":{"code":"LLM_FAILED","message":"文档生成失败:…"}} LLM 调用失败
5.10 POST /analyze/v1/dependency(P9d 契约新增)¶
// req: { "projectId": 1, "codeDir": "data/projects/1" }
// 200: { "available": true, "dependencies": [
// { "name": "org.springframework.boot:spring-boot-starter-web", "version": "2.5.14",
// "type": "MAVEN", "file": "pom.xml",
// "risk": "HIGH", "reason": "Spring Boot 2.5 已 EOL(OSS 支持 2023-11 结束)",
// "latest": "3.2+", "isEol": true }, … ] }
// 无 Maven/npm 依赖文件(缺 pom.xml/package.json)→ 200 + { "available": false, "dependencies": [] }(非错误)
// 404: {"error":{"code":"…","message":"codeDir not found"}} codeDir 不存在
// 注:risk=null 表示未命中 EOL 规则表("未知版本,建议人工确认",不误报);
// EOL 规则表内置常量(dep_eol_rules.py),可扩展
5.11 错误体与降级语义(P6-P7 审查修订)¶
- analyzer 内部 API 非 200 一律
{"error": {"code": "…", "message": "…"}}(§4.5 前为裸字符串/缺包装处已统一) /analyze/v1/chat的done事件 data 为{}(analyzer 无 messageId 概念),由 backend 落库后补发{"messageId": id}(§4.2 对前端保持原样)- PG 未配置 与 PG 宕机(psycopg.Error)降级语义一致:index → 200
stored=false;search → 503RAG_UNAVAILABLE;chat → 兜底话术 SSE(不截断)
6. 分页与排序规范¶
- 分页响应统一:
data = { total, page, size, items }(列表类) sort白名单(后端校验,不在白名单返回 1002,防注入):- projects:
createdAt / lastAnalyzedAt / locTotal / name / healthScore(P9e 起含 healthScore,复用列表子查询 health_score 列) - files:
path / loc / sizeBytes - quality-issues:
severity / line / filePath - 排序默认值:时间类
desc,其余asc
7. 契约变更流程¶
- 修改本文档对应端点(含示例 JSON)
- analyzer:改
schemas.py+ 同步本文档 §5 - backend:改 DTO + 错误码表(§2)如有变化
- frontend:改
types/与 api 模块 - PR 描述标注"契约变更",跑三端验证 + 契约一致性检查(03 规范 §10.2)
- 破坏性变更(字段删除/语义改变)必须升版本号并在文档头部记录
契约变更记录:
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.0 | 2026-08-10 | 初版:全端点 + SSE 协议 + analyzer 错误契约(含 05 审查 P0-1/P0-3/P1-1~4 修复) |
| v1.1 | 2026-08-10 | 第二轮审查修订:新增会话列表/删除端点(§3.15)、错误码 2007/2008、HTTP 映射约定、rag/index 直读磁盘、命名统一 source、regenerate 状态转移 |
| v1.2 | 2026-08-10 | 第三轮审查修订:ai_status 口径统一为 NONE、克隆失败独立错误码 2009(映射 400)、analysis 落库补 report_source/prompt_version(见 02 V001) |