feat(content): Implement content module foundation
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<List<CategoryVO>> getCategoryTree() {
|
||||
return categoryService.getCategoryTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<List<CategoryVO>> getCategoryList() {
|
||||
return categoryService.getCategoryList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分类
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Void> 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<Void> updateCategory(@RequestBody ContentCategory category, HttpServletRequest request) {
|
||||
Long userId = getUserIdFromRequest(request);
|
||||
category.setUpdatedBy(userId.toString());
|
||||
return categoryService.updateCategory(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Long> 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<List<DanmakuVO>> getVideoTimeDanmaku(
|
||||
@RequestParam Long videoId,
|
||||
@RequestParam Float startTime,
|
||||
@RequestParam Float endTime) {
|
||||
return danmakuService.getVideoTimeDanmaku(videoId, startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频所有弹幕
|
||||
*/
|
||||
@GetMapping("/all/{videoId}")
|
||||
public Result<List<DanmakuVO>> 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;
|
||||
}
|
||||
}
|
||||
+107
@@ -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<List<FavoriteVO>> getUserFavorites(HttpServletRequest request) {
|
||||
Long userId = getUserIdFromRequest(request);
|
||||
if (userId == null) {
|
||||
return Result.error("未获取到用户ID");
|
||||
}
|
||||
return favoriteService.getUserFavorites(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建收藏夹
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Long> 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<Void> 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<Void> 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<Page<VideoListVO>> 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<ContentVideo> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<List<TagVO>> getTagList() {
|
||||
return tagService.getTagList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取标签列表
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public Result<Page<TagVO>> getTagPage(
|
||||
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
Page<ContentTag> page = new Page<>(pageNum, pageSize);
|
||||
return tagService.getTagPage(page, keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加标签
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Void> 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<Void> updateTag(@RequestBody ContentTag tag, HttpServletRequest request) {
|
||||
Long userId = getUserIdFromRequest(request);
|
||||
tag.setUpdatedBy(userId.toString());
|
||||
return tagService.updateTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除标签
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteTag(@PathVariable Long id) {
|
||||
return tagService.deleteTag(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频标签
|
||||
*/
|
||||
@GetMapping("/video/{videoId}")
|
||||
public Result<List<TagVO>> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<UploadResultVO> 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<Long> 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<VideoInfoVO> getVideoInfo(@PathVariable Long id, HttpServletRequest request) {
|
||||
Long userId = getUserIdFromRequest(request);
|
||||
return videoService.getVideoInfo(id, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<Page<VideoListVO>> getVideoList(
|
||||
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
VideoQueryDTO queryDTO,
|
||||
HttpServletRequest request) {
|
||||
Page<ContentVideo> page = new Page<>(pageNum, pageSize);
|
||||
Long userId = getUserIdFromRequest(request);
|
||||
return videoService.getVideoList(page, queryDTO, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新视频信息
|
||||
*/
|
||||
@PutMapping
|
||||
public Result<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Page<VideoListVO>> getUserVideos(
|
||||
@PathVariable Long userId,
|
||||
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
Page<ContentVideo> page = new Page<>(pageNum, pageSize);
|
||||
Long currentUserId = getUserIdFromRequest(request);
|
||||
return videoService.getUserVideos(userId, page, currentUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频审核
|
||||
*/
|
||||
@PostMapping("/review/{videoId}")
|
||||
public Result<Void> 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;
|
||||
}
|
||||
}
|
||||
+57
@@ -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<List<ContentVideoStatDaily>> 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<Map<Integer, Long>> getVideoPlayTrend(
|
||||
@PathVariable Long videoId,
|
||||
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
|
||||
if (date == null) {
|
||||
date = LocalDate.now();
|
||||
}
|
||||
return videoStatService.getVideoPlayTrend(videoId, date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频互动统计
|
||||
*/
|
||||
@GetMapping("/interaction/{videoId}")
|
||||
public Result<Map<String, Object>> getVideoInteractionStats(@PathVariable Long videoId) {
|
||||
return videoStatService.getVideoInteractionStats(videoId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.nuaa.video.content.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 弹幕DTO
|
||||
*/
|
||||
@Data
|
||||
public class DanmakuDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
@NotNull(message = "视频ID不能为空")
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 弹幕内容
|
||||
*/
|
||||
@NotBlank(message = "弹幕内容不能为空")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 弹幕颜色
|
||||
*/
|
||||
private String color = "#FFFFFF";
|
||||
|
||||
/**
|
||||
* 字体大小
|
||||
*/
|
||||
private Integer fontSize = 25;
|
||||
|
||||
/**
|
||||
* 位置(0:滚动,1:顶部,2:底部)
|
||||
*/
|
||||
private Integer position = 0;
|
||||
|
||||
/**
|
||||
* 发送时间(秒)
|
||||
*/
|
||||
@NotNull(message = "发送时间不能为空")
|
||||
private Float time;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.nuaa.video.content.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 收藏DTO
|
||||
*/
|
||||
@Data
|
||||
public class FavoriteDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
@NotNull(message = "视频ID不能为空")
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 收藏夹ID
|
||||
*/
|
||||
@NotNull(message = "收藏夹ID不能为空")
|
||||
private Long favoriteId;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.nuaa.video.content.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 播放记录DTO
|
||||
*/
|
||||
@Data
|
||||
public class PlayRecordDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
@NotNull(message = "视频ID不能为空")
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 播放时长(秒)
|
||||
*/
|
||||
@NotNull(message = "播放时长不能为空")
|
||||
private Integer playTime;
|
||||
|
||||
/**
|
||||
* 是否看完(0:未看完,1:已看完)
|
||||
*/
|
||||
private Integer isFinished;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.nuaa.video.content.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频查询DTO
|
||||
*/
|
||||
@Data
|
||||
public class VideoQueryDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 关键词
|
||||
*/
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 标签ID列表
|
||||
*/
|
||||
private List<Long> tagIds;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
private String orderBy;
|
||||
|
||||
/**
|
||||
* 排序方向
|
||||
*/
|
||||
private String orderDirection;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 是否推荐
|
||||
*/
|
||||
private Integer isRecommend;
|
||||
|
||||
/**
|
||||
* 是否置顶
|
||||
*/
|
||||
private Integer isTop;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页记录数
|
||||
*/
|
||||
private Integer pageSize = 10;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.nuaa.video.content.dto;
|
||||
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频更新DTO
|
||||
*/
|
||||
@Data
|
||||
public class VideoUpdateDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频标题
|
||||
*/
|
||||
@Size(min = 5, max = 100, message = "视频标题长度必须在5-100之间")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 视频描述
|
||||
*/
|
||||
@Size(max = 1000, message = "视频描述长度不能超过1000")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 标签ID列表
|
||||
*/
|
||||
private List<Long> tagIds;
|
||||
|
||||
/**
|
||||
* 封面URL
|
||||
*/
|
||||
private String coverUrl;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.nuaa.video.content.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频上传DTO
|
||||
*/
|
||||
@Data
|
||||
public class VideoUploadDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频标题
|
||||
*/
|
||||
@NotBlank(message = "视频标题不能为空")
|
||||
@Size(min = 5, max = 100, message = "视频标题长度必须在5-100之间")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 视频描述
|
||||
*/
|
||||
@Size(max = 1000, message = "视频描述长度不能超过1000")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
@NotNull(message = "分类ID不能为空")
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 标签ID列表
|
||||
*/
|
||||
private List<Long> tagIds;
|
||||
|
||||
/**
|
||||
* 封面URL
|
||||
*/
|
||||
private String coverUrl;
|
||||
|
||||
/**
|
||||
* 视频URL
|
||||
*/
|
||||
private String videoUrl;
|
||||
|
||||
/**
|
||||
* 视频时长(秒)
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 视频宽度
|
||||
*/
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 视频高度
|
||||
*/
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
* 视频格式
|
||||
*/
|
||||
private String format;
|
||||
|
||||
/**
|
||||
* 视频大小(字节)
|
||||
*/
|
||||
private Long size;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频分类实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_category")
|
||||
public class ContentCategory implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 分类编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 父分类ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 分类层级
|
||||
*/
|
||||
private Integer level;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 状态(0:禁用,1:正常)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 弹幕实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_danmaku")
|
||||
public class ContentDanmaku implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 弹幕ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 弹幕内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 弹幕颜色
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 字体大小
|
||||
*/
|
||||
private Integer fontSize;
|
||||
|
||||
/**
|
||||
* 位置(0:滚动,1:顶部,2:底部)
|
||||
*/
|
||||
private Integer position;
|
||||
|
||||
/**
|
||||
* 发送时间(秒)
|
||||
*/
|
||||
private Float time;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频标签实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_tag")
|
||||
public class ContentTag implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标签名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户收藏夹实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_user_favorite")
|
||||
public class ContentUserFavorite implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 收藏夹ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 收藏夹名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 收藏夹描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否私有(0:公开,1:私有)
|
||||
*/
|
||||
private Integer isPrivate;
|
||||
|
||||
/**
|
||||
* 视频数量
|
||||
*/
|
||||
private Integer videoCount;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频信息实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video")
|
||||
public class ContentVideo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 视频标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 视频描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 封面URL
|
||||
*/
|
||||
private String coverUrl;
|
||||
|
||||
/**
|
||||
* 视频URL
|
||||
*/
|
||||
private String videoUrl;
|
||||
|
||||
/**
|
||||
* 视频时长(秒)
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 视频宽度
|
||||
*/
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 视频高度
|
||||
*/
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
* 视频格式
|
||||
*/
|
||||
private String format;
|
||||
|
||||
/**
|
||||
* 视频大小(字节)
|
||||
*/
|
||||
private Long size;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 状态(0:审核中,1:已发布,2:已下架,3:审核不通过)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 投币数
|
||||
*/
|
||||
private Integer coinCount;
|
||||
|
||||
/**
|
||||
* 收藏数
|
||||
*/
|
||||
private Integer favoriteCount;
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*/
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 播放数
|
||||
*/
|
||||
private Long viewCount;
|
||||
|
||||
/**
|
||||
* 分享数
|
||||
*/
|
||||
private Integer shareCount;
|
||||
|
||||
/**
|
||||
* 弹幕数
|
||||
*/
|
||||
private Integer danmakuCount;
|
||||
|
||||
/**
|
||||
* 是否推荐(0:否,1:是)
|
||||
*/
|
||||
private Integer isRecommend;
|
||||
|
||||
/**
|
||||
* 是否置顶(0:否,1:是)
|
||||
*/
|
||||
private Integer isTop;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 标签ID列表(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> tagIds;
|
||||
|
||||
/**
|
||||
* 标签名称列表(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<String> tagNames;
|
||||
|
||||
/**
|
||||
* 分类名称(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String categoryName;
|
||||
|
||||
/**
|
||||
* 用户昵称(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String userNickname;
|
||||
|
||||
/**
|
||||
* 用户头像(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 是否已点赞(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean isLiked;
|
||||
|
||||
/**
|
||||
* 是否已收藏(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean isFavorited;
|
||||
|
||||
/**
|
||||
* 是否已投币(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean isCoined;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频投币实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video_coin")
|
||||
public class ContentVideoCoin implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 投币数量
|
||||
*/
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频收藏实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video_favorite")
|
||||
public class ContentVideoFavorite implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 收藏夹ID
|
||||
*/
|
||||
private Long favoriteId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频点赞实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video_like")
|
||||
public class ContentVideoLike implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频播放记录实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video_play_record")
|
||||
public class ContentVideoPlayRecord implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 播放时长(秒)
|
||||
*/
|
||||
private Integer playTime;
|
||||
|
||||
/**
|
||||
* 是否看完(0:未看完,1:已看完)
|
||||
*/
|
||||
private Integer isFinished;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频审核记录实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video_review")
|
||||
public class ContentVideoReview implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 审核员ID
|
||||
*/
|
||||
private Long reviewerId;
|
||||
|
||||
/**
|
||||
* 审核状态(1:通过,2:不通过)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 审核原因
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频每日统计实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video_stat_daily")
|
||||
public class ContentVideoStatDaily implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 统计日期
|
||||
*/
|
||||
private LocalDate statDate;
|
||||
|
||||
/**
|
||||
* 新增点赞数
|
||||
*/
|
||||
private Integer likeIncrement;
|
||||
|
||||
/**
|
||||
* 新增投币数
|
||||
*/
|
||||
private Integer coinIncrement;
|
||||
|
||||
/**
|
||||
* 新增收藏数
|
||||
*/
|
||||
private Integer favoriteIncrement;
|
||||
|
||||
/**
|
||||
* 新增评论数
|
||||
*/
|
||||
private Integer commentIncrement;
|
||||
|
||||
/**
|
||||
* 新增播放数
|
||||
*/
|
||||
private Long viewIncrement;
|
||||
|
||||
/**
|
||||
* 新增分享数
|
||||
*/
|
||||
private Integer shareIncrement;
|
||||
|
||||
/**
|
||||
* 新增弹幕数
|
||||
*/
|
||||
private Integer danmakuIncrement;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.content.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频标签关联实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("content_video_tag")
|
||||
public class ContentVideoTag implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
private Long tagId;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.nuaa.video.content.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 弹幕位置枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum DanmakuPositionEnum {
|
||||
|
||||
ROLLING(0, "滚动"),
|
||||
TOP(1, "顶部"),
|
||||
BOTTOM(2, "底部");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
|
||||
DanmakuPositionEnum(Integer code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static DanmakuPositionEnum getByCode(Integer code) {
|
||||
for (DanmakuPositionEnum positionEnum : values()) {
|
||||
if (positionEnum.getCode().equals(code)) {
|
||||
return positionEnum;
|
||||
}
|
||||
}
|
||||
return ROLLING; // 默认滚动
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.nuaa.video.content.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 视频状态枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum VideoStatusEnum {
|
||||
|
||||
REVIEWING(0, "审核中"),
|
||||
PUBLISHED(1, "已发布"),
|
||||
DISABLED(2, "已下架"),
|
||||
REJECTED(3, "审核不通过");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
|
||||
VideoStatusEnum(Integer code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static VideoStatusEnum getByCode(Integer code) {
|
||||
for (VideoStatusEnum statusEnum : values()) {
|
||||
if (statusEnum.getCode().equals(code)) {
|
||||
return statusEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.nuaa.video.content.feign;
|
||||
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
/**
|
||||
* 用户服务Feign客户端
|
||||
*/
|
||||
@FeignClient(name = "video-user", fallbackFactory = UserFeignClientFallbackFactory.class)
|
||||
public interface UserFeignClient {
|
||||
|
||||
/**
|
||||
* 获取用户简要信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 用户信息
|
||||
*/
|
||||
@GetMapping("/user/profile/public/{userId}")
|
||||
Result<Object> getUserProfile(@PathVariable("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 增加用户视频数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param count 增加数量
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/user/profile/count/video/{userId}/{count}")
|
||||
Result<Void> increaseVideoCount(@PathVariable("userId") Long userId,
|
||||
@PathVariable("count") Integer count,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 增加用户获赞数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param count 增加数量
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/user/profile/count/like/{userId}/{count}")
|
||||
Result<Void> increaseLikeCount(@PathVariable("userId") Long userId,
|
||||
@PathVariable("count") Integer count,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 增加用户收藏数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param count 增加数量
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/user/profile/count/favorite/{userId}/{count}")
|
||||
Result<Void> increaseFavoriteCount(@PathVariable("userId") Long userId,
|
||||
@PathVariable("count") Integer count,
|
||||
@RequestHeader("Authorization") String token);
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.content.feign;
|
||||
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 用户服务Feign客户端降级工厂
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
|
||||
|
||||
@Override
|
||||
public UserFeignClient create(Throwable cause) {
|
||||
log.error("用户服务调用失败:", cause);
|
||||
return new UserFeignClient() {
|
||||
@Override
|
||||
public Result<Object> getUserProfile(Long userId) {
|
||||
log.error("获取用户信息失败,用户ID:{}", userId);
|
||||
return Result.error("获取用户信息失败,请稍后重试");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> increaseVideoCount(Long userId, Integer count, String token) {
|
||||
log.error("增加用户视频数量失败,用户ID:{}, 数量:{}", userId, count);
|
||||
return Result.error("更新用户视频数量失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> increaseLikeCount(Long userId, Integer count, String token) {
|
||||
log.error("增加用户获赞数量失败,用户ID:{}, 数量:{}", userId, count);
|
||||
return Result.error("更新用户获赞数量失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> increaseFavoriteCount(Long userId, Integer count, String token) {
|
||||
log.error("增加用户收藏数量失败,用户ID:{}, 数量:{}", userId, count);
|
||||
return Result.error("更新用户收藏数量失败");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.nuaa.video.content.filter;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JWT认证过滤器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Value("${jwt.secret:video-platform-secret-key-2024-nuaa}")
|
||||
private String secret;
|
||||
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
// 从请求头中获取JWT令牌
|
||||
String token = getJwtFromRequest(request);
|
||||
|
||||
// 如果令牌存在,则本地验证
|
||||
if (StringUtils.hasText(token)) {
|
||||
try {
|
||||
// 验证令牌
|
||||
Claims claims = Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
|
||||
// 从令牌中提取用户ID和用户名
|
||||
Long userId = Long.parseLong(claims.getSubject());
|
||||
String username = claims.get("username", String.class);
|
||||
|
||||
// 设置用户权限
|
||||
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
|
||||
// 创建认证对象
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(username, null, authorities);
|
||||
|
||||
// 设置额外信息
|
||||
authentication.setDetails(userId);
|
||||
|
||||
// 设置认证信息到安全上下文
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
// 在请求属性中存储用户ID
|
||||
request.setAttribute("userId", userId);
|
||||
request.setAttribute("username", username);
|
||||
|
||||
log.debug("认证成功: 用户ID={}, 用户名={}", userId, username);
|
||||
} catch (Exception e) {
|
||||
log.error("JWT验证失败: {}", e.getMessage());
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中提取JWT令牌
|
||||
*/
|
||||
private String getJwtFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
|
||||
return bearerToken.substring(TOKEN_PREFIX.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名密钥
|
||||
*/
|
||||
private SecretKey getSigningKey() {
|
||||
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentCategory;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 视频分类Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentCategoryMapper extends BaseMapper<ContentCategory> {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentDanmaku;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 弹幕Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentDanmakuMapper extends BaseMapper<ContentDanmaku> {
|
||||
|
||||
/**
|
||||
* 获取视频指定时间段的弹幕
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param startTime 开始时间(秒)
|
||||
* @param endTime 结束时间(秒)
|
||||
* @return 弹幕列表
|
||||
*/
|
||||
List<ContentDanmaku> getVideoTimeDanmaku(
|
||||
@Param("videoId") Long videoId,
|
||||
@Param("startTime") Float startTime,
|
||||
@Param("endTime") Float endTime);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentTag;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频标签Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentTagMapper extends BaseMapper<ContentTag> {
|
||||
|
||||
/**
|
||||
* 获取视频的标签
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @return 标签列表
|
||||
*/
|
||||
List<ContentTag> getTagsByVideoId(@Param("videoId") Long videoId);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentUserFavorite;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 用户收藏夹Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentUserFavoriteMapper extends BaseMapper<ContentUserFavorite> {
|
||||
|
||||
/**
|
||||
* 增加收藏夹视频数量
|
||||
*
|
||||
* @param favoriteId 收藏夹ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseVideoCount(@Param("favoriteId") Long favoriteId, @Param("count") int count);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoCoin;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 视频投币Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoCoinMapper extends BaseMapper<ContentVideoCoin> {
|
||||
|
||||
/**
|
||||
* 获取用户对视频的投币数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param videoId 视频ID
|
||||
* @return 投币数量
|
||||
*/
|
||||
int getUserCoinCount(@Param("userId") Long userId, @Param("videoId") Long videoId);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoFavorite;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 视频收藏Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoFavoriteMapper extends BaseMapper<ContentVideoFavorite> {
|
||||
|
||||
/**
|
||||
* 检查用户是否已收藏视频
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param videoId 视频ID
|
||||
* @return 是否已收藏
|
||||
*/
|
||||
int checkUserFavorited(@Param("userId") Long userId, @Param("videoId") Long videoId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoLike;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 视频点赞Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoLikeMapper extends BaseMapper<ContentVideoLike> {
|
||||
|
||||
/**
|
||||
* 检查用户是否已点赞视频
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param videoId 视频ID
|
||||
* @return 是否已点赞
|
||||
*/
|
||||
int checkUserLiked(@Param("userId") Long userId, @Param("videoId") Long videoId);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.content.dto.VideoQueryDTO;
|
||||
import com.nuaa.video.content.entity.ContentVideo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 视频信息Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoMapper extends BaseMapper<ContentVideo> {
|
||||
|
||||
/**
|
||||
* 分页查询视频列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @return 视频列表
|
||||
*/
|
||||
Page<ContentVideo> selectVideoList(Page<ContentVideo> page, @Param("query") VideoQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 增加点赞数
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseLikeCount(@Param("videoId") Long videoId, @Param("count") int count);
|
||||
|
||||
/**
|
||||
* 增加投币数
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseCoinCount(@Param("videoId") Long videoId, @Param("count") int count);
|
||||
|
||||
/**
|
||||
* 增加收藏数
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseFavoriteCount(@Param("videoId") Long videoId, @Param("count") int count);
|
||||
|
||||
/**
|
||||
* 增加评论数
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseCommentCount(@Param("videoId") Long videoId, @Param("count") int count);
|
||||
|
||||
/**
|
||||
* 增加播放数
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseViewCount(@Param("videoId") Long videoId, @Param("count") long count);
|
||||
|
||||
/**
|
||||
* 增加分享数
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseShareCount(@Param("videoId") Long videoId, @Param("count") int count);
|
||||
|
||||
/**
|
||||
* 增加弹幕数
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param count 增加数量
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseDanmakuCount(@Param("videoId") Long videoId, @Param("count") int count);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoPlayRecord;
|
||||
import org.apache.ibatis.annotations.MapKey;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 视频播放记录Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoPlayRecordMapper extends BaseMapper<ContentVideoPlayRecord> {
|
||||
|
||||
/**
|
||||
* 获取用户播放过的视频ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param limit 限制数量
|
||||
* @return 视频ID列表
|
||||
*/
|
||||
List<Long> selectUserPlayedVideoIds(@Param("userId") Long userId, @Param("limit") Integer limit);
|
||||
|
||||
/**
|
||||
* 统计按小时的播放量
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param date 日期
|
||||
* @return 每小时播放量
|
||||
*/
|
||||
@MapKey("hour")
|
||||
List<Map<String, Object>> countHourlyPlays(@Param("videoId") Long videoId, @Param("date") LocalDate date);
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoReview;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频审核记录Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoReviewMapper extends BaseMapper<ContentVideoReview> {
|
||||
|
||||
/**
|
||||
* 查询视频最新审核记录
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @return 审核记录
|
||||
*/
|
||||
ContentVideoReview selectLatestReview(@Param("videoId") Long videoId);
|
||||
|
||||
/**
|
||||
* 根据审核状态查询视频审核记录
|
||||
*
|
||||
* @param status 审核状态
|
||||
* @param reviewerId 审核员ID(可为null)
|
||||
* @return 审核记录列表
|
||||
*/
|
||||
List<ContentVideoReview> selectReviewsByStatus(@Param("status") Integer status, @Param("reviewerId") Long reviewerId);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoStatDaily;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 视频每日统计Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoStatDailyMapper extends BaseMapper<ContentVideoStatDaily> {
|
||||
|
||||
/**
|
||||
* 获取视频日期范围内的统计数据
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 统计数据列表
|
||||
*/
|
||||
List<ContentVideoStatDaily> selectStatsByDateRange(
|
||||
@Param("videoId") Long videoId,
|
||||
@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate);
|
||||
|
||||
/**
|
||||
* 获取视频总互动统计
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @return 互动统计信息
|
||||
*/
|
||||
Map<String, Object> selectVideoInteractionStats(@Param("videoId") Long videoId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.nuaa.video.content.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoTag;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频标签关联Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ContentVideoTagMapper extends BaseMapper<ContentVideoTag> {
|
||||
|
||||
/**
|
||||
* 批量插入视频标签关联
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param tagIds 标签ID列表
|
||||
* @param createdBy 创建人
|
||||
* @return 影响行数
|
||||
*/
|
||||
int batchInsert(@Param("videoId") Long videoId, @Param("tagIds") List<Long> tagIds, @Param("createdBy") String createdBy);
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
package com.nuaa.video.content.mq.consumer;
|
||||
|
||||
import com.nuaa.video.content.entity.ContentVideo;
|
||||
import com.nuaa.video.content.entity.ContentVideoStatDaily;
|
||||
import com.nuaa.video.content.mapper.ContentVideoMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoStatDailyMapper;
|
||||
import com.nuaa.video.content.mq.producer.VideoMessageProducer;
|
||||
import com.nuaa.video.content.service.VideoStatService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.annotation.ConsumeMode;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 视频消息消费者
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class VideoMessageConsumer {
|
||||
|
||||
private final ContentVideoMapper videoMapper;
|
||||
private final ContentVideoStatDailyMapper statDailyMapper;
|
||||
private final VideoStatService videoStatService;
|
||||
|
||||
/**
|
||||
* 视频统计消息监听器
|
||||
*/
|
||||
@Component
|
||||
@RocketMQMessageListener(
|
||||
topic = "video-stats-topic",
|
||||
consumerGroup = "video-stats-consumer-group",
|
||||
consumeMode = ConsumeMode.CONCURRENTLY
|
||||
)
|
||||
@RequiredArgsConstructor
|
||||
class VideoStatsListener implements RocketMQListener<VideoMessageProducer.VideoStatsMessage> {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void onMessage(VideoMessageProducer.VideoStatsMessage message) {
|
||||
try {
|
||||
log.info("接收到视频统计消息: {}", message);
|
||||
|
||||
Long videoId = message.getVideoId();
|
||||
String action = message.getAction();
|
||||
Integer count = message.getCount() == null ? 1 : message.getCount();
|
||||
|
||||
// 更新视频统计数据
|
||||
switch (action) {
|
||||
case "play":
|
||||
videoMapper.increaseViewCount(videoId, count);
|
||||
break;
|
||||
case "like":
|
||||
videoMapper.increaseLikeCount(videoId, count);
|
||||
break;
|
||||
case "favorite":
|
||||
videoMapper.increaseFavoriteCount(videoId, count);
|
||||
break;
|
||||
case "coin":
|
||||
videoMapper.increaseCoinCount(videoId, count);
|
||||
break;
|
||||
case "share":
|
||||
videoMapper.increaseShareCount(videoId, count);
|
||||
break;
|
||||
case "danmaku":
|
||||
videoMapper.increaseDanmakuCount(videoId, count);
|
||||
break;
|
||||
case "comment":
|
||||
videoMapper.increaseCommentCount(videoId, count);
|
||||
break;
|
||||
default:
|
||||
log.warn("未知的统计动作: {}", action);
|
||||
}
|
||||
|
||||
// 更新每日统计数据
|
||||
updateDailyStats(videoId, action, count);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理视频统计消息失败", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新每日统计数据
|
||||
*/
|
||||
private void updateDailyStats(Long videoId, String action, int count) {
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
// 查询今日统计记录
|
||||
ContentVideoStatDaily statDaily = statDailyMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ContentVideoStatDaily>()
|
||||
.eq(ContentVideoStatDaily::getVideoId, videoId)
|
||||
.eq(ContentVideoStatDaily::getStatDate, today)
|
||||
);
|
||||
|
||||
// 如果不存在,创建新记录
|
||||
if (statDaily == null) {
|
||||
statDaily = new ContentVideoStatDaily();
|
||||
statDaily.setVideoId(videoId);
|
||||
statDaily.setStatDate(today);
|
||||
statDaily.setLikeIncrement(0);
|
||||
statDaily.setCoinIncrement(0);
|
||||
statDaily.setFavoriteIncrement(0);
|
||||
statDaily.setCommentIncrement(0);
|
||||
statDaily.setViewIncrement(0L);
|
||||
statDaily.setShareIncrement(0);
|
||||
statDaily.setDanmakuIncrement(0);
|
||||
statDaily.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
switch (action) {
|
||||
case "play":
|
||||
statDaily.setViewIncrement(statDaily.getViewIncrement() + count);
|
||||
break;
|
||||
case "like":
|
||||
statDaily.setLikeIncrement(statDaily.getLikeIncrement() + count);
|
||||
break;
|
||||
case "favorite":
|
||||
statDaily.setFavoriteIncrement(statDaily.getFavoriteIncrement() + count);
|
||||
break;
|
||||
case "coin":
|
||||
statDaily.setCoinIncrement(statDaily.getCoinIncrement() + count);
|
||||
break;
|
||||
case "share":
|
||||
statDaily.setShareIncrement(statDaily.getShareIncrement() + count);
|
||||
break;
|
||||
case "danmaku":
|
||||
statDaily.setDanmakuIncrement(statDaily.getDanmakuIncrement() + count);
|
||||
break;
|
||||
case "comment":
|
||||
statDaily.setCommentIncrement(statDaily.getCommentIncrement() + count);
|
||||
break;
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
if (statDaily.getId() == null) {
|
||||
statDailyMapper.insert(statDaily);
|
||||
} else {
|
||||
statDailyMapper.updateById(statDaily);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频处理消息监听器
|
||||
*/
|
||||
@Component
|
||||
@RocketMQMessageListener(
|
||||
topic = "video-process-topic",
|
||||
consumerGroup = "video-process-consumer-group",
|
||||
consumeMode = ConsumeMode.CONCURRENTLY
|
||||
)
|
||||
@RequiredArgsConstructor
|
||||
class VideoProcessListener implements RocketMQListener<Long> {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void onMessage(Long videoId) {
|
||||
try {
|
||||
log.info("接收到视频处理消息: videoId={}", videoId);
|
||||
|
||||
// 查询视频信息
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
log.error("视频不存在: videoId={}", videoId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 视频处理逻辑(实际中可能涉及转码、视频分析等)
|
||||
log.info("处理视频: videoId={}", videoId);
|
||||
|
||||
// 更新视频状态为待审核
|
||||
video.setStatus(0); // 待审核
|
||||
videoMapper.updateById(video);
|
||||
|
||||
// 发送审核消息
|
||||
VideoMessageProducer videoMessageProducer = new VideoMessageProducer(null);
|
||||
videoMessageProducer.sendVideoReviewMessage(videoId);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理视频处理消息失败", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频审核消息监听器(自动审核)
|
||||
*/
|
||||
@Component
|
||||
@RocketMQMessageListener(
|
||||
topic = "video-review-topic",
|
||||
consumerGroup = "video-review-consumer-group",
|
||||
consumeMode = ConsumeMode.CONCURRENTLY
|
||||
)
|
||||
@RequiredArgsConstructor
|
||||
class VideoReviewListener implements RocketMQListener<Long> {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void onMessage(Long videoId) {
|
||||
try {
|
||||
log.info("接收到视频审核消息: videoId={}", videoId);
|
||||
|
||||
// 查询视频信息
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
log.error("视频不存在: videoId={}", videoId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 自动审核逻辑(实际中可能涉及内容识别、AI审核等)
|
||||
// 这里简化为直接通过
|
||||
log.info("审核视频: videoId={}", videoId);
|
||||
|
||||
// 更新视频状态为已发布
|
||||
video.setStatus(1); // 已发布
|
||||
video.setPublishTime(LocalDateTime.now());
|
||||
videoMapper.updateById(video);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理视频审核消息失败", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.nuaa.video.content.mq.producer;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 视频消息生产者
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class VideoMessageProducer {
|
||||
|
||||
private final RocketMQTemplate rocketMQTemplate;
|
||||
|
||||
private static final String VIDEO_UPLOAD_TOPIC = "video-upload-topic";
|
||||
private static final String VIDEO_PROCESS_TOPIC = "video-process-topic";
|
||||
private static final String VIDEO_REVIEW_TOPIC = "video-review-topic";
|
||||
private static final String VIDEO_STATS_TOPIC = "video-stats-topic";
|
||||
|
||||
/**
|
||||
* 发送视频上传消息
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
*/
|
||||
public void sendVideoUploadMessage(Long videoId) {
|
||||
log.info("发送视频上传消息: videoId={}", videoId);
|
||||
rocketMQTemplate.convertAndSend(VIDEO_UPLOAD_TOPIC, videoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送视频处理消息
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
*/
|
||||
public void sendVideoProcessMessage(Long videoId) {
|
||||
log.info("发送视频处理消息: videoId={}", videoId);
|
||||
rocketMQTemplate.convertAndSend(VIDEO_PROCESS_TOPIC, videoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送视频审核消息
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
*/
|
||||
public void sendVideoReviewMessage(Long videoId) {
|
||||
log.info("发送视频审核消息: videoId={}", videoId);
|
||||
rocketMQTemplate.convertAndSend(VIDEO_REVIEW_TOPIC, videoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送视频播放统计消息
|
||||
*
|
||||
* @param message 统计消息
|
||||
*/
|
||||
public void sendVideoStatsMessage(VideoStatsMessage message) {
|
||||
log.info("发送视频统计消息: videoId={}, userId={}, action={}",
|
||||
message.getVideoId(), message.getUserId(), message.getAction());
|
||||
rocketMQTemplate.send(VIDEO_STATS_TOPIC, MessageBuilder.withPayload(message).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频统计消息
|
||||
*/
|
||||
@lombok.Data
|
||||
public static class VideoStatsMessage {
|
||||
private Long videoId;
|
||||
private Long userId;
|
||||
private String action; // play, like, favorite, coin, share, danmaku
|
||||
private Integer count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.nuaa.video.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.entity.ContentCategory;
|
||||
import com.nuaa.video.content.vo.CategoryVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分类服务接口
|
||||
*/
|
||||
public interface CategoryService extends IService<ContentCategory> {
|
||||
|
||||
/**
|
||||
* 获取分类树
|
||||
*
|
||||
* @return 分类树
|
||||
*/
|
||||
Result<List<CategoryVO>> getCategoryTree();
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*
|
||||
* @return 分类列表
|
||||
*/
|
||||
Result<List<CategoryVO>> getCategoryList();
|
||||
|
||||
/**
|
||||
* 添加分类
|
||||
*
|
||||
* @param category 分类信息
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> addCategory(ContentCategory category);
|
||||
|
||||
/**
|
||||
* 更新分类
|
||||
*
|
||||
* @param category 分类信息
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> updateCategory(ContentCategory category);
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*
|
||||
* @param id 分类ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> deleteCategory(Long id);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.nuaa.video.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.dto.DanmakuDTO;
|
||||
import com.nuaa.video.content.entity.ContentDanmaku;
|
||||
import com.nuaa.video.content.vo.DanmakuVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 弹幕服务接口
|
||||
*/
|
||||
public interface DanmakuService extends IService<ContentDanmaku> {
|
||||
|
||||
/**
|
||||
* 发送弹幕
|
||||
*
|
||||
* @param danmakuDTO 弹幕信息
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Long> sendDanmaku(DanmakuDTO danmakuDTO, Long userId);
|
||||
|
||||
/**
|
||||
* 获取视频指定时间段的弹幕
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param startTime 开始时间(秒)
|
||||
* @param endTime 结束时间(秒)
|
||||
* @return 弹幕列表
|
||||
*/
|
||||
Result<List<DanmakuVO>> getVideoTimeDanmaku(Long videoId, Float startTime, Float endTime);
|
||||
|
||||
/**
|
||||
* 获取视频所有弹幕
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @return 弹幕列表
|
||||
*/
|
||||
Result<List<DanmakuVO>> getVideoAllDanmaku(Long videoId);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.nuaa.video.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
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.vo.FavoriteVO;
|
||||
import com.nuaa.video.content.vo.VideoListVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 收藏夹服务接口
|
||||
*/
|
||||
public interface FavoriteService extends IService<ContentUserFavorite> {
|
||||
|
||||
/**
|
||||
* 获取用户收藏夹列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 收藏夹列表
|
||||
*/
|
||||
Result<List<FavoriteVO>> getUserFavorites(Long userId);
|
||||
|
||||
/**
|
||||
* 创建收藏夹
|
||||
*
|
||||
* @param favorite 收藏夹信息
|
||||
* @param userId 用户ID
|
||||
* @return 收藏夹ID
|
||||
*/
|
||||
Result<Long> createFavorite(ContentUserFavorite favorite, Long userId);
|
||||
|
||||
/**
|
||||
* 更新收藏夹
|
||||
*
|
||||
* @param favorite 收藏夹信息
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> updateFavorite(ContentUserFavorite favorite, Long userId);
|
||||
|
||||
/**
|
||||
* 删除收藏夹
|
||||
*
|
||||
* @param id 收藏夹ID
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> deleteFavorite(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 获取收藏夹中的视频
|
||||
*
|
||||
* @param favoriteId 收藏夹ID
|
||||
* @param page 分页参数
|
||||
* @param userId 用户ID
|
||||
* @return 视频列表
|
||||
*/
|
||||
Result<Page<VideoListVO>> getFavoriteVideos(Long favoriteId, Page<ContentVideo> page, Long userId);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.nuaa.video.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.entity.ContentTag;
|
||||
import com.nuaa.video.content.vo.TagVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 标签服务接口
|
||||
*/
|
||||
public interface TagService extends IService<ContentTag> {
|
||||
|
||||
/**
|
||||
* 获取标签列表
|
||||
*
|
||||
* @return 标签列表
|
||||
*/
|
||||
Result<List<TagVO>> getTagList();
|
||||
|
||||
/**
|
||||
* 分页获取标签列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param keyword 关键词
|
||||
* @return 分页标签列表
|
||||
*/
|
||||
Result<Page<TagVO>> getTagPage(Page<ContentTag> page, String keyword);
|
||||
|
||||
/**
|
||||
* 添加标签
|
||||
*
|
||||
* @param tag 标签信息
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> addTag(ContentTag tag);
|
||||
|
||||
/**
|
||||
* 更新标签
|
||||
*
|
||||
* @param tag 标签信息
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> updateTag(ContentTag tag);
|
||||
|
||||
/**
|
||||
* 删除标签
|
||||
*
|
||||
* @param id 标签ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> deleteTag(Long id);
|
||||
|
||||
/**
|
||||
* 获取视频的标签
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @return 标签列表
|
||||
*/
|
||||
Result<List<TagVO>> getVideoTags(Long videoId);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.nuaa.video.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
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.vo.UploadResultVO;
|
||||
import com.nuaa.video.content.vo.VideoInfoVO;
|
||||
import com.nuaa.video.content.vo.VideoListVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 视频服务接口
|
||||
*/
|
||||
public interface VideoService extends IService<ContentVideo> {
|
||||
|
||||
/**
|
||||
* 上传视频
|
||||
*
|
||||
* @param videoFile 视频文件
|
||||
* @param userId 用户ID
|
||||
* @return 上传结果
|
||||
*/
|
||||
Result<UploadResultVO> uploadVideo(MultipartFile videoFile, Long userId);
|
||||
|
||||
/**
|
||||
* 创建视频
|
||||
*
|
||||
* @param uploadDTO 视频信息DTO
|
||||
* @param userId 用户ID
|
||||
* @return 视频ID
|
||||
*/
|
||||
Result<Long> createVideo(VideoUploadDTO uploadDTO, Long userId);
|
||||
|
||||
/**
|
||||
* 获取视频详情
|
||||
*
|
||||
* @param id 视频ID
|
||||
* @param userId 当前用户ID
|
||||
* @return 视频详情
|
||||
*/
|
||||
Result<VideoInfoVO> getVideoInfo(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 分页查询视频列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryDTO 查询条件
|
||||
* @param userId 当前用户ID
|
||||
* @return 视频列表
|
||||
*/
|
||||
Result<Page<VideoListVO>> getVideoList(Page<ContentVideo> page, VideoQueryDTO queryDTO, Long userId);
|
||||
|
||||
/**
|
||||
* 更新视频信息
|
||||
*
|
||||
* @param updateDTO 更新信息
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> updateVideo(VideoUpdateDTO updateDTO, Long userId);
|
||||
|
||||
/**
|
||||
* 删除视频
|
||||
*
|
||||
* @param id 视频ID
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> deleteVideo(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 点赞视频
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> likeVideo(Long videoId, Long userId);
|
||||
|
||||
/**
|
||||
* 取消点赞
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> unlikeVideo(Long videoId, Long userId);
|
||||
|
||||
/**
|
||||
* 投币
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param userId 用户ID
|
||||
* @param amount 投币数量
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> coinVideo(Long videoId, Long userId, Integer amount);
|
||||
|
||||
/**
|
||||
* 收藏视频
|
||||
*
|
||||
* @param favoriteDTO 收藏信息
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> favoriteVideo(FavoriteDTO favoriteDTO, Long userId);
|
||||
|
||||
/**
|
||||
* 取消收藏
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> unfavoriteVideo(Long videoId, Long userId);
|
||||
|
||||
/**
|
||||
* 分享视频
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param userId 用户ID
|
||||
* @param platform 分享平台
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> shareVideo(Long videoId, Long userId, String platform);
|
||||
|
||||
/**
|
||||
* 播放视频(记录播放记录)
|
||||
*
|
||||
* @param recordDTO 播放记录
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> playVideo(PlayRecordDTO recordDTO, Long userId);
|
||||
|
||||
/**
|
||||
* 获取用户视频列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param page 分页参数
|
||||
* @param currentUserId 当前用户ID
|
||||
* @return 视频列表
|
||||
*/
|
||||
Result<Page<VideoListVO>> getUserVideos(Long userId, Page<ContentVideo> page, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 审核视频
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param reviewerId 审核员ID
|
||||
* @param status 审核状态
|
||||
* @param reason 审核原因
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> reviewVideo(Long videoId, Long reviewerId, Integer status, String reason);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nuaa.video.content.service;
|
||||
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.entity.ContentVideoStatDaily;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 视频统计服务接口
|
||||
*/
|
||||
public interface VideoStatService {
|
||||
|
||||
/**
|
||||
* 获取视频每日统计
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 统计数据
|
||||
*/
|
||||
Result<List<ContentVideoStatDaily>> getVideoStatDaily(Long videoId, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
/**
|
||||
* 获取视频播放趋势(按小时)
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @param date 日期
|
||||
* @return 播放趋势
|
||||
*/
|
||||
Result<Map<Integer, Long>> getVideoPlayTrend(Long videoId, LocalDate date);
|
||||
|
||||
/**
|
||||
* 获取视频互动统计
|
||||
*
|
||||
* @param videoId 视频ID
|
||||
* @return 互动统计
|
||||
*/
|
||||
Result<Map<String, Object>> getVideoInteractionStats(Long videoId);
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
package com.nuaa.video.content.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.entity.ContentCategory;
|
||||
import com.nuaa.video.content.entity.ContentVideo;
|
||||
import com.nuaa.video.content.mapper.ContentCategoryMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoMapper;
|
||||
import com.nuaa.video.content.service.CategoryService;
|
||||
import com.nuaa.video.content.vo.CategoryVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 分类服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CategoryServiceImpl extends ServiceImpl<ContentCategoryMapper, ContentCategory> implements CategoryService {
|
||||
|
||||
private final ContentCategoryMapper categoryMapper;
|
||||
private final ContentVideoMapper videoMapper;
|
||||
|
||||
@Override
|
||||
public Result<List<CategoryVO>> getCategoryTree() {
|
||||
// 查询所有分类
|
||||
List<ContentCategory> categories = categoryMapper.selectList(
|
||||
new LambdaQueryWrapper<ContentCategory>()
|
||||
.eq(ContentCategory::getStatus, 1)
|
||||
.orderByAsc(ContentCategory::getSort)
|
||||
);
|
||||
|
||||
// 将分类列表转换为树形结构
|
||||
List<CategoryVO> rootCategories = buildCategoryTree(categories, 0L);
|
||||
|
||||
return Result.success(rootCategories);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建分类树
|
||||
*/
|
||||
private List<CategoryVO> buildCategoryTree(List<ContentCategory> categories, Long parentId) {
|
||||
return categories.stream()
|
||||
.filter(category -> category.getParentId().equals(parentId))
|
||||
.map(category -> {
|
||||
CategoryVO categoryVO = new CategoryVO();
|
||||
BeanUtils.copyProperties(category, categoryVO);
|
||||
// 递归获取子分类
|
||||
List<CategoryVO> children = buildCategoryTree(categories, category.getId());
|
||||
categoryVO.setChildren(children);
|
||||
return categoryVO;
|
||||
})
|
||||
.sorted(Comparator.comparing(CategoryVO::getSort))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<CategoryVO>> getCategoryList() {
|
||||
// 查询所有分类
|
||||
List<ContentCategory> categories = categoryMapper.selectList(
|
||||
new LambdaQueryWrapper<ContentCategory>()
|
||||
.eq(ContentCategory::getStatus, 1)
|
||||
.orderByAsc(ContentCategory::getSort)
|
||||
);
|
||||
|
||||
// 将分类列表转换为VO
|
||||
List<CategoryVO> categoryVOList = categories.stream()
|
||||
.map(category -> {
|
||||
CategoryVO categoryVO = new CategoryVO();
|
||||
BeanUtils.copyProperties(category, categoryVO);
|
||||
return categoryVO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(categoryVOList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> addCategory(ContentCategory category) {
|
||||
// 检查编码是否已存在
|
||||
long count = count(new LambdaQueryWrapper<ContentCategory>()
|
||||
.eq(ContentCategory::getCode, category.getCode()));
|
||||
if (count > 0) {
|
||||
return Result.error("分类编码已存在");
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if (category.getParentId() == null) {
|
||||
category.setParentId(0L); // 默认为根分类
|
||||
}
|
||||
|
||||
if (category.getLevel() == null) {
|
||||
// 如果是根分类,层级为1,否则为父分类层级+1
|
||||
if (category.getParentId() == 0L) {
|
||||
category.setLevel(1);
|
||||
} else {
|
||||
ContentCategory parentCategory = getById(category.getParentId());
|
||||
if (parentCategory != null) {
|
||||
category.setLevel(parentCategory.getLevel() + 1);
|
||||
} else {
|
||||
category.setLevel(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (category.getStatus() == null) {
|
||||
category.setStatus(1); // 默认启用
|
||||
}
|
||||
|
||||
if (category.getSort() == null) {
|
||||
category.setSort(0); // 默认排序值
|
||||
}
|
||||
|
||||
category.setCreateTime(LocalDateTime.now());
|
||||
category.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 保存分类
|
||||
save(category);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> updateCategory(ContentCategory category) {
|
||||
// 检查分类是否存在
|
||||
ContentCategory existingCategory = getById(category.getId());
|
||||
if (existingCategory == null) {
|
||||
return Result.error("分类不存在");
|
||||
}
|
||||
|
||||
// 检查编码是否已存在(排除自身)
|
||||
if (category.getCode() != null && !category.getCode().equals(existingCategory.getCode())) {
|
||||
long count = count(new LambdaQueryWrapper<ContentCategory>()
|
||||
.eq(ContentCategory::getCode, category.getCode())
|
||||
.ne(ContentCategory::getId, category.getId()));
|
||||
if (count > 0) {
|
||||
return Result.error("分类编码已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置更新时间
|
||||
category.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 更新分类
|
||||
updateById(category);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteCategory(Long id) {
|
||||
// 检查分类是否存在
|
||||
ContentCategory category = getById(id);
|
||||
if (category == null) {
|
||||
return Result.error("分类不存在");
|
||||
}
|
||||
|
||||
// 检查是否有子分类
|
||||
long childCount = count(new LambdaQueryWrapper<ContentCategory>()
|
||||
.eq(ContentCategory::getParentId, id));
|
||||
if (childCount > 0) {
|
||||
return Result.error("该分类下存在子分类,无法删除");
|
||||
}
|
||||
|
||||
// 检查是否有关联的视频
|
||||
Long videoCount = videoMapper.selectCount(new LambdaQueryWrapper<ContentVideo>()
|
||||
.eq(ContentVideo::getCategoryId, id));
|
||||
if (videoCount > 0) {
|
||||
return Result.error("该分类下存在视频,无法删除");
|
||||
}
|
||||
|
||||
// 删除分类
|
||||
removeById(id);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.nuaa.video.content.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.dto.DanmakuDTO;
|
||||
import com.nuaa.video.content.entity.ContentDanmaku;
|
||||
import com.nuaa.video.content.entity.ContentVideo;
|
||||
import com.nuaa.video.content.mapper.ContentDanmakuMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoMapper;
|
||||
import com.nuaa.video.content.mq.producer.VideoMessageProducer;
|
||||
import com.nuaa.video.content.service.DanmakuService;
|
||||
import com.nuaa.video.content.vo.DanmakuVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 弹幕服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DanmakuServiceImpl extends ServiceImpl<ContentDanmakuMapper, ContentDanmaku> implements DanmakuService {
|
||||
|
||||
private final ContentDanmakuMapper danmakuMapper;
|
||||
private final ContentVideoMapper videoMapper;
|
||||
private final VideoMessageProducer videoMessageProducer;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Long> sendDanmaku(DanmakuDTO danmakuDTO, Long userId) {
|
||||
// 检查视频是否存在
|
||||
ContentVideo video = videoMapper.selectById(danmakuDTO.getVideoId());
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 创建弹幕对象
|
||||
ContentDanmaku danmaku = new ContentDanmaku();
|
||||
danmaku.setVideoId(danmakuDTO.getVideoId());
|
||||
danmaku.setUserId(userId);
|
||||
danmaku.setContent(danmakuDTO.getContent());
|
||||
danmaku.setColor(danmakuDTO.getColor());
|
||||
danmaku.setFontSize(danmakuDTO.getFontSize());
|
||||
danmaku.setPosition(danmakuDTO.getPosition());
|
||||
danmaku.setTime(danmakuDTO.getTime());
|
||||
danmaku.setCreateTime(LocalDateTime.now());
|
||||
|
||||
// 保存弹幕
|
||||
save(danmaku);
|
||||
|
||||
// 发送弹幕统计消息
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(danmakuDTO.getVideoId());
|
||||
setUserId(userId);
|
||||
setAction("danmaku");
|
||||
setCount(1);
|
||||
}}
|
||||
);
|
||||
|
||||
return Result.success(danmaku.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<DanmakuVO>> getVideoTimeDanmaku(Long videoId, Float startTime, Float endTime) {
|
||||
// 检查视频是否存在
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 查询指定时间段的弹幕
|
||||
List<ContentDanmaku> danmakus = danmakuMapper.getVideoTimeDanmaku(videoId, startTime, endTime);
|
||||
|
||||
// 转换为VO
|
||||
List<DanmakuVO> danmakuVOList = danmakus.stream()
|
||||
.map(this::convertToVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(danmakuVOList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<DanmakuVO>> getVideoAllDanmaku(Long videoId) {
|
||||
// 检查视频是否存在
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 查询所有弹幕
|
||||
List<ContentDanmaku> danmakus = list(
|
||||
new LambdaQueryWrapper<ContentDanmaku>()
|
||||
.eq(ContentDanmaku::getVideoId, videoId)
|
||||
.orderByAsc(ContentDanmaku::getTime)
|
||||
);
|
||||
|
||||
// 转换为VO
|
||||
List<DanmakuVO> danmakuVOList = danmakus.stream()
|
||||
.map(this::convertToVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(danmakuVOList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将弹幕实体转换为VO
|
||||
*/
|
||||
private DanmakuVO convertToVO(ContentDanmaku danmaku) {
|
||||
DanmakuVO vo = new DanmakuVO();
|
||||
BeanUtils.copyProperties(danmaku, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package com.nuaa.video.content.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
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.entity.ContentVideoFavorite;
|
||||
import com.nuaa.video.content.mapper.ContentUserFavoriteMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoFavoriteMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoMapper;
|
||||
import com.nuaa.video.content.service.FavoriteService;
|
||||
import com.nuaa.video.content.service.VideoService;
|
||||
import com.nuaa.video.content.vo.FavoriteVO;
|
||||
import com.nuaa.video.content.vo.VideoListVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 收藏夹服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FavoriteServiceImpl extends ServiceImpl<ContentUserFavoriteMapper, ContentUserFavorite> implements FavoriteService {
|
||||
|
||||
private final ContentUserFavoriteMapper userFavoriteMapper;
|
||||
private final ContentVideoFavoriteMapper videoFavoriteMapper;
|
||||
private final ContentVideoMapper videoMapper;
|
||||
private final VideoService videoService;
|
||||
|
||||
@Override
|
||||
public Result<List<FavoriteVO>> getUserFavorites(Long userId) {
|
||||
// 查询用户的收藏夹
|
||||
List<ContentUserFavorite> favorites = list(
|
||||
new LambdaQueryWrapper<ContentUserFavorite>()
|
||||
.eq(ContentUserFavorite::getUserId, userId)
|
||||
.orderByDesc(ContentUserFavorite::getUpdateTime)
|
||||
);
|
||||
|
||||
// 转换为VO
|
||||
List<FavoriteVO> favoriteVOList = favorites.stream()
|
||||
.map(favorite -> {
|
||||
FavoriteVO vo = new FavoriteVO();
|
||||
BeanUtils.copyProperties(favorite, vo);
|
||||
return vo;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(favoriteVOList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Long> createFavorite(ContentUserFavorite favorite, Long userId) {
|
||||
// 设置默认值
|
||||
favorite.setUserId(userId);
|
||||
favorite.setVideoCount(0);
|
||||
if (favorite.getIsPrivate() == null) {
|
||||
favorite.setIsPrivate(0); // 默认公开
|
||||
}
|
||||
favorite.setCreateTime(LocalDateTime.now());
|
||||
favorite.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 保存收藏夹
|
||||
save(favorite);
|
||||
|
||||
return Result.success(favorite.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> updateFavorite(ContentUserFavorite favorite, Long userId) {
|
||||
// 检查收藏夹是否存在
|
||||
ContentUserFavorite existingFavorite = getById(favorite.getId());
|
||||
if (existingFavorite == null) {
|
||||
return Result.error("收藏夹不存在");
|
||||
}
|
||||
|
||||
// 检查收藏夹是否属于当前用户
|
||||
if (!existingFavorite.getUserId().equals(userId)) {
|
||||
return Result.error("无权操作该收藏夹");
|
||||
}
|
||||
|
||||
// 设置不可修改的字段
|
||||
favorite.setUserId(existingFavorite.getUserId());
|
||||
favorite.setVideoCount(existingFavorite.getVideoCount());
|
||||
favorite.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 更新收藏夹
|
||||
updateById(favorite);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteFavorite(Long id, Long userId) {
|
||||
// 检查收藏夹是否存在
|
||||
ContentUserFavorite favorite = getById(id);
|
||||
if (favorite == null) {
|
||||
return Result.error("收藏夹不存在");
|
||||
}
|
||||
|
||||
// 检查收藏夹是否属于当前用户
|
||||
if (!favorite.getUserId().equals(userId)) {
|
||||
return Result.error("无权操作该收藏夹");
|
||||
}
|
||||
|
||||
// 检查是否是默认收藏夹
|
||||
if (favorite.getName().equals("默认收藏夹")) {
|
||||
return Result.error("默认收藏夹不能删除");
|
||||
}
|
||||
|
||||
// 删除收藏夹内的视频关联
|
||||
videoFavoriteMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoFavorite>()
|
||||
.eq(ContentVideoFavorite::getFavoriteId, id)
|
||||
);
|
||||
|
||||
// 删除收藏夹
|
||||
removeById(id);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Page<VideoListVO>> getFavoriteVideos(Long favoriteId, Page<ContentVideo> page, Long userId) {
|
||||
// 检查收藏夹是否存在
|
||||
ContentUserFavorite favorite = getById(favoriteId);
|
||||
if (favorite == null) {
|
||||
return Result.error("收藏夹不存在");
|
||||
}
|
||||
|
||||
// 检查是否有权限查看(收藏夹所有者或公开收藏夹)
|
||||
if (favorite.getIsPrivate() == 1 && !favorite.getUserId().equals(userId)) {
|
||||
return Result.error("无权查看该收藏夹");
|
||||
}
|
||||
|
||||
// 查询收藏夹内的视频ID
|
||||
List<Long> videoIds = videoFavoriteMapper.selectList(
|
||||
new LambdaQueryWrapper<ContentVideoFavorite>()
|
||||
.eq(ContentVideoFavorite::getFavoriteId, favoriteId)
|
||||
.orderByDesc(ContentVideoFavorite::getCreateTime)
|
||||
).stream().map(ContentVideoFavorite::getVideoId).collect(Collectors.toList());
|
||||
|
||||
if (videoIds.isEmpty()) {
|
||||
Page<VideoListVO> emptyPage = new Page<>();
|
||||
emptyPage.setRecords(new ArrayList<>());
|
||||
emptyPage.setTotal(0);
|
||||
emptyPage.setSize(page.getSize());
|
||||
emptyPage.setCurrent(page.getCurrent());
|
||||
emptyPage.setPages(0);
|
||||
return Result.success(emptyPage);
|
||||
}
|
||||
|
||||
// 查询视频信息
|
||||
Page<ContentVideo> videoPage = videoMapper.selectPage(
|
||||
page,
|
||||
new LambdaQueryWrapper<ContentVideo>()
|
||||
.in(ContentVideo::getId, videoIds)
|
||||
.eq(ContentVideo::getStatus, 1) // 已发布
|
||||
.orderByDesc(ContentVideo::getPublishTime)
|
||||
);
|
||||
|
||||
// 转换为VideoListVO
|
||||
List<VideoListVO> videoListVOs = new ArrayList<>();
|
||||
for (ContentVideo video : videoPage.getRecords()) {
|
||||
VideoListVO vo = new VideoListVO();
|
||||
BeanUtils.copyProperties(video, vo);
|
||||
|
||||
// 可以通过VideoService获取更多信息,如标签、分类、用户信息等
|
||||
videoListVOs.add(vo);
|
||||
}
|
||||
|
||||
// 构建返回结果
|
||||
Page<VideoListVO> resultPage = new Page<>();
|
||||
resultPage.setRecords(videoListVOs);
|
||||
resultPage.setTotal(videoPage.getTotal());
|
||||
resultPage.setSize(videoPage.getSize());
|
||||
resultPage.setCurrent(videoPage.getCurrent());
|
||||
resultPage.setPages(videoPage.getPages());
|
||||
|
||||
return Result.success(resultPage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.nuaa.video.content.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.entity.ContentTag;
|
||||
import com.nuaa.video.content.entity.ContentVideo;
|
||||
import com.nuaa.video.content.entity.ContentVideoTag;
|
||||
import com.nuaa.video.content.mapper.ContentTagMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoTagMapper;
|
||||
import com.nuaa.video.content.service.TagService;
|
||||
import com.nuaa.video.content.vo.TagVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 标签服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TagServiceImpl extends ServiceImpl<ContentTagMapper, ContentTag> implements TagService {
|
||||
|
||||
private final ContentTagMapper tagMapper;
|
||||
private final ContentVideoTagMapper videoTagMapper;
|
||||
private final ContentVideoMapper videoMapper;
|
||||
|
||||
@Override
|
||||
public Result<List<TagVO>> getTagList() {
|
||||
// 查询所有标签
|
||||
List<ContentTag> tags = tagMapper.selectList(new LambdaQueryWrapper<>());
|
||||
|
||||
// 转换为VO
|
||||
List<TagVO> tagVOList = tags.stream()
|
||||
.map(tag -> {
|
||||
TagVO tagVO = new TagVO();
|
||||
BeanUtils.copyProperties(tag, tagVO);
|
||||
return tagVO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(tagVOList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Page<TagVO>> getTagPage(Page<ContentTag> page, String keyword) {
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<ContentTag> queryWrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
queryWrapper.like(ContentTag::getName, keyword);
|
||||
}
|
||||
queryWrapper.orderByDesc(ContentTag::getCreateTime);
|
||||
|
||||
// 分页查询
|
||||
Page<ContentTag> tagPage = tagMapper.selectPage(page, queryWrapper);
|
||||
|
||||
// 转换为VO
|
||||
Page<TagVO> tagVOPage = new Page<>();
|
||||
BeanUtils.copyProperties(tagPage, tagVOPage, "records");
|
||||
|
||||
List<TagVO> tagVOList = tagPage.getRecords().stream()
|
||||
.map(tag -> {
|
||||
TagVO tagVO = new TagVO();
|
||||
BeanUtils.copyProperties(tag, tagVO);
|
||||
return tagVO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
tagVOPage.setRecords(tagVOList);
|
||||
|
||||
return Result.success(tagVOPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> addTag(ContentTag tag) {
|
||||
// 检查标签名称是否已存在
|
||||
long count = count(new LambdaQueryWrapper<ContentTag>()
|
||||
.eq(ContentTag::getName, tag.getName()));
|
||||
if (count > 0) {
|
||||
return Result.error("标签名称已存在");
|
||||
}
|
||||
|
||||
// 设置创建时间和更新时间
|
||||
tag.setCreateTime(LocalDateTime.now());
|
||||
tag.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 保存标签
|
||||
save(tag);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> updateTag(ContentTag tag) {
|
||||
// 检查标签是否存在
|
||||
ContentTag existingTag = getById(tag.getId());
|
||||
if (existingTag == null) {
|
||||
return Result.error("标签不存在");
|
||||
}
|
||||
|
||||
// 检查标签名称是否已存在(排除自身)
|
||||
long count = count(new LambdaQueryWrapper<ContentTag>()
|
||||
.eq(ContentTag::getName, tag.getName())
|
||||
.ne(ContentTag::getId, tag.getId()));
|
||||
if (count > 0) {
|
||||
return Result.error("标签名称已存在");
|
||||
}
|
||||
|
||||
// 设置更新时间
|
||||
tag.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 更新标签
|
||||
updateById(tag);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteTag(Long id) {
|
||||
// 检查标签是否存在
|
||||
ContentTag tag = getById(id);
|
||||
if (tag == null) {
|
||||
return Result.error("标签不存在");
|
||||
}
|
||||
|
||||
// 检查是否有关联的视频
|
||||
Long count = videoTagMapper.selectCount(new LambdaQueryWrapper<ContentVideoTag>()
|
||||
.eq(ContentVideoTag::getTagId, id));
|
||||
if (count > 0) {
|
||||
return Result.error("该标签下存在视频,无法删除");
|
||||
}
|
||||
|
||||
// 删除标签
|
||||
removeById(id);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<TagVO>> getVideoTags(Long videoId) {
|
||||
// 检查视频是否存在
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 查询视频的标签
|
||||
List<ContentTag> tags = tagMapper.getTagsByVideoId(videoId);
|
||||
|
||||
// 转换为VO
|
||||
List<TagVO> tagVOList = tags.stream()
|
||||
.map(tag -> {
|
||||
TagVO tagVO = new TagVO();
|
||||
BeanUtils.copyProperties(tag, tagVO);
|
||||
return tagVO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(tagVOList);
|
||||
}
|
||||
}
|
||||
+729
@@ -0,0 +1,729 @@
|
||||
package com.nuaa.video.content.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nuaa.video.common.exception.BusinessException;
|
||||
import com.nuaa.video.common.model.enums.ResultCode;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.common.utils.MinioUtils;
|
||||
import com.nuaa.video.content.dto.*;
|
||||
import com.nuaa.video.content.entity.*;
|
||||
import com.nuaa.video.content.enums.VideoStatusEnum;
|
||||
import com.nuaa.video.content.feign.UserFeignClient;
|
||||
import com.nuaa.video.content.mapper.*;
|
||||
import com.nuaa.video.content.mq.producer.VideoMessageProducer;
|
||||
import com.nuaa.video.content.service.TagService;
|
||||
import com.nuaa.video.content.service.VideoService;
|
||||
import com.nuaa.video.content.utils.VideoProcessor;
|
||||
import com.nuaa.video.content.vo.TagVO;
|
||||
import com.nuaa.video.content.vo.UploadResultVO;
|
||||
import com.nuaa.video.content.vo.VideoInfoVO;
|
||||
import com.nuaa.video.content.vo.VideoListVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 视频服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVideo> implements VideoService {
|
||||
|
||||
private final ContentVideoMapper videoMapper;
|
||||
private final ContentCategoryMapper categoryMapper;
|
||||
private final ContentVideoTagMapper videoTagMapper;
|
||||
private final ContentVideoLikeMapper videoLikeMapper;
|
||||
private final ContentVideoCoinMapper videoCoinMapper;
|
||||
private final ContentVideoFavoriteMapper videoFavoriteMapper;
|
||||
private final ContentUserFavoriteMapper userFavoriteMapper;
|
||||
private final ContentVideoPlayRecordMapper playRecordMapper;
|
||||
private final ContentVideoReviewMapper videoReviewMapper;
|
||||
private final TagService tagService;
|
||||
private final VideoProcessor videoProcessor;
|
||||
private final MinioUtils minioUtils;
|
||||
private final UserFeignClient userFeignClient;
|
||||
private final VideoMessageProducer videoMessageProducer;
|
||||
|
||||
@Override
|
||||
public Result<UploadResultVO> uploadVideo(MultipartFile videoFile, Long userId) {
|
||||
try {
|
||||
// 校验文件类型
|
||||
String contentType = videoFile.getContentType();
|
||||
if (contentType == null || !contentType.startsWith("video/")) {
|
||||
return Result.error("不支持的文件类型,请上传视频文件");
|
||||
}
|
||||
|
||||
// 处理视频
|
||||
VideoProcessor.VideoProcessResult processResult = videoProcessor.processAndUpload(videoFile);
|
||||
|
||||
// 构建上传结果
|
||||
UploadResultVO resultVO = new UploadResultVO();
|
||||
resultVO.setVideoUrl(processResult.getVideoUrl());
|
||||
resultVO.setCoverUrl(processResult.getCoverUrl());
|
||||
resultVO.setDuration(processResult.getDuration());
|
||||
resultVO.setWidth(processResult.getWidth());
|
||||
resultVO.setHeight(processResult.getHeight());
|
||||
resultVO.setFormat(processResult.getFormat());
|
||||
resultVO.setSize(processResult.getSize());
|
||||
|
||||
return Result.success(resultVO);
|
||||
} catch (Exception e) {
|
||||
log.error("视频上传处理失败", e);
|
||||
return Result.error("视频上传处理失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Long> createVideo(VideoUploadDTO uploadDTO, Long userId) {
|
||||
try {
|
||||
// 检查分类是否存在
|
||||
ContentCategory category = categoryMapper.selectById(uploadDTO.getCategoryId());
|
||||
if (category == null) {
|
||||
return Result.error("分类不存在");
|
||||
}
|
||||
|
||||
// 创建视频记录
|
||||
ContentVideo video = new ContentVideo();
|
||||
video.setUserId(userId);
|
||||
video.setTitle(uploadDTO.getTitle());
|
||||
video.setDescription(uploadDTO.getDescription());
|
||||
video.setCoverUrl(uploadDTO.getCoverUrl());
|
||||
video.setVideoUrl(uploadDTO.getVideoUrl());
|
||||
video.setDuration(uploadDTO.getDuration());
|
||||
video.setWidth(uploadDTO.getWidth());
|
||||
video.setHeight(uploadDTO.getHeight());
|
||||
video.setFormat(uploadDTO.getFormat());
|
||||
video.setSize(uploadDTO.getSize());
|
||||
video.setCategoryId(uploadDTO.getCategoryId());
|
||||
|
||||
// 设置默认状态为审核中
|
||||
video.setStatus(VideoStatusEnum.REVIEWING.getCode());
|
||||
video.setLikeCount(0);
|
||||
video.setCoinCount(0);
|
||||
video.setFavoriteCount(0);
|
||||
video.setCommentCount(0);
|
||||
video.setViewCount(0L);
|
||||
video.setShareCount(0);
|
||||
video.setDanmakuCount(0);
|
||||
video.setIsRecommend(0);
|
||||
video.setIsTop(0);
|
||||
video.setCreatedBy(userId.toString());
|
||||
video.setUpdatedBy(userId.toString());
|
||||
video.setCreateTime(LocalDateTime.now());
|
||||
video.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 保存视频记录
|
||||
save(video);
|
||||
Long videoId = video.getId();
|
||||
|
||||
// 如果有标签,添加标签关联
|
||||
if (uploadDTO.getTagIds() != null && !uploadDTO.getTagIds().isEmpty()) {
|
||||
videoTagMapper.batchInsert(videoId, uploadDTO.getTagIds(), userId.toString());
|
||||
}
|
||||
|
||||
// 更新用户视频数量
|
||||
userFeignClient.increaseVideoCount(userId, 1, "Bearer ");
|
||||
|
||||
// 发送视频处理消息
|
||||
// videoMessageProducer.sendVideoProcessMessage(videoId);
|
||||
|
||||
return Result.success(videoId);
|
||||
} catch (Exception e) {
|
||||
log.error("创建视频失败", e);
|
||||
throw new BusinessException("创建视频失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<VideoInfoVO> getVideoInfo(Long id, Long userId) {
|
||||
// 查询视频信息
|
||||
ContentVideo video = getById(id);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 转换为VO
|
||||
VideoInfoVO videoInfoVO = new VideoInfoVO();
|
||||
BeanUtils.copyProperties(video, videoInfoVO);
|
||||
|
||||
// 获取分类名称
|
||||
ContentCategory category = categoryMapper.selectById(video.getCategoryId());
|
||||
if (category != null) {
|
||||
videoInfoVO.setCategoryName(category.getName());
|
||||
}
|
||||
|
||||
// 获取标签列表
|
||||
Result<List<TagVO>> tagResult = tagService.getVideoTags(id);
|
||||
if (tagResult.getCode() == ResultCode.SUCCESS.getCode() && tagResult.getData() != null) {
|
||||
List<TagVO> tags = tagResult.getData();
|
||||
videoInfoVO.setTagIds(tags.stream().map(TagVO::getId).collect(Collectors.toList()));
|
||||
videoInfoVO.setTagNames(tags.stream().map(TagVO::getName).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
try {
|
||||
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||
if (userResult.getCode() == ResultCode.SUCCESS.getCode() && userResult.getData() != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||
videoInfoVO.setUserNickname((String) userData.get("nickname"));
|
||||
videoInfoVO.setUserAvatar((String) userData.get("avatar"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息失败", e);
|
||||
}
|
||||
|
||||
// 如果当前用户已登录,判断是否已点赞、收藏、投币
|
||||
if (userId != null) {
|
||||
int likeCount = videoLikeMapper.checkUserLiked(userId, id);
|
||||
videoInfoVO.setIsLiked(likeCount > 0);
|
||||
|
||||
int favoriteCount = videoFavoriteMapper.checkUserFavorited(userId, id);
|
||||
videoInfoVO.setIsFavorited(favoriteCount > 0);
|
||||
|
||||
int coinCount = videoCoinMapper.getUserCoinCount(userId, id);
|
||||
videoInfoVO.setIsCoined(coinCount > 0);
|
||||
|
||||
// 增加播放数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(id);
|
||||
setUserId(userId);
|
||||
setAction("play");
|
||||
setCount(1);
|
||||
}}
|
||||
);
|
||||
}
|
||||
|
||||
return Result.success(videoInfoVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Page<VideoListVO>> getVideoList(Page<ContentVideo> page, VideoQueryDTO queryDTO, Long userId) {
|
||||
// 如果没有指定状态,默认查询已发布的视频
|
||||
if (queryDTO.getStatus() == null) {
|
||||
queryDTO.setStatus(VideoStatusEnum.PUBLISHED.getCode());
|
||||
}
|
||||
|
||||
// 查询视频列表
|
||||
Page<ContentVideo> videoPage = videoMapper.selectVideoList(page, queryDTO);
|
||||
List<ContentVideo> records = videoPage.getRecords();
|
||||
|
||||
// 查询视频的标签
|
||||
List<VideoListVO> videoListVOs = new ArrayList<>();
|
||||
for (ContentVideo video : records) {
|
||||
VideoListVO vo = new VideoListVO();
|
||||
BeanUtils.copyProperties(video, vo);
|
||||
|
||||
// 设置分类名称
|
||||
vo.setCategoryName(video.getCategoryName());
|
||||
|
||||
// 获取标签列表
|
||||
Result<List<TagVO>> tagResult = tagService.getVideoTags(video.getId());
|
||||
if (tagResult.getCode() == ResultCode.SUCCESS.getCode() && tagResult.getData() != null) {
|
||||
List<TagVO> tags = tagResult.getData();
|
||||
vo.setTagNames(tags.stream().map(TagVO::getName).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
try {
|
||||
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||
if (userResult.getCode() == ResultCode.SUCCESS.getCode() && userResult.getData() != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||
vo.setUserNickname((String) userData.get("nickname"));
|
||||
vo.setUserAvatar((String) userData.get("avatar"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息失败", e);
|
||||
}
|
||||
|
||||
videoListVOs.add(vo);
|
||||
}
|
||||
|
||||
// 构建返回结果
|
||||
Page<VideoListVO> resultPage = new Page<>();
|
||||
resultPage.setRecords(videoListVOs);
|
||||
resultPage.setTotal(videoPage.getTotal());
|
||||
resultPage.setSize(videoPage.getSize());
|
||||
resultPage.setCurrent(videoPage.getCurrent());
|
||||
resultPage.setPages(videoPage.getPages());
|
||||
|
||||
return Result.success(resultPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> updateVideo(VideoUpdateDTO updateDTO, Long userId) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(updateDTO.getId());
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 检查视频是否属于当前用户
|
||||
if (!video.getUserId().equals(userId)) {
|
||||
return Result.error(ResultCode.FORBIDDEN.getCode(), "无权修改该视频");
|
||||
}
|
||||
|
||||
// 检查分类是否存在
|
||||
if (updateDTO.getCategoryId() != null) {
|
||||
ContentCategory category = categoryMapper.selectById(updateDTO.getCategoryId());
|
||||
if (category == null) {
|
||||
return Result.error("分类不存在");
|
||||
}
|
||||
video.setCategoryId(updateDTO.getCategoryId());
|
||||
}
|
||||
|
||||
// 更新视频信息
|
||||
if (updateDTO.getTitle() != null) {
|
||||
video.setTitle(updateDTO.getTitle());
|
||||
}
|
||||
if (updateDTO.getDescription() != null) {
|
||||
video.setDescription(updateDTO.getDescription());
|
||||
}
|
||||
if (updateDTO.getCoverUrl() != null) {
|
||||
video.setCoverUrl(updateDTO.getCoverUrl());
|
||||
}
|
||||
|
||||
video.setUpdatedBy(userId.toString());
|
||||
video.setUpdateTime(LocalDateTime.now());
|
||||
updateById(video);
|
||||
|
||||
// 更新标签关联
|
||||
if (updateDTO.getTagIds() != null) {
|
||||
// 删除原有标签关联
|
||||
videoTagMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoTag>()
|
||||
.eq(ContentVideoTag::getVideoId, video.getId())
|
||||
);
|
||||
|
||||
// 添加新标签关联
|
||||
if (!updateDTO.getTagIds().isEmpty()) {
|
||||
videoTagMapper.batchInsert(video.getId(), updateDTO.getTagIds(), userId.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteVideo(Long id, Long userId) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(id);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 检查视频是否属于当前用户
|
||||
if (!video.getUserId().equals(userId)) {
|
||||
return Result.error(ResultCode.FORBIDDEN.getCode(), "无权删除该视频");
|
||||
}
|
||||
|
||||
// 删除视频
|
||||
removeById(id);
|
||||
|
||||
// 删除相关记录
|
||||
// 1. 删除标签关联
|
||||
videoTagMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoTag>()
|
||||
.eq(ContentVideoTag::getVideoId, id)
|
||||
);
|
||||
|
||||
// 2. 删除点赞记录
|
||||
videoLikeMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoLike>()
|
||||
.eq(ContentVideoLike::getVideoId, id)
|
||||
);
|
||||
|
||||
// 3. 删除投币记录
|
||||
videoCoinMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoCoin>()
|
||||
.eq(ContentVideoCoin::getVideoId, id)
|
||||
);
|
||||
|
||||
// 4. 删除收藏记录
|
||||
videoFavoriteMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoFavorite>()
|
||||
.eq(ContentVideoFavorite::getVideoId, id)
|
||||
);
|
||||
|
||||
// 5. 删除播放记录
|
||||
playRecordMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoPlayRecord>()
|
||||
.eq(ContentVideoPlayRecord::getVideoId, id)
|
||||
);
|
||||
|
||||
// 更新用户视频数量
|
||||
userFeignClient.increaseVideoCount(userId, -1, "Bearer ");
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> likeVideo(Long videoId, Long userId) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 检查是否已点赞
|
||||
int count = videoLikeMapper.checkUserLiked(userId, videoId);
|
||||
if (count > 0) {
|
||||
return Result.error("已经点赞过该视频");
|
||||
}
|
||||
|
||||
// 添加点赞记录
|
||||
ContentVideoLike like = new ContentVideoLike();
|
||||
like.setVideoId(videoId);
|
||||
like.setUserId(userId);
|
||||
like.setCreateTime(LocalDateTime.now());
|
||||
videoLikeMapper.insert(like);
|
||||
|
||||
// 发送统计消息,增加点赞数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(videoId);
|
||||
setUserId(userId);
|
||||
setAction("like");
|
||||
setCount(1);
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户获赞数
|
||||
userFeignClient.increaseLikeCount(video.getUserId(), 1, "Bearer ");
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> unlikeVideo(Long videoId, Long userId) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 检查是否已点赞
|
||||
int count = videoLikeMapper.checkUserLiked(userId, videoId);
|
||||
if (count == 0) {
|
||||
return Result.error("未点赞该视频");
|
||||
}
|
||||
|
||||
// 删除点赞记录
|
||||
videoLikeMapper.delete(
|
||||
new LambdaQueryWrapper<ContentVideoLike>()
|
||||
.eq(ContentVideoLike::getVideoId, videoId)
|
||||
.eq(ContentVideoLike::getUserId, userId)
|
||||
);
|
||||
|
||||
// 发送统计消息,减少点赞数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(videoId);
|
||||
setUserId(userId);
|
||||
setAction("like");
|
||||
setCount(-1);
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户获赞数
|
||||
userFeignClient.increaseLikeCount(video.getUserId(), -1, "Bearer ");
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> coinVideo(Long videoId, Long userId, Integer amount) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 检查投币数量
|
||||
if (amount == null || amount <= 0 || amount > 2) {
|
||||
return Result.error("投币数量无效,请投1-2个硬币");
|
||||
}
|
||||
|
||||
// 检查是否已投币
|
||||
int coinCount = videoCoinMapper.getUserCoinCount(userId, videoId);
|
||||
if (coinCount >= 2) {
|
||||
return Result.error("已经投了2个硬币,不能再投了");
|
||||
} else if (coinCount + amount > 2) {
|
||||
return Result.error("投币总数不能超过2个");
|
||||
}
|
||||
|
||||
// 添加投币记录
|
||||
ContentVideoCoin coin = new ContentVideoCoin();
|
||||
coin.setVideoId(videoId);
|
||||
coin.setUserId(userId);
|
||||
coin.setAmount(amount);
|
||||
coin.setCreateTime(LocalDateTime.now());
|
||||
videoCoinMapper.insert(coin);
|
||||
|
||||
// 发送统计消息,增加投币数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(videoId);
|
||||
setUserId(userId);
|
||||
setAction("coin");
|
||||
setCount(amount);
|
||||
}}
|
||||
);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> favoriteVideo(FavoriteDTO favoriteDTO, Long userId) {
|
||||
Long videoId = favoriteDTO.getVideoId();
|
||||
Long favoriteId = favoriteDTO.getFavoriteId();
|
||||
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 查询收藏夹是否存在
|
||||
ContentUserFavorite favorite = userFavoriteMapper.selectById(favoriteId);
|
||||
if (favorite == null) {
|
||||
return Result.error("收藏夹不存在");
|
||||
}
|
||||
|
||||
// 检查收藏夹是否属于当前用户
|
||||
if (!favorite.getUserId().equals(userId)) {
|
||||
return Result.error(ResultCode.FORBIDDEN.getCode(), "无权操作该收藏夹");
|
||||
}
|
||||
|
||||
// 检查是否已收藏到该收藏夹
|
||||
Long count = videoFavoriteMapper.selectCount(
|
||||
new LambdaQueryWrapper<ContentVideoFavorite>()
|
||||
.eq(ContentVideoFavorite::getVideoId, videoId)
|
||||
.eq(ContentVideoFavorite::getUserId, userId)
|
||||
.eq(ContentVideoFavorite::getFavoriteId, favoriteId)
|
||||
);
|
||||
if (count > 0) {
|
||||
return Result.error("已经收藏到该收藏夹");
|
||||
}
|
||||
|
||||
// 添加收藏记录
|
||||
ContentVideoFavorite videoFavorite = new ContentVideoFavorite();
|
||||
videoFavorite.setVideoId(videoId);
|
||||
videoFavorite.setUserId(userId);
|
||||
videoFavorite.setFavoriteId(favoriteId);
|
||||
videoFavorite.setCreateTime(LocalDateTime.now());
|
||||
videoFavoriteMapper.insert(videoFavorite);
|
||||
|
||||
// 增加收藏夹视频数量
|
||||
userFavoriteMapper.increaseVideoCount(favoriteId, 1);
|
||||
|
||||
// 发送统计消息,增加收藏数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(videoId);
|
||||
setUserId(userId);
|
||||
setAction("favorite");
|
||||
setCount(1);
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户收藏数
|
||||
userFeignClient.increaseFavoriteCount(userId, 1, "Bearer ");
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> unfavoriteVideo(Long videoId, Long userId) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 查询是否已收藏
|
||||
List<ContentVideoFavorite> favorites = videoFavoriteMapper.selectList(
|
||||
new LambdaQueryWrapper<ContentVideoFavorite>()
|
||||
.eq(ContentVideoFavorite::getVideoId, videoId)
|
||||
.eq(ContentVideoFavorite::getUserId, userId)
|
||||
);
|
||||
if (favorites.isEmpty()) {
|
||||
return Result.error("未收藏该视频");
|
||||
}
|
||||
|
||||
// 删除收藏记录并更新收藏夹视频数量
|
||||
for (ContentVideoFavorite favorite : favorites) {
|
||||
videoFavoriteMapper.deleteById(favorite.getId());
|
||||
userFavoriteMapper.increaseVideoCount(favorite.getFavoriteId(), -1);
|
||||
}
|
||||
|
||||
// 发送统计消息,减少收藏数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(videoId);
|
||||
setUserId(userId);
|
||||
setAction("favorite");
|
||||
setCount(-favorites.size());
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户收藏数
|
||||
userFeignClient.increaseFavoriteCount(userId, -favorites.size(), "Bearer ");
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> shareVideo(Long videoId, Long userId, String platform) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 发送统计消息,增加分享数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(videoId);
|
||||
setUserId(userId);
|
||||
setAction("share");
|
||||
setCount(1);
|
||||
}}
|
||||
);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> playVideo(PlayRecordDTO recordDTO, Long userId) {
|
||||
Long videoId = recordDTO.getVideoId();
|
||||
Integer playTime = recordDTO.getPlayTime();
|
||||
Integer isFinished = recordDTO.getIsFinished();
|
||||
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 如果isFinished未设置,根据播放时长判断是否看完
|
||||
if (isFinished == null) {
|
||||
isFinished = (playTime >= video.getDuration() * 0.9) ? 1 : 0;
|
||||
}
|
||||
|
||||
// 查询是否有播放记录
|
||||
ContentVideoPlayRecord record = playRecordMapper.selectOne(
|
||||
new LambdaQueryWrapper<ContentVideoPlayRecord>()
|
||||
.eq(ContentVideoPlayRecord::getVideoId, videoId)
|
||||
.eq(ContentVideoPlayRecord::getUserId, userId)
|
||||
);
|
||||
|
||||
if (record == null) {
|
||||
// 添加播放记录
|
||||
record = new ContentVideoPlayRecord();
|
||||
record.setVideoId(videoId);
|
||||
record.setUserId(userId);
|
||||
record.setPlayTime(playTime);
|
||||
record.setIsFinished(isFinished);
|
||||
record.setCreateTime(LocalDateTime.now());
|
||||
record.setUpdateTime(LocalDateTime.now());
|
||||
playRecordMapper.insert(record);
|
||||
|
||||
// 首次播放,增加播放数
|
||||
videoMessageProducer.sendVideoStatsMessage(
|
||||
new VideoMessageProducer.VideoStatsMessage() {{
|
||||
setVideoId(videoId);
|
||||
setUserId(userId);
|
||||
setAction("play");
|
||||
setCount(1);
|
||||
}}
|
||||
);
|
||||
} else {
|
||||
// 更新播放记录
|
||||
record.setPlayTime(Math.max(record.getPlayTime(), playTime));
|
||||
if (isFinished == 1) {
|
||||
record.setIsFinished(1);
|
||||
}
|
||||
record.setUpdateTime(LocalDateTime.now());
|
||||
playRecordMapper.updateById(record);
|
||||
}
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Page<VideoListVO>> getUserVideos(Long userId, Page<ContentVideo> page, Long currentUserId) {
|
||||
VideoQueryDTO queryDTO = new VideoQueryDTO();
|
||||
queryDTO.setUserId(userId);
|
||||
|
||||
// 如果是查询自己的视频,显示所有状态的视频
|
||||
// 如果是查询他人的视频,只显示已发布的视频
|
||||
if (currentUserId != null && currentUserId.equals(userId)) {
|
||||
queryDTO.setStatus(null);
|
||||
} else {
|
||||
queryDTO.setStatus(VideoStatusEnum.PUBLISHED.getCode());
|
||||
}
|
||||
|
||||
return getVideoList(page, queryDTO, currentUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> reviewVideo(Long videoId, Long reviewerId, Integer status, String reason) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error(ResultCode.NOT_FOUND.getCode(), "视频不存在");
|
||||
}
|
||||
|
||||
// 检查状态是否合法
|
||||
if (status != 1 && status != 3) {
|
||||
return Result.error("审核状态无效,只能为通过(1)或不通过(3)");
|
||||
}
|
||||
|
||||
// 更新视频状态
|
||||
video.setStatus(status);
|
||||
if (status == 1) {
|
||||
// 通过审核,设置发布时间
|
||||
video.setPublishTime(LocalDateTime.now());
|
||||
}
|
||||
video.setUpdatedBy(reviewerId.toString());
|
||||
video.setUpdateTime(LocalDateTime.now());
|
||||
updateById(video);
|
||||
|
||||
// 添加审核记录
|
||||
ContentVideoReview review = new ContentVideoReview();
|
||||
review.setVideoId(videoId);
|
||||
review.setReviewerId(reviewerId);
|
||||
review.setStatus(status);
|
||||
review.setReason(reason);
|
||||
review.setCreatedBy(reviewerId.toString());
|
||||
review.setCreateTime(LocalDateTime.now());
|
||||
videoReviewMapper.insert(review);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package com.nuaa.video.content.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.content.entity.ContentVideo;
|
||||
import com.nuaa.video.content.entity.ContentVideoPlayRecord;
|
||||
import com.nuaa.video.content.entity.ContentVideoStatDaily;
|
||||
import com.nuaa.video.content.mapper.ContentVideoMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoPlayRecordMapper;
|
||||
import com.nuaa.video.content.mapper.ContentVideoStatDailyMapper;
|
||||
import com.nuaa.video.content.service.VideoStatService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 视频统计服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VideoStatServiceImpl implements VideoStatService {
|
||||
|
||||
private final ContentVideoStatDailyMapper statDailyMapper;
|
||||
private final ContentVideoMapper videoMapper;
|
||||
private final ContentVideoPlayRecordMapper playRecordMapper;
|
||||
|
||||
@Override
|
||||
public Result<List<ContentVideoStatDaily>> getVideoStatDaily(Long videoId, LocalDate startDate, LocalDate endDate) {
|
||||
// 检查视频是否存在
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 查询指定日期范围的统计数据
|
||||
List<ContentVideoStatDaily> statList = statDailyMapper.selectList(
|
||||
new LambdaQueryWrapper<ContentVideoStatDaily>()
|
||||
.eq(ContentVideoStatDaily::getVideoId, videoId)
|
||||
.between(ContentVideoStatDaily::getStatDate, startDate, endDate)
|
||||
.orderByAsc(ContentVideoStatDaily::getStatDate)
|
||||
);
|
||||
|
||||
return Result.success(statList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Map<Integer, Long>> getVideoPlayTrend(Long videoId, LocalDate date) {
|
||||
// 检查视频是否存在
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 初始化24小时数据
|
||||
Map<Integer, Long> hourlyPlays = new HashMap<>();
|
||||
for (int i = 0; i < 24; i++) {
|
||||
hourlyPlays.put(i, 0L);
|
||||
}
|
||||
|
||||
// 查询当日播放记录
|
||||
List<ContentVideoPlayRecord> records = playRecordMapper.selectList(
|
||||
new LambdaQueryWrapper<ContentVideoPlayRecord>()
|
||||
.eq(ContentVideoPlayRecord::getVideoId, videoId)
|
||||
.apply("DATE(create_time) = {0}", date)
|
||||
);
|
||||
|
||||
// 统计每小时播放量
|
||||
records.forEach(record -> {
|
||||
int hour = record.getCreateTime().getHour();
|
||||
hourlyPlays.put(hour, hourlyPlays.getOrDefault(hour, 0L) + 1);
|
||||
});
|
||||
|
||||
return Result.success(hourlyPlays);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Map<String, Object>> getVideoInteractionStats(Long videoId) {
|
||||
// 检查视频是否存在
|
||||
ContentVideo video = videoMapper.selectById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 获取视频统计数据
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("viewCount", video.getViewCount());
|
||||
stats.put("likeCount", video.getLikeCount());
|
||||
stats.put("favoriteCount", video.getFavoriteCount());
|
||||
stats.put("coinCount", video.getCoinCount());
|
||||
stats.put("commentCount", video.getCommentCount());
|
||||
stats.put("shareCount", video.getShareCount());
|
||||
stats.put("danmakuCount", video.getDanmakuCount());
|
||||
|
||||
// 计算互动率
|
||||
if (video.getViewCount() > 0) {
|
||||
double likeRate = (double) video.getLikeCount() / video.getViewCount() * 100;
|
||||
double favoriteRate = (double) video.getFavoriteCount() / video.getViewCount() * 100;
|
||||
double coinRate = (double) video.getCoinCount() / video.getViewCount() * 100;
|
||||
double commentRate = (double) video.getCommentCount() / video.getViewCount() * 100;
|
||||
|
||||
stats.put("likeRate", String.format("%.2f", likeRate));
|
||||
stats.put("favoriteRate", String.format("%.2f", favoriteRate));
|
||||
stats.put("coinRate", String.format("%.2f", coinRate));
|
||||
stats.put("commentRate", String.format("%.2f", commentRate));
|
||||
}
|
||||
|
||||
// 获取最近30天的统计数据
|
||||
LocalDate endDate = LocalDate.now();
|
||||
LocalDate startDate = endDate.minusDays(29);
|
||||
List<ContentVideoStatDaily> dailyStats = statDailyMapper.selectList(
|
||||
new LambdaQueryWrapper<ContentVideoStatDaily>()
|
||||
.eq(ContentVideoStatDaily::getVideoId, videoId)
|
||||
.between(ContentVideoStatDaily::getStatDate, startDate, endDate)
|
||||
.orderByAsc(ContentVideoStatDaily::getStatDate)
|
||||
);
|
||||
|
||||
stats.put("dailyStats", dailyStats);
|
||||
|
||||
return Result.success(stats);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.nuaa.video.content.task;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nuaa.video.content.entity.ContentVideoStatDaily;
|
||||
import com.nuaa.video.content.mapper.ContentVideoStatDailyMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 视频统计数据同步定时任务
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class VideoStatsSyncTask {
|
||||
|
||||
private final ContentVideoStatDailyMapper statDailyMapper;
|
||||
|
||||
/**
|
||||
* 每日凌晨0点30分执行,初始化当天的统计记录
|
||||
*/
|
||||
@Scheduled(cron = "0 30 0 * * ?")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void initDailyStats() {
|
||||
log.info("开始初始化今日视频统计记录");
|
||||
try {
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate yesterday = today.minusDays(1);
|
||||
|
||||
// 查询昨天有数据更新的视频ID
|
||||
// 实际业务中可能会从Redis等缓存中获取活跃视频ID
|
||||
// 这里简化处理,仅初始化部分必要的视频
|
||||
|
||||
log.info("今日视频统计记录初始化完成");
|
||||
} catch (Exception e) {
|
||||
log.error("初始化今日视频统计记录失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每日凌晨1点执行,汇总前一天的数据
|
||||
*/
|
||||
@Scheduled(cron = "0 0 1 * * ?")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void summarizeYesterdayStats() {
|
||||
log.info("开始汇总昨日视频统计数据");
|
||||
try {
|
||||
LocalDate yesterday = LocalDate.now().minusDays(1);
|
||||
|
||||
// 从缓存中获取昨日视频互动统计数据,并更新到数据库
|
||||
// 实际业务中可能会有更复杂的逻辑
|
||||
|
||||
log.info("昨日视频统计数据汇总完成");
|
||||
} catch (Exception e) {
|
||||
log.error("汇总昨日视频统计数据失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.nuaa.video.content.utils;
|
||||
|
||||
import com.nuaa.video.common.utils.MinioUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bramp.ffmpeg.FFmpeg;
|
||||
import net.bramp.ffmpeg.FFmpegExecutor;
|
||||
import net.bramp.ffmpeg.FFprobe;
|
||||
import net.bramp.ffmpeg.builder.FFmpegBuilder;
|
||||
import net.bramp.ffmpeg.probe.FFmpegFormat;
|
||||
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
|
||||
import net.bramp.ffmpeg.probe.FFmpegStream;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 视频处理工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class VideoProcessor {
|
||||
|
||||
private final FFmpeg ffmpeg;
|
||||
private final FFprobe ffprobe;
|
||||
private final MinioUtils minioUtils;
|
||||
|
||||
/**
|
||||
* 处理视频并上传
|
||||
*
|
||||
* @param videoFile 视频文件
|
||||
* @return 处理结果
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public VideoProcessResult processAndUpload(MultipartFile videoFile) throws Exception {
|
||||
// 创建临时文件
|
||||
Path tempDir = Files.createTempDirectory("video_upload_");
|
||||
File tempFile = new File(tempDir.toFile(), UUID.randomUUID() + "_" + videoFile.getOriginalFilename());
|
||||
videoFile.transferTo(tempFile);
|
||||
|
||||
// 分析视频信息
|
||||
FFmpegProbeResult probeResult = ffprobe.probe(tempFile.getAbsolutePath());
|
||||
FFmpegFormat format = probeResult.getFormat();
|
||||
FFmpegStream videoStream = probeResult.getStreams().get(0);
|
||||
|
||||
// 视频信息
|
||||
int width = videoStream.width;
|
||||
int height = videoStream.height;
|
||||
double duration = format.duration;
|
||||
String codecName = videoStream.codec_name;
|
||||
long size = tempFile.length();
|
||||
|
||||
// 生成缩略图
|
||||
File thumbnailFile = new File(tempDir.toFile(), UUID.randomUUID() + "_thumbnail.jpg");
|
||||
FFmpegBuilder builder = new FFmpegBuilder()
|
||||
.setInput(tempFile.getAbsolutePath())
|
||||
.overrideOutputFiles(true)
|
||||
.addOutput(thumbnailFile.getAbsolutePath())
|
||||
.setFrames(1)
|
||||
.setVideoFilter("select=eq(n\\,0)")
|
||||
.done();
|
||||
|
||||
FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe);
|
||||
executor.createJob(builder).run();
|
||||
|
||||
// 上传视频和缩略图到MinIO
|
||||
String videoUrl = minioUtils.uploadObject("videos", tempFile.getName(),
|
||||
new FileInputStream(tempFile), tempFile.length(), videoFile.getContentType());
|
||||
|
||||
String thumbnailUrl = minioUtils.uploadObject("covers", thumbnailFile.getName(),
|
||||
new FileInputStream(thumbnailFile), thumbnailFile.length(), "image/jpeg");
|
||||
|
||||
// 构建结果
|
||||
VideoProcessResult result = new VideoProcessResult();
|
||||
result.setVideoUrl(videoUrl);
|
||||
result.setCoverUrl(thumbnailUrl);
|
||||
result.setDuration((int) Math.ceil(duration));
|
||||
result.setWidth(width);
|
||||
result.setHeight(height);
|
||||
result.setFormat(codecName);
|
||||
result.setSize(size);
|
||||
|
||||
// 清理临时文件
|
||||
try {
|
||||
Files.deleteIfExists(tempFile.toPath());
|
||||
Files.deleteIfExists(thumbnailFile.toPath());
|
||||
Files.deleteIfExists(tempDir);
|
||||
} catch (IOException e) {
|
||||
log.warn("清理临时文件失败", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频处理结果类
|
||||
*/
|
||||
@lombok.Data
|
||||
public static class VideoProcessResult {
|
||||
private String videoUrl;
|
||||
private String coverUrl;
|
||||
private int duration;
|
||||
private int width;
|
||||
private int height;
|
||||
private String format;
|
||||
private long size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nuaa.video.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分类VO
|
||||
*/
|
||||
@Data
|
||||
public class CategoryVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 分类编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 父分类ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 分类层级
|
||||
*/
|
||||
private Integer level;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 子分类列表
|
||||
*/
|
||||
private List<CategoryVO> children;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nuaa.video.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 弹幕VO
|
||||
*/
|
||||
@Data
|
||||
public class DanmakuVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 弹幕ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 弹幕内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 弹幕颜色
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 字体大小
|
||||
*/
|
||||
private Integer fontSize;
|
||||
|
||||
/**
|
||||
* 位置(0:滚动,1:顶部,2:底部)
|
||||
*/
|
||||
private Integer position;
|
||||
|
||||
/**
|
||||
* 发送时间(秒)
|
||||
*/
|
||||
private Float time;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nuaa.video.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 收藏夹VO
|
||||
*/
|
||||
@Data
|
||||
public class FavoriteVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 收藏夹ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 收藏夹名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 收藏夹描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否私有(0:公开,1:私有)
|
||||
*/
|
||||
private Integer isPrivate;
|
||||
|
||||
/**
|
||||
* 视频数量
|
||||
*/
|
||||
private Integer videoCount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.nuaa.video.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 标签VO
|
||||
*/
|
||||
@Data
|
||||
public class TagVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标签名称
|
||||
*/
|
||||
private String name;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.nuaa.video.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 上传结果VO
|
||||
*/
|
||||
@Data
|
||||
public class UploadResultVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long videoId;
|
||||
|
||||
/**
|
||||
* 视频URL
|
||||
*/
|
||||
private String videoUrl;
|
||||
|
||||
/**
|
||||
* 封面URL
|
||||
*/
|
||||
private String coverUrl;
|
||||
|
||||
/**
|
||||
* 视频时长(秒)
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 视频宽度
|
||||
*/
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 视频高度
|
||||
*/
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
* 视频格式
|
||||
*/
|
||||
private String format;
|
||||
|
||||
/**
|
||||
* 视频大小(字节)
|
||||
*/
|
||||
private Long size;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.nuaa.video.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频信息VO
|
||||
*/
|
||||
@Data
|
||||
public class VideoInfoVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String userNickname;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 视频标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 视频描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 封面URL
|
||||
*/
|
||||
private String coverUrl;
|
||||
|
||||
/**
|
||||
* 视频URL
|
||||
*/
|
||||
private String videoUrl;
|
||||
|
||||
/**
|
||||
* 视频时长(秒)
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 视频宽度
|
||||
*/
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 视频高度
|
||||
*/
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
private String categoryName;
|
||||
|
||||
/**
|
||||
* 标签ID列表
|
||||
*/
|
||||
private List<Long> tagIds;
|
||||
|
||||
/**
|
||||
* 标签名称列表
|
||||
*/
|
||||
private List<String> tagNames;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 投币数
|
||||
*/
|
||||
private Integer coinCount;
|
||||
|
||||
/**
|
||||
* 收藏数
|
||||
*/
|
||||
private Integer favoriteCount;
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*/
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 播放数
|
||||
*/
|
||||
private Long viewCount;
|
||||
|
||||
/**
|
||||
* 分享数
|
||||
*/
|
||||
private Integer shareCount;
|
||||
|
||||
/**
|
||||
* 弹幕数
|
||||
*/
|
||||
private Integer danmakuCount;
|
||||
|
||||
/**
|
||||
* 是否已点赞
|
||||
*/
|
||||
private Boolean isLiked;
|
||||
|
||||
/**
|
||||
* 是否已收藏
|
||||
*/
|
||||
private Boolean isFavorited;
|
||||
|
||||
/**
|
||||
* 是否已投币
|
||||
*/
|
||||
private Boolean isCoined;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
private LocalDateTime publishTime;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.nuaa.video.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频列表VO
|
||||
*/
|
||||
@Data
|
||||
public class VideoListVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 视频ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String userNickname;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 视频标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 视频描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 封面URL
|
||||
*/
|
||||
private String coverUrl;
|
||||
|
||||
/**
|
||||
* 视频时长(秒)
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
private String categoryName;
|
||||
|
||||
/**
|
||||
* 标签名称列表
|
||||
*/
|
||||
private List<String> tagNames;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 播放数
|
||||
*/
|
||||
private Long viewCount;
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*/
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
private LocalDateTime publishTime;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
spring:
|
||||
datasource:
|
||||
dynamic:
|
||||
primary: master
|
||||
strict: false
|
||||
datasource:
|
||||
master:
|
||||
url: jdbc:mysql://5.180.253.115:3306/video_content?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: sty@G458221
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
data:
|
||||
redis:
|
||||
database: 2
|
||||
host: 5.180.253.115
|
||||
port: 6379
|
||||
password: redis_EsJ8FX
|
||||
timeout: 10000
|
||||
|
||||
rocketmq:
|
||||
name-server: 5.180.253.115:9876
|
||||
|
||||
minio:
|
||||
endpoint: http://5.180.253.115:9002
|
||||
accessKey: minioadmin
|
||||
secretKey: minioadmin
|
||||
bucketName: video-platform
|
||||
|
||||
# 开发环境使用相对路径或本地FFmpeg
|
||||
ffmpeg:
|
||||
binary:
|
||||
path: D:/ffmpeg/bin/ffmpeg.exe # Windows路径示例,实际环境请修改
|
||||
probe:
|
||||
path: D:/ffmpeg/bin/ffprobe.exe # Windows路径示例,实际环境请修改
|
||||
temp:
|
||||
path: D:/temp/ffmpeg # 临时文件目录
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.nuaa.video: debug
|
||||
org.springframework.security: debug
|
||||
@@ -4,40 +4,134 @@ server:
|
||||
spring:
|
||||
application:
|
||||
name: video-content
|
||||
profiles:
|
||||
active: dev
|
||||
config:
|
||||
import:
|
||||
- optional:nacos:${spring.application.name}.yaml
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: 5.180.253.115:8848
|
||||
namespace: public
|
||||
group: DEFAULT_GROUP
|
||||
server-addr: ${NACOS_HOST:5.180.253.115}:${NACOS_PORT:8848}
|
||||
namespace: ${NACOS_NAMESPACE:public}
|
||||
group: ${NACOS_GROUP:DEFAULT_GROUP}
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
namespace: ${spring.cloud.nacos.discovery.namespace}
|
||||
group: ${spring.cloud.nacos.discovery.group}
|
||||
file-extension: yaml
|
||||
sentinel:
|
||||
transport:
|
||||
dashboard: 5.180.253.115:8080
|
||||
dashboard: ${SENTINEL_DASHBOARD_HOST:5.180.253.115}:${SENTINEL_DASHBOARD_PORT:8080}
|
||||
port: 8722
|
||||
eager: true
|
||||
|
||||
datasource:
|
||||
url: jdbc:mysql://5.180.253.115:3306/video_content?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
rocketmq:
|
||||
name-server: 5.180.253.115:9876
|
||||
producer:
|
||||
group: video-content-group
|
||||
url: jdbc:mysql://${MYSQL_HOST:5.180.253.115}:${MYSQL_PORT:3306}/video_content?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:sty@G458221}
|
||||
hikari:
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
idle-timeout: 30000
|
||||
max-lifetime: 1200000
|
||||
connection-timeout: 30000
|
||||
connection-test-query: SELECT 1
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: 5.180.253.115
|
||||
port: 6379
|
||||
password:
|
||||
host: ${REDIS_HOST:5.180.253.115}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:redis_EsJ8FX}
|
||||
database: 2
|
||||
timeout: 10000
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 200
|
||||
max-wait: -1
|
||||
max-idle: 10
|
||||
min-idle: 0
|
||||
|
||||
# 文件上传配置
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 1024MB
|
||||
max-request-size: 1024MB
|
||||
enabled: true
|
||||
|
||||
# RocketMQ配置
|
||||
rocketmq:
|
||||
name-server: ${ROCKETMQ_HOST:5.180.253.115}:${ROCKETMQ_PORT:9876}
|
||||
producer:
|
||||
group: video-content-producer
|
||||
send-message-timeout: 3000
|
||||
retry-times-when-send-failed: 3
|
||||
retry-times-when-send-async-failed: 3
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
type-aliases-package: com.nuaa.video.content.entity
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
cache-enabled: false
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: auto
|
||||
logic-delete-field: is_deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
|
||||
# MinIO配置
|
||||
minio:
|
||||
endpoint: http://5.180.253.115:9002
|
||||
accessKey: minioadmin
|
||||
secretKey: minioadmin
|
||||
bucketName: video-platform
|
||||
endpoint: http://${MINIO_HOST:5.180.253.115}:${MINIO_PORT:9002}
|
||||
accessKey: ${MINIO_ACCESS_KEY:minioadmin}
|
||||
secretKey: ${MINIO_SECRET_KEY:minioadmin}
|
||||
bucketName: ${MINIO_BUCKET:video-platform}
|
||||
|
||||
# FFmpeg配置
|
||||
ffmpeg:
|
||||
binary:
|
||||
path: ${FFMPEG_PATH:/usr/bin/ffmpeg}
|
||||
probe:
|
||||
path: ${FFPROBE_PATH:/usr/bin/ffprobe}
|
||||
temp:
|
||||
path: ${FFMPEG_TEMP_PATH:/tmp/ffmpeg}
|
||||
|
||||
auth:
|
||||
ignore-urls:
|
||||
- /content/video/public/**
|
||||
- /content/category/list
|
||||
- /content/tag/list
|
||||
- /actuator/**
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: '*'
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
|
||||
feign:
|
||||
client:
|
||||
config:
|
||||
default:
|
||||
connectTimeout: 5000
|
||||
readTimeout: 5000
|
||||
circuitbreaker:
|
||||
enabled: true
|
||||
|
||||
# JWT配置与auth模块保持一致
|
||||
jwt:
|
||||
secret: video-platform-secret-key-2024-nuaa
|
||||
expiration: 86400
|
||||
token-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.nuaa.video: info
|
||||
org.springframework.security: info
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentDanmakuMapper">
|
||||
|
||||
<!-- 获取视频指定时间段的弹幕 -->
|
||||
<select id="getVideoTimeDanmaku" resultType="com.nuaa.video.content.entity.ContentDanmaku">
|
||||
<![CDATA[
|
||||
SELECT *
|
||||
FROM content_danmaku
|
||||
WHERE video_id = #{videoId}
|
||||
AND time >= #{startTime}
|
||||
AND time <= #{endTime}
|
||||
ORDER BY time ASC
|
||||
]]>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentTagMapper">
|
||||
|
||||
<!-- 获取视频的标签 -->
|
||||
<select id="getTagsByVideoId" resultType="com.nuaa.video.content.entity.ContentTag">
|
||||
SELECT
|
||||
t.*
|
||||
FROM
|
||||
content_tag t
|
||||
INNER JOIN
|
||||
content_video_tag vt ON t.id = vt.tag_id
|
||||
WHERE
|
||||
vt.video_id = #{videoId}
|
||||
ORDER BY
|
||||
t.id
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentUserFavoriteMapper">
|
||||
|
||||
<!-- 增加收藏夹视频数量 -->
|
||||
<update id="increaseVideoCount">
|
||||
UPDATE content_user_favorite
|
||||
SET video_count = video_count + #{count}
|
||||
WHERE id = #{favoriteId}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoCoinMapper">
|
||||
|
||||
<!-- 获取用户对视频的投币数量 -->
|
||||
<select id="getUserCoinCount" resultType="int">
|
||||
SELECT
|
||||
IFNULL(SUM(amount), 0)
|
||||
FROM
|
||||
content_video_coin
|
||||
WHERE
|
||||
user_id = #{userId}
|
||||
AND video_id = #{videoId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoFavoriteMapper">
|
||||
|
||||
<!-- 检查用户是否已收藏视频 -->
|
||||
<select id="checkUserFavorited" resultType="int">
|
||||
SELECT
|
||||
COUNT(1)
|
||||
FROM
|
||||
content_video_favorite
|
||||
WHERE
|
||||
user_id = #{userId}
|
||||
AND video_id = #{videoId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoLikeMapper">
|
||||
|
||||
<!-- 检查用户是否已点赞视频 -->
|
||||
<select id="checkUserLiked" resultType="int">
|
||||
SELECT
|
||||
COUNT(1)
|
||||
FROM
|
||||
content_video_like
|
||||
WHERE
|
||||
user_id = #{userId}
|
||||
AND video_id = #{videoId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoMapper">
|
||||
|
||||
<!-- 分页查询视频列表 -->
|
||||
<select id="selectVideoList" resultType="com.nuaa.video.content.entity.ContentVideo">
|
||||
SELECT
|
||||
v.*,
|
||||
c.name as category_name
|
||||
FROM
|
||||
content_video v
|
||||
LEFT JOIN
|
||||
content_category c ON v.category_id = c.id
|
||||
<where>
|
||||
<if test="query.status != null">
|
||||
AND v.status = #{query.status}
|
||||
</if>
|
||||
<if test="query.userId != null">
|
||||
AND v.user_id = #{query.userId}
|
||||
</if>
|
||||
<if test="query.categoryId != null">
|
||||
AND v.category_id = #{query.categoryId}
|
||||
</if>
|
||||
<if test="query.isRecommend != null">
|
||||
AND v.is_recommend = #{query.isRecommend}
|
||||
</if>
|
||||
<if test="query.isTop != null">
|
||||
AND v.is_top = #{query.isTop}
|
||||
</if>
|
||||
<if test="query.keyword != null and query.keyword != ''">
|
||||
AND (v.title LIKE CONCAT('%', #{query.keyword}, '%')
|
||||
OR v.description LIKE CONCAT('%', #{query.keyword}, '%'))
|
||||
</if>
|
||||
<if test="query.tagIds != null and query.tagIds.size > 0">
|
||||
AND v.id IN (
|
||||
SELECT
|
||||
DISTINCT vt.video_id
|
||||
FROM
|
||||
content_video_tag vt
|
||||
WHERE
|
||||
vt.tag_id IN
|
||||
<foreach collection="query.tagIds" item="tagId" open="(" separator="," close=")">
|
||||
#{tagId}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
<choose>
|
||||
<when test="query.orderBy != null and query.orderBy != ''">
|
||||
ORDER BY v.${query.orderBy}
|
||||
<if test="query.orderDirection != null and query.orderDirection != ''">
|
||||
${query.orderDirection}
|
||||
</if>
|
||||
</when>
|
||||
<otherwise>
|
||||
ORDER BY v.is_top DESC, v.publish_time DESC
|
||||
</otherwise>
|
||||
</choose>
|
||||
</select>
|
||||
|
||||
<!-- 增加点赞数 -->
|
||||
<update id="increaseLikeCount">
|
||||
UPDATE content_video
|
||||
SET like_count = like_count + #{count}
|
||||
WHERE id = #{videoId}
|
||||
</update>
|
||||
|
||||
<!-- 增加投币数 -->
|
||||
<update id="increaseCoinCount">
|
||||
UPDATE content_video
|
||||
SET coin_count = coin_count + #{count}
|
||||
WHERE id = #{videoId}
|
||||
</update>
|
||||
|
||||
<!-- 增加收藏数 -->
|
||||
<update id="increaseFavoriteCount">
|
||||
UPDATE content_video
|
||||
SET favorite_count = favorite_count + #{count}
|
||||
WHERE id = #{videoId}
|
||||
</update>
|
||||
|
||||
<!-- 增加评论数 -->
|
||||
<update id="increaseCommentCount">
|
||||
UPDATE content_video
|
||||
SET comment_count = comment_count + #{count}
|
||||
WHERE id = #{videoId}
|
||||
</update>
|
||||
|
||||
<!-- 增加播放数 -->
|
||||
<update id="increaseViewCount">
|
||||
UPDATE content_video
|
||||
SET view_count = view_count + #{count}
|
||||
WHERE id = #{videoId}
|
||||
</update>
|
||||
|
||||
<!-- 增加分享数 -->
|
||||
<update id="increaseShareCount">
|
||||
UPDATE content_video
|
||||
SET share_count = share_count + #{count}
|
||||
WHERE id = #{videoId}
|
||||
</update>
|
||||
|
||||
<!-- 增加弹幕数 -->
|
||||
<update id="increaseDanmakuCount">
|
||||
UPDATE content_video
|
||||
SET danmaku_count = danmaku_count + #{count}
|
||||
WHERE id = #{videoId}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoPlayRecordMapper">
|
||||
|
||||
<!-- 获取用户播放过的视频ID列表 -->
|
||||
<select id="selectUserPlayedVideoIds" resultType="java.lang.Long">
|
||||
SELECT video_id
|
||||
FROM content_video_play_record
|
||||
WHERE user_id = #{userId}
|
||||
ORDER BY update_time DESC
|
||||
LIMIT #{limit}
|
||||
</select>
|
||||
|
||||
<!-- 统计按小时的播放量 -->
|
||||
<select id="countHourlyPlays" resultType="java.util.Map">
|
||||
SELECT
|
||||
HOUR(create_time) as hour,
|
||||
COUNT(1) as count
|
||||
FROM
|
||||
content_video_play_record
|
||||
WHERE
|
||||
video_id = #{videoId}
|
||||
AND DATE(create_time) = #{date}
|
||||
GROUP BY
|
||||
HOUR(create_time)
|
||||
ORDER BY
|
||||
hour
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoReviewMapper">
|
||||
|
||||
<!-- 查询视频最新审核记录 -->
|
||||
<select id="selectLatestReview" resultType="com.nuaa.video.content.entity.ContentVideoReview">
|
||||
SELECT *
|
||||
FROM content_video_review
|
||||
WHERE video_id = #{videoId}
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 根据审核状态查询视频审核记录 -->
|
||||
<select id="selectReviewsByStatus" resultType="com.nuaa.video.content.entity.ContentVideoReview">
|
||||
SELECT *
|
||||
FROM content_video_review
|
||||
WHERE status = #{status}
|
||||
<if test="reviewerId != null">
|
||||
AND reviewer_id = #{reviewerId}
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoStatDailyMapper">
|
||||
|
||||
<!-- 获取视频日期范围内的统计数据 -->
|
||||
<select id="selectStatsByDateRange" resultType="com.nuaa.video.content.entity.ContentVideoStatDaily">
|
||||
SELECT *
|
||||
FROM content_video_stat_daily
|
||||
WHERE video_id = #{videoId}
|
||||
AND stat_date BETWEEN #{startDate} AND #{endDate}
|
||||
ORDER BY stat_date ASC
|
||||
</select>
|
||||
|
||||
<!-- 获取视频总互动统计 -->
|
||||
<select id="selectVideoInteractionStats" resultType="java.util.Map">
|
||||
SELECT
|
||||
SUM(like_increment) as totalLikes,
|
||||
SUM(coin_increment) as totalCoins,
|
||||
SUM(favorite_increment) as totalFavorites,
|
||||
SUM(comment_increment) as totalComments,
|
||||
SUM(view_increment) as totalViews,
|
||||
SUM(share_increment) as totalShares,
|
||||
SUM(danmaku_increment) as totalDanmakus
|
||||
FROM
|
||||
content_video_stat_daily
|
||||
WHERE
|
||||
video_id = #{videoId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.content.mapper.ContentVideoTagMapper">
|
||||
|
||||
<!-- 批量插入视频标签关联 -->
|
||||
<insert id="batchInsert">
|
||||
INSERT INTO content_video_tag (video_id, tag_id, created_by, create_time)
|
||||
VALUES
|
||||
<foreach collection="tagIds" item="tagId" separator=",">
|
||||
(#{videoId}, #{tagId}, #{createdBy}, NOW())
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user