跳转至

EvoCode 架构设计(详细版)

4+1 视图 · 类级分解 · 插件 SPI · 线程模型 · 时序 · 演进规划 版本:v1.0 · 2026-08-10

使用说明(灵活性声明):本文档是架构蓝图与基线,服务于"能跑、能演示、能讲清"。类名、包名、SPI 定义为建议形态,实现时可增删调整;凡偏离关键依赖方向(§3.1)与安全底线(§9)之外的选择,记录 AD 后即可执行,事后回填本文档。本设计的核心承诺:三端可替换性(LLM/Sonar/队列)、新增语言与新增分析器是配置级工作而非改造级工作。


目录

  1. 架构目标与设计原则
  2. 总体架构(4+1 视图总览)
  3. 逻辑视图(分层与模块分解)
  4. 进程视图(进程/线程/异步模型)
  5. 物理视图(部署拓扑)
  6. 数据架构(ER 关系与数据流)
  7. 关键场景时序
  8. 扩展点与 SPI 清单
  9. 质量属性与架构对策
  10. 架构治理(防腐与保障)
  11. 架构演进规划(A0 → A3)
  12. 架构决策索引与待决问题

1. 架构目标与设计原则

1.1 架构目标(优先级从高到低)

目标 衡量方式
G1 可演示 一键启动;离线/无 Key 全功能可走通(降级设计)
G2 可替换 LLM / Sonar / 任务队列 / 存储均为可替换 SPI
G3 可扩展 新增语言 ≤1 天(配置级);新增分析器 ≤2 天(插件级)
G4 可解释 分析结论可溯源(引用文件/行号);健康分可复现
G5 可维护 依赖方向单向、分层清晰、单人也能长期维护

1.2 设计原则

  1. 单向依赖:上层依赖下层,禁止反向;同层依赖走接口
  2. 契约先行:analyzer schemas.py 是分析结果唯一事实来源
  3. 降级优先:每个对外能力定义"不可用时的降级行为",先降级后报错
  4. 状态中心化:任务/技术债等状态机只在一个地方流转
  5. 静态只读:被分析代码绝不执行,全部走只读解析
  6. 渐进架构:A0 满足 v0.1,A1/A2 能力通过插件位预埋,不提前实现

2. 总体架构(4+1 视图总览)

视图 回答的问题 对应章节
逻辑视图 系统由哪些模块/类组成,依赖方向 §3
进程视图 运行时有哪些进程/线程,如何协作 §4
物理视图 部署在哪,如何连接 §5
数据视图 数据如何组织与流转 §6
场景视图 关键用例如何穿过各层 §7
┌────────────────────────────────────────────────────────────┐
│ 展示层  Vue3 前端(SPA)                                     │
│  页面 → 组件 → store → api(axios) → /api/v1                 │
└───────────────┬────────────────────────────────────────────┘
                │ HTTP/JSON(REST + SSE)
┌───────────────▼────────────────────────────────────────────┐
│ 应用层  Spring Boot 3 后端(业务编排 + 持久化 + 任务调度)      │
│  controller → service → mapper → PostgreSQL                 │
│  service → AnalyzerClient → analyzer(内部 HTTP;AI 能力在 analyzer 内,backend 永不直连 LLM)│
└───────────────┬────────────────────────────────────────────┘
                │ /analyze/v1(仅 127.0.0.1)
┌───────────────▼────────────────────────────────────────────┐
│ 分析层  Python Analyzer(无状态,被调用执行)                  │
│  管线编排 → 扫描器 → 解析器(插件) → 分析器 → AI(LLM/RAG)        │
└───────────────┬────────────────────────────────────────────┘
                │
┌───────────────▼────────────────────────────────────────────┐
│ 数据层  PostgreSQL(pgvector) · Redis(预留) · 磁盘代码库        │
│ 外部:LLM API(OpenAI 兼容) · GitHub · SonarQube(自部署)       │
└────────────────────────────────────────────────────────────┘

三个进程的职责边界(铁律)

进程 拥有 不拥有
backend 业务状态、任务编排、鉴权(预留)、对外 API、会话持久化 任何代码解析、任何 LLM 直接调用(LLM 出口唯一在 analyzer,见 §3.5)
analyzer 代码解析、质量扫描、AI 生成、RAG 业务状态、页面数据
frontend 交互、展示、流式渲染 业务逻辑

边界理由:解析生态在 Python、业务生态在 Java;两者只通过契约 JSON 通信,互不侵入。


3. 逻辑视图(分层与模块分解)

3.1 分层与依赖规则(防腐基线)

frontend ── HTTP ──▶ controller ──▶ service ──▶ mapper ──▶ PostgreSQL
                          │              │
                          │              └──▶ AnalyzerClient ──▶ analyzer(HTTP) ──▶ AI(LLM/RAG)
                          ▼
                       dto / common(共享形态)

禁止项(ArchUnit 强制,见 §10)

# 禁止 原因
D-1 controller 依赖 mapper / entity 直接返回 破坏分层,越层访问
D-2 service 依赖 controller 反向依赖,循环风险
D-3 service 之间直接 new 对方实现 必须走接口(构造器注入)
D-4 任何 Java 类依赖 analyzer 实现细节 只允许通过 AnalyzerClient + 契约 DTO
D-5 backend 业务代码直接调用任何 LLM 客户端 LLM 出口唯一在 analyzer(P0-1A);backend 只经 AnalyzerClient(含 /chat 透传)
D-6 事务内调用 LLM / 外部 IO(长事务) 锁占用、拖垮连接池

3.2 backend 包结构与类清单(类级分解)

com.evocode
├── EvocodeApplication.java
├── common/
│   ├── Result.java                  # 统一响应 {code,message,data}
│   ├── PageResult.java              # 分页包装
│   ├── ErrorCode.java               # 错误码枚举(1xxx/2xxx/3xxx/5xxx)
│   ├── BusinessException.java
│   ├── GlobalExceptionHandler.java  # 全局兜底(校验/业务/系统)
│   └── util/ FileUtil, JsonUtil, PathSafetyUtil, TimeUtil
├── config/
│   ├── AsyncConfig.java             # analysisExecutor 线程池定义
│   ├── ChatExecutorConfig.java      # SSE 流式线程池(P6)
│   ├── MybatisPlusConfig.java       # 分页插件/逻辑删除/自动填充
│   ├── CorsConfig.java
│   └── EvocodeProperties.java       # @ConfigurationProperties 集中配置(含 analyzer-url;LLM 配置归 analyzer)
├── enums/  ProjectSourceType, ProjectStatus, AnalysisStatus,
│           AnalysisType, RiskLevel, IssueStatus, TechDebtSource, DocType,
│           ChatRole, AiStatus, HotspotLevel, ReportSource, NodeType, EdgeRelation
├── entity/  Project, Analysis, FileNode, Dependency, QualityIssue,
│            ArchitectureNode, ArchitectureEdge, ArchViolation, TechDebt,
│            CommitStat, FileChangeStat, ChatSession, ChatMessage, GeneratedDoc,
│            KnowledgeChunk
├── mapper/  ProjectMapper, AnalysisMapper, FileNodeMapper, …(每实体一个)
├── dto/
│   ├── project/    ProjectCreateReq, ProjectResp, ProjectDetailResp, ProjectSummaryResp
│   ├── analysis/   AnalyzeReq, AnalysisResp, AnalysisStatusResp
│   ├── report/     ReportResp, ReportDimension, ReportRisk, ReportRecommendation
│   ├── scan/       ScanResultResp, ScanFileResp
│   ├── quality/    QualityMetricsResp, QualityIssueResp, IssueExplainReq
│   ├── arch/       ArchGraphResp, ArchNodeResp, ArchEdgeResp, ViolationResp
│   ├── evolution/  EvolutionResp, TrendPoint, FileChangeResp, HotspotResp
│   ├── debt/       TechDebtReq, TechDebtResp
│   ├── chat/       ChatSessionResp, ChatMessageResp, ChatAskReq, ChatDelta
│   └── doc/        DocResp, DocEditReq
├── service/
│   ├── project/
│   │   ├── ProjectService/Impl           # 项目 CRUD + 删除级联编排
│   │   ├── UploadService/Impl            # zip 校验/解压/临时目录/原子移入
│   │   └── GitCloneService/Impl          # clone --depth1 / 全量 / 代理
│   ├── analysis/
│   │   ├── AnalysisService/Impl          # 状态机流转(唯一入口)
│   │   ├── AnalysisAsyncRunner           # @Async 执行体,分阶段调 analyzer
│   │   └── AnalyzerClient                # RestClient 封装,错误映射 3xxx
│   ├── scan/      FileNodeService         # 快照落库 + 前后对比
│   ├── report/    ReportService           # 报告组装/读取/重新生成
│   ├── quality/   QualityService          # 指标 + issue 分页 + 解释任务入队
│   ├── architecture/ ArchService          # 图数据 + 违规查询
│   ├── evolution/ EvolutionService
│   ├── chat/
│   │   ├── ChatService/Impl               # 会话持久化 + 调 analyzer /analyze/v1/chat 并透传 SSE
│   │   └── ChatSseForwarder               # 流式转发(不缓冲;断流 → error 事件 → 落库)
│   ├── debt/     TechDebtService          # 自动生成/状态机/复发复查
│   └── doc/      DocService               # 三类文档 + 编辑版本
└── controller/
    ├── ProjectController / AnalysisController / ReportController
    ├── FileController / QualityController / ArchitectureController
    ├── EvolutionController / TechDebtController
    ├── ChatController(SSE)/ DocController / HealthController

关键服务职责细化

服务 核心职责 关键约束
AnalysisService 发起任务(并发校验)、状态机流转、进度上报 唯一允许修改 analysis 状态的地方
AnalysisAsyncRunner 编排分析阶段:SCAN→REPORT;超时/降级处理 无状态,只靠 analysisId 驱动
UploadService 校验→解压到临时目录→安全校验→原子移入 失败即清理临时目录
ReportService 调 analyzer /analyze/report(内部含 LLM→规则版降级)→ 校验 JSON → 落库 不直连 LLM(LLM 出口在 analyzer)
TechDebtService 从各分析结果生成/去重 issue;复查复发 去重键 (project,source,rule)

3.3 analyzer 模块与插件架构

analyzer/app/
├── main.py                    # FastAPI 实例;路由注册;全局异常
├── config.py                  # Settings(pydantic-settings)
├── schemas.py                 # 契约(唯一事实来源):
│   ScanRequest/Result, QualityRequest/Result, ArchRequest/Result,
│   EvolutionRequest/Result, ReportRequest/Result, RagRequest/Result,
│   ChatRequest/ChatDelta, ExplainRequest/Result
├── api/routes/
│   scan.py quality.py architecture.py evolution.py report.py rag.py
│   chat.py explain.py health.py
├── core/                      # 无状态核心(不 import FastAPI)
│   ├── pipeline.py            # PipelineRegistry:阶段注册 + 权重 + 超时 + 进度
│   ├── filescanner.py         # 目录遍历(os.walk 剪枝)
│   ├── langdetect.py          # 后缀表 + 文件名 + shebang
│   ├── stackdetect.py         # 清单文件解析 → 框架标签
│   ├── loc.py                 # LOC 统计(流式读,跳过二进制)
│   ├── ignore.py              # 忽略规则引擎(默认 + .evocodeignore)
│   └── limits.py              # 文件数/大小/总超时
├── parsers/                   # 插件式语言解析器(SPI-1)
│   ├── base.py                # BaseParser 抽象 + ParsedUnit(Symbol/Relation)
│   ├── registry.py            # ParserRegistry:language → parser
│   ├── java_parser.py  python_parser.py  ts_parser.py
│   ├── go_parser.py   vue_parser.py
├── analyzers/                 # 分析器(面向结果产出,SPI-2)
│   ├── dependency_analyzer.py # 依赖清单解析 + eol 规则引擎(rules/eol.json)
│   ├── quality_analyzer.py    # sonar-scanner 调用 + 指标归一化 + 超时
│   ├── architecture_analyzer.py # 节点/边提取 + 分层违规/循环/上帝类
│   └── evolution_analyzer.py  # git log --numstat 统计 + 热点规则
├── ai/
│   ├── llm_client.py          # OpenAI 兼容客户端(超时/重试/JSON 校验)
│   ├── embed_client.py        # embedding 客户端(bge-m3 等)
│   ├── prompts.py             # 全部 prompt 模板(版本化)
│   ├── report_generator.py    # 报告生成(LLM → 规则版降级链)
│   ├── explainer.py           # issue 解释(信号量限流,并发 ≤4)
│   └── rag.py                 # 切片(chunk) / 索引 / 混合检索
└── utils/
    ├── path.py                # 安全路径(根内校验 + 只读)
    └── process.py             # git 子进程安全封装(参数白名单)

管线 SPI(PipelineRegistry)

@dataclass
class Step:
    name: str            # 如 FILES / LANG / STACK / LOC
    handler: Callable[[StepContext], StepResult]

class PipelineRegistry:
    """端点内子步骤组合器(跨阶段编排在后端 AnalysisAsyncRunner):
       一个分析端点 = 若干 Step 顺序执行;同步完成、无状态、可多 worker"""
    _pipelines: dict[str, list[Step]]

    def register(self, endpoint: str, step: Step): ...
    def run(self, endpoint: str, ctx: StepContext) -> PipelineResult:
        for step in self._pipelines[endpoint]:
            ctx.check_timeout()                 # 单端点总超时
            ctx.remember(step.name, step.handler(ctx))
        return PipelineResult(ctx)

端点内组合示例:/analyze/v1/scan = FILES → LANG → STACK → LOC;/analyze/v1/quality = SONAR → NORMALIZE。 新分析器接入 = 新增 analyzer 模块(新端点 + 端点内注册 Step)+ 契约扩展 + 后端 AnalysisAsyncRunner 增加一个阶段与进度权重(后端仅此一处小改)。整体 ≤2 天(A1 目标)。

解析器 SPI(BaseParser)

class BaseParser(ABC):
    language: str                       # "java"
    @abstractmethod
    def parse(self, source: str) -> ParsedUnit:
        """输出 symbols(类/函数/方法) + relations(调用/导入)"""

class ParserRegistry:
    parsers: dict[str, BaseParser]
    @classmethod
    def register(cls, parser: BaseParser): ...
    @classmethod
    def get(cls, language: str) -> BaseParser | None: ...

新增语言 = 实现 parse + 注册 + 单测 + 后缀表加一行。A0 只实现 java;A1 补 python/ts/go/vue。

3.4 frontend 模块结构

路由表(懒加载)

路径 页面 说明
/ redirect → /projects
/projects project-list 列表 + 搜索 + 筛选
/projects/new project-create 上传/克隆(或弹窗)
/projects/:id/overview project-detail/overview 档案 + 最近分析
/projects/:id/report/:analysisId project-detail/report 体检报告
/projects/:id/architecture project-detail/architecture 架构图
/projects/:id/quality project-detail/quality 质量
/projects/:id/evolution project-detail/evolution 演化
/projects/:id/tech-debt project-detail/debt 技术债
/projects/:id/doctor project-detail/doctor AI 医生(SSE)
/projects/:id/docs project-detail/docs 文档
/dashboard dashboard 跨项目总览(P7)

组件树与数据流

stores/project.ts(当前项目 + 档案)  stores/analysis.ts(轮询任务状态)
        │                                     │
        ▼                                     ▼
api/  project.ts analysis.ts report.ts quality.ts architecture.ts
      evolution.ts debt.ts chat.ts doc.ts          ← 每资源一个文件,返回 types/api.ts 类型
        │
composables/  useProject.ts useAnalysisPolling.ts useChatStream.ts(SSE 解析)
        │
components/   base/(Empty/Loading/Tag/Modal/Pagination)
              charts/(ChartContainer + RelationChart/RadarChart/TrendLine/Donut)
              feature/(UploadPanel/ReportCard/RiskItem/IssueDrawer/ChatMessage/CitationCard/FilePreview)
        │
views/        project-list/ project-create/ project-detail/{overview,report,architecture,
              quality,evolution,debt,doctor,docs}.vue dashboard/

图表抽象:所有图表复用 ChartContainer.vue(ECharts 初始化/resize/主题/空态),业务只传 options 工厂函数——保证图表风格统一与按需加载。

SSE 流式useChatStream 用 fetch + ReadableStream 逐块解析 data: 行;delta 追加渲染,citations 到齐后渲染引用卡片。

3.5 AI 层细化(LLM 出口唯一在 analyzer)

            ┌─────────────────────────────────────────────┐
            │  AI 出口唯一在 analyzer(backend 永不直连 LLM)│
            │  /analyze/v1/report   报告(LLM → 规则版降级) │
            │  /analyze/v1/explain  issue 解释(P3,限流 4)│
            │  /analyze/v1/chat     AI 医生 SSE(P6)       │
            ├─────────────────────────────────────────────┤
analyzer:   llm_client.py(OpenAI 兼容:DeepSeek/OpenAI/Ollama)
            prompts.py(全部 prompt,带 version 元数据)
            配置:baseUrl / apiKey / model / timeout / retries / temperature
            └─────────────────────────────────────────────┘

降级链(每个 AI 能力都定义,在 analyzer 内实现):
  LLM 成功 → 使用结果
  LLM 失败/无 Key → 规则版(rules 模板)→ 结果带 source=LLM|RULES
  RAG 检索失败 → 纯摘要问答(无知识块,prompt 说明)

RAG 管线(P6,检索在 analyzer,会话在 backend):
  代码 → 切片(函数/类 ≤800token) → embed → pgvector(HNSW cosine)
  提问(经 backend 转发) → 检索 topK → 注入 prompt → SSE 流式 → backend 透传前端

backend 侧 ChatService 职责(仅编排,不调 LLM):会话 CRUD → 组装消息 → 调 /analyze/v1/chat → 透传 SSE 事件(delta/citations/done/error)→ 结束落库 + 引用校验(引用路径必须存在于本次知识块集合)。

Prompt 管理:全部在 analyzer/app/ai/prompts.py,每版 prompt 带 version 字段写入结果元数据(promptVersion),便于问题复现与答辩讲解。


4. 进程视图(进程/线程/异步模型)

4.1 进程清单

进程 数量 说明
backend 1(JVM) Tomcat 8080;内部多线程
analyzer 1(可演进为多 worker) uvicorn 8091,仅内网
frontend dev: vite / prod: nginx 静态
postgres/redis/sonar 容器 127.0.0.1

4.2 backend 线程模型

线程池 配置 用途 说明
Tomcat HTTP 默认 200 REST 阻塞短任务(DB 查询)
analysisExecutor core2/max4/queue8 分析任务执行 @Async;队列满 → Abort 并标记任务 FAILED("队列已满"),不丢业务数据
SSE 转发(P6) Servlet 异步,不占线程 透传 analyzer 事件流 断流时向客户端发 error 事件并落库

backend 无 LLM 线程:所有 AI 调用发生在 analyzer(llm_pool,见 §4.3),backend 仅透传。

决策:分析任务不用虚拟线程(JDK17 无),若升级 JDK21 可评估替换 chatExecutor(待决 Q-1)。

4.3 analyzer 线程/GIL 策略

  • 双执行池(避免 CPU 解析被 LLM IO 等待饿死):
  • parser_pool:CPU×2 线程,跑 tree-sitter 解析/扫描(CPU 密集)
  • llm_pool:IO 型(httpx 异步或独立池 8 线程),跑 LLM 调用/embedding
  • 路由:解析类端点(scan/quality/arch/evolution)入 parser_pool;生成类端点(report/explain/chat)内部 LLM 重 → llm_pool
  • GIL 注意:tree-sitter 解析是 CPU 密集,受 GIL 限制 → 并发解析受 parser_pool 上限约束;若 P4 压测吞吐不足,方案 B 为 uvicorn --workers N(多进程,注意 DB 连接池与内存翻倍)(待决 Q-2)
  • 所有分析端点无状态:请求内完成,不保存服务端会话 → 天然支持多 worker

4.4 内存与资源策略

  • 上传/解压:流式;单文件 >2MB 跳过;总解压体积上限 500MB
  • 解析结果:分页写库(每 500 条 flush),不全量驻留内存
  • 报告 JSON:<1MB 预期;超限时截断 risks 列表(保留 TOP 50)

5. 物理视图(部署拓扑)

5.1 开发环境(单人)

[Windows 本机]
  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
  │ frontend     │   │ backend      │   │ analyzer     │
  │ vite :5173   │──▶│ :8080        │──▶│ :8091(仅本机) │
  └──────────────┘   └──────┬───────┘   └──────┬───────┘
                            │                  │
                  ┌─────────▼──────────────────▼─────────┐
                  │ Docker Desktop(仅本机端口)            │
                  │ postgres:5432 · redis:6379 · sonar:9000│
                  └───────────────────────────────────────┘
  外网出口(仅 backend/analyzer 进程)→ LLM API · GitHub

5.2 演示/答辩环境(离线要点)

  • 全套本机;LLM 用 Ollama 本地模型(EMBEDDING_BASE_URL=http://127.0.0.1:11434
  • 断网兜底链:LLM→规则版;GitHub→zip 上传;Sonar→N/A;主演示路径(上传→扫描→报告)零外网依赖
  • 演示前跑 scripts/security-smoke + AC-1/3/5/6/7/8/9 回归

6. 数据架构(ER 关系与数据流)

6.1 ER 关系(核心)

project 1 ── N analysis(每次分析一批结果)
analysis 1 ── N file_node            (扫描快照)
analysis 1 ── N dependency           (依赖风险)
analysis 1 ── N quality_issue        (质量 issue + AI 解释)
analysis 1 ── N architecture_node    (架构节点)
architecture_node 1 ── N architecture_edge(source/target 两个 FK)
analysis 1 ── N arch_violation
project 1 ── N commit_stat            (演化,跨分析累计)
project 1 ── N file_change_stat       (热点文件,唯一约束 project+path)
project 1 ── N tech_debt              (跨分析,闭环状态)
project 1 ── N generated_doc
project 1 ── N chat_session 1 ── N chat_message
project 1 ── N knowledge_chunk        (向量,随分析重建)

设计要点:分析结果都挂 analysis_id(可对比两次分析);演化与技术债挂 project_id(跨分析累积)。删除项目 = 按 6.2 顺序级联。

6.2 数据流

① 创建:zip/GitHub ──▶ data/projects/{id}/(磁盘,DB 只存元数据)
② 分析:磁盘 ──▶ analyzer.scan ──▶ file_node + project 档案更新
③ 报告:扫描摘要 ──▶ LLM(或规则版)──▶ analysis.report_json
④ 质量(P3):磁盘 ──▶ sonar-scanner ──▶ quality_issue
⑤ 架构(P4):磁盘 ──▶ parsers ──▶ nodes/edges/violations
⑥ 演化(P5):磁盘 git 目录 ──▶ git log ──▶ commit_stat / file_change_stat
⑦ RAG(P6):代码 ──▶ 切片 ──▶ embed ──▶ knowledge_chunk(向量)
⑧ 技术债(P7):各分析结果 ──▶ tech_debt(去重/闭环)

6.3 存储策略

数据 存储 说明
业务数据 PostgreSQL DDL 见《开发指导》§5(V001-V006)
代码文件 磁盘 data/projects/{id} 不入库;删除项目时清理
向量 pgvector(HNSW cosine) 维度随 embedding 模型(bge-m3=1024)
配置/规则 配置文件 + rules/eol.json 可编辑可热加载(读时加载)
日志 磁盘 logs/ 按天滚动 14 天 任务级前缀 [analysisId=x]

7. 关键场景时序

7.1 创建项目 + 分析(核心闭环,v0.1)

前端              backend                  analyzer          LLM
 │ POST /projects  │                         │                │
 │ (multipart zip) │ UploadService:          │                │
 │────────────────▶│ 校验→临时目录→安全校验   │                │
 │                 │ →原子移入 data/…/{id}    │                │
 │                 │ 建档(快扫可异步)         │                │
 │◀──── 201 ──────▶│                         │                │
 │ POST /analyses  │ AnalysisService:        │                │
 │────────────────▶│ FOR UPDATE 并发校验      │                │
 │                 │ 建任务 PENDING → @Async  │                │
 │                 │ AnalysisAsyncRunner:    │                │
 │                 │  SCAN 阶段              │ POST /scan     │
 │                 │────────────────────────▶│────────────────▶│
 │                 │                         │ 扫描/语言/技术栈 │
 │                 │◀──────── 结果 ─────────│◀────────────────│
 │                 │ 落库 file_node+档案     │                 │
 │                 │  REPORT 阶段           │ POST /report    │
 │                 │────────────────────────▶│───────────────▶│
 │                 │                         │ summary+prompt │
 │                 │◀─── 报告(source=LLM) ──│◀───────────────│
 │                 │ 写 report_json, SUCCEEDED│                │
 │ 轮询 2s ◀───────│                         │                │
 │ GET /report ◀───│                         │                │

7.2 质量 issue 解释(P3,异步不阻塞)

前端                    backend                  analyzer          LLM
 │ GET /quality-issues │                          │                │
 │────────────────────▶│ 分页返回(ai_status=PENDING)│               │
 │ 点"解释"             │ QualityService 入队      │                │
 │────────────────────▶│ → 调 analyzer /explain   │───────────────▶│
 │                     │   (信号量限流 4)         │◀────解释──────│
 │                     │ 写回 ai_explanation      │                │
 │ 轮询/刷新 ◀─────────│                          │                │

7.3 AI 医生对话(P6,SSE;LLM 出口在 analyzer)

前端                     backend(编排+透传)               analyzer(检索+生成)
 │ POST /chats/{id}/messages   │                            │
 │────────────────────────────▶│ ChatService:               │
 │                             │ 1.会话持久化+组装历史(截断) │
 │                             │ 2.POST /analyze/v1/chat(SSE)│
 │                             │───────────────────────────▶│
 │                             │                            │ RAG检索:向量top8+关键词top4
 │                             │                            │ LLM.stream 生成事件流
 │ SSE delta ◀─────────────────│ ◀────────── SSE 事件 ──────│
 │ SSE citations ◀─────────────│ 透传(不缓冲)             │
 │ SSE done ◀──────────────────│ 引用校验在后端(存在性)    │
 │                             │ analyzer 断流 → 发 error 事件│
 │                             │ → 落库(含 citations)     │

7.4 降级链全景(演示保障)

LLM 不可用 → 报告=规则版(Mock) + source=RULES + 前端横幅
Sonar 未起 → 质量维度=N/A,其余正常
GitHub 不可达 → 提示转 zip 上传
分析中断(服务重启) → 任务标 FAILED("服务重启中断"),可重新发起

7.5 删除项目与分析中的竞态(P1+)

用户                 backend
 │ DELETE /projects/{id}
 │───────────────────▶│ ProjectService:
 │                    │ ① 存在 RUNNING 任务 → 置 CANCELLED
 │                    │ ② 等待执行线程退出(轮询 ≤60s)
 │                    │ ③ 事务内按级联顺序清库(见 §6.1)
 │                    │ ④ 删磁盘目录(失败仅记日志,不阻塞)
 │◀──── 200 ──────────│

8. 扩展点与 SPI 清单

扩展点 位置 接口/机制 接入成本 阶段
SPI-1 新语言解析器 analyzer/app/core/arch BaseParser + ParserRegistry.register(2026-08-13 落地 registry.py,5 语言注册 + languages 过滤) ≤1 天(配置级) A1 达成
SPI-2 新分析器 analyzer/analyzers + PipelineRegistry 新端点 + 端点内 Step 注册 + 后端 AnalysisAsyncRunner 加阶段 ≤2 天(插件级) A1
SPI-3 新 LLM 提供商 analyzer/ai llm_client.py(OpenAI 兼容协议即可覆盖 DeepSeek/OpenAI/Ollama) 0.5 天 A0
SPI-4 新规则(EOL 等) rules/eol.json 配置文件热加载 分钟级 A1
SPI-5 任务队列演进 backend/analysis DB 状态机 + 启动恢复(2026-08-13:AD-019 StartupTaskRecovery 标记中断任务 FAILED);Redis 队列维持可选(出现「自动重跑」硬需求时启用) 1 天(预留) A2 部分落地
SPI-6 报告存储演进 backend/service/report report_json → 拆分表(2026-08-13 落地 analysis_report 表 + ReportStorageService 唯一读写入口,health_score/level/summary 列化) 1 天(预留) A2 达成
SPI-7 前端图表 frontend/charts ChartContainer + options 工厂 分钟级 A0
SPI-8 权限 backend 预留拦截器位(v1.0 后) 远期

演进示例(展示架构可扩展性,答辩加分点):A0 阶段 java 解析器已存在,A1 加 Python 支持 = 新增 python_parser.py + 注册 + 后缀表 1 行 + 单测 4 例,共 ~200 行。


9. 质量属性与架构对策

NFR(引用需求 §8) 架构对策 验证方式
性能:2 万行 ≤10min 忽略剪枝、大文件跳过、异步任务、分页写库 AC-5 计时
可用性:LLM 降级 降级链(§7.4):LLM 失败/无 Key → analyzer 规则版报告(source=RULES) AC-6/AC-12
安全:不执行代码 静态只读 + path.py 安全封装 + 解析白名单 security-smoke 脚本
安全:恶意 zip UploadService 三重校验 + 临时目录 + 原子移入 AC-8
可扩展:新增语言 ≤1 天 SPI-1 插件注册表 演示加语言
可替换:LLM/Sonar/队列 SPI-3/SPI-5,配置切换 换 baseUrl 即生效
可解释:结论可溯源 引用字段(file:line)+ prompt 强约束 + source 标记 报告抽查
可维护:防腐化 ArchUnit(§10)+ 契约测试 + 单向依赖 CI 三命令

10. 架构治理(防腐与保障)

10.1 ArchUnit 规则(backend 测试,P1 起步)

@AnalyzeClasses(packages = "com.evocode")
public class ArchitectureTest {

    @Test
    void controller不得依赖mapper与entity() {
        classes().that().resideInAPackage("..controller..")
            .should().notDependOnClassesThat().resideInAnyPackage("..mapper..", "..entity..")
            .check(importedClasses);
    }

    @Test
    void service不得依赖controller() {
        classes().that().resideInAPackage("..service..")
            .should().notDependOnClassesThat().resideInAPackage("..controller..")
            .check(importedClasses);
    }

    @Test
    void backend不得直连LLM() {
        // LLM 出口唯一在 analyzer:backend 仅能通过 AnalyzerClient(含 chat 透传)通信
        classes().that().resideInAPackage("..service..")
            .should().notDependOnClassesThat().resideInAPackage("..ai..")
            .andShould().notDependOnClassesThat().haveSimpleName("OpenAiCompatibleClient")
            .check(importedClasses);
    }
}

10.2 契约测试(analyzer ↔ backend)

  • 方式:schemas.py 字段清单与后端 DTO 字段名做一致性脚本(Python 侧导出 JSON schema → Java 测试读取比对)
  • 触发:契约变更 PR 必跑;失败阻止合并

10.3 架构评审清单(每阶段自查)

  • [ ] ArchUnit 全绿(分层/反向依赖/LLM 隔离)
  • [ ] 新分析器走管线注册,未在 controller/service 硬编码阶段
  • [ ] 新语言走 ParserRegistry,后缀表已加
  • [ ] 所有外部调用(LLM/git/sonar)有超时与降级
  • [ ] 无新增"偷偷执行"代码的路径(grep eval/exec/subprocess)
  • [ ] 契约字段三端一致(grep 核对)
  • [ ] 大 JSON 未整表查询;列表有分页

11. 架构演进规划(A0 → A3)

架构版本 对应产品 关键架构变化 完成指标
A0 v0.1 三端骨架 + 独立 analyzer + DB 状态机 + 规则版降级链 + SPI-1/3/7 预埋 一键启动;AC-1~12 全过;ArchUnit 引入
A1 v0.2~v0.3 管线插件化(Quality/Arch 阶段注册)+ 解析器扩充(py/ts/go/vue)+ EOL 规则配置化 + Sonar 接入(SPI-4 生效) 新增分析器 ≤2 天;新增语言 ≤1 天(演示验证)
A2 v0.4~v1.0 RAG 全链路(切片/向量/混合检索/SSE)+ 技术债闭环 + 报告拆表(SPI-6,2026-08-13 达成)+ 可选 Redis 队列(SPI-5,读缓存方向已落地 TD-05) AI 医生引用准确率抽查 ≥90%;技术债复发可自动复查
A3 远期 多用户鉴权(SPI-8)、多节点部署(analyzer 多实例)、增量分析、多仓库 视论文篇幅与时间裁剪,不承诺

关键路径与裁剪策略

A0 主线不可裁剪:上传→扫描→报告(12 AC)
A1 若时间不足 → 质量/架构二选一演示(文档结构与报告维度已预埋)
A2 若时间不足 → 技术债闭环(纯后端逻辑,性价比最高)优先于 RAG 深度优化

12. 架构决策索引与待决问题

12.1 决策索引(对应《开发指导》AD-1~16)

AD 决策 架构影响
AD-1/2 单体 + 独立 analyzer 逻辑视图 §3 边界
AD-3/4 自写 LLM 客户端(analyzer)+ pgvector AI 层 §3.5、数据 §6.3
AD-5 DB 状态机 + @Async 进程视图 §4.2、SPI-5
AD-8 Sonar 社区版 降级链 §7.4
AD-9 规则版降级 §7.4、analyzer 降级链
AD-13 报告存 report_json SPI-6 预留
AD-14 健康分规则+AI 修正 报告契约(可复现)
AD-16 SSE 流式 §7.3
AD-17 Redis 宿主端口改绑 6380(本机 polycode 冲突) §8 SPI-5
AD-18 Redis 列表读缓存(TD-05:选读缓存非任务队列,TTL+降级) §8 SPI-5
AD-19 分析任务中断恢复(启动标记 FAILED;SPI-5 压测评估,Redis 队列维持可选) §8 SPI-5、进程视图 §4.2

12.2 待决问题(实现时验证,记录进 AD)

# 问题 影响 决策时机
Q-1 JDK 21 虚拟线程 vs SSE 转发线程模型 流式并发上限 P6 前
Q-2 analyzer 单进程线程池 vs 多 worker 解析吞吐/内存 P4 压测后(已压测:单进程 4 并发 FULL 3 秒内完成、满足常规;多 worker 留待吞吐需求)
Q-3 报告拆表阈值(>1MB?版本历史需求?) 数据层演进 已解决(SPI-6 V010:healthScore/level/summary 列化 + report_json 独立存储,2026-08-13)
Q-4 "全文发送"开关的存储实现(标记文件白名单) 隐私合规 P6 前
Q-5 Sonar 太重时的内置规则替代方案(超长方法等已内置) A1 范围 已解决(TD-07 Sonar 可选化 profile + 内置规则兜底,2026-08-13)
Q-6 .evocodeignore 语法解析器选择(复用 gitignore 库) 用户体验 P1 起步时

附:架构一句话总结

一个无状态的 Python 解析内核 + 一个有状态的 Java 业务中枢 + 一个只做展示的 Vue 壳,三者通过契约 JSON 连接;一切可替换的(LLM/Sonar/队列/语言/规则)都做成 SPI,一切不可靠的(外网/第三方)都有降级路径,一切结论都可溯源到文件与行号。