111 lines
4.8 KiB
Java
111 lines
4.8 KiB
Java
package com.label.controller;
|
||
|
||
import com.label.annotation.RequireRole;
|
||
import com.label.common.auth.TokenPrincipal;
|
||
import com.label.common.result.Result;
|
||
import com.label.entity.VideoProcessJob;
|
||
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)
|
||
* 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")
|
||
@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}")
|
||
@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")
|
||
@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)。
|
||
*
|
||
* 此端点已在 AuthInterceptor 中排除认证,
|
||
* 由 AI 服务直接调用,携带 jobId、status、outputPath 等参数。
|
||
*
|
||
* 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__");
|
||
}
|
||
}
|