Compare commits
6 Commits
c65fdbab5b
...
3ce2deb0a6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ce2deb0a6 | ||
|
|
13945b239e | ||
|
|
eb22998b28 | ||
|
|
f6ba09521a | ||
|
|
73a13fd16d | ||
|
|
00032dd491 |
@@ -392,4 +392,10 @@ docker build -t label-backend:latest .
|
|||||||
- Service 统一放在 `service/`,不拆 `service/impl`
|
- Service 统一放在 `service/`,不拆 `service/impl`
|
||||||
- 业务规则优先放在 Service,Controller 只负责 HTTP 协议层
|
- 业务规则优先放在 Service,Controller 只负责 HTTP 协议层
|
||||||
- 新增接口需同步补齐 Swagger 注解与测试
|
- 新增接口需同步补齐 Swagger 注解与测试
|
||||||
|
- 所有对外接口参数必须在 Swagger 中明确体现名称、类型和含义
|
||||||
|
- 固定结构请求体禁止继续使用匿名 `Map<String, Object>` 或 `Map<String, String>`,必须定义 DTO 并补齐 `@Schema` 字段说明
|
||||||
|
- 固定结构响应应优先使用明确 DTO,或至少为 Swagger 暴露对象补齐字段级 `@Schema` 注解
|
||||||
|
- 路径参数、查询参数、请求体、分页包装和通用返回体都必须维护可读的 OpenAPI 文档说明
|
||||||
|
- 需要保持历史兼容的原始 JSON 字符串请求体可以继续使用 `String`,但必须在 Swagger `@RequestBody` 中说明完整 JSON body 的提交方式和兼容原因
|
||||||
|
- 修改 Controller 参数、请求 DTO、响应 DTO 或对外实体后,必须运行 `mvn -Dtest=OpenApiAnnotationTest test`,确保 Swagger 参数名称、类型和含义没有回退
|
||||||
- 目录、配置、打包方式变化后,README、设计文档和部署说明必须同步更新
|
- 目录、配置、打包方式变化后,README、设计文档和部署说明必须同步更新
|
||||||
|
|||||||
@@ -0,0 +1,808 @@
|
|||||||
|
# label_backend Swagger DTO Annotation Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Make every public `label_backend` API parameter visible in Swagger with its name, type, requiredness, and meaning by replacing fixed `Map` request bodies with DTOs and adding OpenAPI annotations.
|
||||||
|
|
||||||
|
**Architecture:** Keep existing URLs, HTTP methods, JSON field names, service calls, and auth behavior unchanged. Add DTOs in the flat `com.label.dto` package, annotate Controller parameters and public schema models, and extend OpenAPI reflection tests so future endpoints cannot regress to undocumented parameters.
|
||||||
|
|
||||||
|
**Tech Stack:** Java 21, Spring Boot 3.1.5, springdoc-openapi 2.3.0, JUnit 5, AssertJ, Lombok.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Create
|
||||||
|
|
||||||
|
- `src/main/java/com/label/dto/CompanyCreateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/CompanyUpdateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/CompanyStatusUpdateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/UserCreateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/UserUpdateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/UserStatusUpdateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/UserRoleUpdateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/CreateTaskRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/TaskReassignRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/RejectRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/ExportBatchCreateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/VideoProcessCreateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/VideoProcessCallbackRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/SysConfigUpdateRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/SysConfigItemResponse.java`
|
||||||
|
- `src/main/java/com/label/dto/DynamicJsonResponse.java`
|
||||||
|
- `src/main/java/com/label/dto/FinetuneJobResponse.java`
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
|
||||||
|
- `src/test/java/com/label/unit/OpenApiAnnotationTest.java`
|
||||||
|
- `src/main/java/com/label/common/result/Result.java`
|
||||||
|
- `src/main/java/com/label/common/result/PageResult.java`
|
||||||
|
- `src/main/java/com/label/dto/LoginRequest.java`
|
||||||
|
- `src/main/java/com/label/dto/LoginResponse.java`
|
||||||
|
- `src/main/java/com/label/dto/SourceResponse.java`
|
||||||
|
- `src/main/java/com/label/dto/TaskResponse.java`
|
||||||
|
- `src/main/java/com/label/dto/UserInfoResponse.java`
|
||||||
|
- `src/main/java/com/label/controller/AuthController.java`
|
||||||
|
- `src/main/java/com/label/controller/CompanyController.java`
|
||||||
|
- `src/main/java/com/label/controller/UserController.java`
|
||||||
|
- `src/main/java/com/label/controller/SourceController.java`
|
||||||
|
- `src/main/java/com/label/controller/TaskController.java`
|
||||||
|
- `src/main/java/com/label/controller/ExtractionController.java`
|
||||||
|
- `src/main/java/com/label/controller/QaController.java`
|
||||||
|
- `src/main/java/com/label/controller/ExportController.java`
|
||||||
|
- `src/main/java/com/label/controller/SysConfigController.java`
|
||||||
|
- `src/main/java/com/label/controller/VideoController.java`
|
||||||
|
- `src/main/java/com/label/entity/SysCompany.java`
|
||||||
|
- `src/main/java/com/label/entity/SysUser.java`
|
||||||
|
- `src/main/java/com/label/entity/SysConfig.java`
|
||||||
|
- `src/main/java/com/label/entity/TrainingDataset.java`
|
||||||
|
- `src/main/java/com/label/entity/ExportBatch.java`
|
||||||
|
- `src/main/java/com/label/entity/VideoProcessJob.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add Failing OpenAPI Contract Tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/test/java/com/label/unit/OpenApiAnnotationTest.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add DTO and parameter coverage expectations**
|
||||||
|
|
||||||
|
Add tests that fail before implementation because new DTO classes do not exist, some endpoint parameters lack `@Parameter`, and request body parameters still use `Map`.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
@DisplayName("固定结构请求体不能继续使用 Map")
|
||||||
|
void fixedRequestBodiesDoNotUseRawMap() {
|
||||||
|
for (Class<?> controller : CONTROLLERS) {
|
||||||
|
Arrays.stream(controller.getDeclaredMethods())
|
||||||
|
.filter(OpenApiAnnotationTest::isEndpointMethod)
|
||||||
|
.flatMap(method -> Arrays.stream(method.getParameters()))
|
||||||
|
.filter(parameter -> parameter.isAnnotationPresent(org.springframework.web.bind.annotation.RequestBody.class))
|
||||||
|
.forEach(parameter -> assertThat(parameter.getType())
|
||||||
|
.as(controller.getSimpleName() + " request body should use an explicit DTO")
|
||||||
|
.isNotEqualTo(java.util.Map.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("所有 endpoint 公开参数都声明 @Parameter 或请求体说明")
|
||||||
|
void endpointParametersHaveOpenApiDescriptions() {
|
||||||
|
for (Class<?> controller : CONTROLLERS) {
|
||||||
|
Arrays.stream(controller.getDeclaredMethods())
|
||||||
|
.filter(OpenApiAnnotationTest::isEndpointMethod)
|
||||||
|
.forEach(method -> Arrays.stream(method.getParameters())
|
||||||
|
.filter(OpenApiAnnotationTest::isPublicApiParameter)
|
||||||
|
.forEach(parameter -> assertThat(
|
||||||
|
parameter.isAnnotationPresent(io.swagger.v3.oas.annotations.Parameter.class)
|
||||||
|
|| parameter.isAnnotationPresent(io.swagger.v3.oas.annotations.parameters.RequestBody.class))
|
||||||
|
.as(controller.getSimpleName() + "." + method.getName()
|
||||||
|
+ " parameter " + parameter.getName()
|
||||||
|
+ " should have OpenAPI description")
|
||||||
|
.isTrue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add helper:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static boolean isPublicApiParameter(java.lang.reflect.Parameter parameter) {
|
||||||
|
return parameter.isAnnotationPresent(org.springframework.web.bind.annotation.PathVariable.class)
|
||||||
|
|| parameter.isAnnotationPresent(org.springframework.web.bind.annotation.RequestParam.class)
|
||||||
|
|| parameter.isAnnotationPresent(org.springframework.web.bind.annotation.RequestBody.class);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Expand DTO and entity schema coverage**
|
||||||
|
|
||||||
|
Update DTO coverage lists so the test will fail until new DTOs and exposed entities are annotated.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static final List<Class<?>> DTOS = List.of(
|
||||||
|
LoginRequest.class,
|
||||||
|
LoginResponse.class,
|
||||||
|
UserInfoResponse.class,
|
||||||
|
TaskResponse.class,
|
||||||
|
SourceResponse.class,
|
||||||
|
CompanyCreateRequest.class,
|
||||||
|
CompanyUpdateRequest.class,
|
||||||
|
CompanyStatusUpdateRequest.class,
|
||||||
|
UserCreateRequest.class,
|
||||||
|
UserUpdateRequest.class,
|
||||||
|
UserStatusUpdateRequest.class,
|
||||||
|
UserRoleUpdateRequest.class,
|
||||||
|
CreateTaskRequest.class,
|
||||||
|
TaskReassignRequest.class,
|
||||||
|
RejectRequest.class,
|
||||||
|
ExportBatchCreateRequest.class,
|
||||||
|
VideoProcessCreateRequest.class,
|
||||||
|
VideoProcessCallbackRequest.class,
|
||||||
|
SysConfigUpdateRequest.class,
|
||||||
|
SysConfigItemResponse.class,
|
||||||
|
DynamicJsonResponse.class,
|
||||||
|
FinetuneJobResponse.class
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final List<Class<?>> EXPOSED_SCHEMAS = List.of(
|
||||||
|
Result.class,
|
||||||
|
PageResult.class,
|
||||||
|
SysCompany.class,
|
||||||
|
SysUser.class,
|
||||||
|
SysConfig.class,
|
||||||
|
TrainingDataset.class,
|
||||||
|
ExportBatch.class,
|
||||||
|
VideoProcessJob.class
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Add test:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
@DisplayName("公开响应模型都声明 @Schema")
|
||||||
|
void exposedModelsHaveSchema() {
|
||||||
|
assertThat(EXPOSED_SCHEMAS)
|
||||||
|
.allSatisfy(model ->
|
||||||
|
assertThat(model.getAnnotation(Schema.class))
|
||||||
|
.as(model.getSimpleName() + " should have @Schema")
|
||||||
|
.isNotNull());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run test and verify RED**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=OpenApiAnnotationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL because the new DTO classes are missing and existing Controller parameters are not fully annotated.
|
||||||
|
|
||||||
|
### Task 2: Create Explicit Request and Response DTOs
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create all DTO files listed in File Structure
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add company request DTOs**
|
||||||
|
|
||||||
|
Create `CompanyCreateRequest`, `CompanyUpdateRequest`, and `CompanyStatusUpdateRequest` with these fields:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建公司请求")
|
||||||
|
public class CompanyCreateRequest {
|
||||||
|
@Schema(description = "公司名称", example = "示例公司")
|
||||||
|
private String companyName;
|
||||||
|
|
||||||
|
@Schema(description = "公司代码,英文或数字短标识", example = "DEMO")
|
||||||
|
private String companyCode;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`CompanyUpdateRequest` uses the same fields with description "更新公司信息请求".
|
||||||
|
|
||||||
|
`CompanyStatusUpdateRequest`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "更新公司状态请求")
|
||||||
|
public class CompanyStatusUpdateRequest {
|
||||||
|
@Schema(description = "公司状态,可选值:ACTIVE、DISABLED", example = "ACTIVE")
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add user request DTOs**
|
||||||
|
|
||||||
|
Create:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建用户请求")
|
||||||
|
public class UserCreateRequest {
|
||||||
|
@Schema(description = "登录用户名", example = "annotator01")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "明文初始密码", example = "Passw0rd!")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Schema(description = "真实姓名", example = "张三")
|
||||||
|
private String realName;
|
||||||
|
|
||||||
|
@Schema(description = "用户角色,可选值:ADMIN、REVIEWER、ANNOTATOR、UPLOADER", example = "ANNOTATOR")
|
||||||
|
private String role;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`UserUpdateRequest` fields:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Schema(description = "真实姓名", example = "张三")
|
||||||
|
private String realName;
|
||||||
|
|
||||||
|
@Schema(description = "新密码;为空时不修改密码", example = "NewPassw0rd!")
|
||||||
|
private String password;
|
||||||
|
```
|
||||||
|
|
||||||
|
`UserStatusUpdateRequest`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Schema(description = "用户状态,可选值:ACTIVE、DISABLED", example = "ACTIVE")
|
||||||
|
private String status;
|
||||||
|
```
|
||||||
|
|
||||||
|
`UserRoleUpdateRequest`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Schema(description = "用户角色,可选值:ADMIN、REVIEWER、ANNOTATOR、UPLOADER", example = "REVIEWER")
|
||||||
|
private String role;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add task request DTOs**
|
||||||
|
|
||||||
|
Create:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建标注任务请求")
|
||||||
|
public class CreateTaskRequest {
|
||||||
|
@Schema(description = "关联资料 ID", example = "1001")
|
||||||
|
private Long sourceId;
|
||||||
|
|
||||||
|
@Schema(description = "任务类型,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION")
|
||||||
|
private String taskType;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "管理员强制指派任务请求")
|
||||||
|
public class TaskReassignRequest {
|
||||||
|
@Schema(description = "目标用户 ID", example = "2001")
|
||||||
|
private Long userId;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Add common moderation and dynamic JSON DTOs**
|
||||||
|
|
||||||
|
Create:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "审批驳回请求")
|
||||||
|
public class RejectRequest {
|
||||||
|
@Schema(description = "驳回原因", example = "证据不足,请补充来源片段")
|
||||||
|
private String reason;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Data
|
||||||
|
@Schema(description = "动态业务 JSON 响应")
|
||||||
|
public class DynamicJsonResponse {
|
||||||
|
@Schema(description = "动态业务数据;提取阶段通常为三元组/四元组结构,问答阶段通常为问答对结构")
|
||||||
|
private Map<String, Object> content;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Add export and finetune DTOs**
|
||||||
|
|
||||||
|
Create:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建训练数据导出批次请求")
|
||||||
|
public class ExportBatchCreateRequest {
|
||||||
|
@Schema(description = "训练样本 ID 列表", example = "[1,2,3]")
|
||||||
|
private List<Long> sampleIds;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "微调任务响应")
|
||||||
|
public class FinetuneJobResponse {
|
||||||
|
@Schema(description = "导出批次 ID", example = "10")
|
||||||
|
private Long batchId;
|
||||||
|
|
||||||
|
@Schema(description = "微调任务 ID", example = "glm-ft-abc123")
|
||||||
|
private String finetuneJobId;
|
||||||
|
|
||||||
|
@Schema(description = "微调任务状态,可选值:PENDING、RUNNING、SUCCESS、FAILED", example = "RUNNING")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "错误信息;任务失败时返回", example = "training file not found")
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Add video and config DTOs**
|
||||||
|
|
||||||
|
Create:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建视频处理任务请求")
|
||||||
|
public class VideoProcessCreateRequest {
|
||||||
|
@Schema(description = "视频资料 ID", example = "1001")
|
||||||
|
private Long sourceId;
|
||||||
|
|
||||||
|
@Schema(description = "任务类型,可选值:EXTRACT_FRAMES、VIDEO_TO_TEXT", example = "EXTRACT_FRAMES")
|
||||||
|
private String jobType;
|
||||||
|
|
||||||
|
@Schema(description = "任务参数 JSON 字符串,例如抽帧模式、帧间隔或起止时间", example = "{\"mode\":\"interval\",\"frameInterval\":30}")
|
||||||
|
private String params;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "AI 服务视频处理回调请求")
|
||||||
|
public class VideoProcessCallbackRequest {
|
||||||
|
@Schema(description = "视频处理任务 ID", example = "123")
|
||||||
|
private Long jobId;
|
||||||
|
|
||||||
|
@Schema(description = "处理状态,可选值:SUCCESS、FAILED", example = "SUCCESS")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "输出文件路径;成功时返回", example = "frames/1001/0.jpg")
|
||||||
|
private String outputPath;
|
||||||
|
|
||||||
|
@Schema(description = "错误信息;失败时返回", example = "video decode failed")
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "系统配置更新请求")
|
||||||
|
public class SysConfigUpdateRequest {
|
||||||
|
@Schema(description = "配置值", example = "glm-4-flash")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
@Schema(description = "配置说明", example = "默认文本模型")
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "系统配置项响应")
|
||||||
|
public class SysConfigItemResponse {
|
||||||
|
@Schema(description = "配置键", example = "ai.defaultTextModel")
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
@Schema(description = "配置值", example = "glm-4-flash")
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
@Schema(description = "配置说明", example = "默认文本模型")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "配置作用域,可选值:GLOBAL、COMPANY", example = "COMPANY")
|
||||||
|
private String scope;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run test and verify partial GREEN**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=OpenApiAnnotationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL remains, but missing class errors are gone. Failures should now point to missing Controller parameter annotations and raw `Map` request bodies.
|
||||||
|
|
||||||
|
### Task 3: Annotate Common Models, Existing DTOs, and Exposed Entities
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify common result classes, existing DTOs, and exposed entity files listed above
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add `@Schema` to `Result<T>` and `PageResult<T>`**
|
||||||
|
|
||||||
|
Update `Result<T>`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@Schema(description = "统一接口响应包装")
|
||||||
|
public class Result<T> {
|
||||||
|
@Schema(description = "业务状态码;成功为 SUCCESS,失败为具体错误码", example = "SUCCESS")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description = "接口返回主体;不同接口类型不同")
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
@Schema(description = "响应消息;失败时说明错误原因", example = "参数不合法")
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `PageResult<T>` similarly for `items`, `total`, `page`, `pageSize`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Complete existing DTO field descriptions**
|
||||||
|
|
||||||
|
Ensure every field in `LoginRequest`, `LoginResponse`, `UserInfoResponse`, `TaskResponse`, and `SourceResponse` has `@Schema(description = ..., example = ...)`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add class and field schemas to exposed entities**
|
||||||
|
|
||||||
|
For each exposed entity, add class-level `@Schema(description = "...")` and field-level `@Schema`.
|
||||||
|
|
||||||
|
Use these class descriptions:
|
||||||
|
|
||||||
|
- `SysCompany`: "租户公司"
|
||||||
|
- `SysUser`: "系统用户"
|
||||||
|
- `SysConfig`: "系统配置"
|
||||||
|
- `TrainingDataset`: "训练样本数据"
|
||||||
|
- `ExportBatch`: "训练数据导出批次"
|
||||||
|
- `VideoProcessJob`: "视频处理任务"
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run schema coverage test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=OpenApiAnnotationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: raw `Map` body and parameter annotation tests still fail, schema coverage should pass.
|
||||||
|
|
||||||
|
### Task 4: Refactor Controller Request Bodies to DTOs
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify Controller files listed in File Structure
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace company `Map` request bodies**
|
||||||
|
|
||||||
|
In `CompanyController`, replace:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public Result<SysCompany> create(@RequestBody Map<String, String> body)
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public Result<SysCompany> create(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "创建公司请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody CompanyCreateRequest body)
|
||||||
|
```
|
||||||
|
|
||||||
|
Call service with `body.getCompanyName()` and `body.getCompanyCode()`.
|
||||||
|
|
||||||
|
Apply the same pattern to `update` and `updateStatus`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Replace user `Map` request bodies**
|
||||||
|
|
||||||
|
In `UserController`, replace create/update/status/role request bodies with `UserCreateRequest`, `UserUpdateRequest`, `UserStatusUpdateRequest`, and `UserRoleUpdateRequest`.
|
||||||
|
|
||||||
|
Keep service calls unchanged except changing `body.get("field")` to DTO getters.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Replace task request bodies**
|
||||||
|
|
||||||
|
In `TaskController`, use `CreateTaskRequest` and `TaskReassignRequest`.
|
||||||
|
|
||||||
|
`createTask` service call becomes:
|
||||||
|
|
||||||
|
```java
|
||||||
|
taskService.createTask(body.getSourceId(), body.getTaskType(), principal.getCompanyId())
|
||||||
|
```
|
||||||
|
|
||||||
|
`reassign` target becomes:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Long targetUserId = body.getUserId();
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Replace export batch request body**
|
||||||
|
|
||||||
|
In `ExportController#createBatch`, use `ExportBatchCreateRequest`.
|
||||||
|
|
||||||
|
Service call becomes:
|
||||||
|
|
||||||
|
```java
|
||||||
|
return Result.success(exportService.createBatch(body.getSampleIds(), principal(request)));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Replace config update request body**
|
||||||
|
|
||||||
|
In `SysConfigController#updateConfig`, use `SysConfigUpdateRequest`.
|
||||||
|
|
||||||
|
Service call uses `body.getValue()` and `body.getDescription()`.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Replace video request bodies**
|
||||||
|
|
||||||
|
In `VideoController#createJob`, use `VideoProcessCreateRequest`.
|
||||||
|
|
||||||
|
In `VideoController#handleCallback`, use `VideoProcessCallbackRequest`.
|
||||||
|
|
||||||
|
Keep callback secret header logic unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Replace reject request bodies**
|
||||||
|
|
||||||
|
In `ExtractionController#reject` and `QaController#reject`, use `RejectRequest`.
|
||||||
|
|
||||||
|
Set:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String reason = body != null ? body.getReason() : null;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 8: Run raw Map body test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=OpenApiAnnotationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: raw `Map` request-body test passes. Parameter description test may still fail until Task 5.
|
||||||
|
|
||||||
|
### Task 5: Add Controller Parameter and Request Body OpenAPI Descriptions
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify all Controller files
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports**
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
```
|
||||||
|
|
||||||
|
Use fully qualified `io.swagger.v3.oas.annotations.parameters.RequestBody` for Swagger request body annotations to avoid conflict with Spring `@RequestBody`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Annotate pagination query parameters**
|
||||||
|
|
||||||
|
For every `page` parameter:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
|
@RequestParam(defaultValue = "1") int page
|
||||||
|
```
|
||||||
|
|
||||||
|
For every `pageSize` parameter:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
|
@RequestParam(defaultValue = "20") int pageSize
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Annotate status and type filters**
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "任务状态过滤,可选值:UNCLAIMED、IN_PROGRESS、SUBMITTED、APPROVED、REJECTED", example = "SUBMITTED")
|
||||||
|
@RequestParam(required = false) String status
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "任务类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION")
|
||||||
|
@RequestParam(required = false) String taskType
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Annotate path variables**
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long id
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "视频处理任务 ID", example = "123")
|
||||||
|
@PathVariable Long jobId
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "系统配置键", example = "ai.defaultTextModel")
|
||||||
|
@PathVariable String key
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Annotate multipart upload parameters**
|
||||||
|
|
||||||
|
In `SourceController#upload`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "上传文件,支持文本、图片、视频", required = true)
|
||||||
|
@RequestParam("file") MultipartFile file
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Parameter(description = "资料类型,可选值:text、image、video", example = "text", required = true)
|
||||||
|
@RequestParam("dataType") String dataType
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Annotate request bodies**
|
||||||
|
|
||||||
|
Every Spring `@RequestBody` parameter should also have Swagger request-body annotation:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "创建任务请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody CreateTaskRequest body
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run parameter coverage test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=OpenApiAnnotationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS for `OpenApiAnnotationTest`.
|
||||||
|
|
||||||
|
### Task 6: Make Fixed Map Responses Swagger-Readable
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify `ExportController.java`
|
||||||
|
- Modify `SysConfigController.java`
|
||||||
|
- Modify `ExtractionController.java`
|
||||||
|
- Modify `QaController.java`
|
||||||
|
- Modify `FinetuneService.java` only if needed to map stable fields cleanly
|
||||||
|
|
||||||
|
- [ ] **Step 1: Convert config list response**
|
||||||
|
|
||||||
|
Change `SysConfigController#listConfig` return type from:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Result<List<Map<String, Object>>>
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Result<List<SysConfigItemResponse>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Map each item from service map using keys currently returned by `SysConfigService.list`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Wrap dynamic extraction and QA responses**
|
||||||
|
|
||||||
|
Change `ExtractionController#getResult` and `QaController#getResult` return types from `Result<Map<String, Object>>` to `Result<DynamicJsonResponse>`.
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
|
||||||
|
```java
|
||||||
|
DynamicJsonResponse response = new DynamicJsonResponse();
|
||||||
|
response.setContent(extractionService.getResult(taskId, principal(request)));
|
||||||
|
return Result.success(response);
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the same pattern for QA.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Keep raw JSON update request bodies as raw strings**
|
||||||
|
|
||||||
|
Do not change `PUT /api/extraction/{taskId}` or `PUT /api/qa/{taskId}` from raw string request body unless compatibility is explicitly approved.
|
||||||
|
|
||||||
|
Instead, annotate the raw string body with a Swagger request-body description explaining it is a complete JSON string.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Convert finetune responses only if fields are stable**
|
||||||
|
|
||||||
|
Inspect `FinetuneService#trigger` and `FinetuneService#getStatus`.
|
||||||
|
|
||||||
|
If returned maps have stable keys, convert Controller responses to `FinetuneJobResponse` by mapping the known keys.
|
||||||
|
|
||||||
|
If keys are not stable, keep `Map<String,Object>` response but document `@Operation(description = "...")` and do not convert response type.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run compile test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -DskipTests compile
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
### Task 7: Run Focused Compatibility Tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify integration tests only if DTO request binding requires updating test payload helpers
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run OpenAPI annotation test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=OpenApiAnnotationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run API flows affected by DTO request body replacement**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=UserManagementIntegrationTest,SysConfigIntegrationTest,ExportIntegrationTest,VideoCallbackIdempotencyTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS. If Docker/Testcontainers is unavailable, record the failure reason and run `mvn -DskipTests compile` plus `OpenApiAnnotationTest`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run full test suite if environment supports Docker**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS, or Docker/Testcontainers-specific skip/failure documented.
|
||||||
|
|
||||||
|
### Task 8: Final Documentation and Review
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `README.md` only if implementation reveals stricter constraints than already documented
|
||||||
|
- Modify: `docs/superpowers/specs/2026-04-15-label-backend-swagger-annotations-design.md` only if a design decision changes during implementation
|
||||||
|
|
||||||
|
- [ ] **Step 1: Check for remaining raw request body maps**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "@RequestBody Map<|Map<String, Object> body|Map<String, String> body" src/main/java/com/label/controller
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: no fixed-structure request-body maps remain. Dynamic non-request-body maps may remain only where intentionally documented.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Check for missing `@Parameter` on public endpoint params**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -Dtest=OpenApiAnnotationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Review git diff**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff -- src/main/java src/test/java README.md docs/superpowers
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: diff only contains Swagger annotations, DTO additions, DTO request binding updates, and matching tests.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Keep implementation uncommitted for review**
|
||||||
|
|
||||||
|
Do not commit unless the user explicitly asks for it.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: implementation remains available in the worktree for review.
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
# label_backend Swagger 参数注解与 DTO 化设计
|
||||||
|
|
||||||
|
> 日期:2026-04-15
|
||||||
|
> 范围:`label_backend` 对外 REST API 的 Swagger/OpenAPI 文档增强
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
当前 `label_backend` 已经在 Controller 层使用了 `@Tag` 和 `@Operation`,少量 DTO 也已有 `@Schema` 注解,因此 Swagger 页面可以展示基础接口列表和部分对象结构。
|
||||||
|
|
||||||
|
但现状仍存在几个明显问题:
|
||||||
|
|
||||||
|
- 很多路径参数、查询参数、表单参数缺少名称、类型、取值和含义说明
|
||||||
|
- 多个接口仍使用 `Map<String, Object>` 或 `Map<String, String>` 作为请求体,Swagger 无法准确展示字段名、字段类型和字段说明
|
||||||
|
- 一些固定结构响应仍以 `Map<String, Object>` 返回,Swagger 只能显示匿名对象
|
||||||
|
- 部分接口直接暴露实体类,而实体字段尚未补齐 Swagger 字段描述
|
||||||
|
- 通用返回包装 `Result<T>`、`PageResult<T>` 没有字段级别文档说明
|
||||||
|
|
||||||
|
用户目标很明确:在 Swagger 中看到所有接口参数的名称、类型和含义。
|
||||||
|
|
||||||
|
## 2. 目标
|
||||||
|
|
||||||
|
本次改造完成后,`label_backend` 的 Swagger 页面应满足以下目标:
|
||||||
|
|
||||||
|
- 所有公开接口都能展示清晰的路径参数、查询参数、请求头参数、表单参数说明
|
||||||
|
- 所有固定结构的请求体都使用 DTO 建模,并为每个字段提供名称、类型、必填性和含义说明
|
||||||
|
- 所有固定结构的主要响应对象都能展示字段说明
|
||||||
|
- 所有分页与统一返回包装的字段含义清晰可见
|
||||||
|
- 不改变现有接口路径、HTTP 方法和字段名称,尽量保持对现有调用方兼容
|
||||||
|
|
||||||
|
## 3. 非目标
|
||||||
|
|
||||||
|
本次不做以下事情:
|
||||||
|
|
||||||
|
- 不重构整体业务流程
|
||||||
|
- 不调整接口 URL、HTTP 方法和权限策略
|
||||||
|
- 不强制把所有实体类都替换成专门的 Response DTO
|
||||||
|
- 不把完全动态的业务 JSON 全量重建成复杂深层对象,只对固定边界做显式包装
|
||||||
|
- 不顺手做无关的代码整理或目录重构
|
||||||
|
|
||||||
|
## 4. 设计原则
|
||||||
|
|
||||||
|
### 4.1 DTO-first
|
||||||
|
|
||||||
|
凡是 Swagger 需要清楚展示字段名、字段类型和字段说明的固定结构请求体,都应优先使用 DTO,而不是 `Map<String, Object>`。
|
||||||
|
|
||||||
|
### 4.2 文档增强优先,行为保持不变
|
||||||
|
|
||||||
|
本次的核心是 API 契约可读性增强,因此:
|
||||||
|
|
||||||
|
- Controller 仍调用原有 Service
|
||||||
|
- 参数字段名保持不变
|
||||||
|
- 业务语义、状态流转、权限校验保持不变
|
||||||
|
|
||||||
|
### 4.3 固定结构显式化,动态结构边界化
|
||||||
|
|
||||||
|
如果接口返回的是稳定字段集合,应使用明确 DTO 或明确对象字段注解。
|
||||||
|
|
||||||
|
如果业务内部结果本身仍是动态 JSON,则至少提供一个固定外层 DTO,把最外层字段含义说明清楚,避免 Swagger 展示匿名 `Map`。
|
||||||
|
|
||||||
|
### 4.4 最小可控改动
|
||||||
|
|
||||||
|
优先修改 Controller 入口、DTO 定义和 Swagger 展示对象,尽量不侵入 Service 深层逻辑。
|
||||||
|
|
||||||
|
## 5. 目标改造范围
|
||||||
|
|
||||||
|
### 5.1 Controller
|
||||||
|
|
||||||
|
本次覆盖以下 10 个 Controller:
|
||||||
|
|
||||||
|
- `AuthController`
|
||||||
|
- `CompanyController`
|
||||||
|
- `ExportController`
|
||||||
|
- `ExtractionController`
|
||||||
|
- `QaController`
|
||||||
|
- `SourceController`
|
||||||
|
- `SysConfigController`
|
||||||
|
- `TaskController`
|
||||||
|
- `UserController`
|
||||||
|
- `VideoController`
|
||||||
|
|
||||||
|
### 5.2 通用返回模型
|
||||||
|
|
||||||
|
- `Result<T>`
|
||||||
|
- `PageResult<T>`
|
||||||
|
|
||||||
|
### 5.3 现有 DTO
|
||||||
|
|
||||||
|
- `LoginRequest`
|
||||||
|
- `LoginResponse`
|
||||||
|
- `SourceResponse`
|
||||||
|
- `TaskResponse`
|
||||||
|
- `UserInfoResponse`
|
||||||
|
|
||||||
|
### 5.4 当前直接暴露给 Swagger 的实体
|
||||||
|
|
||||||
|
- `SysUser`
|
||||||
|
- `SysCompany`
|
||||||
|
- `SysConfig`
|
||||||
|
- `TrainingDataset`
|
||||||
|
- `ExportBatch`
|
||||||
|
- `VideoProcessJob`
|
||||||
|
|
||||||
|
## 6. DTO 改造策略
|
||||||
|
|
||||||
|
### 6.1 必须从 Map 重构为 DTO 的请求体
|
||||||
|
|
||||||
|
以下接口应从匿名请求体改为明确 DTO:
|
||||||
|
|
||||||
|
- `TaskController#createTask`
|
||||||
|
- 新增 `CreateTaskRequest`
|
||||||
|
- `TaskController#reassign`
|
||||||
|
- 新增 `TaskReassignRequest`
|
||||||
|
- `VideoController#createJob`
|
||||||
|
- 新增 `VideoProcessCreateRequest`
|
||||||
|
- `VideoController#handleCallback`
|
||||||
|
- 新增 `VideoProcessCallbackRequest`
|
||||||
|
- `CompanyController#update`
|
||||||
|
- 新增明确请求 DTO
|
||||||
|
- `CompanyController#updateStatus`
|
||||||
|
- 新增明确请求 DTO
|
||||||
|
- `ExportController#createBatch`
|
||||||
|
- 新增明确请求 DTO
|
||||||
|
- `UserController` 中如仍存在匿名请求体,也统一改成 DTO
|
||||||
|
|
||||||
|
### 6.2 固定结构响应优先改为 DTO
|
||||||
|
|
||||||
|
以下返回如果当前为 `Map<String, Object>` 但字段稳定,应收敛为 DTO:
|
||||||
|
|
||||||
|
- 导出与微调相关状态响应
|
||||||
|
- 视频回调相关固定结构响应
|
||||||
|
- 系统配置项列表响应
|
||||||
|
|
||||||
|
### 6.3 动态结果场景
|
||||||
|
|
||||||
|
`ExtractionController` 和 `QaController` 可能仍涉及结构化 JSON 结果。处理原则如下:
|
||||||
|
|
||||||
|
- 如果最外层字段稳定,则增加外层 DTO 说明
|
||||||
|
- 如果内部 `items` 仍允许动态内容,则在 DTO 字段级别说明该字段承载的业务 JSON 结构
|
||||||
|
|
||||||
|
## 7. Swagger 注解标准
|
||||||
|
|
||||||
|
### 7.1 Controller 方法
|
||||||
|
|
||||||
|
每个接口方法统一遵循:
|
||||||
|
|
||||||
|
- `@Tag`
|
||||||
|
- `@Operation(summary = "...", description = "...")`
|
||||||
|
- 对路径参数、查询参数、请求头参数、表单参数补 `@Parameter`
|
||||||
|
- 对请求体补 `@io.swagger.v3.oas.annotations.parameters.RequestBody`
|
||||||
|
|
||||||
|
参数描述至少包含:
|
||||||
|
|
||||||
|
- 参数业务含义
|
||||||
|
- 是否必填
|
||||||
|
- 枚举值范围或典型值
|
||||||
|
- 分页默认值或限制条件
|
||||||
|
|
||||||
|
### 7.2 DTO 字段
|
||||||
|
|
||||||
|
所有公开请求/响应 DTO 字段统一使用:
|
||||||
|
|
||||||
|
- `@Schema(description = "...", example = "...")`
|
||||||
|
|
||||||
|
必要时增加:
|
||||||
|
|
||||||
|
- `@NotNull`
|
||||||
|
- `@NotBlank`
|
||||||
|
- `@Valid`
|
||||||
|
|
||||||
|
### 7.3 通用包装类
|
||||||
|
|
||||||
|
`Result<T>` 应说明:
|
||||||
|
|
||||||
|
- `code`:业务状态码
|
||||||
|
- `data`:接口返回主体
|
||||||
|
- `message`:失败或补充说明
|
||||||
|
|
||||||
|
`PageResult<T>` 应说明:
|
||||||
|
|
||||||
|
- `items`:当前页数据列表
|
||||||
|
- `total`:总记录数
|
||||||
|
- `page`:当前页码,从 1 开始
|
||||||
|
- `pageSize`:每页条数
|
||||||
|
|
||||||
|
### 7.4 直接暴露实体
|
||||||
|
|
||||||
|
对于当前直接暴露给 Swagger 的实体类,给字段补齐 `@Schema` 说明,但不在本次强制转换为 Response DTO。
|
||||||
|
|
||||||
|
## 8. 代码组织方案
|
||||||
|
|
||||||
|
建议在 `dto/` 下继续保持扁平化风格,新增与接口语义一致的请求和响应类,命名以业务动作为中心,例如:
|
||||||
|
|
||||||
|
- `CreateTaskRequest`
|
||||||
|
- `TaskReassignRequest`
|
||||||
|
- `VideoProcessCreateRequest`
|
||||||
|
- `VideoProcessCallbackRequest`
|
||||||
|
- `CompanyUpdateRequest`
|
||||||
|
- `CompanyStatusUpdateRequest`
|
||||||
|
- `ExportBatchCreateRequest`
|
||||||
|
|
||||||
|
如果某个响应只在单个 Controller 使用,但字段固定,也放在 `dto/` 下,而不是内联 `Map`。
|
||||||
|
|
||||||
|
## 9. 兼容性要求
|
||||||
|
|
||||||
|
为了避免影响现有调用方,本次必须满足:
|
||||||
|
|
||||||
|
- 接口 URL 不变
|
||||||
|
- HTTP 方法不变
|
||||||
|
- JSON 字段名称不变
|
||||||
|
- Multipart 参数名不变
|
||||||
|
- 路径参数名和查询参数名不变
|
||||||
|
|
||||||
|
DTO 只是显式建模现有契约,不是重新设计契约。
|
||||||
|
|
||||||
|
## 9.1 README 约束同步
|
||||||
|
|
||||||
|
本次设计不仅要求代码层落地,还要求将 Swagger/DTO 约束写入项目 `README.md` 的开发规范中,作为后续接口开发的长期规则。
|
||||||
|
|
||||||
|
README 中至少应明确以下要求:
|
||||||
|
|
||||||
|
- 所有对外接口参数必须在 Swagger 中清楚展示名称、类型和含义
|
||||||
|
- 固定结构请求体禁止继续使用匿名 `Map`,必须定义 DTO
|
||||||
|
- 固定结构响应应优先显式建模,避免 Swagger 展示匿名对象
|
||||||
|
- 通用返回体和分页包装也必须维护字段说明
|
||||||
|
|
||||||
|
## 10. 测试策略
|
||||||
|
|
||||||
|
由于本次会把匿名请求体改为 DTO,需要用测试确认请求绑定行为没有被改坏。
|
||||||
|
|
||||||
|
测试策略如下:
|
||||||
|
|
||||||
|
- 优先补或更新 Controller 测试
|
||||||
|
- 覆盖 DTO 替换后的 JSON 反序列化
|
||||||
|
- 验证关键接口的请求字段名保持不变
|
||||||
|
- 验证原有成功路径和主要失败路径仍成立
|
||||||
|
|
||||||
|
至少应覆盖:
|
||||||
|
|
||||||
|
- 创建任务
|
||||||
|
- 重指派任务
|
||||||
|
- 创建视频处理任务
|
||||||
|
- 接收视频回调
|
||||||
|
- 创建导出批次
|
||||||
|
- 公司更新和状态更新
|
||||||
|
|
||||||
|
## 11. 风险与处理
|
||||||
|
|
||||||
|
### 11.1 风险:字段名不一致导致请求绑定失败
|
||||||
|
|
||||||
|
处理方式:
|
||||||
|
|
||||||
|
- DTO 字段严格对齐当前请求 JSON 的既有字段名
|
||||||
|
- 使用 Controller 测试验证兼容性
|
||||||
|
|
||||||
|
### 11.2 风险:动态 JSON 过度 DTO 化,导致业务边界变复杂
|
||||||
|
|
||||||
|
处理方式:
|
||||||
|
|
||||||
|
- 只对固定边界建模
|
||||||
|
- 动态业务内容保留为受控字段,不做过度深挖
|
||||||
|
|
||||||
|
### 11.3 风险:实体类字段太多,注解工作量大
|
||||||
|
|
||||||
|
处理方式:
|
||||||
|
|
||||||
|
- 只处理当前实际暴露到 Swagger 的实体
|
||||||
|
- 优先处理高频接口涉及对象
|
||||||
|
|
||||||
|
## 12. 实施顺序
|
||||||
|
|
||||||
|
推荐按以下顺序实施:
|
||||||
|
|
||||||
|
1. 给 `Result<T>`、`PageResult<T>` 补齐字段说明
|
||||||
|
2. 给已有公开 DTO 补齐 `@Schema`
|
||||||
|
3. 将匿名请求体改造成 DTO,并更新对应 Controller
|
||||||
|
4. 将固定结构的 `Map` 响应改造成 DTO
|
||||||
|
5. 为直接暴露的实体补齐 `@Schema`
|
||||||
|
6. 统一补齐 Controller 参数 `@Parameter` 注解
|
||||||
|
7. 更新或新增相关测试并执行验证
|
||||||
|
|
||||||
|
## 13. 验收标准
|
||||||
|
|
||||||
|
验收通过应满足以下条件:
|
||||||
|
|
||||||
|
- Swagger 页面中所有公开接口参数都能看到名称、类型和含义
|
||||||
|
- 所有固定结构请求体不再以匿名 `Map` 展示
|
||||||
|
- 主要响应对象字段说明齐全
|
||||||
|
- 通用返回体字段说明齐全
|
||||||
|
- `README.md` 已写入 Swagger/DTO 文档约束
|
||||||
|
- Controller 相关测试通过
|
||||||
|
- 未引入接口路径或字段名兼容性破坏
|
||||||
|
|
||||||
|
## 14. 决策总结
|
||||||
|
|
||||||
|
本次采用“DTO-first API 文档化”方案:
|
||||||
|
|
||||||
|
- 请求体优先 DTO 化
|
||||||
|
- 固定结构响应优先显式建模
|
||||||
|
- Controller 参数注释统一化
|
||||||
|
- 通用返回体和当前暴露实体补齐 Swagger 字段说明
|
||||||
|
- 在不改变业务语义的前提下,最大化提升 Swagger 可读性和接口契约清晰度
|
||||||
24
pom.xml
24
pom.xml
@@ -3,12 +3,10 @@
|
|||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>com.label</groupId>
|
<groupId>com.label</groupId>
|
||||||
<artifactId>label-backend</artifactId>
|
<artifactId>label-backend</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>21</java.version>
|
<java.version>21</java.version>
|
||||||
<spring.boot.version>3.1.5</spring.boot.version>
|
<spring.boot.version>3.1.5</spring.boot.version>
|
||||||
@@ -20,15 +18,13 @@
|
|||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
<dependency>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
<version>${spring.boot.version}</version>
|
||||||
<version>${spring.boot.version}</version>
|
<type>pom</type>
|
||||||
<type>pom</type>
|
<scope>import</scope>
|
||||||
<scope>import</scope>
|
</dependency>
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- AWS SDK v2 BOM -->
|
<!-- AWS SDK v2 BOM -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>software.amazon.awssdk</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
@@ -52,6 +48,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Spring Boot Actuator (health check endpoint) -->
|
<!-- Spring Boot Actuator (health check endpoint) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
package com.label.common.result;
|
package com.label.common.result;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@Schema(description = "分页响应")
|
||||||
public class PageResult<T> {
|
public class PageResult<T> {
|
||||||
|
@Schema(description = "当前页数据列表")
|
||||||
private List<T> items;
|
private List<T> items;
|
||||||
|
|
||||||
|
@Schema(description = "总条数", example = "123")
|
||||||
private long total;
|
private long total;
|
||||||
|
|
||||||
|
@Schema(description = "页码(从 1 开始)", example = "1")
|
||||||
private int page;
|
private int page;
|
||||||
|
|
||||||
|
@Schema(description = "每页条数", example = "20")
|
||||||
private int pageSize;
|
private int pageSize;
|
||||||
|
|
||||||
public static <T> PageResult<T> of(List<T> items, long total, int page, int pageSize) {
|
public static <T> PageResult<T> of(List<T> items, long total, int page, int pageSize) {
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package com.label.common.result;
|
package com.label.common.result;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@Schema(description = "通用响应包装")
|
||||||
public class Result<T> {
|
public class Result<T> {
|
||||||
|
@Schema(description = "业务状态码", example = "SUCCESS")
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description = "响应数据")
|
||||||
private T data;
|
private T data;
|
||||||
|
|
||||||
|
@Schema(description = "提示信息", example = "操作成功")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
public static <T> Result<T> success(T data) {
|
public static <T> Result<T> success(T data) {
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ public class AuthController {
|
|||||||
*/
|
*/
|
||||||
@Operation(summary = "用户登录,返回 Bearer Token")
|
@Operation(summary = "用户登录,返回 Bearer Token")
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public Result<LoginResponse> login(@RequestBody LoginRequest request) {
|
public Result<LoginResponse> login(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "用户登录请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody LoginRequest request) {
|
||||||
return Result.success(authService.login(request));
|
return Result.success(authService.login(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ package com.label.controller;
|
|||||||
import com.label.annotation.RequireRole;
|
import com.label.annotation.RequireRole;
|
||||||
import com.label.common.result.PageResult;
|
import com.label.common.result.PageResult;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.CompanyCreateRequest;
|
||||||
|
import com.label.dto.CompanyStatusUpdateRequest;
|
||||||
|
import com.label.dto.CompanyUpdateRequest;
|
||||||
import com.label.entity.SysCompany;
|
import com.label.entity.SysCompany;
|
||||||
import com.label.service.CompanyService;
|
import com.label.service.CompanyService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -20,8 +24,6 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Tag(name = "公司管理", description = "租户公司增删改查")
|
@Tag(name = "公司管理", description = "租户公司增删改查")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/label/api/companies")
|
@RequestMapping("/label/api/companies")
|
||||||
@@ -34,8 +36,11 @@ public class CompanyController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<PageResult<SysCompany>> list(
|
public Result<PageResult<SysCompany>> list(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
|
@Parameter(description = "公司状态过滤,可选值:ACTIVE、DISABLED", example = "ACTIVE")
|
||||||
@RequestParam(required = false) String status) {
|
@RequestParam(required = false) String status) {
|
||||||
return Result.success(companyService.list(page, pageSize, status));
|
return Result.success(companyService.list(page, pageSize, status));
|
||||||
}
|
}
|
||||||
@@ -44,29 +49,47 @@ public class CompanyController {
|
|||||||
@PostMapping
|
@PostMapping
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
public Result<SysCompany> create(@RequestBody Map<String, String> body) {
|
public Result<SysCompany> create(
|
||||||
return Result.success(companyService.create(body.get("companyName"), body.get("companyCode")));
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "创建公司请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody CompanyCreateRequest body) {
|
||||||
|
return Result.success(companyService.create(body.getCompanyName(), body.getCompanyCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "更新公司信息")
|
@Operation(summary = "更新公司信息")
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<SysCompany> update(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
public Result<SysCompany> update(
|
||||||
return Result.success(companyService.update(id, body.get("companyName"), body.get("companyCode")));
|
@Parameter(description = "公司 ID", example = "100")
|
||||||
|
@PathVariable Long id,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "更新公司信息请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody CompanyUpdateRequest body) {
|
||||||
|
return Result.success(companyService.update(id, body.getCompanyName(), body.getCompanyCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "更新公司状态")
|
@Operation(summary = "更新公司状态")
|
||||||
@PutMapping("/{id}/status")
|
@PutMapping("/{id}/status")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Void> updateStatus(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
public Result<Void> updateStatus(
|
||||||
companyService.updateStatus(id, body.get("status"));
|
@Parameter(description = "公司 ID", example = "100")
|
||||||
|
@PathVariable Long id,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "更新公司状态请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody CompanyStatusUpdateRequest body) {
|
||||||
|
companyService.updateStatus(id, body.getStatus());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "删除公司")
|
@Operation(summary = "删除公司")
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Void> delete(@PathVariable Long id) {
|
public Result<Void> delete(
|
||||||
|
@Parameter(description = "公司 ID", example = "100")
|
||||||
|
@PathVariable Long id) {
|
||||||
companyService.delete(id);
|
companyService.delete(id);
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import com.label.annotation.RequireRole;
|
|||||||
import com.label.common.auth.TokenPrincipal;
|
import com.label.common.auth.TokenPrincipal;
|
||||||
import com.label.common.result.PageResult;
|
import com.label.common.result.PageResult;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.ExportBatchCreateRequest;
|
||||||
|
import com.label.dto.FinetuneJobResponse;
|
||||||
import com.label.entity.TrainingDataset;
|
import com.label.entity.TrainingDataset;
|
||||||
import com.label.entity.ExportBatch;
|
import com.label.entity.ExportBatch;
|
||||||
import com.label.service.ExportService;
|
import com.label.service.ExportService;
|
||||||
import com.label.service.FinetuneService;
|
import com.label.service.FinetuneService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -35,9 +38,13 @@ public class ExportController {
|
|||||||
@GetMapping("/api/training/samples")
|
@GetMapping("/api/training/samples")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<PageResult<TrainingDataset>> listSamples(
|
public Result<PageResult<TrainingDataset>> listSamples(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
|
@Parameter(description = "样本类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION")
|
||||||
@RequestParam(required = false) String sampleType,
|
@RequestParam(required = false) String sampleType,
|
||||||
|
@Parameter(description = "是否已导出过滤", example = "false")
|
||||||
@RequestParam(required = false) Boolean exported,
|
@RequestParam(required = false) Boolean exported,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
return Result.success(exportService.listSamples(page, pageSize, sampleType, exported, principal(request)));
|
return Result.success(exportService.listSamples(page, pageSize, sampleType, exported, principal(request)));
|
||||||
@@ -48,32 +55,35 @@ public class ExportController {
|
|||||||
@PostMapping("/api/export/batch")
|
@PostMapping("/api/export/batch")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
public Result<ExportBatch> createBatch(@RequestBody Map<String, Object> body,
|
public Result<ExportBatch> createBatch(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "创建训练数据导出批次请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody ExportBatchCreateRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
@SuppressWarnings("unchecked")
|
return Result.success(exportService.createBatch(body.getSampleIds(), principal(request)));
|
||||||
List<Object> rawIds = (List<Object>) body.get("sampleIds");
|
|
||||||
List<Long> sampleIds = rawIds.stream()
|
|
||||||
.map(id -> Long.parseLong(id.toString()))
|
|
||||||
.toList();
|
|
||||||
return Result.success(exportService.createBatch(sampleIds, principal(request)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** POST /api/export/{batchId}/finetune — 提交微调任务 */
|
/** POST /api/export/{batchId}/finetune — 提交微调任务 */
|
||||||
@Operation(summary = "提交微调任务")
|
@Operation(summary = "提交微调任务")
|
||||||
@PostMapping("/api/export/{batchId}/finetune")
|
@PostMapping("/api/export/{batchId}/finetune")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Map<String, Object>> triggerFinetune(@PathVariable Long batchId,
|
public Result<FinetuneJobResponse> triggerFinetune(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "导出批次 ID", example = "501")
|
||||||
return Result.success(finetuneService.trigger(batchId, principal(request)));
|
@PathVariable Long batchId,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
return Result.success(toFinetuneJobResponse(finetuneService.trigger(batchId, principal(request))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET /api/export/{batchId}/status — 查询微调状态 */
|
/** GET /api/export/{batchId}/status — 查询微调状态 */
|
||||||
@Operation(summary = "查询微调状态")
|
@Operation(summary = "查询微调状态")
|
||||||
@GetMapping("/api/export/{batchId}/status")
|
@GetMapping("/api/export/{batchId}/status")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Map<String, Object>> getFinetuneStatus(@PathVariable Long batchId,
|
public Result<FinetuneJobResponse> getFinetuneStatus(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "导出批次 ID", example = "501")
|
||||||
return Result.success(finetuneService.getStatus(batchId, principal(request)));
|
@PathVariable Long batchId,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
return Result.success(toFinetuneJobResponse(finetuneService.getStatus(batchId, principal(request))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET /api/export/list — 分页查询导出批次列表 */
|
/** GET /api/export/list — 分页查询导出批次列表 */
|
||||||
@@ -81,12 +91,36 @@ public class ExportController {
|
|||||||
@GetMapping("/api/export/list")
|
@GetMapping("/api/export/list")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<PageResult<ExportBatch>> listBatches(
|
public Result<PageResult<ExportBatch>> listBatches(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
return Result.success(exportService.listBatches(page, pageSize, principal(request)));
|
return Result.success(exportService.listBatches(page, pageSize, principal(request)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FinetuneJobResponse toFinetuneJobResponse(Map<String, Object> values) {
|
||||||
|
FinetuneJobResponse response = new FinetuneJobResponse();
|
||||||
|
response.setBatchId(asLong(values.get("batchId")));
|
||||||
|
response.setGlmJobId(asString(values.get("glmJobId")));
|
||||||
|
response.setFinetuneStatus(asString(values.get("finetuneStatus")));
|
||||||
|
response.setProgress(asInteger(values.get("progress")));
|
||||||
|
response.setErrorMessage(asString(values.get("errorMessage")));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long asLong(Object value) {
|
||||||
|
return value == null ? null : Long.parseLong(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer asInteger(Object value) {
|
||||||
|
return value == null ? null : Integer.parseInt(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String asString(Object value) {
|
||||||
|
return value == null ? null : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private TokenPrincipal principal(HttpServletRequest request) {
|
private TokenPrincipal principal(HttpServletRequest request) {
|
||||||
return (TokenPrincipal) request.getAttribute("__token_principal__");
|
return (TokenPrincipal) request.getAttribute("__token_principal__");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package com.label.controller;
|
|||||||
import com.label.annotation.RequireRole;
|
import com.label.annotation.RequireRole;
|
||||||
import com.label.common.auth.TokenPrincipal;
|
import com.label.common.auth.TokenPrincipal;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.RejectRequest;
|
||||||
import com.label.service.ExtractionService;
|
import com.label.service.ExtractionService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -27,8 +29,10 @@ public class ExtractionController {
|
|||||||
@Operation(summary = "获取提取标注结果")
|
@Operation(summary = "获取提取标注结果")
|
||||||
@GetMapping("/{taskId}")
|
@GetMapping("/{taskId}")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Map<String, Object>> getResult(@PathVariable Long taskId,
|
public Result<Map<String, Object>> getResult(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
HttpServletRequest request) {
|
||||||
return Result.success(extractionService.getResult(taskId, principal(request)));
|
return Result.success(extractionService.getResult(taskId, principal(request)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +40,13 @@ public class ExtractionController {
|
|||||||
@Operation(summary = "更新提取标注结果")
|
@Operation(summary = "更新提取标注结果")
|
||||||
@PutMapping("/{taskId}")
|
@PutMapping("/{taskId}")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Void> updateResult(@PathVariable Long taskId,
|
public Result<Void> updateResult(
|
||||||
@RequestBody String resultJson,
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "完整提取标注结果 JSON 字符串,保持原始 JSON body 直接提交",
|
||||||
|
required = true)
|
||||||
|
@RequestBody String resultJson,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
extractionService.updateResult(taskId, resultJson, principal(request));
|
extractionService.updateResult(taskId, resultJson, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
@@ -47,8 +56,10 @@ public class ExtractionController {
|
|||||||
@Operation(summary = "提交提取标注结果")
|
@Operation(summary = "提交提取标注结果")
|
||||||
@PostMapping("/{taskId}/submit")
|
@PostMapping("/{taskId}/submit")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Void> submit(@PathVariable Long taskId,
|
public Result<Void> submit(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
HttpServletRequest request) {
|
||||||
extractionService.submit(taskId, principal(request));
|
extractionService.submit(taskId, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
@@ -57,8 +68,10 @@ public class ExtractionController {
|
|||||||
@Operation(summary = "审批通过提取结果")
|
@Operation(summary = "审批通过提取结果")
|
||||||
@PostMapping("/{taskId}/approve")
|
@PostMapping("/{taskId}/approve")
|
||||||
@RequireRole("REVIEWER")
|
@RequireRole("REVIEWER")
|
||||||
public Result<Void> approve(@PathVariable Long taskId,
|
public Result<Void> approve(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
HttpServletRequest request) {
|
||||||
extractionService.approve(taskId, principal(request));
|
extractionService.approve(taskId, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
@@ -67,10 +80,15 @@ public class ExtractionController {
|
|||||||
@Operation(summary = "驳回提取结果")
|
@Operation(summary = "驳回提取结果")
|
||||||
@PostMapping("/{taskId}/reject")
|
@PostMapping("/{taskId}/reject")
|
||||||
@RequireRole("REVIEWER")
|
@RequireRole("REVIEWER")
|
||||||
public Result<Void> reject(@PathVariable Long taskId,
|
public Result<Void> reject(
|
||||||
@RequestBody Map<String, String> body,
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "驳回提取结果请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody RejectRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
String reason = body != null ? body.get("reason") : null;
|
String reason = body != null ? body.getReason() : null;
|
||||||
extractionService.reject(taskId, reason, principal(request));
|
extractionService.reject(taskId, reason, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package com.label.controller;
|
|||||||
import com.label.annotation.RequireRole;
|
import com.label.annotation.RequireRole;
|
||||||
import com.label.common.auth.TokenPrincipal;
|
import com.label.common.auth.TokenPrincipal;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.RejectRequest;
|
||||||
import com.label.service.QaService;
|
import com.label.service.QaService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -27,8 +29,10 @@ public class QaController {
|
|||||||
@Operation(summary = "获取候选问答对")
|
@Operation(summary = "获取候选问答对")
|
||||||
@GetMapping("/{taskId}")
|
@GetMapping("/{taskId}")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Map<String, Object>> getResult(@PathVariable Long taskId,
|
public Result<Map<String, Object>> getResult(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
HttpServletRequest request) {
|
||||||
return Result.success(qaService.getResult(taskId, principal(request)));
|
return Result.success(qaService.getResult(taskId, principal(request)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +40,13 @@ public class QaController {
|
|||||||
@Operation(summary = "更新候选问答对")
|
@Operation(summary = "更新候选问答对")
|
||||||
@PutMapping("/{taskId}")
|
@PutMapping("/{taskId}")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Void> updateResult(@PathVariable Long taskId,
|
public Result<Void> updateResult(
|
||||||
@RequestBody String body,
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "完整问答标注结果 JSON 字符串,保持原始 JSON body 直接提交",
|
||||||
|
required = true)
|
||||||
|
@RequestBody String body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
qaService.updateResult(taskId, body, principal(request));
|
qaService.updateResult(taskId, body, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
@@ -47,8 +56,10 @@ public class QaController {
|
|||||||
@Operation(summary = "提交问答对")
|
@Operation(summary = "提交问答对")
|
||||||
@PostMapping("/{taskId}/submit")
|
@PostMapping("/{taskId}/submit")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Void> submit(@PathVariable Long taskId,
|
public Result<Void> submit(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
HttpServletRequest request) {
|
||||||
qaService.submit(taskId, principal(request));
|
qaService.submit(taskId, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
@@ -57,8 +68,10 @@ public class QaController {
|
|||||||
@Operation(summary = "审批通过问答对")
|
@Operation(summary = "审批通过问答对")
|
||||||
@PostMapping("/{taskId}/approve")
|
@PostMapping("/{taskId}/approve")
|
||||||
@RequireRole("REVIEWER")
|
@RequireRole("REVIEWER")
|
||||||
public Result<Void> approve(@PathVariable Long taskId,
|
public Result<Void> approve(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
HttpServletRequest request) {
|
||||||
qaService.approve(taskId, principal(request));
|
qaService.approve(taskId, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
@@ -67,10 +80,15 @@ public class QaController {
|
|||||||
@Operation(summary = "驳回答案对")
|
@Operation(summary = "驳回答案对")
|
||||||
@PostMapping("/{taskId}/reject")
|
@PostMapping("/{taskId}/reject")
|
||||||
@RequireRole("REVIEWER")
|
@RequireRole("REVIEWER")
|
||||||
public Result<Void> reject(@PathVariable Long taskId,
|
public Result<Void> reject(
|
||||||
@RequestBody Map<String, String> body,
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "驳回问答结果请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody RejectRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
String reason = body != null ? body.get("reason") : null;
|
String reason = body != null ? body.getReason() : null;
|
||||||
qaService.reject(taskId, reason, principal(request));
|
qaService.reject(taskId, reason, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.label.common.result.Result;
|
|||||||
import com.label.dto.SourceResponse;
|
import com.label.dto.SourceResponse;
|
||||||
import com.label.service.SourceService;
|
import com.label.service.SourceService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -38,7 +39,9 @@ public class SourceController {
|
|||||||
@RequireRole("UPLOADER")
|
@RequireRole("UPLOADER")
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
public Result<SourceResponse> upload(
|
public Result<SourceResponse> upload(
|
||||||
|
@Parameter(description = "上传文件,支持文本、图片、视频", required = true)
|
||||||
@RequestParam("file") MultipartFile file,
|
@RequestParam("file") MultipartFile file,
|
||||||
|
@Parameter(description = "资料类型,可选值:text、image、video", example = "text", required = true)
|
||||||
@RequestParam("dataType") String dataType,
|
@RequestParam("dataType") String dataType,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__");
|
TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__");
|
||||||
@@ -53,9 +56,13 @@ public class SourceController {
|
|||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@RequireRole("UPLOADER")
|
@RequireRole("UPLOADER")
|
||||||
public Result<PageResult<SourceResponse>> list(
|
public Result<PageResult<SourceResponse>> list(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
|
@Parameter(description = "资料类型过滤,可选值:text、image、video", example = "text")
|
||||||
@RequestParam(required = false) String dataType,
|
@RequestParam(required = false) String dataType,
|
||||||
|
@Parameter(description = "资料状态过滤", example = "PENDING")
|
||||||
@RequestParam(required = false) String status,
|
@RequestParam(required = false) String status,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__");
|
TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__");
|
||||||
@@ -68,7 +75,9 @@ public class SourceController {
|
|||||||
@Operation(summary = "查询资料详情")
|
@Operation(summary = "查询资料详情")
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@RequireRole("UPLOADER")
|
@RequireRole("UPLOADER")
|
||||||
public Result<SourceResponse> findById(@PathVariable Long id) {
|
public Result<SourceResponse> findById(
|
||||||
|
@Parameter(description = "资料 ID", example = "1001")
|
||||||
|
@PathVariable Long id) {
|
||||||
return Result.success(sourceService.findById(id));
|
return Result.success(sourceService.findById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +88,10 @@ public class SourceController {
|
|||||||
@Operation(summary = "删除资料")
|
@Operation(summary = "删除资料")
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Void> delete(@PathVariable Long id, HttpServletRequest request) {
|
public Result<Void> delete(
|
||||||
|
@Parameter(description = "资料 ID", example = "1001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
HttpServletRequest request) {
|
||||||
TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__");
|
TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__");
|
||||||
sourceService.delete(id, principal.getCompanyId());
|
sourceService.delete(id, principal.getCompanyId());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package com.label.controller;
|
|||||||
import com.label.annotation.RequireRole;
|
import com.label.annotation.RequireRole;
|
||||||
import com.label.common.auth.TokenPrincipal;
|
import com.label.common.auth.TokenPrincipal;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.SysConfigItemResponse;
|
||||||
|
import com.label.dto.SysConfigUpdateRequest;
|
||||||
import com.label.entity.SysConfig;
|
import com.label.entity.SysConfig;
|
||||||
import com.label.service.SysConfigService;
|
import com.label.service.SysConfigService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -38,9 +41,11 @@ public class SysConfigController {
|
|||||||
@Operation(summary = "查询合并后的系统配置")
|
@Operation(summary = "查询合并后的系统配置")
|
||||||
@GetMapping("/api/config")
|
@GetMapping("/api/config")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<List<Map<String, Object>>> listConfig(HttpServletRequest request) {
|
public Result<List<SysConfigItemResponse>> listConfig(HttpServletRequest request) {
|
||||||
TokenPrincipal principal = principal(request);
|
TokenPrincipal principal = principal(request);
|
||||||
return Result.success(sysConfigService.list(principal.getCompanyId()));
|
return Result.success(sysConfigService.list(principal.getCompanyId()).stream()
|
||||||
|
.map(this::toConfigItemResponse)
|
||||||
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,14 +56,36 @@ public class SysConfigController {
|
|||||||
@Operation(summary = "更新或创建公司专属配置")
|
@Operation(summary = "更新或创建公司专属配置")
|
||||||
@PutMapping("/api/config/{key}")
|
@PutMapping("/api/config/{key}")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<SysConfig> updateConfig(@PathVariable String key,
|
public Result<SysConfig> updateConfig(
|
||||||
@RequestBody Map<String, String> body,
|
@Parameter(description = "系统配置键,可选值:token_ttl_seconds、model_default、video_frame_interval", example = "model_default")
|
||||||
|
@PathVariable String key,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "系统配置更新请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody SysConfigUpdateRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
String value = body.get("value");
|
|
||||||
String description = body.get("description");
|
|
||||||
TokenPrincipal principal = principal(request);
|
TokenPrincipal principal = principal(request);
|
||||||
return Result.success(
|
return Result.success(
|
||||||
sysConfigService.update(key, value, description, principal.getCompanyId()));
|
sysConfigService.update(key, body.getValue(), body.getDescription(), principal.getCompanyId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysConfigItemResponse toConfigItemResponse(Map<String, Object> item) {
|
||||||
|
SysConfigItemResponse response = new SysConfigItemResponse();
|
||||||
|
response.setId(asLong(item.get("id")));
|
||||||
|
response.setConfigKey(asString(item.get("configKey")));
|
||||||
|
response.setConfigValue(asString(item.get("configValue")));
|
||||||
|
response.setDescription(asString(item.get("description")));
|
||||||
|
response.setScope(asString(item.get("scope")));
|
||||||
|
response.setCompanyId(asLong(item.get("companyId")));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long asLong(Object value) {
|
||||||
|
return value == null ? null : Long.parseLong(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String asString(Object value) {
|
||||||
|
return value == null ? null : value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenPrincipal principal(HttpServletRequest request) {
|
private TokenPrincipal principal(HttpServletRequest request) {
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ import com.label.annotation.RequireRole;
|
|||||||
import com.label.common.auth.TokenPrincipal;
|
import com.label.common.auth.TokenPrincipal;
|
||||||
import com.label.common.result.PageResult;
|
import com.label.common.result.PageResult;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.CreateTaskRequest;
|
||||||
|
import com.label.dto.TaskReassignRequest;
|
||||||
import com.label.dto.TaskResponse;
|
import com.label.dto.TaskResponse;
|
||||||
import com.label.service.TaskClaimService;
|
import com.label.service.TaskClaimService;
|
||||||
import com.label.service.TaskService;
|
import com.label.service.TaskService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任务管理接口(10 个端点)。
|
* 任务管理接口(10 个端点)。
|
||||||
*/
|
*/
|
||||||
@@ -32,7 +33,9 @@ public class TaskController {
|
|||||||
@GetMapping("/pool")
|
@GetMapping("/pool")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<PageResult<TaskResponse>> getPool(
|
public Result<PageResult<TaskResponse>> getPool(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
return Result.success(taskService.getPool(page, pageSize, principal(request)));
|
return Result.success(taskService.getPool(page, pageSize, principal(request)));
|
||||||
@@ -43,8 +46,11 @@ public class TaskController {
|
|||||||
@GetMapping("/mine")
|
@GetMapping("/mine")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<PageResult<TaskResponse>> getMine(
|
public Result<PageResult<TaskResponse>> getMine(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
|
@Parameter(description = "任务状态过滤,可选值:UNCLAIMED、IN_PROGRESS、SUBMITTED、APPROVED、REJECTED", example = "IN_PROGRESS")
|
||||||
@RequestParam(required = false) String status,
|
@RequestParam(required = false) String status,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
return Result.success(taskService.getMine(page, pageSize, status, principal(request)));
|
return Result.success(taskService.getMine(page, pageSize, status, principal(request)));
|
||||||
@@ -55,8 +61,11 @@ public class TaskController {
|
|||||||
@GetMapping("/pending-review")
|
@GetMapping("/pending-review")
|
||||||
@RequireRole("REVIEWER")
|
@RequireRole("REVIEWER")
|
||||||
public Result<PageResult<TaskResponse>> getPendingReview(
|
public Result<PageResult<TaskResponse>> getPendingReview(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
|
@Parameter(description = "任务类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION")
|
||||||
@RequestParam(required = false) String taskType) {
|
@RequestParam(required = false) String taskType) {
|
||||||
return Result.success(taskService.getPendingReview(page, pageSize, taskType));
|
return Result.success(taskService.getPendingReview(page, pageSize, taskType));
|
||||||
}
|
}
|
||||||
@@ -66,9 +75,13 @@ public class TaskController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<PageResult<TaskResponse>> getAll(
|
public Result<PageResult<TaskResponse>> getAll(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
|
@Parameter(description = "任务状态过滤,可选值:UNCLAIMED、IN_PROGRESS、SUBMITTED、APPROVED、REJECTED", example = "SUBMITTED")
|
||||||
@RequestParam(required = false) String status,
|
@RequestParam(required = false) String status,
|
||||||
|
@Parameter(description = "任务类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "QA_GENERATION")
|
||||||
@RequestParam(required = false) String taskType) {
|
@RequestParam(required = false) String taskType) {
|
||||||
return Result.success(taskService.getAll(page, pageSize, status, taskType));
|
return Result.success(taskService.getAll(page, pageSize, status, taskType));
|
||||||
}
|
}
|
||||||
@@ -77,20 +90,24 @@ public class TaskController {
|
|||||||
@Operation(summary = "管理员创建任务")
|
@Operation(summary = "管理员创建任务")
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<TaskResponse> createTask(@RequestBody Map<String, Object> body,
|
public Result<TaskResponse> createTask(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "创建标注任务请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody CreateTaskRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
Long sourceId = Long.parseLong(body.get("sourceId").toString());
|
|
||||||
String taskType = body.get("taskType").toString();
|
|
||||||
TokenPrincipal principal = principal(request);
|
TokenPrincipal principal = principal(request);
|
||||||
return Result.success(taskService.toPublicResponse(
|
return Result.success(taskService.toPublicResponse(
|
||||||
taskService.createTask(sourceId, taskType, principal.getCompanyId())));
|
taskService.createTask(body.getSourceId(), body.getTaskType(), principal.getCompanyId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET /api/tasks/{id} — 查询任务详情 */
|
/** GET /api/tasks/{id} — 查询任务详情 */
|
||||||
@Operation(summary = "查询任务详情")
|
@Operation(summary = "查询任务详情")
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<TaskResponse> getById(@PathVariable Long id) {
|
public Result<TaskResponse> getById(
|
||||||
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long id) {
|
||||||
return Result.success(taskService.toPublicResponse(taskService.getById(id)));
|
return Result.success(taskService.toPublicResponse(taskService.getById(id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +115,10 @@ public class TaskController {
|
|||||||
@Operation(summary = "领取任务")
|
@Operation(summary = "领取任务")
|
||||||
@PostMapping("/{id}/claim")
|
@PostMapping("/{id}/claim")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Void> claim(@PathVariable Long id, HttpServletRequest request) {
|
public Result<Void> claim(
|
||||||
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
HttpServletRequest request) {
|
||||||
taskClaimService.claim(id, principal(request));
|
taskClaimService.claim(id, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
@@ -107,7 +127,10 @@ public class TaskController {
|
|||||||
@Operation(summary = "放弃任务")
|
@Operation(summary = "放弃任务")
|
||||||
@PostMapping("/{id}/unclaim")
|
@PostMapping("/{id}/unclaim")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Void> unclaim(@PathVariable Long id, HttpServletRequest request) {
|
public Result<Void> unclaim(
|
||||||
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
HttpServletRequest request) {
|
||||||
taskClaimService.unclaim(id, principal(request));
|
taskClaimService.unclaim(id, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
@@ -116,7 +139,10 @@ public class TaskController {
|
|||||||
@Operation(summary = "重领被驳回的任务")
|
@Operation(summary = "重领被驳回的任务")
|
||||||
@PostMapping("/{id}/reclaim")
|
@PostMapping("/{id}/reclaim")
|
||||||
@RequireRole("ANNOTATOR")
|
@RequireRole("ANNOTATOR")
|
||||||
public Result<Void> reclaim(@PathVariable Long id, HttpServletRequest request) {
|
public Result<Void> reclaim(
|
||||||
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
HttpServletRequest request) {
|
||||||
taskClaimService.reclaim(id, principal(request));
|
taskClaimService.reclaim(id, principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
@@ -125,11 +151,15 @@ public class TaskController {
|
|||||||
@Operation(summary = "管理员强制指派任务")
|
@Operation(summary = "管理员强制指派任务")
|
||||||
@PutMapping("/{id}/reassign")
|
@PutMapping("/{id}/reassign")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Void> reassign(@PathVariable Long id,
|
public Result<Void> reassign(
|
||||||
@RequestBody Map<String, Object> body,
|
@Parameter(description = "任务 ID", example = "1001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "管理员强制改派任务请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody TaskReassignRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
Long targetUserId = Long.parseLong(body.get("userId").toString());
|
taskService.reassign(id, body.getUserId(), principal(request));
|
||||||
taskService.reassign(id, targetUserId, principal(request));
|
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.label.controller;
|
package com.label.controller;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -15,10 +13,15 @@ import com.label.annotation.RequireRole;
|
|||||||
import com.label.common.auth.TokenPrincipal;
|
import com.label.common.auth.TokenPrincipal;
|
||||||
import com.label.common.result.PageResult;
|
import com.label.common.result.PageResult;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.UserCreateRequest;
|
||||||
|
import com.label.dto.UserRoleUpdateRequest;
|
||||||
|
import com.label.dto.UserStatusUpdateRequest;
|
||||||
|
import com.label.dto.UserUpdateRequest;
|
||||||
import com.label.entity.SysUser;
|
import com.label.entity.SysUser;
|
||||||
import com.label.service.UserService;
|
import com.label.service.UserService;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -39,7 +42,9 @@ public class UserController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<PageResult<SysUser>> listUsers(
|
public Result<PageResult<SysUser>> listUsers(
|
||||||
|
@Parameter(description = "页码,从 1 开始", example = "1")
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@Parameter(description = "每页条数", example = "20")
|
||||||
@RequestParam(defaultValue = "20") int pageSize,
|
@RequestParam(defaultValue = "20") int pageSize,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
return Result.success(userService.listUsers(page, pageSize, principal(request)));
|
return Result.success(userService.listUsers(page, pageSize, principal(request)));
|
||||||
@@ -49,13 +54,17 @@ public class UserController {
|
|||||||
@Operation(summary = "创建用户")
|
@Operation(summary = "创建用户")
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<SysUser> createUser(@RequestBody Map<String, String> body,
|
public Result<SysUser> createUser(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "创建用户请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody UserCreateRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
return Result.success(userService.createUser(
|
return Result.success(userService.createUser(
|
||||||
body.get("username"),
|
body.getUsername(),
|
||||||
body.get("password"),
|
body.getPassword(),
|
||||||
body.get("realName"),
|
body.getRealName(),
|
||||||
body.get("role"),
|
body.getRole(),
|
||||||
principal(request)));
|
principal(request)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,13 +72,18 @@ public class UserController {
|
|||||||
@Operation(summary = "更新用户基本信息")
|
@Operation(summary = "更新用户基本信息")
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<SysUser> updateUser(@PathVariable Long id,
|
public Result<SysUser> updateUser(
|
||||||
@RequestBody Map<String, String> body,
|
@Parameter(description = "用户 ID", example = "2001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "更新用户基本信息请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody UserUpdateRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
return Result.success(userService.updateUser(
|
return Result.success(userService.updateUser(
|
||||||
id,
|
id,
|
||||||
body.get("realName"),
|
body.getRealName(),
|
||||||
body.get("password"),
|
body.getPassword(),
|
||||||
principal(request)));
|
principal(request)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,10 +91,15 @@ public class UserController {
|
|||||||
@Operation(summary = "变更用户状态", description = "status:ACTIVE、DISABLED")
|
@Operation(summary = "变更用户状态", description = "status:ACTIVE、DISABLED")
|
||||||
@PutMapping("/{id}/status")
|
@PutMapping("/{id}/status")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Void> updateStatus(@PathVariable Long id,
|
public Result<Void> updateStatus(
|
||||||
@RequestBody Map<String, String> body,
|
@Parameter(description = "用户 ID", example = "2001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "更新用户状态请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody UserStatusUpdateRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
userService.updateStatus(id, body.get("status"), principal(request));
|
userService.updateStatus(id, body.getStatus(), principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,10 +107,15 @@ public class UserController {
|
|||||||
@Operation(summary = "变更用户角色", description = "role:ADMIN、UPLOADER、VIEWER")
|
@Operation(summary = "变更用户角色", description = "role:ADMIN、UPLOADER、VIEWER")
|
||||||
@PutMapping("/{id}/role")
|
@PutMapping("/{id}/role")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<Void> updateRole(@PathVariable Long id,
|
public Result<Void> updateRole(
|
||||||
@RequestBody Map<String, String> body,
|
@Parameter(description = "用户 ID", example = "2001")
|
||||||
|
@PathVariable Long id,
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "更新用户角色请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody UserRoleUpdateRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
userService.updateRole(id, body.get("role"), principal(request));
|
userService.updateRole(id, body.getRole(), principal(request));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package com.label.controller;
|
|||||||
import com.label.annotation.RequireRole;
|
import com.label.annotation.RequireRole;
|
||||||
import com.label.common.auth.TokenPrincipal;
|
import com.label.common.auth.TokenPrincipal;
|
||||||
import com.label.common.result.Result;
|
import com.label.common.result.Result;
|
||||||
|
import com.label.dto.VideoProcessCallbackRequest;
|
||||||
|
import com.label.dto.VideoProcessCreateRequest;
|
||||||
import com.label.entity.VideoProcessJob;
|
import com.label.entity.VideoProcessJob;
|
||||||
import com.label.service.VideoProcessService;
|
import com.label.service.VideoProcessService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -13,8 +16,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 视频处理接口(4 个端点)。
|
* 视频处理接口(4 个端点)。
|
||||||
*
|
*
|
||||||
@@ -39,16 +40,18 @@ public class VideoController {
|
|||||||
@Operation(summary = "触发视频处理任务")
|
@Operation(summary = "触发视频处理任务")
|
||||||
@PostMapping("/api/video/process")
|
@PostMapping("/api/video/process")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<VideoProcessJob> createJob(@RequestBody Map<String, Object> body,
|
public Result<VideoProcessJob> createJob(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "创建视频处理任务请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody VideoProcessCreateRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
Object sourceIdVal = body.get("sourceId");
|
Long sourceId = body.getSourceId();
|
||||||
Object jobTypeVal = body.get("jobType");
|
String jobType = body.getJobType();
|
||||||
if (sourceIdVal == null || jobTypeVal == null) {
|
if (sourceId == null || jobType == null) {
|
||||||
return Result.failure("INVALID_PARAMS", "sourceId 和 jobType 不能为空");
|
return Result.failure("INVALID_PARAMS", "sourceId 和 jobType 不能为空");
|
||||||
}
|
}
|
||||||
Long sourceId = Long.parseLong(sourceIdVal.toString());
|
String params = body.getParams();
|
||||||
String jobType = jobTypeVal.toString();
|
|
||||||
String params = body.containsKey("params") ? body.get("params").toString() : null;
|
|
||||||
|
|
||||||
TokenPrincipal principal = principal(request);
|
TokenPrincipal principal = principal(request);
|
||||||
return Result.success(
|
return Result.success(
|
||||||
@@ -59,8 +62,10 @@ public class VideoController {
|
|||||||
@Operation(summary = "查询视频处理任务状态")
|
@Operation(summary = "查询视频处理任务状态")
|
||||||
@GetMapping("/api/video/jobs/{jobId}")
|
@GetMapping("/api/video/jobs/{jobId}")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<VideoProcessJob> getJob(@PathVariable Long jobId,
|
public Result<VideoProcessJob> getJob(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "视频处理任务 ID", example = "9001")
|
||||||
|
@PathVariable Long jobId,
|
||||||
|
HttpServletRequest request) {
|
||||||
return Result.success(videoProcessService.getJob(jobId, principal(request).getCompanyId()));
|
return Result.success(videoProcessService.getJob(jobId, principal(request).getCompanyId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +73,10 @@ public class VideoController {
|
|||||||
@Operation(summary = "重置失败的视频处理任务")
|
@Operation(summary = "重置失败的视频处理任务")
|
||||||
@PostMapping("/api/video/jobs/{jobId}/reset")
|
@PostMapping("/api/video/jobs/{jobId}/reset")
|
||||||
@RequireRole("ADMIN")
|
@RequireRole("ADMIN")
|
||||||
public Result<VideoProcessJob> resetJob(@PathVariable Long jobId,
|
public Result<VideoProcessJob> resetJob(
|
||||||
HttpServletRequest request) {
|
@Parameter(description = "视频处理任务 ID", example = "9001")
|
||||||
|
@PathVariable Long jobId,
|
||||||
|
HttpServletRequest request) {
|
||||||
return Result.success(videoProcessService.reset(jobId, principal(request).getCompanyId()));
|
return Result.success(videoProcessService.reset(jobId, principal(request).getCompanyId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +92,11 @@ public class VideoController {
|
|||||||
*/
|
*/
|
||||||
@Operation(summary = "接收 AI 服务视频处理回调")
|
@Operation(summary = "接收 AI 服务视频处理回调")
|
||||||
@PostMapping("/api/video/callback")
|
@PostMapping("/api/video/callback")
|
||||||
public Result<Void> handleCallback(@RequestBody Map<String, Object> body,
|
public Result<Void> handleCallback(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "AI 服务视频处理回调请求体",
|
||||||
|
required = true)
|
||||||
|
@RequestBody VideoProcessCallbackRequest body,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
// 共享密钥校验(配置了 VIDEO_CALLBACK_SECRET 时强制校验)
|
// 共享密钥校验(配置了 VIDEO_CALLBACK_SECRET 时强制校验)
|
||||||
if (callbackSecret != null && !callbackSecret.isBlank()) {
|
if (callbackSecret != null && !callbackSecret.isBlank()) {
|
||||||
@@ -95,10 +106,10 @@ public class VideoController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Long jobId = Long.parseLong(body.get("jobId").toString());
|
Long jobId = body.getJobId();
|
||||||
String status = (String) body.get("status");
|
String status = body.getStatus();
|
||||||
String outputPath = body.containsKey("outputPath") ? (String) body.get("outputPath") : null;
|
String outputPath = body.getOutputPath();
|
||||||
String errorMessage = body.containsKey("errorMessage") ? (String) body.get("errorMessage") : null;
|
String errorMessage = body.getErrorMessage();
|
||||||
|
|
||||||
log.info("视频处理回调:jobId={}, status={}", jobId, status);
|
log.info("视频处理回调:jobId={}, status={}", jobId, status);
|
||||||
videoProcessService.handleCallback(jobId, status, outputPath, errorMessage);
|
videoProcessService.handleCallback(jobId, status, outputPath, errorMessage);
|
||||||
|
|||||||
14
src/main/java/com/label/dto/CompanyCreateRequest.java
Normal file
14
src/main/java/com/label/dto/CompanyCreateRequest.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建公司请求")
|
||||||
|
public class CompanyCreateRequest {
|
||||||
|
@Schema(description = "公司名称", example = "示例科技")
|
||||||
|
private String companyName;
|
||||||
|
|
||||||
|
@Schema(description = "公司代码(英文简写)", example = "DEMO")
|
||||||
|
private String companyCode;
|
||||||
|
}
|
||||||
11
src/main/java/com/label/dto/CompanyStatusUpdateRequest.java
Normal file
11
src/main/java/com/label/dto/CompanyStatusUpdateRequest.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "公司状态变更请求")
|
||||||
|
public class CompanyStatusUpdateRequest {
|
||||||
|
@Schema(description = "公司状态,可选值:ACTIVE / DISABLED", example = "ACTIVE")
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
14
src/main/java/com/label/dto/CompanyUpdateRequest.java
Normal file
14
src/main/java/com/label/dto/CompanyUpdateRequest.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "更新公司请求")
|
||||||
|
public class CompanyUpdateRequest {
|
||||||
|
@Schema(description = "公司名称", example = "示例科技(升级版)")
|
||||||
|
private String companyName;
|
||||||
|
|
||||||
|
@Schema(description = "公司代码(英文简写)", example = "DEMO")
|
||||||
|
private String companyCode;
|
||||||
|
}
|
||||||
14
src/main/java/com/label/dto/CreateTaskRequest.java
Normal file
14
src/main/java/com/label/dto/CreateTaskRequest.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建任务请求")
|
||||||
|
public class CreateTaskRequest {
|
||||||
|
@Schema(description = "资料 ID", example = "1001")
|
||||||
|
private Long sourceId;
|
||||||
|
|
||||||
|
@Schema(description = "任务类型,可选值:EXTRACTION / QA_GENERATION", example = "EXTRACTION")
|
||||||
|
private String taskType;
|
||||||
|
}
|
||||||
13
src/main/java/com/label/dto/DynamicJsonResponse.java
Normal file
13
src/main/java/com/label/dto/DynamicJsonResponse.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "动态 JSON 响应")
|
||||||
|
public class DynamicJsonResponse {
|
||||||
|
@Schema(description = "动态 JSON 内容", example = "{\"label\":\"cat\",\"score\":0.98}")
|
||||||
|
private Map<String, Object> content;
|
||||||
|
}
|
||||||
13
src/main/java/com/label/dto/ExportBatchCreateRequest.java
Normal file
13
src/main/java/com/label/dto/ExportBatchCreateRequest.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建导出批次请求")
|
||||||
|
public class ExportBatchCreateRequest {
|
||||||
|
@Schema(description = "样本 ID 列表", example = "[101, 102, 103]")
|
||||||
|
private List<Long> sampleIds;
|
||||||
|
}
|
||||||
23
src/main/java/com/label/dto/FinetuneJobResponse.java
Normal file
23
src/main/java/com/label/dto/FinetuneJobResponse.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "微调任务响应")
|
||||||
|
public class FinetuneJobResponse {
|
||||||
|
@Schema(description = "导出批次 ID", example = "501")
|
||||||
|
private Long batchId;
|
||||||
|
|
||||||
|
@Schema(description = "GLM 微调任务 ID", example = "glm-ft-001")
|
||||||
|
private String glmJobId;
|
||||||
|
|
||||||
|
@Schema(description = "微调状态", example = "RUNNING")
|
||||||
|
private String finetuneStatus;
|
||||||
|
|
||||||
|
@Schema(description = "进度百分比", example = "35")
|
||||||
|
private Integer progress;
|
||||||
|
|
||||||
|
@Schema(description = "错误信息", example = "")
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
@@ -15,10 +15,10 @@ public class LoginResponse {
|
|||||||
@Schema(description = "Bearer Token", example = "550e8400-e29b-41d4-a716-446655440000")
|
@Schema(description = "Bearer Token", example = "550e8400-e29b-41d4-a716-446655440000")
|
||||||
private String token;
|
private String token;
|
||||||
/** 用户主键 */
|
/** 用户主键 */
|
||||||
@Schema(description = "用户主键")
|
@Schema(description = "用户主键", example = "1")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
/** 登录用户名 */
|
/** 登录用户名 */
|
||||||
@Schema(description = "登录用户名")
|
@Schema(description = "登录用户名", example = "admin")
|
||||||
private String username;
|
private String username;
|
||||||
/** 角色:UPLOADER / ANNOTATOR / REVIEWER / ADMIN */
|
/** 角色:UPLOADER / ANNOTATOR / REVIEWER / ADMIN */
|
||||||
@Schema(description = "角色", example = "ADMIN")
|
@Schema(description = "角色", example = "ADMIN")
|
||||||
|
|||||||
11
src/main/java/com/label/dto/RejectRequest.java
Normal file
11
src/main/java/com/label/dto/RejectRequest.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "驳回请求")
|
||||||
|
public class RejectRequest {
|
||||||
|
@Schema(description = "驳回原因", example = "标注结果缺少关键字段")
|
||||||
|
private String reason;
|
||||||
|
}
|
||||||
@@ -14,25 +14,25 @@ import java.time.LocalDateTime;
|
|||||||
@Builder
|
@Builder
|
||||||
@Schema(description = "原始资料响应")
|
@Schema(description = "原始资料响应")
|
||||||
public class SourceResponse {
|
public class SourceResponse {
|
||||||
@Schema(description = "资料主键")
|
@Schema(description = "资料主键", example = "2001")
|
||||||
private Long id;
|
private Long id;
|
||||||
@Schema(description = "文件名")
|
@Schema(description = "文件名", example = "demo.txt")
|
||||||
private String fileName;
|
private String fileName;
|
||||||
@Schema(description = "资料类型", example = "TEXT")
|
@Schema(description = "资料类型", example = "TEXT")
|
||||||
private String dataType;
|
private String dataType;
|
||||||
@Schema(description = "文件大小(字节)")
|
@Schema(description = "文件大小(字节)", example = "1024")
|
||||||
private Long fileSize;
|
private Long fileSize;
|
||||||
@Schema(description = "资料状态", example = "PENDING")
|
@Schema(description = "资料状态", example = "PENDING")
|
||||||
private String status;
|
private String status;
|
||||||
/** 上传用户 ID(列表端点返回) */
|
/** 上传用户 ID(列表端点返回) */
|
||||||
@Schema(description = "上传用户 ID")
|
@Schema(description = "上传用户 ID", example = "1")
|
||||||
private Long uploaderId;
|
private Long uploaderId;
|
||||||
/** 15 分钟预签名下载链接(详情端点返回) */
|
/** 15 分钟预签名下载链接(详情端点返回) */
|
||||||
@Schema(description = "预签名下载链接")
|
@Schema(description = "预签名下载链接", example = "https://example.com/presigned-url")
|
||||||
private String presignedUrl;
|
private String presignedUrl;
|
||||||
/** 父资料 ID(视频帧 / 文本片段;详情端点返回) */
|
/** 父资料 ID(视频帧 / 文本片段;详情端点返回) */
|
||||||
@Schema(description = "父资料 ID")
|
@Schema(description = "父资料 ID", example = "1001")
|
||||||
private Long parentSourceId;
|
private Long parentSourceId;
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/main/java/com/label/dto/SysConfigItemResponse.java
Normal file
26
src/main/java/com/label/dto/SysConfigItemResponse.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "系统配置项响应")
|
||||||
|
public class SysConfigItemResponse {
|
||||||
|
@Schema(description = "配置主键", example = "1")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "配置键", example = "model_default")
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
@Schema(description = "配置值", example = "glm-4-flash")
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
@Schema(description = "配置说明", example = "默认文本模型")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "配置来源作用域,可选值:COMPANY、GLOBAL", example = "COMPANY")
|
||||||
|
private String scope;
|
||||||
|
|
||||||
|
@Schema(description = "所属公司 ID;GLOBAL 配置为空,COMPANY 配置为当前公司 ID", example = "100")
|
||||||
|
private Long companyId;
|
||||||
|
}
|
||||||
14
src/main/java/com/label/dto/SysConfigUpdateRequest.java
Normal file
14
src/main/java/com/label/dto/SysConfigUpdateRequest.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "系统配置更新请求")
|
||||||
|
public class SysConfigUpdateRequest {
|
||||||
|
@Schema(description = "配置值", example = "https://api.example.com")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
@Schema(description = "配置说明", example = "AI 服务基础地址")
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
11
src/main/java/com/label/dto/TaskReassignRequest.java
Normal file
11
src/main/java/com/label/dto/TaskReassignRequest.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "任务改派请求")
|
||||||
|
public class TaskReassignRequest {
|
||||||
|
@Schema(description = "目标用户 ID", example = "2001")
|
||||||
|
private Long userId;
|
||||||
|
}
|
||||||
@@ -13,26 +13,26 @@ import java.time.LocalDateTime;
|
|||||||
@Builder
|
@Builder
|
||||||
@Schema(description = "标注任务响应")
|
@Schema(description = "标注任务响应")
|
||||||
public class TaskResponse {
|
public class TaskResponse {
|
||||||
@Schema(description = "任务主键")
|
@Schema(description = "任务主键", example = "1001")
|
||||||
private Long id;
|
private Long id;
|
||||||
@Schema(description = "关联资料 ID")
|
@Schema(description = "关联资料 ID", example = "2001")
|
||||||
private Long sourceId;
|
private Long sourceId;
|
||||||
/** 任务类型(对应 taskType 字段):EXTRACTION / QA_GENERATION */
|
/** 任务类型(对应 taskType 字段):EXTRACTION / QA_GENERATION */
|
||||||
@Schema(description = "任务类型", example = "EXTRACTION")
|
@Schema(description = "任务类型", example = "EXTRACTION")
|
||||||
private String taskType;
|
private String taskType;
|
||||||
@Schema(description = "任务状态", example = "UNCLAIMED")
|
@Schema(description = "任务状态", example = "UNCLAIMED")
|
||||||
private String status;
|
private String status;
|
||||||
@Schema(description = "领取人用户 ID")
|
@Schema(description = "领取人用户 ID", example = "1")
|
||||||
private Long claimedBy;
|
private Long claimedBy;
|
||||||
@Schema(description = "领取时间")
|
@Schema(description = "领取时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime claimedAt;
|
private LocalDateTime claimedAt;
|
||||||
@Schema(description = "提交时间")
|
@Schema(description = "提交时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime submittedAt;
|
private LocalDateTime submittedAt;
|
||||||
@Schema(description = "完成时间")
|
@Schema(description = "完成时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime completedAt;
|
private LocalDateTime completedAt;
|
||||||
/** 驳回原因(REJECTED 状态时非空) */
|
/** 驳回原因(REJECTED 状态时非空) */
|
||||||
@Schema(description = "驳回原因")
|
@Schema(description = "驳回原因")
|
||||||
private String rejectReason;
|
private String rejectReason;
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/main/java/com/label/dto/UserCreateRequest.java
Normal file
20
src/main/java/com/label/dto/UserCreateRequest.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建用户请求")
|
||||||
|
public class UserCreateRequest {
|
||||||
|
@Schema(description = "登录用户名", example = "reviewer01")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "明文密码", example = "Pass@123")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Schema(description = "真实姓名", example = "张三")
|
||||||
|
private String realName;
|
||||||
|
|
||||||
|
@Schema(description = "角色,可选值:ADMIN / REVIEWER / ANNOTATOR / UPLOADER", example = "REVIEWER")
|
||||||
|
private String role;
|
||||||
|
}
|
||||||
@@ -11,16 +11,16 @@ import lombok.Data;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Schema(description = "当前登录用户信息")
|
@Schema(description = "当前登录用户信息")
|
||||||
public class UserInfoResponse {
|
public class UserInfoResponse {
|
||||||
@Schema(description = "用户主键")
|
@Schema(description = "用户主键", example = "1")
|
||||||
private Long id;
|
private Long id;
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名", example = "admin")
|
||||||
private String username;
|
private String username;
|
||||||
@Schema(description = "真实姓名")
|
@Schema(description = "真实姓名", example = "张三")
|
||||||
private String realName;
|
private String realName;
|
||||||
@Schema(description = "角色", example = "ADMIN")
|
@Schema(description = "角色", example = "ADMIN")
|
||||||
private String role;
|
private String role;
|
||||||
@Schema(description = "所属公司 ID")
|
@Schema(description = "所属公司 ID", example = "1")
|
||||||
private Long companyId;
|
private Long companyId;
|
||||||
@Schema(description = "所属公司名称")
|
@Schema(description = "所属公司名称", example = "示例科技有限公司")
|
||||||
private String companyName;
|
private String companyName;
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main/java/com/label/dto/UserRoleUpdateRequest.java
Normal file
11
src/main/java/com/label/dto/UserRoleUpdateRequest.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "用户角色变更请求")
|
||||||
|
public class UserRoleUpdateRequest {
|
||||||
|
@Schema(description = "用户角色,可选值:ADMIN / REVIEWER / ANNOTATOR / UPLOADER", example = "ANNOTATOR")
|
||||||
|
private String role;
|
||||||
|
}
|
||||||
11
src/main/java/com/label/dto/UserStatusUpdateRequest.java
Normal file
11
src/main/java/com/label/dto/UserStatusUpdateRequest.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "用户状态变更请求")
|
||||||
|
public class UserStatusUpdateRequest {
|
||||||
|
@Schema(description = "用户状态,可选值:ACTIVE / DISABLED", example = "DISABLED")
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
14
src/main/java/com/label/dto/UserUpdateRequest.java
Normal file
14
src/main/java/com/label/dto/UserUpdateRequest.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "更新用户请求")
|
||||||
|
public class UserUpdateRequest {
|
||||||
|
@Schema(description = "真实姓名", example = "李四")
|
||||||
|
private String realName;
|
||||||
|
|
||||||
|
@Schema(description = "新密码,可为空或 null 表示保持不变", example = "")
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
20
src/main/java/com/label/dto/VideoProcessCallbackRequest.java
Normal file
20
src/main/java/com/label/dto/VideoProcessCallbackRequest.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "视频处理回调请求")
|
||||||
|
public class VideoProcessCallbackRequest {
|
||||||
|
@Schema(description = "视频处理任务 ID", example = "9001")
|
||||||
|
private Long jobId;
|
||||||
|
|
||||||
|
@Schema(description = "处理状态", example = "SUCCESS")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "输出文件路径", example = "/data/output/video-9001.json")
|
||||||
|
private String outputPath;
|
||||||
|
|
||||||
|
@Schema(description = "失败时的错误信息", example = "ffmpeg error")
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
17
src/main/java/com/label/dto/VideoProcessCreateRequest.java
Normal file
17
src/main/java/com/label/dto/VideoProcessCreateRequest.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.label.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "创建视频处理任务请求")
|
||||||
|
public class VideoProcessCreateRequest {
|
||||||
|
@Schema(description = "资料 ID", example = "3001")
|
||||||
|
private Long sourceId;
|
||||||
|
|
||||||
|
@Schema(description = "处理任务类型,可选值:FRAME_EXTRACT、VIDEO_TO_TEXT", example = "FRAME_EXTRACT")
|
||||||
|
private String jobType;
|
||||||
|
|
||||||
|
@Schema(description = "任务参数 JSON 字符串", example = "{\"frameInterval\":5}")
|
||||||
|
private String params;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.label.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -15,30 +16,40 @@ import java.util.UUID;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("export_batch")
|
@TableName("export_batch")
|
||||||
|
@Schema(description = "导出批次")
|
||||||
public class ExportBatch {
|
public class ExportBatch {
|
||||||
|
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
|
@Schema(description = "导出批次主键", example = "1")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** 所属公司(多租户键) */
|
/** 所属公司(多租户键) */
|
||||||
|
@Schema(description = "所属公司 ID", example = "1")
|
||||||
private Long companyId;
|
private Long companyId;
|
||||||
|
|
||||||
/** 批次唯一标识(UUID,DB 默认 gen_random_uuid()) */
|
/** 批次唯一标识(UUID,DB 默认 gen_random_uuid()) */
|
||||||
|
@Schema(description = "批次 UUID", example = "550e8400-e29b-41d4-a716-446655440000")
|
||||||
private UUID batchUuid;
|
private UUID batchUuid;
|
||||||
|
|
||||||
/** 本批次样本数量 */
|
/** 本批次样本数量 */
|
||||||
|
@Schema(description = "样本数量", example = "1000")
|
||||||
private Integer sampleCount;
|
private Integer sampleCount;
|
||||||
|
|
||||||
/** 导出 JSONL 的 RustFS 路径 */
|
/** 导出 JSONL 的 RustFS 路径 */
|
||||||
|
@Schema(description = "数据集文件路径(JSONL)", example = "datasets/export/2026-04-15/batch.jsonl")
|
||||||
private String datasetFilePath;
|
private String datasetFilePath;
|
||||||
|
|
||||||
/** GLM fine-tune 任务 ID(提交微调后填写) */
|
/** GLM fine-tune 任务 ID(提交微调后填写) */
|
||||||
|
@Schema(description = "GLM 微调任务 ID", example = "glm-job-123456")
|
||||||
private String glmJobId;
|
private String glmJobId;
|
||||||
|
|
||||||
/** 微调任务状态:NOT_STARTED / RUNNING / COMPLETED / FAILED */
|
/** 微调任务状态:NOT_STARTED / RUNNING / COMPLETED / FAILED */
|
||||||
|
@Schema(description = "微调任务状态", example = "NOT_STARTED")
|
||||||
private String finetuneStatus;
|
private String finetuneStatus;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.label.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -13,22 +14,29 @@ import java.time.LocalDateTime;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("sys_company")
|
@TableName("sys_company")
|
||||||
|
@Schema(description = "租户公司")
|
||||||
public class SysCompany {
|
public class SysCompany {
|
||||||
|
|
||||||
/** 公司主键,自增 */
|
/** 公司主键,自增 */
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
|
@Schema(description = "公司主键", example = "1")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** 公司全称,全局唯一 */
|
/** 公司全称,全局唯一 */
|
||||||
|
@Schema(description = "公司全称", example = "示例科技有限公司")
|
||||||
private String companyName;
|
private String companyName;
|
||||||
|
|
||||||
/** 公司代码(英文简写),全局唯一 */
|
/** 公司代码(英文简写),全局唯一 */
|
||||||
|
@Schema(description = "公司代码(英文简写)", example = "DEMO")
|
||||||
private String companyCode;
|
private String companyCode;
|
||||||
|
|
||||||
/** 状态:ACTIVE / DISABLED */
|
/** 状态:ACTIVE / DISABLED */
|
||||||
|
@Schema(description = "状态", example = "ACTIVE")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.label.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -15,27 +16,35 @@ import java.time.LocalDateTime;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("sys_config")
|
@TableName("sys_config")
|
||||||
|
@Schema(description = "系统配置")
|
||||||
public class SysConfig {
|
public class SysConfig {
|
||||||
|
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
|
@Schema(description = "配置主键", example = "1")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 所属公司 ID(NULL = 全局默认配置;非 NULL = 租户专属配置)。
|
* 所属公司 ID(NULL = 全局默认配置;非 NULL = 租户专属配置)。
|
||||||
* 注意:不能用 @TableField(exist = false) 排除,必须保留以支持 company_id IS NULL 查询。
|
* 注意:不能用 @TableField(exist = false) 排除,必须保留以支持 company_id IS NULL 查询。
|
||||||
*/
|
*/
|
||||||
|
@Schema(description = "所属公司 ID(NULL 表示全局默认配置)", example = "1")
|
||||||
private Long companyId;
|
private Long companyId;
|
||||||
|
|
||||||
/** 配置键 */
|
/** 配置键 */
|
||||||
|
@Schema(description = "配置键", example = "STORAGE_BUCKET")
|
||||||
private String configKey;
|
private String configKey;
|
||||||
|
|
||||||
/** 配置值 */
|
/** 配置值 */
|
||||||
|
@Schema(description = "配置值", example = "label-bucket")
|
||||||
private String configValue;
|
private String configValue;
|
||||||
|
|
||||||
/** 配置说明 */
|
/** 配置说明 */
|
||||||
|
@Schema(description = "配置说明", example = "对象存储桶名称")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -15,16 +16,20 @@ import java.time.LocalDateTime;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("sys_user")
|
@TableName("sys_user")
|
||||||
|
@Schema(description = "系统用户")
|
||||||
public class SysUser {
|
public class SysUser {
|
||||||
|
|
||||||
/** 用户主键,自增 */
|
/** 用户主键,自增 */
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
|
@Schema(description = "用户主键", example = "1")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** 所属公司 ID(多租户键) */
|
/** 所属公司 ID(多租户键) */
|
||||||
|
@Schema(description = "所属公司 ID", example = "1")
|
||||||
private Long companyId;
|
private Long companyId;
|
||||||
|
|
||||||
/** 登录用户名(同公司内唯一) */
|
/** 登录用户名(同公司内唯一) */
|
||||||
|
@Schema(description = "登录用户名", example = "admin")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,18 +37,24 @@ public class SysUser {
|
|||||||
* 序列化时排除,防止密码哈希泄漏到 API 响应。
|
* 序列化时排除,防止密码哈希泄漏到 API 响应。
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
@Schema(description = "密码哈希(不会在响应中返回)")
|
||||||
private String passwordHash;
|
private String passwordHash;
|
||||||
|
|
||||||
/** 真实姓名 */
|
/** 真实姓名 */
|
||||||
|
@Schema(description = "真实姓名", example = "张三")
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
/** 角色:UPLOADER / ANNOTATOR / REVIEWER / ADMIN */
|
/** 角色:UPLOADER / ANNOTATOR / REVIEWER / ADMIN */
|
||||||
|
@Schema(description = "角色", example = "ADMIN")
|
||||||
private String role;
|
private String role;
|
||||||
|
|
||||||
/** 状态:ACTIVE / DISABLED */
|
/** 状态:ACTIVE / DISABLED */
|
||||||
|
@Schema(description = "状态", example = "ACTIVE")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.label.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -15,32 +16,44 @@ import java.time.LocalDateTime;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("training_dataset")
|
@TableName("training_dataset")
|
||||||
|
@Schema(description = "训练数据集样本")
|
||||||
public class TrainingDataset {
|
public class TrainingDataset {
|
||||||
|
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
|
@Schema(description = "样本主键", example = "1")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** 所属公司(多租户键) */
|
/** 所属公司(多租户键) */
|
||||||
|
@Schema(description = "所属公司 ID", example = "1")
|
||||||
private Long companyId;
|
private Long companyId;
|
||||||
|
|
||||||
|
@Schema(description = "关联任务 ID", example = "1001")
|
||||||
private Long taskId;
|
private Long taskId;
|
||||||
|
|
||||||
|
@Schema(description = "关联资料 ID", example = "2001")
|
||||||
private Long sourceId;
|
private Long sourceId;
|
||||||
|
|
||||||
/** 样本类型:TEXT / IMAGE / VIDEO_FRAME */
|
/** 样本类型:TEXT / IMAGE / VIDEO_FRAME */
|
||||||
|
@Schema(description = "样本类型", example = "TEXT")
|
||||||
private String sampleType;
|
private String sampleType;
|
||||||
|
|
||||||
/** GLM fine-tune 格式的 JSON 字符串(JSONB) */
|
/** GLM fine-tune 格式的 JSON 字符串(JSONB) */
|
||||||
|
@Schema(description = "GLM 微调格式 JSON", example = "{\"messages\":[{\"role\":\"user\",\"content\":\"...\"},{\"role\":\"assistant\",\"content\":\"...\"}]}")
|
||||||
private String glmFormatJson;
|
private String glmFormatJson;
|
||||||
|
|
||||||
/** 状态:PENDING_REVIEW / APPROVED / REJECTED */
|
/** 状态:PENDING_REVIEW / APPROVED / REJECTED */
|
||||||
|
@Schema(description = "状态", example = "APPROVED")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "导出批次 ID", example = "3001")
|
||||||
private Long exportBatchId;
|
private Long exportBatchId;
|
||||||
|
|
||||||
|
@Schema(description = "导出时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime exportedAt;
|
private LocalDateTime exportedAt;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.label.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -15,43 +16,58 @@ import java.time.LocalDateTime;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("video_process_job")
|
@TableName("video_process_job")
|
||||||
|
@Schema(description = "视频处理任务")
|
||||||
public class VideoProcessJob {
|
public class VideoProcessJob {
|
||||||
|
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
|
@Schema(description = "任务主键", example = "1")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** 所属公司(多租户键) */
|
/** 所属公司(多租户键) */
|
||||||
|
@Schema(description = "所属公司 ID", example = "1")
|
||||||
private Long companyId;
|
private Long companyId;
|
||||||
|
|
||||||
/** 关联资料 ID */
|
/** 关联资料 ID */
|
||||||
|
@Schema(description = "关联资料 ID", example = "2001")
|
||||||
private Long sourceId;
|
private Long sourceId;
|
||||||
|
|
||||||
/** 任务类型:FRAME_EXTRACT / VIDEO_TO_TEXT */
|
/** 任务类型:FRAME_EXTRACT / VIDEO_TO_TEXT */
|
||||||
|
@Schema(description = "任务类型", example = "FRAME_EXTRACT")
|
||||||
private String jobType;
|
private String jobType;
|
||||||
|
|
||||||
/** 任务状态:PENDING / RUNNING / SUCCESS / FAILED / RETRYING */
|
/** 任务状态:PENDING / RUNNING / SUCCESS / FAILED / RETRYING */
|
||||||
|
@Schema(description = "任务状态", example = "PENDING")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
/** 任务参数(JSONB,例如 {"frameInterval": 30}) */
|
/** 任务参数(JSONB,例如 {"frameInterval": 30}) */
|
||||||
|
@Schema(description = "任务参数(JSON)", example = "{\"frameInterval\":30}")
|
||||||
private String params;
|
private String params;
|
||||||
|
|
||||||
/** AI 处理输出路径(成功后填写) */
|
/** AI 处理输出路径(成功后填写) */
|
||||||
|
@Schema(description = "输出路径", example = "outputs/video/2026-04-15/result.json")
|
||||||
private String outputPath;
|
private String outputPath;
|
||||||
|
|
||||||
/** 已重试次数 */
|
/** 已重试次数 */
|
||||||
|
@Schema(description = "已重试次数", example = "0")
|
||||||
private Integer retryCount;
|
private Integer retryCount;
|
||||||
|
|
||||||
/** 最大重试次数(默认 3) */
|
/** 最大重试次数(默认 3) */
|
||||||
|
@Schema(description = "最大重试次数", example = "3")
|
||||||
private Integer maxRetries;
|
private Integer maxRetries;
|
||||||
|
|
||||||
/** 错误信息 */
|
/** 错误信息 */
|
||||||
|
@Schema(description = "错误信息")
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
|
|
||||||
|
@Schema(description = "开始时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime startedAt;
|
private LocalDateTime startedAt;
|
||||||
|
|
||||||
|
@Schema(description = "完成时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime completedAt;
|
private LocalDateTime completedAt;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间", example = "2026-04-15T12:34:56")
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
server:
|
server:
|
||||||
port: 18082
|
port: 18082
|
||||||
|
servlet:
|
||||||
|
context-path: /label
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
@@ -60,7 +62,7 @@ rustfs:
|
|||||||
region: us-east-1
|
region: us-east-1
|
||||||
|
|
||||||
ai-service:
|
ai-service:
|
||||||
base-url: ${AI_SERVICE_BASE_URL:http://39.107.112.174:18000}
|
base-url: ${AI_SERVICE_BASE_URL:http://http://172.28.77.215:18000}
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<!-- <appender-ref ref="CONSOLE"/> -->
|
<appender-ref ref="CONSOLE"/>
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE"/>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user