diff --git a/pom.xml b/pom.xml index 2079d64..c7629ea 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,11 @@ 3.5.10 + + org.springframework.boot + spring-boot-starter-data-redis + + org.springframework.boot spring-boot-starter-web @@ -56,6 +61,12 @@ spring-boot-starter-test test + + + org.mockito + mockito-inline + test + diff --git a/src/main/java/com/bao/dating/anno/Log.java b/src/main/java/com/bao/dating/anno/Log.java new file mode 100644 index 0000000..d867395 --- /dev/null +++ b/src/main/java/com/bao/dating/anno/Log.java @@ -0,0 +1,11 @@ +package com.bao.dating.anno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Log { +} diff --git a/src/main/java/com/bao/dating/aspect/LoggingAspect.java b/src/main/java/com/bao/dating/aspect/LoggingAspect.java new file mode 100644 index 0000000..e104d61 --- /dev/null +++ b/src/main/java/com/bao/dating/aspect/LoggingAspect.java @@ -0,0 +1,48 @@ +package com.bao.dating.aspect; + + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.*; +import org.springframework.stereotype.Component; + +/** + * 日志切面 + * @author KilLze + */ +@Aspect +@Component +@Slf4j +public class LoggingAspect { + @Pointcut("execution(* com.bao.dating.service.impl.*.*(..))") + private void pt(){} + + /** + * 方法执行前执行 + * @param joinPoint 方法参数 + */ + @Before("pt()") + public void logBeforeMethod(JoinPoint joinPoint){ + // 获取方法名 + String methodName = joinPoint.getSignature().getName(); + // 获取参数 + Object[] args = joinPoint.getArgs(); + log.info("方法 {} 开始执行,参数: {}", methodName, args); + } + + /** + * 方法执行成功后执行 + * @param joinPoint 方法参数 + */ + @AfterReturning(pointcut = "pt()", returning = "result") + public void logAfterMethod(JoinPoint joinPoint, Object result){ + String methodName = joinPoint.getSignature().getName(); + log.info("方法 {} 执行成功,返回值: {}", methodName, result); + } + + @AfterThrowing(pointcut = "pt()", throwing = "exception") + public void logAfterThrowing(JoinPoint joinPoint, Exception exception){ + String methodName = joinPoint.getSignature().getName(); + log.error("方法 {} 执行异常", methodName, exception); + } +} diff --git a/src/main/java/com/bao/dating/aspect/OperateLogAspect.java b/src/main/java/com/bao/dating/aspect/OperateLogAspect.java new file mode 100644 index 0000000..440f141 --- /dev/null +++ b/src/main/java/com/bao/dating/aspect/OperateLogAspect.java @@ -0,0 +1,64 @@ +package com.bao.dating.aspect; + + +import com.bao.dating.context.UserContext; +import com.bao.dating.mapper.OperateLogMapper; +import com.bao.dating.pojo.entity.OperateLog; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +import java.time.LocalDateTime; +import java.util.Arrays; + +/** + * 记录操作日志 + * @author KilLze + */ +@Slf4j +@Aspect +@Component +public class OperateLogAspect { + + @Autowired + private OperateLogMapper operateLogMapper; + + @Around("@annotation(com.bao.dating.anno.Log)") + public Object logOperate(ProceedingJoinPoint pjp) throws Throwable{ + + // 记录方法开始的时间 + long startTime = System.currentTimeMillis(); + + // 执行目标方法 + Object result = pjp.proceed(); + + long endTime = System.currentTimeMillis(); + long costTime = endTime - startTime; + + + // 构建日志对象 + OperateLog operatelog = new OperateLog(); + operatelog.setOperateUserId(getUserId()); + operatelog.setOperateTime(LocalDateTime.now()); + operatelog.setClassName(pjp.getTarget().getClass().getName()); + operatelog.setMethodName(pjp.getSignature().getName()); + operatelog.setMethodParams(Arrays.toString(pjp.getArgs())); + operatelog.setReturnValue(result != null ? result.toString() : "void"); + operatelog.setCostTime(costTime); + + log.info("记录操作日志: {}", operatelog); + + operateLogMapper.insert(operatelog); + + return result; + } + + private Long getUserId() { + return UserContext.getUserId(); + } + +} diff --git a/src/main/java/com/bao/dating/config/RedisConfig.java b/src/main/java/com/bao/dating/config/RedisConfig.java new file mode 100644 index 0000000..93dcbbd --- /dev/null +++ b/src/main/java/com/bao/dating/config/RedisConfig.java @@ -0,0 +1,33 @@ +package com.bao.dating.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * Redis 配置类 + * @author KilLze + */ +@Configuration +public class RedisConfig { + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + // 创建RedisTemplate对象 + RedisTemplate redisTemplate = new RedisTemplate<>(); + // 设置redis的连接工厂对象 + redisTemplate.setConnectionFactory(redisConnectionFactory); + + // 设置redis key的序列化器 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + // 设置value的序列化器 + redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + // 设置hash类型的key和value的序列化器 + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); + + return redisTemplate; + } +} diff --git a/src/main/java/com/bao/dating/controller/PostController.java b/src/main/java/com/bao/dating/controller/PostController.java index 648e32f..b4fb20b 100644 --- a/src/main/java/com/bao/dating/controller/PostController.java +++ b/src/main/java/com/bao/dating/controller/PostController.java @@ -1,6 +1,7 @@ package com.bao.dating.controller; +import com.bao.dating.anno.Log; import com.bao.dating.common.Result; import com.bao.dating.common.ResultCode; import com.bao.dating.pojo.dto.PostRequestDTO; @@ -30,6 +31,7 @@ public class PostController { * @param files 媒体文件数组 * @return 上传后的文件URL列表 */ + @Log @PostMapping(value = "/upload", consumes = "multipart/form-data") public Result> uploadMedia(@RequestParam("files") MultipartFile[] files) { List fileUrls = postService.uploadMedia(files); @@ -41,6 +43,7 @@ public class PostController { * @param postDTO 动态信息 * @return 发布的动态对象 */ + @Log @PostMapping( "/createPost") public Result createPostJson(@RequestBody PostRequestDTO postDTO) { // 调用 Service 层处理发布动态业务逻辑 @@ -54,10 +57,11 @@ public class PostController { * @param postIds 动态ID * @return 删除结果 */ + @Log @PostMapping("/deletePost") public Result deleteById(@RequestBody List postIds){ int deletedCount = postService.deletePostById(postIds); - return Result.success(ResultCode.SUCCESS_DELETE, deletedCount > 0 ? "成功删除" : "删除失败,该动态不存在", null); + return Result.success(ResultCode.SUCCESS_DELETE, "成功删除" + deletedCount + "条动态", null); } /** @@ -65,7 +69,7 @@ public class PostController { * @param postId 动态ID * @return 动态对象 */ - @PostMapping("/{postId}") + @GetMapping("/{postId}") public Result getPostById(@PathVariable Long postId) { PostEditVO postEditVO = postService.getPostForEdit(postId); return Result.success(ResultCode.SUCCESS,"查询成功", postEditVO); @@ -77,6 +81,7 @@ public class PostController { * @param postRequestDTO 动态信息 * @return 更新后的动态对象 */ + @Log @PostMapping("/{postId}/updatePost") public Result updatePost(@PathVariable Long postId, @RequestBody PostRequestDTO postRequestDTO) { PostEditVO result = postService.updatePost(postId, postRequestDTO); diff --git a/src/main/java/com/bao/dating/controller/PostFavoriteController.java b/src/main/java/com/bao/dating/controller/PostFavoriteController.java index fd44e56..4f60435 100644 --- a/src/main/java/com/bao/dating/controller/PostFavoriteController.java +++ b/src/main/java/com/bao/dating/controller/PostFavoriteController.java @@ -4,7 +4,6 @@ import com.bao.dating.common.Result; import com.bao.dating.common.ResultCode; import com.bao.dating.pojo.entity.User; import com.bao.dating.service.PostFavoriteService; -import com.bao.dating.service.PostService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/bao/dating/controller/PostLikeController.java b/src/main/java/com/bao/dating/controller/PostLikeController.java index ef246e7..df0cdc6 100644 --- a/src/main/java/com/bao/dating/controller/PostLikeController.java +++ b/src/main/java/com/bao/dating/controller/PostLikeController.java @@ -2,13 +2,11 @@ package com.bao.dating.controller; import com.bao.dating.common.Result; import com.bao.dating.common.ResultCode; -import com.bao.dating.mapper.PostLikeMapper; import com.bao.dating.service.PostLikeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; -import java.util.Objects; @RestController @RequestMapping("/posts") diff --git a/src/main/java/com/bao/dating/controller/UserController.java b/src/main/java/com/bao/dating/controller/UserController.java index b62cdfd..aad6328 100644 --- a/src/main/java/com/bao/dating/controller/UserController.java +++ b/src/main/java/com/bao/dating/controller/UserController.java @@ -1,5 +1,6 @@ package com.bao.dating.controller; +import com.bao.dating.anno.Log; import com.bao.dating.common.Result; import com.bao.dating.common.ResultCode; import com.bao.dating.context.UserContext; @@ -8,10 +9,13 @@ import com.bao.dating.pojo.dto.UserLoginDTO; import com.bao.dating.pojo.vo.UserInfoVO; import com.bao.dating.pojo.vo.UserLoginVO; import com.bao.dating.service.UserService; +import io.jsonwebtoken.Jwt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; + /** * 用户接口 * @@ -34,6 +38,17 @@ public class UserController { return Result.success(ResultCode.SUCCESS, "登录成功", userloginVO); } + /** + * 退出登录 + * 从请求头中获取token并将其加入黑名单 + */ + @PostMapping("/logout") + public Result logout(HttpServletRequest request) { + String token = request.getHeader("token"); + userService.logout(token); + return Result.success(ResultCode.SUCCESS,"退出登录成功",null); + } + /** * 获取用户信息 * @return 用户信息 @@ -50,6 +65,7 @@ public class UserController { * @param file 头像文件 * @return 上传后的文件URL列表 */ + @Log @PostMapping(value = "/info/uploadAvatar", consumes = "multipart/form-data") public Result uploadAvatar(@RequestParam("file") MultipartFile file) { String fileUrl = userService.uploadAvatar(file); @@ -61,6 +77,7 @@ public class UserController { * @param file 背景文件 * @return 上传后的文件URL列表 */ + @Log @PostMapping(value = "/info/uploadBackground", consumes = "multipart/form-data") public Result uploadBackground(@RequestParam("file") MultipartFile file) { String fileUrl = userService.uploadBackground(file); @@ -72,6 +89,7 @@ public class UserController { * @param userInfoUpdateDTO 用户信息更新参数 * @return 更新后的用户信息 */ + @Log @PostMapping("/info/update") public Result userInfoUpdate(@RequestBody UserInfoUpdateDTO userInfoUpdateDTO) { Long userId = UserContext.getUserId(); diff --git a/src/main/java/com/bao/dating/handler/GlobalExceptionHandler.java b/src/main/java/com/bao/dating/handler/GlobalExceptionHandler.java index 4e1993d..25eb6c3 100644 --- a/src/main/java/com/bao/dating/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/bao/dating/handler/GlobalExceptionHandler.java @@ -4,7 +4,6 @@ import com.bao.dating.common.Result; import com.bao.dating.common.ResultCode; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; -import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -15,7 +14,7 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep import org.springframework.web.servlet.NoHandlerFoundException; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import java.util.stream.Collectors; /** * 全局异常处理器 @@ -32,7 +31,12 @@ public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.error("参数验证失败: {}", e.getMessage()); - return Result.error(ResultCode.PARAM_ERROR, e.getBindingResult().getFieldError().getDefaultMessage()); + String msg = e.getBindingResult() + .getFieldErrors() + .stream() + .map(error -> error.getField() + ":" + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); + return Result.error(ResultCode.PARAM_ERROR, msg); } /** diff --git a/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java b/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java index e520ce8..8e11f59 100644 --- a/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java +++ b/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java @@ -5,7 +5,10 @@ import javax.servlet.http.HttpServletResponse; import com.bao.dating.context.UserContext; import com.bao.dating.util.JwtUtil; +import io.jsonwebtoken.Claims; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; @@ -18,6 +21,10 @@ import org.springframework.web.servlet.HandlerInterceptor; @Slf4j @Component public class TokenInterceptor implements HandlerInterceptor { + + @Autowired + private RedisTemplate redisTemplate; + /** * 在请求处理之前进行拦截 * 从请求头或URL参数中获取token,验证其有效性,并将用户ID保存到ThreadLocal中 @@ -45,13 +52,38 @@ public class TokenInterceptor implements HandlerInterceptor { if (!JwtUtil.validateToken(token)) { log.error("Token无效或已过期"); response.setStatus(401); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("Token无效或已过期"); + return false; + } + + // 检查 token 是否在黑名单中 + Object blacklistToken = redisTemplate.opsForValue().get("jwt:blacklist:" + token); + if (blacklistToken != null) { + log.error("Token已在黑名单中"); + response.setStatus(401); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("登录已失效, 请重新登录"); return false; } // 解析 token String userId = JwtUtil.getSubjectFromToken(token); + + // 从Redis获取存储的token进行比对 + Object redisTokenObj = redisTemplate.opsForValue().get("login:token:" + userId); + String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null; + + // 验证Redis中的token是否存在且匹配 + if (redisToken == null || !redisToken.equals(token)) { + log.error("登录已失效"); + response.setStatus(401); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("登录已失效"); + return false; + } + log.info("用户: {}", userId); - // 保存 userId 到 ThreadLocal UserContext.setUserId(Long.valueOf(userId)); return true; diff --git a/src/main/java/com/bao/dating/mapper/CommentsMapper.java b/src/main/java/com/bao/dating/mapper/CommentsMapper.java index 1de2c6d..78ca350 100644 --- a/src/main/java/com/bao/dating/mapper/CommentsMapper.java +++ b/src/main/java/com/bao/dating/mapper/CommentsMapper.java @@ -18,4 +18,11 @@ public interface CommentsMapper { // 根据动态ID查询评论列表 @Select("SELECT * FROM comments WHERE post_id = #{post_id} ORDER BY created_at DESC") List getCommentsByPostId(@Param("post_id") Long post_id); + + /** + * 根据动态ID批量删除评论 + * @param postIds + * @return + */ + int deleteCommentsByPostIds(@Param("postIds") List postIds); } diff --git a/src/main/java/com/bao/dating/mapper/OperateLogMapper.java b/src/main/java/com/bao/dating/mapper/OperateLogMapper.java new file mode 100644 index 0000000..e97c421 --- /dev/null +++ b/src/main/java/com/bao/dating/mapper/OperateLogMapper.java @@ -0,0 +1,19 @@ +package com.bao.dating.mapper; + + +import com.bao.dating.pojo.entity.OperateLog; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; + +/** + * 操作日志Mapper + * @author KilLze + */ +@Mapper +public interface OperateLogMapper { + + @Insert("insert into operate_log (operate_user_id, operate_time, class_name, method_name, method_params, return_value, cost_time) " + + "values (#{operateUserId}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});") + public void insert(OperateLog log); + +} diff --git a/src/main/java/com/bao/dating/mapper/PostFavoriteMapper.java b/src/main/java/com/bao/dating/mapper/PostFavoriteMapper.java index a0aa443..88f05e9 100644 --- a/src/main/java/com/bao/dating/mapper/PostFavoriteMapper.java +++ b/src/main/java/com/bao/dating/mapper/PostFavoriteMapper.java @@ -12,4 +12,12 @@ public interface PostFavoriteMapper { List selectUserIDByPostID(@Param("postId") Long postId); int addPostFavorite(PostFavorite postFavorite); int deletePostFavorite(@Param("postId") Long postId); + + /** + * 批量删除收藏 + * @param postIds + * @return + */ + int deleteFavoritesByPostIds(@Param("postIds") List postIds); + } diff --git a/src/main/java/com/bao/dating/mapper/PostLikeMapper.java b/src/main/java/com/bao/dating/mapper/PostLikeMapper.java index 41783e4..50e28be 100644 --- a/src/main/java/com/bao/dating/mapper/PostLikeMapper.java +++ b/src/main/java/com/bao/dating/mapper/PostLikeMapper.java @@ -4,6 +4,8 @@ import com.bao.dating.pojo.entity.PostLike; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.List; + @Mapper public interface PostLikeMapper { /** @@ -31,4 +33,12 @@ public interface PostLikeMapper { * @return */ int deleteByPostIdAndUserId(@Param("postId") Long postId, @Param("userId") Long userId); + + /** + * 批量删除点赞记录 + * + * @param postIds + * @return + */ + int deleteLikesByPostIds(@Param("postIds") List postIds); } diff --git a/src/main/java/com/bao/dating/mapper/PostMapper.java b/src/main/java/com/bao/dating/mapper/PostMapper.java index 8380a2f..5a3ac72 100644 --- a/src/main/java/com/bao/dating/mapper/PostMapper.java +++ b/src/main/java/com/bao/dating/mapper/PostMapper.java @@ -21,11 +21,11 @@ public interface PostMapper { void insert(Post post); /** - * 根据ID删除动态 + * 根据ID修改动态状态 * * @param postIds 动态ID */ - int deletePostByIds(List postIds); + int updatePublicById(@Param("postIds") List postIds, @Param("userId") Long userId); /** * 根据ID查询动态 diff --git a/src/main/java/com/bao/dating/pojo/dto/UserInfoUpdateDTO.java b/src/main/java/com/bao/dating/pojo/dto/UserInfoUpdateDTO.java index 97dd6e9..a3bc0ab 100644 --- a/src/main/java/com/bao/dating/pojo/dto/UserInfoUpdateDTO.java +++ b/src/main/java/com/bao/dating/pojo/dto/UserInfoUpdateDTO.java @@ -2,6 +2,7 @@ package com.bao.dating.pojo.dto; import lombok.Data; +import java.io.Serializable; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -11,7 +12,7 @@ import java.util.List; * @author KilLze */ @Data -public class UserInfoUpdateDTO { +public class UserInfoUpdateDTO implements Serializable { private Long userId; private String userName; private String nickname; diff --git a/src/main/java/com/bao/dating/pojo/entity/OperateLog.java b/src/main/java/com/bao/dating/pojo/entity/OperateLog.java new file mode 100644 index 0000000..c17f0c1 --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/entity/OperateLog.java @@ -0,0 +1,30 @@ +package com.bao.dating.pojo.entity; + +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 操作日志 + * @author KilLze + */ +@Data +public class OperateLog implements Serializable { + /** ID */ + private Long id; + /** 操作人ID */ + private Long operateUserId; + /** 操作时间 */ + private LocalDateTime operateTime; + /** 操作类名 */ + private String className; + /** 操作方法名 */ + private String methodName; + /** 操作方法参数 */ + private String methodParams; + /** 操作方法返回值 */ + private String returnValue; + /** 操作耗时 */ + private Long costTime; +} diff --git a/src/main/java/com/bao/dating/service/PostService.java b/src/main/java/com/bao/dating/service/PostService.java index 627fed6..b598daa 100644 --- a/src/main/java/com/bao/dating/service/PostService.java +++ b/src/main/java/com/bao/dating/service/PostService.java @@ -27,7 +27,7 @@ public interface PostService { Post createPost(PostRequestDTO postRequestDTO); /** - * 批量删除动态 + * 批量删除动态(将动态状态改为已删除) * @param postIds 动态ID * @return 删除的动态对象 */ diff --git a/src/main/java/com/bao/dating/service/UserService.java b/src/main/java/com/bao/dating/service/UserService.java index 5705ceb..8dd074c 100644 --- a/src/main/java/com/bao/dating/service/UserService.java +++ b/src/main/java/com/bao/dating/service/UserService.java @@ -18,6 +18,13 @@ public interface UserService { */ UserLoginVO userLogin(UserLoginDTO userLoginDTO); + /** + * 退出登录 + * @param token 登录凭证 + * @return 注册结果 + */ + void logout(String token); + /** * 查询个人信息 * @param userId 动态ID diff --git a/src/main/java/com/bao/dating/service/impl/PostServiceImpl.java b/src/main/java/com/bao/dating/service/impl/PostServiceImpl.java index 5a430cd..2b47c9d 100644 --- a/src/main/java/com/bao/dating/service/impl/PostServiceImpl.java +++ b/src/main/java/com/bao/dating/service/impl/PostServiceImpl.java @@ -4,17 +4,22 @@ import com.bao.dating.common.aliyun.GreenImageScan; import com.bao.dating.common.aliyun.GreenTextScan; import com.bao.dating.common.result.GreenAuditResult; import com.bao.dating.context.UserContext; +import com.bao.dating.mapper.CommentsMapper; +import com.bao.dating.mapper.PostFavoriteMapper; +import com.bao.dating.mapper.PostLikeMapper; import com.bao.dating.mapper.PostMapper; import com.bao.dating.pojo.dto.PostRequestDTO; import com.bao.dating.pojo.entity.Post; import com.bao.dating.pojo.vo.PostEditVO; import com.bao.dating.service.PostService; import com.bao.dating.common.aliyun.AliOssUtil; +import com.bao.dating.service.UserService; import com.bao.dating.util.FileUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -43,6 +48,15 @@ public class PostServiceImpl implements PostService { @Autowired private PostMapper postMapper; + @Autowired + private PostLikeMapper postLikeMapper; + + @Autowired + private PostFavoriteMapper postFavoriteMapper; + + @Autowired + private CommentsMapper commentsMapper; + /** * 上传媒体文件 * @param files 媒体文件数组 @@ -177,7 +191,7 @@ public class PostServiceImpl implements PostService { } /** - * 批量删除动态 + * 批量删除动态(将动态状态改为已删除) * * @param postIds 动态ID * @return 删除的动态对象 @@ -188,19 +202,21 @@ public class PostServiceImpl implements PostService { // 判断用户权限 Long userId = UserContext.getUserId(); - // 遍历所有要删除的帖子ID,验证权限 - for (Long postId : postIds) { - Post post = postMapper.selectById(postId); - if (post == null) { - throw new RuntimeException("动态不存在"); - } - // 验证用户权限 - if (post.getUserId() == null || !post.getUserId().equals(userId)) { - throw new RuntimeException("无权限删除此动态"); - } + if (CollectionUtils.isEmpty(postIds)) { + return 0; } - // 批量删除动态 - return postMapper.deletePostByIds(postIds); + int affected = postMapper.updatePublicById(postIds, userId); + + if (affected == 0) { + throw new RuntimeException("未删除任何动态,可能无权限或动态不存在"); + } + + // 删除动态下的评论、点赞、收藏 + commentsMapper.deleteCommentsByPostIds(postIds); + postLikeMapper.deleteLikesByPostIds(postIds); + postFavoriteMapper.deleteFavoritesByPostIds(postIds); + + return affected; } /** diff --git a/src/main/java/com/bao/dating/service/impl/UserServiceImpl.java b/src/main/java/com/bao/dating/service/impl/UserServiceImpl.java index d6b48dd..189b7e3 100644 --- a/src/main/java/com/bao/dating/service/impl/UserServiceImpl.java +++ b/src/main/java/com/bao/dating/service/impl/UserServiceImpl.java @@ -5,6 +5,7 @@ import com.bao.dating.common.aliyun.GreenImageScan; import com.bao.dating.common.aliyun.GreenTextScan; import com.bao.dating.common.result.AliOssResult; import com.bao.dating.common.result.GreenAuditResult; +import com.bao.dating.config.RedisConfig; import com.bao.dating.context.UserContext; import com.bao.dating.mapper.UserMapper; import com.bao.dating.pojo.dto.UserInfoUpdateDTO; @@ -16,17 +17,17 @@ import com.bao.dating.service.UserService; import com.bao.dating.util.FileUtil; import com.bao.dating.util.JwtUtil; import com.bao.dating.util.MD5Util; +import io.jsonwebtoken.Claims; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import java.util.concurrent.TimeUnit; /** * 用户服务实现类 @@ -45,6 +46,9 @@ public class UserServiceImpl implements UserService { @Autowired private GreenImageScan greenImageScan; + @Autowired + private RedisTemplate redisTemplate; + @Autowired private UserMapper userMapper; @@ -76,6 +80,15 @@ public class UserServiceImpl implements UserService { } // 生成token String token = JwtUtil.generateToken(String.valueOf(user.getUserId())); + + String redisKey = "login:token:" + user.getUserId(); + redisTemplate.opsForValue().set( + redisKey, + token, + 7, + TimeUnit.DAYS + ); + // 封装返回 UserLoginVO userLoginVO = new UserLoginVO(); userLoginVO.setUserId(user.getUserId()); @@ -84,6 +97,29 @@ public class UserServiceImpl implements UserService { return userLoginVO; } + /** + * 退出登录 + * @param token 登录凭证 + */ + @Override + public void logout(String token) { + Claims claims = JwtUtil.getClaimsFromToken(token); + Date expiration = claims.getExpiration(); + // 判断 token 是否已过期 + long ttl = expiration.getTime() - System.currentTimeMillis(); + // 如果 token 已过期,则不用处理 + if (ttl <= 0) { + return; + } + + String logoutKey = "jwt:blacklist:" + token; + redisTemplate.opsForValue().set( + logoutKey, + "logout", + ttl, + TimeUnit.MILLISECONDS); + } + /** * 获取用户信息 * diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82aafc7..5cc67fa 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,6 +2,11 @@ server: port: 8080 spring: + mvc: + throw-exception-if-no-handler-found: true + web: + resources: + add-mappings: false datasource: url: jdbc:mysql://110.42.41.177:3306/dating?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 username: root diff --git a/src/main/resources/com/bao/dating/mapper/CommentsMapper.xml b/src/main/resources/com/bao/dating/mapper/CommentsMapper.xml new file mode 100644 index 0000000..55f46c5 --- /dev/null +++ b/src/main/resources/com/bao/dating/mapper/CommentsMapper.xml @@ -0,0 +1,15 @@ + + + + + + + DELETE FROM comments + WHERE post_id IN + + #{postId} + + + + \ No newline at end of file diff --git a/src/main/resources/com/bao/dating/mapper/PostFavoriteMapper.xml b/src/main/resources/com/bao/dating/mapper/PostFavoriteMapper.xml index 41b5195..9467b9c 100644 --- a/src/main/resources/com/bao/dating/mapper/PostFavoriteMapper.xml +++ b/src/main/resources/com/bao/dating/mapper/PostFavoriteMapper.xml @@ -14,4 +14,14 @@ + + + + DELETE FROM post_favorite + WHERE post_id IN + + #{postId} + + + \ No newline at end of file diff --git a/src/main/resources/com/bao/dating/mapper/PostLikeMapper.xml b/src/main/resources/com/bao/dating/mapper/PostLikeMapper.xml index fe82536..9e26cd2 100644 --- a/src/main/resources/com/bao/dating/mapper/PostLikeMapper.xml +++ b/src/main/resources/com/bao/dating/mapper/PostLikeMapper.xml @@ -14,4 +14,14 @@ delete from dating.post_like where post_id = #{postId} and user_id = #{userId} + + + + DELETE FROM post_like + WHERE post_id IN + + #{postId} + + + \ No newline at end of file diff --git a/src/main/resources/com/bao/dating/mapper/PostMapper.xml b/src/main/resources/com/bao/dating/mapper/PostMapper.xml index b1924e4..d3c96bd 100644 --- a/src/main/resources/com/bao/dating/mapper/PostMapper.xml +++ b/src/main/resources/com/bao/dating/mapper/PostMapper.xml @@ -28,25 +28,19 @@ #{isPublic}, 0, 0, #{createdAt}, #{updatedAt}) - - - DELETE FROM post WHERE post_id IN + + + UPDATE post + + is_public = 3, + updated_at = NOW() + + WHERE post_id IN #{postId} - - - - DELETE FROM post_favorite WHERE post_id = #{postId} - - - - DELETE FROM post_like WHERE post_id = #{postId} - - - - DELETE FROM comments WHERE post_id = #{postId} - + AND user_id = #{userId} +