← 全部文章

分数链路全链路现状分析与修改方案

分数链路全链路现状分析与修改方案

本文目录 10 节
分数链路全链路现状分析与修改方案封面

背景

  • 练习场景:需要向用户展示得分(OI 计分,passedCases / totalCases 或 caseScores 加权)
  • OI 比赛场景:需要向用户展示得分,并用于排行榜
  • ACM 比赛场景:不向用户展示分数,只关心是否 AC

一、数据模型现状(均已就绪,无需改动)

类字段状态
QuestionJudgeConfig(judge服务 & 主服务)caseScores: List<Integer>✅ 已有
QuestionSubmitJudgeInfo(judge服务 & 主服务)earnedScore, maxScore, passedCaseCount, totalCaseCount✅ 已有
StatsMessage(judge服务 & 主服务)earnedScore, maxScore, passedCases, totalCases✅ 已有
QuestionSubmit.judgeInfoJSON 列,存储整个 QuestionSubmitJudgeInfo✅ 已有

二、链路各节点现状分析

节点 1:DefaultJudgeStrategy.doJudge() ❌ 缺失

文件:backend-judge-service/.../judge/strategy/DefaultJudgeStrategy.java

现状:

  • 已正确统计 passedCaseCount / totalCaseCount
  • judgeConfig.getCaseScores() 已传入但从未读取
  • 返回的 QuestionSubmitJudgeInfo 中 earnedScore / maxScore 永远为 null

修改方案:在判题循环中,按测试点是否通过累加分数:

// 在循环前初始化
List<Integer> caseScores = judgeConfig.getCaseScores();
int earnedScore = 0;
int maxScore = 0;

// 在每个测试点处理时
int caseScore = (caseScores != null && i < caseScores.size()) ? caseScores.get(i) : 1;
maxScore += caseScore;
if (/* 该测试点通过 */) {
    earnedScore += caseScore;
}

// 构建返回值时
.earnedScore(earnedScore)
.maxScore(maxScore)

节点 2:JudgeServiceImpl:判题结果回写 DB ⚠️ 依赖节点1

文件:backend-judge-service/.../service/impl/JudgeServiceImpl.java,第 152 行

updateSubmitStatus(questionSubmitId, finalStatus, judgeInfo);

现状:

  • 将整个 judgeInfo 对象作为 JSON 写入 question_submit.judge_info
  • 节点 1 修好后,earnedScore / maxScore 会自动随 JSON 落库
  • 本节点无需单独修改

节点 3:JudgeServiceImpl.sendContestStatsMessage() ❌ 缺失

文件:backend-judge-service/.../service/impl/JudgeServiceImpl.java,第 535-546 行

现状:发送 MQ 消息时未填 earnedScore / maxScore:

StatsMessage.builder()
    .passedCases(judgeInfo.getPassedCaseCount())
    .totalCases(judgeInfo.getTotalCaseCount())
    // ❌ 缺少:
    // .earnedScore(judgeInfo.getEarnedScore())
    // .maxScore(judgeInfo.getMaxScore())
    .build()

修改方案:补上两个字段:

.earnedScore(judgeInfo.getEarnedScore())
.maxScore(judgeInfo.getMaxScore())

注意:此处仅在 scene == CONTEST 时发送,练习场景不走 MQ,分数直接存 DB 即可。


节点 4:StatsConsumer → OI 排行 ⚠️ 依赖节点3

文件:backend-tyut-oj/.../mq/StatsConsumer.java,第 87-112 行

现状:

int passedCases = message.getPassedCases() != null ? message.getPassedCases() : 0;
int totalCases  = message.getTotalCases()  != null ? message.getTotalCases()  : 0;
// ...
contestRankService.updateOIRanking(
        message.getContestId(), message.getUserId(),
        message.getQuestionId(), passedCases, totalCases);  // ❌ 用 case 数而非分数

修改方案:优先使用 earnedScore / maxScore,回退到 passedCases / totalCases:

int earnedScore = message.getEarnedScore() != null ? message.getEarnedScore() : passedCases;
int maxScore    = message.getMaxScore()    != null ? message.getMaxScore()    : totalCases;
contestRankService.updateOIRanking(
        message.getContestId(), message.getUserId(),
        message.getQuestionId(), earnedScore, maxScore);

ACM 赛制路径(updateACMRanking)不涉及分数,无需改动。


三、前端展示策略

场景是否展示分数数据来源
练习✅ 展示questionSubmit.judgeInfo.earnedScore / maxScore
OI 比赛✅ 展示同上
ACM 比赛❌ 不展示只看 status == ACCEPTED

前端判断逻辑:根据比赛的 type 字段(ACM / OI)决定是否渲染分数区域。练习提交无 contestId,默认展示分数。


四、修改清单(按优先级)

优先级文件改动影响范围
1DefaultJudgeStrategy.java计算并填充 earnedScore/maxScore所有场景的分数落库
2JudgeServiceImpl.java(sendContestStatsMessage)MQ 消息补填两个分数字段OI 比赛排行
3StatsConsumer.javaupdateOIRanking 改用分数而非 case 数OI 比赛排行
4前端提交详情组件根据赛制决定是否展示分数用户界面

节点 2(DB 回写)无需修改,节点 1 修好后自动生效。


五、向下兼容说明

  • caseScores == null 时:earnedScore = passedCaseCount,maxScore = totalCaseCount,退化为等权计分,存量数据行为不变
  • ACM 赛制排行逻辑完全不读取分数字段,不受任何影响

评论