后台异常处理优化
This commit is contained in:
@@ -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;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
* <p>
|
||||
* 处理策略:
|
||||
* 1. 业务异常(BusinessException):记录 INFO 级别日志,返回详细错误信息给前端
|
||||
* 2. 参数校验异常:记录 WARN 级别日志,返回字段错误信息
|
||||
* 3. 系统异常(Exception):记录 ERROR 级别日志,隐藏详细信息,返回通用错误提示
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
* 业务异常是预期内的错误,需要详细返回给前端
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> 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<Void> 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<Void> 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,39 +47,51 @@ public class AnnotationResultArchiveService {
|
||||
|
||||
public PageResult<AnnotationResultHistoryResponse> pageHistory(LoginUser currentUser,
|
||||
AnnotationResultHistoryPageQuery query) {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
try {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
|
||||
var wrapper = new LambdaQueryWrapper<AnnotationResultHistory>()
|
||||
.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<AnnotationResultHistory>()
|
||||
.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<AnnotationResultHistory>(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<AnnotationResultHistory>(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<AnnotationResult> results = annotationResultMapper.selectList(new LambdaQueryWrapper<AnnotationResult>()
|
||||
.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<AnnotationResult> results = annotationResultMapper.selectList(new LambdaQueryWrapper<AnnotationResult>()
|
||||
.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, "加载文件内容失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,145 +43,180 @@ public class AnnotationResultService {
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public PageResult<AnnotationResultResponse> pageResults(LoginUser currentUser, AnnotationResultPageQuery query) {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
try {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
|
||||
var wrapper = new LambdaQueryWrapper<AnnotationResult>()
|
||||
.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<AnnotationResult>()
|
||||
.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<AnnotationResult>(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<AnnotationResult>(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<AnnotationResultCompareResponse.QaRecord> qaRecords = qaContent.records().stream()
|
||||
.map(qa -> new AnnotationResultCompareResponse.QaRecord(
|
||||
qa.id(),
|
||||
qa.question(),
|
||||
qa.answer(),
|
||||
qa.requiresReview()
|
||||
)).toList();
|
||||
|
||||
// 转换差异记录
|
||||
List<AnnotationResultCompareResponse.DiffRecord> 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<AnnotationResultCompareResponse.QaRecord> qaRecords = qaContent.records().stream()
|
||||
.map(qa -> new AnnotationResultCompareResponse.QaRecord(
|
||||
qa.id(),
|
||||
qa.question(),
|
||||
qa.answer(),
|
||||
qa.requiresReview()
|
||||
)).toList();
|
||||
|
||||
// 转换差异记录
|
||||
List<AnnotationResultCompareResponse.DiffRecord> 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<QaContent.QaRecord> 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<QaContent.QaRecord> 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);
|
||||
|
||||
@@ -45,132 +45,163 @@ public class AnnotationTaskService {
|
||||
|
||||
@Transactional
|
||||
public AnnotationTaskResponse createTask(LoginUser currentUser, CreateAnnotationTaskRequest request) {
|
||||
List<SourceResource> resources = loadAndValidateResources(currentUser, request.resourceIds());
|
||||
try {
|
||||
List<SourceResource> 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<SourceResource> resources = null;
|
||||
|
||||
if (request.resourceIds() != null && !request.resourceIds().isEmpty()) {
|
||||
List<Long> currentResourceIds = normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId));
|
||||
List<Long> 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<SourceResource> resources = null;
|
||||
|
||||
if (request.resourceIds() != null && !request.resourceIds().isEmpty()) {
|
||||
List<Long> currentResourceIds = normalizeIds(
|
||||
annotationTaskResourceMapper.listResourceIdsByTaskId(taskId));
|
||||
List<Long> 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<Long> 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<Long> 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<AnnotationTaskResponse> pageTasks(LoginUser currentUser, AnnotationTaskPageQuery query) {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
try {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
|
||||
LambdaQueryWrapper<AnnotationTask> wrapper = new LambdaQueryWrapper<AnnotationTask>()
|
||||
.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<AnnotationTask> wrapper = new LambdaQueryWrapper<AnnotationTask>()
|
||||
.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<AnnotationTask> page = new Page<>(query.pageNo(), query.pageSize());
|
||||
Page<AnnotationTask> resultPage = annotationTaskMapper.selectPage(page, wrapper);
|
||||
|
||||
List<AnnotationTaskResponse> 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<AnnotationTask> page = new Page<>(query.pageNo(), query.pageSize());
|
||||
Page<AnnotationTask> resultPage = annotationTaskMapper.selectPage(page, wrapper);
|
||||
|
||||
List<AnnotationTaskResponse> 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<SourceResource> loadAndValidateResources(LoginUser currentUser, List<Long> resourceIds) {
|
||||
|
||||
@@ -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<CompanyOptionResponse> 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) {
|
||||
|
||||
@@ -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<SysCompany> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <T> List<T> filterByRole(
|
||||
LoginUser currentUser,
|
||||
List<T> allRecords,
|
||||
Function<T, UserRole> roleExtractor,
|
||||
Function<T, Long> 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<String> 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<BizDataRecord> listVisibleRecords(LoginUser currentUser) {
|
||||
List<BizDataRecord> 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<BizDataRecord> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SourceResourceResponse> pageResources(LoginUser currentUser, SourceResourcePageQuery query) {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
try {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
|
||||
LambdaQueryWrapper<SourceResource> wrapper =
|
||||
new LambdaQueryWrapper<SourceResource>().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<SourceResource> wrapper =
|
||||
new LambdaQueryWrapper<SourceResource>().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<SourceResource> page = new Page<>(query.pageNo(), query.pageSize());
|
||||
Page<SourceResource> resultPage = sourceResourceMapper.selectPage(page, wrapper);
|
||||
List<SourceResourceResponse> records = resultPage.getRecords().stream().map(this::toResponse).toList();
|
||||
return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(),
|
||||
(int) resultPage.getSize());
|
||||
} else {
|
||||
// 不分页,查询全部
|
||||
List<SourceResource> records = sourceResourceMapper.selectList(wrapper);
|
||||
List<SourceResourceResponse> responseList = records.stream().map(this::toResponse).toList();
|
||||
return new PageResult<>(responseList, (long) responseList.size(), 1, responseList.size());
|
||||
// 判断是否需要分页
|
||||
if (query.needPagination()) {
|
||||
Page<SourceResource> page = new Page<>(query.pageNo(), query.pageSize());
|
||||
Page<SourceResource> resultPage = sourceResourceMapper.selectPage(page, wrapper);
|
||||
List<SourceResourceResponse> records = resultPage.getRecords().stream().map(this::toResponse).toList();
|
||||
return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(),
|
||||
(int) resultPage.getSize());
|
||||
} else {
|
||||
// 不分页,查询全部
|
||||
List<SourceResource> records = sourceResourceMapper.selectList(wrapper);
|
||||
List<SourceResourceResponse> 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<ImageBboxResponse.BboxCoordinateResponse> 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<ImageBboxResponse.BboxCoordinateResponse> parseBboxJson(String bboxJson) {
|
||||
if (!StringUtils.hasText(bboxJson)) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(bboxJson,
|
||||
new TypeReference<List<ImageBboxResponse.BboxCoordinateResponse>>() {
|
||||
});
|
||||
} 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<ImageBboxResponse.BboxCoordinateResponse> 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<ImageBboxResponse.BboxCoordinateResponse> parseBboxJson(String bboxJson) {
|
||||
if (!StringUtils.hasText(bboxJson)) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(bboxJson,
|
||||
new TypeReference<List<ImageBboxResponse.BboxCoordinateResponse>>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("Failed to parse bbox json: {}", e.getMessage());
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<SysConfigResponse> pageConfigs(LoginUser currentUser, SysConfigPageQuery query) {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
try {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
|
||||
LambdaQueryWrapper<SysConfig> wrapper = new LambdaQueryWrapper<SysConfig>()
|
||||
.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<SysConfig> wrapper = new LambdaQueryWrapper<SysConfig>()
|
||||
.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<SysConfig> page = new Page<>(query.pageNo(), query.pageSize());
|
||||
Page<SysConfig> resultPage = sysConfigMapper.selectPage(page, wrapper);
|
||||
|
||||
List<SysConfigResponse> 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<SysConfig> page = new Page<>(query.pageNo(), query.pageSize());
|
||||
Page<SysConfig> resultPage = sysConfigMapper.selectPage(page, wrapper);
|
||||
|
||||
List<SysConfigResponse> 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) {
|
||||
|
||||
@@ -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<SysUser> listAllUsers(LoginUser currentUser) {
|
||||
assertSystemAdmin(currentUser);
|
||||
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<SysUser>().orderByAsc(SysUser::getId);
|
||||
return sysUserMapper.selectList(wrapper);
|
||||
try {
|
||||
assertSystemAdmin(currentUser);
|
||||
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<SysUser>().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<SysUser> listCompanyUsers(LoginUser currentUser) {
|
||||
assertCompanyAdmin(currentUser);
|
||||
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getCompanyId, currentUser.companyId()).orderByAsc(SysUser::getId);
|
||||
return sysUserMapper.selectList(wrapper);
|
||||
try {
|
||||
assertCompanyAdmin(currentUser);
|
||||
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<SysUser>()
|
||||
.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<SysUser> 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user