TL;DR

今天把作业提交从「必须注册登录」改成了「游客模式(文件名解析)」,同时修了一堆连环 bug,包括 JSON 解析、数据库约束、双重拦截、重复提交等。本文记录踩坑过程和修复思路。


背景

作业批改系统原先是老师端内部使用的,学生需要注册登录后才能提交作业。需求改成:学生通过文件名直接上传作业,无需注册登录。

文件名格式:姓名_班级_作业名称.扩展名


问题 1:extractJsonFromMarkdown 提取不完整

现象

前端提交后,submission 表的 totalScore 为 null。数据库 INSERT 时 total_score 列没有值。

排查

OpenClaw 返回的响应包含 markdown 前缀文本 + ```json 包裹的 JSON:

1
2
3
4
根据批改作业助手的技能指南,我现在对...进行批改。

```json
{"totalScore": 72, ...}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

旧版 `extractJsonFromMarkdown` 只去掉首尾 ``` 标记,**没有裁剪前缀文本**,导致 `ObjectMapper` 解析失败,走了 catch 分支只存了 `raw_response`。

### 解决

改进提取逻辑,先找到第一个 `{` 和最后一个 `}`,只提取 JSON 部分:

```java
private String extractJsonFromMarkdown(String text) {
if (text == null) return null;
// 找到第一个 { 和最后一个 }
int start = text.indexOf('{');
int end = text.lastIndexOf('}');
if (start != -1 && end > start) {
return text.substring(start, end + 1);
}
return text;
}

问题 2:knowledgePoints 未设置 status 默认值

现象

OpenClaw 返回的 JSON 中 knowledgePoints 数组使用了 status 字段(值为 "掌握""薄弱" 等),但代码中 saveKnowledgePoints 没有处理这些值,导致批量插入时该字段为 null。

解决

KnowledgeDTO 中确认字段映射正确(JSON 中有 status 字段),代码直接使用 JSON 中的值。


问题 3:homework_knowledge 表 evaluation_id 非空约束

现象

JSON 解析成功后执行 saveSubmission,插入 submission 成功,但在插入 homework_knowledge 时报错:

1
null value in column "evaluation_id" of relation "homework_knowledge" violates not-null constraint

新数据使用 submission_id 关联,不再依赖旧表的 evaluation_id

解决

在 V7 migration 中添加:

1
ALTER TABLE homework_knowledge ALTER COLUMN evaluation_id DROP NOT NULL;

这样新提交的知识点记录只传 submission_id 即可。


问题 4:前端的重复提交

现象

用户在页面上点击了一次提交后觉得没反应,又点了一次,数据库出现两条相同记录:一条有分数,一条为 null(因为第一次成功,第二次的 knowledge 插入异常走了 catch 分支)。

排查

homework_knowledge 表的 evaluation_id 字段有 NOT NULL 约束,但 V7 migration 只加了 submission_id 字段,没把 evaluation_id 改成可空。

解决

1
2
DELETE FROM submission WHERE total_score IS NULL;
ALTER TABLE homework_knowledge ALTER COLUMN evaluation_id DROP NOT NULL;

问题 5:仪表盘学生数量统计源错误

现象

学生端作业提交成功后,仪表盘上显示的学生数量为 0,因为统计仍只查 sys_user 表(教师管理系统用户)。

分析

DashboardServiceImpl.getMetrics() 中的 totalStudents 只有这一行:

1
Integer studentCount = userMapper.countStudentsByClassId(classId);

现在 sys_user 只存老师用户,学生直接通过文件名上传,数据在 submission 表中。

解决

1
2
3
// 学生总数 = submission 表独立提交者
Integer studentCount = submissionMapper.countDistinctStudentsByClassId(classId);
metrics.setTotalStudents(studentCount != null ? studentCount : 0);
1
2
3
// SubmissionMapper
@Select("SELECT COUNT(DISTINCT student_name) FROM submission WHERE class_id = #{classId}")
Integer countDistinctStudentsByClassId(@Param("classId") Long classId);

总结

今天的改动核心是把作业提交改为游客模式,过程中暴露了几个典型的全栈坑:

层面 问题 根因类型
数据 JSON 提取不完整 字符串处理不够健壮
数据库 旧表约束不兼容新逻辑 表设计未考虑 Schema 变化
前端 重复请求 evaluation_id 字段有 NOT NULL 约束
统计 计数值来源错误 业务逻辑未随数据源变化更新

教训:需求改动时要通盘考虑认证、数据处理、数据库约束、前端交互四个层面,缺一不可。