fix(aihr): bind practice sessions to owner

This commit is contained in:
2026-07-14 10:05:07 +08:00
parent cd756150a1
commit 4f96df27d2
6 changed files with 62 additions and 4 deletions
@@ -131,12 +131,12 @@ public class AihrPracticeController {
@PostMapping("/turn")
public R<TurnResponse> turn(@RequestBody TurnRequest request) {
return R.ok(practiceSeedService.turn(request));
return R.ok(practiceSeedService.turn(request, currentAppUsername()));
}
@PostMapping("/finish")
public R<FinishResponse> finish(@RequestBody FinishRequest request) {
return R.ok(practiceSeedService.finish(request));
return R.ok(practiceSeedService.finish(request, currentAppUsername()));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@@ -494,8 +494,15 @@ public class AihrPracticeSeedService {
}
public TurnResponse turn(TurnRequest request) {
return turn(request, "");
}
public TurnResponse turn(TurnRequest request, String ownerIdentity) {
String style = normalizeRegenerateStyle(request == null ? null : request.style());
ActiveSession session = request == null || isBlank(request.sessionId()) ? null : activeSessions.get(request.sessionId());
if (!isBlank(ownerIdentity)) {
requireSessionOwner(session, ownerIdentity);
}
ScenarioSeed scenario = resolveScenario(session == null ? null : session.scenarioId(), request == null ? null : request.sessionId());
int roundIndex = normalizeRoundIndex(request == null ? null : request.roundIndex(), scenario.rounds().size());
if (request != null && Boolean.TRUE.equals(request.regenerate())) {
@@ -630,8 +637,19 @@ public class AihrPracticeSeedService {
}
public FinishResponse finish(FinishRequest request) {
return finish(request, "");
}
public FinishResponse finish(FinishRequest request, String ownerIdentity) {
String sessionId = request == null ? null : request.sessionId();
ActiveSession activeSession = sessionId == null ? null : activeSessions.remove(sessionId);
ActiveSession activeSession;
if (isBlank(ownerIdentity)) {
activeSession = sessionId == null ? null : activeSessions.remove(sessionId);
} else {
ActiveSession existing = sessionId == null ? null : activeSessions.get(sessionId);
requireSessionOwner(existing, ownerIdentity);
activeSession = activeSessions.remove(sessionId);
}
ScenarioSeed scenario = resolveScenario(activeSession == null ? null : activeSession.scenarioId(), sessionId);
String trainee = activeSession == null ? scenario.trainee() : activeSession.trainee();
PracticeResult result = evaluate(activeSession, scenario);
@@ -1896,6 +1914,14 @@ public class AihrPracticeSeedService {
return request.extPartyId().trim();
}
private static void requireSessionOwner(ActiveSession session, String ownerIdentity) {
String owner = ownerIdentity == null ? "" : ownerIdentity.trim();
String sessionOwner = session == null || session.extPartyId() == null ? "" : session.extPartyId().trim();
if (session == null || !owner.equals(sessionOwner)) {
throw new ServiceException("训练会话不存在或无权访问");
}
}
private String resolveTrainee(StartRequest request, ScenarioSeed scenario) {
if (!isMobile(request) || request.extPartyId() == null || request.extPartyId().isBlank()) {
return scenario.trainee();
@@ -2,8 +2,11 @@ package org.dromara.aihr.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.aihr.domain.AihrPracticeDto.CompetencyResponse;
import org.dromara.aihr.domain.AihrPracticeDto.FinishRequest;
import org.dromara.aihr.domain.AihrPracticeDto.PracticeAssignmentRequest;
import org.dromara.aihr.domain.AihrPracticeDto.ReviewAdviceRequest;
import org.dromara.aihr.domain.AihrPracticeDto.StartRequest;
import org.dromara.aihr.domain.AihrPracticeDto.TurnRequest;
import org.dromara.common.core.exception.ServiceException;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -106,6 +109,31 @@ public class AihrPracticeSeedServiceTest {
assertEquals("缺少员工ID,无法派发专项训练", error.getMessage());
}
@Test
public void mobilePracticeSessionRejectsForeignAndUnknownOwner() {
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
when(jdbcTemplate.query(contains("SELECT scenario_code"), any(RowMapper.class), eq("000000"), eq("complaint-water")))
.thenReturn(List.of());
AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null);
var ownerSession = service.start(new StartRequest("employee-a", "complaint-water", "mobile", null));
var otherSession = service.start(new StartRequest("employee-b", "complaint-water", "mobile", null));
ServiceException foreign = assertThrows(ServiceException.class, () -> service.turn(
new TurnRequest(otherSession.sessionId(), 0, "越权回答", null, null, false, null), "employee-a"
));
assertEquals("训练会话不存在或无权访问", foreign.getMessage());
ServiceException unknown = assertThrows(ServiceException.class, () -> service.finish(
new FinishRequest("seed-not-started", null), "employee-a"
));
assertEquals("训练会话不存在或无权访问", unknown.getMessage());
ServiceException missing = assertThrows(ServiceException.class, () -> service.finish(
new FinishRequest(ownerSession.sessionId(), null), "employee-other"
));
assertEquals("训练会话不存在或无权访问", missing.getMessage());
}
@Test
public void createAssignmentRejectsOutOfProjectAssignee() {
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);