diff --git a/.idea/.cache/.Apifox_Helper/.toolWindow.db b/.idea/.cache/.Apifox_Helper/.toolWindow.db
index 7d3a966..1bfb44b 100644
Binary files a/.idea/.cache/.Apifox_Helper/.toolWindow.db and b/.idea/.cache/.Apifox_Helper/.toolWindow.db differ
diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml
index 517f9ea..f7f3101 100644
--- a/.idea/ApifoxUploaderProjectSetting.xml
+++ b/.idea/ApifoxUploaderProjectSetting.xml
@@ -6,9 +6,10 @@
+
-
-
+
+
\ No newline at end of file
diff --git a/video-common/src/main/java/com/nuaa/video/common/utils/MinioUtils.java b/video-common/src/main/java/com/nuaa/video/common/utils/MinioUtils.java
index dd0c11d..c4eb866 100644
--- a/video-common/src/main/java/com/nuaa/video/common/utils/MinioUtils.java
+++ b/video-common/src/main/java/com/nuaa/video/common/utils/MinioUtils.java
@@ -224,4 +224,24 @@ public class MinioUtils {
throw new RuntimeException("获取文件基础URL失败", e);
}
}
+
+ public String uploadObject(String fileType, String fileName, InputStream inputStream, long size, String contentType) throws Exception {
+ // 检查存储桶是否存在
+ createBucket(bucketName);
+
+ // 生成对象名称
+ String objectName = fileType + "/" + fileName;
+
+ // 上传文件
+ minioClient.putObject(
+ PutObjectArgs.builder()
+ .bucket(bucketName)
+ .object(objectName)
+ .stream(inputStream, size, -1)
+ .contentType(contentType)
+ .build()
+ );
+
+ return objectName;
+ }
}
\ No newline at end of file
diff --git a/video-content/pom.xml b/video-content/pom.xml
index 0abb635..29d2388 100644
--- a/video-content/pom.xml
+++ b/video-content/pom.xml
@@ -12,12 +12,87 @@
video-content
+
com.nuaa.video
video-common
${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-sentinel
+
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ 3.5.11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
org.apache.rocketmq
@@ -29,7 +104,38 @@
net.bramp.ffmpeg
ffmpeg
- 0.7.0
+ 0.8.0
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.26
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/ContentApplication.java b/video-content/src/main/java/com/nuaa/video/content/ContentApplication.java
index 413dbbb..c1c698f 100644
--- a/video-content/src/main/java/com/nuaa/video/content/ContentApplication.java
+++ b/video-content/src/main/java/com/nuaa/video/content/ContentApplication.java
@@ -1,11 +1,20 @@
package com.nuaa.video.content;
+import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.scheduling.annotation.EnableScheduling;
+/**
+ * 内容服务启动类
+ */
@SpringBootApplication
@EnableDiscoveryClient
+@EnableFeignClients
+@EnableScheduling
+@MapperScan("com.nuaa.video.content.mapper")
public class ContentApplication {
public static void main(String[] args) {
SpringApplication.run(ContentApplication.class, args);
diff --git a/video-content/src/main/java/com/nuaa/video/content/config/FFmpegConfig.java b/video-content/src/main/java/com/nuaa/video/content/config/FFmpegConfig.java
new file mode 100644
index 0000000..d3341f1
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/config/FFmpegConfig.java
@@ -0,0 +1,68 @@
+package com.nuaa.video.content.config;
+
+import lombok.Data;
+import net.bramp.ffmpeg.FFmpeg;
+import net.bramp.ffmpeg.FFprobe;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.IOException;
+
+/**
+ * FFmpeg配置类
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "ffmpeg")
+public class FFmpegConfig {
+
+ /**
+ * binary配置
+ */
+ private Binary binary = new Binary();
+
+ /**
+ * probe配置
+ */
+ private Probe probe = new Probe();
+
+ /**
+ * 临时文件配置
+ */
+ private Temp temp = new Temp();
+
+ @Data
+ public static class Binary {
+ /**
+ * FFmpeg可执行文件路径
+ */
+ private String path = "/usr/bin/ffmpeg";
+ }
+
+ @Data
+ public static class Probe {
+ /**
+ * FFprobe可执行文件路径
+ */
+ private String path = "/usr/bin/ffprobe";
+ }
+
+ @Data
+ public static class Temp {
+ /**
+ * 临时文件目录
+ */
+ private String path = "/tmp/ffmpeg";
+ }
+
+ @Bean
+ public FFmpeg ffmpeg() throws IOException {
+ return new FFmpeg(binary.getPath());
+ }
+
+ @Bean
+ public FFprobe ffprobe() throws IOException {
+ return new FFprobe(probe.getPath());
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/config/MinioConfig.java b/video-content/src/main/java/com/nuaa/video/content/config/MinioConfig.java
new file mode 100644
index 0000000..b878e3b
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/config/MinioConfig.java
@@ -0,0 +1,52 @@
+package com.nuaa.video.content.config;
+
+import com.nuaa.video.common.utils.MinioUtils;
+import io.minio.MinioClient;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MinIO配置类
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "minio")
+public class MinioConfig {
+
+ /**
+ * 服务地址
+ */
+ private String endpoint;
+
+ /**
+ * 访问密钥
+ */
+ private String accessKey;
+
+ /**
+ * 密钥
+ */
+ private String secretKey;
+
+ /**
+ * 存储桶名称
+ */
+ private String bucketName;
+
+ @Bean
+ public MinioClient minioClient() {
+ return MinioClient.builder()
+ .endpoint(endpoint)
+ .credentials(accessKey, secretKey)
+ .build();
+ }
+
+ @Bean
+ public MinioUtils minioUtils(MinioClient minioClient) {
+ MinioUtils minioUtils = new MinioUtils(minioClient);
+ // 如果MinioUtils有setBucketName方法或需要其他配置,在这里设置
+ return minioUtils;
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/config/RocketConfig.java b/video-content/src/main/java/com/nuaa/video/content/config/RocketConfig.java
new file mode 100644
index 0000000..286df2d
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/config/RocketConfig.java
@@ -0,0 +1,29 @@
+package com.nuaa.video.content.config;
+
+import org.apache.rocketmq.client.producer.DefaultMQProducer;
+import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RocketConfig {
+
+ @Value("${spring.rocketmq.producer.group}")
+ private String producerGroup;
+
+ @Value("${spring.rocketmq.name-server}")
+ private String nameServer;
+
+ @Bean(name = "rocketMQTemplate")
+ public RocketMQTemplate rocketMQTemplate() {
+ RocketMQTemplate rocketMQTemplate = new RocketMQTemplate();
+ DefaultMQProducer defaultMQProducer = new DefaultMQProducer(producerGroup);
+ defaultMQProducer.setProducerGroup(producerGroup);
+ defaultMQProducer.setNamesrvAddr(nameServer);
+ rocketMQTemplate.setProducer(defaultMQProducer);
+ return rocketMQTemplate;
+ }
+
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/config/SecurityConfig.java b/video-content/src/main/java/com/nuaa/video/content/config/SecurityConfig.java
new file mode 100644
index 0000000..6a0f26a
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/config/SecurityConfig.java
@@ -0,0 +1,43 @@
+package com.nuaa.video.content.config;
+
+import com.nuaa.video.content.filter.JwtAuthenticationFilter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+/**
+ * 安全配置类
+ */
+@Configuration
+@EnableWebSecurity
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+ private final JwtAuthenticationFilter jwtAuthenticationFilter;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(csrf -> csrf.disable())
+ .cors(cors -> cors.disable())
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/content/video/public/**",
+ "/content/category/list",
+ "/content/tag/list",
+ "/actuator/**",
+ "/error"
+ ).permitAll()
+ .anyRequest().authenticated()
+ )
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/controller/CategoryController.java b/video-content/src/main/java/com/nuaa/video/content/controller/CategoryController.java
new file mode 100644
index 0000000..edea5a0
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/controller/CategoryController.java
@@ -0,0 +1,80 @@
+package com.nuaa.video.content.controller;
+
+import com.nuaa.video.common.model.vo.Result;
+import com.nuaa.video.content.entity.ContentCategory;
+import com.nuaa.video.content.service.CategoryService;
+import com.nuaa.video.content.vo.CategoryVO;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 分类控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/content/category")
+@RequiredArgsConstructor
+public class CategoryController {
+
+ private final CategoryService categoryService;
+
+ /**
+ * 获取分类树
+ */
+ @GetMapping("/tree")
+ public Result> getCategoryTree() {
+ return categoryService.getCategoryTree();
+ }
+
+ /**
+ * 获取分类列表
+ */
+ @GetMapping("/list")
+ public Result> getCategoryList() {
+ return categoryService.getCategoryList();
+ }
+
+ /**
+ * 添加分类
+ */
+ @PostMapping
+ public Result addCategory(@RequestBody ContentCategory category, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ category.setCreatedBy(userId.toString());
+ category.setUpdatedBy(userId.toString());
+ return categoryService.addCategory(category);
+ }
+
+ /**
+ * 更新分类
+ */
+ @PutMapping
+ public Result updateCategory(@RequestBody ContentCategory category, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ category.setUpdatedBy(userId.toString());
+ return categoryService.updateCategory(category);
+ }
+
+ /**
+ * 删除分类
+ */
+ @DeleteMapping("/{id}")
+ public Result deleteCategory(@PathVariable Long id) {
+ return categoryService.deleteCategory(id);
+ }
+
+ /**
+ * 从请求中获取用户ID
+ */
+ private Long getUserIdFromRequest(HttpServletRequest request) {
+ Object userIdObj = request.getAttribute("userId");
+ if (userIdObj instanceof Long) {
+ return (Long) userIdObj;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/controller/DanmakuController.java b/video-content/src/main/java/com/nuaa/video/content/controller/DanmakuController.java
new file mode 100644
index 0000000..811b6fd
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/controller/DanmakuController.java
@@ -0,0 +1,67 @@
+package com.nuaa.video.content.controller;
+
+import com.nuaa.video.common.model.vo.Result;
+import com.nuaa.video.content.dto.DanmakuDTO;
+import com.nuaa.video.content.service.DanmakuService;
+import com.nuaa.video.content.vo.DanmakuVO;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 弹幕控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/content/danmaku")
+@RequiredArgsConstructor
+public class DanmakuController {
+
+ private final DanmakuService danmakuService;
+
+ /**
+ * 发送弹幕
+ */
+ @PostMapping
+ public Result sendDanmaku(@RequestBody @Valid DanmakuDTO danmakuDTO, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return danmakuService.sendDanmaku(danmakuDTO, userId);
+ }
+
+ /**
+ * 获取视频指定时间段的弹幕
+ */
+ @GetMapping("/time")
+ public Result> getVideoTimeDanmaku(
+ @RequestParam Long videoId,
+ @RequestParam Float startTime,
+ @RequestParam Float endTime) {
+ return danmakuService.getVideoTimeDanmaku(videoId, startTime, endTime);
+ }
+
+ /**
+ * 获取视频所有弹幕
+ */
+ @GetMapping("/all/{videoId}")
+ public Result> getVideoAllDanmaku(@PathVariable Long videoId) {
+ return danmakuService.getVideoAllDanmaku(videoId);
+ }
+
+ /**
+ * 从请求中获取用户ID
+ */
+ private Long getUserIdFromRequest(HttpServletRequest request) {
+ Object userIdObj = request.getAttribute("userId");
+ if (userIdObj instanceof Long) {
+ return (Long) userIdObj;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/controller/FavoriteController.java b/video-content/src/main/java/com/nuaa/video/content/controller/FavoriteController.java
new file mode 100644
index 0000000..32eac93
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/controller/FavoriteController.java
@@ -0,0 +1,107 @@
+package com.nuaa.video.content.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.nuaa.video.common.model.vo.Result;
+import com.nuaa.video.content.entity.ContentUserFavorite;
+import com.nuaa.video.content.entity.ContentVideo;
+import com.nuaa.video.content.service.FavoriteService;
+import com.nuaa.video.content.vo.FavoriteVO;
+import com.nuaa.video.content.vo.VideoListVO;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 收藏夹控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/content/favorite")
+@RequiredArgsConstructor
+public class FavoriteController {
+
+ private final FavoriteService favoriteService;
+
+ /**
+ * 获取用户收藏夹列表
+ */
+ @GetMapping("/list")
+ public Result> getUserFavorites(HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return favoriteService.getUserFavorites(userId);
+ }
+
+ /**
+ * 创建收藏夹
+ */
+ @PostMapping
+ public Result createFavorite(@RequestBody ContentUserFavorite favorite, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ favorite.setUserId(userId);
+ favorite.setCreatedBy(userId.toString());
+ favorite.setUpdatedBy(userId.toString());
+ return favoriteService.createFavorite(favorite, userId);
+ }
+
+ /**
+ * 更新收藏夹
+ */
+ @PutMapping
+ public Result updateFavorite(@RequestBody ContentUserFavorite favorite, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ favorite.setUpdatedBy(userId.toString());
+ return favoriteService.updateFavorite(favorite, userId);
+ }
+
+ /**
+ * 删除收藏夹
+ */
+ @DeleteMapping("/{id}")
+ public Result deleteFavorite(@PathVariable Long id, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return favoriteService.deleteFavorite(id, userId);
+ }
+
+ /**
+ * 获取收藏夹中的视频
+ */
+ @GetMapping("/{favoriteId}/videos")
+ public Result> getFavoriteVideos(
+ @PathVariable Long favoriteId,
+ @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+ @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+ HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ Page page = new Page<>(pageNum, pageSize);
+ return favoriteService.getFavoriteVideos(favoriteId, page, userId);
+ }
+
+ /**
+ * 从请求中获取用户ID
+ */
+ private Long getUserIdFromRequest(HttpServletRequest request) {
+ Object userIdObj = request.getAttribute("userId");
+ if (userIdObj instanceof Long) {
+ return (Long) userIdObj;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/controller/TagController.java b/video-content/src/main/java/com/nuaa/video/content/controller/TagController.java
new file mode 100644
index 0000000..e151b59
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/controller/TagController.java
@@ -0,0 +1,93 @@
+package com.nuaa.video.content.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.nuaa.video.common.model.vo.Result;
+import com.nuaa.video.content.entity.ContentTag;
+import com.nuaa.video.content.service.TagService;
+import com.nuaa.video.content.vo.TagVO;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 标签控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/content/tag")
+@RequiredArgsConstructor
+public class TagController {
+
+ private final TagService tagService;
+
+ /**
+ * 获取标签列表
+ */
+ @GetMapping("/list")
+ public Result> getTagList() {
+ return tagService.getTagList();
+ }
+
+ /**
+ * 分页获取标签列表
+ */
+ @GetMapping("/page")
+ public Result> getTagPage(
+ @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+ @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+ @RequestParam(value = "keyword", required = false) String keyword) {
+ Page page = new Page<>(pageNum, pageSize);
+ return tagService.getTagPage(page, keyword);
+ }
+
+ /**
+ * 添加标签
+ */
+ @PostMapping
+ public Result addTag(@RequestBody ContentTag tag, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ tag.setCreatedBy(userId.toString());
+ tag.setUpdatedBy(userId.toString());
+ return tagService.addTag(tag);
+ }
+
+ /**
+ * 更新标签
+ */
+ @PutMapping
+ public Result updateTag(@RequestBody ContentTag tag, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ tag.setUpdatedBy(userId.toString());
+ return tagService.updateTag(tag);
+ }
+
+ /**
+ * 删除标签
+ */
+ @DeleteMapping("/{id}")
+ public Result deleteTag(@PathVariable Long id) {
+ return tagService.deleteTag(id);
+ }
+
+ /**
+ * 获取视频标签
+ */
+ @GetMapping("/video/{videoId}")
+ public Result> getVideoTags(@PathVariable Long videoId) {
+ return tagService.getVideoTags(videoId);
+ }
+
+ /**
+ * 从请求中获取用户ID
+ */
+ private Long getUserIdFromRequest(HttpServletRequest request) {
+ Object userIdObj = request.getAttribute("userId");
+ if (userIdObj instanceof Long) {
+ return (Long) userIdObj;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/controller/VideoController.java b/video-content/src/main/java/com/nuaa/video/content/controller/VideoController.java
new file mode 100644
index 0000000..0f4c764
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/controller/VideoController.java
@@ -0,0 +1,230 @@
+package com.nuaa.video.content.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.nuaa.video.common.model.vo.Result;
+import com.nuaa.video.content.dto.*;
+import com.nuaa.video.content.entity.ContentVideo;
+import com.nuaa.video.content.service.VideoService;
+import com.nuaa.video.content.vo.UploadResultVO;
+import com.nuaa.video.content.vo.VideoInfoVO;
+import com.nuaa.video.content.vo.VideoListVO;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 视频控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/content/video")
+@RequiredArgsConstructor
+public class VideoController {
+
+ private final VideoService videoService;
+
+ /**
+ * 上传视频
+ */
+ @PostMapping("/upload")
+ public Result uploadVideo(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.uploadVideo(file, userId);
+ }
+
+ /**
+ * 创建视频
+ */
+ @PostMapping
+ public Result createVideo(@RequestBody @Valid VideoUploadDTO uploadDTO, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.createVideo(uploadDTO, userId);
+ }
+
+ /**
+ * 获取视频详情
+ */
+ @GetMapping("/{id}")
+ public Result getVideoInfo(@PathVariable Long id, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ return videoService.getVideoInfo(id, userId);
+ }
+
+ /**
+ * 获取视频列表
+ */
+ @GetMapping("/list")
+ public Result> getVideoList(
+ @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+ @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+ VideoQueryDTO queryDTO,
+ HttpServletRequest request) {
+ Page page = new Page<>(pageNum, pageSize);
+ Long userId = getUserIdFromRequest(request);
+ return videoService.getVideoList(page, queryDTO, userId);
+ }
+
+ /**
+ * 更新视频信息
+ */
+ @PutMapping
+ public Result updateVideo(@RequestBody @Valid VideoUpdateDTO updateDTO, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.updateVideo(updateDTO, userId);
+ }
+
+ /**
+ * 删除视频
+ */
+ @DeleteMapping("/{id}")
+ public Result deleteVideo(@PathVariable Long id, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.deleteVideo(id, userId);
+ }
+
+ /**
+ * 点赞视频
+ */
+ @PostMapping("/like/{videoId}")
+ public Result likeVideo(@PathVariable Long videoId, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.likeVideo(videoId, userId);
+ }
+
+ /**
+ * 取消点赞
+ */
+ @DeleteMapping("/like/{videoId}")
+ public Result unlikeVideo(@PathVariable Long videoId, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.unlikeVideo(videoId, userId);
+ }
+
+ /**
+ * 投币
+ */
+ @PostMapping("/coin/{videoId}")
+ public Result coinVideo(
+ @PathVariable Long videoId,
+ @RequestParam(value = "amount", defaultValue = "1") Integer amount,
+ HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.coinVideo(videoId, userId, amount);
+ }
+
+ /**
+ * 收藏视频
+ */
+ @PostMapping("/favorite")
+ public Result favoriteVideo(@RequestBody @Valid FavoriteDTO favoriteDTO, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.favoriteVideo(favoriteDTO, userId);
+ }
+
+ /**
+ * 取消收藏
+ */
+ @DeleteMapping("/favorite/{videoId}")
+ public Result unfavoriteVideo(@PathVariable Long videoId, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.unfavoriteVideo(videoId, userId);
+ }
+
+ /**
+ * 分享视频
+ */
+ @PostMapping("/share/{videoId}")
+ public Result shareVideo(
+ @PathVariable Long videoId,
+ @RequestParam(value = "platform", required = false) String platform,
+ HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.shareVideo(videoId, userId, platform);
+ }
+
+ /**
+ * 播放记录
+ */
+ @PostMapping("/play")
+ public Result playVideo(@RequestBody @Valid PlayRecordDTO recordDTO, HttpServletRequest request) {
+ Long userId = getUserIdFromRequest(request);
+ if (userId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.playVideo(recordDTO, userId);
+ }
+
+ /**
+ * 获取用户视频列表
+ */
+ @GetMapping("/user/{userId}")
+ public Result> getUserVideos(
+ @PathVariable Long userId,
+ @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+ @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+ HttpServletRequest request) {
+ Page page = new Page<>(pageNum, pageSize);
+ Long currentUserId = getUserIdFromRequest(request);
+ return videoService.getUserVideos(userId, page, currentUserId);
+ }
+
+ /**
+ * 视频审核
+ */
+ @PostMapping("/review/{videoId}")
+ public Result reviewVideo(
+ @PathVariable Long videoId,
+ @RequestParam Integer status,
+ @RequestParam(required = false) String reason,
+ HttpServletRequest request) {
+ Long reviewerId = getUserIdFromRequest(request);
+ if (reviewerId == null) {
+ return Result.error("未获取到用户ID");
+ }
+ return videoService.reviewVideo(videoId, reviewerId, status, reason);
+ }
+
+ /**
+ * 从请求中获取用户ID
+ */
+ private Long getUserIdFromRequest(HttpServletRequest request) {
+ Object userIdObj = request.getAttribute("userId");
+ if (userIdObj instanceof Long) {
+ return (Long) userIdObj;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/video-content/src/main/java/com/nuaa/video/content/controller/VideoStatController.java b/video-content/src/main/java/com/nuaa/video/content/controller/VideoStatController.java
new file mode 100644
index 0000000..a7ac2ce
--- /dev/null
+++ b/video-content/src/main/java/com/nuaa/video/content/controller/VideoStatController.java
@@ -0,0 +1,57 @@
+package com.nuaa.video.content.controller;
+
+import com.nuaa.video.common.model.vo.Result;
+import com.nuaa.video.content.entity.ContentVideoStatDaily;
+import com.nuaa.video.content.service.VideoStatService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 视频统计控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/content/stat")
+@RequiredArgsConstructor
+public class VideoStatController {
+
+ private final VideoStatService videoStatService;
+
+ /**
+ * 获取视频每日统计
+ */
+ @GetMapping("/daily/{videoId}")
+ public Result> getVideoStatDaily(
+ @PathVariable Long videoId,
+ @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
+ @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
+ return videoStatService.getVideoStatDaily(videoId, startDate, endDate);
+ }
+
+ /**
+ * 获取视频播放趋势(按小时)
+ */
+ @GetMapping("/play/trend/{videoId}")
+ public Result