From 4708aa0f28a7eb0f8661a2925f99013001a4bf26 Mon Sep 17 00:00:00 2001 From: wh Date: Wed, 15 Apr 2026 18:25:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8D=E8=BF=BD=E8=B8=AA=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...5-label-backend-swagger-dto-annotations.md | 808 ------------------ ...abel-backend-swagger-annotations-design.md | 301 ------- 2 files changed, 1109 deletions(-) delete mode 100644 docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md delete mode 100644 docs/superpowers/specs/2026-04-15-label-backend-swagger-annotations-design.md diff --git a/docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md b/docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md deleted file mode 100644 index 0e107e3..0000000 --- a/docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md +++ /dev/null @@ -1,808 +0,0 @@ -# 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. diff --git a/docs/superpowers/specs/2026-04-15-label-backend-swagger-annotations-design.md b/docs/superpowers/specs/2026-04-15-label-backend-swagger-annotations-design.md deleted file mode 100644 index 4625f37..0000000 --- a/docs/superpowers/specs/2026-04-15-label-backend-swagger-annotations-design.md +++ /dev/null @@ -1,301 +0,0 @@ -# label_backend Swagger 参数注解与 DTO 化设计 - -> 日期:2026-04-15 -> 范围:`label_backend` 对外 REST API 的 Swagger/OpenAPI 文档增强 - -## 1. 背景 - -当前 `label_backend` 已经在 Controller 层使用了 `@Tag` 和 `@Operation`,少量 DTO 也已有 `@Schema` 注解,因此 Swagger 页面可以展示基础接口列表和部分对象结构。 - -但现状仍存在几个明显问题: - -- 很多路径参数、查询参数、表单参数缺少名称、类型、取值和含义说明 -- 多个接口仍使用 `Map` 或 `Map` 作为请求体,Swagger 无法准确展示字段名、字段类型和字段说明 -- 一些固定结构响应仍以 `Map` 返回,Swagger 只能显示匿名对象 -- 部分接口直接暴露实体类,而实体字段尚未补齐 Swagger 字段描述 -- 通用返回包装 `Result`、`PageResult` 没有字段级别文档说明 - -用户目标很明确:在 Swagger 中看到所有接口参数的名称、类型和含义。 - -## 2. 目标 - -本次改造完成后,`label_backend` 的 Swagger 页面应满足以下目标: - -- 所有公开接口都能展示清晰的路径参数、查询参数、请求头参数、表单参数说明 -- 所有固定结构的请求体都使用 DTO 建模,并为每个字段提供名称、类型、必填性和含义说明 -- 所有固定结构的主要响应对象都能展示字段说明 -- 所有分页与统一返回包装的字段含义清晰可见 -- 不改变现有接口路径、HTTP 方法和字段名称,尽量保持对现有调用方兼容 - -## 3. 非目标 - -本次不做以下事情: - -- 不重构整体业务流程 -- 不调整接口 URL、HTTP 方法和权限策略 -- 不强制把所有实体类都替换成专门的 Response DTO -- 不把完全动态的业务 JSON 全量重建成复杂深层对象,只对固定边界做显式包装 -- 不顺手做无关的代码整理或目录重构 - -## 4. 设计原则 - -### 4.1 DTO-first - -凡是 Swagger 需要清楚展示字段名、字段类型和字段说明的固定结构请求体,都应优先使用 DTO,而不是 `Map`。 - -### 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` -- `PageResult` - -### 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` 但字段稳定,应收敛为 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` 应说明: - -- `code`:业务状态码 -- `data`:接口返回主体 -- `message`:失败或补充说明 - -`PageResult` 应说明: - -- `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`、`PageResult` 补齐字段说明 -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 可读性和接口契约清晰度