Files
label_backend/src/main/java/com/label/controller/VideoController.java

111 lines
4.8 KiB
Java
Raw Normal View History

2026-04-14 13:47:38 +08:00
package com.label.controller;
2026-04-14 16:33:34 +08:00
import com.label.annotation.RequireRole;
import com.label.common.auth.TokenPrincipal;
import com.label.common.result.Result;
import com.label.entity.VideoProcessJob;
2026-04-14 13:45:15 +08:00
import com.label.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;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 视频处理接口4 个端点
*
* POST /api/video/process 触发视频处理ADMIN
* GET /api/video/jobs/{jobId} 查询任务状态ADMIN
* POST /api/video/jobs/{jobId}/reset 重置失败任务ADMIN
2026-04-14 16:33:34 +08:00
* POST /api/video/callback AI 回调接口无需认证已在 AuthInterceptor 中排除
*/
@Tag(name = "视频处理", description = "视频处理任务创建、查询、重置和回调")
@Slf4j
@RestController
@RequiredArgsConstructor
public class VideoController {
private final VideoProcessService videoProcessService;
@Value("${video.callback-secret:}")
private String callbackSecret;
/** POST /api/video/process — 触发视频处理任务 */
@Operation(summary = "触发视频处理任务")
@PostMapping("/api/video/process")
2026-04-14 16:33:34 +08:00
@RequireRole("ADMIN")
public Result<VideoProcessJob> createJob(@RequestBody Map<String, Object> body,
HttpServletRequest request) {
Object sourceIdVal = body.get("sourceId");
Object jobTypeVal = body.get("jobType");
if (sourceIdVal == null || jobTypeVal == 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;
TokenPrincipal principal = principal(request);
return Result.success(
videoProcessService.createJob(sourceId, jobType, params, principal.getCompanyId()));
}
/** GET /api/video/jobs/{jobId} — 查询视频处理任务 */
@Operation(summary = "查询视频处理任务状态")
@GetMapping("/api/video/jobs/{jobId}")
2026-04-14 16:33:34 +08:00
@RequireRole("ADMIN")
public Result<VideoProcessJob> getJob(@PathVariable Long jobId,
HttpServletRequest request) {
return Result.success(videoProcessService.getJob(jobId, principal(request).getCompanyId()));
}
/** POST /api/video/jobs/{jobId}/reset — 管理员重置失败任务 */
@Operation(summary = "重置失败的视频处理任务")
@PostMapping("/api/video/jobs/{jobId}/reset")
2026-04-14 16:33:34 +08:00
@RequireRole("ADMIN")
public Result<VideoProcessJob> resetJob(@PathVariable Long jobId,
HttpServletRequest request) {
return Result.success(videoProcessService.reset(jobId, principal(request).getCompanyId()));
}
/**
* POST /api/video/callback AI 服务回调无需 Bearer Token
*
2026-04-14 16:33:34 +08:00
* 此端点已在 AuthInterceptor 中排除认证
* AI 服务直接调用携带 jobIdstatusoutputPath 等参数
*
* Body 示例
* { "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) {
// 共享密钥校验(配置了 VIDEO_CALLBACK_SECRET 时强制校验)
if (callbackSecret != null && !callbackSecret.isBlank()) {
String provided = request.getHeader("X-Callback-Secret");
if (!callbackSecret.equals(provided)) {
return Result.failure("UNAUTHORIZED", "回调密钥无效");
}
}
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;
log.info("视频处理回调jobId={}, status={}", jobId, status);
videoProcessService.handleCallback(jobId, status, outputPath, errorMessage);
return Result.success(null);
}
private TokenPrincipal principal(HttpServletRequest request) {
return (TokenPrincipal) request.getAttribute("__token_principal__");
}
}