CodeQ 后端开发日志(8)—— ETL 自动化调度与质量报告持久化:全链路打通
项目:CodeQ 代码大模型训练语料质量评估平台
开发模块:SparkJobService ETL 任务编排、QualityReportController 报告保存接口、全链路自动化闭环
当前进度:Spark ETL 子进程调度、任务状态跟踪、自动触发 AI 评估、质量报告 JPA 持久化全部完成,全自动化链路代码层面验证通过。
一、任务与目标
在此之前,ETL 数据采集、AI 评估、报告写入是三个独立的手动步骤。本次目标是实现一条 /api/etl/submit?layer=all 请求触发从数据采集到报告入库的全自动流程,同时解决一个关键技术问题——中文文本通过外部管道写入 MySQL 出现乱码。
二、SparkJobService 任务编排
核心设计是"父进程调度子进程"模式:backend-api 收到 ETL 请求后,通过 ProcessBuilder 启动 fat jar 子进程执行 Spark ETL,并在子进程退出后自动触发 AI 评估。
任务生命周期管理 :
public enum JobStatus { SUBMITTED, RUNNING, SUCCEEDED, FAILED, UNKNOWN }
每个任务分配唯一 UUID jobId,状态存储在 ConcurrentHashMap 中,支持通过 /api/etl/status/{jobId} 实时轮询。
子进程启动逻辑 :
List<String> cmd = new ArrayList<>();
cmd.add("java");
cmd.add("-jar");
cmd.add(resolvedJar);
cmd.add("--layer");
cmd.add(info.layer);
cmd.add("--limit");
cmd.add("10");
cmd.add("--max-size-mb");
cmd.add("500");
ProcessBuilder pb = new ProcessBuilder(cmd);
Map<String, String> env = pb.environment();
env.put("GITHUB_TOKEN", githubToken);
env.put("MYSQL_PASSWORD", mysqlPassword);
pb.redirectErrorStream(true);
关键设计决策:
- --limit 10 --max-size-mb 500 :默认只扫描 3 个仓库、限制 100MB 会导致热门 Java 仓库(如 hello-algo 453MB)全部被跳过。放宽参数保证代码解析步骤能实际产出数据。
- 环境变量注入 :GITHUB_TOKEN 和 MYSQL_PASSWORD 从父进程环境传递给子进程,避免硬编码。
- stderr 合并到 stdout : redirectErrorStream(true) 确保错误日志能完整捕获到 errorLog 字段。
成功后自动触发 AI ( triggerAgentAnalysis 方法):
1. 查询最新 analysis_task 记录
2. 从 metric_result 读取汇总指标
3. 从 file_metric 读取文件级详情(Top 5 高复杂度文件)
4. 调用 llmService.evaluateRepository() 生成评估
5. 通过 JPA 写入 quality_report 表
三、QualityReportController POST 保存接口
新增 POST /api/quality-report/save 接口,接收完整报告 JSON:
@PostMapping("/save")
public R<QualityReport> save(@RequestBody QualityReport report) {
return R.success(qualityReportService.save(report));
}
这个接口解决了一个隐蔽但关键的问题——中文写入 MySQL 出现乱码。
问题定位过程 :
- 测试阶段通过 PowerShell mysql -e "INSERT ..." 管道写入含中文的 quality_report
- DBeaver 查询显示所有中文字符变为 ?
- HEX 检查确认每个中文字节都被替换为 3F ( ? 的 ASCII 码)
- 多次尝试设置 [Console]::OutputEncoding = UTF8 、 charset utf8mb4 均无效
根本原因 :Windows PowerShell 的管道机制在处理非 ASCII 字符时,会将字节流按照当前控制台编码(通常是 GBK)重新解释,导致 UTF-8 中文被破坏。
解决方案 :不经过外部管道,由 backend-api 的 JPA(Hibernate)直接连接 MySQL 执行 INSERT。Spring Boot + Hibernate 的 JDBC 连接默认使用 UTF-8 编码,与数据库的 utf8mb4 字符集一致,中文传输路径完全在 Java 进程内,不经过 Shell。
验证结果——HEX 对比:
错误(管道):5B42 3F ... → [B?
正确(JPA):5B42 E7BAA7 ... → [B级
四、全链路数据流
完整的自动化流程:
POST /api/etl/submit?layer=all
↓
SparkJobService.submitJob()
↓ ProcessBuilder 启动子进程
java -jar fat.jar --layer all --limit 10 --max-size-mb 500
↓ [1/7] GitHub API 采集 → ods_github_api_raw
↓ [2/7] JSON 解析 → dwd_repo_detail
↓ [3/7] 贡献者采集
↓ [4/7] Issue 采集
↓ [5/7] 代码解析 → dwd_file_metric_detail
↓ [6/7] DWS 聚合 → metric_result
↓ [7/7] ADS 评分 → file_metric
↓ exitCode = 0
SparkJobService.triggerAgentAnalysis()
↓ 读取 metric_result + file_metric
↓ 调用 LLM API(带重试)
↓ JPA 写入 quality_report(中文无乱码)
五、接口测试与验证
启动 backend-api 后执行测试:
POST /api/etl/submit?layer=all
→ jobId: c41b4acb, status: SUBMITTED
GET /api/etl/status/c41b4acb
→ status: SUCCEEDED
查询 quality_report 表:
→ 中文完整显示:summary = "[B级 | Medium | 78分] Arthas 是高影响力..."
→ advantages = ["高 star 数量,广泛行业认可", ...]
→ problems = ["文档得分极低,严重缺乏说明", ...]
→ suggestions = ["立即启动文档重构", ...]
六、技术理解
本次全链路自动化开发的几个关键收获:
1. 父进程调度子进程 是独立模块间协作的经典模式——backend-api 不需要引入 Spark 依赖,只需管理进程生命周期和退出码判断。
2. 编码问题往往不是数据库的问题 。MySQL 的 utf8mb4 完全能存储中文,问题出在传输路径上(Shell 管道 vs JPA 直连)。
3. 后端自动化逻辑的判断应该保守 ——ETL exit code != 0 时不触发 AI、metric_result 为空时跳过评估,避免产生误导性报告。
4. 状态机设计要简单 :SUBMITTED → RUNNING → SUCCEEDED/FAILED,没有过度设计的状态转换,用 ConcurrentHashMap 做内存存储足够。
七、当前项目进度
- SparkJobService 完整实现(子进程调度 + 状态跟踪)
- --limit 10 --max-size-mb 500 参数传递修复
- ETL 成功后自动触发 AI 评估
- QualityReportController 新增 POST /save 接口
- 中文质量报告 JPA 写入无乱码
- 全链路自动化代码层面打通
更多推荐
所有评论(0)