fix(backend): Restore service flow and admin management integration
This commit is contained in:
@@ -2,8 +2,11 @@ package com.nuaa.video.user.config;
|
||||
|
||||
import com.nuaa.video.user.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,27 @@ public class SecurityConfig {
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(
|
||||
"/user/profile/public/**",
|
||||
"/user/profile/count/**",
|
||||
"/user/profile/init",
|
||||
"/user/profile/search",
|
||||
"/user/follow/following/**",
|
||||
"/user/follow/followers/**",
|
||||
"/actuator/**",
|
||||
"/error"
|
||||
).permitAll()
|
||||
.requestMatchers(
|
||||
"/user/profile/count/**",
|
||||
"/user/profile/init",
|
||||
"/user/profile/admin/**",
|
||||
"/user/notification/send/**"
|
||||
).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);
|
||||
}
|
||||
}
|
||||
|
||||
+35
-8
@@ -5,8 +5,10 @@ import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.user.entity.UserInfo;
|
||||
import com.nuaa.video.user.service.UserInfoService;
|
||||
import com.nuaa.video.user.vo.UserProfileVO;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,9 @@ import org.springframework.web.bind.annotation.*;
|
||||
@RequiredArgsConstructor
|
||||
public class AdminUserController {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final UserInfoService userInfoService;
|
||||
|
||||
/**
|
||||
@@ -27,7 +32,11 @@ public class AdminUserController {
|
||||
public Result<Page<UserProfileVO>> getUserList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
Page<UserInfo> pageParam = new Page<>(page, size);
|
||||
return userInfoService.getAdminUserList(pageParam, keyword);
|
||||
}
|
||||
@@ -36,7 +45,10 @@ public class AdminUserController {
|
||||
* 获取用户详情 - 管理接口
|
||||
*/
|
||||
@GetMapping("/{userId}")
|
||||
public Result<UserProfileVO> getUserDetail(@PathVariable Long userId) {
|
||||
public Result<UserProfileVO> getUserDetail(@PathVariable Long userId, HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return userInfoService.getAdminUserDetail(userId);
|
||||
}
|
||||
|
||||
@@ -46,8 +58,12 @@ public class AdminUserController {
|
||||
@PutMapping("/status/{userId}/{status}")
|
||||
public Result<Void> updateUserStatus(
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Integer status) {
|
||||
return userInfoService.updateUserStatus(userId, status);
|
||||
@PathVariable Integer status,
|
||||
HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return userInfoService.updateUserStatus(userId, status, internalServiceToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,15 +72,26 @@ public class AdminUserController {
|
||||
@PutMapping("/reset-password/{userId}")
|
||||
public Result<Void> resetUserPassword(
|
||||
@PathVariable Long userId,
|
||||
@RequestParam String newPassword) {
|
||||
return userInfoService.resetUserPassword(userId, newPassword);
|
||||
@RequestParam String newPassword,
|
||||
HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return userInfoService.resetUserPassword(userId, newPassword, internalServiceToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户统计信息 - 管理接口
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
public Result<Object> getUserStats() {
|
||||
public Result<Object> getUserStats(HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return userInfoService.getUserStats();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInternalRequest(HttpServletRequest request) {
|
||||
return internalServiceToken.equals(request.getHeader("X-Internal-Token"));
|
||||
}
|
||||
}
|
||||
|
||||
+8
-22
@@ -8,10 +8,9 @@ import com.nuaa.video.user.vo.UserNotificationVO;
|
||||
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.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户通知控制器
|
||||
*/
|
||||
@@ -21,6 +20,9 @@ import java.time.LocalDateTime;
|
||||
@RequiredArgsConstructor
|
||||
public class UserNotificationController {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final UserNotificationService userNotificationService;
|
||||
|
||||
/**
|
||||
@@ -86,17 +88,10 @@ public class UserNotificationController {
|
||||
@RequestParam(required = false) String content,
|
||||
@RequestParam(required = false) Long relatedId,
|
||||
HttpServletRequest request) {
|
||||
// 验证调用者身份
|
||||
Long currentUserId = getUserIdFromRequest(request);
|
||||
if (currentUserId == null) {
|
||||
return Result.error("未登录");
|
||||
if (!internalServiceToken.equals(request.getHeader("X-Internal-Token"))) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
|
||||
// 创建通知对象
|
||||
UserNotification notification = new UserNotification();
|
||||
notification.setUserId(userId);
|
||||
notification.setType(type);
|
||||
|
||||
// 设置通知内容
|
||||
String notificationContent;
|
||||
if (content != null && !content.isEmpty()) {
|
||||
@@ -120,16 +115,7 @@ public class UserNotificationController {
|
||||
notificationContent = "新通知";
|
||||
}
|
||||
}
|
||||
notification.setContent(notificationContent);
|
||||
|
||||
notification.setRelatedId(relatedId);
|
||||
notification.setIsRead(0); // 未读
|
||||
notification.setCreateTime(LocalDateTime.now());
|
||||
|
||||
// 保存通知
|
||||
userNotificationService.save(notification);
|
||||
|
||||
return Result.success();
|
||||
return userNotificationService.createNotification(userId, type, notificationContent, relatedId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,4 +128,4 @@ public class UserNotificationController {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-20
@@ -11,6 +11,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 java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class UserProfileController {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final UserInfoService userInfoService;
|
||||
private final AuthFeignClient authFeignClient;
|
||||
|
||||
@@ -33,7 +37,11 @@ public class UserProfileController {
|
||||
*/
|
||||
@PostMapping("/init")
|
||||
public Result<Void> initUserInfo(@RequestParam("userId") Long userId,
|
||||
@RequestParam("username") String username) {
|
||||
@RequestParam("username") String username,
|
||||
HttpServletRequest request) {
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
return userInfoService.createUserInfo(userId, username);
|
||||
}
|
||||
|
||||
@@ -130,13 +138,9 @@ public class UserProfileController {
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Integer count,
|
||||
HttpServletRequest request) {
|
||||
// 验证权限(实际可以通过更复杂的权限验证)
|
||||
Long currentUserId = getUserIdFromRequest(request);
|
||||
if (currentUserId == null) {
|
||||
return Result.error("未登录");
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
|
||||
// 调用service增加用户视频数量
|
||||
return userInfoService.increaseVideoCount(userId, count);
|
||||
}
|
||||
|
||||
@@ -148,13 +152,9 @@ public class UserProfileController {
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Integer count,
|
||||
HttpServletRequest request) {
|
||||
// 验证权限
|
||||
Long currentUserId = getUserIdFromRequest(request);
|
||||
if (currentUserId == null) {
|
||||
return Result.error("未登录");
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
|
||||
// 调用service增加用户获赞数量
|
||||
return userInfoService.increaseLikeCount(userId, count);
|
||||
}
|
||||
|
||||
@@ -166,13 +166,9 @@ public class UserProfileController {
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Integer count,
|
||||
HttpServletRequest request) {
|
||||
// 验证权限
|
||||
Long currentUserId = getUserIdFromRequest(request);
|
||||
if (currentUserId == null) {
|
||||
return Result.error("未登录");
|
||||
if (!isInternalRequest(request)) {
|
||||
return Result.error("非法的内部调用");
|
||||
}
|
||||
|
||||
// 调用service增加用户收藏数量
|
||||
return userInfoService.increaseFavoriteCount(userId, count);
|
||||
}
|
||||
|
||||
@@ -197,4 +193,8 @@ public class UserProfileController {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInternalRequest(HttpServletRequest request) {
|
||||
return internalServiceToken.equals(request.getHeader("X-Internal-Token"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ public interface AuthFeignClient {
|
||||
*/
|
||||
@PutMapping("/admin/lock/{userId}/{locked}")
|
||||
Result<Void> lockUser(@PathVariable Long userId,
|
||||
@PathVariable Boolean locked);
|
||||
@PathVariable Boolean locked,
|
||||
@RequestHeader("X-Internal-Token") String internalToken);
|
||||
|
||||
/**
|
||||
* 管理员重置用户密码 - 管理接口
|
||||
@@ -41,5 +42,6 @@ public interface AuthFeignClient {
|
||||
@PutMapping("/admin/reset-password/{userId}")
|
||||
Result<Void> resetPassword(
|
||||
@PathVariable Long userId,
|
||||
@RequestParam String newPassword);
|
||||
}
|
||||
@RequestParam String newPassword,
|
||||
@RequestHeader("X-Internal-Token") String internalToken);
|
||||
}
|
||||
|
||||
+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认证过滤器(不依赖Feign)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,19 @@ public interface UserInfoMapper extends BaseMapper<UserInfo> {
|
||||
* @return 影响行数
|
||||
*/
|
||||
int increaseFollowerCount(@Param("userId") Long userId, @Param("count") int count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加视频数
|
||||
*/
|
||||
int increaseVideoCount(@Param("userId") Long userId, @Param("count") int count);
|
||||
|
||||
/**
|
||||
* 增加获赞数
|
||||
*/
|
||||
int increaseLikeCount(@Param("userId") Long userId, @Param("count") int count);
|
||||
|
||||
/**
|
||||
* 增加收藏数
|
||||
*/
|
||||
int increaseFavoriteCount(@Param("userId") Long userId, @Param("count") int count);
|
||||
}
|
||||
|
||||
+15
-1
@@ -3,10 +3,24 @@ package com.nuaa.video.user.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.user.entity.UserStatDaily;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 用户每日统计Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserStatDailyMapper extends BaseMapper<UserStatDaily> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 原子更新用户每日统计,避免并发下先查后写导致锁等待。
|
||||
*/
|
||||
int upsertStatIncrement(@Param("userId") Long userId,
|
||||
@Param("statDate") java.time.LocalDate statDate,
|
||||
@Param("followerIncrement") int followerIncrement,
|
||||
@Param("followingIncrement") int followingIncrement,
|
||||
@Param("videoIncrement") int videoIncrement,
|
||||
@Param("likeIncrement") int likeIncrement,
|
||||
@Param("coinIncrement") int coinIncrement,
|
||||
@Param("favoriteIncrement") int favoriteIncrement,
|
||||
@Param("viewIncrement") long viewIncrement);
|
||||
}
|
||||
|
||||
@@ -106,15 +106,15 @@ public interface UserInfoService extends IService<UserInfo> {
|
||||
/**
|
||||
* 更新用户状态 - 管理接口
|
||||
*/
|
||||
Result<Void> updateUserStatus(Long userId, Integer status);
|
||||
Result<Void> updateUserStatus(Long userId, Integer status, String token);
|
||||
|
||||
/**
|
||||
* 重置用户密码 - 管理接口
|
||||
*/
|
||||
Result<Void> resetUserPassword(Long userId, String newPassword);
|
||||
Result<Void> resetUserPassword(Long userId, String newPassword, String token);
|
||||
|
||||
/**
|
||||
* 获取用户统计信息 - 管理接口
|
||||
*/
|
||||
Result<Object> getUserStats();
|
||||
}
|
||||
}
|
||||
|
||||
+70
-67
@@ -19,6 +19,7 @@ import com.nuaa.video.user.vo.UserProfileVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -29,6 +30,7 @@ import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -39,6 +41,9 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
|
||||
|
||||
@Value("${service.auth.token}")
|
||||
private String internalServiceToken;
|
||||
|
||||
private final UserInfoMapper userInfoMapper;
|
||||
private final UserFollowMapper userFollowMapper;
|
||||
private final UserSettingService userSettingService;
|
||||
@@ -199,7 +204,6 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> increaseVideoCount(Long userId, Integer count) {
|
||||
// 验证用户是否存在
|
||||
UserInfo userInfo = getById(userId);
|
||||
@@ -207,19 +211,14 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
|
||||
}
|
||||
|
||||
// 更新视频数量
|
||||
userInfo.setVideoCount(userInfo.getVideoCount() + count);
|
||||
userInfo.setUpdateTime(LocalDateTime.now());
|
||||
updateById(userInfo);
|
||||
userInfoMapper.increaseVideoCount(userId, count);
|
||||
|
||||
// 更新每日统计
|
||||
updateUserStatDaily(userId, "video", count);
|
||||
updateUserStatDailyAsync(userId, "video", count);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> increaseLikeCount(Long userId, Integer count) {
|
||||
// 验证用户是否存在
|
||||
UserInfo userInfo = getById(userId);
|
||||
@@ -227,16 +226,13 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
|
||||
}
|
||||
|
||||
// 更新获赞数量
|
||||
userInfo.setLikeCount(userInfo.getLikeCount() + count);
|
||||
userInfo.setUpdateTime(LocalDateTime.now());
|
||||
updateById(userInfo);
|
||||
userInfoMapper.increaseLikeCount(userId, count);
|
||||
updateUserStatDailyAsync(userId, "like", count);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> increaseFavoriteCount(Long userId, Integer count) {
|
||||
// 验证用户是否存在
|
||||
UserInfo userInfo = getById(userId);
|
||||
@@ -244,10 +240,8 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
|
||||
}
|
||||
|
||||
// 更新收藏数量
|
||||
userInfo.setFavoriteCount(userInfo.getFavoriteCount() + count);
|
||||
userInfo.setUpdateTime(LocalDateTime.now());
|
||||
updateById(userInfo);
|
||||
userInfoMapper.increaseFavoriteCount(userId, count);
|
||||
updateUserStatDailyAsync(userId, "favorite", count);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
@@ -306,7 +300,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> updateUserStatus(Long userId, Integer status) {
|
||||
public Result<Void> updateUserStatus(Long userId, Integer status, String token) {
|
||||
// 查询用户是否存在
|
||||
UserInfo userInfo = getById(userId);
|
||||
if (userInfo == null) {
|
||||
@@ -320,19 +314,14 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
updateUser.setUpdatedBy("admin");
|
||||
updateById(updateUser);
|
||||
|
||||
// 当禁用用户,需要清除用户相关的登录态等
|
||||
if (status == 0) {
|
||||
// 发送事件通知auth服务禁用该用户账号
|
||||
//TODO 可以优化为消息队列
|
||||
authFeignClient.lockUser(userId, true);
|
||||
}
|
||||
authFeignClient.lockUser(userId, status == 0, internalServiceToken);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> resetUserPassword(Long userId, String newPassword) {
|
||||
public Result<Void> resetUserPassword(Long userId, String newPassword, String token) {
|
||||
// 查询用户是否存在
|
||||
UserInfo userInfo = getById(userId);
|
||||
if (userInfo == null) {
|
||||
@@ -340,7 +329,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
}
|
||||
|
||||
// 调用auth服务重置密码(实际项目中通过Feign或消息队列)
|
||||
authFeignClient.resetPassword(userId, newPassword);
|
||||
authFeignClient.resetPassword(userId, newPassword, internalServiceToken);
|
||||
|
||||
log.info("重置用户密码: userId={}, newPassword=***", userId);
|
||||
|
||||
@@ -362,9 +351,21 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
.ge(UserInfo::getCreateTime, today.atStartOfDay()));
|
||||
stats.put("todayNewUsers", todayNewUsers);
|
||||
|
||||
// 活跃用户数(最近7天有登录记录的用户)
|
||||
// 此处需要结合用户登录记录表查询,简化处理
|
||||
stats.put("activeUsers", totalUsers * 0.3); // 示例数据
|
||||
LocalDate activeSince = today.minusDays(6);
|
||||
List<UserStatDaily> activeStats = userStatDailyMapper.selectList(new LambdaQueryWrapper<UserStatDaily>()
|
||||
.ge(UserStatDaily::getStatDate, activeSince));
|
||||
long activeUsers = activeStats.stream()
|
||||
.filter(stat -> positive(stat.getFollowerIncrement())
|
||||
|| positive(stat.getFollowingIncrement())
|
||||
|| positive(stat.getVideoIncrement())
|
||||
|| positive(stat.getLikeIncrement())
|
||||
|| positive(stat.getCoinIncrement())
|
||||
|| positive(stat.getFavoriteIncrement())
|
||||
|| positive(stat.getViewIncrement()))
|
||||
.map(UserStatDaily::getUserId)
|
||||
.distinct()
|
||||
.count();
|
||||
stats.put("activeUsers", activeUsers);
|
||||
|
||||
// 用户等级分布
|
||||
// 假设level字段代表用户等级
|
||||
@@ -384,59 +385,61 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
*/
|
||||
private void updateUserStatDaily(Long userId, String statType, int count) {
|
||||
LocalDate today = LocalDate.now();
|
||||
int followerIncrement = 0;
|
||||
int followingIncrement = 0;
|
||||
int videoIncrement = 0;
|
||||
int likeIncrement = 0;
|
||||
int coinIncrement = 0;
|
||||
int favoriteIncrement = 0;
|
||||
long viewIncrement = 0L;
|
||||
|
||||
// 查询今日统计记录
|
||||
UserStatDaily statDaily = userStatDailyMapper.selectOne(
|
||||
new LambdaQueryWrapper<UserStatDaily>()
|
||||
.eq(UserStatDaily::getUserId, userId)
|
||||
.eq(UserStatDaily::getStatDate, today)
|
||||
);
|
||||
|
||||
// 如果不存在,创建新记录
|
||||
if (statDaily == null) {
|
||||
statDaily = new UserStatDaily();
|
||||
statDaily.setUserId(userId);
|
||||
statDaily.setStatDate(today);
|
||||
statDaily.setFollowerIncrement(0);
|
||||
statDaily.setFollowingIncrement(0);
|
||||
statDaily.setVideoIncrement(0);
|
||||
statDaily.setLikeIncrement(0);
|
||||
statDaily.setCoinIncrement(0);
|
||||
statDaily.setFavoriteIncrement(0);
|
||||
statDaily.setViewIncrement(0L);
|
||||
statDaily.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// 根据统计类型更新数据
|
||||
switch (statType) {
|
||||
case "video":
|
||||
statDaily.setVideoIncrement(statDaily.getVideoIncrement() + count);
|
||||
videoIncrement = count;
|
||||
break;
|
||||
case "like":
|
||||
statDaily.setLikeIncrement(statDaily.getLikeIncrement() + count);
|
||||
likeIncrement = count;
|
||||
break;
|
||||
case "follower":
|
||||
statDaily.setFollowerIncrement(statDaily.getFollowerIncrement() + count);
|
||||
followerIncrement = count;
|
||||
break;
|
||||
case "following":
|
||||
statDaily.setFollowingIncrement(statDaily.getFollowingIncrement() + count);
|
||||
followingIncrement = count;
|
||||
break;
|
||||
case "favorite":
|
||||
statDaily.setFavoriteIncrement(statDaily.getFavoriteIncrement() + count);
|
||||
favoriteIncrement = count;
|
||||
break;
|
||||
case "coin":
|
||||
statDaily.setCoinIncrement(statDaily.getCoinIncrement() + count);
|
||||
coinIncrement = count;
|
||||
break;
|
||||
case "view":
|
||||
statDaily.setViewIncrement(statDaily.getViewIncrement() + count);
|
||||
viewIncrement = count;
|
||||
break;
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
if (statDaily.getId() == null) {
|
||||
userStatDailyMapper.insert(statDaily);
|
||||
} else {
|
||||
userStatDailyMapper.updateById(statDaily);
|
||||
}
|
||||
userStatDailyMapper.upsertStatIncrement(
|
||||
userId,
|
||||
today,
|
||||
followerIncrement,
|
||||
followingIncrement,
|
||||
videoIncrement,
|
||||
likeIncrement,
|
||||
coinIncrement,
|
||||
favoriteIncrement,
|
||||
viewIncrement
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUserStatDailyAsync(Long userId, String statType, int count) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
updateUserStatDaily(userId, statType, count);
|
||||
} catch (Exception e) {
|
||||
log.warn("异步更新用户日统计失败,userId={}, statType={}, count={}", userId, statType, count, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean positive(Number value) {
|
||||
return value != null && value.longValue() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_user?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: 1
|
||||
timeout: 10000
|
||||
lettuce:
|
||||
@@ -40,10 +40,25 @@ 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
|
||||
logic-delete-field: is_deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
logic-not-delete-value: 0
|
||||
|
||||
minio:
|
||||
endpoint: http://${MINIO_HOST:minio}:${MINIO_PORT:9000}
|
||||
accessKey: ${MINIO_ACCESS_KEY}
|
||||
secretKey: ${MINIO_SECRET_KEY}
|
||||
bucketName: ${MINIO_BUCKET}
|
||||
|
||||
service:
|
||||
auth:
|
||||
token: ${SERVICE_AUTH_TOKEN}
|
||||
|
||||
jwt:
|
||||
secret: ${USER_JWT_SECRET}
|
||||
expiration: ${JWT_EXPIRATION}
|
||||
token-header: Authorization
|
||||
token-prefix: Bearer
|
||||
|
||||
@@ -16,4 +16,25 @@
|
||||
WHERE id = #{userId}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
<update id="increaseVideoCount">
|
||||
UPDATE user_info
|
||||
SET video_count = GREATEST(video_count + #{count}, 0),
|
||||
update_time = NOW()
|
||||
WHERE id = #{userId}
|
||||
</update>
|
||||
|
||||
<update id="increaseLikeCount">
|
||||
UPDATE user_info
|
||||
SET like_count = GREATEST(like_count + #{count}, 0),
|
||||
update_time = NOW()
|
||||
WHERE id = #{userId}
|
||||
</update>
|
||||
|
||||
<update id="increaseFavoriteCount">
|
||||
UPDATE user_info
|
||||
SET favorite_count = GREATEST(favorite_count + #{count}, 0),
|
||||
update_time = NOW()
|
||||
WHERE id = #{userId}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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.user.mapper.UserStatDailyMapper">
|
||||
|
||||
<insert id="upsertStatIncrement">
|
||||
INSERT INTO user_stat_daily (
|
||||
user_id,
|
||||
stat_date,
|
||||
follower_increment,
|
||||
following_increment,
|
||||
video_increment,
|
||||
like_increment,
|
||||
coin_increment,
|
||||
favorite_increment,
|
||||
view_increment
|
||||
) VALUES (
|
||||
#{userId},
|
||||
#{statDate},
|
||||
#{followerIncrement},
|
||||
#{followingIncrement},
|
||||
#{videoIncrement},
|
||||
#{likeIncrement},
|
||||
#{coinIncrement},
|
||||
#{favoriteIncrement},
|
||||
#{viewIncrement}
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
follower_increment = follower_increment + VALUES(follower_increment),
|
||||
following_increment = following_increment + VALUES(following_increment),
|
||||
video_increment = video_increment + VALUES(video_increment),
|
||||
like_increment = like_increment + VALUES(like_increment),
|
||||
coin_increment = coin_increment + VALUES(coin_increment),
|
||||
favorite_increment = favorite_increment + VALUES(favorite_increment),
|
||||
view_increment = view_increment + VALUES(view_increment)
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
+3
@@ -13,7 +13,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 java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
@@ -27,6 +29,7 @@ import static org.mockito.Mockito.*;
|
||||
* UserFollowService 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@DisplayName("用户关注服务测试")
|
||||
class UserFollowServiceTest {
|
||||
|
||||
|
||||
+115
-120
@@ -1,30 +1,40 @@
|
||||
package com.nuaa.video.user.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import com.nuaa.video.common.utils.MinioUtils;
|
||||
import com.nuaa.video.user.dto.UserProfileDTO;
|
||||
import com.nuaa.video.user.entity.UserInfo;
|
||||
import com.nuaa.video.user.entity.UserStatDaily;
|
||||
import com.nuaa.video.user.feign.AuthFeignClient;
|
||||
import com.nuaa.video.user.mapper.UserFollowMapper;
|
||||
import com.nuaa.video.user.mapper.UserInfoMapper;
|
||||
import com.nuaa.video.user.mapper.UserStatDailyMapper;
|
||||
import com.nuaa.video.user.service.impl.UserInfoServiceImpl;
|
||||
import com.nuaa.video.user.vo.UserProfileVO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* UserInfoService 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("用户信息服务测试")
|
||||
class UserInfoServiceTest {
|
||||
@@ -32,6 +42,22 @@ class UserInfoServiceTest {
|
||||
@Mock
|
||||
private UserInfoMapper userInfoMapper;
|
||||
|
||||
@Mock
|
||||
private UserFollowMapper userFollowMapper;
|
||||
|
||||
@Mock
|
||||
private UserSettingService userSettingService;
|
||||
|
||||
@Mock
|
||||
private MinioUtils minioUtils;
|
||||
|
||||
@Mock
|
||||
private UserStatDailyMapper userStatDailyMapper;
|
||||
|
||||
@Mock
|
||||
private AuthFeignClient authFeignClient;
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private UserInfoServiceImpl userInfoService;
|
||||
|
||||
@@ -62,158 +88,127 @@ class UserInfoServiceTest {
|
||||
@Test
|
||||
@DisplayName("创建用户信息 - 成功")
|
||||
void testCreateUserInfoSuccess() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
String username = "testuser";
|
||||
doReturn(null).when(userInfoService).getById(1L);
|
||||
doReturn(true).when(userInfoService).save(any(UserInfo.class));
|
||||
when(userSettingService.createDefaultSetting(1L)).thenReturn(Result.success());
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(null);
|
||||
when(userInfoMapper.insert(any(UserInfo.class))).thenReturn(1);
|
||||
Result<Void> result = userInfoService.createUserInfo(1L, "testuser");
|
||||
|
||||
// When
|
||||
Result<Void> result = userInfoService.createUserInfo(userId, username);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals(200, result.getCode());
|
||||
verify(userInfoMapper, times(1)).insert(any(UserInfo.class));
|
||||
verify(userInfoService, times(1)).save(ArgumentMatchers.argThat(user ->
|
||||
user.getId().equals(1L)
|
||||
&& "testuser".equals(user.getNickname())
|
||||
&& "default/avatar.png".equals(user.getAvatar())
|
||||
&& user.getVideoCount() == 0
|
||||
));
|
||||
verify(userSettingService, times(1)).createDefaultSetting(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建用户信息 - 用户已存在")
|
||||
@DisplayName("创建用户信息 - 已存在")
|
||||
void testCreateUserInfoAlreadyExists() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
String username = "testuser";
|
||||
doReturn(testUserInfo).when(userInfoService).getById(1L);
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(testUserInfo);
|
||||
Result<Void> result = userInfoService.createUserInfo(1L, "testuser");
|
||||
|
||||
// When
|
||||
Result<Void> result = userInfoService.createUserInfo(userId, username);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals(200, result.getCode());
|
||||
verify(userInfoMapper, never()).insert(any(UserInfo.class));
|
||||
assertNotEquals(200, result.getCode());
|
||||
assertEquals("用户信息已存在", result.getMessage());
|
||||
verify(userInfoService, times(0)).save(any(UserInfo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("获取用户资料 - 成功")
|
||||
@DisplayName("获取用户资料 - 成功并标记关注状态")
|
||||
void testGetUserProfileSuccess() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
Long currentUserId = 2L;
|
||||
doReturn(testUserInfo).when(userInfoService).getById(1L);
|
||||
when(userFollowMapper.checkIsFollowed(2L, 1L)).thenReturn(1);
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(testUserInfo);
|
||||
Result<UserProfileVO> result = userInfoService.getUserProfile(1L, 2L);
|
||||
|
||||
// Then
|
||||
assertNotNull(testUserInfo);
|
||||
assertEquals("测试用户", testUserInfo.getNickname());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("获取用户资料 - 用户不存在")
|
||||
void testGetUserProfileNotFound() {
|
||||
// Given
|
||||
Long userId = 999L;
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(null);
|
||||
|
||||
// When
|
||||
UserInfo user = userInfoMapper.selectById(userId);
|
||||
|
||||
// Then
|
||||
assertNull(user);
|
||||
assertEquals(200, result.getCode());
|
||||
assertNotNull(result.getData());
|
||||
assertEquals("测试用户", result.getData().getNickname());
|
||||
assertTrue(result.getData().getIsFollowed());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新用户资料 - 成功")
|
||||
void testUpdateUserProfileSuccess() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
UserProfileDTO dto = new UserProfileDTO();
|
||||
dto.setNickname("新昵称");
|
||||
dto.setSignature("新签名");
|
||||
dto.setGender(1);
|
||||
dto.setRegion("上海");
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(testUserInfo);
|
||||
when(userInfoMapper.updateById(any(UserInfo.class))).thenReturn(1);
|
||||
doReturn(testUserInfo).when(userInfoService).getById(1L);
|
||||
doReturn(true).when(userInfoService).updateById(any(UserInfo.class));
|
||||
|
||||
// Then
|
||||
assertNotNull(dto);
|
||||
assertEquals("新昵称", dto.getNickname());
|
||||
Result<Void> result = userInfoService.updateUserProfile(1L, dto);
|
||||
|
||||
assertEquals(200, result.getCode());
|
||||
verify(userInfoService, times(1)).updateById(ArgumentMatchers.argThat(user ->
|
||||
"新昵称".equals(user.getNickname())
|
||||
&& "新签名".equals(user.getSignature())
|
||||
&& "上海".equals(user.getRegion())
|
||||
&& "1".equals(user.getUpdatedBy())
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("获取简要用户信息 - 成功")
|
||||
void testGetBriefUserInfoSuccess() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(testUserInfo);
|
||||
|
||||
// When
|
||||
UserInfo user = userInfoMapper.selectById(userId);
|
||||
|
||||
// Then
|
||||
assertNotNull(user);
|
||||
assertEquals("测试用户", user.getNickname());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("搜索用户 - 成功")
|
||||
void testSearchUsersSuccess() {
|
||||
// Given
|
||||
Page<UserInfo> page = new Page<>(1, 10);
|
||||
String keyword = "测试";
|
||||
|
||||
// Then
|
||||
assertNotNull(page);
|
||||
assertEquals(1, page.getCurrent());
|
||||
assertEquals(10, page.getSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("增加用户视频数量 - 成功")
|
||||
@DisplayName("增加用户视频数量 - 成功并写入每日统计")
|
||||
void testIncreaseVideoCountSuccess() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
Integer count = 1;
|
||||
int oldVideoCount = testUserInfo.getVideoCount();
|
||||
doReturn(testUserInfo).when(userInfoService).getById(1L);
|
||||
doReturn(true).when(userInfoService).updateById(any(UserInfo.class));
|
||||
when(userStatDailyMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(userStatDailyMapper.insert(ArgumentMatchers.<UserStatDaily>any())).thenReturn(1);
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(testUserInfo);
|
||||
when(userInfoMapper.updateById(any(UserInfo.class))).thenReturn(1);
|
||||
Result<Void> result = userInfoService.increaseVideoCount(1L, 2);
|
||||
|
||||
// Then
|
||||
assertEquals(10, oldVideoCount);
|
||||
assertEquals(200, result.getCode());
|
||||
verify(userInfoService).updateById(ArgumentMatchers.argThat(user -> user.getVideoCount() == 12));
|
||||
verify(userStatDailyMapper).insert(ArgumentMatchers.<UserStatDaily>argThat(stat ->
|
||||
stat.getUserId().equals(1L)
|
||||
&& stat.getVideoIncrement() == 2
|
||||
&& stat.getStatDate().equals(LocalDate.now())
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("增加用户获赞数量 - 成功")
|
||||
void testIncreaseLikeCountSuccess() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
Integer count = 10;
|
||||
int oldLikeCount = testUserInfo.getLikeCount();
|
||||
@DisplayName("获取用户统计 - 基于近7日活跃记录聚合")
|
||||
void testGetUserStatsSuccess() {
|
||||
UserStatDaily activeOne = new UserStatDaily();
|
||||
activeOne.setUserId(1L);
|
||||
activeOne.setStatDate(LocalDate.now());
|
||||
activeOne.setVideoIncrement(1);
|
||||
activeOne.setViewIncrement(0L);
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(testUserInfo);
|
||||
when(userInfoMapper.updateById(any(UserInfo.class))).thenReturn(1);
|
||||
UserStatDaily activeTwo = new UserStatDaily();
|
||||
activeTwo.setUserId(2L);
|
||||
activeTwo.setStatDate(LocalDate.now().minusDays(1));
|
||||
activeTwo.setLikeIncrement(3);
|
||||
|
||||
// Then
|
||||
assertEquals(1000, oldLikeCount);
|
||||
}
|
||||
UserStatDaily inactive = new UserStatDaily();
|
||||
inactive.setUserId(3L);
|
||||
inactive.setStatDate(LocalDate.now().minusDays(2));
|
||||
inactive.setVideoIncrement(0);
|
||||
inactive.setLikeIncrement(0);
|
||||
inactive.setFavoriteIncrement(0);
|
||||
inactive.setCoinIncrement(0);
|
||||
inactive.setFollowerIncrement(0);
|
||||
inactive.setFollowingIncrement(0);
|
||||
inactive.setViewIncrement(0L);
|
||||
|
||||
@Test
|
||||
@DisplayName("增加用户收藏数量 - 成功")
|
||||
void testIncreaseFavoriteCountSuccess() {
|
||||
// Given
|
||||
Long userId = 1L;
|
||||
Integer count = 5;
|
||||
int oldFavoriteCount = testUserInfo.getFavoriteCount();
|
||||
doReturn(10L).when(userInfoService).count();
|
||||
doReturn(2L).when(userInfoService).count(any(LambdaQueryWrapper.class));
|
||||
when(userStatDailyMapper.selectList(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(List.of(activeOne, activeTwo, inactive));
|
||||
|
||||
when(userInfoMapper.selectById(userId)).thenReturn(testUserInfo);
|
||||
when(userInfoMapper.updateById(any(UserInfo.class))).thenReturn(1);
|
||||
Result<Object> result = userInfoService.getUserStats();
|
||||
|
||||
// Then
|
||||
assertEquals(30, oldFavoriteCount);
|
||||
assertEquals(200, result.getCode());
|
||||
assertNotNull(result.getData());
|
||||
@SuppressWarnings("unchecked")
|
||||
var stats = (java.util.Map<String, Object>) result.getData();
|
||||
assertEquals(10L, stats.get("totalUsers"));
|
||||
assertEquals(2L, stats.get("todayNewUsers"));
|
||||
assertEquals(2L, stats.get("activeUsers"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user