fix(backend): Restore service flow and admin management integration
This commit is contained in:
+24
-1
@@ -2,8 +2,11 @@ package com.nuaa.video.content.config;
|
||||
|
||||
import com.nuaa.video.content.filter.JwtAuthenticationFilter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
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;
|
||||
@@ -15,11 +18,15 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
@@ -29,15 +36,31 @@ public class SecurityConfig {
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(
|
||||
"/content/video/public/**",
|
||||
"/content/category/tree",
|
||||
"/content/category/list",
|
||||
"/content/tag/list",
|
||||
"/content/tag/page",
|
||||
"/content/danmaku/all/**",
|
||||
"/content/danmaku/time",
|
||||
"/content/stat/**",
|
||||
"/actuator/**",
|
||||
"/error"
|
||||
).permitAll()
|
||||
.requestMatchers(
|
||||
"/content/video/count/comment/**",
|
||||
"/content/video/review/**",
|
||||
"/content/video/admin/**",
|
||||
"/content/category/admin/**"
|
||||
).access((authentication, context) ->
|
||||
new AuthorizationDecision(isInternalRequest(context.getRequest().getHeader("X-Internal-Token"))))
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInternalRequest(String internalToken) {
|
||||
return internalServiceToken.equals(internalToken);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-5
@@ -4,8 +4,10 @@ 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.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -19,13 +21,19 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class AdminCategoryController {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
/**
|
||||
* 获取分类列表 - 管理接口
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<List<CategoryVO>> getCategoryList() {
|
||||
public Result<List<CategoryVO>> getCategoryList(HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return categoryService.getCategoryList();
|
||||
}
|
||||
|
||||
@@ -33,7 +41,10 @@ public class AdminCategoryController {
|
||||
* 添加分类 - 管理接口
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Void> addCategory(@RequestBody ContentCategory category) {
|
||||
public Result<Void> addCategory(@RequestBody ContentCategory category, HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return categoryService.addCategory(category);
|
||||
}
|
||||
|
||||
@@ -41,7 +52,10 @@ public class AdminCategoryController {
|
||||
* 更新分类 - 管理接口
|
||||
*/
|
||||
@PutMapping
|
||||
public Result<Void> updateCategory(@RequestBody ContentCategory category) {
|
||||
public Result<Void> updateCategory(@RequestBody ContentCategory category, HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return categoryService.updateCategory(category);
|
||||
}
|
||||
|
||||
@@ -49,7 +63,14 @@ public class AdminCategoryController {
|
||||
* 删除分类 - 管理接口
|
||||
*/
|
||||
@DeleteMapping("/{categoryId}")
|
||||
public Result<Void> deleteCategory(@PathVariable Long categoryId) {
|
||||
public Result<Void> deleteCategory(@PathVariable Long categoryId, HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return categoryService.deleteCategory(categoryId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInternalRequest(HttpServletRequest request) {
|
||||
return internalServiceToken.equals(request.getHeader("X-Internal-Token"));
|
||||
}
|
||||
}
|
||||
|
||||
+47
-8
@@ -6,8 +6,10 @@ 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 jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
@@ -19,6 +21,9 @@ import org.springframework.web.bind.annotation.*;
|
||||
@RequiredArgsConstructor
|
||||
public class AdminVideoController {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final VideoService videoService;
|
||||
|
||||
/**
|
||||
@@ -30,7 +35,11 @@ public class AdminVideoController {
|
||||
@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) {
|
||||
@RequestParam(value = "status", required = false) Integer status,
|
||||
HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
|
||||
VideoQueryDTO queryDTO = new VideoQueryDTO();
|
||||
queryDTO.setKeyword(keyword);
|
||||
@@ -47,7 +56,10 @@ public class AdminVideoController {
|
||||
* 获取视频详情 - 管理接口
|
||||
*/
|
||||
@GetMapping("/{videoId}")
|
||||
public Result<VideoInfoVO> getVideoDetail(@PathVariable Long videoId) {
|
||||
public Result<VideoInfoVO> getVideoDetail(@PathVariable Long videoId, HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return videoService.getAdminVideoDetail(videoId);
|
||||
}
|
||||
|
||||
@@ -58,23 +70,50 @@ public class AdminVideoController {
|
||||
public Result<Void> reviewVideo(
|
||||
@PathVariable Long videoId,
|
||||
@RequestParam Integer status,
|
||||
@RequestParam(required = false) String reason) {
|
||||
return videoService.reviewVideo(videoId, null, status, reason);
|
||||
@RequestParam(required = false) String reason,
|
||||
HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return videoService.reviewVideo(videoId, getAdminId(request), status, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下架视频 - 管理接口
|
||||
*/
|
||||
@PutMapping("/disable/{videoId}")
|
||||
public Result<Void> disableVideo(@PathVariable Long videoId) {
|
||||
return videoService.disableVideo(videoId);
|
||||
public Result<Void> disableVideo(@PathVariable Long videoId, HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return videoService.disableVideo(videoId, getAdminId(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频统计信息 - 管理接口
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
public Result<Object> getVideoStats() {
|
||||
public Result<Object> getVideoStats(HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return videoService.getVideoStats();
|
||||
}
|
||||
}
|
||||
|
||||
private Long getAdminId(HttpServletRequest request) {
|
||||
String adminIdHeader = request.getHeader("X-Admin-Id");
|
||||
if (adminIdHeader != null && !adminIdHeader.isBlank()) {
|
||||
try {
|
||||
return Long.parseLong(adminIdHeader);
|
||||
} catch (NumberFormatException ignored) {
|
||||
log.warn("非法的X-Admin-Id请求头: {}", adminIdHeader);
|
||||
}
|
||||
}
|
||||
Object adminId = request.getAttribute("adminId");
|
||||
return adminId instanceof Long ? (Long) adminId : 0L;
|
||||
}
|
||||
|
||||
private boolean isInternalRequest(HttpServletRequest request) {
|
||||
return internalServiceToken.equals(request.getHeader("X-Internal-Token"));
|
||||
}
|
||||
}
|
||||
|
||||
+28
-7
@@ -13,6 +13,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -25,6 +26,9 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
@RequiredArgsConstructor
|
||||
public class VideoController {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final VideoService videoService;
|
||||
private final ContentVideoMapper videoMapper;
|
||||
|
||||
@@ -235,9 +239,12 @@ public class VideoController {
|
||||
@RequestParam Integer status,
|
||||
@RequestParam(required = false) String reason,
|
||||
HttpServletRequest request) {
|
||||
Long reviewerId = getUserIdFromRequest(request);
|
||||
if (!internalServiceToken.equals(request.getHeader("X-Internal-Token"))) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
Long reviewerId = getAdminIdFromRequest(request);
|
||||
if (reviewerId == null) {
|
||||
return Result.error("未获取到用户ID");
|
||||
return Result.error("未获取到审核员ID");
|
||||
}
|
||||
return videoService.reviewVideo(videoId, reviewerId, status, reason);
|
||||
}
|
||||
@@ -250,10 +257,8 @@ public class VideoController {
|
||||
@PathVariable Long videoId,
|
||||
@PathVariable Integer count,
|
||||
HttpServletRequest request) {
|
||||
// 验证权限(实际可以根据需要添加更复杂的权限验证)
|
||||
Long userId = getUserIdFromRequest(request);
|
||||
if (userId == null) {
|
||||
return Result.error("未登录");
|
||||
if (!internalServiceToken.equals(request.getHeader("X-Internal-Token"))) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -281,4 +286,20 @@ public class VideoController {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Long getAdminIdFromRequest(HttpServletRequest request) {
|
||||
String adminIdHeader = request.getHeader("X-Admin-Id");
|
||||
if (adminIdHeader != null && !adminIdHeader.isBlank()) {
|
||||
try {
|
||||
return Long.parseLong(adminIdHeader);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("非法的X-Admin-Id请求头: {}", adminIdHeader);
|
||||
}
|
||||
}
|
||||
Object adminIdObj = request.getAttribute("adminId");
|
||||
if (adminIdObj instanceof Long) {
|
||||
return (Long) adminIdObj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -32,7 +32,7 @@ public interface UserFeignClient {
|
||||
@PostMapping("/user/profile/count/video/{userId}/{count}")
|
||||
Result<Void> increaseVideoCount(@PathVariable("userId") Long userId,
|
||||
@PathVariable("count") Integer count,
|
||||
@RequestHeader("Authorization") String token);
|
||||
@RequestHeader("X-Internal-Token") String internalToken);
|
||||
|
||||
/**
|
||||
* 增加用户获赞数量
|
||||
@@ -44,7 +44,7 @@ public interface UserFeignClient {
|
||||
@PostMapping("/user/profile/count/like/{userId}/{count}")
|
||||
Result<Void> increaseLikeCount(@PathVariable("userId") Long userId,
|
||||
@PathVariable("count") Integer count,
|
||||
@RequestHeader("Authorization") String token);
|
||||
@RequestHeader("X-Internal-Token") String internalToken);
|
||||
|
||||
/**
|
||||
* 增加用户收藏数量
|
||||
@@ -56,5 +56,5 @@ public interface UserFeignClient {
|
||||
@PostMapping("/user/profile/count/favorite/{userId}/{count}")
|
||||
Result<Void> increaseFavoriteCount(@PathVariable("userId") Long userId,
|
||||
@PathVariable("count") Integer count,
|
||||
@RequestHeader("Authorization") String token);
|
||||
}
|
||||
@RequestHeader("X-Internal-Token") String internalToken);
|
||||
}
|
||||
|
||||
+4
-4
@@ -23,22 +23,22 @@ public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeign
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> increaseVideoCount(Long userId, Integer count, String token) {
|
||||
public Result<Void> increaseVideoCount(Long userId, Integer count, String internalToken) {
|
||||
log.error("增加用户视频数量失败,用户ID:{}, 数量:{}", userId, count);
|
||||
return Result.error("更新用户视频数量失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> increaseLikeCount(Long userId, Integer count, String token) {
|
||||
public Result<Void> increaseLikeCount(Long userId, Integer count, String internalToken) {
|
||||
log.error("增加用户获赞数量失败,用户ID:{}, 数量:{}", userId, count);
|
||||
return Result.error("更新用户获赞数量失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> increaseFavoriteCount(Long userId, Integer count, String token) {
|
||||
public Result<Void> increaseFavoriteCount(Long userId, Integer count, String internalToken) {
|
||||
log.error("增加用户收藏数量失败,用户ID:{}, 数量:{}", userId, count);
|
||||
return Result.error("更新用户收藏数量失败");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-3
@@ -21,6 +21,7 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT认证过滤器
|
||||
@@ -29,7 +30,7 @@ import java.util.List;
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Value("${jwt.secret:video-platform-secret-key-2024-nuaa}")
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
@@ -55,7 +56,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
String username = claims.get("username", String.class);
|
||||
|
||||
// 设置用户权限
|
||||
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
List<String> authorityClaims = claims.get("authorities", List.class);
|
||||
List<SimpleGrantedAuthority> authorities = authorityClaims == null
|
||||
? new ArrayList<>()
|
||||
: authorityClaims.stream()
|
||||
.filter(StringUtils::hasText)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
// 创建认证对象
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
@@ -70,6 +77,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
// 在请求属性中存储用户ID
|
||||
request.setAttribute("userId", userId);
|
||||
request.setAttribute("username", username);
|
||||
if (!authorities.isEmpty()) {
|
||||
request.setAttribute("adminId", userId);
|
||||
}
|
||||
|
||||
log.debug("认证成功: 用户ID={}, 用户名={}", userId, username);
|
||||
} catch (Exception e) {
|
||||
@@ -99,4 +109,4 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-2
@@ -136,7 +136,30 @@ public class VideoMessageProducer {
|
||||
videoMapper.increaseViewCount(videoId, count);
|
||||
log.info("直接更新数据库:视频播放计数 +{}, videoId={}", count, videoId);
|
||||
break;
|
||||
// 其他case处理...
|
||||
case "like":
|
||||
videoMapper.increaseLikeCount(videoId, count);
|
||||
log.info("直接更新数据库:视频点赞计数 {:+d}, videoId={}", count, videoId);
|
||||
break;
|
||||
case "coin":
|
||||
videoMapper.increaseCoinCount(videoId, count);
|
||||
log.info("直接更新数据库:视频投币计数 {:+d}, videoId={}", count, videoId);
|
||||
break;
|
||||
case "favorite":
|
||||
videoMapper.increaseFavoriteCount(videoId, count);
|
||||
log.info("直接更新数据库:视频收藏计数 {:+d}, videoId={}", count, videoId);
|
||||
break;
|
||||
case "share":
|
||||
videoMapper.increaseShareCount(videoId, count);
|
||||
log.info("直接更新数据库:视频分享计数 {:+d}, videoId={}", count, videoId);
|
||||
break;
|
||||
case "danmaku":
|
||||
videoMapper.increaseDanmakuCount(videoId, count);
|
||||
log.info("直接更新数据库:视频弹幕计数 {:+d}, videoId={}", count, videoId);
|
||||
break;
|
||||
default:
|
||||
log.warn("未识别的视频统计动作,无法降级更新数据库: action={}, videoId={}, count={}",
|
||||
action, videoId, count);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("降级处理失败: 直接更新数据库出错", e);
|
||||
@@ -156,4 +179,4 @@ public class VideoMessageProducer {
|
||||
private String action; // play, like, favorite, coin, share, danmaku
|
||||
private Integer count = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -169,10 +169,10 @@ public interface VideoService extends IService<ContentVideo> {
|
||||
/**
|
||||
* 下架视频 - 管理接口
|
||||
*/
|
||||
Result<Void> disableVideo(Long videoId);
|
||||
Result<Void> disableVideo(Long videoId, Long adminId);
|
||||
|
||||
/**
|
||||
* 获取视频统计信息 - 管理接口
|
||||
*/
|
||||
Result<Object> getVideoStats();
|
||||
}
|
||||
}
|
||||
|
||||
+87
-30
@@ -22,9 +22,12 @@ 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.factory.annotation.Value;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@@ -33,6 +36,8 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -43,6 +48,9 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVideo> implements VideoService {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final ContentVideoMapper videoMapper;
|
||||
private final ContentCategoryMapper categoryMapper;
|
||||
private final ContentVideoTagMapper videoTagMapper;
|
||||
@@ -136,8 +144,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
videoTagMapper.batchInsert(videoId, uploadDTO.getTagIds(), userId.toString());
|
||||
}
|
||||
|
||||
// 更新用户视频数量
|
||||
userFeignClient.increaseVideoCount(userId, 1, "Bearer ");
|
||||
updateUserVideoCountAsync(userId, 1);
|
||||
|
||||
// 发送视频处理消息
|
||||
// videoMessageProducer.sendVideoProcessMessage(videoId);
|
||||
@@ -169,7 +176,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
|
||||
// 获取标签列表
|
||||
Result<List<TagVO>> tagResult = tagService.getVideoTags(id);
|
||||
if (tagResult.getCode() == ResultCode.SUCCESS.getCode() && tagResult.getData() != null) {
|
||||
if (isSuccess(tagResult.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()));
|
||||
@@ -178,7 +185,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
// 获取用户信息
|
||||
try {
|
||||
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||
if (userResult.getCode() == ResultCode.SUCCESS.getCode() && userResult.getData() != null) {
|
||||
if (isSuccess(userResult.getCode()) && userResult.getData() != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||
videoInfoVO.setUserNickname((String) userData.get("nickname"));
|
||||
@@ -200,14 +207,6 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
videoInfoVO.setIsCoined(coinCount > 0);
|
||||
}
|
||||
|
||||
// 增加播放数
|
||||
VideoMessageProducer.VideoStatsMessage message = new VideoMessageProducer.VideoStatsMessage();
|
||||
message.setVideoId(id);
|
||||
message.setUserId(userId);
|
||||
message.setAction("play");
|
||||
message.setCount(1);
|
||||
videoMessageProducer.sendVideoStatsMessage(message);
|
||||
|
||||
return Result.success(videoInfoVO);
|
||||
}
|
||||
|
||||
@@ -218,6 +217,11 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
queryDTO.setStatus(VideoStatusEnum.PUBLISHED.getCode());
|
||||
}
|
||||
|
||||
return buildVideoListResult(page, queryDTO, userId);
|
||||
}
|
||||
|
||||
private Result<Page<VideoListVO>> buildVideoListResult(Page<ContentVideo> page, VideoQueryDTO queryDTO, Long userId) {
|
||||
|
||||
// 查询视频列表
|
||||
Page<ContentVideo> videoPage = videoMapper.selectVideoList(page, queryDTO);
|
||||
List<ContentVideo> records = videoPage.getRecords();
|
||||
@@ -233,7 +237,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
|
||||
// 获取标签列表
|
||||
Result<List<TagVO>> tagResult = tagService.getVideoTags(video.getId());
|
||||
if (tagResult.getCode() == ResultCode.SUCCESS.getCode() && tagResult.getData() != null) {
|
||||
if (isSuccess(tagResult.getCode()) && tagResult.getData() != null) {
|
||||
List<TagVO> tags = tagResult.getData();
|
||||
vo.setTagNames(tags.stream().map(TagVO::getName).collect(Collectors.toList()));
|
||||
}
|
||||
@@ -241,7 +245,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
// 获取用户信息
|
||||
try {
|
||||
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||
if (userResult.getCode() == ResultCode.SUCCESS.getCode() && userResult.getData() != null) {
|
||||
if (isSuccess(userResult.getCode()) && userResult.getData() != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||
vo.setUserNickname((String) userData.get("nickname"));
|
||||
@@ -368,8 +372,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
.eq(ContentVideoPlayRecord::getVideoId, id)
|
||||
);
|
||||
|
||||
// 更新用户视频数量
|
||||
userFeignClient.increaseVideoCount(userId, -1, "Bearer ");
|
||||
updateUserVideoCountAsync(userId, -1);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
@@ -406,8 +409,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户获赞数
|
||||
userFeignClient.increaseLikeCount(video.getUserId(), 1, "Bearer ");
|
||||
updateUserLikeCountAsync(video.getUserId(), 1);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
@@ -444,8 +446,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户获赞数
|
||||
userFeignClient.increaseLikeCount(video.getUserId(), -1, "Bearer ");
|
||||
updateUserLikeCountAsync(video.getUserId(), -1);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
@@ -548,8 +549,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户收藏数
|
||||
userFeignClient.increaseFavoriteCount(userId, 1, "Bearer ");
|
||||
updateUserFavoriteCountAsync(userId, 1);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
@@ -589,8 +589,7 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
}}
|
||||
);
|
||||
|
||||
// 更新用户收藏数
|
||||
userFeignClient.increaseFavoriteCount(userId, -favorites.size(), "Bearer ");
|
||||
updateUserFavoriteCountAsync(userId, -favorites.size());
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
@@ -687,12 +686,16 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
queryDTO.setStatus(VideoStatusEnum.PUBLISHED.getCode());
|
||||
}
|
||||
|
||||
return getVideoList(page, queryDTO, currentUserId);
|
||||
return buildVideoListResult(page, queryDTO, currentUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> reviewVideo(Long videoId, Long reviewerId, Integer status, String reason) {
|
||||
if (reviewerId == null) {
|
||||
reviewerId = 0L;
|
||||
}
|
||||
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
@@ -842,28 +845,30 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> disableVideo(Long videoId) {
|
||||
public Result<Void> disableVideo(Long videoId, Long adminId) {
|
||||
// 查询视频是否存在
|
||||
ContentVideo video = getById(videoId);
|
||||
if (video == null) {
|
||||
return Result.error("视频不存在");
|
||||
}
|
||||
|
||||
Long operatorId = adminId != null && adminId > 0 ? adminId : 0L;
|
||||
|
||||
// 更新视频状态为已下架
|
||||
ContentVideo updateVideo = new ContentVideo();
|
||||
updateVideo.setId(videoId);
|
||||
updateVideo.setStatus(2); // 已下架
|
||||
updateVideo.setUpdatedBy("admin");
|
||||
updateVideo.setUpdatedBy(operatorId.toString());
|
||||
updateVideo.setUpdateTime(LocalDateTime.now());
|
||||
updateById(updateVideo);
|
||||
|
||||
// 记录操作日志
|
||||
ContentVideoReview review = new ContentVideoReview();
|
||||
review.setVideoId(videoId);
|
||||
review.setReviewerId(0L); // 系统/管理员ID
|
||||
review.setReviewerId(operatorId);
|
||||
review.setStatus(2); // 下架
|
||||
review.setReason("管理员下架操作");
|
||||
review.setCreatedBy("admin");
|
||||
review.setCreatedBy(operatorId.toString());
|
||||
review.setCreateTime(LocalDateTime.now());
|
||||
videoReviewMapper.insert(review);
|
||||
|
||||
@@ -917,4 +922,56 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
||||
|
||||
return Result.success(stats);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUserVideoCountAsync(Long userId, Integer count) {
|
||||
runAfterCommitAsync("更新用户视频数", () -> {
|
||||
Result<Void> countResult = userFeignClient.increaseVideoCount(userId, count, internalServiceToken);
|
||||
if (countResult == null || countResult.getCode() != 200) {
|
||||
log.warn("异步更新用户视频数失败,userId={}, count={}, result={}", userId, count, countResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateUserLikeCountAsync(Long userId, Integer count) {
|
||||
runAfterCommitAsync("更新用户获赞数", () -> {
|
||||
Result<Void> countResult = userFeignClient.increaseLikeCount(userId, count, internalServiceToken);
|
||||
if (countResult == null || countResult.getCode() != 200) {
|
||||
log.warn("异步更新用户获赞数失败,userId={}, count={}, result={}", userId, count, countResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateUserFavoriteCountAsync(Long userId, Integer count) {
|
||||
runAfterCommitAsync("更新用户收藏数", () -> {
|
||||
Result<Void> countResult = userFeignClient.increaseFavoriteCount(userId, count, internalServiceToken);
|
||||
if (countResult == null || countResult.getCode() != 200) {
|
||||
log.warn("异步更新用户收藏数失败,userId={}, count={}, result={}", userId, count, countResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void runAfterCommitAsync(String action, Runnable task) {
|
||||
Runnable wrappedTask = () -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
log.warn("{}异常", action, e);
|
||||
}
|
||||
});
|
||||
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
wrappedTask.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wrappedTask.run();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuccess(Integer code) {
|
||||
return Objects.equals(code, ResultCode.SUCCESS.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_content?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
password: ${MYSQL_ROOT_PASSWORD}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:redis}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: redis_EsJ8FX
|
||||
password: ${REDIS_PASSWORD}
|
||||
database: 2
|
||||
timeout: 10000
|
||||
lettuce:
|
||||
@@ -57,7 +57,6 @@ mybatis-plus:
|
||||
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
|
||||
@@ -68,9 +67,9 @@ mybatis-plus:
|
||||
# MinIO配置
|
||||
minio:
|
||||
endpoint: http://${MINIO_HOST:minio}:${MINIO_PORT:9000}
|
||||
accessKey: ${MINIO_ACCESS_KEY:minioadmin}
|
||||
secretKey: ${MINIO_SECRET_KEY:minioadmin}
|
||||
bucketName: ${MINIO_BUCKET:video-platform}
|
||||
accessKey: ${MINIO_ACCESS_KEY}
|
||||
secretKey: ${MINIO_SECRET_KEY}
|
||||
bucketName: ${MINIO_BUCKET}
|
||||
|
||||
# FFmpeg配置
|
||||
ffmpeg:
|
||||
@@ -84,4 +83,14 @@ ffmpeg:
|
||||
auth:
|
||||
ignore-urls:
|
||||
- /api/v1/videos/public/**
|
||||
- /api/v1/categories/**
|
||||
- /api/v1/categories/**
|
||||
|
||||
service:
|
||||
auth:
|
||||
token: ${SERVICE_AUTH_TOKEN}
|
||||
|
||||
jwt:
|
||||
secret: ${USER_JWT_SECRET}
|
||||
expiration: ${JWT_EXPIRATION}
|
||||
token-header: Authorization
|
||||
token-prefix: Bearer
|
||||
|
||||
+3
@@ -17,7 +17,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -32,6 +34,7 @@ import static org.mockito.Mockito.*;
|
||||
* VideoService 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@DisplayName("视频服务测试")
|
||||
class VideoServiceTest {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user