diff --git a/README.md b/README.md index 5835873..c21b44f 100644 --- a/README.md +++ b/README.md @@ -396,4 +396,6 @@ docker build -t label-backend:latest . - 固定结构请求体禁止继续使用匿名 `Map` 或 `Map`,必须定义 DTO 并补齐 `@Schema` 字段说明 - 固定结构响应应优先使用明确 DTO,或至少为 Swagger 暴露对象补齐字段级 `@Schema` 注解 - 路径参数、查询参数、请求体、分页包装和通用返回体都必须维护可读的 OpenAPI 文档说明 +- 需要保持历史兼容的原始 JSON 字符串请求体可以继续使用 `String`,但必须在 Swagger `@RequestBody` 中说明完整 JSON body 的提交方式和兼容原因 +- 修改 Controller 参数、请求 DTO、响应 DTO 或对外实体后,必须运行 `mvn -Dtest=OpenApiAnnotationTest test`,确保 Swagger 参数名称、类型和含义没有回退 - 目录、配置、打包方式变化后,README、设计文档和部署说明必须同步更新 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 index 7e6972f..0e107e3 100644 --- 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 @@ -30,7 +30,6 @@ - `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/RawJsonUpdateRequest.java` - `src/main/java/com/label/dto/FinetuneJobResponse.java` ### Modify @@ -142,7 +141,6 @@ private static final List> DTOS = List.of( SysConfigUpdateRequest.class, SysConfigItemResponse.class, DynamicJsonResponse.class, - RawJsonUpdateRequest.class, FinetuneJobResponse.class ); @@ -302,14 +300,6 @@ public class RejectRequest { ```java @Data -@Schema(description = "原始 JSON 更新请求") -public class RawJsonUpdateRequest { - @Schema(description = "完整 JSON 字符串;用于整体覆盖提取或问答标注结果", example = "{\"items\":[]}") - private String body; -} -``` - -```java @Data @Schema(description = "动态业务 JSON 响应") public class DynamicJsonResponse { @@ -710,7 +700,7 @@ return Result.success(response); Use the same pattern for QA. -- [ ] **Step 3: Use `RawJsonUpdateRequest` for raw JSON update bodies only if existing clients can send `{"body":"..."}`** +- [ ] **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. @@ -805,13 +795,14 @@ 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: Commit implementation** +- [ ] **Step 4: Keep implementation uncommitted for review** + +Do not commit unless the user explicitly asks for it. Run: ```bash -git add src/main/java src/test/java README.md docs/superpowers -git commit -m "docs: document backend api parameters in swagger" +git status --short ``` -Expected: commit succeeds. +Expected: implementation remains available in the worktree for review. diff --git a/src/main/java/com/label/common/result/PageResult.java b/src/main/java/com/label/common/result/PageResult.java index aeeeaf7..9e24359 100644 --- a/src/main/java/com/label/common/result/PageResult.java +++ b/src/main/java/com/label/common/result/PageResult.java @@ -1,14 +1,23 @@ package com.label.common.result; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data +@Schema(description = "分页响应") public class PageResult { + @Schema(description = "当前页数据列表") private List items; + + @Schema(description = "总条数", example = "123") private long total; + + @Schema(description = "页码(从 1 开始)", example = "1") private int page; + + @Schema(description = "每页条数", example = "20") private int pageSize; public static PageResult of(List items, long total, int page, int pageSize) { diff --git a/src/main/java/com/label/common/result/Result.java b/src/main/java/com/label/common/result/Result.java index 0cae155..022a3c0 100644 --- a/src/main/java/com/label/common/result/Result.java +++ b/src/main/java/com/label/common/result/Result.java @@ -1,11 +1,18 @@ package com.label.common.result; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data +@Schema(description = "通用响应包装") public class Result { + @Schema(description = "业务状态码", example = "SUCCESS") private String code; + + @Schema(description = "响应数据") private T data; + + @Schema(description = "提示信息", example = "操作成功") private String message; public static Result success(T data) { diff --git a/src/main/java/com/label/controller/AuthController.java b/src/main/java/com/label/controller/AuthController.java index 42aa24f..b27ee95 100644 --- a/src/main/java/com/label/controller/AuthController.java +++ b/src/main/java/com/label/controller/AuthController.java @@ -34,7 +34,11 @@ public class AuthController { */ @Operation(summary = "用户登录,返回 Bearer Token") @PostMapping("/login") - public Result login(@RequestBody LoginRequest request) { + public Result login( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "用户登录请求体", + required = true) + @RequestBody LoginRequest request) { return Result.success(authService.login(request)); } diff --git a/src/main/java/com/label/controller/CompanyController.java b/src/main/java/com/label/controller/CompanyController.java index 7bdfc90..0afa7e2 100644 --- a/src/main/java/com/label/controller/CompanyController.java +++ b/src/main/java/com/label/controller/CompanyController.java @@ -3,9 +3,13 @@ package com.label.controller; import com.label.annotation.RequireRole; import com.label.common.result.PageResult; 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.service.CompanyService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; 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.RestController; -import java.util.Map; - @Tag(name = "公司管理", description = "租户公司增删改查") @RestController @RequestMapping("/api/companies") @@ -34,8 +36,11 @@ public class CompanyController { @GetMapping @RequireRole("ADMIN") public Result> list( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, + @Parameter(description = "公司状态过滤,可选值:ACTIVE、DISABLED", example = "ACTIVE") @RequestParam(required = false) String status) { return Result.success(companyService.list(page, pageSize, status)); } @@ -44,29 +49,47 @@ public class CompanyController { @PostMapping @RequireRole("ADMIN") @ResponseStatus(HttpStatus.CREATED) - public Result create(@RequestBody Map body) { - return Result.success(companyService.create(body.get("companyName"), body.get("companyCode"))); + public Result create( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "创建公司请求体", + required = true) + @RequestBody CompanyCreateRequest body) { + return Result.success(companyService.create(body.getCompanyName(), body.getCompanyCode())); } @Operation(summary = "更新公司信息") @PutMapping("/{id}") @RequireRole("ADMIN") - public Result update(@PathVariable Long id, @RequestBody Map body) { - return Result.success(companyService.update(id, body.get("companyName"), body.get("companyCode"))); + public Result update( + @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 = "更新公司状态") @PutMapping("/{id}/status") @RequireRole("ADMIN") - public Result updateStatus(@PathVariable Long id, @RequestBody Map body) { - companyService.updateStatus(id, body.get("status")); + public Result updateStatus( + @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); } @Operation(summary = "删除公司") @DeleteMapping("/{id}") @RequireRole("ADMIN") - public Result delete(@PathVariable Long id) { + public Result delete( + @Parameter(description = "公司 ID", example = "100") + @PathVariable Long id) { companyService.delete(id); return Result.success(null); } diff --git a/src/main/java/com/label/controller/ExportController.java b/src/main/java/com/label/controller/ExportController.java index 6e88d7e..6d471d1 100644 --- a/src/main/java/com/label/controller/ExportController.java +++ b/src/main/java/com/label/controller/ExportController.java @@ -4,11 +4,14 @@ import com.label.annotation.RequireRole; import com.label.common.auth.TokenPrincipal; import com.label.common.result.PageResult; 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.ExportBatch; import com.label.service.ExportService; import com.label.service.FinetuneService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -34,9 +37,13 @@ public class ExportController { @GetMapping("/api/training/samples") @RequireRole("ADMIN") public Result> listSamples( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, + @Parameter(description = "样本类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION") @RequestParam(required = false) String sampleType, + @Parameter(description = "是否已导出过滤", example = "false") @RequestParam(required = false) Boolean exported, HttpServletRequest request) { return Result.success(exportService.listSamples(page, pageSize, sampleType, exported, principal(request))); @@ -47,32 +54,35 @@ public class ExportController { @PostMapping("/api/export/batch") @RequireRole("ADMIN") @ResponseStatus(HttpStatus.CREATED) - public Result createBatch(@RequestBody Map body, + public Result createBatch( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "创建训练数据导出批次请求体", + required = true) + @RequestBody ExportBatchCreateRequest body, HttpServletRequest request) { - @SuppressWarnings("unchecked") - List rawIds = (List) body.get("sampleIds"); - List sampleIds = rawIds.stream() - .map(id -> Long.parseLong(id.toString())) - .toList(); - return Result.success(exportService.createBatch(sampleIds, principal(request))); + return Result.success(exportService.createBatch(body.getSampleIds(), principal(request))); } /** POST /api/export/{batchId}/finetune — 提交微调任务 */ @Operation(summary = "提交微调任务") @PostMapping("/api/export/{batchId}/finetune") @RequireRole("ADMIN") - public Result> triggerFinetune(@PathVariable Long batchId, - HttpServletRequest request) { - return Result.success(finetuneService.trigger(batchId, principal(request))); + public Result triggerFinetune( + @Parameter(description = "导出批次 ID", example = "501") + @PathVariable Long batchId, + HttpServletRequest request) { + return Result.success(toFinetuneJobResponse(finetuneService.trigger(batchId, principal(request)))); } /** GET /api/export/{batchId}/status — 查询微调状态 */ @Operation(summary = "查询微调状态") @GetMapping("/api/export/{batchId}/status") @RequireRole("ADMIN") - public Result> getFinetuneStatus(@PathVariable Long batchId, - HttpServletRequest request) { - return Result.success(finetuneService.getStatus(batchId, principal(request))); + public Result getFinetuneStatus( + @Parameter(description = "导出批次 ID", example = "501") + @PathVariable Long batchId, + HttpServletRequest request) { + return Result.success(toFinetuneJobResponse(finetuneService.getStatus(batchId, principal(request)))); } /** GET /api/export/list — 分页查询导出批次列表 */ @@ -80,12 +90,36 @@ public class ExportController { @GetMapping("/api/export/list") @RequireRole("ADMIN") public Result> listBatches( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, HttpServletRequest request) { return Result.success(exportService.listBatches(page, pageSize, principal(request))); } + private FinetuneJobResponse toFinetuneJobResponse(Map 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) { return (TokenPrincipal) request.getAttribute("__token_principal__"); } diff --git a/src/main/java/com/label/controller/ExtractionController.java b/src/main/java/com/label/controller/ExtractionController.java index 65abec5..85afdf6 100644 --- a/src/main/java/com/label/controller/ExtractionController.java +++ b/src/main/java/com/label/controller/ExtractionController.java @@ -3,8 +3,10 @@ package com.label.controller; import com.label.annotation.RequireRole; import com.label.common.auth.TokenPrincipal; import com.label.common.result.Result; +import com.label.dto.RejectRequest; import com.label.service.ExtractionService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -27,8 +29,10 @@ public class ExtractionController { @Operation(summary = "获取提取标注结果") @GetMapping("/{taskId}") @RequireRole("ANNOTATOR") - public Result> getResult(@PathVariable Long taskId, - HttpServletRequest request) { + public Result> getResult( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + HttpServletRequest request) { return Result.success(extractionService.getResult(taskId, principal(request))); } @@ -36,8 +40,13 @@ public class ExtractionController { @Operation(summary = "更新提取标注结果") @PutMapping("/{taskId}") @RequireRole("ANNOTATOR") - public Result updateResult(@PathVariable Long taskId, - @RequestBody String resultJson, + public Result updateResult( + @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) { extractionService.updateResult(taskId, resultJson, principal(request)); return Result.success(null); @@ -47,8 +56,10 @@ public class ExtractionController { @Operation(summary = "提交提取标注结果") @PostMapping("/{taskId}/submit") @RequireRole("ANNOTATOR") - public Result submit(@PathVariable Long taskId, - HttpServletRequest request) { + public Result submit( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + HttpServletRequest request) { extractionService.submit(taskId, principal(request)); return Result.success(null); } @@ -57,8 +68,10 @@ public class ExtractionController { @Operation(summary = "审批通过提取结果") @PostMapping("/{taskId}/approve") @RequireRole("REVIEWER") - public Result approve(@PathVariable Long taskId, - HttpServletRequest request) { + public Result approve( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + HttpServletRequest request) { extractionService.approve(taskId, principal(request)); return Result.success(null); } @@ -67,10 +80,15 @@ public class ExtractionController { @Operation(summary = "驳回提取结果") @PostMapping("/{taskId}/reject") @RequireRole("REVIEWER") - public Result reject(@PathVariable Long taskId, - @RequestBody Map body, + public Result reject( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "驳回提取结果请求体", + required = true) + @RequestBody RejectRequest body, HttpServletRequest request) { - String reason = body != null ? body.get("reason") : null; + String reason = body != null ? body.getReason() : null; extractionService.reject(taskId, reason, principal(request)); return Result.success(null); } diff --git a/src/main/java/com/label/controller/QaController.java b/src/main/java/com/label/controller/QaController.java index 5c30b9c..f17d16b 100644 --- a/src/main/java/com/label/controller/QaController.java +++ b/src/main/java/com/label/controller/QaController.java @@ -3,8 +3,10 @@ package com.label.controller; import com.label.annotation.RequireRole; import com.label.common.auth.TokenPrincipal; import com.label.common.result.Result; +import com.label.dto.RejectRequest; import com.label.service.QaService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -27,8 +29,10 @@ public class QaController { @Operation(summary = "获取候选问答对") @GetMapping("/{taskId}") @RequireRole("ANNOTATOR") - public Result> getResult(@PathVariable Long taskId, - HttpServletRequest request) { + public Result> getResult( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + HttpServletRequest request) { return Result.success(qaService.getResult(taskId, principal(request))); } @@ -36,8 +40,13 @@ public class QaController { @Operation(summary = "更新候选问答对") @PutMapping("/{taskId}") @RequireRole("ANNOTATOR") - public Result updateResult(@PathVariable Long taskId, - @RequestBody String body, + public Result updateResult( + @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) { qaService.updateResult(taskId, body, principal(request)); return Result.success(null); @@ -47,8 +56,10 @@ public class QaController { @Operation(summary = "提交问答对") @PostMapping("/{taskId}/submit") @RequireRole("ANNOTATOR") - public Result submit(@PathVariable Long taskId, - HttpServletRequest request) { + public Result submit( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + HttpServletRequest request) { qaService.submit(taskId, principal(request)); return Result.success(null); } @@ -57,8 +68,10 @@ public class QaController { @Operation(summary = "审批通过问答对") @PostMapping("/{taskId}/approve") @RequireRole("REVIEWER") - public Result approve(@PathVariable Long taskId, - HttpServletRequest request) { + public Result approve( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + HttpServletRequest request) { qaService.approve(taskId, principal(request)); return Result.success(null); } @@ -67,10 +80,15 @@ public class QaController { @Operation(summary = "驳回答案对") @PostMapping("/{taskId}/reject") @RequireRole("REVIEWER") - public Result reject(@PathVariable Long taskId, - @RequestBody Map body, + public Result reject( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long taskId, + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "驳回问答结果请求体", + required = true) + @RequestBody RejectRequest body, HttpServletRequest request) { - String reason = body != null ? body.get("reason") : null; + String reason = body != null ? body.getReason() : null; qaService.reject(taskId, reason, principal(request)); return Result.success(null); } diff --git a/src/main/java/com/label/controller/SourceController.java b/src/main/java/com/label/controller/SourceController.java index e134d7b..c285fae 100644 --- a/src/main/java/com/label/controller/SourceController.java +++ b/src/main/java/com/label/controller/SourceController.java @@ -7,6 +7,7 @@ import com.label.common.result.Result; import com.label.dto.SourceResponse; import com.label.service.SourceService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -38,7 +39,9 @@ public class SourceController { @RequireRole("UPLOADER") @ResponseStatus(HttpStatus.CREATED) public Result upload( + @Parameter(description = "上传文件,支持文本、图片、视频", required = true) @RequestParam("file") MultipartFile file, + @Parameter(description = "资料类型,可选值:text、image、video", example = "text", required = true) @RequestParam("dataType") String dataType, HttpServletRequest request) { TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__"); @@ -53,9 +56,13 @@ public class SourceController { @GetMapping("/list") @RequireRole("UPLOADER") public Result> list( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, + @Parameter(description = "资料类型过滤,可选值:text、image、video", example = "text") @RequestParam(required = false) String dataType, + @Parameter(description = "资料状态过滤", example = "PENDING") @RequestParam(required = false) String status, HttpServletRequest request) { TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__"); @@ -68,7 +75,9 @@ public class SourceController { @Operation(summary = "查询资料详情") @GetMapping("/{id}") @RequireRole("UPLOADER") - public Result findById(@PathVariable Long id) { + public Result findById( + @Parameter(description = "资料 ID", example = "1001") + @PathVariable Long id) { return Result.success(sourceService.findById(id)); } @@ -79,7 +88,10 @@ public class SourceController { @Operation(summary = "删除资料") @DeleteMapping("/{id}") @RequireRole("ADMIN") - public Result delete(@PathVariable Long id, HttpServletRequest request) { + public Result delete( + @Parameter(description = "资料 ID", example = "1001") + @PathVariable Long id, + HttpServletRequest request) { TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__"); sourceService.delete(id, principal.getCompanyId()); return Result.success(null); diff --git a/src/main/java/com/label/controller/SysConfigController.java b/src/main/java/com/label/controller/SysConfigController.java index b0d775e..d371a9e 100644 --- a/src/main/java/com/label/controller/SysConfigController.java +++ b/src/main/java/com/label/controller/SysConfigController.java @@ -3,9 +3,12 @@ package com.label.controller; import com.label.annotation.RequireRole; import com.label.common.auth.TokenPrincipal; import com.label.common.result.Result; +import com.label.dto.SysConfigItemResponse; +import com.label.dto.SysConfigUpdateRequest; import com.label.entity.SysConfig; import com.label.service.SysConfigService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -37,9 +40,11 @@ public class SysConfigController { @Operation(summary = "查询合并后的系统配置") @GetMapping("/api/config") @RequireRole("ADMIN") - public Result>> listConfig(HttpServletRequest request) { + public Result> listConfig(HttpServletRequest request) { TokenPrincipal principal = principal(request); - return Result.success(sysConfigService.list(principal.getCompanyId())); + return Result.success(sysConfigService.list(principal.getCompanyId()).stream() + .map(this::toConfigItemResponse) + .toList()); } /** @@ -50,14 +55,36 @@ public class SysConfigController { @Operation(summary = "更新或创建公司专属配置") @PutMapping("/api/config/{key}") @RequireRole("ADMIN") - public Result updateConfig(@PathVariable String key, - @RequestBody Map body, + public Result updateConfig( + @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) { - String value = body.get("value"); - String description = body.get("description"); TokenPrincipal principal = principal(request); return Result.success( - sysConfigService.update(key, value, description, principal.getCompanyId())); + sysConfigService.update(key, body.getValue(), body.getDescription(), principal.getCompanyId())); + } + + private SysConfigItemResponse toConfigItemResponse(Map 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) { diff --git a/src/main/java/com/label/controller/TaskController.java b/src/main/java/com/label/controller/TaskController.java index a63ed8a..074a106 100644 --- a/src/main/java/com/label/controller/TaskController.java +++ b/src/main/java/com/label/controller/TaskController.java @@ -4,17 +4,18 @@ import com.label.annotation.RequireRole; import com.label.common.auth.TokenPrincipal; import com.label.common.result.PageResult; import com.label.common.result.Result; +import com.label.dto.CreateTaskRequest; +import com.label.dto.TaskReassignRequest; import com.label.dto.TaskResponse; import com.label.service.TaskClaimService; import com.label.service.TaskService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import java.util.Map; - /** * 任务管理接口(10 个端点)。 */ @@ -32,7 +33,9 @@ public class TaskController { @GetMapping("/pool") @RequireRole("ANNOTATOR") public Result> getPool( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, HttpServletRequest request) { return Result.success(taskService.getPool(page, pageSize, principal(request))); @@ -43,8 +46,11 @@ public class TaskController { @GetMapping("/mine") @RequireRole("ANNOTATOR") public Result> getMine( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, + @Parameter(description = "任务状态过滤,可选值:UNCLAIMED、IN_PROGRESS、SUBMITTED、APPROVED、REJECTED", example = "IN_PROGRESS") @RequestParam(required = false) String status, HttpServletRequest request) { return Result.success(taskService.getMine(page, pageSize, status, principal(request))); @@ -55,8 +61,11 @@ public class TaskController { @GetMapping("/pending-review") @RequireRole("REVIEWER") public Result> getPendingReview( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, + @Parameter(description = "任务类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION") @RequestParam(required = false) String taskType) { return Result.success(taskService.getPendingReview(page, pageSize, taskType)); } @@ -66,9 +75,13 @@ public class TaskController { @GetMapping @RequireRole("ADMIN") public Result> getAll( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, + @Parameter(description = "任务状态过滤,可选值:UNCLAIMED、IN_PROGRESS、SUBMITTED、APPROVED、REJECTED", example = "SUBMITTED") @RequestParam(required = false) String status, + @Parameter(description = "任务类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "QA_GENERATION") @RequestParam(required = false) String taskType) { return Result.success(taskService.getAll(page, pageSize, status, taskType)); } @@ -77,20 +90,24 @@ public class TaskController { @Operation(summary = "管理员创建任务") @PostMapping @RequireRole("ADMIN") - public Result createTask(@RequestBody Map body, + public Result createTask( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "创建标注任务请求体", + required = true) + @RequestBody CreateTaskRequest body, HttpServletRequest request) { - Long sourceId = Long.parseLong(body.get("sourceId").toString()); - String taskType = body.get("taskType").toString(); TokenPrincipal principal = principal(request); return Result.success(taskService.toPublicResponse( - taskService.createTask(sourceId, taskType, principal.getCompanyId()))); + taskService.createTask(body.getSourceId(), body.getTaskType(), principal.getCompanyId()))); } /** GET /api/tasks/{id} — 查询任务详情 */ @Operation(summary = "查询任务详情") @GetMapping("/{id}") @RequireRole("ANNOTATOR") - public Result getById(@PathVariable Long id) { + public Result getById( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long id) { return Result.success(taskService.toPublicResponse(taskService.getById(id))); } @@ -98,7 +115,10 @@ public class TaskController { @Operation(summary = "领取任务") @PostMapping("/{id}/claim") @RequireRole("ANNOTATOR") - public Result claim(@PathVariable Long id, HttpServletRequest request) { + public Result claim( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long id, + HttpServletRequest request) { taskClaimService.claim(id, principal(request)); return Result.success(null); } @@ -107,7 +127,10 @@ public class TaskController { @Operation(summary = "放弃任务") @PostMapping("/{id}/unclaim") @RequireRole("ANNOTATOR") - public Result unclaim(@PathVariable Long id, HttpServletRequest request) { + public Result unclaim( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long id, + HttpServletRequest request) { taskClaimService.unclaim(id, principal(request)); return Result.success(null); } @@ -116,7 +139,10 @@ public class TaskController { @Operation(summary = "重领被驳回的任务") @PostMapping("/{id}/reclaim") @RequireRole("ANNOTATOR") - public Result reclaim(@PathVariable Long id, HttpServletRequest request) { + public Result reclaim( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long id, + HttpServletRequest request) { taskClaimService.reclaim(id, principal(request)); return Result.success(null); } @@ -125,11 +151,15 @@ public class TaskController { @Operation(summary = "管理员强制指派任务") @PutMapping("/{id}/reassign") @RequireRole("ADMIN") - public Result reassign(@PathVariable Long id, - @RequestBody Map body, + public Result reassign( + @Parameter(description = "任务 ID", example = "1001") + @PathVariable Long id, + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "管理员强制改派任务请求体", + required = true) + @RequestBody TaskReassignRequest body, HttpServletRequest request) { - Long targetUserId = Long.parseLong(body.get("userId").toString()); - taskService.reassign(id, targetUserId, principal(request)); + taskService.reassign(id, body.getUserId(), principal(request)); return Result.success(null); } diff --git a/src/main/java/com/label/controller/UserController.java b/src/main/java/com/label/controller/UserController.java index 8e450e9..c17f713 100644 --- a/src/main/java/com/label/controller/UserController.java +++ b/src/main/java/com/label/controller/UserController.java @@ -1,7 +1,5 @@ package com.label.controller; -import java.util.Map; - import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; 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.result.PageResult; 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.service.UserService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -39,7 +42,9 @@ public class UserController { @GetMapping @RequireRole("ADMIN") public Result> listUsers( + @Parameter(description = "页码,从 1 开始", example = "1") @RequestParam(defaultValue = "1") int page, + @Parameter(description = "每页条数", example = "20") @RequestParam(defaultValue = "20") int pageSize, HttpServletRequest request) { return Result.success(userService.listUsers(page, pageSize, principal(request))); @@ -49,13 +54,17 @@ public class UserController { @Operation(summary = "创建用户") @PostMapping @RequireRole("ADMIN") - public Result createUser(@RequestBody Map body, + public Result createUser( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "创建用户请求体", + required = true) + @RequestBody UserCreateRequest body, HttpServletRequest request) { return Result.success(userService.createUser( - body.get("username"), - body.get("password"), - body.get("realName"), - body.get("role"), + body.getUsername(), + body.getPassword(), + body.getRealName(), + body.getRole(), principal(request))); } @@ -63,13 +72,18 @@ public class UserController { @Operation(summary = "更新用户基本信息") @PutMapping("/{id}") @RequireRole("ADMIN") - public Result updateUser(@PathVariable Long id, - @RequestBody Map body, + public Result updateUser( + @Parameter(description = "用户 ID", example = "2001") + @PathVariable Long id, + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "更新用户基本信息请求体", + required = true) + @RequestBody UserUpdateRequest body, HttpServletRequest request) { return Result.success(userService.updateUser( id, - body.get("realName"), - body.get("password"), + body.getRealName(), + body.getPassword(), principal(request))); } @@ -77,10 +91,15 @@ public class UserController { @Operation(summary = "变更用户状态", description = "status:ACTIVE、DISABLED") @PutMapping("/{id}/status") @RequireRole("ADMIN") - public Result updateStatus(@PathVariable Long id, - @RequestBody Map body, + public Result updateStatus( + @Parameter(description = "用户 ID", example = "2001") + @PathVariable Long id, + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "更新用户状态请求体", + required = true) + @RequestBody UserStatusUpdateRequest body, HttpServletRequest request) { - userService.updateStatus(id, body.get("status"), principal(request)); + userService.updateStatus(id, body.getStatus(), principal(request)); return Result.success(null); } @@ -88,10 +107,15 @@ public class UserController { @Operation(summary = "变更用户角色", description = "role:ADMIN、UPLOADER、VIEWER") @PutMapping("/{id}/role") @RequireRole("ADMIN") - public Result updateRole(@PathVariable Long id, - @RequestBody Map body, + public Result updateRole( + @Parameter(description = "用户 ID", example = "2001") + @PathVariable Long id, + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "更新用户角色请求体", + required = true) + @RequestBody UserRoleUpdateRequest body, HttpServletRequest request) { - userService.updateRole(id, body.get("role"), principal(request)); + userService.updateRole(id, body.getRole(), principal(request)); return Result.success(null); } diff --git a/src/main/java/com/label/controller/VideoController.java b/src/main/java/com/label/controller/VideoController.java index 1749f51..ebc3ae7 100644 --- a/src/main/java/com/label/controller/VideoController.java +++ b/src/main/java/com/label/controller/VideoController.java @@ -3,9 +3,12 @@ package com.label.controller; import com.label.annotation.RequireRole; import com.label.common.auth.TokenPrincipal; import com.label.common.result.Result; +import com.label.dto.VideoProcessCallbackRequest; +import com.label.dto.VideoProcessCreateRequest; import com.label.entity.VideoProcessJob; import com.label.service.VideoProcessService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -13,8 +16,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; -import java.util.Map; - /** * 视频处理接口(4 个端点)。 * @@ -38,16 +39,18 @@ public class VideoController { @Operation(summary = "触发视频处理任务") @PostMapping("/api/video/process") @RequireRole("ADMIN") - public Result createJob(@RequestBody Map body, + public Result createJob( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "创建视频处理任务请求体", + required = true) + @RequestBody VideoProcessCreateRequest body, HttpServletRequest request) { - Object sourceIdVal = body.get("sourceId"); - Object jobTypeVal = body.get("jobType"); - if (sourceIdVal == null || jobTypeVal == null) { + Long sourceId = body.getSourceId(); + String jobType = body.getJobType(); + if (sourceId == null || jobType == null) { return Result.failure("INVALID_PARAMS", "sourceId 和 jobType 不能为空"); } - Long sourceId = Long.parseLong(sourceIdVal.toString()); - String jobType = jobTypeVal.toString(); - String params = body.containsKey("params") ? body.get("params").toString() : null; + String params = body.getParams(); TokenPrincipal principal = principal(request); return Result.success( @@ -58,8 +61,10 @@ public class VideoController { @Operation(summary = "查询视频处理任务状态") @GetMapping("/api/video/jobs/{jobId}") @RequireRole("ADMIN") - public Result getJob(@PathVariable Long jobId, - HttpServletRequest request) { + public Result getJob( + @Parameter(description = "视频处理任务 ID", example = "9001") + @PathVariable Long jobId, + HttpServletRequest request) { return Result.success(videoProcessService.getJob(jobId, principal(request).getCompanyId())); } @@ -67,8 +72,10 @@ public class VideoController { @Operation(summary = "重置失败的视频处理任务") @PostMapping("/api/video/jobs/{jobId}/reset") @RequireRole("ADMIN") - public Result resetJob(@PathVariable Long jobId, - HttpServletRequest request) { + public Result resetJob( + @Parameter(description = "视频处理任务 ID", example = "9001") + @PathVariable Long jobId, + HttpServletRequest request) { return Result.success(videoProcessService.reset(jobId, principal(request).getCompanyId())); } @@ -84,7 +91,11 @@ public class VideoController { */ @Operation(summary = "接收 AI 服务视频处理回调") @PostMapping("/api/video/callback") - public Result handleCallback(@RequestBody Map body, + public Result handleCallback( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "AI 服务视频处理回调请求体", + required = true) + @RequestBody VideoProcessCallbackRequest body, HttpServletRequest request) { // 共享密钥校验(配置了 VIDEO_CALLBACK_SECRET 时强制校验) if (callbackSecret != null && !callbackSecret.isBlank()) { @@ -94,10 +105,10 @@ public class VideoController { } } - Long jobId = Long.parseLong(body.get("jobId").toString()); - String status = (String) body.get("status"); - String outputPath = body.containsKey("outputPath") ? (String) body.get("outputPath") : null; - String errorMessage = body.containsKey("errorMessage") ? (String) body.get("errorMessage") : null; + Long jobId = body.getJobId(); + String status = body.getStatus(); + String outputPath = body.getOutputPath(); + String errorMessage = body.getErrorMessage(); log.info("视频处理回调:jobId={}, status={}", jobId, status); videoProcessService.handleCallback(jobId, status, outputPath, errorMessage); diff --git a/src/main/java/com/label/dto/CompanyCreateRequest.java b/src/main/java/com/label/dto/CompanyCreateRequest.java new file mode 100644 index 0000000..89eb2c6 --- /dev/null +++ b/src/main/java/com/label/dto/CompanyCreateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/CompanyStatusUpdateRequest.java b/src/main/java/com/label/dto/CompanyStatusUpdateRequest.java new file mode 100644 index 0000000..ea40165 --- /dev/null +++ b/src/main/java/com/label/dto/CompanyStatusUpdateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/CompanyUpdateRequest.java b/src/main/java/com/label/dto/CompanyUpdateRequest.java new file mode 100644 index 0000000..9f34b1d --- /dev/null +++ b/src/main/java/com/label/dto/CompanyUpdateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/CreateTaskRequest.java b/src/main/java/com/label/dto/CreateTaskRequest.java new file mode 100644 index 0000000..8fe7afa --- /dev/null +++ b/src/main/java/com/label/dto/CreateTaskRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/DynamicJsonResponse.java b/src/main/java/com/label/dto/DynamicJsonResponse.java new file mode 100644 index 0000000..260732b --- /dev/null +++ b/src/main/java/com/label/dto/DynamicJsonResponse.java @@ -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 content; +} diff --git a/src/main/java/com/label/dto/ExportBatchCreateRequest.java b/src/main/java/com/label/dto/ExportBatchCreateRequest.java new file mode 100644 index 0000000..196e6ae --- /dev/null +++ b/src/main/java/com/label/dto/ExportBatchCreateRequest.java @@ -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 sampleIds; +} diff --git a/src/main/java/com/label/dto/FinetuneJobResponse.java b/src/main/java/com/label/dto/FinetuneJobResponse.java new file mode 100644 index 0000000..82dbea9 --- /dev/null +++ b/src/main/java/com/label/dto/FinetuneJobResponse.java @@ -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; +} diff --git a/src/main/java/com/label/dto/LoginResponse.java b/src/main/java/com/label/dto/LoginResponse.java index 7d9d923..fb46f30 100644 --- a/src/main/java/com/label/dto/LoginResponse.java +++ b/src/main/java/com/label/dto/LoginResponse.java @@ -15,10 +15,10 @@ public class LoginResponse { @Schema(description = "Bearer Token", example = "550e8400-e29b-41d4-a716-446655440000") private String token; /** 用户主键 */ - @Schema(description = "用户主键") + @Schema(description = "用户主键", example = "1") private Long userId; /** 登录用户名 */ - @Schema(description = "登录用户名") + @Schema(description = "登录用户名", example = "admin") private String username; /** 角色:UPLOADER / ANNOTATOR / REVIEWER / ADMIN */ @Schema(description = "角色", example = "ADMIN") diff --git a/src/main/java/com/label/dto/RejectRequest.java b/src/main/java/com/label/dto/RejectRequest.java new file mode 100644 index 0000000..bfedc65 --- /dev/null +++ b/src/main/java/com/label/dto/RejectRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/SourceResponse.java b/src/main/java/com/label/dto/SourceResponse.java index 857d0a2..86a957b 100644 --- a/src/main/java/com/label/dto/SourceResponse.java +++ b/src/main/java/com/label/dto/SourceResponse.java @@ -14,25 +14,25 @@ import java.time.LocalDateTime; @Builder @Schema(description = "原始资料响应") public class SourceResponse { - @Schema(description = "资料主键") + @Schema(description = "资料主键", example = "2001") private Long id; - @Schema(description = "文件名") + @Schema(description = "文件名", example = "demo.txt") private String fileName; @Schema(description = "资料类型", example = "TEXT") private String dataType; - @Schema(description = "文件大小(字节)") + @Schema(description = "文件大小(字节)", example = "1024") private Long fileSize; @Schema(description = "资料状态", example = "PENDING") private String status; /** 上传用户 ID(列表端点返回) */ - @Schema(description = "上传用户 ID") + @Schema(description = "上传用户 ID", example = "1") private Long uploaderId; /** 15 分钟预签名下载链接(详情端点返回) */ - @Schema(description = "预签名下载链接") + @Schema(description = "预签名下载链接", example = "https://example.com/presigned-url") private String presignedUrl; /** 父资料 ID(视频帧 / 文本片段;详情端点返回) */ - @Schema(description = "父资料 ID") + @Schema(description = "父资料 ID", example = "1001") private Long parentSourceId; - @Schema(description = "创建时间") + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; } diff --git a/src/main/java/com/label/dto/SysConfigItemResponse.java b/src/main/java/com/label/dto/SysConfigItemResponse.java new file mode 100644 index 0000000..a779c63 --- /dev/null +++ b/src/main/java/com/label/dto/SysConfigItemResponse.java @@ -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; +} diff --git a/src/main/java/com/label/dto/SysConfigUpdateRequest.java b/src/main/java/com/label/dto/SysConfigUpdateRequest.java new file mode 100644 index 0000000..53d0dec --- /dev/null +++ b/src/main/java/com/label/dto/SysConfigUpdateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/TaskReassignRequest.java b/src/main/java/com/label/dto/TaskReassignRequest.java new file mode 100644 index 0000000..7d98970 --- /dev/null +++ b/src/main/java/com/label/dto/TaskReassignRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/TaskResponse.java b/src/main/java/com/label/dto/TaskResponse.java index 555088f..a928448 100644 --- a/src/main/java/com/label/dto/TaskResponse.java +++ b/src/main/java/com/label/dto/TaskResponse.java @@ -13,26 +13,26 @@ import java.time.LocalDateTime; @Builder @Schema(description = "标注任务响应") public class TaskResponse { - @Schema(description = "任务主键") + @Schema(description = "任务主键", example = "1001") private Long id; - @Schema(description = "关联资料 ID") + @Schema(description = "关联资料 ID", example = "2001") private Long sourceId; /** 任务类型(对应 taskType 字段):EXTRACTION / QA_GENERATION */ @Schema(description = "任务类型", example = "EXTRACTION") private String taskType; @Schema(description = "任务状态", example = "UNCLAIMED") private String status; - @Schema(description = "领取人用户 ID") + @Schema(description = "领取人用户 ID", example = "1") private Long claimedBy; - @Schema(description = "领取时间") + @Schema(description = "领取时间", example = "2026-04-15T12:34:56") private LocalDateTime claimedAt; - @Schema(description = "提交时间") + @Schema(description = "提交时间", example = "2026-04-15T12:34:56") private LocalDateTime submittedAt; - @Schema(description = "完成时间") + @Schema(description = "完成时间", example = "2026-04-15T12:34:56") private LocalDateTime completedAt; /** 驳回原因(REJECTED 状态时非空) */ @Schema(description = "驳回原因") private String rejectReason; - @Schema(description = "创建时间") + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; } diff --git a/src/main/java/com/label/dto/UserCreateRequest.java b/src/main/java/com/label/dto/UserCreateRequest.java new file mode 100644 index 0000000..dc7c3c4 --- /dev/null +++ b/src/main/java/com/label/dto/UserCreateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/UserInfoResponse.java b/src/main/java/com/label/dto/UserInfoResponse.java index 45bddd8..c077d65 100644 --- a/src/main/java/com/label/dto/UserInfoResponse.java +++ b/src/main/java/com/label/dto/UserInfoResponse.java @@ -11,16 +11,16 @@ import lombok.Data; @AllArgsConstructor @Schema(description = "当前登录用户信息") public class UserInfoResponse { - @Schema(description = "用户主键") + @Schema(description = "用户主键", example = "1") private Long id; - @Schema(description = "用户名") + @Schema(description = "用户名", example = "admin") private String username; - @Schema(description = "真实姓名") + @Schema(description = "真实姓名", example = "张三") private String realName; @Schema(description = "角色", example = "ADMIN") private String role; - @Schema(description = "所属公司 ID") + @Schema(description = "所属公司 ID", example = "1") private Long companyId; - @Schema(description = "所属公司名称") + @Schema(description = "所属公司名称", example = "示例科技有限公司") private String companyName; } diff --git a/src/main/java/com/label/dto/UserRoleUpdateRequest.java b/src/main/java/com/label/dto/UserRoleUpdateRequest.java new file mode 100644 index 0000000..034d0ff --- /dev/null +++ b/src/main/java/com/label/dto/UserRoleUpdateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/UserStatusUpdateRequest.java b/src/main/java/com/label/dto/UserStatusUpdateRequest.java new file mode 100644 index 0000000..9e1e855 --- /dev/null +++ b/src/main/java/com/label/dto/UserStatusUpdateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/UserUpdateRequest.java b/src/main/java/com/label/dto/UserUpdateRequest.java new file mode 100644 index 0000000..6aa84c1 --- /dev/null +++ b/src/main/java/com/label/dto/UserUpdateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/VideoProcessCallbackRequest.java b/src/main/java/com/label/dto/VideoProcessCallbackRequest.java new file mode 100644 index 0000000..bd860bf --- /dev/null +++ b/src/main/java/com/label/dto/VideoProcessCallbackRequest.java @@ -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; +} diff --git a/src/main/java/com/label/dto/VideoProcessCreateRequest.java b/src/main/java/com/label/dto/VideoProcessCreateRequest.java new file mode 100644 index 0000000..e3948b4 --- /dev/null +++ b/src/main/java/com/label/dto/VideoProcessCreateRequest.java @@ -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; +} diff --git a/src/main/java/com/label/entity/ExportBatch.java b/src/main/java/com/label/entity/ExportBatch.java index 299b083..9d774d7 100644 --- a/src/main/java/com/label/entity/ExportBatch.java +++ b/src/main/java/com/label/entity/ExportBatch.java @@ -3,6 +3,7 @@ package com.label.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; @@ -15,30 +16,40 @@ import java.util.UUID; */ @Data @TableName("export_batch") +@Schema(description = "导出批次") public class ExportBatch { @TableId(type = IdType.AUTO) + @Schema(description = "导出批次主键", example = "1") private Long id; /** 所属公司(多租户键) */ + @Schema(description = "所属公司 ID", example = "1") private Long companyId; /** 批次唯一标识(UUID,DB 默认 gen_random_uuid()) */ + @Schema(description = "批次 UUID", example = "550e8400-e29b-41d4-a716-446655440000") private UUID batchUuid; /** 本批次样本数量 */ + @Schema(description = "样本数量", example = "1000") private Integer sampleCount; /** 导出 JSONL 的 RustFS 路径 */ + @Schema(description = "数据集文件路径(JSONL)", example = "datasets/export/2026-04-15/batch.jsonl") private String datasetFilePath; /** GLM fine-tune 任务 ID(提交微调后填写) */ + @Schema(description = "GLM 微调任务 ID", example = "glm-job-123456") private String glmJobId; /** 微调任务状态:NOT_STARTED / RUNNING / COMPLETED / FAILED */ + @Schema(description = "微调任务状态", example = "NOT_STARTED") private String finetuneStatus; + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; + @Schema(description = "更新时间", example = "2026-04-15T12:34:56") private LocalDateTime updatedAt; } diff --git a/src/main/java/com/label/entity/SysCompany.java b/src/main/java/com/label/entity/SysCompany.java index 70fcaa4..43d30e3 100644 --- a/src/main/java/com/label/entity/SysCompany.java +++ b/src/main/java/com/label/entity/SysCompany.java @@ -3,6 +3,7 @@ package com.label.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; @@ -13,22 +14,29 @@ import java.time.LocalDateTime; */ @Data @TableName("sys_company") +@Schema(description = "租户公司") public class SysCompany { /** 公司主键,自增 */ @TableId(type = IdType.AUTO) + @Schema(description = "公司主键", example = "1") private Long id; /** 公司全称,全局唯一 */ + @Schema(description = "公司全称", example = "示例科技有限公司") private String companyName; /** 公司代码(英文简写),全局唯一 */ + @Schema(description = "公司代码(英文简写)", example = "DEMO") private String companyCode; /** 状态:ACTIVE / DISABLED */ + @Schema(description = "状态", example = "ACTIVE") private String status; + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; + @Schema(description = "更新时间", example = "2026-04-15T12:34:56") private LocalDateTime updatedAt; } diff --git a/src/main/java/com/label/entity/SysConfig.java b/src/main/java/com/label/entity/SysConfig.java index 94978be..ec8b7f8 100644 --- a/src/main/java/com/label/entity/SysConfig.java +++ b/src/main/java/com/label/entity/SysConfig.java @@ -3,6 +3,7 @@ package com.label.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; @@ -15,27 +16,35 @@ import java.time.LocalDateTime; */ @Data @TableName("sys_config") +@Schema(description = "系统配置") public class SysConfig { @TableId(type = IdType.AUTO) + @Schema(description = "配置主键", example = "1") private Long id; /** * 所属公司 ID(NULL = 全局默认配置;非 NULL = 租户专属配置)。 * 注意:不能用 @TableField(exist = false) 排除,必须保留以支持 company_id IS NULL 查询。 */ + @Schema(description = "所属公司 ID(NULL 表示全局默认配置)", example = "1") private Long companyId; /** 配置键 */ + @Schema(description = "配置键", example = "STORAGE_BUCKET") private String configKey; /** 配置值 */ + @Schema(description = "配置值", example = "label-bucket") private String configValue; /** 配置说明 */ + @Schema(description = "配置说明", example = "对象存储桶名称") private String description; + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; + @Schema(description = "更新时间", example = "2026-04-15T12:34:56") private LocalDateTime updatedAt; } diff --git a/src/main/java/com/label/entity/SysUser.java b/src/main/java/com/label/entity/SysUser.java index 1f67e34..8a759b0 100644 --- a/src/main/java/com/label/entity/SysUser.java +++ b/src/main/java/com/label/entity/SysUser.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; @@ -15,16 +16,20 @@ import java.time.LocalDateTime; */ @Data @TableName("sys_user") +@Schema(description = "系统用户") public class SysUser { /** 用户主键,自增 */ @TableId(type = IdType.AUTO) + @Schema(description = "用户主键", example = "1") private Long id; /** 所属公司 ID(多租户键) */ + @Schema(description = "所属公司 ID", example = "1") private Long companyId; /** 登录用户名(同公司内唯一) */ + @Schema(description = "登录用户名", example = "admin") private String username; /** @@ -32,18 +37,24 @@ public class SysUser { * 序列化时排除,防止密码哈希泄漏到 API 响应。 */ @JsonIgnore + @Schema(description = "密码哈希(不会在响应中返回)") private String passwordHash; /** 真实姓名 */ + @Schema(description = "真实姓名", example = "张三") private String realName; /** 角色:UPLOADER / ANNOTATOR / REVIEWER / ADMIN */ + @Schema(description = "角色", example = "ADMIN") private String role; /** 状态:ACTIVE / DISABLED */ + @Schema(description = "状态", example = "ACTIVE") private String status; + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; + @Schema(description = "更新时间", example = "2026-04-15T12:34:56") private LocalDateTime updatedAt; } diff --git a/src/main/java/com/label/entity/TrainingDataset.java b/src/main/java/com/label/entity/TrainingDataset.java index 0811713..30a82bf 100644 --- a/src/main/java/com/label/entity/TrainingDataset.java +++ b/src/main/java/com/label/entity/TrainingDataset.java @@ -3,6 +3,7 @@ package com.label.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; @@ -15,32 +16,44 @@ import java.time.LocalDateTime; */ @Data @TableName("training_dataset") +@Schema(description = "训练数据集样本") public class TrainingDataset { @TableId(type = IdType.AUTO) + @Schema(description = "样本主键", example = "1") private Long id; /** 所属公司(多租户键) */ + @Schema(description = "所属公司 ID", example = "1") private Long companyId; + @Schema(description = "关联任务 ID", example = "1001") private Long taskId; + @Schema(description = "关联资料 ID", example = "2001") private Long sourceId; /** 样本类型:TEXT / IMAGE / VIDEO_FRAME */ + @Schema(description = "样本类型", example = "TEXT") private String sampleType; /** GLM fine-tune 格式的 JSON 字符串(JSONB) */ + @Schema(description = "GLM 微调格式 JSON", example = "{\"messages\":[{\"role\":\"user\",\"content\":\"...\"},{\"role\":\"assistant\",\"content\":\"...\"}]}") private String glmFormatJson; /** 状态:PENDING_REVIEW / APPROVED / REJECTED */ + @Schema(description = "状态", example = "APPROVED") private String status; + @Schema(description = "导出批次 ID", example = "3001") private Long exportBatchId; + @Schema(description = "导出时间", example = "2026-04-15T12:34:56") private LocalDateTime exportedAt; + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; + @Schema(description = "更新时间", example = "2026-04-15T12:34:56") private LocalDateTime updatedAt; } diff --git a/src/main/java/com/label/entity/VideoProcessJob.java b/src/main/java/com/label/entity/VideoProcessJob.java index d039929..7259e05 100644 --- a/src/main/java/com/label/entity/VideoProcessJob.java +++ b/src/main/java/com/label/entity/VideoProcessJob.java @@ -3,6 +3,7 @@ package com.label.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; @@ -15,43 +16,58 @@ import java.time.LocalDateTime; */ @Data @TableName("video_process_job") +@Schema(description = "视频处理任务") public class VideoProcessJob { @TableId(type = IdType.AUTO) + @Schema(description = "任务主键", example = "1") private Long id; /** 所属公司(多租户键) */ + @Schema(description = "所属公司 ID", example = "1") private Long companyId; /** 关联资料 ID */ + @Schema(description = "关联资料 ID", example = "2001") private Long sourceId; /** 任务类型:FRAME_EXTRACT / VIDEO_TO_TEXT */ + @Schema(description = "任务类型", example = "FRAME_EXTRACT") private String jobType; /** 任务状态:PENDING / RUNNING / SUCCESS / FAILED / RETRYING */ + @Schema(description = "任务状态", example = "PENDING") private String status; /** 任务参数(JSONB,例如 {"frameInterval": 30}) */ + @Schema(description = "任务参数(JSON)", example = "{\"frameInterval\":30}") private String params; /** AI 处理输出路径(成功后填写) */ + @Schema(description = "输出路径", example = "outputs/video/2026-04-15/result.json") private String outputPath; /** 已重试次数 */ + @Schema(description = "已重试次数", example = "0") private Integer retryCount; /** 最大重试次数(默认 3) */ + @Schema(description = "最大重试次数", example = "3") private Integer maxRetries; /** 错误信息 */ + @Schema(description = "错误信息") private String errorMessage; + @Schema(description = "开始时间", example = "2026-04-15T12:34:56") private LocalDateTime startedAt; + @Schema(description = "完成时间", example = "2026-04-15T12:34:56") private LocalDateTime completedAt; + @Schema(description = "创建时间", example = "2026-04-15T12:34:56") private LocalDateTime createdAt; + @Schema(description = "更新时间", example = "2026-04-15T12:34:56") private LocalDateTime updatedAt; }