fix(aihr): bind candidate interview sessions to owner
This commit is contained in:
+2
-2
@@ -51,12 +51,12 @@ public class AihrInterviewController {
|
||||
|
||||
@PostMapping("/answer")
|
||||
public R<AnswerResponse> answer(@RequestBody AnswerRequest request) {
|
||||
return R.ok(interviewService.answer(request));
|
||||
return R.ok(interviewService.answer(request, currentAppUsername()));
|
||||
}
|
||||
|
||||
@PostMapping("/finish")
|
||||
public R<FinishResponse> finish(@RequestBody FinishRequest request) {
|
||||
return R.ok(interviewService.finish(request));
|
||||
return R.ok(interviewService.finish(request, currentAppUsername()));
|
||||
}
|
||||
|
||||
@GetMapping("/records")
|
||||
|
||||
+30
-7
@@ -64,8 +64,15 @@ public class AihrInterviewService {
|
||||
}
|
||||
|
||||
public AnswerResponse answer(AnswerRequest request) {
|
||||
return answer(request, "");
|
||||
}
|
||||
|
||||
public AnswerResponse answer(AnswerRequest request, String ownerIdentity) {
|
||||
String answerText = request == null ? "" : clean(request.answerText());
|
||||
InterviewSession session = sessions.get(request == null ? null : request.sessionId());
|
||||
if (!isBlank(ownerIdentity)) {
|
||||
requireSessionOwner(session, ownerIdentity);
|
||||
}
|
||||
if (session != null && request != null && !isBlank(request.questionId())) {
|
||||
session.answers().put(request.questionId(), answerText);
|
||||
}
|
||||
@@ -78,14 +85,24 @@ public class AihrInterviewService {
|
||||
}
|
||||
|
||||
public FinishResponse finish(FinishRequest request) {
|
||||
return finish(request, "");
|
||||
}
|
||||
|
||||
public FinishResponse finish(FinishRequest request, String ownerIdentity) {
|
||||
String sessionKey = request == null || isBlank(request.sessionId()) ? "iv-" + UUID.randomUUID() : clean(request.sessionId());
|
||||
InterviewSession session = sessions.computeIfAbsent(
|
||||
sessionKey,
|
||||
key -> {
|
||||
CandidateProfile candidate = resolveCandidate(null, null, null, key);
|
||||
return new InterviewSession(candidate, candidate.questions(), new ConcurrentHashMap<>(), "local-rubric");
|
||||
}
|
||||
);
|
||||
InterviewSession session;
|
||||
if (isBlank(ownerIdentity)) {
|
||||
session = sessions.computeIfAbsent(
|
||||
sessionKey,
|
||||
key -> {
|
||||
CandidateProfile candidate = resolveCandidate(null, null, null, key);
|
||||
return new InterviewSession(candidate, candidate.questions(), new ConcurrentHashMap<>(), "local-rubric");
|
||||
}
|
||||
);
|
||||
} else {
|
||||
session = sessions.get(sessionKey);
|
||||
requireSessionOwner(session, ownerIdentity);
|
||||
}
|
||||
if (request != null && request.answers() != null) {
|
||||
request.answers().forEach((questionId, answer) -> {
|
||||
if (!isBlank(questionId)) {
|
||||
@@ -570,6 +587,12 @@ public class AihrInterviewService {
|
||||
return candidates.get("zhang-ming");
|
||||
}
|
||||
|
||||
private static void requireSessionOwner(InterviewSession session, String ownerIdentity) {
|
||||
if (session == null || !clean(ownerIdentity).equals(clean(session.candidate().id()))) {
|
||||
throw new IllegalArgumentException("面试会话不存在或无权访问");
|
||||
}
|
||||
}
|
||||
|
||||
private CandidateProfile dynamicCandidate(String candidateId, String candidateName, String positionCode) {
|
||||
CandidateProfile template = candidates.get("zhang-ming");
|
||||
String cleanId = clean(candidateId);
|
||||
|
||||
+25
@@ -3,6 +3,7 @@ package org.dromara.aihr.service;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.aihr.domain.AihrInterviewDto.CandidateLinkRequest;
|
||||
import org.dromara.aihr.domain.AihrInterviewDto.CandidateLinkResponse;
|
||||
import org.dromara.aihr.domain.AihrInterviewDto.AnswerRequest;
|
||||
import org.dromara.aihr.domain.AihrInterviewDto.FinishRequest;
|
||||
import org.dromara.aihr.domain.AihrInterviewDto.RecordResponse;
|
||||
import org.dromara.aihr.domain.AihrInterviewDto.ReviewRequest;
|
||||
@@ -174,4 +175,28 @@ class AihrInterviewServiceTest {
|
||||
assertEquals("待复核", record.reviewStatus());
|
||||
assertEquals(record.score(), record.finalScore());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("dev")
|
||||
void appInterviewSessionRejectsForeignAndUnknownOwner() {
|
||||
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
|
||||
AihrModelSeedService modelService = mock(AihrModelSeedService.class);
|
||||
when(modelService.tryChat(anyString(), anyString(), anyDouble())).thenReturn(Optional.empty());
|
||||
AihrInterviewService service = new AihrInterviewService(new ObjectMapper(), modelService, jdbcTemplate);
|
||||
|
||||
var ownerSession = service.start(new StartRequest("candidate-a", "候选人A", "customer-service", "mobile"));
|
||||
var otherSession = service.start(new StartRequest("candidate-b", "候选人B", "customer-service", "mobile"));
|
||||
|
||||
IllegalArgumentException foreign = assertThrows(IllegalArgumentException.class, () -> service.answer(
|
||||
new AnswerRequest(otherSession.sessionId(), "q1", "越权回答", null), "candidate-a"
|
||||
));
|
||||
assertEquals("面试会话不存在或无权访问", foreign.getMessage());
|
||||
|
||||
IllegalArgumentException unknown = assertThrows(IllegalArgumentException.class, () -> service.finish(
|
||||
new FinishRequest("iv-not-started", Map.of("q1", "越权完成")), "candidate-a"
|
||||
));
|
||||
assertEquals("面试会话不存在或无权访问", unknown.getMessage());
|
||||
|
||||
service.answer(new AnswerRequest(ownerSession.sessionId(), "q1", "本人回答", null), "candidate-a");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user