feat(admin): Implement admin module foundation
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分类管理控制器 - 供管理后台调用
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/content/category/admin")
|
||||
@RequiredArgsConstructor
|
||||
public class AdminCategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
/**
|
||||
* 获取分类列表 - 管理接口
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<List<CategoryVO>> getCategoryList() {
|
||||
return categoryService.getCategoryList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分类 - 管理接口
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Void> addCategory(@RequestBody ContentCategory category) {
|
||||
return categoryService.addCategory(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分类 - 管理接口
|
||||
*/
|
||||
@PutMapping
|
||||
public Result<Void> updateCategory(@RequestBody ContentCategory category) {
|
||||
return categoryService.updateCategory(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类 - 管理接口
|
||||
*/
|
||||
@DeleteMapping("/{categoryId}")
|
||||
public Result<Void> deleteCategory(@PathVariable Long categoryId) {
|
||||
return categoryService.deleteCategory(categoryId);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
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.ContentVideo;
|
||||
import com.nuaa.video.content.dto.VideoQueryDTO;
|
||||
import com.nuaa.video.content.service.VideoService;
|
||||
import com.nuaa.video.content.vo.VideoInfoVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 视频管理控制器 - 供管理后台调用
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/content/video/admin")
|
||||
@RequiredArgsConstructor
|
||||
public class AdminVideoController {
|
||||
|
||||
private final VideoService videoService;
|
||||
|
||||
/**
|
||||
* 获取视频列表 - 管理接口
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<Page<VideoInfoVO>> getVideoList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "categoryId", required = false) Long categoryId,
|
||||
@RequestParam(value = "status", required = false) Integer status) {
|
||||
|
||||
VideoQueryDTO queryDTO = new VideoQueryDTO();
|
||||
queryDTO.setKeyword(keyword);
|
||||
queryDTO.setCategoryId(categoryId);
|
||||
queryDTO.setStatus(status);
|
||||
queryDTO.setPageNum(page);
|
||||
queryDTO.setPageSize(size);
|
||||
|
||||
Page<ContentVideo> pageParam = new Page<>(page, size);
|
||||
return videoService.getAdminVideoList(pageParam, queryDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频详情 - 管理接口
|
||||
*/
|
||||
@GetMapping("/{videoId}")
|
||||
public Result<VideoInfoVO> getVideoDetail(@PathVariable Long videoId) {
|
||||
return videoService.getAdminVideoDetail(videoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核视频 - 管理接口
|
||||
*/
|
||||
@PostMapping("/review/{videoId}")
|
||||
public Result<Void> reviewVideo(
|
||||
@PathVariable Long videoId,
|
||||
@RequestParam Integer status,
|
||||
@RequestParam(required = false) String reason) {
|
||||
return videoService.reviewVideo(videoId, null, status, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下架视频 - 管理接口
|
||||
*/
|
||||
@PutMapping("/disable/{videoId}")
|
||||
public Result<Void> disableVideo(@PathVariable Long videoId) {
|
||||
return videoService.disableVideo(videoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频统计信息 - 管理接口
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
public Result<Object> getVideoStats() {
|
||||
return videoService.getVideoStats();
|
||||
}
|
||||
}
|
||||
@@ -155,4 +155,24 @@ public interface VideoService extends IService<ContentVideo> {
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> reviewVideo(Long videoId, Long reviewerId, Integer status, String reason);
|
||||
|
||||
/**
|
||||
* 获取视频列表 - 管理接口
|
||||
*/
|
||||
Result<Page<VideoInfoVO>> getAdminVideoList(Page<ContentVideo> page, VideoQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 获取视频详情 - 管理接口
|
||||
*/
|
||||
Result<VideoInfoVO> getAdminVideoDetail(Long videoId);
|
||||
|
||||
/**
|
||||
* 下架视频 - 管理接口
|
||||
*/
|
||||
Result<Void> disableVideo(Long videoId);
|
||||
|
||||
/**
|
||||
* 获取视频统计信息 - 管理接口
|
||||
*/
|
||||
Result<Object> getVideoStats();
|
||||
}
|
||||
+193
@@ -27,8 +27,10 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -724,4 +726,195 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
// 在 com.nuaa.video.content.service.impl.VideoServiceImpl 类中添加:
|
||||
|
||||
@Override
|
||||
public Result<Page<VideoInfoVO>> getAdminVideoList(Page<ContentVideo> page, VideoQueryDTO queryDTO) {
|
||||
// 管理员可查看所有状态的视频,不需要过滤status
|
||||
Page<ContentVideo> videoPage = videoMapper.selectVideoList(page, queryDTO);
|
||||
|
||||
// 转换为VO
|
||||
Page<VideoInfoVO> voPage = new Page<>();
|
||||
BeanUtils.copyProperties(videoPage, voPage, "records");
|
||||
|
||||
List<VideoInfoVO> voList = new ArrayList<>();
|
||||
for (ContentVideo video : videoPage.getRecords()) {
|
||||
VideoInfoVO vo = new VideoInfoVO();
|
||||
BeanUtils.copyProperties(video, vo);
|
||||
|
||||
// 获取分类名称
|
||||
if (video.getCategoryId() != null) {
|
||||
ContentCategory category = categoryMapper.selectById(video.getCategoryId());
|
||||
if (category != null) {
|
||||
vo.setCategoryName(category.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 获取标签
|
||||
Result<List<TagVO>> tagResult = tagService.getVideoTags(video.getId());
|
||||
if (tagResult.getCode() == 200 && tagResult.getData() != null) {
|
||||
List<TagVO> tags = tagResult.getData();
|
||||
vo.setTagIds(tags.stream().map(TagVO::getId).collect(Collectors.toList()));
|
||||
vo.setTagNames(tags.stream().map(TagVO::getName).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
try {
|
||||
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||
if (userResult.getCode() == 200 && 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.getMessage());
|
||||
}
|
||||
|
||||
voList.add(vo);
|
||||
}
|
||||
|
||||
voPage.setRecords(voList);
|
||||
|
||||
return Result.success(voPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<VideoInfoVO> getAdminVideoDetail(Long videoId) {
|
||||
// 查询视频信息
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 转换为VO
|
||||
VideoInfoVO vo = new VideoInfoVO();
|
||||
BeanUtils.copyProperties(video, vo);
|
||||
|
||||
// 获取分类名称
|
||||
if (video.getCategoryId() != null) {
|
||||
ContentCategory category = categoryMapper.selectById(video.getCategoryId());
|
||||
if (category != null) {
|
||||
vo.setCategoryName(category.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 获取标签
|
||||
Result<List<TagVO>> tagResult = tagService.getVideoTags(video.getId());
|
||||
if (tagResult.getCode() == 200 && tagResult.getData() != null) {
|
||||
List<TagVO> tags = tagResult.getData();
|
||||
vo.setTagIds(tags.stream().map(TagVO::getId).collect(Collectors.toList()));
|
||||
vo.setTagNames(tags.stream().map(TagVO::getName).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
try {
|
||||
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||
if (userResult.getCode() == 200 && 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.getMessage());
|
||||
}
|
||||
|
||||
// 获取审核记录
|
||||
ContentVideoReview latestReview = videoReviewMapper.selectOne(
|
||||
new LambdaQueryWrapper<ContentVideoReview>()
|
||||
.eq(ContentVideoReview::getVideoId, videoId)
|
||||
.orderByDesc(ContentVideoReview::getCreateTime)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
// 可以将审核记录信息添加到VO中返回
|
||||
if (latestReview != null) {
|
||||
vo.setReviewStatus(latestReview.getStatus());
|
||||
vo.setReviewReason(latestReview.getReason());
|
||||
vo.setReviewerId(latestReview.getReviewerId());
|
||||
vo.setReviewTime(latestReview.getCreateTime());
|
||||
}
|
||||
|
||||
return Result.success(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> disableVideo(Long videoId) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
// 更新视频状态为已下架
|
||||
ContentVideo updateVideo = new ContentVideo();
|
||||
updateVideo.setId(videoId);
|
||||
updateVideo.setStatus(2); // 已下架
|
||||
updateVideo.setUpdatedBy("admin");
|
||||
updateVideo.setUpdateTime(LocalDateTime.now());
|
||||
updateById(updateVideo);
|
||||
|
||||
// 记录操作日志
|
||||
ContentVideoReview review = new ContentVideoReview();
|
||||
review.setVideoId(videoId);
|
||||
review.setReviewerId(0L); // 系统/管理员ID
|
||||
review.setStatus(2); // 下架
|
||||
review.setReason("管理员下架操作");
|
||||
review.setCreatedBy("admin");
|
||||
review.setCreateTime(LocalDateTime.now());
|
||||
videoReviewMapper.insert(review);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Object> getVideoStats() {
|
||||
// 收集视频统计数据
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
|
||||
// 总视频数
|
||||
long totalVideos = count();
|
||||
stats.put("totalVideos", totalVideos);
|
||||
|
||||
// 今日新增视频
|
||||
LocalDate today = LocalDate.now();
|
||||
long todayNewVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||
.ge(ContentVideo::getCreateTime, today.atStartOfDay()));
|
||||
stats.put("todayNewVideos", todayNewVideos);
|
||||
|
||||
// 待审核视频数
|
||||
long pendingReviewVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||
.eq(ContentVideo::getStatus, 0));
|
||||
stats.put("pendingReviewVideos", pendingReviewVideos);
|
||||
|
||||
// 已发布视频数
|
||||
long publishedVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||
.eq(ContentVideo::getStatus, 1));
|
||||
stats.put("publishedVideos", publishedVideos);
|
||||
|
||||
// 已下架视频数
|
||||
long disabledVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||
.eq(ContentVideo::getStatus, 2));
|
||||
stats.put("disabledVideos", disabledVideos);
|
||||
|
||||
// 审核未通过视频数
|
||||
long rejectedVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||
.eq(ContentVideo::getStatus, 3));
|
||||
stats.put("rejectedVideos", rejectedVideos);
|
||||
|
||||
// 各分类视频数量分布
|
||||
Map<String, Long> categoryDistribution = new HashMap<>();
|
||||
List<ContentCategory> categories = categoryMapper.selectList(null);
|
||||
for (ContentCategory category : categories) {
|
||||
long count = count(new LambdaQueryWrapper<ContentVideo>()
|
||||
.eq(ContentVideo::getCategoryId, category.getId()));
|
||||
categoryDistribution.put(category.getName(), count);
|
||||
}
|
||||
stats.put("categoryDistribution", categoryDistribution);
|
||||
|
||||
return Result.success(stats);
|
||||
}
|
||||
}
|
||||
@@ -142,4 +142,24 @@ public class VideoInfoVO implements Serializable {
|
||||
* 发布时间
|
||||
*/
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
/**
|
||||
* 审核员ID
|
||||
*/
|
||||
private Long reviewerId;
|
||||
|
||||
/**
|
||||
* 审核状态(1:通过,2:不通过)
|
||||
*/
|
||||
private Integer reviewStatus;
|
||||
|
||||
/**
|
||||
* 审核原因
|
||||
*/
|
||||
private String reviewReason;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime reviewTime;
|
||||
}
|
||||
Reference in New Issue
Block a user