fix(backend): Restore service flow and admin management integration

This commit is contained in:
2026-04-18 18:20:58 +08:00
parent 92a139e470
commit 3adb088eca
83 changed files with 1696 additions and 703 deletions
@@ -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);
}
}
@@ -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"));
}
}
@@ -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"));
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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("更新用户收藏数量失败");
}
};
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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
@@ -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 {