创新实训8——后端开发指南

文档版本: 2026-06-07
背景:数据部分开发完成,暴露了SparkSubmit的能力,需要由后端工程师将Spark提交和agent分析整合。特整理此文档,让后端工程师了解仓库现状
适用对象: 接手后端开发的工程师(阅读本文档前请先完整阅读 CLAUDE.mdREADME.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 已知问题

  1. 首次调用往往返回 500(DashScope 瞬态错误),重试即正常。OpenAIServiceImpl 中需加重试逻辑。
  2. info 字段需手动传,当前不会自动从 MySQL 读取 metric_result 数据。
  3. 结果不持久化 — 返回给前端后即丢弃,不写入 quality_report 表。

六、P0 任务:ETL 完成后自动 AI 评估 + 写入 quality_report

6.1 任务描述

当前 SparkJobService.triggerAgentAnalysis() (第178行) 在 ETL 成功后调用,但只有骨架代码(只打日志)。需要实现:

  1. ETL 完成后,从 DB 读取 metric_result + 相关指标
  2. 组装 repoInfo 字符串
  3. 调用 LLMService.evaluateRepository()
  4. 将评估结果写入 quality_report

6.2 关键代码位置

需要知道的 文件:行号
ETL 完成后调用的方法 SparkJobService.java:178triggerAgentAnalysis(JobInfo info)
LLM 评估方法签名 LLMService.javaevaluateRepository(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 需要的改动

  1. SparkJobService.java — 注入 MetricResultRepositoryFileMetricRepositoryQualityReportRepository
  2. MetricResultRepository.java — 可能需要加 findByTaskId(Long taskId) 方法(检查是否存在,不存在则添加)
  3. FileMetricRepository.java — 可能需要加 findByTaskId(Long taskId) 方法(同上)
  4. QualityReportRepository.java — 检查是否有 findByTaskId 方法

七、P0 任务:自动从 DB 读指标

7.1 任务描述

当前 POST /api/quality/evaluate 需要前端传 info 字段(包含指标文本)。应改为:后端收到 { repository: "owner/repo" } 后,自动从 MySQL 读取该仓库的 metric_resultfile_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

十二、常见陷阱

  1. 不要在 Spring Boot JVM 内嵌 SparkSession — 依赖冲突(logback/jackson 版本不一致),始终用子进程方式(SparkJobService 已正确实现为启子进程)。

  2. quality/evaluate 首次调用 500 — DashScope API 偶发瞬态错误,需加重试(如重试 1-2 次,间隔 1 秒)。

  3. Redis 不是强依赖 — 但 /api/repo/{id}/api/task/{id} 会因 Redis 异常而返回 500,需加 try-catch 降级。

  4. metric_result 是单条全量聚合 — 所有仓库合并为一条记录。如果要按仓库查评分,需要改 ETL 的 ADS 层 DWSToADSStep.computeMetricResults() 的 GROUP BY 逻辑。

  5. quality_report 表已就绪 — 实体、Repository 都已存在,只差写入代码。

  6. application.propertiesspark.etl.main-class — 已从 UnifiedETLJob 修正为 ETLJobobject ETLJob 定义在 UnifiedETLJob.scala 文件中,文件名未改)。

  7. MySQL 密码散落多处application.propertiesAppConfig.scalaSparkJobService.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 共享上下文
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐