架构设计
分层
web / cli 入口层:REST 控制器、命令行
service 编排层:一次分析的完整流水线、进度、异步
impact 分析层:契约匹配 + 传播 + 打分
report 输出层:报告模型、Markdown、AI 解读
diff 变更层:文件 diff、语义变更检测
parse 索引层:Maven / Java / SQL / API / 配置 / 消息
source 来源层:本地目录、Git、GitHub、GitLab、PR
store 持久层:SQLite
schedule 调度层:每日定时扫描
依赖方向是单向的:入口 → 编排 → 分析 → 索引 → 来源。没有反向依赖,也没有循环。
一次分析的数据流
RepoSource(代码源记录)
│ RepositoryPreparer
▼
RevisionPair { base, head } ← 两个 FileSource 快照
│ FileDiffCollector
▼
List<FileChange> ← 哪些文件变了
│ CodeIndexer × 2(base / head 各索引一次)
▼
CodeIndex base, CodeIndex head ← 两套代码知识图谱
│ ChangeDetector
▼
List<ChangeRecord> ← 语义变更(带稳定符号 key)
│ PeerImpactEngine(每个 peer 一次)
▼
List<PeerImpact> ← 每个 peer 的契约命中 + 影响
│ ReportBuilder
▼
ImpactReport ← 落库 + 返回给前端
关键设计决定
为什么不合并两个仓库的图
技术上可以合并,但会引入一堆伪关系:两个仓库里同名但无关的类会被误连。 契约是唯一可靠的连接点,所以先用契约接上,再在各自的图里传播。
为什么索引不落库
索引随代码变化,落库就要处理失效。每次分析从快照重建(毫秒级到秒级), 永远和代码一致。只有报告落库。
为什么用快照抽象而不是直接读工作区
FileSource 接口统一了”某个版本的文件集合”:工作区、某个提交、远程仓库缓存都是它。
这样索引逻辑完全不关心代码来自哪里,也让 base/head 可以用同一套代码处理。
为什么风险与置信度分开
见 评分模型。简而言之:合并成一个数字会丢掉”不确定”这个信息, 而”不确定”恰恰是静态分析最需要诚实表达的部分。
并发模型
- 分析在固定线程池里异步执行(2 个 worker),HTTP 请求立即返回 id
- 进度写入 SQLite,前端轮询(阶段边界必写,文件回调 150ms 节流)
- 定时扫描有单例守卫,同一时刻只有一个扫描在跑
- 启动时恢复孤儿任务(进程被杀时留下的 RUNNING 记录)
目录结构
src/main/java/com/geek/association/
├── source/ 快照抽象与仓库准备
├── parse/ 各类分析器(Java / SQL / API / 配置 / 消息)
├── diff/ 文件 diff 与语义变更检测
├── impact/ 契约匹配、传播、评分
├── report/ 报告模型与渲染
├── service/ 分析编排
├── schedule/ 定时扫描
├── store/ SQLite 持久化
├── web/ REST 接口
├── cli/ 命令行入口
├── config/ 配置绑定
└── util/ 工具类