# 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> 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> 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 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 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` and `PageResult`** Update `Result`: ```java @Data @Schema(description = "统一接口响应包装") public class Result { @Schema(description = "业务状态码;成功为 SUCCESS,失败为具体错误码", example = "SUCCESS") private String code; @Schema(description = "接口返回主体;不同接口类型不同") private T data; @Schema(description = "响应消息;失败时说明错误原因", example = "参数不合法") private String message; } ``` Update `PageResult` 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 create(@RequestBody Map body) ``` with: ```java public Result 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>> ``` to: ```java Result> ``` 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>` to `Result`. 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` 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 body|Map 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.