Compare commits
30 Commits
6ee34de3fe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f01c991390 | ||
|
|
4065d993e2 | ||
|
|
c3fdf96ceb | ||
|
|
d679340f3c | ||
|
|
7fbe76559c | ||
|
|
2bf06dce54 | ||
|
|
83a412d3fd | ||
|
|
cc2508d014 | ||
|
|
2ccd8f39fe | ||
|
|
9d8d06427c | ||
|
|
3d5b693d22 | ||
|
|
99ffa9d490 | ||
|
|
fbbd73c916 | ||
|
|
74674990d8 | ||
|
|
942c68989c | ||
|
|
ca81514d43 | ||
|
|
7e35cf146e | ||
|
|
343df65c69 | ||
|
|
a01a06d4c1 | ||
|
|
1314e290d5 | ||
|
|
d404c7d187 | ||
|
|
79812b2c77 | ||
|
|
5662c1fda9 | ||
|
|
849e78658d | ||
|
|
ebe8b6c7ed | ||
|
|
b3c9fdfedd | ||
|
|
298e8eb35c | ||
|
|
2beebbe469 | ||
|
|
f087687bfa | ||
|
|
9483bea005 |
10
pom.xml
10
pom.xml
@@ -15,6 +15,8 @@
|
||||
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
||||
<springdoc-openapi.version>2.3.0</springdoc-openapi.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -82,6 +84,14 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>sts</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.labelsys.backend;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.labelsys.backend.mapper")
|
||||
@EnableScheduling
|
||||
public class LabelsysBackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -11,13 +11,13 @@ import lombok.NoArgsConstructor;
|
||||
@Schema(description = "统一返回结果")
|
||||
public class Result<T> {
|
||||
|
||||
@Schema(description = "业务状态码")
|
||||
@Schema(description = "业务状态码", example = "0")
|
||||
private Integer code;
|
||||
|
||||
@Schema(description = "返回消息")
|
||||
@Schema(description = "返回消息", example = "success")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "返回数据")
|
||||
@Schema(description = "返回数据", example = "{}")
|
||||
private T data;
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
|
||||
@@ -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 })
|
||||
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())
|
||||
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())
|
||||
.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) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.labelsys.backend.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.POSTGRE_SQL);
|
||||
paginationInnerInterceptor.setOverflow(false);
|
||||
paginationInnerInterceptor.setMaxLimit(200L);
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.labelsys.backend.config;
|
||||
|
||||
import java.net.URI;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.S3Configuration;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@EnableConfigurationProperties(ObjectStorageProperties.class)
|
||||
public class ObjectStorageConfig {
|
||||
|
||||
private final ObjectStorageProperties properties;
|
||||
|
||||
@Bean
|
||||
public S3Client s3Client() {
|
||||
return S3Client.builder()
|
||||
.endpointOverride(URI.create(properties.getEndpoint()))
|
||||
.region(Region.of(properties.getRegion()))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())))
|
||||
.serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(properties.isPathStyleAccess()).build())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.labelsys.backend.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "labelsys.object-storage")
|
||||
public class ObjectStorageProperties {
|
||||
private String endpoint;
|
||||
private String region;
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
private boolean pathStyleAccess = true;
|
||||
private String sourceBucket;
|
||||
private String artifactBucket;
|
||||
private String exportBucket;
|
||||
}
|
||||
@@ -7,37 +7,28 @@ import com.labelsys.backend.enums.UserRole;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "当前登录用户上下文")
|
||||
public record LoginUser(
|
||||
@Schema(description = "用户ID") Long userId,
|
||||
@Schema(description = "公司ID") Long companyId,
|
||||
@Schema(description = "公司编码") String companyCode,
|
||||
@Schema(description = "公司名称") String companyName,
|
||||
public record LoginUser(@Schema(description = "用户ID") Long userId, @Schema(description = "公司ID") Long companyId,
|
||||
@Schema(description = "公司编码") String companyCode, @Schema(description = "公司名称") String companyName,
|
||||
@Schema(description = "手机号") String phone,
|
||||
@Schema(description = "用户名,可为空", example = "alpha-reviewer") String username,
|
||||
@Schema(description = "真实姓名") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||
@Schema(
|
||||
description = "岗位,枚举值:ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员、SUPER_ADMIN系统管理员") UserPosition position,
|
||||
@Schema(description = "是否必须修改密码") boolean mustChangePassword,
|
||||
@Schema(description = "会话版本") Integer sessionVersion
|
||||
) {
|
||||
@Schema(description = "会话版本") Integer sessionVersion) {
|
||||
|
||||
public static LoginUser from(SysUser user, SysCompany company) {
|
||||
return new LoginUser(
|
||||
user.getId(),
|
||||
company.getId(),
|
||||
company.getCompanyCode(),
|
||||
company.getCompanyName(),
|
||||
user.getPhone(),
|
||||
user.getUsername(),
|
||||
user.getRealName(),
|
||||
user.getRole(),
|
||||
user.getPosition(),
|
||||
Boolean.TRUE.equals(user.getMustChangePassword()),
|
||||
user.getSessionVersion()
|
||||
);
|
||||
return new LoginUser(user.getId(), company.getId(), company.getCompanyCode(), company.getCompanyName(),
|
||||
user.getPhone(), user.getUsername(), user.getRealName(), user.getRole(), user.getPosition(),
|
||||
Boolean.TRUE.equals(user.getMustChangePassword()), user.getSessionVersion());
|
||||
}
|
||||
|
||||
public boolean isPlatformAdmin() {
|
||||
return "PLATFORM".equals(companyCode) && position == UserPosition.ADMIN;
|
||||
// public boolean isPlatformAdmin() {
|
||||
// return "PLATFORM".equals(companyCode) && position == UserPosition.ADMIN;
|
||||
// }
|
||||
|
||||
public boolean isSuperAdmin() {
|
||||
return position == UserPosition.SUPER_ADMIN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.labelsys.backend.controller;
|
||||
|
||||
import com.labelsys.backend.context.UserContext;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.AnnotationResultHistoryPageQuery;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultHistoryResponse;
|
||||
import com.labelsys.backend.dto.response.FileContentResponse;
|
||||
import com.labelsys.backend.service.AnnotationResultArchiveService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/annotation-result-history")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "标注结果归档管理", description = "标注结果归档历史查询接口")
|
||||
public class AnnotationResultArchiveController {
|
||||
|
||||
private final AnnotationResultArchiveService annotationResultArchiveService;
|
||||
|
||||
@Operation(summary = "分页查询归档历史")
|
||||
@GetMapping
|
||||
public ResponseEntity<PageResult<AnnotationResultHistoryResponse>> pageHistory(
|
||||
@Valid AnnotationResultHistoryPageQuery query) {
|
||||
return ResponseEntity.ok(annotationResultArchiveService.pageHistory(UserContext.requireUser(), query));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询归档历史详情")
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<AnnotationResultHistoryResponse> getHistory(
|
||||
@Parameter(description = "历史记录ID", example = "901")
|
||||
@PathVariable Long id) {
|
||||
return ResponseEntity.ok(annotationResultArchiveService.getHistory(UserContext.requireUser(), id));
|
||||
}
|
||||
|
||||
@Operation(summary = "加载归档文件内容")
|
||||
@GetMapping("/{id}/content")
|
||||
public ResponseEntity<FileContentResponse> loadFileContent(
|
||||
@Parameter(description = "历史记录ID", example = "901")
|
||||
@PathVariable Long id) {
|
||||
return ResponseEntity.ok(annotationResultArchiveService.loadFileContent(UserContext.requireUser(), id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.labelsys.backend.controller;
|
||||
|
||||
import com.labelsys.backend.annotation.RequirePosition;
|
||||
import com.labelsys.backend.context.UserContext;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.AnnotationResultPageQuery;
|
||||
import com.labelsys.backend.dto.request.MergeReviewResultRequest;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultCompareResponse;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultDetailResponse;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultResponse;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
import com.labelsys.backend.service.AnnotationResultService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/annotation-results")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "标注结果管理", description = "标注结果相关接口")
|
||||
public class AnnotationResultController {
|
||||
|
||||
private final AnnotationResultService annotationResultService;
|
||||
|
||||
@Operation(summary = "分页查询标注结果")
|
||||
@GetMapping
|
||||
public ResponseEntity<PageResult<AnnotationResultResponse>> pageResults(
|
||||
@Valid AnnotationResultPageQuery query) {
|
||||
return ResponseEntity.ok(annotationResultService.pageResults(UserContext.requireUser(), query));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询标注结果详情")
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<AnnotationResultDetailResponse> getResult(
|
||||
@Parameter(description = "结果ID", example = "191000000000000401")
|
||||
@PathVariable Long id) {
|
||||
return ResponseEntity.ok(annotationResultService.getResult(UserContext.requireUser(), id));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询结果比对信息,REVIEWER岗位以上可操作")
|
||||
@GetMapping("/{id}/compare")
|
||||
@RequirePosition(UserPosition.REVIEWER)
|
||||
public ResponseEntity<AnnotationResultCompareResponse> compareResult(
|
||||
@Parameter(description = "结果ID", example = "191000000000000401")
|
||||
@PathVariable Long id) {
|
||||
return ResponseEntity.ok(annotationResultService.compareResult(UserContext.requireUser(), id));
|
||||
}
|
||||
|
||||
@Operation(summary = "提交合并审核结果,REVIEWER岗位以上可操作")
|
||||
@PostMapping("/{id}/merge")
|
||||
@RequirePosition(UserPosition.REVIEWER)
|
||||
public ResponseEntity<Void> mergeReviewResult(
|
||||
@Parameter(description = "结果ID", example = "191000000000000401")
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody MergeReviewResultRequest request) {
|
||||
annotationResultService.mergeReviewResult(UserContext.requireUser(), id, request);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.labelsys.backend.controller;
|
||||
|
||||
import com.labelsys.backend.common.Result;
|
||||
import com.labelsys.backend.context.UserContext;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.AnnotationTaskPageQuery;
|
||||
import com.labelsys.backend.dto.request.CreateAnnotationTaskRequest;
|
||||
import com.labelsys.backend.dto.request.UpdateAnnotationTaskRequest;
|
||||
import com.labelsys.backend.dto.response.AnnotationTaskResponse;
|
||||
import com.labelsys.backend.service.AnnotationTaskService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "标注任务管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/annotation-tasks")
|
||||
@RequiredArgsConstructor
|
||||
public class AnnotationTaskController {
|
||||
|
||||
private final AnnotationTaskService annotationTaskService;
|
||||
|
||||
@Operation(summary = "创建标注任务")
|
||||
@PostMapping
|
||||
public Result<AnnotationTaskResponse> create(@Valid @RequestBody CreateAnnotationTaskRequest request) {
|
||||
return Result.success(annotationTaskService.createTask(UserContext.requireUser(), request));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新标注任务")
|
||||
@PutMapping("/{id}")
|
||||
public Result<AnnotationTaskResponse> update(
|
||||
@Parameter(description = "任务ID", example = "191000000000000301") @PathVariable Long id,
|
||||
@Valid @RequestBody UpdateAnnotationTaskRequest request) {
|
||||
return Result.success(annotationTaskService.updateTask(UserContext.requireUser(), id, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询标注任务")
|
||||
@GetMapping
|
||||
public Result<PageResult<AnnotationTaskResponse>> page(@ParameterObject AnnotationTaskPageQuery query) {
|
||||
return Result.success(annotationTaskService.pageTasks(UserContext.requireUser(), query));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询标注任务详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<AnnotationTaskResponse> detail(
|
||||
@Parameter(description = "任务ID", example = "191000000000000301") @PathVariable Long id) {
|
||||
return Result.success(annotationTaskService.getTask(UserContext.requireUser(), id));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除标注任务")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> delete(
|
||||
@Parameter(description = "任务ID", example = "191000000000000301") @PathVariable Long id) {
|
||||
annotationTaskService.deleteTask(UserContext.requireUser(), id);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.labelsys.backend.dto.response.CurrentUserResponse;
|
||||
import com.labelsys.backend.dto.response.LoginResponse;
|
||||
import com.labelsys.backend.service.AuthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
@@ -33,7 +34,10 @@ public class AuthController {
|
||||
|
||||
@Operation(summary = "根据手机号查询可登录公司")
|
||||
@GetMapping("/companies")
|
||||
public Result<List<CompanyOptionResponse>> listCompanies(@RequestParam String phone) {
|
||||
public Result<List<CompanyOptionResponse>> listCompanies(
|
||||
@Parameter(description = "手机号", example = "13800138000")
|
||||
@RequestParam String phone
|
||||
) {
|
||||
return Result.success(authService.listAvailableCompanies(phone));
|
||||
}
|
||||
|
||||
@@ -52,7 +56,10 @@ public class AuthController {
|
||||
|
||||
@Operation(summary = "退出登录")
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
|
||||
public Result<Void> logout(
|
||||
@Parameter(description = "Bearer 访问令牌", example = "Bearer eyJhbGciOiJIUzI1NiJ9.demo.token")
|
||||
@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization
|
||||
) {
|
||||
authService.logout(extractToken(authorization));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.labelsys.backend.dto.response.UserResponse;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
import com.labelsys.backend.service.UserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
@@ -45,14 +46,22 @@ public class CompanyUserController {
|
||||
|
||||
@Operation(summary = "修改员工角色和岗位")
|
||||
@PutMapping("/{userId}/assignment")
|
||||
public Result<Void> updateAssignment(@PathVariable Long userId, @Valid @RequestBody UpdateUserAssignmentRequest request) {
|
||||
public Result<Void> updateAssignment(
|
||||
@Parameter(description = "用户ID", example = "191000000000000021")
|
||||
@PathVariable Long userId,
|
||||
@Valid @RequestBody UpdateUserAssignmentRequest request
|
||||
) {
|
||||
userService.updateAssignment(UserContext.requireUser(), userId, request);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "修改员工状态")
|
||||
@PutMapping("/{userId}/status")
|
||||
public Result<Void> updateStatus(@PathVariable Long userId, @Valid @RequestBody UpdateUserStatusRequest request) {
|
||||
public Result<Void> updateStatus(
|
||||
@Parameter(description = "用户ID", example = "191000000000000021")
|
||||
@PathVariable Long userId,
|
||||
@Valid @RequestBody UpdateUserStatusRequest request
|
||||
) {
|
||||
userService.updateStatus(UserContext.requireUser(), userId, request);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import com.labelsys.backend.annotation.RequirePosition;
|
||||
import com.labelsys.backend.common.Result;
|
||||
import com.labelsys.backend.context.UserContext;
|
||||
import com.labelsys.backend.dto.request.CreateCompanyAdminRequest;
|
||||
import com.labelsys.backend.dto.request.CreateSystemEngineerAdminRequest;
|
||||
import com.labelsys.backend.dto.request.UpdateUserStatusRequest;
|
||||
import com.labelsys.backend.dto.response.UserResponse;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
import com.labelsys.backend.service.UserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
@@ -25,7 +27,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@Tag(name = "平台公司管理员管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/company-admins")
|
||||
@RequirePosition(UserPosition.ADMIN)
|
||||
@RequirePosition(UserPosition.SUPER_ADMIN)
|
||||
@RequiredArgsConstructor
|
||||
public class PlatformCompanyAdminController {
|
||||
|
||||
@@ -33,20 +35,37 @@ public class PlatformCompanyAdminController {
|
||||
|
||||
@Operation(summary = "查询指定公司管理员列表")
|
||||
@GetMapping
|
||||
public Result<List<UserResponse>> listCompanyAdmins(@RequestParam Long companyId) {
|
||||
public Result<List<UserResponse>> listCompanyAdmins(
|
||||
@Parameter(description = "公司ID", example = "191000000000000001")
|
||||
@RequestParam Long companyId
|
||||
) {
|
||||
return Result.success(userService.listCompanyAdmins(UserContext.requireUser(), companyId).stream().map(UserResponse::from).toList());
|
||||
}
|
||||
|
||||
@Operation(summary = "查询所有公司用户列表")
|
||||
@GetMapping("/all")
|
||||
public Result<List<UserResponse>> listAllUsers() {
|
||||
return Result.success(userService.listAllUsers(UserContext.requireUser()).stream().map(UserResponse::from).toList());
|
||||
}
|
||||
|
||||
@Operation(summary = "创建公司管理员")
|
||||
@PostMapping
|
||||
public Result<UserResponse> createCompanyAdmin(@Valid @RequestBody CreateCompanyAdminRequest request) {
|
||||
return Result.success(UserResponse.from(userService.createCompanyAdmin(UserContext.requireUser(), request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "创建系统管理员")
|
||||
@PostMapping("/system-engineer")
|
||||
public Result<UserResponse> createSystemEngineerAdmin(@Valid @RequestBody CreateSystemEngineerAdminRequest request) {
|
||||
return Result.success(UserResponse.from(userService.createSystemEngineerAdmin(UserContext.requireUser(), request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "修改公司管理员状态")
|
||||
@PutMapping("/{companyId}/{userId}/status")
|
||||
public Result<Void> updateCompanyAdminStatus(
|
||||
@Parameter(description = "公司ID", example = "191000000000000001")
|
||||
@PathVariable Long companyId,
|
||||
@Parameter(description = "用户ID", example = "191000000000000021")
|
||||
@PathVariable Long userId,
|
||||
@Valid @RequestBody UpdateUserStatusRequest request
|
||||
) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.labelsys.backend.dto.response.CompanyResponse;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
import com.labelsys.backend.service.CompanyService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
@@ -24,7 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@Tag(name = "平台公司管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/companies")
|
||||
@RequirePosition(UserPosition.ADMIN)
|
||||
@RequirePosition(UserPosition.SUPER_ADMIN)
|
||||
@RequiredArgsConstructor
|
||||
public class PlatformCompanyController {
|
||||
|
||||
@@ -44,7 +45,11 @@ public class PlatformCompanyController {
|
||||
|
||||
@Operation(summary = "修改公司状态")
|
||||
@PutMapping("/{companyId}/status")
|
||||
public Result<Void> updateCompanyStatus(@PathVariable Long companyId, @Valid @RequestBody UpdateCompanyStatusRequest request) {
|
||||
public Result<Void> updateCompanyStatus(
|
||||
@Parameter(description = "公司ID", example = "191000000000000001")
|
||||
@PathVariable Long companyId,
|
||||
@Valid @RequestBody UpdateCompanyStatusRequest request
|
||||
) {
|
||||
companyService.updateStatus(UserContext.requireUser(), companyId, request);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.labelsys.backend.controller;
|
||||
|
||||
import com.labelsys.backend.common.Result;
|
||||
import com.labelsys.backend.context.UserContext;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.SaveImageBboxRequest;
|
||||
import com.labelsys.backend.dto.request.SourceResourcePageQuery;
|
||||
import com.labelsys.backend.dto.request.SourceUploadRequest;
|
||||
import com.labelsys.backend.dto.response.ImageBboxResponse;
|
||||
import com.labelsys.backend.dto.response.SourceResourceResponse;
|
||||
import com.labelsys.backend.dto.response.SourceUploadResponse;
|
||||
import com.labelsys.backend.entity.SourceResource;
|
||||
import com.labelsys.backend.service.SourceResourceService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "资源管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/source-resources")
|
||||
@RequiredArgsConstructor
|
||||
public class SourceResourceController {
|
||||
|
||||
private final SourceResourceService sourceResourceService;
|
||||
|
||||
@Operation(summary = "上传资源")
|
||||
@PostMapping("/upload")
|
||||
public Result<SourceUploadResponse> upload(@ParameterObject @ModelAttribute SourceUploadRequest request) {
|
||||
return Result.success(sourceResourceService.upload(UserContext.requireUser(), request));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询资源")
|
||||
@GetMapping
|
||||
public Result<PageResult<SourceResourceResponse>> page(
|
||||
@ParameterObject @ModelAttribute SourceResourcePageQuery query) {
|
||||
return Result.success(sourceResourceService.pageResources(UserContext.requireUser(), query));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询资源详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<SourceResourceResponse> detail(
|
||||
@Parameter(description = "资源ID", example = "191000000000000101")
|
||||
@PathVariable Long id
|
||||
) {
|
||||
return Result.success(sourceResourceService.getResource(UserContext.requireUser(), id));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除资源")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> delete(
|
||||
@Parameter(description = "资源ID", example = "191000000000000101")
|
||||
@PathVariable Long id
|
||||
) {
|
||||
sourceResourceService.deleteResource(UserContext.requireUser(), id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "下载资源")
|
||||
@GetMapping("/{id}/download")
|
||||
public ResponseEntity<byte[]> downloadResource(
|
||||
@Parameter(description = "资源ID", example = "191000000000000101")
|
||||
@PathVariable Long id
|
||||
) {
|
||||
var currentUser = UserContext.requireUser();
|
||||
byte[] resourceData = sourceResourceService.downloadResource(currentUser, id);
|
||||
|
||||
// 获取资源信息以确定Content-Type
|
||||
SourceResource resource = sourceResourceService.getResourceEntity(id);
|
||||
String contentType = sourceResourceService.getContentType(resource);
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_TYPE, contentType)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getResourceName() + "\"")
|
||||
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.getFileSize()))
|
||||
.body(resourceData);
|
||||
}
|
||||
|
||||
// 添加新接口
|
||||
@Operation(summary = "查询图片资源BBOX标注")
|
||||
@GetMapping("/{id}/bbox")
|
||||
public Result<ImageBboxResponse> getImageBbox(
|
||||
@Parameter(description = "资源ID", example = "191000000000000101")
|
||||
@PathVariable Long id
|
||||
) {
|
||||
return Result.success(sourceResourceService.getImageBbox(UserContext.requireUser(), id));
|
||||
}
|
||||
|
||||
@Operation(summary = "保存图片资源BBOX标注")
|
||||
@PostMapping("/{id}/bbox")
|
||||
public Result<ImageBboxResponse> saveImageBbox(
|
||||
@Parameter(description = "资源ID", example = "191000000000000101")
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody SaveImageBboxRequest request
|
||||
) {
|
||||
return Result.success(sourceResourceService.saveImageBbox(UserContext.requireUser(), id, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除图片资源BBOX标注")
|
||||
@DeleteMapping("/{id}/bbox")
|
||||
public Result<Void> deleteImageBbox(
|
||||
@Parameter(description = "资源ID", example = "191000000000000101")
|
||||
@PathVariable Long id
|
||||
) {
|
||||
sourceResourceService.deleteImageBbox(UserContext.requireUser(), id);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.labelsys.backend.controller;
|
||||
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.labelsys.backend.common.Result;
|
||||
import com.labelsys.backend.context.UserContext;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.SaveSysConfigRequest;
|
||||
import com.labelsys.backend.dto.request.SysConfigPageQuery;
|
||||
import com.labelsys.backend.dto.request.UpdateSysConfigRequest;
|
||||
import com.labelsys.backend.dto.response.SysConfigResponse;
|
||||
import com.labelsys.backend.service.SysConfigService;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Tag(name = "系统配置管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/sys-configs")
|
||||
@RequiredArgsConstructor
|
||||
public class SysConfigController {
|
||||
|
||||
private final SysConfigService sysConfigService;
|
||||
|
||||
@Operation(summary = "创建系统配置")
|
||||
// @RequirePosition(UserPosition.ADMIN)
|
||||
@PostMapping
|
||||
public Result<SysConfigResponse> create(@Valid @RequestBody SaveSysConfigRequest request) {
|
||||
return Result
|
||||
.success(sysConfigService.toResponse(sysConfigService.saveConfig(UserContext.requireUser(), request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新系统配置")
|
||||
// @RequirePosition(UserPosition.ADMIN)
|
||||
@PutMapping("/{id}")
|
||||
public Result<SysConfigResponse> update(
|
||||
@Parameter(description = "配置ID", example = "191000000000000501") @PathVariable Long id,
|
||||
@Valid @RequestBody UpdateSysConfigRequest request) {
|
||||
return Result.success(
|
||||
sysConfigService.toResponse(sysConfigService.updateConfig(UserContext.requireUser(), id, request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询系统配置")
|
||||
@GetMapping
|
||||
public Result<PageResult<SysConfigResponse>> page(@ParameterObject SysConfigPageQuery query) {
|
||||
return Result.success(sysConfigService.pageConfigs(UserContext.requireUser(), query));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询系统配置详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<SysConfigResponse>
|
||||
detail(@Parameter(description = "配置ID", example = "191000000000000501") @PathVariable Long id) {
|
||||
return Result.success(sysConfigService.getConfig(UserContext.requireUser(), id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.labelsys.backend.dto.common;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "分页结果")
|
||||
public record PageResult<T>(
|
||||
@Schema(description = "当前页记录", example = "[]") List<T> records,
|
||||
@Schema(description = "总记录数", example = "2") Long total,
|
||||
@Schema(description = "页码", example = "1") Integer pageNo,
|
||||
@Schema(description = "每页数量", example = "10") Integer pageSize
|
||||
) {
|
||||
public static <T> PageResult<T> from(IPage<T> page) {
|
||||
return new PageResult<>(page.getRecords(), page.getTotal(), (int) page.getCurrent(), (int) page.getSize());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// AnnotationResultHistoryPageQuery.java
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "归档历史分页查询")
|
||||
public record AnnotationResultHistoryPageQuery(
|
||||
@Schema(description = "任务ID", example = "701") Long taskId,
|
||||
@Schema(description = "资源ID", example = "601") Long resourceId,
|
||||
@Schema(description = "页码", example = "1") Integer pageNo,
|
||||
@Schema(description = "每页数量", example = "10") Integer pageSize
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "标注结果分页查询请求")
|
||||
public record AnnotationResultPageQuery(
|
||||
@Schema(description = "任务ID", example = "191000000000000301") Long taskId,
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
|
||||
@Schema(description = "是否需要人工审核", example = "true") Boolean requiresManualReview,
|
||||
@Schema(description = "运行态状态", example = "MANUAL_REVIEW_PENDING") String runtimeStatus,
|
||||
@Schema(description = "页码", example = "1") Integer pageNo,
|
||||
@Schema(description = "每页数量", example = "10") Integer pageSize
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import com.labelsys.backend.enums.TaskType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "标注任务分页查询请求")
|
||||
public record AnnotationTaskPageQuery(
|
||||
@Schema(description = "关键字", example = "运输") String keyword,
|
||||
@Schema(description = "任务类型", example = "EXTRACT_QA") TaskType taskType,
|
||||
@Schema(description = "任务状态", example = "PENDING") String taskStatus,
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
|
||||
@Schema(description = "是否已删除", example = "false") Boolean isDeleted,
|
||||
@Schema(description = "页码", example = "1") Integer pageNo,
|
||||
@Schema(description = "每页数量", example = "10") Integer pageSize
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "BBOX坐标对象")
|
||||
public record BboxCoordinate(
|
||||
@Schema(description = "BBOX标识", example = "bbox_001") String id,
|
||||
@NotNull @Schema(description = "左上角X坐标", example = "100") Integer x,
|
||||
@NotNull @Schema(description = "左上角Y坐标", example = "50") Integer y,
|
||||
@NotNull @Schema(description = "宽度", example = "200") Integer width,
|
||||
@NotNull @Schema(description = "高度", example = "150") Integer height,
|
||||
@Schema(description = "标注标签", example = "车辆") String label
|
||||
//@Schema(description = "置信度", example = "0.95") Double confidence
|
||||
) {
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "修改密码请求")
|
||||
public record ChangePasswordRequest(
|
||||
@Schema(description = "旧密码") @NotBlank(message = "不能为空") String oldPassword,
|
||||
@Schema(description = "新密码") @NotBlank(message = "不能为空") String newPassword,
|
||||
@Schema(description = "确认新密码") @NotBlank(message = "不能为空") String confirmPassword
|
||||
@Schema(description = "旧密码", example = "P@ssw0rd!") @NotBlank(message = "不能为空") String oldPassword,
|
||||
@Schema(description = "新密码", example = "N3wP@ssw0rd!") @NotBlank(message = "不能为空") String newPassword,
|
||||
@Schema(description = "确认新密码", example = "N3wP@ssw0rd!") @NotBlank(message = "不能为空") String confirmPassword
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import com.labelsys.backend.enums.IndustryType;
|
||||
import com.labelsys.backend.enums.TaskType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "创建标注任务请求")
|
||||
public record CreateAnnotationTaskRequest(
|
||||
@Schema(description = "任务名称", example = "运输文档问答抽取任务") @NotBlank(message = "任务名称不能为空") String taskName,
|
||||
@Schema(description = "行业类型", defaultValue = "TRANSPORT", example = "TRANSPORT") @NotNull(message = "行业类型不能为空") IndustryType industryType,
|
||||
@Schema(description = "任务类型", defaultValue = "EXTRACT_QA", example = "EXTRACT_QA") @NotNull(message = "任务类型不能为空") TaskType taskType,
|
||||
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") @NotEmpty(message = "资源列表不能为空") List<Long> resourceIds) {
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "创建公司管理员请求")
|
||||
public record CreateCompanyAdminRequest(
|
||||
@Schema(description = "公司ID") @NotNull(message = "不能为空") Long companyId,
|
||||
@Schema(description = "手机号") @NotBlank(message = "不能为空") String phone,
|
||||
@Schema(description = "公司ID", example = "191000000000000001") @NotNull(message = "不能为空") Long companyId,
|
||||
@Schema(description = "手机号", example = "13800138001") @NotBlank(message = "不能为空") String phone,
|
||||
@Schema(description = "用户名,前端展示用,可为空", example = "platform-ops") String username,
|
||||
@Schema(description = "真实姓名") @NotBlank(message = "不能为空") String realName
|
||||
@Schema(description = "真实姓名", example = "平台运维") @NotBlank(message = "不能为空") String realName
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "创建公司请求")
|
||||
public record CreateCompanyRequest(
|
||||
@Schema(description = "公司编码") @NotBlank(message = "不能为空") String companyCode,
|
||||
@Schema(description = "公司名称") @NotBlank(message = "不能为空") String companyName
|
||||
@Schema(description = "公司编码", example = "ALPHA") @NotBlank(message = "不能为空") String companyCode,
|
||||
@Schema(description = "公司名称", example = "阿尔法标注有限公司") @NotBlank(message = "不能为空") String companyName
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "创建系统工程师管理员请求")
|
||||
public record CreateSystemEngineerAdminRequest(
|
||||
@Schema(description = "手机号", example = "13800138002") @NotBlank(message = "不能为空") String phone,
|
||||
@Schema(description = "用户名,前端展示用,可为空", example = "system-engineer") String username,
|
||||
@Schema(description = "真实姓名", example = "系统工程师") @NotBlank(message = "不能为空") String realName) {
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "创建员工请求")
|
||||
public record CreateUserRequest(
|
||||
@Schema(description = "手机号") @NotBlank(message = "不能为空") String phone,
|
||||
@Schema(description = "手机号", example = "13800138002") @NotBlank(message = "不能为空") String phone,
|
||||
@Schema(description = "用户名,前端展示用,可为空", example = "alpha-admin") String username,
|
||||
@Schema(description = "真实姓名") @NotBlank(message = "不能为空") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") @NotNull(message = "不能为空") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") @NotNull(message = "不能为空") UserPosition position
|
||||
@Schema(description = "真实姓名", example = "张审核") @NotBlank(message = "不能为空") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师", example = "EMPLOYEE") @NotNull(message = "不能为空") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员", example = "ANNOTATOR") @NotNull(message = "不能为空") UserPosition position
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "登录请求")
|
||||
public record LoginRequest(
|
||||
@Schema(description = "手机号") @NotBlank(message = "不能为空") String phone,
|
||||
@Schema(description = "公司编码") @NotBlank(message = "不能为空") String companyCode,
|
||||
@Schema(description = "登录密码") @NotBlank(message = "不能为空") String password
|
||||
@Schema(description = "手机号", example = "平台管理员:13900000000") @NotBlank(message = "不能为空") String phone,
|
||||
@Schema(description = "公司编码", example = "平台编号:PLATFORM") @NotBlank(message = "不能为空") String companyCode,
|
||||
@Schema(description = "登录密码", example = "平台密码:admin@123") @NotBlank(message = "不能为空") String password
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "合并审核结果请求")
|
||||
public record MergeReviewResultRequest(
|
||||
@Schema(description = "合并后的答案映射,key为qa记录ID,value为合并后的答案")
|
||||
Map<String, String> mergedAnswers,
|
||||
|
||||
@Schema(description = "审核备注")
|
||||
String reviewComment
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "保存图片BBOX请求")
|
||||
public record SaveImageBboxRequest(
|
||||
@NotEmpty @Schema(description = "BBOX坐标列表")
|
||||
@Valid List<BboxCoordinate> bboxes,
|
||||
@Schema(description = "备注") String remark
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "保存系统配置请求")
|
||||
public record SaveSysConfigRequest(
|
||||
@Schema(description = "配置类型", example = "MODEL") @NotBlank(message = "配置类型不能为空") String configType,
|
||||
@Schema(description = "配置名称", example = "qwen-plus-extract") @NotBlank(message = "配置名称不能为空") String configName,
|
||||
@Schema(description = "配置值",
|
||||
example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") @NotBlank(
|
||||
message = "配置值不能为空") String configValue,
|
||||
@Schema(description = "配置状态", example = "ENABLED") String status) {}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "资源分页查询请求")
|
||||
public record SourceResourcePageQuery(
|
||||
@Schema(description = "关键字", example = "运输") String keyword,
|
||||
@Schema(description = "资源类型", example = "TEXT") String resourceType,
|
||||
@Schema(description = "页码(可选,与pageSize同时提供时启用分页)", example = "1") Integer pageNo,
|
||||
@Schema(description = "每页数量(可选,与pageNo同时提供时启用分页)", example = "10") Integer pageSize
|
||||
) {
|
||||
/**
|
||||
* 判断是否需要分页
|
||||
* @return true表示需要分页,false表示查询全部
|
||||
*/
|
||||
public boolean needPagination() {
|
||||
return pageNo != null && pageSize != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Data
|
||||
@Schema(description = "资源上传请求")
|
||||
public class SourceUploadRequest {
|
||||
|
||||
@Schema(description = "资源名称", example = "2026年4月运输巡检记录")
|
||||
private String resourceName;
|
||||
|
||||
@Schema(description = "资源类型:TEXT、IMAGE、VIDEO", example = "TEXT")
|
||||
private String resourceType;
|
||||
|
||||
@Schema(description = "备注", example = "第一批导入样本")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "上传文件", example = "inspection-record.txt")
|
||||
private MultipartFile file;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "系统配置分页查询请求")
|
||||
public record SysConfigPageQuery(@Schema(description = "配置类型", example = "MODEL") String configType,
|
||||
@Schema(description = "配置名称", example = "qwen-plus-extract") String configName,
|
||||
@Schema(description = "配置状态", example = "ENABLED") String status,
|
||||
@Schema(description = "页码", example = "1") Integer pageNo,
|
||||
@Schema(description = "每页数量", example = "10") Integer pageSize) {}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.labelsys.backend.enums.IndustryType;
|
||||
import com.labelsys.backend.enums.TaskType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "更新标注任务请求")
|
||||
public record UpdateAnnotationTaskRequest(
|
||||
@Schema(description = "行业类型", example = "TRANSPORT") IndustryType industryType,
|
||||
@Schema(description = "任务类型", example = "EXTRACT_QA") TaskType taskType,
|
||||
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") List<Long> resourceIds) {
|
||||
}
|
||||
@@ -6,6 +6,6 @@ import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "修改公司状态请求")
|
||||
public record UpdateCompanyStatusRequest(
|
||||
@Schema(description = "公司状态,枚举值:ENABLED启用、DISABLED禁用") @NotNull(message = "不能为空") CompanyStatus status
|
||||
@Schema(description = "公司状态,枚举值:ENABLED启用、DISABLED禁用", example = "ENABLED") @NotNull(message = "不能为空") CompanyStatus status
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "更新系统配置请求")
|
||||
public record UpdateSysConfigRequest(
|
||||
|
||||
@Schema(description = "配置类型", example = "MODEL") String configType,
|
||||
@Schema(description = "配置名称", example = "qwen-plus-extract") String configName,
|
||||
@Schema(description = "配置值",
|
||||
example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") @NotBlank(
|
||||
message = "配置值不能为空") String configValue,
|
||||
@Schema(description = "配置状态", example = "ENABLED") String status) {}
|
||||
@@ -7,7 +7,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "修改员工角色岗位请求")
|
||||
public record UpdateUserAssignmentRequest(
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") @NotNull(message = "不能为空") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") @NotNull(message = "不能为空") UserPosition position
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师", example = "EMPLOYEE") @NotNull(message = "不能为空") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员", example = "REVIEWER") @NotNull(message = "不能为空") UserPosition position
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "修改员工状态请求")
|
||||
public record UpdateUserStatusRequest(
|
||||
@Schema(description = "用户状态,枚举值:ENABLED启用、DISABLED禁用") @NotNull(message = "不能为空") UserStatus status
|
||||
@Schema(description = "用户状态,枚举值:ENABLED启用、DISABLED禁用", example = "DISABLED") @NotNull(message = "不能为空") UserStatus status
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "标注结果比对响应")
|
||||
public record AnnotationResultCompareResponse(
|
||||
@Schema(description = "结果ID", example = "191000000000000401") Long id,
|
||||
@Schema(description = "任务ID", example = "191000000000000301") Long taskId,
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
|
||||
|
||||
@Schema(description = "问答对列表") List<QaRecord> qaRecords,
|
||||
@Schema(description = "差异列表") List<DiffRecord> diffRecords,
|
||||
@Schema(description = "资源预览路径", example = "preview/191000000000000101/index.html") String sourcePreviewPath
|
||||
) {
|
||||
|
||||
@Schema(description = "问答记录")
|
||||
public record QaRecord(
|
||||
@Schema(description = "记录ID", example = "qa_001") String id,
|
||||
@Schema(description = "问题", example = "运输时效是多久?") String question,
|
||||
@Schema(description = "答案", example = "3天") String answer,
|
||||
@Schema(description = "是否需要审核", example = "true") Boolean requiresReview
|
||||
) {}
|
||||
|
||||
@Schema(description = "差异记录")
|
||||
public record DiffRecord(
|
||||
@Schema(description = "关联的问答记录ID", example = "qa_001") String qaId,
|
||||
@Schema(description = "问题", example = "运输时效是多久?") String question,
|
||||
@Schema(description = "提取模型答案", example = "3天") String extractAnswer,
|
||||
@Schema(description = "校验模型答案", example = "72小时") String verifyAnswer,
|
||||
@Schema(description = "差异原因", example = "时间单位不一致") String diffReason,
|
||||
@Schema(description = "合并后的最终答案", example = "72小时(3天)") String mergedAnswer
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import com.labelsys.backend.enums.AnnotationResultStatus;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "标注结果详情响应(包含文件内容)")
|
||||
public record AnnotationResultDetailResponse(
|
||||
@Schema(description = "结果ID", example = "191000000000000401") Long id,
|
||||
@Schema(description = "任务ID", example = "191000000000000301") Long taskId,
|
||||
@Schema(description = "任务名称", example = "产品说明书标注") String taskName,
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
|
||||
@Schema(description = "资源名称", example = "产品A说明书.pdf") String resourceName,
|
||||
@Schema(description = "标注结果状态", example = "MANUAL_REVIEW_PENDING") AnnotationResultStatus runtimeStatus,
|
||||
@Schema(description = "是否需要人工审核", example = "true") Boolean requiresManualReview,
|
||||
@Schema(description = "是否已删除", example = "false") Boolean isDeleted,
|
||||
|
||||
// 文件路径
|
||||
@Schema(description = "问答内容文件路径", example = "annotation-results/2/qa/801.json") String qaContentFilePath,
|
||||
@Schema(description = "差异摘要文件路径", example = "annotation-results/2/diff/801.json") String diffSummaryFilePath,
|
||||
|
||||
// 文件内容
|
||||
@Schema(description = "问答内容") QaContentDto qaContent,
|
||||
@Schema(description = "差异摘要(需要审核时有值)") DiffContentDto diffSummary,
|
||||
|
||||
@Schema(description = "审核备注", example = "需统一时间字段口径。") String reviewComment,
|
||||
@Schema(description = "审核时间", example = "2026-04-27T11:00:00") LocalDateTime reviewedAt,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T10:40:00") LocalDateTime createdAt
|
||||
) {
|
||||
@Schema(description = "问答内容结构")
|
||||
public record QaContentDto(
|
||||
@Schema(description = "问答记录列表") List<QaRecordDto> records
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(description = "问答记录")
|
||||
public record QaRecordDto(
|
||||
@Schema(description = "记录ID", example = "q1") String id,
|
||||
@Schema(description = "问题", example = "产品重量是多少?") String question,
|
||||
@Schema(description = "答案", example = "5kg") String answer,
|
||||
@Schema(description = "是否需要审核", example = "false") Boolean requiresReview
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(description = "差异摘要结构")
|
||||
public record DiffContentDto(
|
||||
@Schema(description = "差异记录列表") List<DiffRecordDto> records
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(description = "差异记录")
|
||||
public record DiffRecordDto(
|
||||
@Schema(description = "关联问答ID", example = "q2") String qaId,
|
||||
@Schema(description = "问题", example = "保修期多久?") String question,
|
||||
@Schema(description = "抽取答案", example = "2年") String extractAnswer,
|
||||
@Schema(description = "验证答案", example = "3年") String verifyAnswer,
|
||||
@Schema(description = "差异原因", example = "抽取与验证结果不一致") String diffReason,
|
||||
@Schema(description = "合并后答案") String mergedAnswer
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// AnnotationResultHistoryResponse.java
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "归档历史响应")
|
||||
public record AnnotationResultHistoryResponse(
|
||||
@Schema(description = "历史记录ID, 不显示", example = "901") Long id,
|
||||
//@Schema(description = "来源结果ID", example = "802") Long sourceResultId,
|
||||
@Schema(description = "任务ID", example = "191000000000000301") Long taskId,
|
||||
@Schema(description = "任务名称", example = "产品说明书标注") String taskName, // 新增
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
|
||||
@Schema(description = "资源名称", example = "产品A说明书.pdf") String resourceName, // 新增
|
||||
|
||||
@Schema(description = "问答内容文件路径", example = "annotation-results/2/qa/802.json") String qaContentFilePath,
|
||||
@Schema(description = "归档原因", example = "审核通过后归档") String archiveReason,
|
||||
@Schema(description = "归档操作人ID", example = "5") Long archivedBy,
|
||||
@Schema(description = "归档时间", example = "2026-05-06T10:30:00") LocalDateTime archivedAt,
|
||||
@Schema(description = "创建时间", example = "2026-05-06T10:30:00") LocalDateTime createdAt,
|
||||
@Schema(description = "审核人ID,自动归档时为null", example = "5") Long reviewerId,
|
||||
@Schema(description = "审核人姓名,自动归档时为auto", example = "张三") String reviewerName,
|
||||
@Schema(description = "审核意见,自动归档时为auto", example = "内容符合要求") String reviewerComment
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import com.labelsys.backend.enums.AnnotationResultStatus;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "标注结果响应")
|
||||
public record AnnotationResultResponse(
|
||||
@Schema(description = "结果ID", example = "191000000000000401") Long id,
|
||||
@Schema(description = "任务ID", example = "191000000000000301") Long taskId,
|
||||
@Schema(description = "任务名称", example = "产品说明书标注") String taskName, // 新增
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
|
||||
@Schema(description = "资源名称", example = "产品A说明书.pdf") String resourceName, // 新增
|
||||
@Schema(description = "标注结果状态", example = "MANUAL_REVIEW_PENDING") AnnotationResultStatus runtimeStatus,
|
||||
@Schema(description = "是否需要人工审核", example = "true") Boolean requiresManualReview,
|
||||
@Schema(description = "是否已删除", example = "false") Boolean isDeleted,
|
||||
@Schema(description = "问答内容文件路径", example = "annotation-results/2/qa/801.json") String qaContentFilePath,
|
||||
@Schema(description = "差异摘要文件路径", example = "annotation-results/2/diff/801.json") String diffSummaryFilePath,
|
||||
@Schema(description = "审核备注", example = "需统一时间字段口径。") String reviewComment,
|
||||
@Schema(description = "审核时间", example = "2026-04-27T11:00:00") LocalDateTime reviewedAt,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T10:40:00") LocalDateTime createdAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import com.labelsys.backend.enums.IndustryType;
|
||||
import com.labelsys.backend.enums.TaskType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "标注任务响应")
|
||||
public record AnnotationTaskResponse(
|
||||
@Schema(description = "任务ID", example = "191000000000000301") Long id,
|
||||
@Schema(description = "任务名称", example = "运输文档问答抽取任务") String taskName,
|
||||
@Schema(description = "行业类型", example = "transport") IndustryType industryType,
|
||||
@Schema(description = "任务类型", example = "EXTRACT_QA") TaskType taskType,
|
||||
@Schema(description = "任务状态", example = "PENDING") String taskStatus,
|
||||
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") List<Long> resourceIds,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T10:20:00") LocalDateTime createdAt,
|
||||
@Schema(description = "更新时间", example = "2026-04-27T10:30:00") LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "可登录公司选项")
|
||||
public record CompanyOptionResponse(
|
||||
@Schema(description = "公司ID") Long companyId,
|
||||
@Schema(description = "公司编码") String companyCode,
|
||||
@Schema(description = "公司名称") String companyName
|
||||
@Schema(description = "公司ID", example = "191000000000000001") Long companyId,
|
||||
@Schema(description = "公司编码", example = "ALPHA") String companyCode,
|
||||
@Schema(description = "公司名称", example = "阿尔法标注有限公司") String companyName
|
||||
) {
|
||||
public static CompanyOptionResponse from(SysCompany company) {
|
||||
return new CompanyOptionResponse(company.getId(), company.getCompanyCode(), company.getCompanyName());
|
||||
|
||||
@@ -6,10 +6,10 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "公司响应")
|
||||
public record CompanyResponse(
|
||||
@Schema(description = "公司ID") Long companyId,
|
||||
@Schema(description = "公司编码") String companyCode,
|
||||
@Schema(description = "公司名称") String companyName,
|
||||
@Schema(description = "公司状态,枚举值:ENABLED启用、DISABLED禁用") CompanyStatus status
|
||||
@Schema(description = "公司ID", example = "191000000000000001") Long companyId,
|
||||
@Schema(description = "公司编码", example = "ALPHA") String companyCode,
|
||||
@Schema(description = "公司名称", example = "阿尔法标注有限公司") String companyName,
|
||||
@Schema(description = "公司状态,枚举值:ENABLED启用、DISABLED禁用", example = "ENABLED") CompanyStatus status
|
||||
) {
|
||||
public static CompanyResponse from(SysCompany company) {
|
||||
return new CompanyResponse(company.getId(), company.getCompanyCode(), company.getCompanyName(), company.getStatus());
|
||||
|
||||
@@ -7,16 +7,16 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "当前登录用户响应")
|
||||
public record CurrentUserResponse(
|
||||
@Schema(description = "用户ID") Long userId,
|
||||
@Schema(description = "公司ID") Long companyId,
|
||||
@Schema(description = "公司编码") String companyCode,
|
||||
@Schema(description = "公司名称") String companyName,
|
||||
@Schema(description = "手机号") String phone,
|
||||
@Schema(description = "用户ID", example = "191000000000000021") Long userId,
|
||||
@Schema(description = "公司ID", example = "191000000000000001") Long companyId,
|
||||
@Schema(description = "公司编码", example = "ALPHA") String companyCode,
|
||||
@Schema(description = "公司名称", example = "阿尔法标注有限公司") String companyName,
|
||||
@Schema(description = "手机号", example = "13800138002") String phone,
|
||||
@Schema(description = "用户名,可为空", example = "alpha-admin") String username,
|
||||
@Schema(description = "真实姓名") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||
@Schema(description = "是否必须修改密码") boolean mustChangePassword
|
||||
@Schema(description = "真实姓名", example = "张审核") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师", example = "EMPLOYEE") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员", example = "REVIEWER") UserPosition position,
|
||||
@Schema(description = "是否必须修改密码", example = "false") boolean mustChangePassword
|
||||
) {
|
||||
public static CurrentUserResponse from(LoginUser loginUser) {
|
||||
return new CurrentUserResponse(
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import com.labelsys.backend.entity.BizDataRecord;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "数据记录响应")
|
||||
public record DataRecordResponse(
|
||||
@Schema(description = "记录ID") Long id,
|
||||
@Schema(description = "公司ID") Long companyId,
|
||||
@Schema(description = "创建人ID") Long creatorId,
|
||||
@Schema(description = "创建人角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole creatorRole,
|
||||
@Schema(description = "记录名称") String recordName
|
||||
) {
|
||||
public static DataRecordResponse from(BizDataRecord record) {
|
||||
return new DataRecordResponse(
|
||||
record.getId(),
|
||||
record.getCompanyId(),
|
||||
record.getCreatorId(),
|
||||
record.getCreatorRole(),
|
||||
record.getRecordName()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "文件内容响应")
|
||||
public record FileContentResponse(
|
||||
@Schema(description = "文件路径", example = "annotation-results/2/qa/801.json") String filePath,
|
||||
@Schema(description = "文件内容") String content,
|
||||
@Schema(description = "文件大小(字节)", example = "1024") Integer size
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "图片BBOX响应")
|
||||
public record ImageBboxResponse(
|
||||
@Schema(description = "bbox标识ID", example = "191000000000000101") Long id,
|
||||
@Schema(description = "资源ID", example = "191000000000000102") Long resourceId,
|
||||
@Schema(description = "BBOX坐标列表") List<BboxCoordinateResponse> bboxes,
|
||||
@Schema(description = "备注", example = "车辆检测标注") String remark,
|
||||
@Schema(description = "创建人名称", example = "张审核") String creatorName,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T10:00:00") LocalDateTime createdAt,
|
||||
@Schema(description = "更新时间", example = "2026-04-27T10:05:00") LocalDateTime updatedAt
|
||||
) {
|
||||
|
||||
@Schema(description = "BBOX坐标响应对象")
|
||||
public record BboxCoordinateResponse(
|
||||
@Schema(description = "BBOX标识", example = "bbox_001") String id,
|
||||
@Schema(description = "左上角X坐标", example = "100") Integer x,
|
||||
@Schema(description = "左上角Y坐标", example = "50") Integer y,
|
||||
@Schema(description = "宽度", example = "200") Integer width,
|
||||
@Schema(description = "高度", example = "150") Integer height,
|
||||
@Schema(description = "标注标签", example = "车辆") String label
|
||||
// @Schema(description = "置信度", example = "0.95") Double confidence
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,14 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "登录响应")
|
||||
public record LoginResponse(
|
||||
@Schema(description = "访问令牌") String token,
|
||||
@Schema(description = "当前公司信息") CompanyOptionResponse company,
|
||||
@Schema(description = "手机号") String phone,
|
||||
@Schema(description = "访问令牌", example = "eyJhbGciOiJIUzI1NiJ9.demo.token") String token,
|
||||
@Schema(description = "当前公司信息", example = "{\"companyId\":191000000000000001,\"companyCode\":\"ALPHA\",\"companyName\":\"阿尔法标注有限公司\"}") CompanyOptionResponse company,
|
||||
@Schema(description = "手机号", example = "13800138002") String phone,
|
||||
@Schema(description = "用户名,可为空", example = "alpha-admin") String username,
|
||||
@Schema(description = "真实姓名") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||
@Schema(description = "是否必须修改密码") boolean mustChangePassword
|
||||
@Schema(description = "真实姓名", example = "张审核") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师", example = "EMPLOYEE") UserRole role,
|
||||
@Schema(description = "岗位枚举,枚举值:ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN公司管理员、SUPER_ADMIN超级管理员", example = "REVIEWER") UserPosition position,
|
||||
@Schema(description = "是否必须修改密码", example = "false") boolean mustChangePassword
|
||||
) {
|
||||
public static LoginResponse from(String token, LoginUser loginUser, SysCompany company) {
|
||||
return new LoginResponse(
|
||||
|
||||
@@ -5,10 +5,10 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "菜单响应")
|
||||
public record MenuResponse(
|
||||
@Schema(description = "菜单编码") String menuCode,
|
||||
@Schema(description = "菜单名称") String menuName,
|
||||
@Schema(description = "菜单路径") String path,
|
||||
@Schema(description = "排序") Integer sortOrder
|
||||
@Schema(description = "菜单编码", example = "annotation.task.list") String menuCode,
|
||||
@Schema(description = "菜单名称", example = "标注任务") String menuName,
|
||||
@Schema(description = "菜单路径", example = "/annotation/tasks") String path,
|
||||
@Schema(description = "排序", example = "10") Integer sortOrder
|
||||
) {
|
||||
public static MenuResponse from(SysMenu menu) {
|
||||
return new MenuResponse(menu.getMenuCode(), menu.getMenuName(), menu.getPath(), menu.getSortOrder());
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "合并审核结果响应")
|
||||
public record MergeReviewResultResponse(
|
||||
@Schema(description = "运行态结果ID", example = "191000000000000401") Long resultId,
|
||||
@Schema(description = "历史结果ID", example = "191000000000000451") Long historyId,
|
||||
@Schema(description = "归档原因", example = "MANUAL_REVIEW_MERGED") String archiveReason,
|
||||
@Schema(description = "归档时间", example = "2026-04-27T11:05:00") LocalDateTime archivedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "资源响应")
|
||||
public record SourceResourceResponse(
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long id,
|
||||
@Schema(description = "资源名称", example = "2026年4月运输巡检记录") String resourceName,
|
||||
@Schema(description = "资源类型", example = "TEXT") String resourceType,
|
||||
@Schema(description = "桶名称", example = "annotation-source") String bucketName,
|
||||
@Schema(description = "文件路径", example = "company/191000000000000001/text/191000000000000101.txt") String filePath,
|
||||
@Schema(description = "文件大小", example = "20480") Long fileSize,
|
||||
@Schema(description = "存储提供方", example = "rustfs") String storageProvider,
|
||||
@Schema(description = "是否有BBOX标注,不显示", example = "false") Boolean hasBbox,
|
||||
@Schema(description = "备注", example = "第一批导入样本") String remark,
|
||||
@Schema(description = "创建人名称", example = "张审核") String creatorName,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T10:00:00") LocalDateTime createdAt,
|
||||
@Schema(description = "更新时间", example = "2026-04-27T10:05:00") LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "资源上传响应")
|
||||
public record SourceUploadResponse(
|
||||
@Schema(description = "资源ID", example = "191000000000000101") Long id,
|
||||
@Schema(description = "资源名称", example = "2026年4月运输巡检记录") String resourceName,
|
||||
@Schema(description = "资源类型", example = "TEXT") String resourceType,
|
||||
@Schema(description = "桶名称", example = "annotation-source") String bucketName,
|
||||
@Schema(description = "文件路径", example = "company/191000000000000001/text/191000000000000101.txt") String filePath,
|
||||
@Schema(description = "文件大小", example = "20480") Long fileSize,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T10:00:00") LocalDateTime createdAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "系统配置响应")
|
||||
public record SysConfigResponse(
|
||||
@Schema(description = "配置ID", example = "191000000000000501") Long id,
|
||||
@Schema(description = "配置类型", example = "MODEL") String configType,
|
||||
@Schema(description = "配置名称", example = "qwen-plus-extract") String configName,
|
||||
@Schema(description = "配置值", example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") String configValue,
|
||||
@Schema(description = "配置状态", example = "ENABLED") String status,
|
||||
@Schema(description = "创建人ID", example = "191000000000000021") Long creatorId,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T09:50:00") LocalDateTime createdAt,
|
||||
@Schema(description = "更新时间", example = "2026-04-27T10:15:00") LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -8,15 +8,15 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "用户响应")
|
||||
public record UserResponse(
|
||||
@Schema(description = "用户ID") Long userId,
|
||||
@Schema(description = "公司ID") Long companyId,
|
||||
@Schema(description = "手机号") String phone,
|
||||
@Schema(description = "用户ID", example = "191000000000000021") Long userId,
|
||||
@Schema(description = "公司ID", example = "191000000000000001") Long companyId,
|
||||
@Schema(description = "手机号", example = "13800138002") String phone,
|
||||
@Schema(description = "用户名,可为空", example = "alpha-admin") String username,
|
||||
@Schema(description = "真实姓名") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||
@Schema(description = "用户状态,枚举值:ENABLED启用、DISABLED禁用") UserStatus status,
|
||||
@Schema(description = "是否必须修改密码") boolean mustChangePassword
|
||||
@Schema(description = "真实姓名", example = "张审核") String realName,
|
||||
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师", example = "EMPLOYEE") UserRole role,
|
||||
@Schema(description = "岗位,枚举值:ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员", example = "REVIEWER") UserPosition position,
|
||||
@Schema(description = "用户状态,枚举值:ENABLED启用、DISABLED禁用", example = "ENABLED") UserStatus status,
|
||||
@Schema(description = "是否必须修改密码", example = "false") boolean mustChangePassword
|
||||
) {
|
||||
public static UserResponse from(SysUser user) {
|
||||
return new UserResponse(
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("annotation_result")
|
||||
public class AnnotationResult {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
|
||||
@TableField("company_id")
|
||||
private Long companyId;
|
||||
|
||||
@TableField("creator_id")
|
||||
private Long creatorId;
|
||||
|
||||
@TableField("creator_role")
|
||||
private String creatorRole;
|
||||
|
||||
@TableField("task_id")
|
||||
private Long taskId;
|
||||
|
||||
@TableField("resource_id")
|
||||
private Long resourceId;
|
||||
|
||||
// 添加字段
|
||||
@TableField("task_name")
|
||||
private String taskName;
|
||||
|
||||
@TableField("resource_name")
|
||||
private String resourceName;
|
||||
|
||||
@TableField("qa_content_file_path")
|
||||
private String qaContentFilePath;
|
||||
|
||||
@TableField("diff_summary_file_path")
|
||||
private String diffSummaryFilePath;
|
||||
|
||||
@TableField("requires_manual_review")
|
||||
private Boolean requiresManualReview;
|
||||
|
||||
@TableField("is_deleted")
|
||||
private Boolean isDeleted;
|
||||
|
||||
@TableField("reviewer_id")
|
||||
private Long reviewerId;
|
||||
|
||||
@TableField("review_comment")
|
||||
private String reviewComment;
|
||||
|
||||
@TableField("reviewed_at")
|
||||
private LocalDateTime reviewedAt;
|
||||
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("annotation_result_history")
|
||||
public class AnnotationResultHistory {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private Long creatorId;
|
||||
private String creatorRole;
|
||||
private Long sourceResultId;
|
||||
private Long taskId;
|
||||
private Long resourceId;
|
||||
private String taskName;
|
||||
private String resourceName;
|
||||
//private String qaContentJson;
|
||||
// private String qaContentStorageMode;
|
||||
private String qaContentFilePath;
|
||||
private String archiveReason;
|
||||
private Long archivedBy;
|
||||
private LocalDateTime archivedAt;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
// 新增审核人相关字段
|
||||
private Long reviewerId;
|
||||
private String reviewerName;
|
||||
private String reviewerComment;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.labelsys.backend.enums.IndustryType;
|
||||
import com.labelsys.backend.enums.TaskType;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("annotation_task")
|
||||
public class AnnotationTask {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private Long creatorId;
|
||||
private UserRole creatorRole;
|
||||
private String taskName;
|
||||
private IndustryType industryType;
|
||||
private TaskType taskType;
|
||||
private String taskStatus;
|
||||
private Boolean isDeleted;
|
||||
private LocalDateTime startedAt;
|
||||
private LocalDateTime finishedAt;
|
||||
private String errorMessage;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("annotation_task_resource")
|
||||
public class AnnotationTaskResource {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private Long taskId;
|
||||
private Long resourceId;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -14,9 +11,7 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("biz_data_record")
|
||||
public class BizDataRecord {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private Long creatorId;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 图片BBOX标注表实体类
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("image_bbox_annotation")
|
||||
public class ImageBboxAnnotation {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 所属公司ID
|
||||
*/
|
||||
private Long companyId;
|
||||
|
||||
/**
|
||||
* 关联的图片资源ID
|
||||
*/
|
||||
private Long resourceId;
|
||||
|
||||
/**
|
||||
* bbox坐标信息JSON数组
|
||||
*/
|
||||
private String bboxJson;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建人用户ID
|
||||
*/
|
||||
private Long creatorId;
|
||||
|
||||
/**
|
||||
* 创建人数据权限角色,默认 EMPLOYEE
|
||||
*/
|
||||
private UserRole creatorRole;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("source_resource")
|
||||
public class SourceResource {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private Long creatorId;
|
||||
private UserRole creatorRole;
|
||||
private String resourceName;
|
||||
private String resourceType;
|
||||
private String bucketName;
|
||||
private String filePath;
|
||||
private Long fileSize;
|
||||
private String storageProvider;
|
||||
private Boolean hasBbox;
|
||||
private String remark;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
29
src/main/java/com/labelsys/backend/entity/SysConfig.java
Normal file
29
src/main/java/com/labelsys/backend/entity/SysConfig.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("sys_config")
|
||||
public class SysConfig {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private String configType;
|
||||
private String configName;
|
||||
private String configValue;
|
||||
private String status;
|
||||
private Long creatorId;
|
||||
private String creatorRole;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -18,10 +18,10 @@ public class SysMenu {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private String permissionCode;
|
||||
private String menuCode;
|
||||
private String menuName;
|
||||
private String path;
|
||||
private String visiblePositions;
|
||||
private Integer sortOrder;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "标注结果状态枚举值:MANUAL_REVIEW_PENDING待人工审核、MANUAL_REVIEW_PENDING待自动归档、ARCHIVED已归档")
|
||||
public enum AnnotationResultStatus {
|
||||
MANUAL_REVIEW_PENDING,
|
||||
AUTO_ARCHIVE_PENDING,
|
||||
ARCHIVED
|
||||
}
|
||||
9
src/main/java/com/labelsys/backend/enums/ConfigMode.java
Normal file
9
src/main/java/com/labelsys/backend/enums/ConfigMode.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "模型配置模式,枚举值:SELECT:选择已有模型配置、MANUAL:手动录入新模型")
|
||||
public enum ConfigMode {
|
||||
SELECT,
|
||||
MANUAL
|
||||
}
|
||||
15
src/main/java/com/labelsys/backend/enums/ConfigType.java
Normal file
15
src/main/java/com/labelsys/backend/enums/ConfigType.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "系统配置类型,枚举值:model:大模型配置、prompt:提示词配置, system:其他配置项")
|
||||
public enum ConfigType {
|
||||
MODEL,
|
||||
PROMPT,
|
||||
SYSTEM;
|
||||
|
||||
public static boolean isValid(String value) {
|
||||
return Arrays.stream(values()).anyMatch(type -> type.name().equals(value));
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/labelsys/backend/enums/IndustryType.java
Normal file
12
src/main/java/com/labelsys/backend/enums/IndustryType.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "行业类型,枚举值:TRANSPORT:交通、ELECTRICITY:电力、FINANCE:金融、MEDICAL:医疗、EDUCATION:教育")
|
||||
public enum IndustryType {
|
||||
TRANSPORT,
|
||||
ELECTRICITY,
|
||||
FINANCE,
|
||||
MEDICAL,
|
||||
EDUCATION
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "问答对存储形式,枚举值:INLINE:记录中保存、EXTERNAL:外部存储器rustfs")
|
||||
public enum QaContentStorageMode {
|
||||
INLINE,
|
||||
EXTERNAL;
|
||||
public static boolean isValid(String value) {
|
||||
return Arrays.stream(values()).anyMatch(mode -> mode.name().equals(value));
|
||||
}
|
||||
}
|
||||
16
src/main/java/com/labelsys/backend/enums/ResourceType.java
Normal file
16
src/main/java/com/labelsys/backend/enums/ResourceType.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "资源类型,枚举值:TEXT:文本、IMAGE:图片、VIDEO:视频")
|
||||
public enum ResourceType {
|
||||
TEXT,
|
||||
IMAGE,
|
||||
VIDEO;
|
||||
|
||||
public static boolean isValid(String value) {
|
||||
return Arrays.stream(values()).anyMatch(type -> type.name().equals(value));
|
||||
}
|
||||
}
|
||||
17
src/main/java/com/labelsys/backend/enums/TaskStatus.java
Normal file
17
src/main/java/com/labelsys/backend/enums/TaskStatus.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "任务状态枚举值:PENDING:待执行、RUNNING:处理中、COMPLETED:已完成、FAILED:失败")
|
||||
public enum TaskStatus {
|
||||
PENDING,
|
||||
RUNNING,
|
||||
COMPLETED,
|
||||
FAILED;
|
||||
|
||||
public static boolean isValid(String value) {
|
||||
return Arrays.stream(values()).anyMatch(status -> status.name().equals(value));
|
||||
}
|
||||
}
|
||||
9
src/main/java/com/labelsys/backend/enums/TaskType.java
Normal file
9
src/main/java/com/labelsys/backend/enums/TaskType.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "标注任务类型枚举值:EXTRACT_QA:抽取任务、FINE_TUNE:微调任务")
|
||||
public enum TaskType {
|
||||
EXTRACT_QA,
|
||||
FINE_TUNE
|
||||
}
|
||||
@@ -6,13 +6,9 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@Schema(description = "岗位枚举,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员")
|
||||
@Schema(description = "岗位枚举,枚举值:ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN公司管理员、SUPER_ADMIN超级管理员")
|
||||
public enum UserPosition {
|
||||
UPLOADER(1),
|
||||
ANNOTATOR(2),
|
||||
DATA_TRAINER(3),
|
||||
REVIEWER(4),
|
||||
ADMIN(5);
|
||||
ANNOTATOR(1), DATA_TRAINER(2), REVIEWER(3), ADMIN(4), SUPER_ADMIN(5);
|
||||
|
||||
private final int level;
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
package com.labelsys.backend.interceptor;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import com.labelsys.backend.annotation.RequirePosition;
|
||||
import com.labelsys.backend.common.exception.ForbiddenException;
|
||||
import com.labelsys.backend.common.exception.UnauthorizedException;
|
||||
@@ -8,48 +16,36 @@ import com.labelsys.backend.context.UserContext;
|
||||
import com.labelsys.backend.entity.SysCompany;
|
||||
import com.labelsys.backend.entity.SysUser;
|
||||
import com.labelsys.backend.enums.CompanyStatus;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
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 jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final Set<String> OPEN_PATHS = Set.of(
|
||||
"/label/api/auth/companies",
|
||||
"/label/api/auth/login",
|
||||
"/label/swagger-ui.html",
|
||||
"/label/v3/api-docs",
|
||||
"/label/v3/api-docs/swagger-config"
|
||||
);
|
||||
"/api/auth/companies", "/label/api/auth/companies",
|
||||
"/api/auth/login", "/label/api/auth/login",
|
||||
"/swagger-ui.html", "/label/swagger-ui.html",
|
||||
"/v3/api-docs", "/label/v3/api-docs",
|
||||
"/v3/api-docs/swagger-config", "/label/v3/api-docs/swagger-config");
|
||||
|
||||
private static final Set<String> ALLOWED_WHEN_MUST_CHANGE_PASSWORD = Set.of(
|
||||
"/label/api/auth/change-password",
|
||||
"/label/api/auth/logout",
|
||||
"/label/api/auth/me"
|
||||
);
|
||||
"/api/auth/change-password", "/label/api/auth/change-password",
|
||||
"/api/auth/logout", "/label/api/auth/logout",
|
||||
"/api/auth/me", "/label/api/auth/me");
|
||||
|
||||
private final TokenSessionRepository tokenSessionRepository;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final SysCompanyMapper sysCompanyMapper;
|
||||
private final Duration sessionTtl;
|
||||
|
||||
public AuthInterceptor(
|
||||
TokenSessionRepository tokenSessionRepository,
|
||||
SysUserMapper sysUserMapper,
|
||||
SysCompanyMapper sysCompanyMapper,
|
||||
@Value("${labelsys.session.ttl:PT2H}") Duration sessionTtl
|
||||
) {
|
||||
public AuthInterceptor(TokenSessionRepository tokenSessionRepository, SysUserMapper sysUserMapper,
|
||||
SysCompanyMapper sysCompanyMapper, @Value("${labelsys.session.ttl:PT2H}") Duration sessionTtl) {
|
||||
this.tokenSessionRepository = tokenSessionRepository;
|
||||
this.sysUserMapper = sysUserMapper;
|
||||
this.sysCompanyMapper = sysCompanyMapper;
|
||||
@@ -69,12 +65,13 @@ public class AuthInterceptor implements HandlerInterceptor {
|
||||
return true;
|
||||
}
|
||||
String token = extractToken(request.getHeader("Authorization"));
|
||||
LoginUser loginUser = tokenSessionRepository.find(token)
|
||||
.orElseThrow(() -> new UnauthorizedException("未登录或登录已过期"));
|
||||
LoginUser loginUser =
|
||||
tokenSessionRepository.find(token).orElseThrow(() -> new UnauthorizedException("未登录或登录已过期"));
|
||||
|
||||
SysUser user = sysUserMapper.findByIdAndCompanyId(loginUser.userId(), loginUser.companyId());
|
||||
SysCompany company = sysCompanyMapper.findById(loginUser.companyId());
|
||||
if (user == null || company == null || user.getStatus() != UserStatus.ENABLED || company.getStatus() != CompanyStatus.ENABLED) {
|
||||
SysCompany company = sysCompanyMapper.selectById(loginUser.companyId());
|
||||
if (user == null || company == null || user.getStatus() != UserStatus.ENABLED
|
||||
|| company.getStatus() != CompanyStatus.ENABLED) {
|
||||
throw new UnauthorizedException("未登录或登录已过期");
|
||||
}
|
||||
if (!user.getSessionVersion().equals(loginUser.sessionVersion())) {
|
||||
@@ -96,7 +93,8 @@ public class AuthInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
Exception ex) {
|
||||
UserContext.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.AnnotationResultHistory;
|
||||
|
||||
public interface AnnotationResultHistoryMapper extends BaseMapper<AnnotationResultHistory> {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.AnnotationResult;
|
||||
import java.time.LocalDateTime;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface AnnotationResultMapper extends BaseMapper<AnnotationResult> {
|
||||
|
||||
AnnotationResult findActiveByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
|
||||
|
||||
int markArchived(@Param("id") Long id,
|
||||
@Param("companyId") Long companyId,
|
||||
@Param("reviewerId") Long reviewerId,
|
||||
@Param("reviewComment") String reviewComment,
|
||||
@Param("reviewedAt") LocalDateTime reviewedAt);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.AnnotationTask;
|
||||
|
||||
public interface AnnotationTaskMapper extends BaseMapper<AnnotationTask> {
|
||||
|
||||
AnnotationTask findByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.AnnotationTaskResource;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface AnnotationTaskResourceMapper extends BaseMapper<AnnotationTaskResource> {
|
||||
|
||||
List<Long> listResourceIdsByTaskId(@Param("taskId") Long taskId);
|
||||
|
||||
int deleteByTaskId(@Param("taskId") Long taskId);
|
||||
|
||||
int countByResourceId(@Param("resourceId") Long resourceId);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.BizDataRecord;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface BizDataRecordMapper extends BaseMapper<BizDataRecord> {
|
||||
|
||||
List<BizDataRecord> listVisibleByEmployee(@Param("companyId") Long companyId, @Param("creatorId") Long creatorId);
|
||||
|
||||
List<BizDataRecord> listVisibleByManager(@Param("companyId") Long companyId);
|
||||
|
||||
List<BizDataRecord> listVisibleByEngineer(@Param("companyId") Long companyId);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.ImageBboxAnnotation;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface ImageBboxAnnotationMapper extends BaseMapper<ImageBboxAnnotation> {
|
||||
ImageBboxAnnotation selectByResourceId(@Param("resourceId") Long resourceId);
|
||||
|
||||
int deleteByResourceId(@Param("resourceId") Long resourceId);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.SourceResource;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface SourceResourceMapper extends BaseMapper<SourceResource> {
|
||||
|
||||
List<SourceResource> selectByCompanyIdAndIds(@Param("companyId") Long companyId, @Param("resourceIds") List<Long> resourceIds);
|
||||
|
||||
SourceResource selectByCompanyIdAndResourceName(@Param("companyId") Long companyId, @Param("resourceName") String resourceName);
|
||||
}
|
||||
@@ -8,16 +8,10 @@ import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface SysCompanyMapper extends BaseMapper<SysCompany> {
|
||||
|
||||
int insert(SysCompany company);
|
||||
|
||||
SysCompany findById(@Param("id") Long id);
|
||||
|
||||
SysCompany findByCompanyCode(@Param("companyCode") String companyCode);
|
||||
|
||||
List<SysCompany> findEnabledCompaniesByPhone(@Param("phone") String phone);
|
||||
|
||||
List<SysCompany> listAll();
|
||||
|
||||
int updateStatus(@Param("id") Long id, @Param("status") CompanyStatus status);
|
||||
|
||||
void deleteAll();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.SysConfig;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface SysConfigMapper extends BaseMapper<SysConfig> {
|
||||
|
||||
SysConfig findByCompanyIdAndConfigName(@Param("companyId") Long companyId, @Param("configName") String configName);
|
||||
|
||||
SysConfig findByCompanyIdAndConfigNameAndType(@Param("companyId") Long companyId,
|
||||
@Param("configName") String configName,
|
||||
@Param("configType") String configType);
|
||||
}
|
||||
@@ -7,5 +7,5 @@ import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface SysMenuMapper extends BaseMapper<SysMenu> {
|
||||
|
||||
List<SysMenu> listCurrentMenus(@Param("companyId") Long companyId, @Param("positionCodes") List<String> positionCodes);
|
||||
List<SysMenu> listCurrentMenus(@Param("companyId") Long companyId, @Param("visiblePositions") List<String> visiblePositions);
|
||||
}
|
||||
|
||||
@@ -10,31 +10,17 @@ import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface SysUserMapper extends BaseMapper<SysUser> {
|
||||
|
||||
int insert(SysUser user);
|
||||
|
||||
SysUser findById(@Param("id") Long id);
|
||||
|
||||
SysUser findByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
|
||||
|
||||
SysUser findByCompanyIdAndPhone(@Param("companyId") Long companyId, @Param("phone") String phone);
|
||||
|
||||
List<SysUser> listByCompanyId(@Param("companyId") Long companyId);
|
||||
|
||||
List<SysUser> listCompanyAdmins(@Param("companyId") Long companyId);
|
||||
|
||||
int updatePassword(
|
||||
@Param("id") Long id,
|
||||
@Param("companyId") Long companyId,
|
||||
@Param("passwordHash") String passwordHash,
|
||||
@Param("mustChangePassword") boolean mustChangePassword
|
||||
);
|
||||
int updatePassword(@Param("id") Long id, @Param("companyId") Long companyId,
|
||||
@Param("passwordHash") String passwordHash, @Param("mustChangePassword") boolean mustChangePassword);
|
||||
|
||||
int updateAssignment(
|
||||
@Param("id") Long id,
|
||||
@Param("companyId") Long companyId,
|
||||
@Param("role") UserRole role,
|
||||
@Param("position") UserPosition position
|
||||
);
|
||||
int updateAssignment(@Param("id") Long id, @Param("companyId") Long companyId, @Param("role") UserRole role,
|
||||
@Param("position") UserPosition position);
|
||||
|
||||
int updateStatus(@Param("id") Long id, @Param("companyId") Long companyId, @Param("status") UserStatus status);
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.labelsys.backend.scheduled;
|
||||
|
||||
import com.labelsys.backend.service.AnnotationResultArchiveService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AutoArchiveAnnotationResultJob {
|
||||
|
||||
private final AnnotationResultArchiveService annotationResultArchiveService;
|
||||
|
||||
@Scheduled(fixedDelayString = "${labelsys.annotation.auto-archive-fixed-delay:300000}")
|
||||
public void autoArchiveEligibleResults() {
|
||||
int archivedCount = annotationResultArchiveService.autoArchiveEligibleResults();
|
||||
if (archivedCount > 0) {
|
||||
log.info("auto archived annotation results, count={}", archivedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.AnnotationResultHistoryPageQuery;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultHistoryResponse;
|
||||
import com.labelsys.backend.dto.response.FileContentResponse;
|
||||
import com.labelsys.backend.dto.response.MergeReviewResultResponse;
|
||||
import com.labelsys.backend.entity.AnnotationResult;
|
||||
import com.labelsys.backend.entity.AnnotationResultHistory;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import com.labelsys.backend.mapper.AnnotationResultHistoryMapper;
|
||||
import com.labelsys.backend.mapper.AnnotationResultMapper;
|
||||
import com.labelsys.backend.util.IdGenerator;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AnnotationResultArchiveService {
|
||||
|
||||
private static final String MANUAL_ARCHIVE_REASON = "MANUAL_REVIEW";
|
||||
|
||||
private final AnnotationResultMapper annotationResultMapper;
|
||||
private final AnnotationResultHistoryMapper annotationResultHistoryMapper;
|
||||
private final ObjectStorageService objectStorageService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final DataPermissionService dataPermissionService;
|
||||
|
||||
@Value("${labelsys.annotation.auto-archive-timeout:PT2H}")
|
||||
private Duration autoArchiveTimeout;
|
||||
|
||||
public PageResult<AnnotationResultHistoryResponse> pageHistory(LoginUser currentUser,
|
||||
AnnotationResultHistoryPageQuery query) {
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotationResultHistoryResponse getHistory(LoginUser currentUser, Long historyId) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertHistoryPermission(LoginUser currentUser, AnnotationResultHistory history) {
|
||||
if (!dataPermissionService.canAccessCreator(currentUser, history.getCreatorId(),
|
||||
UserRole.valueOf(history.getCreatorRole()))) {
|
||||
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问该归档记录");
|
||||
}
|
||||
}
|
||||
|
||||
private AnnotationResultHistoryResponse toResponse(AnnotationResultHistory history) {
|
||||
return new AnnotationResultHistoryResponse(
|
||||
history.getId(),
|
||||
// history.getSourceResultId(),
|
||||
history.getTaskId(),
|
||||
history.getTaskName(), // 新增
|
||||
history.getResourceId(),
|
||||
history.getResourceName(), // 新增
|
||||
history.getQaContentFilePath(),
|
||||
history.getArchiveReason(),
|
||||
history.getArchivedBy(),
|
||||
history.getArchivedAt(),
|
||||
history.getCreatedAt(),
|
||||
history.getReviewerId(),
|
||||
history.getReviewerName(),
|
||||
history.getReviewerComment()
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public int autoArchiveEligibleResults() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertReviewer(LoginUser currentUser) {
|
||||
if (currentUser.position() != UserPosition.REVIEWER && currentUser.position() != UserPosition.ADMIN) {
|
||||
throw new BusinessException(ResultCode.FORBIDDEN, "当前用户没有审核权限");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档运行态标注结果到历史表
|
||||
* 从对象存储读取 qa.json 内容进行归档
|
||||
*/
|
||||
private MergeReviewResultResponse archiveRuntimeResult(AnnotationResult result,
|
||||
Long reviewerId,
|
||||
String archiveReason,
|
||||
String reviewComment) {
|
||||
LocalDateTime archivedAt = LocalDateTime.now();
|
||||
|
||||
// 从对象存储读取 qa.json 内容
|
||||
// String qaContentJson = loadQaContentJson(result);
|
||||
|
||||
AnnotationResultHistory history = AnnotationResultHistory.builder()
|
||||
.id(IdGenerator.nextId())
|
||||
.companyId(result.getCompanyId())
|
||||
.creatorId(result.getCreatorId())
|
||||
.creatorRole(result.getCreatorRole())
|
||||
.sourceResultId(result.getId())
|
||||
.taskId(result.getTaskId())
|
||||
.taskName(result.getTaskName())
|
||||
.resourceId(result.getResourceId())
|
||||
.resourceName(result.getResourceName())
|
||||
//.qaContentJson(qaContentJson) // 使用从对象存储读取的内容
|
||||
.qaContentFilePath(result.getQaContentFilePath())
|
||||
.archiveReason(archiveReason)
|
||||
.archivedBy(reviewerId)
|
||||
.archivedAt(archivedAt)
|
||||
.reviewerId(null)
|
||||
.reviewerName("auto")
|
||||
.reviewerComment("auto")
|
||||
.build();
|
||||
annotationResultHistoryMapper.insert(history);
|
||||
|
||||
int updated = annotationResultMapper.markArchived(
|
||||
result.getId(),
|
||||
result.getCompanyId(),
|
||||
reviewerId,
|
||||
reviewComment,
|
||||
archivedAt);
|
||||
if (updated == 0) {
|
||||
return null;
|
||||
}
|
||||
return new MergeReviewResultResponse(result.getId(), history.getId(), archiveReason, archivedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从对象存储读取 qa.json 内容
|
||||
*/
|
||||
private String loadQaContentJson(AnnotationResult result) {
|
||||
try {
|
||||
String filePath = result.getQaContentFilePath();
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
log.warn("qa_content_file_path is null or empty, resultId={}", result.getId());
|
||||
return "{}";
|
||||
}
|
||||
|
||||
// 解析文件路径,提取 bucket 和 object key
|
||||
String bucketName = extractBucketName(filePath);
|
||||
String objectKey = extractObjectKey(filePath);
|
||||
|
||||
// 从对象存储下载文件内容
|
||||
byte[] content = objectStorageService.download(bucketName, objectKey);
|
||||
return new String(content, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load qa content from object storage, resultId={}, filePath={}",
|
||||
result.getId(), result.getQaContentFilePath(), e);
|
||||
// 如果读取失败,返回空 JSON
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件路径中提取 bucket 名称
|
||||
* 例如:annotation-results/2/qa/801.json -> annotation-results
|
||||
*/
|
||||
private String extractBucketName(String filePath) {
|
||||
int firstSlash = filePath.indexOf('/');
|
||||
return firstSlash > 0 ? filePath.substring(0, firstSlash) : filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件路径中提取 object key
|
||||
* 例如:annotation-results/2/qa/801.json -> 2/qa/801.json
|
||||
*/
|
||||
private String extractObjectKey(String filePath) {
|
||||
int firstSlash = filePath.indexOf('/');
|
||||
return firstSlash > 0 ? filePath.substring(firstSlash + 1) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载归档记录的文件内容
|
||||
* @param currentUser 当前用户
|
||||
* @param historyId 历史记录ID
|
||||
* @return 文件内容响应
|
||||
*/
|
||||
public FileContentResponse loadFileContent(LoginUser currentUser, Long historyId) {
|
||||
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("loadFileContent failed, companyId={}, userId={}, historyId={}, error={}",
|
||||
currentUser.companyId(), currentUser.userId(), historyId, e.getMessage(), e);
|
||||
throw new BusinessException(ResultCode.ERROR, "加载文件内容失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.AnnotationResultPageQuery;
|
||||
import com.labelsys.backend.dto.request.MergeReviewResultRequest;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultCompareResponse;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultDetailResponse;
|
||||
import com.labelsys.backend.dto.response.AnnotationResultResponse;
|
||||
import com.labelsys.backend.entity.AnnotationResult;
|
||||
import com.labelsys.backend.entity.AnnotationResultHistory;
|
||||
import com.labelsys.backend.entity.SourceResource;
|
||||
import com.labelsys.backend.enums.AnnotationResultStatus;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import com.labelsys.backend.mapper.AnnotationResultHistoryMapper;
|
||||
import com.labelsys.backend.mapper.AnnotationResultMapper;
|
||||
import com.labelsys.backend.mapper.SourceResourceMapper;
|
||||
import com.labelsys.backend.util.IdGenerator;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AnnotationResultService {
|
||||
|
||||
private final AnnotationResultMapper annotationResultMapper;
|
||||
private final AnnotationResultHistoryMapper annotationResultHistoryMapper;
|
||||
private final SourceResourceMapper sourceResourceMapper;
|
||||
private final DataPermissionService dataPermissionService;
|
||||
private final ObjectStorageService objectStorageService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public PageResult<AnnotationResultResponse> pageResults(LoginUser currentUser, AnnotationResultPageQuery query) {
|
||||
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);
|
||||
|
||||
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().name()))
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotationResultDetailResponse getResult(LoginUser currentUser, Long resultId) {
|
||||
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;
|
||||
|
||||
return toDetailResponse(result, qaContent, diffContent);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
private AnnotationResultDetailResponse toDetailResponse(AnnotationResult result,
|
||||
QaContent qaContent, DiffContent diffContent) {
|
||||
|
||||
// 转换 QA 内容(仅保留 records)
|
||||
AnnotationResultDetailResponse.QaContentDto qaContentDto = new AnnotationResultDetailResponse.QaContentDto(
|
||||
qaContent.records().stream()
|
||||
.map(r -> new AnnotationResultDetailResponse.QaRecordDto(
|
||||
r.id(), r.question(), r.answer(), r.requiresReview()))
|
||||
.toList()
|
||||
);
|
||||
|
||||
// 转换差异内容(仅保留 records)
|
||||
AnnotationResultDetailResponse.DiffContentDto diffContentDto = null;
|
||||
if (diffContent != null) {
|
||||
diffContentDto = new AnnotationResultDetailResponse.DiffContentDto(
|
||||
diffContent.records().stream()
|
||||
.map(r -> new AnnotationResultDetailResponse.DiffRecordDto(
|
||||
r.qaId(), r.question(), r.extractAnswer(),
|
||||
r.verifyAnswer(), r.diffReason(), r.mergedAnswer()))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
return new AnnotationResultDetailResponse(
|
||||
result.getId(),
|
||||
result.getTaskId(),
|
||||
result.getTaskName(),
|
||||
result.getResourceId(),
|
||||
result.getResourceName(),
|
||||
deriveStatus(result),
|
||||
result.getRequiresManualReview(),
|
||||
result.getIsDeleted(),
|
||||
result.getQaContentFilePath(),
|
||||
result.getDiffSummaryFilePath(),
|
||||
qaContentDto,
|
||||
diffContentDto,
|
||||
result.getReviewComment(),
|
||||
result.getReviewedAt(),
|
||||
result.getCreatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
public AnnotationResultCompareResponse compareResult(LoginUser currentUser, Long resultId) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void mergeReviewResult(LoginUser currentUser, Long resultId, MergeReviewResultRequest request) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private AnnotationResultResponse toResponse(AnnotationResult result) {
|
||||
return new AnnotationResultResponse(
|
||||
result.getId(),
|
||||
result.getTaskId(),
|
||||
result.getTaskName(), // 新增
|
||||
result.getResourceId(),
|
||||
result.getResourceName(), // 新增
|
||||
deriveStatus(result),
|
||||
result.getRequiresManualReview(),
|
||||
result.getIsDeleted(),
|
||||
result.getQaContentFilePath(),
|
||||
result.getDiffSummaryFilePath(),
|
||||
result.getReviewComment(),
|
||||
result.getReviewedAt(),
|
||||
result.getCreatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
private AnnotationResultStatus deriveStatus(AnnotationResult result) {
|
||||
if (Boolean.TRUE.equals(result.getIsDeleted())) {
|
||||
return AnnotationResultStatus.ARCHIVED;
|
||||
}
|
||||
if (Boolean.TRUE.equals(result.getRequiresManualReview())) {
|
||||
return AnnotationResultStatus.MANUAL_REVIEW_PENDING;
|
||||
}
|
||||
return AnnotationResultStatus.AUTO_ARCHIVE_PENDING;
|
||||
}
|
||||
|
||||
private QaContent loadQaContent(AnnotationResult result) {
|
||||
try {
|
||||
String filePath = result.getQaContentFilePath();
|
||||
String bucketName = extractBucketName(filePath);
|
||||
String objectKey = extractObjectKey(filePath);
|
||||
byte[] content = objectStorageService.download(bucketName, objectKey);
|
||||
String jsonContent = new String(content, StandardCharsets.UTF_8);
|
||||
return objectMapper.readValue(jsonContent, new TypeReference<QaContent>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load qa content, resultId={}, filePath={}", result.getId(),
|
||||
result.getQaContentFilePath(), e);
|
||||
throw new BusinessException(ResultCode.ERROR, "加载问答内容失败");
|
||||
}
|
||||
}
|
||||
|
||||
private DiffContent loadDiffSummary(AnnotationResult result) {
|
||||
try {
|
||||
String filePath = result.getDiffSummaryFilePath();
|
||||
String bucketName = extractBucketName(filePath);
|
||||
String objectKey = extractObjectKey(filePath);
|
||||
byte[] content = objectStorageService.download(bucketName, objectKey);
|
||||
String jsonContent = new String(content, StandardCharsets.UTF_8);
|
||||
return objectMapper.readValue(jsonContent, new TypeReference<DiffContent>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load diff summary, resultId={}, filePath={}", result.getId(),
|
||||
result.getDiffSummaryFilePath(), e);
|
||||
throw new BusinessException(ResultCode.ERROR, "加载差异摘要失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void saveQaContent(AnnotationResult result, QaContent qaContent) {
|
||||
try {
|
||||
String jsonContent = objectMapper.writeValueAsString(qaContent);
|
||||
String filePath = result.getQaContentFilePath();
|
||||
String bucketName = extractBucketName(filePath);
|
||||
String objectKey = extractObjectKey(filePath);
|
||||
objectStorageService.upload(bucketName, objectKey, jsonContent.getBytes(StandardCharsets.UTF_8),
|
||||
"application/json");
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to save qa content, resultId={}", result.getId(), e);
|
||||
throw new BusinessException(ResultCode.ERROR, "保存问答内容失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertResultPermission(LoginUser currentUser, AnnotationResult result) {
|
||||
if (!dataPermissionService.canAccessCreator(currentUser, result.getCreatorId(),
|
||||
UserRole.valueOf(result.getCreatorRole()))) {
|
||||
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问该标注结果");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档到历史表
|
||||
*
|
||||
* @param result 标注结果
|
||||
* @param currentUser 当前用户
|
||||
* @param archiveReason 归档原因
|
||||
* @param isAutoArchive 是否自动归档(true=自动归档,false=人工审核后归档)
|
||||
*/
|
||||
private void archiveToHistory(AnnotationResult result, LoginUser currentUser, String archiveReason,
|
||||
boolean isAutoArchive) {
|
||||
try {
|
||||
// 读取 qa.json 内容用于归档
|
||||
QaContent qaContent = loadQaContent(result);
|
||||
|
||||
// 构建归档记录
|
||||
AnnotationResultHistory.AnnotationResultHistoryBuilder historyBuilder = AnnotationResultHistory.builder()
|
||||
.id(IdGenerator.nextId())
|
||||
.companyId(result.getCompanyId())
|
||||
.creatorId(result.getCreatorId())
|
||||
.creatorRole(result.getCreatorRole())
|
||||
.sourceResultId(result.getId())
|
||||
.taskId(result.getTaskId())
|
||||
.taskName(result.getTaskName())
|
||||
.resourceId(result.getResourceId())
|
||||
.resourceName(result.getResourceName())
|
||||
//.qaContentJson(objectMapper.writeValueAsString(qaContent))
|
||||
.qaContentFilePath(result.getQaContentFilePath())
|
||||
.archiveReason(archiveReason)
|
||||
.archivedBy(currentUser.userId())
|
||||
.archivedAt(LocalDateTime.now())
|
||||
.createdAt(LocalDateTime.now());
|
||||
|
||||
// 根据归档类型设置审核人信息
|
||||
if (isAutoArchive) {
|
||||
// 自动归档:reviewer_id为NULL,name和comment为"auto"
|
||||
historyBuilder
|
||||
.reviewerId(null)
|
||||
.reviewerName("auto")
|
||||
.reviewerComment("auto");
|
||||
} else {
|
||||
// 人工审核后归档:使用审核人信息
|
||||
historyBuilder
|
||||
.reviewerId(result.getReviewerId())
|
||||
.reviewerName(currentUser.realName())
|
||||
.reviewerComment(result.getReviewComment());
|
||||
}
|
||||
|
||||
annotationResultHistoryMapper.insert(historyBuilder.build());
|
||||
|
||||
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);
|
||||
throw new BusinessException(ResultCode.ERROR, "归档失败");
|
||||
}
|
||||
}
|
||||
|
||||
private String extractBucketName(String filePath) {
|
||||
// 从文件路径中提取 bucket 名称
|
||||
// 例如:annotation-results/2/qa/801.json -> annotation-results
|
||||
int firstSlash = filePath.indexOf('/');
|
||||
return firstSlash > 0 ? filePath.substring(0, firstSlash) : filePath;
|
||||
}
|
||||
|
||||
private String extractObjectKey(String filePath) {
|
||||
// 从文件路径中提取 object key
|
||||
// 例如:annotation-results/2/qa/801.json -> 2/qa/801.json
|
||||
int firstSlash = filePath.indexOf('/');
|
||||
return firstSlash > 0 ? filePath.substring(firstSlash + 1) : "";
|
||||
}
|
||||
|
||||
// 内部类:qa.json 结构
|
||||
private record QaContent(
|
||||
Long taskId,
|
||||
Long resourceId,
|
||||
List<QaRecord> records,
|
||||
Metadata metadata
|
||||
) {
|
||||
private record QaRecord(String id, String question, String answer, Boolean requiresReview) {
|
||||
}
|
||||
|
||||
private record Metadata(String createdAt, String updatedAt) {
|
||||
}
|
||||
}
|
||||
|
||||
// 内部类:diff.json 结构
|
||||
private record DiffContent(
|
||||
Long taskId,
|
||||
Long resourceId,
|
||||
List<DiffRecord> records,
|
||||
Metadata metadata
|
||||
) {
|
||||
private record DiffRecord(String qaId, String question, String extractAnswer,
|
||||
String verifyAnswer, String diffReason, String mergedAnswer) {
|
||||
}
|
||||
|
||||
private record Metadata(String createdAt) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.AnnotationTaskPageQuery;
|
||||
import com.labelsys.backend.dto.request.CreateAnnotationTaskRequest;
|
||||
import com.labelsys.backend.dto.request.UpdateAnnotationTaskRequest;
|
||||
import com.labelsys.backend.dto.response.AnnotationTaskResponse;
|
||||
import com.labelsys.backend.entity.AnnotationTask;
|
||||
import com.labelsys.backend.entity.AnnotationTaskResource;
|
||||
import com.labelsys.backend.entity.SourceResource;
|
||||
import com.labelsys.backend.enums.IndustryType;
|
||||
import com.labelsys.backend.enums.TaskStatus;
|
||||
import com.labelsys.backend.enums.TaskType;
|
||||
import com.labelsys.backend.mapper.AnnotationTaskMapper;
|
||||
import com.labelsys.backend.mapper.AnnotationTaskResourceMapper;
|
||||
import com.labelsys.backend.mapper.SourceResourceMapper;
|
||||
import com.labelsys.backend.util.IdGenerator;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AnnotationTaskService {
|
||||
|
||||
private final AnnotationTaskMapper annotationTaskMapper;
|
||||
private final AnnotationTaskResourceMapper annotationTaskResourceMapper;
|
||||
private final SourceResourceMapper sourceResourceMapper;
|
||||
private final DataPermissionService dataPermissionService;
|
||||
|
||||
@Transactional
|
||||
public AnnotationTaskResponse createTask(LoginUser currentUser, CreateAnnotationTaskRequest request) {
|
||||
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();
|
||||
|
||||
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());
|
||||
|
||||
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) {
|
||||
try {
|
||||
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, "运行中的任务不允许修改资源");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotationTaskResponse getTask(LoginUser currentUser, Long taskId) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public PageResult<AnnotationTaskResponse> pageTasks(LoginUser currentUser, AnnotationTaskPageQuery query) {
|
||||
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());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteTask(LoginUser currentUser, Long taskId) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private List<SourceResource> loadAndValidateResources(LoginUser currentUser, List<Long> resourceIds) {
|
||||
if (resourceIds == null || resourceIds.isEmpty()) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "任务资源不能为空");
|
||||
}
|
||||
List<Long> normalizedIds = normalizeIds(resourceIds);
|
||||
List<SourceResource> resources = sourceResourceMapper.selectByCompanyIdAndIds(currentUser.companyId(),
|
||||
normalizedIds);
|
||||
if (resources.size() != normalizedIds.size()) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "存在无效资源");
|
||||
}
|
||||
for (SourceResource resource : resources) {
|
||||
if (!dataPermissionService.canAccessCreator(currentUser, resource.getCreatorId(),
|
||||
resource.getCreatorRole())) {
|
||||
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问资源");
|
||||
}
|
||||
}
|
||||
resources.sort(Comparator.comparing(SourceResource::getId));
|
||||
return resources;
|
||||
}
|
||||
|
||||
private void saveTaskBindings(Long taskId, Long companyId, List<SourceResource> resources) {
|
||||
for (SourceResource resource : resources) {
|
||||
annotationTaskResourceMapper.insert(AnnotationTaskResource.builder()
|
||||
.id(IdGenerator.nextId())
|
||||
.companyId(companyId)
|
||||
.taskId(taskId)
|
||||
.resourceId(resource.getId())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private AnnotationTaskResponse buildTaskResponse(AnnotationTask task, List<Long> resourceIds) {
|
||||
return new AnnotationTaskResponse(
|
||||
task.getId(),
|
||||
task.getTaskName(),
|
||||
task.getIndustryType(),
|
||||
task.getTaskType(),
|
||||
task.getTaskStatus(),
|
||||
resourceIds,
|
||||
task.getCreatedAt(),
|
||||
task.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
private List<Long> resourceIds(List<SourceResource> resources) {
|
||||
return resources.stream().map(SourceResource::getId).sorted().toList();
|
||||
}
|
||||
|
||||
private List<Long> normalizeIds(List<Long> resourceIds) {
|
||||
Set<Long> uniqueIds = new HashSet<>(resourceIds);
|
||||
List<Long> sortedIds = new ArrayList<>(uniqueIds);
|
||||
sortedIds.sort(Long::compareTo);
|
||||
return sortedIds;
|
||||
}
|
||||
|
||||
private void assertTaskPermission(LoginUser currentUser, AnnotationTask task) {
|
||||
if (!dataPermissionService.canAccessCreator(currentUser, task.getCreatorId(), task.getCreatorRole())) {
|
||||
throw new BusinessException(ResultCode.FORBIDDEN, "无权操作任务");
|
||||
}
|
||||
}
|
||||
|
||||
private IndustryType defaultIndustryType(IndustryType industryType) {
|
||||
return industryType != null ? industryType : IndustryType.TRANSPORT;
|
||||
}
|
||||
|
||||
private TaskType defaultTaskType(TaskType taskType) {
|
||||
return taskType != null ? taskType : TaskType.EXTRACT_QA;
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,18 @@ 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 {
|
||||
@@ -37,12 +40,18 @@ public class AuthService {
|
||||
private Duration sessionTtl;
|
||||
|
||||
public List<CompanyOptionResponse> listAvailableCompanies(String phone) {
|
||||
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) {
|
||||
try {
|
||||
SysCompany company = loadEnabledCompany(request.companyCode());
|
||||
SysUser user = loadEnabledUser(company.getId(), request.phone());
|
||||
if (!passwordEncoder.matches(request.password(), user.getPasswordHash())) {
|
||||
@@ -52,15 +61,30 @@ public class AuthService {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public LoginUser getCurrentUser(String token) {
|
||||
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) {
|
||||
try {
|
||||
if (!request.newPassword().equals(request.confirmPassword())) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "两次输入的新密码不一致");
|
||||
}
|
||||
@@ -71,13 +95,26 @@ public class AuthService {
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
|
||||
public void logout(String 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,11 +23,20 @@ public class CompanyService {
|
||||
private final SysCompanyMapper sysCompanyMapper;
|
||||
|
||||
public List<SysCompany> listCompanies(LoginUser currentUser) {
|
||||
try {
|
||||
assertPlatformAdmin(currentUser);
|
||||
return sysCompanyMapper.listAll();
|
||||
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) {
|
||||
try {
|
||||
assertPlatformAdmin(currentUser);
|
||||
if (sysCompanyMapper.findByCompanyCode(request.companyCode()) != null) {
|
||||
throw new BusinessException(ResultCode.CONFLICT, "公司编码已存在");
|
||||
@@ -37,18 +49,33 @@ public class CompanyService {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateStatus(LoginUser currentUser, Long companyId, UpdateCompanyStatusRequest request) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPlatformAdmin(LoginUser currentUser) {
|
||||
if (!currentUser.isPlatformAdmin()) {
|
||||
throw new ForbiddenException("仅平台管理员可操作");
|
||||
if (!currentUser.isSuperAdmin()) {
|
||||
throw new ForbiddenException("仅系统管理员可操作");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,52 +3,42 @@ package com.labelsys.backend.service;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.entity.BizDataRecord;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import com.labelsys.backend.mapper.BizDataRecordMapper;
|
||||
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;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DataPermissionService {
|
||||
|
||||
private final BizDataRecordMapper bizDataRecordMapper;
|
||||
|
||||
public List<BizDataRecord> listVisibleRecords(LoginUser currentUser) {
|
||||
return switch (currentUser.role()) {
|
||||
case EMPLOYEE -> bizDataRecordMapper.listVisibleByEmployee(currentUser.companyId(), currentUser.userId());
|
||||
case MANAGER -> bizDataRecordMapper.listVisibleByManager(currentUser.companyId());
|
||||
case ENGINEER -> bizDataRecordMapper.listVisibleByEngineer(currentUser.companyId());
|
||||
};
|
||||
}
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public boolean canAccessCreator(LoginUser currentUser, Long creatorId, UserRole creatorRole) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用数据过滤方法
|
||||
*
|
||||
* @param currentUser 当前登录用户
|
||||
* @param allRecords 待过滤的全量数据列表
|
||||
* @param roleExtractor 从数据对象中提取“关联角色”或“创建者角色”的函数
|
||||
* @param ownerIdExtractor 从数据对象中提取“所有者ID”的函数(用于员工只能看自己的情况)
|
||||
* @param <T> 数据类型
|
||||
* @return 过滤后的数据列表
|
||||
*/
|
||||
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();
|
||||
}
|
||||
@@ -62,32 +52,71 @@ public class DataPermissionService {
|
||||
Long recordOwnerId = ownerIdExtractor.apply(record);
|
||||
|
||||
return switch (currentRole) {
|
||||
case EMPLOYEE ->
|
||||
// 员工只能查看自己创建/拥有的数据
|
||||
currentUserId.equals(recordOwnerId);
|
||||
|
||||
case MANAGER ->
|
||||
// 经理可以查看员工和经理的数据,不能查看总工程师的数据
|
||||
recordRole == UserRole.EMPLOYEE || recordRole == UserRole.MANAGER;
|
||||
|
||||
case ENGINEER ->
|
||||
// 总工程师可以查看所有数据
|
||||
true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对 BizDataRecord 的便捷调用方法
|
||||
*/
|
||||
// public List<BizDataRecord> listVisibleRecordsGeneric(LoginUser currentUser, List<BizDataRecord> allRecords) {
|
||||
// return filterByRole(
|
||||
// currentUser,
|
||||
// allRecords,
|
||||
// BizDataRecord::
|
||||
// BizDataRecord::getCreatorRole, // 提取创建者角色
|
||||
// BizDataRecord::getCreatorId // 提取创建者ID
|
||||
// );
|
||||
// }
|
||||
public List<String> getAllowedRoles(LoginUser currentUser) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldFilterByUserId(LoginUser currentUser) {
|
||||
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) {
|
||||
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);
|
||||
} catch (Exception e) {
|
||||
log.error("listVisibleRecords failed, companyId={}, userId={}, error={}",
|
||||
currentUser.companyId(), currentUser.userId(), e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ public class MenuService {
|
||||
private final SysMenuMapper sysMenuMapper;
|
||||
|
||||
public List<MenuResponse> listCurrentMenus(LoginUser currentUser) {
|
||||
List<String> positionCodes = java.util.Arrays.stream(com.labelsys.backend.enums.UserPosition.values())
|
||||
List<String> visiblePositions = java.util.Arrays.stream(com.labelsys.backend.enums.UserPosition.values())
|
||||
.filter(position -> currentUser.position().canAccess(position))
|
||||
.map(Enum::name)
|
||||
.toList();
|
||||
return sysMenuMapper.listCurrentMenus(currentUser.companyId(), positionCodes)
|
||||
return sysMenuMapper.listCurrentMenus(currentUser.companyId(), visiblePositions)
|
||||
.stream()
|
||||
.map(MenuResponse::from)
|
||||
.toList();
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.labelsys.backend.config.ObjectStorageProperties;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
|
||||
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
|
||||
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ObjectStorageInitializer {
|
||||
|
||||
private final S3Client s3Client;
|
||||
private final ObjectStorageProperties properties;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void initializeBuckets() {
|
||||
log.info("开始初始化对象存储桶...");
|
||||
|
||||
String[] buckets = {
|
||||
properties.getSourceBucket(),
|
||||
properties.getArtifactBucket(),
|
||||
properties.getExportBucket()
|
||||
};
|
||||
|
||||
for (String bucketName : buckets) {
|
||||
try {
|
||||
if (!bucketExists(bucketName)) {
|
||||
createBucket(bucketName);
|
||||
log.info("成功创建存储桶: {}", bucketName);
|
||||
} else {
|
||||
log.info("存储桶已存在: {}", bucketName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("初始化存储桶 {} 失败: {}", bucketName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("对象存储桶初始化完成");
|
||||
}
|
||||
|
||||
private boolean bucketExists(String bucketName) {
|
||||
try {
|
||||
s3Client.headBucket(HeadBucketRequest.builder().bucket(bucketName).build());
|
||||
return true;
|
||||
} catch (NoSuchBucketException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void createBucket(String bucketName) {
|
||||
s3Client.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
public interface ObjectStorageService {
|
||||
|
||||
String upload(String bucketName, String objectKey, byte[] content, String contentType);
|
||||
|
||||
void delete(String bucketName, String objectKey);
|
||||
|
||||
byte[] download(String bucketName, String objectKey);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.core.sync.ResponseTransformer;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RustfsObjectStorageService implements ObjectStorageService {
|
||||
|
||||
private final S3Client s3Client;
|
||||
|
||||
@Override
|
||||
public String upload(String bucketName, String objectKey, byte[] content, String contentType) {
|
||||
try {
|
||||
PutObjectRequest.Builder requestBuilder = PutObjectRequest.builder()
|
||||
.bucket(bucketName)
|
||||
.key(objectKey);
|
||||
if (contentType != null && !contentType.isBlank()) {
|
||||
requestBuilder.contentType(contentType);
|
||||
}
|
||||
s3Client.putObject(requestBuilder.build(), RequestBody.fromBytes(content));
|
||||
return objectKey;
|
||||
} catch (Exception ex) {
|
||||
throw new BusinessException(ResultCode.ERROR, "对象存储上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String bucketName, String objectKey) {
|
||||
try {
|
||||
s3Client.deleteObject(DeleteObjectRequest.builder()
|
||||
.bucket(bucketName)
|
||||
.key(objectKey)
|
||||
.build());
|
||||
} catch (Exception ex) {
|
||||
throw new BusinessException(ResultCode.ERROR, "对象存储删除失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] download(String bucketName, String objectKey) {
|
||||
try {
|
||||
GetObjectRequest request = GetObjectRequest.builder()
|
||||
.bucket(bucketName)
|
||||
.key(objectKey)
|
||||
.build();
|
||||
return s3Client.getObject(request, ResponseTransformer.toBytes()).asByteArray();
|
||||
} catch (Exception ex) {
|
||||
throw new BusinessException(ResultCode.ERROR, "对象存储下载失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user