修改mybatis版本启动报错,swagger注解问题

This commit is contained in:
wh
2026-04-12 00:15:59 +08:00
parent c3308e069d
commit a489e2b204
25 changed files with 510 additions and 64 deletions

View File

@@ -1,3 +1,4 @@
package com.label;
import org.springframework.boot.SpringApplication;
@@ -11,11 +12,13 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* Spring Boot 3. 的 jakarta.servlet 命名空间冲突。 认证/ 授权逻辑改由
* TokenFilterOncePerRequestFilter+ ShiroConfig 手动装配。
*/
@SpringBootApplication(excludeName = {
"org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration",
"org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration",
"org.apache.shiro.spring.config.web.autoconfigure.ShiroWebMvcAutoConfiguration" })
// (excludeName = {
// "org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration",
// "org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration",
// "org.apache.shiro.spring.config.web.autoconfigure.ShiroWebMvcAutoConfiguration" })
@SpringBootApplication
public class LabelBackendApplication {
public static void main(String[] args) {

View File

@@ -23,9 +23,7 @@ public class AiServiceClient {
@PostConstruct
public void init() {
restClient = RestClient.builder()
.baseUrl(baseUrl)
.build();
restClient = RestClient.builder().baseUrl(baseUrl).build();
}
// DTO classes
@@ -42,7 +40,7 @@ public class AiServiceClient {
@Data
public static class ExtractionResponse {
private List<Map<String, Object>> items; // triple/quadruple items
private List<Map<String, Object>> items; // triple/quadruple items
private String rawOutput;
}
@@ -52,7 +50,7 @@ public class AiServiceClient {
private Long sourceId;
private String filePath;
private String bucket;
private Map<String, Object> params; // frameInterval, mode etc.
private Map<String, Object> params; // frameInterval, mode etc.
}
@Data
@@ -63,7 +61,7 @@ public class AiServiceClient {
@Data
@Builder
public static class FinetuneRequest {
private String datasetPath; // RustFS path to JSONL file
private String datasetPath; // RustFS path to JSONL file
private String model;
private Long batchId;
}
@@ -77,73 +75,42 @@ public class AiServiceClient {
@Data
public static class FinetuneStatusResponse {
private String jobId;
private String status; // PENDING/RUNNING/COMPLETED/FAILED
private Integer progress; // 0-100
private String status; // PENDING/RUNNING/COMPLETED/FAILED
private Integer progress; // 0-100
private String errorMessage;
}
// The 8 endpoints:
public ExtractionResponse extractText(ExtractionRequest request) {
return restClient.post()
.uri("/extract/text")
.body(request)
.retrieve()
.body(ExtractionResponse.class);
return restClient.post().uri("/extract/text").body(request).retrieve().body(ExtractionResponse.class);
}
public ExtractionResponse extractImage(ExtractionRequest request) {
return restClient.post()
.uri("/extract/image")
.body(request)
.retrieve()
.body(ExtractionResponse.class);
return restClient.post().uri("/extract/image").body(request).retrieve().body(ExtractionResponse.class);
}
public void extractFrames(VideoProcessRequest request) {
restClient.post()
.uri("/video/extract-frames")
.body(request)
.retrieve()
.toBodilessEntity();
restClient.post().uri("/video/extract-frames").body(request).retrieve().toBodilessEntity();
}
public void videoToText(VideoProcessRequest request) {
restClient.post()
.uri("/video/to-text")
.body(request)
.retrieve()
.toBodilessEntity();
restClient.post().uri("/video/to-text").body(request).retrieve().toBodilessEntity();
}
public QaGenResponse genTextQa(ExtractionRequest request) {
return restClient.post()
.uri("/qa/gen-text")
.body(request)
.retrieve()
.body(QaGenResponse.class);
return restClient.post().uri("/qa/gen-text").body(request).retrieve().body(QaGenResponse.class);
}
public QaGenResponse genImageQa(ExtractionRequest request) {
return restClient.post()
.uri("/qa/gen-image")
.body(request)
.retrieve()
.body(QaGenResponse.class);
return restClient.post().uri("/qa/gen-image").body(request).retrieve().body(QaGenResponse.class);
}
public FinetuneResponse startFinetune(FinetuneRequest request) {
return restClient.post()
.uri("/finetune/start")
.body(request)
.retrieve()
.body(FinetuneResponse.class);
return restClient.post().uri("/finetune/start").body(request).retrieve().body(FinetuneResponse.class);
}
public FinetuneStatusResponse getFinetuneStatus(String jobId) {
return restClient.get()
.uri("/finetune/status/{jobId}", jobId)
.retrieve()
.body(FinetuneStatusResponse.class);
return restClient.get().uri("/finetune/status/{jobId}", jobId).retrieve().body(FinetuneStatusResponse.class);
}
}

View File

@@ -12,10 +12,10 @@ public class CompanyContext {
}
public static void clear() {
COMPANY_ID.remove(); // Use remove() not set(null) to prevent memory leaks
COMPANY_ID.remove(); // Use remove() not set(null) to prevent memory leaks
}
private CompanyContext() { // Prevent instantiation
private CompanyContext() { // Prevent instantiation
throw new UnsupportedOperationException("Utility class");
}
}

View File

@@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.ThreadContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -38,6 +39,24 @@ public class TokenFilter extends OncePerRequestFilter {
private final RedisService redisService;
private final ObjectMapper objectMapper;
@Value("${shiro.auth.enabled:true}")
private boolean authEnabled;
@Value("${shiro.auth.mock-company-id:1}")
private Long mockCompanyId;
@Value("${shiro.auth.mock-user-id:1}")
private Long mockUserId;
@Value("${shiro.auth.mock-role:ADMIN}")
private String mockRole;
@Value("${shiro.auth.mock-username:mock}")
private String mockUsername;
@Value("${token.ttl-seconds:7200}")
private long tokenTtlSeconds;
/**
* 公开端点跳过过滤:非 /api/ 前缀路径,以及登录接口本身。
*/
@@ -46,13 +65,25 @@ public class TokenFilter extends OncePerRequestFilter {
String path = request.getServletPath();
return !path.startsWith("/api/")
|| path.equals("/api/auth/login")
|| path.equals("/api/video/callback"); // AI 服务内部回调,不走用户 Token 认证
|| path.equals("/api/video/callback")
|| path.startsWith("/swagger-ui")
|| path.startsWith("/v3/api-docs"); // AI 服务内部回调,不走用户 Token 认证
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
if (!authEnabled) {
TokenPrincipal principal = new TokenPrincipal(
mockUserId, mockRole, mockCompanyId, mockUsername, "mock-token");
CompanyContext.set(mockCompanyId);
SecurityUtils.getSubject().login(new BearerToken("mock-token", principal));
request.setAttribute("__token_principal__", principal);
filterChain.doFilter(request, response);
return;
}
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
writeUnauthorized(response, "缺少或无效的认证令牌");
@@ -79,6 +110,8 @@ public class TokenFilter extends OncePerRequestFilter {
TokenPrincipal principal = new TokenPrincipal(userId, role, companyId, username, token);
SecurityUtils.getSubject().login(new BearerToken(token, principal));
request.setAttribute("__token_principal__", principal);
redisService.expire(RedisKeyManager.tokenKey(token), tokenTtlSeconds);
redisService.expire(RedisKeyManager.userSessionsKey(userId), tokenTtlSeconds);
filterChain.doFilter(request, response);
} catch (Exception e) {

View File

@@ -3,6 +3,8 @@ package com.label.module.annotation.controller;
import com.label.common.result.Result;
import com.label.common.shiro.TokenPrincipal;
import com.label.module.annotation.service.ExtractionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresRoles;
@@ -13,6 +15,7 @@ import java.util.Map;
/**
* 提取阶段标注工作台接口5 个端点)。
*/
@Tag(name = "提取标注", description = "提取阶段的查看、编辑、提交和审批")
@RestController
@RequestMapping("/api/extraction")
@RequiredArgsConstructor
@@ -21,6 +24,7 @@ public class ExtractionController {
private final ExtractionService extractionService;
/** GET /api/extraction/{taskId} — 获取当前标注结果 */
@Operation(summary = "获取提取标注结果")
@GetMapping("/{taskId}")
@RequiresRoles("ANNOTATOR")
public Result<Map<String, Object>> getResult(@PathVariable Long taskId,
@@ -29,6 +33,7 @@ public class ExtractionController {
}
/** PUT /api/extraction/{taskId} — 更新标注结果(整体覆盖) */
@Operation(summary = "更新提取标注结果")
@PutMapping("/{taskId}")
@RequiresRoles("ANNOTATOR")
public Result<Void> updateResult(@PathVariable Long taskId,
@@ -39,6 +44,7 @@ public class ExtractionController {
}
/** POST /api/extraction/{taskId}/submit — 提交标注结果 */
@Operation(summary = "提交提取标注结果")
@PostMapping("/{taskId}/submit")
@RequiresRoles("ANNOTATOR")
public Result<Void> submit(@PathVariable Long taskId,
@@ -48,6 +54,7 @@ public class ExtractionController {
}
/** POST /api/extraction/{taskId}/approve — 审批通过REVIEWER */
@Operation(summary = "审批通过提取结果")
@PostMapping("/{taskId}/approve")
@RequiresRoles("REVIEWER")
public Result<Void> approve(@PathVariable Long taskId,
@@ -57,6 +64,7 @@ public class ExtractionController {
}
/** POST /api/extraction/{taskId}/reject — 驳回REVIEWER */
@Operation(summary = "驳回提取结果")
@PostMapping("/{taskId}/reject")
@RequiresRoles("REVIEWER")
public Result<Void> reject(@PathVariable Long taskId,

View File

@@ -3,6 +3,8 @@ package com.label.module.annotation.controller;
import com.label.common.result.Result;
import com.label.common.shiro.TokenPrincipal;
import com.label.module.annotation.service.QaService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresRoles;
@@ -13,6 +15,7 @@ import java.util.Map;
/**
* 问答生成阶段标注工作台接口5 个端点)。
*/
@Tag(name = "问答生成", description = "问答生成阶段的查看、编辑、提交和审批")
@RestController
@RequestMapping("/api/qa")
@RequiredArgsConstructor
@@ -21,6 +24,7 @@ public class QaController {
private final QaService qaService;
/** GET /api/qa/{taskId} — 获取候选问答对 */
@Operation(summary = "获取候选问答对")
@GetMapping("/{taskId}")
@RequiresRoles("ANNOTATOR")
public Result<Map<String, Object>> getResult(@PathVariable Long taskId,
@@ -29,6 +33,7 @@ public class QaController {
}
/** PUT /api/qa/{taskId} — 整体覆盖问答对 */
@Operation(summary = "更新候选问答对")
@PutMapping("/{taskId}")
@RequiresRoles("ANNOTATOR")
public Result<Void> updateResult(@PathVariable Long taskId,
@@ -39,6 +44,7 @@ public class QaController {
}
/** POST /api/qa/{taskId}/submit — 提交问答对 */
@Operation(summary = "提交问答对")
@PostMapping("/{taskId}/submit")
@RequiresRoles("ANNOTATOR")
public Result<Void> submit(@PathVariable Long taskId,
@@ -48,6 +54,7 @@ public class QaController {
}
/** POST /api/qa/{taskId}/approve — 审批通过REVIEWER */
@Operation(summary = "审批通过问答对")
@PostMapping("/{taskId}/approve")
@RequiresRoles("REVIEWER")
public Result<Void> approve(@PathVariable Long taskId,
@@ -57,6 +64,7 @@ public class QaController {
}
/** POST /api/qa/{taskId}/reject — 驳回REVIEWER */
@Operation(summary = "驳回答案对")
@PostMapping("/{taskId}/reject")
@RequiresRoles("REVIEWER")
public Result<Void> reject(@PathVariable Long taskId,

View File

@@ -4,6 +4,8 @@ import com.label.common.result.Result;
import com.label.common.shiro.TokenPrincipal;
import com.label.module.config.entity.SysConfig;
import com.label.module.config.service.SysConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresRoles;
@@ -18,6 +20,7 @@ import java.util.Map;
* GET /api/config — 查询当前公司所有可见配置(公司专属 + 全局默认合并)
* PUT /api/config/{key} — 更新/创建公司专属配置UPSERT
*/
@Tag(name = "系统配置", description = "全局和公司级系统配置管理")
@RestController
@RequiredArgsConstructor
public class SysConfigController {
@@ -31,6 +34,7 @@ public class SysConfigController {
* - "COMPANY":当前公司专属配置(优先生效)
* - "GLOBAL":全局默认配置(公司未覆盖时生效)
*/
@Operation(summary = "查询合并后的系统配置")
@GetMapping("/api/config")
@RequiresRoles("ADMIN")
public Result<List<Map<String, Object>>> listConfig(HttpServletRequest request) {
@@ -43,6 +47,7 @@ public class SysConfigController {
*
* Body: { "value": "...", "description": "..." }
*/
@Operation(summary = "更新或创建公司专属配置")
@PutMapping("/api/config/{key}")
@RequiresRoles("ADMIN")
public Result<SysConfig> updateConfig(@PathVariable String key,

View File

@@ -7,6 +7,8 @@ import com.label.module.annotation.entity.TrainingDataset;
import com.label.module.export.entity.ExportBatch;
import com.label.module.export.service.ExportService;
import com.label.module.export.service.FinetuneService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresRoles;
@@ -19,6 +21,7 @@ import java.util.Map;
/**
* 训练数据导出与微调接口5 个端点,全部 ADMIN 权限)。
*/
@Tag(name = "导出管理", description = "训练样本查询、导出批次和微调任务")
@RestController
@RequiredArgsConstructor
public class ExportController {
@@ -27,6 +30,7 @@ public class ExportController {
private final FinetuneService finetuneService;
/** GET /api/training/samples — 分页查询已审批可导出样本 */
@Operation(summary = "分页查询可导出训练样本")
@GetMapping("/api/training/samples")
@RequiresRoles("ADMIN")
public Result<PageResult<TrainingDataset>> listSamples(
@@ -39,6 +43,7 @@ public class ExportController {
}
/** POST /api/export/batch — 创建导出批次 */
@Operation(summary = "创建导出批次")
@PostMapping("/api/export/batch")
@RequiresRoles("ADMIN")
@ResponseStatus(HttpStatus.CREATED)
@@ -53,6 +58,7 @@ public class ExportController {
}
/** POST /api/export/{batchId}/finetune — 提交微调任务 */
@Operation(summary = "提交微调任务")
@PostMapping("/api/export/{batchId}/finetune")
@RequiresRoles("ADMIN")
public Result<Map<String, Object>> triggerFinetune(@PathVariable Long batchId,
@@ -61,6 +67,7 @@ public class ExportController {
}
/** GET /api/export/{batchId}/status — 查询微调状态 */
@Operation(summary = "查询微调状态")
@GetMapping("/api/export/{batchId}/status")
@RequiresRoles("ADMIN")
public Result<Map<String, Object>> getFinetuneStatus(@PathVariable Long batchId,
@@ -69,6 +76,7 @@ public class ExportController {
}
/** GET /api/export/list — 分页查询导出批次列表 */
@Operation(summary = "分页查询导出批次")
@GetMapping("/api/export/list")
@RequiresRoles("ADMIN")
public Result<PageResult<ExportBatch>> listBatches(

View File

@@ -5,6 +5,8 @@ import com.label.common.result.Result;
import com.label.common.shiro.TokenPrincipal;
import com.label.module.source.dto.SourceResponse;
import com.label.module.source.service.SourceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresRoles;
@@ -19,6 +21,7 @@ import org.springframework.web.multipart.MultipartFile;
* - 上传 / 列表 / 详情UPLOADER 及以上角色(含 ANNOTATOR、REVIEWER、ADMIN
* - 删除:仅 ADMIN
*/
@Tag(name = "资料管理", description = "原始资料上传、查询和删除")
@RestController
@RequestMapping("/api/source")
@RequiredArgsConstructor
@@ -30,6 +33,7 @@ public class SourceController {
* 上传文件multipart/form-data
* 返回 201 Created + 资料摘要。
*/
@Operation(summary = "上传原始资料")
@PostMapping("/upload")
@RequiresRoles("UPLOADER")
@ResponseStatus(HttpStatus.CREATED)
@@ -45,6 +49,7 @@ public class SourceController {
* 分页查询资料列表。
* UPLOADER 只见自己的资料ADMIN 见全公司资料。
*/
@Operation(summary = "分页查询资料列表")
@GetMapping("/list")
@RequiresRoles("UPLOADER")
public Result<PageResult<SourceResponse>> list(
@@ -60,6 +65,7 @@ public class SourceController {
/**
* 查询资料详情(含 15 分钟预签名下载链接)。
*/
@Operation(summary = "查询资料详情")
@GetMapping("/{id}")
@RequiresRoles("UPLOADER")
public Result<SourceResponse> findById(@PathVariable Long id) {
@@ -70,6 +76,7 @@ public class SourceController {
* 删除资料(仅 PENDING 状态可删)。
* 同步删除 RustFS 文件及 DB 记录。
*/
@Operation(summary = "删除资料")
@DeleteMapping("/{id}")
@RequiresRoles("ADMIN")
public Result<Void> delete(@PathVariable Long id, HttpServletRequest request) {

View File

@@ -1,5 +1,6 @@
package com.label.module.source.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
@@ -11,17 +12,27 @@ import java.time.LocalDateTime;
*/
@Data
@Builder
@Schema(description = "原始资料响应")
public class SourceResponse {
@Schema(description = "资料主键")
private Long id;
@Schema(description = "文件名")
private String fileName;
@Schema(description = "资料类型", example = "TEXT")
private String dataType;
@Schema(description = "文件大小(字节)")
private Long fileSize;
@Schema(description = "资料状态", example = "PENDING")
private String status;
/** 上传用户 ID列表端点返回 */
@Schema(description = "上传用户 ID")
private Long uploaderId;
/** 15 分钟预签名下载链接(详情端点返回) */
@Schema(description = "预签名下载链接")
private String presignedUrl;
/** 父资料 ID视频帧 / 文本片段;详情端点返回) */
@Schema(description = "父资料 ID")
private Long parentSourceId;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
}

View File

@@ -6,6 +6,8 @@ import com.label.common.shiro.TokenPrincipal;
import com.label.module.task.dto.TaskResponse;
import com.label.module.task.service.TaskClaimService;
import com.label.module.task.service.TaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresRoles;
@@ -16,6 +18,7 @@ import java.util.Map;
/**
* 任务管理接口10 个端点)。
*/
@Tag(name = "任务管理", description = "任务池、我的任务、审批队列和管理操作")
@RestController
@RequestMapping("/api/tasks")
@RequiredArgsConstructor
@@ -25,6 +28,7 @@ public class TaskController {
private final TaskClaimService taskClaimService;
/** GET /api/tasks/pool — 查询可领取任务池(角色感知) */
@Operation(summary = "查询可领取任务池")
@GetMapping("/pool")
@RequiresRoles("ANNOTATOR")
public Result<PageResult<TaskResponse>> getPool(
@@ -35,6 +39,7 @@ public class TaskController {
}
/** GET /api/tasks/mine — 查询我的任务 */
@Operation(summary = "查询我的任务")
@GetMapping("/mine")
@RequiresRoles("ANNOTATOR")
public Result<PageResult<TaskResponse>> getMine(
@@ -46,6 +51,7 @@ public class TaskController {
}
/** GET /api/tasks/pending-review — 待审批队列REVIEWER 专属) */
@Operation(summary = "查询待审批任务")
@GetMapping("/pending-review")
@RequiresRoles("REVIEWER")
public Result<PageResult<TaskResponse>> getPendingReview(
@@ -56,6 +62,7 @@ public class TaskController {
}
/** GET /api/tasks — 查询全部任务ADMIN */
@Operation(summary = "管理员查询全部任务")
@GetMapping
@RequiresRoles("ADMIN")
public Result<PageResult<TaskResponse>> getAll(
@@ -67,6 +74,7 @@ public class TaskController {
}
/** POST /api/tasks — 创建任务ADMIN */
@Operation(summary = "管理员创建任务")
@PostMapping
@RequiresRoles("ADMIN")
public Result<TaskResponse> createTask(@RequestBody Map<String, Object> body,
@@ -79,6 +87,7 @@ public class TaskController {
}
/** GET /api/tasks/{id} — 查询任务详情 */
@Operation(summary = "查询任务详情")
@GetMapping("/{id}")
@RequiresRoles("ANNOTATOR")
public Result<TaskResponse> getById(@PathVariable Long id) {
@@ -86,6 +95,7 @@ public class TaskController {
}
/** POST /api/tasks/{id}/claim — 领取任务 */
@Operation(summary = "领取任务")
@PostMapping("/{id}/claim")
@RequiresRoles("ANNOTATOR")
public Result<Void> claim(@PathVariable Long id, HttpServletRequest request) {
@@ -94,6 +104,7 @@ public class TaskController {
}
/** POST /api/tasks/{id}/unclaim — 放弃任务 */
@Operation(summary = "放弃任务")
@PostMapping("/{id}/unclaim")
@RequiresRoles("ANNOTATOR")
public Result<Void> unclaim(@PathVariable Long id, HttpServletRequest request) {
@@ -102,6 +113,7 @@ public class TaskController {
}
/** POST /api/tasks/{id}/reclaim — 重领被驳回的任务 */
@Operation(summary = "重领被驳回的任务")
@PostMapping("/{id}/reclaim")
@RequiresRoles("ANNOTATOR")
public Result<Void> reclaim(@PathVariable Long id, HttpServletRequest request) {
@@ -110,6 +122,7 @@ public class TaskController {
}
/** PUT /api/tasks/{id}/reassign — ADMIN 强制指派 */
@Operation(summary = "管理员强制指派任务")
@PutMapping("/{id}/reassign")
@RequiresRoles("ADMIN")
public Result<Void> reassign(@PathVariable Long id,

View File

@@ -1,5 +1,6 @@
package com.label.module.task.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
@@ -10,17 +11,28 @@ import java.time.LocalDateTime;
*/
@Data
@Builder
@Schema(description = "标注任务响应")
public class TaskResponse {
@Schema(description = "任务主键")
private Long id;
@Schema(description = "关联资料 ID")
private Long sourceId;
/** 任务类型(对应 taskType 字段EXTRACTION / QA_GENERATION */
@Schema(description = "任务类型", example = "EXTRACTION")
private String taskType;
@Schema(description = "任务状态", example = "UNCLAIMED")
private String status;
@Schema(description = "领取人用户 ID")
private Long claimedBy;
@Schema(description = "领取时间")
private LocalDateTime claimedAt;
@Schema(description = "提交时间")
private LocalDateTime submittedAt;
@Schema(description = "完成时间")
private LocalDateTime completedAt;
/** 驳回原因REJECTED 状态时非空) */
@Schema(description = "驳回原因")
private String rejectReason;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
}

View File

@@ -6,6 +6,8 @@ import com.label.module.user.dto.LoginRequest;
import com.label.module.user.dto.LoginResponse;
import com.label.module.user.dto.UserInfoResponse;
import com.label.module.user.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -18,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
* - POST /api/auth/logout → 需要有效 TokenTokenFilter 校验)
* - GET /api/auth/me → 需要有效 TokenTokenFilter 校验)
*/
@Tag(name = "认证管理", description = "登录、退出和当前用户信息")
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@@ -28,6 +31,7 @@ public class AuthController {
/**
* 登录接口(匿名,无需 Token
*/
@Operation(summary = "用户登录,返回 Bearer Token")
@PostMapping("/login")
public Result<LoginResponse> login(@RequestBody LoginRequest request) {
return Result.success(authService.login(request));
@@ -36,6 +40,7 @@ public class AuthController {
/**
* 退出登录,立即删除 Redis Token。
*/
@Operation(summary = "退出登录并立即失效当前 Token")
@PostMapping("/logout")
public Result<Void> logout(HttpServletRequest request) {
String token = extractToken(request);
@@ -47,6 +52,7 @@ public class AuthController {
* 获取当前登录用户信息。
* TokenPrincipal 由 TokenFilter 写入请求属性 "__token_principal__"。
*/
@Operation(summary = "获取当前登录用户信息")
@GetMapping("/me")
public Result<UserInfoResponse> me(HttpServletRequest request) {
TokenPrincipal principal = (TokenPrincipal) request.getAttribute("__token_principal__");

View File

@@ -5,6 +5,8 @@ import com.label.common.result.Result;
import com.label.common.shiro.TokenPrincipal;
import com.label.module.user.entity.SysUser;
import com.label.module.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresRoles;
@@ -15,6 +17,7 @@ import java.util.Map;
/**
* 用户管理接口5 个端点,全部 ADMIN 权限)。
*/
@Tag(name = "用户管理", description = "管理员维护公司用户")
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@@ -23,6 +26,7 @@ public class UserController {
private final UserService userService;
/** GET /api/users — 分页查询用户列表 */
@Operation(summary = "分页查询用户列表")
@GetMapping
@RequiresRoles("ADMIN")
public Result<PageResult<SysUser>> listUsers(
@@ -33,6 +37,7 @@ public class UserController {
}
/** POST /api/users — 创建用户 */
@Operation(summary = "创建用户")
@PostMapping
@RequiresRoles("ADMIN")
public Result<SysUser> createUser(@RequestBody Map<String, String> body,
@@ -46,6 +51,7 @@ public class UserController {
}
/** PUT /api/users/{id} — 更新用户基本信息 */
@Operation(summary = "更新用户基本信息")
@PutMapping("/{id}")
@RequiresRoles("ADMIN")
public Result<SysUser> updateUser(@PathVariable Long id,
@@ -59,6 +65,7 @@ public class UserController {
}
/** PUT /api/users/{id}/status — 变更用户状态 */
@Operation(summary = "变更用户状态")
@PutMapping("/{id}/status")
@RequiresRoles("ADMIN")
public Result<Void> updateStatus(@PathVariable Long id,
@@ -69,6 +76,7 @@ public class UserController {
}
/** PUT /api/users/{id}/role — 变更用户角色 */
@Operation(summary = "变更用户角色")
@PutMapping("/{id}/role")
@RequiresRoles("ADMIN")
public Result<Void> updateRole(@PathVariable Long id,

View File

@@ -1,16 +1,21 @@
package com.label.module.user.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 登录请求体。
*/
@Data
@Schema(description = "登录请求")
public class LoginRequest {
/** 公司代码(英文简写),用于确定租户 */
@Schema(description = "公司代码(英文简写)", example = "DEMO")
private String companyCode;
/** 登录用户名 */
@Schema(description = "登录用户名", example = "admin")
private String username;
/** 明文密码(传输层应使用 HTTPS 保护) */
@Schema(description = "明文密码", example = "admin123")
private String password;
}

View File

@@ -1,5 +1,6 @@
package com.label.module.user.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -8,15 +9,21 @@ import lombok.Data;
*/
@Data
@AllArgsConstructor
@Schema(description = "登录响应")
public class LoginResponse {
/** Bearer TokenUUID v4后续请求放入 Authorization 头 */
@Schema(description = "Bearer Token", example = "550e8400-e29b-41d4-a716-446655440000")
private String token;
/** 用户主键 */
@Schema(description = "用户主键")
private Long userId;
/** 登录用户名 */
@Schema(description = "登录用户名")
private String username;
/** 角色UPLOADER / ANNOTATOR / REVIEWER / ADMIN */
@Schema(description = "角色", example = "ADMIN")
private String role;
/** Token 有效期(秒) */
@Schema(description = "Token 有效期(秒)", example = "7200")
private Long expiresIn;
}

View File

@@ -1,5 +1,6 @@
package com.label.module.user.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -8,11 +9,18 @@ import lombok.Data;
*/
@Data
@AllArgsConstructor
@Schema(description = "当前登录用户信息")
public class UserInfoResponse {
@Schema(description = "用户主键")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "角色", example = "ADMIN")
private String role;
@Schema(description = "所属公司 ID")
private Long companyId;
@Schema(description = "所属公司名称")
private String companyName;
}

View File

@@ -4,6 +4,8 @@ import com.label.common.result.Result;
import com.label.common.shiro.TokenPrincipal;
import com.label.module.video.entity.VideoProcessJob;
import com.label.module.video.service.VideoProcessService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -21,6 +23,7 @@ import java.util.Map;
* POST /api/video/jobs/{jobId}/reset — 重置失败任务ADMIN
* POST /api/video/callback — AI 回调接口(无需认证,已在 TokenFilter 中排除)
*/
@Tag(name = "视频处理", description = "视频处理任务创建、查询、重置和回调")
@Slf4j
@RestController
@RequiredArgsConstructor
@@ -32,6 +35,7 @@ public class VideoController {
private String callbackSecret;
/** POST /api/video/process — 触发视频处理任务 */
@Operation(summary = "触发视频处理任务")
@PostMapping("/api/video/process")
@RequiresRoles("ADMIN")
public Result<VideoProcessJob> createJob(@RequestBody Map<String, Object> body,
@@ -51,6 +55,7 @@ public class VideoController {
}
/** GET /api/video/jobs/{jobId} — 查询视频处理任务 */
@Operation(summary = "查询视频处理任务状态")
@GetMapping("/api/video/jobs/{jobId}")
@RequiresRoles("ADMIN")
public Result<VideoProcessJob> getJob(@PathVariable Long jobId,
@@ -59,6 +64,7 @@ public class VideoController {
}
/** POST /api/video/jobs/{jobId}/reset — 管理员重置失败任务 */
@Operation(summary = "重置失败的视频处理任务")
@PostMapping("/api/video/jobs/{jobId}/reset")
@RequiresRoles("ADMIN")
public Result<VideoProcessJob> resetJob(@PathVariable Long jobId,
@@ -76,9 +82,10 @@ public class VideoController {
* { "jobId": 123, "status": "SUCCESS", "outputPath": "processed/123/frames.zip" }
* { "jobId": 123, "status": "FAILED", "errorMessage": "ffmpeg error: ..." }
*/
@Operation(summary = "接收 AI 服务视频处理回调")
@PostMapping("/api/video/callback")
public Result<Void> handleCallback(@RequestBody Map<String, Object> body,
HttpServletRequest request) {
HttpServletRequest request) {
// 共享密钥校验(配置了 VIDEO_CALLBACK_SECRET 时强制校验)
if (callbackSecret != null && !callbackSecret.isBlank()) {
String provided = request.getHeader("X-Callback-Secret");