fix(aihr): protect daily drill submissions

This commit is contained in:
2026-07-14 11:23:26 +08:00
parent 88702867b8
commit 83161569d1
4 changed files with 17 additions and 2 deletions
@@ -204,7 +204,7 @@ public class AihrMobileController {
@PostMapping("/practice/assignments/{id}/answer") @PostMapping("/practice/assignments/{id}/answer")
public R<DailyDrillAnswerResponse> answerDailyDrill(@PathVariable Long id, @RequestBody(required = false) DailyDrillAnswerRequest request) { public R<DailyDrillAnswerResponse> answerDailyDrill(@PathVariable Long id, @RequestBody(required = false) DailyDrillAnswerRequest request) {
DailyDrillAnswerResponse response = mobileSeedService.answerDailyDrill(id, request, ownMobileExtPartyId(null)); DailyDrillAnswerResponse response = mobileSeedService.answerDailyDrill(id, request, ownMobileExtPartyId(null));
return response == null ? R.fail("每日三题不存在") : R.ok(response); return response == null ? R.fail("每日三题不存在或已完成") : R.ok(response);
} }
@PostMapping("/practice/satisfaction") @PostMapping("/practice/satisfaction")
@@ -1913,6 +1913,7 @@ public class AihrPracticeSeedService {
SELECT id, reason, source, status, scenario_id SELECT id, reason, source, status, scenario_id
FROM aihr_practice_assignment FROM aihr_practice_assignment
WHERE tenant_id = ? AND id = ? AND source = 'daily' WHERE tenant_id = ? AND id = ? AND source = 'daily'
AND status = '待训练'
AND (? = '' OR ext_party_id = ?) AND (? = '' OR ext_party_id = ?)
LIMIT 1 LIMIT 1
""", tenantId(), id, party, party); """, tenantId(), id, party, party);
@@ -1935,7 +1936,7 @@ public class AihrPracticeSeedService {
correct = ?, correct = ?,
finished_time = ?, finished_time = ?,
update_time = ? update_time = ?
WHERE tenant_id = ? AND id = ? AND source = 'daily' WHERE tenant_id = ? AND id = ? AND source = 'daily' AND status = '待训练'
""", """,
answer, answer,
audioUrl, audioUrl,
@@ -120,6 +120,19 @@ public class AihrPracticeSeedServiceTest {
assertFalse(AihrPracticeSeedService.isWithinNewHireWindow(null, asOf)); assertFalse(AihrPracticeSeedService.isWithinNewHireWindow(null, asOf));
} }
@Test
public void dailyDrillAnswerDoesNotOverwriteCompletedAttempt() throws Exception {
String source = Files.readString(Path.of("src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java"));
int methodStart = source.indexOf("public DailyDrillAnswerResponse answerDailyDrill");
int methodEnd = source.indexOf("public List<PromptTemplateResponse> promptTemplates", methodStart);
assertTrue(methodStart >= 0 && methodEnd > methodStart);
String method = source.substring(methodStart, methodEnd);
String statusGuard = "status = '待训练'";
assertTrue(method.contains("AND " + statusGuard));
assertTrue(method.indexOf("AND " + statusGuard) != method.lastIndexOf("AND " + statusGuard));
}
@Test @Test
public void mobilePracticeSessionRejectsForeignAndUnknownOwner() { public void mobilePracticeSessionRejectsForeignAndUnknownOwner() {
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
+1
View File
@@ -175,6 +175,7 @@
- 2026-07-14 BRD G3 移动首页权限复核:发现公开首页方法对已登录 SYS 用户仍可能调用全局统计分支;现要求只有 APP 用户且身份非空才读取员工/主管动态数据,后台系统用户和异常身份统一返回无业务统计的公开 seed。AIHR 全量测试 `70/70` 通过,未修改生产环境。 - 2026-07-14 BRD G3 移动首页权限复核:发现公开首页方法对已登录 SYS 用户仍可能调用全局统计分支;现要求只有 APP 用户且身份非空才读取员工/主管动态数据,后台系统用户和异常身份统一返回无业务统计的公开 seed。AIHR 全量测试 `70/70` 通过,未修改生产环境。
- 2026-07-14 BRD G3 音频读取权限复核:发现移动端音频下载接口对已登录 SYS 用户未校验角色,普通后台账号理论上可枚举 `ossId` 读取训练录音;现复用 `superadmin/hr_operator` 门禁,APP 用户继续按本人/主管项目范围校验,其他身份返回 403。AIHR 全量测试 `70/70` 通过,未修改生产环境。 - 2026-07-14 BRD G3 音频读取权限复核:发现移动端音频下载接口对已登录 SYS 用户未校验角色,普通后台账号理论上可枚举 `ossId` 读取训练录音;现复用 `superadmin/hr_operator` 门禁,APP 用户继续按本人/主管项目范围校验,其他身份返回 403。AIHR 全量测试 `70/70` 通过,未修改生产环境。
- 2026-07-14 BRD 4.4 每日三题资格补强:每日派题现在在组织快照存在 `hire_date` 列时按“入职日期至今不超过 3 个月”判断,边界按自然月且包含当天;旧 Demo 快照没有该列时继续使用“移动端训练记录少于 3 次”的兼容回退。正式试点仍需在外部组织同步落地并校验 `hire_date`,不能把回退口径当成正式新员工资格。 - 2026-07-14 BRD 4.4 每日三题资格补强:每日派题现在在组织快照存在 `hire_date` 列时按“入职日期至今不超过 3 个月”判断,边界按自然月且包含当天;旧 Demo 快照没有该列时继续使用“移动端训练记录少于 3 次”的兼容回退。正式试点仍需在外部组织同步落地并校验 `hire_date`,不能把回退口径当成正式新员工资格。
- 2026-07-14 BRD 4.4 每日三题提交幂等复核:前端虽已禁用已完成题,后端原先仍允许重复 POST 覆盖答案、反馈和音频证据;现要求查询和更新均只接受 `status='待训练'`,已完成记录保留首次提交,重复请求按“不存在或已完成”处理。
- 2026-07-14 BRD G3 候选资料审核状态复核:候选人资料审核接口此前在空请求体时默认写回“待审核”,可能覆盖已有“已通过/已驳回”状态;现要求审核动作显式提交三态之一,空值和未知状态直接拒绝,避免后台误操作回退审核结论。 - 2026-07-14 BRD G3 候选资料审核状态复核:候选人资料审核接口此前在空请求体时默认写回“待审核”,可能覆盖已有“已通过/已驳回”状态;现要求审核动作显式提交三态之一,空值和未知状态直接拒绝,避免后台误操作回退审核结论。
- 2026-07-14 BRD 案例署名复核:案例库已有负责人字段,但此前上传、整理和入库记录都硬编码为“培训组”,无法追溯案例贡献者;现复用既有 `owner` 字段保存首次上传身份,整理和入库阶段保留原署名,手机号身份仅显示员工末四位,避免把原始手机号写入案例展示。 - 2026-07-14 BRD 案例署名复核:案例库已有负责人字段,但此前上传、整理和入库记录都硬编码为“培训组”,无法追溯案例贡献者;现复用既有 `owner` 字段保存首次上传身份,整理和入库阶段保留原署名,手机号身份仅显示员工末四位,避免把原始手机号写入案例展示。
- 2026-07-14 BRD 候选人关联完整性复核:候选人—员工关联接口此前只校验员工主体在职,允许给不存在的任意 `candidateId` 建立入职关联;现要求候选人必须来自内置候选档案、活动面试会话或当前租户已持久化面试记录,保留已有幂等关联和历史数据兼容。 - 2026-07-14 BRD 候选人关联完整性复核:候选人—员工关联接口此前只校验员工主体在职,允许给不存在的任意 `candidateId` 建立入职关联;现要求候选人必须来自内置候选档案、活动面试会话或当前租户已持久化面试记录,保留已有幂等关联和历史数据兼容。