创新实训8——后端开发指南
创新实训8——后端开发指南
文档版本: 2026-06-07
背景:数据部分开发完成,暴露了SparkSubmit的能力,需要由后端工程师将Spark提交和agent分析整合。特整理此文档,让后端工程师了解仓库现状
适用对象: 接手后端开发的工程师(阅读本文档前请先完整阅读CLAUDE.md和README.md)
核心原则: 本文档描述的是 代码现状(what exists),而非「理想设计」。所有实现请基于实际代码结构,不要猜测。
一、项目全景
1.1 项目定位
从 GitHub 采集仓库数据 → Spark ETL 清洗/聚合/评分 → MySQL 存储 → Spring Boot API 暴露结果 → AI Agent 生成质量报告。
用户 / 前端
│
▼
┌──────────────────────────────────────────────────────────────┐
│ backend-api (Spring Boot) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ 用户/仓库 │ │ 分析任务 │ │ ETL 调度 │ │ AI 质量评估 │ │
│ │ Controller│ │ Controller│ │Controller│ │ Controller │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───────┬────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ MySQL (codeq_db) 18张表 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ▲ │
│ Spark ETL (子进程) │
│ data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar │
└──────────────────────────────────────────────────────────────┘
1.2 技术栈
| 组件 | 技术 | 版本 | 说明 |
|---|---|---|---|
| 后端框架 | Spring Boot | 2.7.0 | REST API |
| ORM | Spring Data JPA / Hibernate | 5.6.9 | MySQL 数据访问 |
| 缓存 | Spring Data Redis | - | 仓库/任务缓存 |
| AI | DashScope (通义千问) | qwen-plus | OpenAI 兼容接口 |
| 数据库 | MySQL | 8.0 | codeq_db |
| ETL | Apache Spark | 3.0.0 | 子进程运行,不嵌入 JVM |
| 构建 | Maven | 3.6.3 | 多模块 |
1.3 模块依赖关系
backend-api
├── 依赖 common (共用 AppConfig、R.java)
├── 依赖 data-collector 的 fat jar (运行时子进程调用)
└── 不依赖 feature-processor (已删除)
二、当前状态评估
2.1 已完成的(不需要动)
| 模块 | 状态 |
|---|---|
| 用户管理 API(注册/登录/CRUD) | ✅ 完整可用 |
| 仓库管理 API(CRUD + Redis 缓存) | ✅ 完整可用 |
| 分析任务 API(CRUD + Redis 缓存) | ✅ 完整可用 |
| ETL 调度 API(提交/状态/取消/统计) | ✅ 完整可用 |
| 个人中心 API(收藏/历史/统计) | ✅ 完整可用 |
| 操作日志 API(查询/分页) | ✅ 完整可用 |
| AI 质量评估 API(单次评估) | ✅ 可用,需手动传 info |
| ETL 全链路(ODS→DWD→DWS→ADS) | ✅ exit code 0 |
| MySQL 建表 DDL(18张表) | ✅ |
| fat jar 打包 | ✅ 108MB |
2.2 待开发的(需要实现)
| 优先级 | 任务 | 影响面 | 现状 |
|---|---|---|---|
| P0 | ETL 完成后自动触发 AI 评估+写入 quality_report | 核心闭环 | SparkJobService.triggerAgentAnalysis() 第178行是骨架 |
| P0 | POST /api/quality/evaluate 自动从 DB 读指标 |
用户体验 | 当前需要前端手动传 info 字段 |
| P1 | Redis 不可用时降级到 DB 查询 | 系统稳定性 | /api/repo/{id} 和 /api/task/{id} 在 Redis 未启动时返回 500 |
| P1 | /api/quality/evaluate 首次调用 500 的处理 |
可靠性 | DashScope 瞬态错误,需重试机制 |
| P2 | ETL 进度实时反馈 | 用户体验 | 当前是同步等待,无 WebSocket/SSE |
| P2 | 测试修复 | 质量保障 | ScalaTest 依赖缺失,多个测试无法编译 |
三、数据库核心表结构
以下是与后端开发最相关的业务表。DDL 在项目根目录 DDL.txt。
3.1 repository(仓库表)
CREATE TABLE repository (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
repo_name VARCHAR(255),
repo_url VARCHAR(500),
language VARCHAR(50),
fork_count INT DEFAULT 0,
star_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id)
);
JPA 实体: backend-api/.../entity/CodeRepository.java — 映射到 repository 表。
3.2 analysis_task(分析任务表)
CREATE TABLE analysis_task (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
repo_id BIGINT NOT NULL,
status VARCHAR(20) DEFAULT 'PENDING',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (repo_id) REFERENCES repository(id)
);
JPA 实体: backend-api/.../entity/AnalysisTask.java
3.3 metric_result(指标结果表)
CREATE TABLE metric_result (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
task_id BIGINT NOT NULL,
code_lines BIGINT DEFAULT 0,
comment_lines BIGINT DEFAULT 0,
blank_lines BIGINT DEFAULT 0,
comment_density DOUBLE DEFAULT 0,
avg_cyclomatic_complexity DOUBLE DEFAULT 0,
high_complexity_func_count INT DEFAULT 0,
duplication_rate DOUBLE DEFAULT 0,
naming_violation_count INT DEFAULT 0,
static_warning_count INT DEFAULT 0,
quality_score DOUBLE DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES analysis_task(id)
);
JPA 实体: backend-api/.../entity/MetricResult.java
重要: ETL 的 ADS 层 DWSToADSStep 将 metric_result 作为单条全量聚合写入(所有仓库合并计算一条记录)。如需按仓库拆分,需要修改 ADS 层。
3.4 file_metric(文件级指标表)
CREATE TABLE file_metric (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
task_id BIGINT NOT NULL,
file_path VARCHAR(500),
code_lines INT DEFAULT 0,
comment_density DOUBLE DEFAULT 0,
cyclomatic_complexity INT DEFAULT 0,
warning_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES analysis_task(id)
);
JPA 实体: backend-api/.../entity/FileMetric.java
3.5 quality_report(质量报告表 — 当前为空)
CREATE TABLE quality_report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
task_id BIGINT NOT NULL,
total_score INT,
grade VARCHAR(10),
risk_level VARCHAR(20),
summary TEXT,
strengths TEXT, -- JSON array
weaknesses TEXT, -- JSON array
recommendations TEXT, -- JSON array
dimensions TEXT, -- JSON object
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES analysis_task(id)
);
JPA 实体: backend-api/.../entity/QualityReport.java ✅ 已定义
JPA Repository: backend-api/.../repository/QualityReportRepository.java ✅ 已定义
status: 表已建、实体已写、但目前没有任何代码向其写入数据。
3.6 外键关系
repository ──→ analysis_task ──→ metric_result
──→ quality_report
──→ file_metric
四、后端 API 完整清单
4.1 all endpoints
| 方法 | 路径 | Controller | 文件位置 |
|---|---|---|---|
| GET | /api/quality/status |
QualityController |
api/QualityController.java:16 |
| POST | /api/quality/evaluate |
QualityController |
api/QualityController.java:25 |
| POST | /api/user/register |
UserController |
controller/UserController.java:27 |
| POST | /api/user/login |
UserController |
controller/UserController.java:47 |
| GET | /api/user/{id} |
UserController |
controller/UserController.java:66 |
| PUT | /api/user/{id} |
UserController |
controller/UserController.java:97 |
| DELETE | /api/user/{id} |
UserController |
controller/UserController.java:118 |
| GET | /api/user/username/{username} |
UserController |
controller/UserController.java:81 |
| POST | /api/repo/add |
RepositoryController |
controller/RepositoryController.java:29 |
| GET | /api/repo/user/{userId} |
RepositoryController |
controller/RepositoryController.java:47 |
| GET | /api/repo/{id} |
RepositoryController |
controller/RepositoryController.java:58 |
| DELETE | /api/repo/{id} |
RepositoryController |
controller/RepositoryController.java:73 |
| POST | /api/task/create |
AnalysisTaskController |
controller/AnalysisTaskController.java:30 |
| PUT | /api/task/{id}/status |
AnalysisTaskController |
controller/AnalysisTaskController.java:44 |
| GET | /api/task/user/{userId} |
AnalysisTaskController |
controller/AnalysisTaskController.java:66 |
| GET | /api/task/{id} |
AnalysisTaskController |
controller/AnalysisTaskController.java:78 |
| POST | /api/etl/submit |
EtlController |
controller/EtlController.java:26 |
| GET | /api/etl/status/{jobId} |
EtlController |
controller/EtlController.java:43 |
| GET | /api/etl/jobs |
EtlController |
controller/EtlController.java:60 |
| POST | /api/etl/cancel/{jobId} |
EtlController |
controller/EtlController.java:68 |
| POST | /api/etl/analyze |
EtlController |
controller/EtlController.java:77 |
| GET | /api/etl/stats |
EtlController |
controller/EtlController.java:94 |
| POST | /api/personal/collect |
PersonalController |
controller/PersonalController.java:41 |
| POST | /api/personal/collect/cancel |
PersonalController |
controller/PersonalController.java:56 |
| GET | /api/personal/collects/{userId} |
PersonalController |
controller/PersonalController.java:71 |
| GET | /api/personal/history/{userId} |
PersonalController |
controller/PersonalController.java:85 |
| GET | /api/personal/stats/{userId} |
PersonalController |
controller/PersonalController.java:100 |
| GET | /api/log/user/{userId} |
OperationLogController |
controller/OperationLogController.java:30 |
| GET | /api/log/user/{userId}/page |
OperationLogController |
controller/OperationLogController.java:43 |
| GET | /api/log/action/{action} |
OperationLogController |
controller/OperationLogController.java:58 |
| DELETE | /api/log/cleanup |
OperationLogController |
controller/OperationLogController.java:68 |
4.2 响应格式
统一使用 com.codequality.common.R.java:
{ "code": 200, "msg": "操作成功", "data": { ... } }
{ "code": 500, "msg": "错误信息", "data": null }
4.3 异常处理模式
Controller 层统一捕获 IllegalArgumentException 返回 R.error()。其他异常(如 NullPointerException)会返回 500。参见各 Controller 的 try-catch 模式。
五、AI Agent 集成详解
5.1 当前实现
| 组件 | 文件 | 说明 |
|---|---|---|
| Controller | api/QualityController.java |
POST /evaluate 接收 {repository, info} |
| 接口 | api/service/LLMService.java |
evaluateRepository(repoName, repoInfo) |
| 实现 | api/service/impl/OpenAIServiceImpl.java |
调用 DashScope API |
| Prompt 模板 | api/prompt/PromptBuilder.java |
构建 prompt 要求 LLM 返回 JSON |
| DTO | api/model/EvaluationResult.java |
解析 LLM 返回的 JSON |
| Spring 配置 | application.properties |
openai.api.key/url/model |
5.2 API 调用链路
POST /api/quality/evaluate { repository: "alibaba/arthas", info: "..." }
→ QualityController.evaluate()
→ OpenAIServiceImpl.evaluateRepository(repoName, info)
→ PromptBuilder.buildEvaluationPrompt(repoName, repoInfo)
→ HTTP POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
Headers: Authorization=Bearer sk-50ad5cfb1e954c9bbab6bfd1539aa87a
Body: { model: "qwen-plus", messages: [...], response_format: { type: "json_object" } }
← { choices[0].message.content = '{ "totalScore": 92, "grade": "A", ... }' }
← EvaluationResult (已解析)
→ R.success(evaluationResult)
5.3 EvaluationResult 结构
{
"totalScore": 92,
"grade": "A",
"riskLevel": "Low",
"dimensions": {
"documentation": 85, "activity": 90,
"community": 92, "code_quality": 78, "maintainability": 75
},
"strengths": ["..."],
"weaknesses": ["..."],
"recommendations": ["..."],
"summary": "..."
}
5.4 已知问题
- 首次调用往往返回 500(DashScope 瞬态错误),重试即正常。
OpenAIServiceImpl中需加重试逻辑。 info字段需手动传,当前不会自动从 MySQL 读取 metric_result 数据。- 结果不持久化 — 返回给前端后即丢弃,不写入
quality_report表。
六、P0 任务:ETL 完成后自动 AI 评估 + 写入 quality_report
6.1 任务描述
当前 SparkJobService.triggerAgentAnalysis() (第178行) 在 ETL 成功后调用,但只有骨架代码(只打日志)。需要实现:
- ETL 完成后,从 DB 读取 metric_result + 相关指标
- 组装 repoInfo 字符串
- 调用
LLMService.evaluateRepository() - 将评估结果写入
quality_report表
6.2 关键代码位置
| 需要知道的 | 文件:行号 |
|---|---|
| ETL 完成后调用的方法 | SparkJobService.java:178 → triggerAgentAnalysis(JobInfo info) |
| LLM 评估方法签名 | LLMService.java → evaluateRepository(repoName, repoInfo) |
| quality_report JPA 实体 | entity/QualityReport.java |
| quality_report Repository | repository/QualityReportRepository.java |
| Prompt 构建 | api/prompt/PromptBuilder.java |
| PromptBuilderLLMTest 演示 | api/PromptBuilderLLMTest.java(有从 DB 读指标的示例代码) |
6.3 实现建议
// SparkJobService.triggerAgentAnalysis() 中需要完成的逻辑:
private void triggerAgentAnalysis(JobInfo info) {
new Thread(() -> {
try {
// 1. 从 DB 读取最新 analysis_task 的 metric_result
List<AnalysisTask> tasks = analysisTaskRepository.findTop10ByOrderByCreatedAtDesc();
if (tasks.isEmpty()) return;
AnalysisTask task = tasks.get(0);
// 2. 读取 metric_result(通过 MetricResultRepository)
MetricResult metric = metricResultRepository.findByTaskId(task.getId());
// 3. 读取 file_metric 聚合数据
List<FileMetric> files = fileMetricRepository.findByTaskId(task.getId());
// 4. 组装 repoInfo 字符串(参考 PromptBuilderLLMTest)
String repoInfo = buildRepoInfo(task, metric, files);
// 5. 调用 LLM 评估
EvaluationResult result = llmService.evaluateRepository(
task.getRepository().getRepoName(), repoInfo);
// 6. 写入 quality_report
QualityReport report = new QualityReport();
report.setTaskId(task.getId());
report.setTotalScore(result.getTotalScore());
report.setGrade(result.getGrade());
report.setRiskLevel(result.getRiskLevel());
report.setSummary(result.getSummary());
report.setStrengths(toJson(result.getStrengths()));
report.setWeaknesses(toJson(result.getWeaknesses()));
report.setRecommendations(toJson(result.getRecommendations()));
report.setDimensions(toJson(result.getDimensions()));
qualityReportRepository.save(report);
} catch (Exception e) {
log.error("AI analysis failed", e);
}
}).start();
}
// 注意:MetricResultRepository、FileMetricRepository、QualityReportRepository 已存在
// 需要 @Autowired(required = false) 注入到 SparkJobService
6.4 需要的改动
SparkJobService.java— 注入MetricResultRepository、FileMetricRepository、QualityReportRepositoryMetricResultRepository.java— 可能需要加findByTaskId(Long taskId)方法(检查是否存在,不存在则添加)FileMetricRepository.java— 可能需要加findByTaskId(Long taskId)方法(同上)QualityReportRepository.java— 检查是否有findByTaskId方法
七、P0 任务:自动从 DB 读指标
7.1 任务描述
当前 POST /api/quality/evaluate 需要前端传 info 字段(包含指标文本)。应改为:后端收到 { repository: "owner/repo" } 后,自动从 MySQL 读取该仓库的 metric_result 和 file_metric 数据,组装成 info 再调 LLM。
7.2 实现位置
QualityController.evaluate() 或 OpenAIServiceImpl.evaluateRepository() — 在调 LLM 之前,先查 DB 组装数据。
八、P1 任务:Redis 不可用时降级
8.1 当前问题
RepositoryService.getRepositoryById() (第124行) 和 AnalysisTaskService 中,先从 Redis 读取缓存:
Object cached = redisTemplate.opsForValue().get(REPO_CACHE_PREFIX + repoId);
if (cached != null) return (CodeRepository) cached;
如果 Redis 未启动,redisTemplate.opsForValue().get() 抛出异常,返回 500。
8.2 修复方案
在 Redis 读取操作周围加 try-catch,失败时回退到 DB 查询:
try {
Object cached = redisTemplate.opsForValue().get(REPO_CACHE_PREFIX + repoId);
if (cached != null) return (CodeRepository) cached;
} catch (Exception e) {
log.warn("Redis unavailable, falling back to DB for repoId={}", repoId);
}
// 从 DB 查询
CodeRepository repo = codeRepositoryRepository.findById(repoId).orElse(null);
九、ETL 数据模块(非后端工作但必须理解)
9.1 ETL 调用方式
后端通过子进程调用 fat jar:
// SparkJobService.java 第114-139行
List<String> cmd = new ArrayList<>();
cmd.add("java"); cmd.add("-jar"); cmd.add(fatJarPath);
cmd.add("--layer"); cmd.add(layer);
ProcessBuilder pb = new ProcessBuilder(cmd);
// 传递环境变量 GITHUB_TOKEN、MYSQL_URL/USER/PASSWORD
fat jar 路径: data-collector/target/data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar
入口类: com.codequality.collector.ETLJob(文件名为 UnifiedETLJob.scala)
当前 application.properties 已配置: spark.etl.main-class=com.codequality.collector.ETLJob
9.2 ETL 数据写入策略
| 表类型 | 策略 | 涉及表 |
|---|---|---|
| 过渡表 | TRUNCATE + append | ods_, dwd_, dws_* |
| 业务表 | DELETE + INSERT by key | repository, analysis_task, metric_result, file_metric |
9.3 各层数据量参考
| 表 | 行数 | 说明 |
|---|---|---|
| ods_github_api_raw | 50 | 50个高星Java仓库 |
| dwd_repo_detail | 50 | 结构化仓库信息 |
| dwd_contributor_detail | ~3,700 | 全量贡献者 |
| dwd_issue_detail | ~2,200 | 全量Issues |
| dwd_file_metric_detail | ~12,000 | 解析的Java文件 |
| dws_repo_daily_metrics | 50 | 每日聚合 |
| repository | 50 | 业务表 |
| analysis_task | 50 | 业务表 |
| metric_result | 1 (全量聚合) | 质量评分 |
| file_metric | ~12,000 | 文件级指标 |
| quality_report | 0 | 需你实现写入 |
十、配置说明
10.1 application.properties 关键配置
server.port=8080
# MySQL(后端独用,与 AppConfig.scala 独立维护)
spring.datasource.url=jdbc:mysql://localhost:3306/codeq_db?...
spring.datasource.username=root
spring.datasource.password=D200504193010
# DashScope AI
openai.api.key=sk-50ad5cfb1e954c9bbab6bfd1539aa87a
openai.api.url=https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
openai.model=qwen-plus
# Redis(非必须,但 repo/task 的 get-by-id 需要)
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456
# Spark ETL(子进程调用)
spark.etl.jar-path=../data-collector/target/data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar
spark.etl.main-class=com.codequality.collector.ETLJob
spark.etl.master=local[2]
10.2 Redis 配置
config/RedisConfig.java 使用 Jackson 序列化 GenericJackson2JsonRedisSerializer。已解决 LocalDateTime 序列化问题。
十一、如何验证
11.1 环境准备
# 1. 编译打包
mvn clean package -Dmaven.test.skip=true
# 2. 初始化数据库(首次)
mysql -u root -p < DDL.txt
# 3. 跑 ETL 全链路(确保 DB 有数据)
java -jar data-collector/target/data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar --layer all
# 4. 启动后端
mvn spring-boot:run -pl backend-api
11.2 验证命令
# 健康检查
curl http://localhost:8080/api/quality/status
# AI 评估
curl -X POST http://localhost:8080/api/quality/evaluate \
-H "Content-Type: application/json;charset=UTF-8" \
-d '{"repository":"alibaba/arthas","info":"317210 code lines, avg complexity 11.9, quality score 37.5"}'
# 验证 DB
mysql -u root -p -e "USE codeq_db; SELECT COUNT(*) FROM metric_result; SELECT COUNT(*) FROM quality_report;"
11.3 ETL 验证
# 各层独立验证
java -jar data-collector/target/data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar --layer ods
java -jar data-collector/target/data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar --layer dwd
java -jar data-collector/target/data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar --layer dws
java -jar data-collector/target/data-collector-1.0-SNAPSHOT-jar-with-dependencies.jar --layer ads
十二、常见陷阱
-
不要在 Spring Boot JVM 内嵌 SparkSession — 依赖冲突(logback/jackson 版本不一致),始终用子进程方式(
SparkJobService已正确实现为启子进程)。 -
quality/evaluate 首次调用 500 — DashScope API 偶发瞬态错误,需加重试(如重试 1-2 次,间隔 1 秒)。
-
Redis 不是强依赖 — 但
/api/repo/{id}和/api/task/{id}会因 Redis 异常而返回 500,需加 try-catch 降级。 -
metric_result 是单条全量聚合 — 所有仓库合并为一条记录。如果要按仓库查评分,需要改 ETL 的 ADS 层
DWSToADSStep.computeMetricResults()的 GROUP BY 逻辑。 -
quality_report 表已就绪 — 实体、Repository 都已存在,只差写入代码。
-
application.properties里spark.etl.main-class— 已从UnifiedETLJob修正为ETLJob(object ETLJob定义在UnifiedETLJob.scala文件中,文件名未改)。 -
MySQL 密码散落多处 —
application.properties、AppConfig.scala、SparkJobService.java中都有。改密码时需全部同步。
十三、参考文件索引
| 文件路径 | 内容 |
|---|---|
CLAUDE.md |
Claude 工作指引(架构/命令/约定) |
README.md |
项目说明书 |
docs/etl-context-implementation.md |
ETLContext 重构实现报告 |
docs/config-consolidation.md |
配置整合报告 |
docs/handoff-to-backend.md |
后端交接文档(旧版,仅供参考) |
docs/fix-summary.md |
P0-P2 修复总结 |
docs/known-issues.md |
数据模块已知问题 |
backend-api/API_COMPLETE_LIST.md |
后端接口清单 |
backend-api/API_TEST_REPORT.md |
接口测试报告 |
DDL.txt |
数据库建表语句 |
data-collector/src/main/scala/.../DWSToADSDirect.scala |
ADS 层代码(质量评分公式) |
data-collector/src/main/scala/.../ETLContext.scala |
ETL 共享上下文 |
更多推荐

所有评论(0)