diff --git a/src/main/java/com/labelsys/backend/common/exception/GlobalExceptionHandler.java b/src/main/java/com/labelsys/backend/common/exception/GlobalExceptionHandler.java index 7300d91..8234307 100644 --- a/src/main/java/com/labelsys/backend/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/labelsys/backend/common/exception/GlobalExceptionHandler.java @@ -3,44 +3,78 @@ package com.labelsys.backend.common.exception; import com.labelsys.backend.common.Result; import com.labelsys.backend.common.ResultCode; import jakarta.servlet.http.HttpServletResponse; -import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.stream.Collectors; + +/** + * 全局异常处理器 + *

+ * 处理策略: + * 1. 业务异常(BusinessException):记录 INFO 级别日志,返回详细错误信息给前端 + * 2. 参数校验异常:记录 WARN 级别日志,返回字段错误信息 + * 3. 系统异常(Exception):记录 ERROR 级别日志,隐藏详细信息,返回通用错误提示 + */ +@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { + /** + * 处理业务异常 + * 业务异常是预期内的错误,需要详细返回给前端 + */ @ExceptionHandler(BusinessException.class) public Result handleBusiness(BusinessException exception, HttpServletResponse response) { + // 记录 INFO 级别日志(业务异常属于预期内错误) + log.info("Business exception occurred: code={}, message={}", + exception.getResultCode().getCode(), exception.getMessage()); + response.setStatus(toHttpStatus(exception.getResultCode()).value()); return new Result<>(exception.getResultCode().getCode(), exception.getMessage(), null); } - @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class}) + /** + * 处理参数校验异常 + */ + @ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class }) public Result handleValidation(Exception exception, HttpServletResponse response) { - response.setStatus(HttpStatus.BAD_REQUEST.value()); String message; - if (exception instanceof MethodArgumentNotValidException methodArgumentNotValidException) { - message = methodArgumentNotValidException.getBindingResult().getFieldErrors().stream() - .map(error -> error.getField() + error.getDefaultMessage()) - .collect(Collectors.joining(";")); + if (exception instanceof MethodArgumentNotValidException methodException) { + message = methodException.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); } else if (exception instanceof BindException bindException) { message = bindException.getBindingResult().getFieldErrors().stream() - .map(error -> error.getField() + error.getDefaultMessage()) - .collect(Collectors.joining(";")); + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); } else { message = ResultCode.BAD_REQUEST.getMessage(); } + + // 记录 WARN 级别日志 + log.warn("Validation failed: {}", message); + + response.setStatus(HttpStatus.BAD_REQUEST.value()); return new Result<>(ResultCode.BAD_REQUEST.getCode(), message, null); } + /** + * 处理系统异常 + * 系统异常是预期外的错误,需要隐藏详细信息 + */ @ExceptionHandler(Exception.class) public Result handleUnexpected(Exception exception, HttpServletResponse response) { + // 记录 ERROR 级别日志(包含完整堆栈) + log.error("Unexpected exception occurred", exception); + + // 返回通用错误信息,不暴露内部细节 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - return new Result<>(ResultCode.ERROR.getCode(), exception.getMessage(), null); + return new Result<>(ResultCode.ERROR.getCode(), "系统内部错误,请稍后重试", null); } private HttpStatus toHttpStatus(ResultCode resultCode) { @@ -53,4 +87,4 @@ public class GlobalExceptionHandler { default -> HttpStatus.INTERNAL_SERVER_ERROR; }; } -} +} \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java b/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java index 63f453c..4570421 100644 --- a/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java +++ b/src/main/java/com/labelsys/backend/service/AnnotationResultArchiveService.java @@ -47,39 +47,51 @@ public class AnnotationResultArchiveService { public PageResult pageHistory(LoginUser currentUser, AnnotationResultHistoryPageQuery query) { - List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); - boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); + try { + List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); + boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); - var wrapper = new LambdaQueryWrapper() - .eq(AnnotationResultHistory::getCompanyId, currentUser.companyId()) - .eq(query.taskId() != null, AnnotationResultHistory::getTaskId, query.taskId()) - .eq(query.resourceId() != null, AnnotationResultHistory::getResourceId, query.resourceId()) - .orderByDesc(AnnotationResultHistory::getCreatedAt); + var wrapper = new LambdaQueryWrapper() + .eq(AnnotationResultHistory::getCompanyId, currentUser.companyId()) + .eq(query.taskId() != null, AnnotationResultHistory::getTaskId, query.taskId()) + .eq(query.resourceId() != null, AnnotationResultHistory::getResourceId, query.resourceId()) + .orderByDesc(AnnotationResultHistory::getCreatedAt); - if (shouldFilterByUserId) { - wrapper.eq(AnnotationResultHistory::getCreatorId, currentUser.userId()); - } else if (!allowedRoles.isEmpty()) { - wrapper.in(AnnotationResultHistory::getCreatorRole, allowedRoles); + if (shouldFilterByUserId) { + wrapper.eq(AnnotationResultHistory::getCreatorId, currentUser.userId()); + } else if (!allowedRoles.isEmpty()) { + wrapper.in(AnnotationResultHistory::getCreatorRole, allowedRoles); + } + + var page = new Page(query.pageNo(), query.pageSize()); + var resultPage = annotationResultHistoryMapper.selectPage(page, wrapper); + + var records = resultPage.getRecords().stream() + .map(this::toResponse) + .toList(); + + return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), + (int) resultPage.getSize()); + } catch (Exception e) { + log.error("pageHistory failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; } - - var page = new Page(query.pageNo(), query.pageSize()); - var resultPage = annotationResultHistoryMapper.selectPage(page, wrapper); - - var records = resultPage.getRecords().stream() - .map(this::toResponse) - .toList(); - - return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), - (int) resultPage.getSize()); } public AnnotationResultHistoryResponse getHistory(LoginUser currentUser, Long historyId) { - AnnotationResultHistory history = annotationResultHistoryMapper.selectById(historyId); - if (history == null || !history.getCompanyId().equals(currentUser.companyId())) { - throw new BusinessException(ResultCode.NOT_FOUND, "历史记录不存在"); + try { + AnnotationResultHistory history = annotationResultHistoryMapper.selectById(historyId); + if (history == null || !history.getCompanyId().equals(currentUser.companyId())) { + throw new BusinessException(ResultCode.NOT_FOUND, "历史记录不存在"); + } + assertHistoryPermission(currentUser, history); + return toResponse(history); + } catch (Exception e) { + log.error("getHistory failed, companyId={}, userId={}, historyId={}, error={}", + currentUser.companyId(), currentUser.userId(), historyId, e.getMessage(), e); + throw e; } - assertHistoryPermission(currentUser, history); - return toResponse(history); } private void assertHistoryPermission(LoginUser currentUser, AnnotationResultHistory history) { @@ -110,18 +122,23 @@ public class AnnotationResultArchiveService { @Transactional public int autoArchiveEligibleResults() { - LocalDateTime cutoff = LocalDateTime.now().minus(autoArchiveTimeout); - List results = annotationResultMapper.selectList(new LambdaQueryWrapper() - .eq(AnnotationResult::getIsDeleted, false) - .eq(AnnotationResult::getRequiresManualReview, false) - .lt(AnnotationResult::getCreatedAt, cutoff)); - int archivedCount = 0; - for (AnnotationResult result : results) { - if (archiveRuntimeResult(result, null, "AUTO_ARCHIVE", null) != null) { - archivedCount++; + try { + LocalDateTime cutoff = LocalDateTime.now().minus(autoArchiveTimeout); + List results = annotationResultMapper.selectList(new LambdaQueryWrapper() + .eq(AnnotationResult::getIsDeleted, false) + .eq(AnnotationResult::getRequiresManualReview, false) + .lt(AnnotationResult::getCreatedAt, cutoff)); + int archivedCount = 0; + for (AnnotationResult result : results) { + if (archiveRuntimeResult(result, null, "AUTO_ARCHIVE", null) != null) { + archivedCount++; + } } + return archivedCount; + } catch (Exception e) { + log.error("autoArchiveEligibleResults failed, error={}", e.getMessage(), e); + throw e; } - return archivedCount; } private void assertReviewer(LoginUser currentUser) { @@ -227,26 +244,29 @@ public class AnnotationResultArchiveService { * @return 文件内容响应 */ public FileContentResponse loadFileContent(LoginUser currentUser, Long historyId) { - AnnotationResultHistory history = annotationResultHistoryMapper.selectById(historyId); - if (history == null || !history.getCompanyId().equals(currentUser.companyId())) { - throw new BusinessException(ResultCode.NOT_FOUND, "历史记录不存在"); - } - //assertHistoryPermission(currentUser, history); - - String filePath = history.getQaContentFilePath(); - if (filePath == null || filePath.isEmpty()) { - throw new BusinessException(ResultCode.ERROR, "文件路径为空"); - } - try { + AnnotationResultHistory history = annotationResultHistoryMapper.selectById(historyId); + if (history == null || !history.getCompanyId().equals(currentUser.companyId())) { + throw new BusinessException(ResultCode.NOT_FOUND, "历史记录不存在"); + } + //assertHistoryPermission(currentUser, history); + + String filePath = history.getQaContentFilePath(); + if (filePath == null || filePath.isEmpty()) { + throw new BusinessException(ResultCode.ERROR, "文件路径为空"); + } + String bucketName = extractBucketName(filePath); String objectKey = extractObjectKey(filePath); byte[] content = objectStorageService.download(bucketName, objectKey); String contentStr = new String(content, StandardCharsets.UTF_8); return new FileContentResponse(filePath, contentStr, content.length); + } catch (BusinessException e) { + throw e; } catch (Exception e) { - log.error("Failed to load file content, historyId={}, filePath={}", historyId, filePath, e); + log.error("loadFileContent failed, companyId={}, userId={}, historyId={}, error={}", + currentUser.companyId(), currentUser.userId(), historyId, e.getMessage(), e); throw new BusinessException(ResultCode.ERROR, "加载文件内容失败"); } } diff --git a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java index a3f3346..1e6f845 100644 --- a/src/main/java/com/labelsys/backend/service/AnnotationResultService.java +++ b/src/main/java/com/labelsys/backend/service/AnnotationResultService.java @@ -43,145 +43,180 @@ public class AnnotationResultService { private final ObjectMapper objectMapper; public PageResult pageResults(LoginUser currentUser, AnnotationResultPageQuery query) { - List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); - boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); + try { + List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); + boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); - var wrapper = new LambdaQueryWrapper() - .eq(AnnotationResult::getIsDeleted, Boolean.FALSE) - .eq(AnnotationResult::getCompanyId, currentUser.companyId()) - .eq(query.taskId() != null, AnnotationResult::getTaskId, query.taskId()) - .eq(query.resourceId() != null, AnnotationResult::getResourceId, query.resourceId()) - .eq(query.requiresManualReview() != null, AnnotationResult::getRequiresManualReview, - query.requiresManualReview()) - .orderByDesc(AnnotationResult::getCreatedAt); + var wrapper = new LambdaQueryWrapper() + .eq(AnnotationResult::getIsDeleted, Boolean.FALSE) + .eq(AnnotationResult::getCompanyId, currentUser.companyId()) + .eq(query.taskId() != null, AnnotationResult::getTaskId, query.taskId()) + .eq(query.resourceId() != null, AnnotationResult::getResourceId, query.resourceId()) + .eq(query.requiresManualReview() != null, AnnotationResult::getRequiresManualReview, + query.requiresManualReview()) + .orderByDesc(AnnotationResult::getCreatedAt); - if (shouldFilterByUserId) { - wrapper.eq(AnnotationResult::getCreatorId, currentUser.userId()); - } else if (!allowedRoles.isEmpty()) { - wrapper.in(AnnotationResult::getCreatorRole, allowedRoles); + if (shouldFilterByUserId) { + wrapper.eq(AnnotationResult::getCreatorId, currentUser.userId()); + } else if (!allowedRoles.isEmpty()) { + wrapper.in(AnnotationResult::getCreatorRole, allowedRoles); + } + + var page = new Page(query.pageNo(), query.pageSize()); + var resultPage = annotationResultMapper.selectPage(page, wrapper); + + var records = resultPage.getRecords().stream() + .map(this::toResponse) + .filter(response -> query.runtimeStatus() == null + || query.runtimeStatus().equals(response.runtimeStatus())) + .toList(); + + return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), + (int) resultPage.getSize()); + } catch (Exception e) { + log.error("pageResults failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; } - - var page = new Page(query.pageNo(), query.pageSize()); - var resultPage = annotationResultMapper.selectPage(page, wrapper); - - var records = resultPage.getRecords().stream() - .map(this::toResponse) - .filter(response -> query.runtimeStatus() == null - || query.runtimeStatus().equals(response.runtimeStatus())) - .toList(); - - return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), - (int) resultPage.getSize()); } public AnnotationResultResponse getResult(LoginUser currentUser, Long resultId) { - AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId()); - if (result == null) { - log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", resultId, - currentUser.companyId(), currentUser.userId()); - throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); + try { + AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, + currentUser.companyId()); + if (result == null) { + log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", + resultId, + currentUser.companyId(), currentUser.userId()); + throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); + } + //assertResultPermission(currentUser, result); + return toResponse(result); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("getResult failed, companyId={}, userId={}, resultId={}, error={}", + currentUser.companyId(), currentUser.userId(), resultId, e.getMessage(), e); + throw e; } - //assertResultPermission(currentUser, result); - return toResponse(result); } public AnnotationResultCompareResponse compareResult(LoginUser currentUser, Long resultId) { - AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId()); - if (result == null) { - log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", resultId, - currentUser.companyId(), currentUser.userId()); - throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); + try { + AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, + currentUser.companyId()); + if (result == null) { + log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", + resultId, + currentUser.companyId(), currentUser.userId()); + throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); + } + //assertResultPermission(currentUser, result); + + QaContent qaContent = loadQaContent(result); + DiffContent diffContent = Boolean.TRUE.equals(result.getRequiresManualReview()) ? + loadDiffSummary(result) : null; + + SourceResource resource = sourceResourceMapper.selectById(result.getResourceId()); + + // 转换 QA 记录 + List qaRecords = qaContent.records().stream() + .map(qa -> new AnnotationResultCompareResponse.QaRecord( + qa.id(), + qa.question(), + qa.answer(), + qa.requiresReview() + )).toList(); + + // 转换差异记录 + List diffRecords = diffContent != null ? + diffContent.records().stream() + .map(diff -> new AnnotationResultCompareResponse.DiffRecord( + diff.qaId(), + diff.question(), + diff.extractAnswer(), + diff.verifyAnswer(), + diff.diffReason(), + diff.mergedAnswer() + )).toList() : List.of(); + + return new AnnotationResultCompareResponse( + result.getId(), + result.getTaskId(), + result.getResourceId(), + qaRecords, + diffRecords, + resource == null ? null : resource.getFilePath() + ); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("compareResult failed, companyId={}, userId={}, resultId={}, error={}", + currentUser.companyId(), currentUser.userId(), resultId, e.getMessage(), e); + throw e; } - //assertResultPermission(currentUser, result); - - QaContent qaContent = loadQaContent(result); - DiffContent diffContent = Boolean.TRUE.equals(result.getRequiresManualReview()) ? - loadDiffSummary(result) : null; - - SourceResource resource = sourceResourceMapper.selectById(result.getResourceId()); - - // 转换 QA 记录 - List qaRecords = qaContent.records().stream() - .map(qa -> new AnnotationResultCompareResponse.QaRecord( - qa.id(), - qa.question(), - qa.answer(), - qa.requiresReview() - )).toList(); - - // 转换差异记录 - List diffRecords = diffContent != null ? - diffContent.records().stream() - .map(diff -> new AnnotationResultCompareResponse.DiffRecord( - diff.qaId(), - diff.question(), - diff.extractAnswer(), - diff.verifyAnswer(), - diff.diffReason(), - diff.mergedAnswer() - )).toList() : List.of(); - - return new AnnotationResultCompareResponse( - result.getId(), - result.getTaskId(), - result.getResourceId(), - qaRecords, - diffRecords, - resource == null ? null : resource.getFilePath() - ); } @Transactional public void mergeReviewResult(LoginUser currentUser, Long resultId, MergeReviewResultRequest request) { - AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId()); - if (result == null) { - throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); + try { + AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, + currentUser.companyId()); + if (result == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在"); + } + //assertResultPermission(currentUser, result); + + // 读取当前 qa.json + QaContent qaContent = loadQaContent(result); + + // 更新 qa.json 的 answer 字段 + List updatedQaRecords = qaContent.records().stream() + .map(record -> { + String mergedAnswer = request.mergedAnswers().get(record.id()); + if (mergedAnswer != null) { + return new QaContent.QaRecord( + record.id(), + record.question(), + mergedAnswer, + false + ); + } + return record; + }) + .toList(); + + QaContent updatedQaContent = new QaContent( + qaContent.taskId(), + qaContent.resourceId(), + updatedQaRecords, + new QaContent.Metadata( + qaContent.metadata().createdAt(), + LocalDateTime.now().toString() + ) + ); + saveQaContent(result, updatedQaContent); + + // 更新数据库记录 + result.setIsDeleted(Boolean.TRUE); + result.setReviewerId(currentUser.userId()); + result.setReviewComment(request.reviewComment()); + result.setReviewedAt(LocalDateTime.now()); + result.setRequiresManualReview(false); + annotationResultMapper.updateById(result); + + // 归档到历史表(人工审核后归档) + archiveToHistory(result, currentUser, "审核通过后归档", false); + + log.info("merged review result, companyId={}, userId={}, resultId={}", + currentUser.companyId(), currentUser.userId(), resultId); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("mergeReviewResult failed, companyId={}, userId={}, resultId={}, error={}", + currentUser.companyId(), currentUser.userId(), resultId, e.getMessage(), e); + throw e; } - //assertResultPermission(currentUser, result); - - // 读取当前 qa.json - QaContent qaContent = loadQaContent(result); - - // 更新 qa.json 的 answer 字段 - List updatedQaRecords = qaContent.records().stream() - .map(record -> { - String mergedAnswer = request.mergedAnswers().get(record.id()); - if (mergedAnswer != null) { - return new QaContent.QaRecord( - record.id(), - record.question(), - mergedAnswer, - false - ); - } - return record; - }) - .toList(); - - QaContent updatedQaContent = new QaContent( - qaContent.taskId(), - qaContent.resourceId(), - updatedQaRecords, - new QaContent.Metadata( - qaContent.metadata().createdAt(), - LocalDateTime.now().toString() - ) - ); - saveQaContent(result, updatedQaContent); - - // 更新数据库记录 - result.setIsDeleted(Boolean.TRUE); - result.setReviewerId(currentUser.userId()); - result.setReviewComment(request.reviewComment()); - result.setReviewedAt(LocalDateTime.now()); - result.setRequiresManualReview(false); - annotationResultMapper.updateById(result); - - // 归档到历史表(人工审核后归档) - archiveToHistory(result, currentUser, "审核通过后归档", false); - - log.info("merged review result, companyId={}, userId={}, resultId={}", - currentUser.companyId(), currentUser.userId(), resultId); } private AnnotationResultResponse toResponse(AnnotationResult result) { @@ -267,12 +302,14 @@ public class AnnotationResultService { /** * 归档到历史表 - * @param result 标注结果 - * @param currentUser 当前用户 + * + * @param result 标注结果 + * @param currentUser 当前用户 * @param archiveReason 归档原因 * @param isAutoArchive 是否自动归档(true=自动归档,false=人工审核后归档) */ - private void archiveToHistory(AnnotationResult result, LoginUser currentUser, String archiveReason, boolean isAutoArchive) { + private void archiveToHistory(AnnotationResult result, LoginUser currentUser, String archiveReason, + boolean isAutoArchive) { try { // 读取 qa.json 内容用于归档 QaContent qaContent = loadQaContent(result); @@ -312,7 +349,7 @@ public class AnnotationResultService { annotationResultHistoryMapper.insert(historyBuilder.build()); - log.info("archived result to history, resultId={}, historyId={}, isAutoArchive={}", + log.info("archived result to history, resultId={}, historyId={}, isAutoArchive={}", result.getId(), historyBuilder.build().getId(), isAutoArchive); } catch (Exception e) { log.error("Failed to archive result to history, resultId={}", result.getId(), e); diff --git a/src/main/java/com/labelsys/backend/service/AnnotationTaskService.java b/src/main/java/com/labelsys/backend/service/AnnotationTaskService.java index d63788a..956aa12 100644 --- a/src/main/java/com/labelsys/backend/service/AnnotationTaskService.java +++ b/src/main/java/com/labelsys/backend/service/AnnotationTaskService.java @@ -45,132 +45,163 @@ public class AnnotationTaskService { @Transactional public AnnotationTaskResponse createTask(LoginUser currentUser, CreateAnnotationTaskRequest request) { - List resources = loadAndValidateResources(currentUser, request.resourceIds()); + try { + List resources = loadAndValidateResources(currentUser, request.resourceIds()); - AnnotationTask task = AnnotationTask.builder() - .id(IdGenerator.nextId()) - .companyId(currentUser.companyId()) - .creatorId(currentUser.userId()) - .creatorRole(currentUser.role()) - .taskName(request.taskName()) - .industryType(defaultIndustryType(request.industryType())) - .taskType(defaultTaskType(request.taskType())) - .taskStatus(TaskStatus.PENDING.name()) - .isDeleted(false) - .build(); + AnnotationTask task = AnnotationTask.builder() + .id(IdGenerator.nextId()) + .companyId(currentUser.companyId()) + .creatorId(currentUser.userId()) + .creatorRole(currentUser.role()) + .taskName(request.taskName()) + .industryType(defaultIndustryType(request.industryType())) + .taskType(defaultTaskType(request.taskType())) + .taskStatus(TaskStatus.PENDING.name()) + .isDeleted(false) + .build(); - annotationTaskMapper.insert(task); - saveTaskBindings(task.getId(), currentUser.companyId(), resources); + annotationTaskMapper.insert(task); + saveTaskBindings(task.getId(), currentUser.companyId(), resources); - log.info("created annotation task, companyId={}, userId={}, taskId={}, resourceCount={}", - currentUser.companyId(), currentUser.userId(), task.getId(), resources.size()); + log.info("created annotation task, companyId={}, userId={}, taskId={}, resourceCount={}", + currentUser.companyId(), currentUser.userId(), task.getId(), resources.size()); - return buildTaskResponse(task, resourceIds(resources)); + return buildTaskResponse(task, resourceIds(resources)); + } catch (Exception e) { + log.error("createTask failed, companyId={}, userId={}, taskName={}, error={}", + currentUser.companyId(), currentUser.userId(), request.taskName(), e.getMessage(), e); + throw e; + } } @Transactional public AnnotationTaskResponse updateTask(LoginUser currentUser, Long taskId, UpdateAnnotationTaskRequest request) { - AnnotationTask task = annotationTaskMapper.findByIdAndCompanyId(taskId, currentUser.companyId()); - if (task == null) { - throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); - } - assertTaskPermission(currentUser, task); - - boolean resourcesChanged = false; - List resources = null; - - if (request.resourceIds() != null && !request.resourceIds().isEmpty()) { - List currentResourceIds = normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId)); - List targetResourceIds = normalizeIds(request.resourceIds()); - resourcesChanged = !currentResourceIds.equals(targetResourceIds); - - if (TaskStatus.RUNNING.name().equals(task.getTaskStatus()) && resourcesChanged) { - throw new BusinessException(ResultCode.CONFLICT, "运行中的任务不允许修改资源"); + try { + AnnotationTask task = annotationTaskMapper.findByIdAndCompanyId(taskId, currentUser.companyId()); + if (task == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); } - resources = loadAndValidateResources(currentUser, request.resourceIds()); + assertTaskPermission(currentUser, task); + + boolean resourcesChanged = false; + List resources = null; + + if (request.resourceIds() != null && !request.resourceIds().isEmpty()) { + List currentResourceIds = normalizeIds( + annotationTaskResourceMapper.listResourceIdsByTaskId(taskId)); + List targetResourceIds = normalizeIds(request.resourceIds()); + resourcesChanged = !currentResourceIds.equals(targetResourceIds); + + if (TaskStatus.RUNNING.name().equals(task.getTaskStatus()) && resourcesChanged) { + throw new BusinessException(ResultCode.CONFLICT, "运行中的任务不允许修改资源"); + } + resources = loadAndValidateResources(currentUser, request.resourceIds()); + } + + if (request.industryType() != null) { + task.setIndustryType(request.industryType()); + } + + if (request.taskType() != null) { + task.setTaskType(request.taskType()); + } + + annotationTaskMapper.updateById(task); + + if (resourcesChanged && resources != null) { + annotationTaskResourceMapper.deleteByTaskId(taskId); + saveTaskBindings(taskId, currentUser.companyId(), resources); + } + + log.info("updated annotation task, companyId={}, userId={}, taskId={}, resourcesChanged={}", + currentUser.companyId(), currentUser.userId(), taskId, resourcesChanged); + + List finalResourceIds = resources != null ? resourceIds(resources) + : normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId)); + + return buildTaskResponse(task, finalResourceIds); + } catch (Exception e) { + log.error("updateTask failed, companyId={}, userId={}, taskId={}, error={}", + currentUser.companyId(), currentUser.userId(), taskId, e.getMessage(), e); + throw e; } - - if (request.industryType() != null) { - task.setIndustryType(request.industryType()); - } - - if (request.taskType() != null) { - task.setTaskType(request.taskType()); - } - - annotationTaskMapper.updateById(task); - - if (resourcesChanged && resources != null) { - annotationTaskResourceMapper.deleteByTaskId(taskId); - saveTaskBindings(taskId, currentUser.companyId(), resources); - } - - log.info("updated annotation task, companyId={}, userId={}, taskId={}, resourcesChanged={}", - currentUser.companyId(), currentUser.userId(), taskId, resourcesChanged); - - List finalResourceIds = resources != null ? resourceIds(resources) - : normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId)); - - return buildTaskResponse(task, finalResourceIds); } public AnnotationTaskResponse getTask(LoginUser currentUser, Long taskId) { - AnnotationTask task = annotationTaskMapper.findByIdAndCompanyId(taskId, currentUser.companyId()); - if (task == null) { - throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); + try { + AnnotationTask task = annotationTaskMapper.findByIdAndCompanyId(taskId, currentUser.companyId()); + if (task == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); + } + assertTaskPermission(currentUser, task); + return buildTaskResponse(task, normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId))); + } catch (Exception e) { + log.error("getTask failed, companyId={}, userId={}, taskId={}, error={}", + currentUser.companyId(), currentUser.userId(), taskId, e.getMessage(), e); + throw e; } - assertTaskPermission(currentUser, task); - return buildTaskResponse(task, normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId))); } public PageResult pageTasks(LoginUser currentUser, AnnotationTaskPageQuery query) { - List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); - boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); + try { + List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); + boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(AnnotationTask::getCompanyId, currentUser.companyId()) - .eq(query.taskType() != null, AnnotationTask::getTaskType, query.taskType()) - .eq(StringUtils.hasText(query.taskStatus()), AnnotationTask::getTaskStatus, query.taskStatus()) - .eq(query.isDeleted() != null, AnnotationTask::getIsDeleted, query.isDeleted()) - .like(StringUtils.hasText(query.keyword()), AnnotationTask::getTaskName, query.keyword()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AnnotationTask::getCompanyId, currentUser.companyId()) + .eq(query.taskType() != null, AnnotationTask::getTaskType, query.taskType()) + .eq(StringUtils.hasText(query.taskStatus()), AnnotationTask::getTaskStatus, query.taskStatus()) + .eq(query.isDeleted() != null, AnnotationTask::getIsDeleted, query.isDeleted()) + .like(StringUtils.hasText(query.keyword()), AnnotationTask::getTaskName, query.keyword()); - if (shouldFilterByUserId) { - wrapper.eq(AnnotationTask::getCreatorId, currentUser.userId()); - } else if (!allowedRoles.isEmpty()) { - wrapper.in(AnnotationTask::getCreatorRole, allowedRoles); + if (shouldFilterByUserId) { + wrapper.eq(AnnotationTask::getCreatorId, currentUser.userId()); + } else if (!allowedRoles.isEmpty()) { + wrapper.in(AnnotationTask::getCreatorRole, allowedRoles); + } + + wrapper.orderByDesc(AnnotationTask::getCreatedAt); + + Page page = new Page<>(query.pageNo(), query.pageSize()); + Page resultPage = annotationTaskMapper.selectPage(page, wrapper); + + List records = resultPage.getRecords().stream() + .filter(task -> query.resourceId() == null + || annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId()) + .contains(query.resourceId())) + .map(task -> buildTaskResponse(task, + normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId())))) + .toList(); + + return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), + (int) resultPage.getSize()); + } catch (Exception e) { + log.error("pageTasks failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; } - - wrapper.orderByDesc(AnnotationTask::getCreatedAt); - - Page page = new Page<>(query.pageNo(), query.pageSize()); - Page resultPage = annotationTaskMapper.selectPage(page, wrapper); - - List records = resultPage.getRecords().stream() - .filter(task -> query.resourceId() == null - || annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId()) - .contains(query.resourceId())) - .map(task -> buildTaskResponse(task, - normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId())))) - .toList(); - - return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), - (int) resultPage.getSize()); } @Transactional public void deleteTask(LoginUser currentUser, Long taskId) { - AnnotationTask task = annotationTaskMapper.findByIdAndCompanyId(taskId, currentUser.companyId()); - if (task == null) { - throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); + try { + AnnotationTask task = annotationTaskMapper.findByIdAndCompanyId(taskId, currentUser.companyId()); + if (task == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); + } + assertTaskPermission(currentUser, task); + if (TaskStatus.RUNNING.name().equals(task.getTaskStatus())) { + throw new BusinessException(ResultCode.CONFLICT, "运行中的任务不允许删除"); + } + task.setIsDeleted(true); + annotationTaskMapper.updateById(task); + log.info("deleted annotation task logically, companyId={}, userId={}, taskId={}", currentUser.companyId(), + currentUser.userId(), taskId); + } catch (Exception e) { + log.error("deleteTask failed, companyId={}, userId={}, taskId={}, error={}", + currentUser.companyId(), currentUser.userId(), taskId, e.getMessage(), e); + throw e; } - assertTaskPermission(currentUser, task); - if (TaskStatus.RUNNING.name().equals(task.getTaskStatus())) { - throw new BusinessException(ResultCode.CONFLICT, "运行中的任务不允许删除"); - } - task.setIsDeleted(true); - annotationTaskMapper.updateById(task); - log.info("deleted annotation task logically, companyId={}, userId={}, taskId={}", currentUser.companyId(), - currentUser.userId(), taskId); } private List loadAndValidateResources(LoginUser currentUser, List resourceIds) { diff --git a/src/main/java/com/labelsys/backend/service/AuthService.java b/src/main/java/com/labelsys/backend/service/AuthService.java index a407f35..4fc36fd 100644 --- a/src/main/java/com/labelsys/backend/service/AuthService.java +++ b/src/main/java/com/labelsys/backend/service/AuthService.java @@ -15,69 +15,106 @@ import com.labelsys.backend.enums.UserStatus; import com.labelsys.backend.mapper.SysCompanyMapper; import com.labelsys.backend.mapper.SysUserMapper; import com.labelsys.backend.service.session.TokenSessionRepository; -import java.time.Duration; -import java.util.List; -import java.util.UUID; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; +import java.util.List; +import java.util.UUID; + +@Slf4j @Service @RequiredArgsConstructor public class AuthService { - private final SysCompanyMapper sysCompanyMapper; - private final SysUserMapper sysUserMapper; - private final PasswordEncoder passwordEncoder; + private final SysCompanyMapper sysCompanyMapper; + private final SysUserMapper sysUserMapper; + private final PasswordEncoder passwordEncoder; private final TokenSessionRepository tokenSessionRepository; @Value("${labelsys.session.ttl:PT2H}") private Duration sessionTtl; public List listAvailableCompanies(String phone) { - return sysCompanyMapper.findEnabledCompaniesByPhone(phone).stream() - .map(CompanyOptionResponse::from) - .toList(); + try { + return sysCompanyMapper.findEnabledCompaniesByPhone(phone).stream() + .map(CompanyOptionResponse::from) + .toList(); + } catch (Exception e) { + log.error("listAvailableCompanies failed, phone={}, error={}", phone, e.getMessage(), e); + throw e; + } } public LoginResponse login(LoginRequest request) { - SysCompany company = loadEnabledCompany(request.companyCode()); - SysUser user = loadEnabledUser(company.getId(), request.phone()); - if (!passwordEncoder.matches(request.password(), user.getPasswordHash())) { - throw new UnauthorizedException("手机号、公司或密码错误"); + try { + SysCompany company = loadEnabledCompany(request.companyCode()); + SysUser user = loadEnabledUser(company.getId(), request.phone()); + if (!passwordEncoder.matches(request.password(), user.getPasswordHash())) { + throw new UnauthorizedException("手机号、公司或密码错误"); + } + LoginUser loginUser = LoginUser.from(user, company); + String token = UUID.randomUUID().toString(); + tokenSessionRepository.save(token, loginUser, sessionTtl); + return LoginResponse.from(token, loginUser, company); + } catch (UnauthorizedException e) { + throw e; + } catch (Exception e) { + log.error("login failed, companyCode={}, phone={}, error={}", + request.companyCode(), request.phone(), e.getMessage(), e); + throw e; } - LoginUser loginUser = LoginUser.from(user, company); - String token = UUID.randomUUID().toString(); - tokenSessionRepository.save(token, loginUser, sessionTtl); - return LoginResponse.from(token, loginUser, company); } public LoginUser getCurrentUser(String token) { - return tokenSessionRepository.find(token) - .orElseThrow(() -> new UnauthorizedException("未登录或登录已过期")); + try { + return tokenSessionRepository.find(token) + .orElseThrow(() -> new UnauthorizedException("未登录或登录已过期")); + } catch (UnauthorizedException e) { + throw e; + } catch (Exception e) { + log.error("getCurrentUser failed, token={}, error={}", token, e.getMessage(), e); + throw e; + } } @Transactional public void changePassword(LoginUser currentUser, ChangePasswordRequest request) { - if (!request.newPassword().equals(request.confirmPassword())) { - throw new BusinessException(ResultCode.BAD_REQUEST, "两次输入的新密码不一致"); + try { + if (!request.newPassword().equals(request.confirmPassword())) { + throw new BusinessException(ResultCode.BAD_REQUEST, "两次输入的新密码不一致"); + } + SysUser user = sysUserMapper.findByIdAndCompanyId(currentUser.userId(), currentUser.companyId()); + if (user == null || user.getStatus() != UserStatus.ENABLED) { + throw new UnauthorizedException("未登录或登录已过期"); + } + if (!passwordEncoder.matches(request.oldPassword(), user.getPasswordHash())) { + throw new BusinessException(ResultCode.BAD_REQUEST, "旧密码错误"); + } + sysUserMapper.updatePassword(user.getId(), user.getCompanyId(), + passwordEncoder.encode(request.newPassword()), false); + sysUserMapper.bumpSessionVersion(user.getId(), user.getCompanyId()); + tokenSessionRepository.removeAll(user.getId()); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("changePassword failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; } - SysUser user = sysUserMapper.findByIdAndCompanyId(currentUser.userId(), currentUser.companyId()); - if (user == null || user.getStatus() != UserStatus.ENABLED) { - throw new UnauthorizedException("未登录或登录已过期"); - } - if (!passwordEncoder.matches(request.oldPassword(), user.getPasswordHash())) { - throw new BusinessException(ResultCode.BAD_REQUEST, "旧密码错误"); - } - sysUserMapper.updatePassword(user.getId(), user.getCompanyId(), passwordEncoder.encode(request.newPassword()), false); - sysUserMapper.bumpSessionVersion(user.getId(), user.getCompanyId()); - tokenSessionRepository.removeAll(user.getId()); } public void logout(String token) { - tokenSessionRepository.remove(token); + try { + tokenSessionRepository.remove(token); + } catch (Exception e) { + log.error("logout failed, token={}, error={}", token, e.getMessage(), e); + throw e; + } } private SysCompany loadEnabledCompany(String companyCode) { diff --git a/src/main/java/com/labelsys/backend/service/CompanyService.java b/src/main/java/com/labelsys/backend/service/CompanyService.java index e73ad1f..4cd8765 100644 --- a/src/main/java/com/labelsys/backend/service/CompanyService.java +++ b/src/main/java/com/labelsys/backend/service/CompanyService.java @@ -9,10 +9,13 @@ import com.labelsys.backend.dto.request.UpdateCompanyStatusRequest; import com.labelsys.backend.entity.SysCompany; import com.labelsys.backend.mapper.SysCompanyMapper; import com.labelsys.backend.util.IdGenerator; -import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + +@Slf4j @Service @RequiredArgsConstructor public class CompanyService { @@ -20,29 +23,53 @@ public class CompanyService { private final SysCompanyMapper sysCompanyMapper; public List listCompanies(LoginUser currentUser) { - assertPlatformAdmin(currentUser); - return sysCompanyMapper.selectList(null); + try { + assertPlatformAdmin(currentUser); + return sysCompanyMapper.selectList(null); + } catch (ForbiddenException e) { + throw e; + } catch (Exception e) { + log.error("listCompanies failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; + } } public SysCompany createCompany(LoginUser currentUser, CreateCompanyRequest request) { - assertPlatformAdmin(currentUser); - if (sysCompanyMapper.findByCompanyCode(request.companyCode()) != null) { - throw new BusinessException(ResultCode.CONFLICT, "公司编码已存在"); + try { + assertPlatformAdmin(currentUser); + if (sysCompanyMapper.findByCompanyCode(request.companyCode()) != null) { + throw new BusinessException(ResultCode.CONFLICT, "公司编码已存在"); + } + SysCompany company = SysCompany.builder() + .id(IdGenerator.nextId()) + .companyCode(request.companyCode()) + .companyName(request.companyName()) + .status(com.labelsys.backend.enums.CompanyStatus.ENABLED) + .build(); + sysCompanyMapper.insert(company); + return company; + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("createCompany failed, companyId={}, userId={}, companyCode={}, error={}", + currentUser.companyId(), currentUser.userId(), request.companyCode(), e.getMessage(), e); + throw e; } - SysCompany company = SysCompany.builder() - .id(IdGenerator.nextId()) - .companyCode(request.companyCode()) - .companyName(request.companyName()) - .status(com.labelsys.backend.enums.CompanyStatus.ENABLED) - .build(); - sysCompanyMapper.insert(company); - return company; } public void updateStatus(LoginUser currentUser, Long companyId, UpdateCompanyStatusRequest request) { - assertPlatformAdmin(currentUser); - if (sysCompanyMapper.updateStatus(companyId, request.status()) == 0) { - throw new BusinessException(ResultCode.NOT_FOUND, "公司不存在"); + try { + assertPlatformAdmin(currentUser); + if (sysCompanyMapper.updateStatus(companyId, request.status()) == 0) { + throw new BusinessException(ResultCode.NOT_FOUND, "公司不存在"); + } + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("updateStatus failed, companyId={}, userId={}, targetCompanyId={}, error={}", + currentUser.companyId(), currentUser.userId(), companyId, e.getMessage(), e); + throw e; } } diff --git a/src/main/java/com/labelsys/backend/service/DataPermissionService.java b/src/main/java/com/labelsys/backend/service/DataPermissionService.java index 43c3c3a..243dda8 100644 --- a/src/main/java/com/labelsys/backend/service/DataPermissionService.java +++ b/src/main/java/com/labelsys/backend/service/DataPermissionService.java @@ -3,13 +3,16 @@ package com.labelsys.backend.service; import com.labelsys.backend.context.LoginUser; import com.labelsys.backend.entity.BizDataRecord; import com.labelsys.backend.enums.UserRole; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.jdbc.core.JdbcTemplate; +@Slf4j @Service @RequiredArgsConstructor public class DataPermissionService { @@ -17,79 +20,103 @@ public class DataPermissionService { private final JdbcTemplate jdbcTemplate; public boolean canAccessCreator(LoginUser currentUser, Long creatorId, UserRole creatorRole) { - return switch (currentUser.role()) { - case EMPLOYEE -> currentUser.userId().equals(creatorId); - case MANAGER -> creatorRole == UserRole.EMPLOYEE || creatorRole == UserRole.MANAGER; - case ENGINEER -> true; - }; + try { + return switch (currentUser.role()) { + case EMPLOYEE -> currentUser.userId().equals(creatorId); + case MANAGER -> creatorRole == UserRole.EMPLOYEE || creatorRole == UserRole.MANAGER; + case ENGINEER -> true; + }; + } catch (Exception e) { + log.error("canAccessCreator failed, companyId={}, userId={}, creatorId={}, error={}", + currentUser.companyId(), currentUser.userId(), creatorId, e.getMessage(), e); + throw e; + } } - /** - * Generic in-memory role-based data filter for records already loaded in memory. - */ public List filterByRole( LoginUser currentUser, List allRecords, Function roleExtractor, Function ownerIdExtractor) { + try { + if (allRecords == null || allRecords.isEmpty()) { + return List.of(); + } - if (allRecords == null || allRecords.isEmpty()) { - return List.of(); + UserRole currentRole = currentUser.role(); + Long currentUserId = currentUser.userId(); + + return allRecords.stream() + .filter(record -> { + UserRole recordRole = roleExtractor.apply(record); + Long recordOwnerId = ownerIdExtractor.apply(record); + + return switch (currentRole) { + case EMPLOYEE -> currentUserId.equals(recordOwnerId); + case MANAGER -> recordRole == UserRole.EMPLOYEE || recordRole == UserRole.MANAGER; + case ENGINEER -> true; + }; + }) + .collect(Collectors.toList()); + } catch (Exception e) { + log.error("filterByRole failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; } - - UserRole currentRole = currentUser.role(); - Long currentUserId = currentUser.userId(); - - return allRecords.stream() - .filter(record -> { - UserRole recordRole = roleExtractor.apply(record); - Long recordOwnerId = ownerIdExtractor.apply(record); - - return switch (currentRole) { - case EMPLOYEE -> currentUserId.equals(recordOwnerId); - case MANAGER -> recordRole == UserRole.EMPLOYEE || recordRole == UserRole.MANAGER; - case ENGINEER -> true; - }; - }) - .collect(Collectors.toList()); } - /** - * Returns the creator roles visible to the current user for SQL-side filtering. - */ public List getAllowedRoles(LoginUser currentUser) { - return switch (currentUser.role()) { - case EMPLOYEE -> List.of(); - case MANAGER -> List.of("EMPLOYEE", "MANAGER"); - case ENGINEER -> List.of("EMPLOYEE", "MANAGER", "ENGINEER"); - }; + try { + return switch (currentUser.role()) { + case EMPLOYEE -> List.of(); + case MANAGER -> List.of("EMPLOYEE", "MANAGER"); + case ENGINEER -> List.of("EMPLOYEE", "MANAGER", "ENGINEER"); + }; + } catch (Exception e) { + log.error("getAllowedRoles failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; + } } - /** - * Whether SQL queries should additionally restrict by creator/user id. - */ public boolean shouldFilterByUserId(LoginUser currentUser) { - return currentUser.role() == UserRole.EMPLOYEE; + try { + return currentUser.role() == UserRole.EMPLOYEE; + } catch (Exception e) { + log.error("shouldFilterByUserId failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; + } } public List listVisibleRecords(LoginUser currentUser) { - List allRecords = jdbcTemplate.query(""" - select id, company_id, creator_id, creator_role, record_name, created_at, updated_at - from biz_data_record - where company_id = ? - order by id - """, - (rs, rowNum) -> BizDataRecord.builder() - .id(rs.getLong("id")) - .companyId(rs.getLong("company_id")) - .creatorId(rs.getLong("creator_id")) - .creatorRole(UserRole.valueOf(rs.getString("creator_role"))) - .recordName(rs.getString("record_name")) - .createdAt(rs.getTimestamp("created_at") == null ? null : rs.getTimestamp("created_at").toLocalDateTime()) - .updatedAt(rs.getTimestamp("updated_at") == null ? null : rs.getTimestamp("updated_at").toLocalDateTime()) - .build(), - currentUser.companyId()); + try { + List allRecords = jdbcTemplate.query(""" + select id, company_id, creator_id, creator_role, record_name, created_at, updated_at + from biz_data_record + where company_id = ? + order by id + """, + (rs, rowNum) -> BizDataRecord.builder() + .id(rs.getLong("id")) + .companyId(rs.getLong("company_id")) + .creatorId(rs.getLong("creator_id")) + .creatorRole(UserRole.valueOf(rs.getString("creator_role"))) + .recordName(rs.getString("record_name")) + .createdAt(rs.getTimestamp("created_at") == null ? + null : + rs.getTimestamp("created_at").toLocalDateTime()) + .updatedAt(rs.getTimestamp("updated_at") == null ? + null : + rs.getTimestamp("updated_at").toLocalDateTime()) + .build(), + currentUser.companyId()); - return filterByRole(currentUser, allRecords, BizDataRecord::getCreatorRole, BizDataRecord::getCreatorId); + return filterByRole(currentUser, allRecords, BizDataRecord::getCreatorRole, BizDataRecord::getCreatorId); + } catch (Exception e) { + log.error("listVisibleRecords failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; + } } } diff --git a/src/main/java/com/labelsys/backend/service/SourceResourceService.java b/src/main/java/com/labelsys/backend/service/SourceResourceService.java index 9a6960e..7d52d16 100644 --- a/src/main/java/com/labelsys/backend/service/SourceResourceService.java +++ b/src/main/java/com/labelsys/backend/service/SourceResourceService.java @@ -54,92 +54,119 @@ public class SourceResourceService { @Transactional public SourceUploadResponse upload(LoginUser currentUser, SourceUploadRequest request) { - MultipartFile file = request.getFile(); - if (file == null || file.isEmpty()) { - throw new BusinessException(ResultCode.BAD_REQUEST, "上传文件不能为空"); - } - if (!ResourceType.isValid(request.getResourceType())) { - throw new BusinessException(ResultCode.BAD_REQUEST, "资源类型非法"); - } - String resourceName = - StringUtils.hasText(request.getResourceName()) ? request.getResourceName() : file.getOriginalFilename(); - SourceResource existingResource = - sourceResourceMapper.selectByCompanyIdAndResourceName(currentUser.companyId(), resourceName); - if (existingResource != null) { - throw new BusinessException(ResultCode.BAD_REQUEST, "资源名称已存在:" + resourceName); - } - - long resourceId = IdGenerator.nextId(); - String extension = resolveExtension(file.getOriginalFilename(), request.getResourceType()); - String objectKey = ObjectStoragePathBuilder.sourceObjectKey(currentUser.companyId(), request.getResourceType(), - resourceId, extension); try { - objectStorageService.upload(objectStorageProperties.getSourceBucket(), objectKey, file.getBytes(), - file.getContentType()); - } catch (IOException ex) { - throw new BusinessException(ResultCode.BAD_REQUEST, "读取上传文件失败"); - } + MultipartFile file = request.getFile(); + if (file == null || file.isEmpty()) { + throw new BusinessException(ResultCode.BAD_REQUEST, "上传文件不能为空"); + } + if (!ResourceType.isValid(request.getResourceType())) { + throw new BusinessException(ResultCode.BAD_REQUEST, "资源类型非法"); + } + String resourceName = + StringUtils.hasText(request.getResourceName()) ? + request.getResourceName() : + file.getOriginalFilename(); + SourceResource existingResource = + sourceResourceMapper.selectByCompanyIdAndResourceName(currentUser.companyId(), resourceName); + if (existingResource != null) { + throw new BusinessException(ResultCode.BAD_REQUEST, "资源名称已存在:" + resourceName); + } - SourceResource resource = SourceResource.builder().id(resourceId).companyId(currentUser.companyId()) - .creatorId(currentUser.userId()).creatorRole(currentUser.role()) - .resourceName( - StringUtils.hasText(request.getResourceName()) ? - request.getResourceName() : - file.getOriginalFilename()) - .resourceType(request.getResourceType()).bucketName(objectStorageProperties.getSourceBucket()) - .filePath(objectKey).fileSize(file.getSize()).sourceStatus(SourceStatus.READY.name()) - .storageProvider("rustfs").remark(request.getRemark()).build(); - sourceResourceMapper.insert(resource); - log.info("uploaded source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(), - currentUser.userId(), resourceId); - return new SourceUploadResponse(resource.getId(), resource.getResourceName(), resource.getResourceType(), - resource.getBucketName(), resource.getFilePath(), resource.getFileSize(), resource.getSourceStatus(), - resource.getCreatedAt()); + long resourceId = IdGenerator.nextId(); + String extension = resolveExtension(file.getOriginalFilename(), request.getResourceType()); + String objectKey = ObjectStoragePathBuilder.sourceObjectKey(currentUser.companyId(), + request.getResourceType(), + resourceId, extension); + try { + objectStorageService.upload(objectStorageProperties.getSourceBucket(), objectKey, file.getBytes(), + file.getContentType()); + } catch (IOException ex) { + throw new BusinessException(ResultCode.BAD_REQUEST, "读取上传文件失败"); + } + + SourceResource resource = SourceResource.builder().id(resourceId).companyId(currentUser.companyId()) + .creatorId(currentUser.userId()).creatorRole(currentUser.role()) + .resourceName( + StringUtils.hasText(request.getResourceName()) ? + request.getResourceName() : + file.getOriginalFilename()) + .resourceType(request.getResourceType()).bucketName(objectStorageProperties.getSourceBucket()) + .filePath(objectKey).fileSize(file.getSize()).sourceStatus(SourceStatus.READY.name()) + .storageProvider("rustfs").remark(request.getRemark()).build(); + sourceResourceMapper.insert(resource); + log.info("uploaded source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(), + currentUser.userId(), resourceId); + return new SourceUploadResponse(resource.getId(), resource.getResourceName(), resource.getResourceType(), + resource.getBucketName(), resource.getFilePath(), resource.getFileSize(), + resource.getSourceStatus(), + resource.getCreatedAt()); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("upload failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; + } } public PageResult pageResources(LoginUser currentUser, SourceResourcePageQuery query) { - List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); - boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); + try { + List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); + boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); - LambdaQueryWrapper wrapper = - new LambdaQueryWrapper().eq(SourceResource::getCompanyId, currentUser.companyId()) - .eq(StringUtils.hasText(query.resourceType()), SourceResource::getResourceType, - query.resourceType()) - .eq(StringUtils.hasText(query.sourceStatus()), SourceResource::getSourceStatus, - query.sourceStatus()) - .like(StringUtils.hasText(query.keyword()), SourceResource::getResourceName, query.keyword()); + LambdaQueryWrapper wrapper = + new LambdaQueryWrapper().eq(SourceResource::getCompanyId, currentUser.companyId()) + .eq(StringUtils.hasText(query.resourceType()), SourceResource::getResourceType, + query.resourceType()) + .eq(StringUtils.hasText(query.sourceStatus()), SourceResource::getSourceStatus, + query.sourceStatus()) + .like(StringUtils.hasText(query.keyword()), SourceResource::getResourceName, + query.keyword()); - if (shouldFilterByUserId) { - wrapper.eq(SourceResource::getCreatorId, currentUser.userId()); - } else if (!allowedRoles.isEmpty()) { - wrapper.in(SourceResource::getCreatorRole, allowedRoles); - } + if (shouldFilterByUserId) { + wrapper.eq(SourceResource::getCreatorId, currentUser.userId()); + } else if (!allowedRoles.isEmpty()) { + wrapper.in(SourceResource::getCreatorRole, allowedRoles); + } - wrapper.orderByDesc(SourceResource::getCreatedAt); + wrapper.orderByDesc(SourceResource::getCreatedAt); - // 判断是否需要分页 - if (query.needPagination()) { - Page page = new Page<>(query.pageNo(), query.pageSize()); - Page resultPage = sourceResourceMapper.selectPage(page, wrapper); - List records = resultPage.getRecords().stream().map(this::toResponse).toList(); - return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), - (int) resultPage.getSize()); - } else { - // 不分页,查询全部 - List records = sourceResourceMapper.selectList(wrapper); - List responseList = records.stream().map(this::toResponse).toList(); - return new PageResult<>(responseList, (long) responseList.size(), 1, responseList.size()); + // 判断是否需要分页 + if (query.needPagination()) { + Page page = new Page<>(query.pageNo(), query.pageSize()); + Page resultPage = sourceResourceMapper.selectPage(page, wrapper); + List records = resultPage.getRecords().stream().map(this::toResponse).toList(); + return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), + (int) resultPage.getSize()); + } else { + // 不分页,查询全部 + List records = sourceResourceMapper.selectList(wrapper); + List responseList = records.stream().map(this::toResponse).toList(); + return new PageResult<>(responseList, (long) responseList.size(), 1, responseList.size()); + } + } catch (Exception e) { + log.error("pageResources failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; } } public SourceResourceResponse getResource(LoginUser currentUser, Long resourceId) { - SourceResource resource = sourceResourceMapper.selectById(resourceId); - if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { - log.warn("Resource not found or cross-tenant access attempt: resourceId={}, companyId={}, userId={}", - resourceId, currentUser.companyId(), currentUser.userId()); - throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + try { + SourceResource resource = sourceResourceMapper.selectById(resourceId); + if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { + log.warn("Resource not found or cross-tenant access attempt: resourceId={}, companyId={}, userId={}", + resourceId, currentUser.companyId(), currentUser.userId()); + throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + } + return toResponse(resource); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("getResource failed, companyId={}, userId={}, resourceId={}, error={}", + currentUser.companyId(), currentUser.userId(), resourceId, e.getMessage(), e); + throw e; } - return toResponse(resource); } /** @@ -149,30 +176,201 @@ public class SourceResourceService { * @return 资源实体 */ public SourceResource getResourceEntity(Long resourceId) { - return sourceResourceMapper.selectById(resourceId); + try { + return sourceResourceMapper.selectById(resourceId); + } catch (Exception e) { + log.error("getResourceEntity failed, resourceId={}, error={}", resourceId, e.getMessage(), e); + throw e; + } } @Transactional public void deleteResource(LoginUser currentUser, Long resourceId) { - SourceResource resource = sourceResourceMapper.selectById(resourceId); - if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { - throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + try { + SourceResource resource = sourceResourceMapper.selectById(resourceId); + if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { + throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + } + if (!dataPermissionService.canAccessCreator(currentUser, resource.getCreatorId(), + resource.getCreatorRole())) { + throw new BusinessException(ResultCode.FORBIDDEN, "无权删除资源"); + } + int bindCount = annotationTaskResourceMapper.countByResourceId(resourceId); + if (bindCount > 0) { + resource.setSourceStatus(SourceStatus.ARCHIVED.name()); + sourceResourceMapper.updateById(resource); + log.info("archived referenced source resource, companyId={}, userId={}, resourceId={}", + currentUser.companyId(), currentUser.userId(), resourceId); + return; + } + objectStorageService.delete(resource.getBucketName(), resource.getFilePath()); + sourceResourceMapper.deleteById(resourceId); + log.info("deleted source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(), + currentUser.userId(), resourceId); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("deleteResource failed, companyId={}, userId={}, resourceId={}, error={}", + currentUser.companyId(), currentUser.userId(), resourceId, e.getMessage(), e); + throw e; } - if (!dataPermissionService.canAccessCreator(currentUser, resource.getCreatorId(), resource.getCreatorRole())) { - throw new BusinessException(ResultCode.FORBIDDEN, "无权删除资源"); + } + + /** + * 下载资源(支持 TEXT、IMAGE、VIDEO) + * + * @param currentUser 当前用户 + * @param resourceId 资源ID + * @return 资源字节数组 + */ + public byte[] downloadResource(LoginUser currentUser, Long resourceId) { + try { + SourceResource resource = sourceResourceMapper.selectById(resourceId); + if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { + log.warn("Resource not found or cross-tenant access attempt: resourceId={}, companyId={}, userId={}", + resourceId, currentUser.companyId(), currentUser.userId()); + throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + } + if (!"READY".equals(resource.getSourceStatus())) { + throw new BusinessException(ResultCode.BAD_REQUEST, "资源未就绪"); + } + return objectStorageService.download(resource.getBucketName(), resource.getFilePath()); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("downloadResource failed, companyId={}, userId={}, resourceId={}, error={}", + currentUser.companyId(), currentUser.userId(), resourceId, e.getMessage(), e); + throw e; } - int bindCount = annotationTaskResourceMapper.countByResourceId(resourceId); - if (bindCount > 0) { - resource.setSourceStatus(SourceStatus.ARCHIVED.name()); - sourceResourceMapper.updateById(resource); - log.info("archived referenced source resource, companyId={}, userId={}, resourceId={}", - currentUser.companyId(), currentUser.userId(), resourceId); - return; + } + + public ImageBboxResponse getImageBbox(LoginUser currentUser, Long resourceId) { + try { + SourceResource resource = sourceResourceMapper.selectById(resourceId); + if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { + throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + } + if (!"IMAGE".equals(resource.getResourceType())) { + throw new BusinessException(ResultCode.BAD_REQUEST, "仅图片资源支持BBOX标注"); + } + + ImageBboxAnnotation annotation = imageBboxAnnotationMapper.selectByResourceId(resourceId); + if (annotation == null) { + return new ImageBboxResponse(null, resourceId, List.of(), null, null, null, null); + } + + List bboxes = parseBboxJson(annotation.getBboxJson()); + SysUser creator = sysUserMapper.selectById(annotation.getCreatorId()); + + return new ImageBboxResponse( + annotation.getId(), + annotation.getResourceId(), + bboxes, + annotation.getRemark(), + creator == null ? null : creator.getRealName(), + annotation.getCreatedAt(), + annotation.getUpdatedAt() + ); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("getImageBbox failed, companyId={}, userId={}, resourceId={}, error={}", + currentUser.companyId(), currentUser.userId(), resourceId, e.getMessage(), e); + throw e; + } + } + + @Transactional + public ImageBboxResponse saveImageBbox(LoginUser currentUser, Long resourceId, SaveImageBboxRequest request) { + try { + SourceResource resource = sourceResourceMapper.selectById(resourceId); + if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { + throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + } + if (!"IMAGE".equals(resource.getResourceType())) { + throw new BusinessException(ResultCode.BAD_REQUEST, "仅图片资源支持BBOX标注"); + } + + String bboxJson; + try { + bboxJson = objectMapper.writeValueAsString(request.bboxes()); + } catch (JsonProcessingException e) { + throw new BusinessException(ResultCode.BAD_REQUEST, "BBOX数据序列化失败"); + } + + boolean isNewAnnotation = imageBboxAnnotationMapper.selectByResourceId(resourceId) == null; + + ImageBboxAnnotation existing = imageBboxAnnotationMapper.selectByResourceId(resourceId); + if (existing != null) { + existing.setBboxJson(bboxJson); + existing.setRemark(request.remark()); + existing.setUpdatedAt(LocalDateTime.now()); + imageBboxAnnotationMapper.updateById(existing); + } else { + long annotationId = IdGenerator.nextId(); + ImageBboxAnnotation annotation = ImageBboxAnnotation.builder() + .id(annotationId) + .companyId(currentUser.companyId()) + .resourceId(resourceId) + .bboxJson(bboxJson) + .remark(request.remark()) + .creatorId(currentUser.userId()) + .creatorRole(currentUser.role()) + .build(); + imageBboxAnnotationMapper.insert(annotation); + } + + // 更新资源表的has_bbox字段 + if (isNewAnnotation || Boolean.FALSE.equals(resource.getHasBbox())) { + resource.setHasBbox(true); + sourceResourceMapper.updateById(resource); + } + + return getImageBbox(currentUser, resourceId); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("saveImageBbox failed, companyId={}, userId={}, resourceId={}, error={}", + currentUser.companyId(), currentUser.userId(), resourceId, e.getMessage(), e); + throw e; + } + } + + @Transactional + public void deleteImageBbox(LoginUser currentUser, Long resourceId) { + try { + SourceResource resource = sourceResourceMapper.selectById(resourceId); + if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { + throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); + } + imageBboxAnnotationMapper.deleteByResourceId(resourceId); + + // 更新资源表的has_bbox字段为false + if (Boolean.TRUE.equals(resource.getHasBbox())) { + resource.setHasBbox(false); + sourceResourceMapper.updateById(resource); + } + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("deleteImageBbox failed, companyId={}, userId={}, resourceId={}, error={}", + currentUser.companyId(), currentUser.userId(), resourceId, e.getMessage(), e); + throw e; + } + } + + private List parseBboxJson(String bboxJson) { + if (!StringUtils.hasText(bboxJson)) { + return List.of(); + } + try { + return objectMapper.readValue(bboxJson, + new TypeReference>() { + }); + } catch (JsonProcessingException e) { + log.warn("Failed to parse bbox json: {}", e.getMessage()); + return List.of(); } - objectStorageService.delete(resource.getBucketName(), resource.getFilePath()); - sourceResourceMapper.deleteById(resourceId); - log.info("deleted source resource, companyId={}, userId={}, resourceId={}", currentUser.companyId(), - currentUser.userId(), resourceId); } private SourceResourceResponse toResponse(SourceResource resource) { @@ -195,26 +393,6 @@ public class SourceResourceService { }; } - /** - * 下载资源(支持 TEXT、IMAGE、VIDEO) - * - * @param currentUser 当前用户 - * @param resourceId 资源ID - * @return 资源字节数组 - */ - public byte[] downloadResource(LoginUser currentUser, Long resourceId) { - SourceResource resource = sourceResourceMapper.selectById(resourceId); - if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { - log.warn("Resource not found or cross-tenant access attempt: resourceId={}, companyId={}, userId={}", - resourceId, currentUser.companyId(), currentUser.userId()); - throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); - } - if (!"READY".equals(resource.getSourceStatus())) { - throw new BusinessException(ResultCode.BAD_REQUEST, "资源未就绪"); - } - return objectStorageService.download(resource.getBucketName(), resource.getFilePath()); - } - /** * 获取资源的Content-Type(支持 TEXT、IMAGE、VIDEO) * @@ -224,7 +402,7 @@ public class SourceResourceService { public String getContentType(SourceResource resource) { String filePath = resource.getFilePath(); String resourceType = resource.getResourceType(); - + // 优先根据文件扩展名判断 if (filePath != null && filePath.contains(".")) { String extension = filePath.substring(filePath.lastIndexOf('.') + 1).toLowerCase(); @@ -250,11 +428,11 @@ public class SourceResourceService { default -> getContentTypeByResourceType(resourceType); }; } - + // 如果没有扩展名,根据资源类型判断 return getContentTypeByResourceType(resourceType); } - + /** * 根据资源类型获取默认Content-Type * @@ -269,110 +447,4 @@ public class SourceResourceService { default -> "application/octet-stream"; }; } - - public ImageBboxResponse getImageBbox(LoginUser currentUser, Long resourceId) { - SourceResource resource = sourceResourceMapper.selectById(resourceId); - if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { - throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); - } - if (!"IMAGE".equals(resource.getResourceType())) { - throw new BusinessException(ResultCode.BAD_REQUEST, "仅图片资源支持BBOX标注"); - } - - ImageBboxAnnotation annotation = imageBboxAnnotationMapper.selectByResourceId(resourceId); - if (annotation == null) { - return new ImageBboxResponse(null, resourceId, List.of(), null, null, null, null); - } - - List bboxes = parseBboxJson(annotation.getBboxJson()); - SysUser creator = sysUserMapper.selectById(annotation.getCreatorId()); - - return new ImageBboxResponse( - annotation.getId(), - annotation.getResourceId(), - bboxes, - annotation.getRemark(), - creator == null ? null : creator.getRealName(), - annotation.getCreatedAt(), - annotation.getUpdatedAt() - ); - } - - @Transactional - public ImageBboxResponse saveImageBbox(LoginUser currentUser, Long resourceId, SaveImageBboxRequest request) { - SourceResource resource = sourceResourceMapper.selectById(resourceId); - if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { - throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); - } - if (!"IMAGE".equals(resource.getResourceType())) { - throw new BusinessException(ResultCode.BAD_REQUEST, "仅图片资源支持BBOX标注"); - } - - String bboxJson; - try { - bboxJson = objectMapper.writeValueAsString(request.bboxes()); - } catch (JsonProcessingException e) { - throw new BusinessException(ResultCode.BAD_REQUEST, "BBOX数据序列化失败"); - } - - boolean isNewAnnotation = imageBboxAnnotationMapper.selectByResourceId(resourceId) == null; - - ImageBboxAnnotation existing = imageBboxAnnotationMapper.selectByResourceId(resourceId); - if (existing != null) { - existing.setBboxJson(bboxJson); - existing.setRemark(request.remark()); - existing.setUpdatedAt(LocalDateTime.now()); - imageBboxAnnotationMapper.updateById(existing); - } else { - long annotationId = IdGenerator.nextId(); - ImageBboxAnnotation annotation = ImageBboxAnnotation.builder() - .id(annotationId) - .companyId(currentUser.companyId()) - .resourceId(resourceId) - .bboxJson(bboxJson) - .remark(request.remark()) - .creatorId(currentUser.userId()) - .creatorRole(currentUser.role()) - .build(); - imageBboxAnnotationMapper.insert(annotation); - } - - // 更新资源表的has_bbox字段 - if (isNewAnnotation || Boolean.FALSE.equals(resource.getHasBbox())) { - resource.setHasBbox(true); - sourceResourceMapper.updateById(resource); - } - - return getImageBbox(currentUser, resourceId); - } - - @Transactional - public void deleteImageBbox(LoginUser currentUser, Long resourceId) { - SourceResource resource = sourceResourceMapper.selectById(resourceId); - if (resource == null || !currentUser.companyId().equals(resource.getCompanyId())) { - throw new BusinessException(ResultCode.NOT_FOUND, "资源不存在"); - } - imageBboxAnnotationMapper.deleteByResourceId(resourceId); - - // 更新资源表的has_bbox字段为false - if (Boolean.TRUE.equals(resource.getHasBbox())) { - resource.setHasBbox(false); - sourceResourceMapper.updateById(resource); - } - } - - private List parseBboxJson(String bboxJson) { - if (!StringUtils.hasText(bboxJson)) { - return List.of(); - } - try { - return objectMapper.readValue(bboxJson, - new TypeReference>() { - }); - } catch (JsonProcessingException e) { - log.warn("Failed to parse bbox json: {}", e.getMessage()); - return List.of(); - } - } - } \ No newline at end of file diff --git a/src/main/java/com/labelsys/backend/service/SysConfigService.java b/src/main/java/com/labelsys/backend/service/SysConfigService.java index 46c9eaa..b8e17ef 100644 --- a/src/main/java/com/labelsys/backend/service/SysConfigService.java +++ b/src/main/java/com/labelsys/backend/service/SysConfigService.java @@ -33,88 +33,118 @@ public class SysConfigService { @Transactional public SysConfig saveConfig(LoginUser currentUser, SaveSysConfigRequest request) { - validateConfigType(request.configType()); - SysConfig existing = sysConfigMapper.findByCompanyIdAndConfigName(currentUser.companyId(), - request.configName()); - if (existing != null) { - throw new BusinessException(ResultCode.CONFLICT, "配置名称已存在"); + try { + validateConfigType(request.configType()); + SysConfig existing = sysConfigMapper.findByCompanyIdAndConfigName(currentUser.companyId(), + request.configName()); + if (existing != null) { + throw new BusinessException(ResultCode.CONFLICT, "配置名称已存在"); + } + SysConfig config = SysConfig.builder() + .id(IdGenerator.nextId()) + .companyId(currentUser.companyId()) + .configType(request.configType()) + .configName(request.configName()) + .configValue(request.configValue()) + .status(request.status()) + .creatorId(currentUser.userId()) + .creatorRole(currentUser.role().name()) + .build(); + sysConfigMapper.insert(config); + log.info("saved sys config, companyId={}, userId={}, userRole={}, configName={}, configType={}", + currentUser.companyId(), currentUser.userId(), currentUser.role().name(), + request.configName(), request.configType()); + return config; + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("saveConfig failed, companyId={}, userId={}, configName={}, error={}", + currentUser.companyId(), currentUser.userId(), request.configName(), e.getMessage(), e); + throw e; } - SysConfig config = SysConfig.builder() - .id(IdGenerator.nextId()) - .companyId(currentUser.companyId()) - .configType(request.configType()) - .configName(request.configName()) - .configValue(request.configValue()) - .status(request.status()) - .creatorId(currentUser.userId()) - .creatorRole(currentUser.role().name()) - .build(); - sysConfigMapper.insert(config); - log.info("saved sys config, companyId={}, userId={}, userRole={}, configName={}, configType={}", - currentUser.companyId(), currentUser.userId(), currentUser.role().name(), - request.configName(), request.configType()); - return config; } @Transactional public SysConfig updateConfig(LoginUser currentUser, Long configId, UpdateSysConfigRequest request) { - validateConfigType(request.configType()); - SysConfig existing = getConfigEntity(currentUser, configId); + try { + validateConfigType(request.configType()); + SysConfig existing = getConfigEntity(currentUser, configId); - if (StringUtils.hasText(request.configName())) { - existing.setConfigName(request.configName()); + if (StringUtils.hasText(request.configName())) { + existing.setConfigName(request.configName()); + } + if (StringUtils.hasText(request.configType())) { + existing.setConfigType(request.configType()); + } + if (StringUtils.hasText(request.configValue())) { + existing.setConfigValue(request.configValue()); + } + if (StringUtils.hasText(request.status())) { + existing.setStatus(request.status()); + } + sysConfigMapper.updateById(existing); + log.info("updated sys config, companyId={}, userId={}, configId={}", + currentUser.companyId(), currentUser.userId(), configId); + return existing; + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("updateConfig failed, companyId={}, userId={}, configId={}, error={}", + currentUser.companyId(), currentUser.userId(), configId, e.getMessage(), e); + throw e; } - if (StringUtils.hasText(request.configType())) { - existing.setConfigType(request.configType()); - } - if (StringUtils.hasText(request.configValue())) { - existing.setConfigValue(request.configValue()); - } - if (StringUtils.hasText(request.status())) { - existing.setStatus(request.status()); - } - sysConfigMapper.updateById(existing); - log.info("updated sys config, companyId={}, userId={}, configId={}", - currentUser.companyId(), currentUser.userId(), configId); - return existing; } public SysConfigResponse getConfig(LoginUser currentUser, Long configId) { - SysConfig config = getConfigEntity(currentUser, configId); - if (!dataPermissionService.canAccessCreator(currentUser, config.getCreatorId(), - UserRole.valueOf(config.getCreatorRole()))) { - throw new BusinessException(ResultCode.FORBIDDEN, "无权访问配置"); + try { + SysConfig config = getConfigEntity(currentUser, configId); + if (!dataPermissionService.canAccessCreator(currentUser, config.getCreatorId(), + UserRole.valueOf(config.getCreatorRole()))) { + throw new BusinessException(ResultCode.FORBIDDEN, "无权访问配置"); + } + return toResponse(config); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("getConfig failed, companyId={}, userId={}, configId={}, error={}", + currentUser.companyId(), currentUser.userId(), configId, e.getMessage(), e); + throw e; } - return toResponse(config); } public PageResult pageConfigs(LoginUser currentUser, SysConfigPageQuery query) { - List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); - boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); + try { + List allowedRoles = dataPermissionService.getAllowedRoles(currentUser); + boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(SysConfig::getCompanyId, currentUser.companyId()) - .eq(StringUtils.hasText(query.configType()), SysConfig::getConfigType, query.configType()) - .eq(StringUtils.hasText(query.status()), SysConfig::getStatus, query.status()) - .like(StringUtils.hasText(query.configName()), SysConfig::getConfigName, query.configName()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SysConfig::getCompanyId, currentUser.companyId()) + .eq(StringUtils.hasText(query.configType()), SysConfig::getConfigType, query.configType()) + .eq(StringUtils.hasText(query.status()), SysConfig::getStatus, query.status()) + .like(StringUtils.hasText(query.configName()), SysConfig::getConfigName, query.configName()); - if (shouldFilterByUserId) { - wrapper.eq(SysConfig::getCreatorId, currentUser.userId()); - } else if (!allowedRoles.isEmpty()) { - wrapper.in(SysConfig::getCreatorRole, allowedRoles); + if (shouldFilterByUserId) { + wrapper.eq(SysConfig::getCreatorId, currentUser.userId()); + } else if (!allowedRoles.isEmpty()) { + wrapper.in(SysConfig::getCreatorRole, allowedRoles); + } + + wrapper.orderByDesc(SysConfig::getCreatedAt); + + Page page = new Page<>(query.pageNo(), query.pageSize()); + Page resultPage = sysConfigMapper.selectPage(page, wrapper); + + List records = resultPage.getRecords().stream() + .map(this::toResponse) + .toList(); + + return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), + (int) resultPage.getSize()); + } catch (Exception e) { + log.error("pageConfigs failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; } - - wrapper.orderByDesc(SysConfig::getCreatedAt); - - Page page = new Page<>(query.pageNo(), query.pageSize()); - Page resultPage = sysConfigMapper.selectPage(page, wrapper); - - List records = resultPage.getRecords().stream() - .map(this::toResponse) - .toList(); - - return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), - (int) resultPage.getSize()); } public SysConfigResponse toResponse(SysConfig config) { diff --git a/src/main/java/com/labelsys/backend/service/UserService.java b/src/main/java/com/labelsys/backend/service/UserService.java index 59e2b5b..29ced5a 100644 --- a/src/main/java/com/labelsys/backend/service/UserService.java +++ b/src/main/java/com/labelsys/backend/service/UserService.java @@ -1,11 +1,5 @@ package com.labelsys.backend.service; -import java.util.List; - -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.labelsys.backend.common.ResultCode; import com.labelsys.backend.common.exception.BusinessException; @@ -26,99 +20,188 @@ import com.labelsys.backend.mapper.SysCompanyMapper; import com.labelsys.backend.mapper.SysUserMapper; import com.labelsys.backend.service.session.TokenSessionRepository; import com.labelsys.backend.util.IdGenerator; - import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; + +@Slf4j @Service @RequiredArgsConstructor public class UserService { private static final String DEFAULT_PASSWORD = "123456"; - private final SysUserMapper sysUserMapper; - private final SysCompanyMapper sysCompanyMapper; - private final PasswordEncoder passwordEncoder; + private final SysUserMapper sysUserMapper; + private final SysCompanyMapper sysCompanyMapper; + private final PasswordEncoder passwordEncoder; private final TokenSessionRepository tokenSessionRepository; public List listAllUsers(LoginUser currentUser) { - assertSystemAdmin(currentUser); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper().orderByAsc(SysUser::getId); - return sysUserMapper.selectList(wrapper); + try { + assertSystemAdmin(currentUser); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper().orderByAsc(SysUser::getId); + return sysUserMapper.selectList(wrapper); + } catch (ForbiddenException e) { + throw e; + } catch (Exception e) { + log.error("listAllUsers failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; + } } public List listCompanyUsers(LoginUser currentUser) { - assertCompanyAdmin(currentUser); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(SysUser::getCompanyId, currentUser.companyId()).orderByAsc(SysUser::getId); - return sysUserMapper.selectList(wrapper); + try { + assertCompanyAdmin(currentUser); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SysUser::getCompanyId, currentUser.companyId()).orderByAsc(SysUser::getId); + return sysUserMapper.selectList(wrapper); + } catch (ForbiddenException e) { + throw e; + } catch (Exception e) { + log.error("listCompanyUsers failed, companyId={}, userId={}, error={}", + currentUser.companyId(), currentUser.userId(), e.getMessage(), e); + throw e; + } } public List listCompanyAdmins(LoginUser currentUser, Long companyId) { - assertSystemAdmin(currentUser); - return sysUserMapper.listCompanyAdmins(companyId); + try { + assertSystemAdmin(currentUser); + return sysUserMapper.listCompanyAdmins(companyId); + } catch (ForbiddenException e) { + throw e; + } catch (Exception e) { + log.error("listCompanyAdmins failed, companyId={}, userId={}, targetCompanyId={}, error={}", + currentUser.companyId(), currentUser.userId(), companyId, e.getMessage(), e); + throw e; + } } public SysUser createCompanyAdmin(LoginUser currentUser, CreateCompanyAdminRequest request) { - assertSystemAdmin(currentUser); - ensureEnabledCompany(request.companyId()); - return createUser(request.companyId(), new CreateUserRequest(request.phone(), request.username(), - request.realName(), UserRole.EMPLOYEE, UserPosition.ADMIN)); + try { + assertSystemAdmin(currentUser); + ensureEnabledCompany(request.companyId()); + return createUser(request.companyId(), new CreateUserRequest(request.phone(), request.username(), + request.realName(), UserRole.EMPLOYEE, UserPosition.ADMIN)); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("createCompanyAdmin failed, companyId={}, userId={}, targetCompanyId={}, phone={}, error={}", + currentUser.companyId(), currentUser.userId(), request.companyId(), request.phone(), e.getMessage(), + e); + throw e; + } } public SysUser createSystemEngineerAdmin(LoginUser currentUser, CreateSystemEngineerAdminRequest request) { - assertSystemAdmin(currentUser); - Long systemCompanyId = 1L; - ensureEnabledCompany(systemCompanyId); + try { + assertSystemAdmin(currentUser); + Long systemCompanyId = 1L; + ensureEnabledCompany(systemCompanyId); - if (sysUserMapper.findByCompanyIdAndPhone(systemCompanyId, request.phone()) != null) { - throw new BusinessException(ResultCode.CONFLICT, "同一公司内手机号已存在"); + if (sysUserMapper.findByCompanyIdAndPhone(systemCompanyId, request.phone()) != null) { + throw new BusinessException(ResultCode.CONFLICT, "同一公司内手机号已存在"); + } + + SysUser user = SysUser.builder().id(IdGenerator.nextId()).companyId(systemCompanyId).phone(request.phone()) + .username(request.username()).realName(request.realName()).role(UserRole.ENGINEER) + .position(UserPosition.SUPER_ADMIN).passwordHash(passwordEncoder.encode(DEFAULT_PASSWORD)) + .mustChangePassword(true).status(UserStatus.ENABLED).sessionVersion(1).build(); + sysUserMapper.insert(user); + return user; + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("createSystemEngineerAdmin failed, companyId={}, userId={}, phone={}, error={}", + currentUser.companyId(), currentUser.userId(), request.phone(), e.getMessage(), e); + throw e; } - - SysUser user = SysUser.builder().id(IdGenerator.nextId()).companyId(systemCompanyId).phone(request.phone()) - .username(request.username()).realName(request.realName()).role(UserRole.ENGINEER) - .position(UserPosition.SUPER_ADMIN).passwordHash(passwordEncoder.encode(DEFAULT_PASSWORD)) - .mustChangePassword(true).status(UserStatus.ENABLED).sessionVersion(1).build(); - sysUserMapper.insert(user); - return user; } public SysUser createCompanyUser(LoginUser currentUser, CreateUserRequest request) { - assertCompanyAdmin(currentUser); - return createUser(currentUser.companyId(), request); + try { + assertCompanyAdmin(currentUser); + return createUser(currentUser.companyId(), request); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("createCompanyUser failed, companyId={}, userId={}, phone={}, error={}", + currentUser.companyId(), currentUser.userId(), request.phone(), e.getMessage(), e); + throw e; + } } public SysUser createCompanyUser(LoginUser currentUser, Long companyId, CreateUserRequest request) { - assertSystemAdmin(currentUser); - ensureEnabledCompany(companyId); - return createUser(companyId, request); + try { + assertSystemAdmin(currentUser); + ensureEnabledCompany(companyId); + return createUser(companyId, request); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("createCompanyUser failed, companyId={}, userId={}, targetCompanyId={}, phone={}, error={}", + currentUser.companyId(), currentUser.userId(), companyId, request.phone(), e.getMessage(), e); + throw e; + } } @Transactional public void updateAssignment(LoginUser currentUser, Long userId, UpdateUserAssignmentRequest request) { - assertCompanyAdmin(currentUser); - if (sysUserMapper.updateAssignment(userId, currentUser.companyId(), request.role(), request.position()) == 0) { - throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在"); + try { + assertCompanyAdmin(currentUser); + if (sysUserMapper.updateAssignment(userId, currentUser.companyId(), request.role(), request.position()) + == 0) { + throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在"); + } + tokenSessionRepository.removeAll(userId); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("updateAssignment failed, companyId={}, userId={}, targetUserId={}, error={}", + currentUser.companyId(), currentUser.userId(), userId, e.getMessage(), e); + throw e; } - tokenSessionRepository.removeAll(userId); } @Transactional public void updateStatus(LoginUser currentUser, Long userId, UpdateUserStatusRequest request) { - assertCompanyAdmin(currentUser); - if (sysUserMapper.updateStatus(userId, currentUser.companyId(), request.status()) == 0) { - throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在"); + try { + assertCompanyAdmin(currentUser); + if (sysUserMapper.updateStatus(userId, currentUser.companyId(), request.status()) == 0) { + throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在"); + } + tokenSessionRepository.removeAll(userId); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("updateStatus failed, companyId={}, userId={}, targetUserId={}, error={}", + currentUser.companyId(), currentUser.userId(), userId, e.getMessage(), e); + throw e; } - tokenSessionRepository.removeAll(userId); } @Transactional public void updateCompanyAdminStatus(LoginUser currentUser, Long companyId, Long userId, - UpdateUserStatusRequest request) { - assertSystemAdmin(currentUser); - if (sysUserMapper.updateStatus(userId, companyId, request.status()) == 0) { - throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在"); + UpdateUserStatusRequest request) { + try { + assertSystemAdmin(currentUser); + if (sysUserMapper.updateStatus(userId, companyId, request.status()) == 0) { + throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在"); + } + tokenSessionRepository.removeAll(userId); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error( + "updateCompanyAdminStatus failed, companyId={}, userId={}, targetCompanyId={}, targetUserId={}, error={}", + currentUser.companyId(), currentUser.userId(), companyId, userId, e.getMessage(), e); + throw e; } - tokenSessionRepository.removeAll(userId); } private SysUser createUser(Long companyId, CreateUserRequest request) { @@ -126,9 +209,11 @@ public class UserService { throw new BusinessException(ResultCode.CONFLICT, "同一公司内手机号已存在"); } SysUser user = SysUser.builder().id(IdGenerator.nextId()).companyId(companyId).phone(request.phone()) - .username(request.username()).realName(request.realName()).role(request.role()).position(request.position()) - .passwordHash(passwordEncoder.encode(DEFAULT_PASSWORD)).mustChangePassword(true).status(UserStatus.ENABLED) - .sessionVersion(1).build(); + .username(request.username()).realName(request.realName()).role(request.role()) + .position(request.position()) + .passwordHash(passwordEncoder.encode(DEFAULT_PASSWORD)).mustChangePassword(true) + .status(UserStatus.ENABLED) + .sessionVersion(1).build(); sysUserMapper.insert(user); return user; } diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql index 800944e..60ab227 100644 --- a/src/main/resources/sql/data.sql +++ b/src/main/resources/sql/data.sql @@ -69,9 +69,9 @@ INSERT INTO annotation_task ( id, company_id, creator_id, creator_role, task_name, industry_type, task_type, task_status, is_deleted, started_at, finished_at, error_message ) VALUES - (701, 2, 4, 'EMPLOYEE', '多资源问答抽取任务', 'electricity', 'EXTRACT_QA', + (701, 2, 4, 'EMPLOYEE', '多资源问答抽取任务', 'ELECTRICITY', 'EXTRACT_QA', 'PENDING', FALSE, NULL, NULL, NULL), - (702, 2, 4, 'EMPLOYEE', '图片问答抽取任务', 'transport', 'EXTRACT_QA', + (702, 2, 4, 'EMPLOYEE', '图片问答抽取任务', 'TRANSPORT', 'EXTRACT_QA', 'COMPLETED', FALSE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL) ON CONFLICT DO NOTHING; diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index c015a0c..52dfa20 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -244,7 +244,6 @@ COMMENT ON COLUMN annotation_task_resource.task_id IS '任务ID。'; COMMENT ON COLUMN annotation_task_resource.resource_id IS '资源ID。'; COMMENT ON COLUMN annotation_task_resource.created_at IS '创建时间。'; -drop table if exists annotation_result; CREATE TABLE IF NOT EXISTS annotation_result ( id BIGINT PRIMARY KEY,