fix(aihr): enforce practice audio ownership
This commit is contained in:
+9
-1
@@ -204,7 +204,7 @@ public class AihrMobileController {
|
||||
|
||||
@PostMapping("/practice/assignments/{id}/answer")
|
||||
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), currentAppUserId());
|
||||
return response == null ? R.fail("每日三题不存在或已完成") : R.ok(response);
|
||||
}
|
||||
|
||||
@@ -255,6 +255,14 @@ public class AihrMobileController {
|
||||
return username == null ? "" : username.trim();
|
||||
}
|
||||
|
||||
private static Long currentAppUserId() {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (loginUser == null || !UserType.APP_USER.getUserType().equals(loginUser.getUserType())) {
|
||||
return null;
|
||||
}
|
||||
return loginUser.getUserId();
|
||||
}
|
||||
|
||||
private static ReviewAdviceRequest bindReviewOperator(ReviewAdviceRequest request) {
|
||||
return new ReviewAdviceRequest(
|
||||
currentOperatorUsername(),
|
||||
|
||||
+9
-1
@@ -131,7 +131,7 @@ public class AihrPracticeController {
|
||||
|
||||
@PostMapping("/turn")
|
||||
public R<TurnResponse> turn(@RequestBody TurnRequest request) {
|
||||
return R.ok(practiceSeedService.turn(request, currentAppUsername()));
|
||||
return R.ok(practiceSeedService.turn(request, currentAppUsername(), currentAppUserId()));
|
||||
}
|
||||
|
||||
@PostMapping("/finish")
|
||||
@@ -187,6 +187,14 @@ public class AihrPracticeController {
|
||||
return username == null ? "" : username.trim();
|
||||
}
|
||||
|
||||
private static Long currentAppUserId() {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (loginUser == null || !UserType.APP_USER.getUserType().equals(loginUser.getUserType())) {
|
||||
return null;
|
||||
}
|
||||
return loginUser.getUserId();
|
||||
}
|
||||
|
||||
private static String currentOperatorUsername() {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (loginUser == null || loginUser.getUsername() == null) {
|
||||
|
||||
+4
@@ -182,6 +182,10 @@ public class AihrMobileSeedService {
|
||||
return practiceSeedService.answerDailyDrill(id, request, extPartyId);
|
||||
}
|
||||
|
||||
public DailyDrillAnswerResponse answerDailyDrill(Long id, DailyDrillAnswerRequest request, String extPartyId, Long ownerUserId) {
|
||||
return practiceSeedService.answerDailyDrill(id, request, extPartyId, ownerUserId);
|
||||
}
|
||||
|
||||
private static String normalize(String role) {
|
||||
if (role == null) {
|
||||
return "user";
|
||||
|
||||
+42
-5
@@ -518,11 +518,16 @@ public class AihrPracticeSeedService {
|
||||
}
|
||||
|
||||
public TurnResponse turn(TurnRequest request, String ownerIdentity) {
|
||||
return turn(request, ownerIdentity, null);
|
||||
}
|
||||
|
||||
public TurnResponse turn(TurnRequest request, String ownerIdentity, Long ownerUserId) {
|
||||
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);
|
||||
}
|
||||
validateAudioOwnership(request == null ? null : request.traineeAudioOssId(), ownerIdentity, ownerUserId);
|
||||
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())) {
|
||||
@@ -1455,6 +1460,7 @@ public class AihrPracticeSeedService {
|
||||
|
||||
public boolean canReadPracticeAudio(Long ossId, String identity) {
|
||||
ensureAudioTable();
|
||||
ensureAssignmentTable();
|
||||
if (ossId == null || isBlank(identity)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1464,24 +1470,50 @@ public class AihrPracticeSeedService {
|
||||
JOIN aihr_practice_session s
|
||||
ON s.tenant_id = a.tenant_id AND s.session_id = a.session_id
|
||||
WHERE a.tenant_id = ? AND a.oss_id = ? AND s.mode = 'mobile'
|
||||
""", (rs, rowNum) -> rs.getString("ext_party_id"), tenantId(), ossId);
|
||||
UNION ALL
|
||||
SELECT d.ext_party_id
|
||||
FROM aihr_practice_assignment d
|
||||
WHERE d.tenant_id = ? AND d.answer_audio_oss_id = ?
|
||||
""", (rs, rowNum) -> rs.getString("ext_party_id"), tenantId(), ossId, tenantId(), ossId);
|
||||
String owner = identity.trim();
|
||||
return sessionParties.stream().anyMatch(party -> owner.equals(party) || inTeamScope(owner, party));
|
||||
}
|
||||
|
||||
public boolean canReadPracticeAudioForTenant(Long ossId) {
|
||||
ensureAudioTable();
|
||||
ensureAssignmentTable();
|
||||
if (ossId == null) {
|
||||
return false;
|
||||
}
|
||||
Integer count = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM aihr_practice_audio
|
||||
WHERE tenant_id = ? AND oss_id = ?
|
||||
""", Integer.class, tenantId(), ossId);
|
||||
SELECT (
|
||||
SELECT COUNT(*) FROM aihr_practice_audio
|
||||
WHERE tenant_id = ? AND oss_id = ?
|
||||
) + (
|
||||
SELECT COUNT(*) FROM aihr_practice_assignment
|
||||
WHERE tenant_id = ? AND answer_audio_oss_id = ?
|
||||
)
|
||||
""", Integer.class, tenantId(), ossId, tenantId(), ossId);
|
||||
return count != null && count > 0;
|
||||
}
|
||||
|
||||
private void validateAudioOwnership(Long ossId, String ownerIdentity, Long ownerUserId) {
|
||||
if (ossId == null) {
|
||||
return;
|
||||
}
|
||||
if (isBlank(ownerIdentity) || ownerUserId == null || ownerUserId <= 0) {
|
||||
throw new ServiceException("录音归属校验失败,请重新录音后提交");
|
||||
}
|
||||
Integer count = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM sys_oss
|
||||
WHERE tenant_id = ? AND oss_id = ? AND create_by = ?
|
||||
""", Integer.class, tenantId(), ossId, ownerUserId);
|
||||
if (count == null || count == 0) {
|
||||
throw new ServiceException("录音不属于当前账号,请重新录音后提交");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean markReviewed(Long id) {
|
||||
return markReviewed(id, null);
|
||||
@@ -2135,6 +2167,10 @@ public class AihrPracticeSeedService {
|
||||
}
|
||||
|
||||
public DailyDrillAnswerResponse answerDailyDrill(Long id, DailyDrillAnswerRequest request, String extPartyId) {
|
||||
return answerDailyDrill(id, request, extPartyId, null);
|
||||
}
|
||||
|
||||
public DailyDrillAnswerResponse answerDailyDrill(Long id, DailyDrillAnswerRequest request, String extPartyId, Long ownerUserId) {
|
||||
ensureAssignmentTable();
|
||||
if (id == null) {
|
||||
return null;
|
||||
@@ -2156,6 +2192,7 @@ public class AihrPracticeSeedService {
|
||||
String answer = maskSensitiveText(firstNonBlank(request == null ? null : request.answer(), ""));
|
||||
String audioUrl = firstNonBlank(request == null ? null : request.audioUrl(), "");
|
||||
Long audioOssId = request == null ? null : request.audioOssId();
|
||||
validateAudioOwnership(audioOssId, extPartyId, ownerUserId);
|
||||
DailyDrillScore score = scoreDailyDrill(parts.question(), answer, parts.referenceAnswer());
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int updated = jdbcTemplate.update("""
|
||||
|
||||
+14
@@ -305,6 +305,20 @@ public class AihrPracticeSeedServiceTest {
|
||||
assertEquals("训练会话不存在或无权访问", missing.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void practiceAudioMustMatchCurrentAppUploaderAndDailyAudioIsReadable() throws Exception {
|
||||
String service = Files.readString(Path.of("src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java"));
|
||||
String controller = Files.readString(Path.of("src/main/java/org/dromara/aihr/controller/AihrPracticeController.java"));
|
||||
String mobileController = Files.readString(Path.of("src/main/java/org/dromara/aihr/controller/AihrMobileController.java"));
|
||||
|
||||
assertTrue(service.contains("FROM sys_oss\n WHERE tenant_id = ? AND oss_id = ? AND create_by = ?"));
|
||||
assertTrue(service.contains("validateAudioOwnership(request == null ? null : request.traineeAudioOssId(), ownerIdentity, ownerUserId)"));
|
||||
assertTrue(service.contains("validateAudioOwnership(audioOssId, extPartyId, ownerUserId)"));
|
||||
assertTrue(service.contains("d.answer_audio_oss_id = ?"));
|
||||
assertTrue(controller.contains("currentAppUserId()"));
|
||||
assertTrue(mobileController.contains("answerDailyDrill(id, request, ownMobileExtPartyId(null), currentAppUserId())"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startChecksScenarioEnabledBeforeSeedFallback() throws Exception {
|
||||
String source = Files.readString(Path.of("src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java"));
|
||||
|
||||
Reference in New Issue
Block a user