package com.campus.controller; import com.campus.common.Result; import com.campus.dto.response.VodUploadCredential; import com.campus.service.VodService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 阿里云 VOD 视频点播接口 * * 上传流程: * 1. POST /api/vod/upload → 前端发送视频文件 → 后端 CreateUploadVideo + 上传到 VOD Bucket * 2. VOD 自动转码/审核 → 回调 POST /api/oss/vod-callback → 更新数据库 * * 播放流程: * 3. GET /api/vod/play-auth/{videoId} → 获取 PlayAuth → 前端初始化播放器 */ @Slf4j @RestController @RequestMapping("/api/vod") @RequiredArgsConstructor public class VodController { private final VodService vodService; /** * 上传视频到 VOD(一步完成:创建凭证 + 上传到 VOD Bucket) * * POST /api/vod/upload * Content-Type: multipart/form-data * 参数: file (视频文件) * * @return { videoId, requestId } */ @PostMapping("/upload") public Result> uploadVideo(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error(400, "文件不能为空"); } String fileName = file.getOriginalFilename(); if (fileName == null) { fileName = "video.mp4"; } try { // Step 1: 创建上传凭证(CreateUploadVideo) log.info("开始VOD上传: fileName={}, size={}MB", fileName, String.format("%.1f", file.getSize() / 1024.0 / 1024.0)); VodUploadCredential credential = vodService.createUploadVideo(fileName); // Step 2: 使用临时凭证上传到 VOD Bucket String videoId = vodService.uploadToVodBucket( file.getInputStream(), file.getSize(), credential, file.getContentType() ); Map result = new HashMap<>(); result.put("videoId", videoId); result.put("requestId", credential.getRequestId()); log.info("VOD上传完成: videoId={}", videoId); return Result.success(result); } catch (IOException e) { log.error("VOD上传失败: 文件读取错误", e); return Result.error(500, "文件读取失败: " + e.getMessage()); } catch (Exception e) { log.error("VOD上传失败: fileName={}", fileName, e); return Result.error(500, "视频上传失败: " + e.getMessage()); } } /** * 上传图片到 VOD(一步完成:创建凭证 + 上传到 VOD Bucket) * * POST /api/vod/upload-image * Content-Type: multipart/form-data * 参数: file (图片文件) * * @return { imageId, imageUrl, requestId } */ @PostMapping("/upload-image") public Result> uploadImage(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error(400, "文件不能为空"); } String fileName = file.getOriginalFilename(); if (fileName == null) { fileName = "image.jpg"; } try { log.info("开始VOD图片上传: fileName={}, size={}KB", fileName, String.format("%.1f", file.getSize() / 1024.0)); // Step 1: 创建上传凭证(CreateUploadImage) VodUploadCredential credential = vodService.createUploadImage(fileName); // Step 2: 使用临时凭证上传到 VOD Bucket String imageId = vodService.uploadToVodBucket( file.getInputStream(), file.getSize(), credential, file.getContentType() ); Map result = new HashMap<>(); result.put("imageId", imageId); result.put("imageUrl", credential.getImageUrl()); result.put("requestId", credential.getRequestId()); log.info("VOD图片上传完成: imageId={}, imageUrl={}", imageId, credential.getImageUrl()); return Result.success(result); } catch (IOException e) { log.error("VOD图片上传失败: 文件读取错误", e); return Result.error(500, "文件读取失败: " + e.getMessage()); } catch (Exception e) { log.error("VOD图片上传失败: fileName={}", fileName, e); return Result.error(500, "图片上传失败: " + e.getMessage()); } } /** * 创建上传凭证(用于前端直传场景 — 高级用法,需要前端集成 OSS SDK) * * POST /api/vod/create-upload * Body: { "fileName": "video.mp4" } * * 注意:React Native 环境下建议使用 POST /api/vod/upload,后端代理上传更稳定 */ @PostMapping("/create-upload") public Result createUpload(@RequestBody Map body) { String fileName = body.getOrDefault("fileName", "video.mp4"); try { VodUploadCredential credential = vodService.createUploadVideo(fileName); return Result.success(credential); } catch (Exception e) { log.error("创建上传凭证失败: fileName={}", fileName, e); return Result.error(500, "创建上传凭证失败: " + e.getMessage()); } } /** * 刷新上传凭证(凭证有效期 3000 秒) * * POST /api/vod/refresh-upload/{videoId} */ @PostMapping("/refresh-upload/{videoId}") public Result refreshUpload(@PathVariable String videoId) { try { VodUploadCredential credential = vodService.refreshUploadVideo(videoId); return Result.success(credential); } catch (Exception e) { log.error("刷新上传凭证失败: videoId={}", videoId, e); return Result.error(500, "刷新上传凭证失败: " + e.getMessage()); } } /** * 获取视频播放凭证(PlayAuth — 安全播放) * * GET /api/vod/play-auth/{videoId} * * 返回 PlayAuth,前端用它初始化阿里云播放器 SDK * 同时返回视频元信息(封面、时长等) */ @GetMapping("/play-auth/{videoId}") public Result> getPlayAuth(@PathVariable String videoId) { try { String playAuth = vodService.getPlayAuth(videoId); Map playInfo = vodService.getPlayInfo(videoId); Map result = new HashMap<>(); result.put("videoId", videoId); result.put("playAuth", playAuth); result.put("coverUrl", playInfo.get("coverUrl")); result.put("duration", playInfo.get("duration")); return Result.success(result); } catch (Exception e) { log.error("获取播放凭证失败: videoId={}", videoId, e); return Result.error(500, "获取播放凭证失败: " + e.getMessage()); } } /** * 获取视频播放地址(简单场景,直接返回 m3u8 URL) * * GET /api/vod/play-info/{videoId} */ @GetMapping("/play-info/{videoId}") public Result> getPlayInfo(@PathVariable String videoId) { try { Map playInfo = vodService.getPlayInfo(videoId); return Result.success(playInfo); } catch (Exception e) { log.error("获取播放信息失败: videoId={}", videoId, e); return Result.error(500, "获取播放信息失败: " + e.getMessage()); } } }