809 lines
24 KiB
Markdown
809 lines
24 KiB
Markdown
# 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.
|