22 Commits

Author SHA1 Message Date
KilLze
a6259875f2 修消失的MD5 2026-01-05 10:49:32 +08:00
bao
34cad7457b 邮箱验证码
(cherry picked from commit eb2b0cb999)
2026-01-05 10:43:10 +08:00
bao
61c4c9d442 统一redis服务器
(cherry picked from commit 30db5ad706)
2026-01-05 10:02:56 +08:00
KilLze
717c0a0507 优化redis配置类 2026-01-05 01:52:10 +08:00
KilLze
413bafa275 优化和完善批量删除动态功能
实现动态软删除,被删除的动态的状态将改为3,将动态关联的评论,点赞,收藏硬删除
2026-01-03 20:57:37 +08:00
KilLze
2ce8116126 优化一下喵 2026-01-03 19:14:11 +08:00
KilLze
60df001385 用户退出功能实现
用户退出将Redis的token自动添加到黑名单
2026-01-02 12:46:44 +08:00
KilLze
27c64b1106 优化喵 2026-01-02 11:56:21 +08:00
KilLze
0762b84c36 加注释喵 2026-01-02 11:40:14 +08:00
KilLze
61d100fac0 全局异常优化 2026-01-02 11:09:12 +08:00
KilLze
8a6e44e1cb 全局日志打印 2026-01-02 10:38:00 +08:00
KilLze
a004982355 将token令牌存入redis,确保安全性 2026-01-01 21:26:09 +08:00
KilLze
3d8a32cbf7 Merge remote-tracking branch 'upstream/feature-KilLze' into feature-KilLze 2026-01-01 16:24:30 +08:00
KilLze
34f41d61e2 增加日志记录功能,现在在controller层的方法上添加@Log注解即可记录增删改的日志 2026-01-01 16:23:53 +08:00
KilLze
0d166aa400 增加日志记录功能 2026-01-01 16:19:39 +08:00
KilLze
44c0b3611d 加注释 2026-01-01 09:46:27 +08:00
KilLze
f98b0e26f2 AOP记录方法运行耗时(没事闲的) 2025-12-30 01:32:01 +08:00
KilLze
79345eb93e 添加全局异常处理器 2025-12-29 19:29:24 +08:00
KilLze
bfd6674dd9 优化sql语句 2025-12-29 19:01:40 +08:00
KilLze
70a1d0012e 添加注释,优化代码,减少魔法值 2025-12-29 17:58:40 +08:00
KilLze
cc88ec820c 随便改改 2025-12-29 17:24:07 +08:00
KilLze
0c4ddc2803 优化用添加个人信息功能 2025-12-29 17:03:06 +08:00
50 changed files with 1379 additions and 110 deletions

23
pom.xml
View File

@@ -26,6 +26,11 @@
<version>3.5.10</version> <version>3.5.10</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
@@ -56,6 +61,12 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<!-- JUnit Platform Launcher for resolving junit-platform-launcher:1.8.2 issue --> <!-- JUnit Platform Launcher for resolving junit-platform-launcher:1.8.2 issue -->
<dependency> <dependency>
@@ -71,6 +82,12 @@
<version>3.12.0</version> <version>3.12.0</version>
</dependency> </dependency>
<!-- AOP起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 阿里云相关依赖 --> <!-- 阿里云相关依赖 -->
<dependency> <dependency>
<groupId>com.aliyun.oss</groupId> <groupId>com.aliyun.oss</groupId>
@@ -142,6 +159,12 @@
<artifactId>spring-boot-starter-mail</artifactId> <artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<!-- Redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>

View File

@@ -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 {
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -2,6 +2,7 @@ package com.bao.dating.common;
/** /**
* 响应状态码枚举 * 响应状态码枚举
* @author KilLze
*/ */
public enum ResultCode { public enum ResultCode {
/** 成功 */ /** 成功 */
@@ -18,6 +19,14 @@ public enum ResultCode {
FORBIDDEN(403, "无权限"), FORBIDDEN(403, "无权限"),
/** 数据不存在 */ /** 数据不存在 */
NOT_FOUND(404, "数据不存在"), NOT_FOUND(404, "数据不存在"),
/** 验证码已发送 */
CODE_SENT(200, "验证码已发送"),
/** 验证码错误 */
CODE_ERROR(400, "验证码错误"),
/** 验证码已过期 */
CODE_EXPIRED(400, "验证码已过期"),
/** 验证码发送失败 */
CODE_SEND_FAIL(500, "验证码发送失败"),
/** 系统异常 */ /** 系统异常 */
SYSTEM_ERROR(500, "系统异常"), SYSTEM_ERROR(500, "系统异常"),
/** 操作失败 */ /** 操作失败 */

View File

@@ -4,16 +4,17 @@ import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS; import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException; import com.aliyun.oss.OSSException;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* 阿里云OSS工具类
* @author KilLze
*/
@Data @Data
@Slf4j @Slf4j
@Component @Component

View File

@@ -149,3 +149,4 @@ public class SmsUtil {

View File

@@ -0,0 +1,11 @@
package com.bao.dating.common.result;
/**
* 阿里云 OSS 文件上传结果
* @author KilLze
*/
public class AliOssResult {
public static final String IMAGE = "image";
public static final String VIDEO = "video";
}

View File

@@ -0,0 +1,15 @@
package com.bao.dating.common.result;
/**
* 文件上传结果
* @author KilLze
*/
public class FileResult {
public static final String JPG = "jpg";
public static final String JPEG = "jpeg";
public static final String PNG = "png";
public static final String GIF = "gif";
public static final String MP4 = "mp4";
public static final String AVI = "avi";
public static final String MOV = "mov";
}

View File

@@ -0,0 +1,11 @@
package com.bao.dating.common.result;
/**
* 阿里云敏感内容审核结果
* @author KilLze
*/
public class GreenAuditResult {
public static final String PASS = "pass";
public static final String REVIEW = "review";
public static final String BLOCK = "block";
}

View File

@@ -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<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建RedisTemplate对象
RedisTemplate<String, Object> 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;
}
}

View File

@@ -7,6 +7,10 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* WebMvc配置类
* @author KilLze
*/
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {

View File

@@ -1,19 +1,19 @@
package com.bao.dating.context; package com.bao.dating.context;
/** /**
* 用户上下文类 * 用户上下文类用于保存当前线程的用户ID
* @author lenovo * @author lenovo
*/ */
public class UserContext { public class UserContext {
private static final ThreadLocal<Long> userHolder = new ThreadLocal<>(); private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
/** /**
* 设置当前线程的用户ID * 设置当前线程的用户ID
* @param userId 用户ID * @param userId 用户ID
*/ */
public static void setUserId(Long userId) { public static void setUserId(Long userId) {
userHolder.set(userId); USER_HOLDER.set(userId);
} }
/** /**
@@ -21,13 +21,13 @@ public class UserContext {
* @return 当前用户ID如果未设置则返回null * @return 当前用户ID如果未设置则返回null
*/ */
public static Long getUserId() { public static Long getUserId() {
return userHolder.get(); return USER_HOLDER.get();
} }
/** /**
* 清除当前线程的用户ID * 清除当前线程的用户ID
*/ */
public static void clear() { public static void clear() {
userHolder.remove(); USER_HOLDER.remove();
} }
} }

View File

@@ -1,6 +1,7 @@
package com.bao.dating.controller; package com.bao.dating.controller;
import com.bao.dating.anno.Log;
import com.bao.dating.common.Result; import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode; import com.bao.dating.common.ResultCode;
import com.bao.dating.pojo.dto.PostRequestDTO; import com.bao.dating.pojo.dto.PostRequestDTO;
@@ -13,6 +14,11 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
/**
* 动态接口
*
* @author KilLze
*/
@RestController @RestController
@RequestMapping("/posts") @RequestMapping("/posts")
public class PostController { public class PostController {
@@ -25,6 +31,7 @@ public class PostController {
* @param files 媒体文件数组 * @param files 媒体文件数组
* @return 上传后的文件URL列表 * @return 上传后的文件URL列表
*/ */
@Log
@PostMapping(value = "/upload", consumes = "multipart/form-data") @PostMapping(value = "/upload", consumes = "multipart/form-data")
public Result<List<String>> uploadMedia(@RequestParam("files") MultipartFile[] files) { public Result<List<String>> uploadMedia(@RequestParam("files") MultipartFile[] files) {
List<String> fileUrls = postService.uploadMedia(files); List<String> fileUrls = postService.uploadMedia(files);
@@ -36,6 +43,7 @@ public class PostController {
* @param postDTO 动态信息 * @param postDTO 动态信息
* @return 发布的动态对象 * @return 发布的动态对象
*/ */
@Log
@PostMapping( "/createPost") @PostMapping( "/createPost")
public Result<Post> createPostJson(@RequestBody PostRequestDTO postDTO) { public Result<Post> createPostJson(@RequestBody PostRequestDTO postDTO) {
// 调用 Service 层处理发布动态业务逻辑 // 调用 Service 层处理发布动态业务逻辑
@@ -49,10 +57,11 @@ public class PostController {
* @param postIds 动态ID * @param postIds 动态ID
* @return 删除结果 * @return 删除结果
*/ */
@Log
@PostMapping("/deletePost") @PostMapping("/deletePost")
public Result<String> deleteById(@RequestBody List<Long> postIds){ public Result<String> deleteById(@RequestBody List<Long> postIds){
int deletedCount = postService.deletePostById(postIds); int deletedCount = postService.deletePostById(postIds);
return Result.success(ResultCode.SUCCESS_DELETE, deletedCount > 0 ? "成功删除" : "删除失败,该动态不存在", null); return Result.success(ResultCode.SUCCESS_DELETE, "成功删除" + deletedCount + "条动态", null);
} }
/** /**
@@ -60,7 +69,7 @@ public class PostController {
* @param postId 动态ID * @param postId 动态ID
* @return 动态对象 * @return 动态对象
*/ */
@PostMapping("/{postId}") @GetMapping("/{postId}")
public Result<PostEditVO> getPostById(@PathVariable Long postId) { public Result<PostEditVO> getPostById(@PathVariable Long postId) {
PostEditVO postEditVO = postService.getPostForEdit(postId); PostEditVO postEditVO = postService.getPostForEdit(postId);
return Result.success(ResultCode.SUCCESS,"查询成功", postEditVO); return Result.success(ResultCode.SUCCESS,"查询成功", postEditVO);
@@ -72,6 +81,7 @@ public class PostController {
* @param postRequestDTO 动态信息 * @param postRequestDTO 动态信息
* @return 更新后的动态对象 * @return 更新后的动态对象
*/ */
@Log
@PostMapping("/{postId}/updatePost") @PostMapping("/{postId}/updatePost")
public Result<PostEditVO> updatePost(@PathVariable Long postId, @RequestBody PostRequestDTO postRequestDTO) { public Result<PostEditVO> updatePost(@PathVariable Long postId, @RequestBody PostRequestDTO postRequestDTO) {
PostEditVO result = postService.updatePost(postId, postRequestDTO); PostEditVO result = postService.updatePost(postId, postRequestDTO);

View File

@@ -4,7 +4,6 @@ import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode; import com.bao.dating.common.ResultCode;
import com.bao.dating.pojo.entity.User; import com.bao.dating.pojo.entity.User;
import com.bao.dating.service.PostFavoriteService; import com.bao.dating.service.PostFavoriteService;
import com.bao.dating.service.PostService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

@@ -2,13 +2,11 @@ package com.bao.dating.controller;
import com.bao.dating.common.Result; import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode; import com.bao.dating.common.ResultCode;
import com.bao.dating.mapper.PostLikeMapper;
import com.bao.dating.service.PostLikeService; import com.bao.dating.service.PostLikeService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Map; import java.util.Map;
import java.util.Objects;
@RestController @RestController
@RequestMapping("/posts") @RequestMapping("/posts")

View File

@@ -1,20 +1,26 @@
package com.bao.dating.controller; package com.bao.dating.controller;
import com.bao.dating.anno.Log;
import com.bao.dating.common.Result; import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode; import com.bao.dating.common.ResultCode;
import com.bao.dating.context.UserContext; import com.bao.dating.context.UserContext;
import com.bao.dating.pojo.dto.UserInfoUpdateDTO; import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
import com.bao.dating.pojo.dto.UserLoginDTO; import com.bao.dating.pojo.dto.UserLoginDTO;
import com.bao.dating.pojo.entity.User;
import com.bao.dating.pojo.vo.UserInfoVO; import com.bao.dating.pojo.vo.UserInfoVO;
import com.bao.dating.pojo.vo.UserLoginVO; import com.bao.dating.pojo.vo.UserLoginVO;
import com.bao.dating.service.UserService; import com.bao.dating.service.UserService;
import io.jsonwebtoken.Jwt;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List; import javax.servlet.http.HttpServletRequest;
/**
* 用户接口
*
* @author KilLze
*/
@RestController @RestController
@RequestMapping("/user") @RequestMapping("/user")
public class UserController { public class UserController {
@@ -32,6 +38,17 @@ public class UserController {
return Result.success(ResultCode.SUCCESS, "登录成功", userloginVO); return Result.success(ResultCode.SUCCESS, "登录成功", userloginVO);
} }
/**
* 退出登录
* 从请求头中获取token并将其加入黑名单
*/
@PostMapping("/logout")
public Result<Void> logout(HttpServletRequest request) {
String token = request.getHeader("token");
userService.logout(token);
return Result.success(ResultCode.SUCCESS,"退出登录成功",null);
}
/** /**
* 获取用户信息 * 获取用户信息
* @return 用户信息 * @return 用户信息
@@ -48,6 +65,7 @@ public class UserController {
* @param file 头像文件 * @param file 头像文件
* @return 上传后的文件URL列表 * @return 上传后的文件URL列表
*/ */
@Log
@PostMapping(value = "/info/uploadAvatar", consumes = "multipart/form-data") @PostMapping(value = "/info/uploadAvatar", consumes = "multipart/form-data")
public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) { public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
String fileUrl = userService.uploadAvatar(file); String fileUrl = userService.uploadAvatar(file);
@@ -59,6 +77,7 @@ public class UserController {
* @param file 背景文件 * @param file 背景文件
* @return 上传后的文件URL列表 * @return 上传后的文件URL列表
*/ */
@Log
@PostMapping(value = "/info/uploadBackground", consumes = "multipart/form-data") @PostMapping(value = "/info/uploadBackground", consumes = "multipart/form-data")
public Result<String> uploadBackground(@RequestParam("file") MultipartFile file) { public Result<String> uploadBackground(@RequestParam("file") MultipartFile file) {
String fileUrl = userService.uploadBackground(file); String fileUrl = userService.uploadBackground(file);
@@ -70,11 +89,12 @@ public class UserController {
* @param userInfoUpdateDTO 用户信息更新参数 * @param userInfoUpdateDTO 用户信息更新参数
* @return 更新后的用户信息 * @return 更新后的用户信息
*/ */
@Log
@PostMapping("/info/update") @PostMapping("/info/update")
public Result userInfoUpdate(@RequestBody UserInfoUpdateDTO userInfoUpdateDTO) { public Result<UserInfoVO> userInfoUpdate(@RequestBody UserInfoUpdateDTO userInfoUpdateDTO) {
Long userId = UserContext.getUserId(); Long userId = UserContext.getUserId();
userInfoUpdateDTO.setUserId(userId); userInfoUpdateDTO.setUserId(userId);
userService.updateUserInfo(userInfoUpdateDTO); UserInfoVO userInfoVO =userService.updateUserInfo(userInfoUpdateDTO);
return Result.success(ResultCode.SUCCESS, "用户信息更新成功", null); return Result.success(ResultCode.SUCCESS, "用户信息更新成功", userInfoVO);
} }
} }

View File

@@ -0,0 +1,90 @@
package com.bao.dating.controller;
import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode;
import com.bao.dating.service.VerificationCodeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
/**
* 验证码控制器
* @author KilLze
*/
@Slf4j
@RestController
@RequestMapping("/api/verification")
public class VerificationCodeController {
@Autowired
private VerificationCodeService verificationCodeService;
/**
* 发送邮箱验证码
* @param email 邮箱地址
* @return 结果
*/
@PostMapping("/send-email-code")
public Result<String> sendEmailCode(@RequestParam String email) {
// 参数校验
if (!StringUtils.hasText(email)) {
return Result.error(ResultCode.PARAM_ERROR, "邮箱地址不能为空");
}
// 简单的邮箱格式校验
if (!isValidEmail(email)) {
return Result.error(ResultCode.PARAM_ERROR, "邮箱格式不正确");
}
// 发送验证码
boolean success = verificationCodeService.sendEmailCode(email);
if (success) {
return Result.success(ResultCode.CODE_SENT, "验证码已发送到您的邮箱,请查收");
} else {
return Result.error(ResultCode.CODE_SEND_FAIL, "验证码发送失败,请稍后重试");
}
}
/**
* 验证邮箱验证码
* @param email 邮箱地址
* @param code 验证码
* @return 结果
*/
@PostMapping("/verify-email-code")
public Result<String> verifyEmailCode(@RequestParam String email, @RequestParam String code) {
// 参数校验
if (!StringUtils.hasText(email)) {
return Result.error(ResultCode.PARAM_ERROR, "邮箱地址不能为空");
}
if (!StringUtils.hasText(code)) {
return Result.error(ResultCode.PARAM_ERROR, "验证码不能为空");
}
// 验证验证码
boolean success = verificationCodeService.verifyEmailCode(email, code);
if (success) {
return Result.success(ResultCode.SUCCESS, "验证码验证成功");
} else {
return Result.error(ResultCode.CODE_ERROR, "验证码错误或已过期");
}
}
/**
* 简单的邮箱格式校验
* @param email 邮箱地址
* @return 是否有效
*/
private boolean isValidEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
// 简单的邮箱格式校验:包含@和.
return email.contains("@") && email.contains(".") && email.length() > 5;
}
}

View File

@@ -0,0 +1,113 @@
package com.bao.dating.handler;
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.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
/**
* 全局异常处理器
* 统一处理控制器层抛出的异常
* @author KilLze
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数验证失败异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("参数验证失败: {}", e.getMessage());
String msg = e.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + "" + error.getDefaultMessage())
.collect(Collectors.joining("; "));
return Result.error(ResultCode.PARAM_ERROR, msg);
}
/**
* 处理请求参数缺失异常
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public Result<String> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
log.error("请求参数缺失: 参数名={}, 参数类型={}", e.getParameterName(), e.getParameterType());
return Result.error(ResultCode.PARAM_ERROR, "缺少必需的请求参数: " + e.getParameterName());
}
/**
* 处理请求参数类型不匹配异常
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public Result<String> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.error("请求参数类型不匹配: {}", e.getMessage());
return Result.error(ResultCode.PARAM_ERROR, "请求参数类型错误: " + e.getName());
}
/**
* 处理请求体缺失或格式错误异常
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result<String> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("请求体格式错误: {}", e.getMessage());
return Result.error(ResultCode.PARAM_ERROR, "请求体格式错误或缺失");
}
/**
* 处理不支持的HTTP请求方法异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result<String> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("不支持的HTTP请求方法: {}", e.getMethod());
return Result.error(ResultCode.PARAM_ERROR, "不支持的请求方法: " + e.getMethod());
}
/**
* 处理404异常
*/
@ExceptionHandler(NoHandlerFoundException.class)
public Result<String> handleNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) {
log.error("请求的接口不存在: {} {}", request.getMethod(), request.getRequestURI());
return Result.error(ResultCode.NOT_FOUND, "请求的接口不存在");
}
/**
* 处理数据库唯一约束违反异常
*/
@ExceptionHandler(DuplicateKeyException.class)
public Result<String> handleDuplicateKeyException(DuplicateKeyException e) {
log.error("数据库唯一约束违反: {}", e.getMessage());
return Result.error(ResultCode.FAIL, "数据已存在,操作失败");
}
/**
* 处理运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public Result<String> handleRuntimeException(RuntimeException e) {
log.error("运行时异常: ", e);
return Result.error(ResultCode.SYSTEM_ERROR, e.getMessage());
}
/**
* 处理通用异常
*/
@ExceptionHandler(Exception.class)
public Result<String> handleException(HttpServletRequest request, Exception e) {
log.error("系统异常 [{} {}]: ", request.getMethod(), request.getRequestURI(), e);
return Result.error(ResultCode.SYSTEM_ERROR, "系统内部错误");
}
}

View File

@@ -13,11 +13,16 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
@MappedJdbcTypes(JdbcType.VARCHAR) // 也可以使用JdbcType.JSON如果数据库支持的话
/**
* List类型转换成JSON类型
* @author KilLze
*/
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class) @MappedTypes(List.class)
public class ListToJsonTypeHandler implements TypeHandler<List<String>> { public class ListToJsonTypeHandler implements TypeHandler<List<String>> {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override @Override
public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException { public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
@@ -26,7 +31,7 @@ public class ListToJsonTypeHandler implements TypeHandler<List<String>> {
return; return;
} }
try { try {
String json = objectMapper.writeValueAsString(parameter); String json = OBJECT_MAPPER.writeValueAsString(parameter);
ps.setString(i, json); ps.setString(i, json);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new SQLException("Error converting list to JSON", e); throw new SQLException("Error converting list to JSON", e);
@@ -56,7 +61,7 @@ public class ListToJsonTypeHandler implements TypeHandler<List<String>> {
return null; return null;
} }
try { try {
return objectMapper.readValue(json, new TypeReference<List<String>>() {}); return OBJECT_MAPPER.readValue(json, new TypeReference<List<String>>() {});
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new SQLException("Error converting JSON to list", e); throw new SQLException("Error converting JSON to list", e);
} }

View File

@@ -7,6 +7,8 @@ import com.bao.dating.context.UserContext;
import com.bao.dating.util.JwtUtil; import com.bao.dating.util.JwtUtil;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j; 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.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
@@ -14,10 +16,15 @@ import org.springframework.web.servlet.HandlerInterceptor;
/** /**
* HttpToken拦截器类 * HttpToken拦截器类
* 用于拦截请求并验证JWT token的有效性同时从token中解析用户信息 * 用于拦截请求并验证JWT token的有效性同时从token中解析用户信息
* @author KilLze
*/ */
@Slf4j @Slf4j
@Component @Component
public class TokenInterceptor implements HandlerInterceptor { public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/** /**
* 在请求处理之前进行拦截 * 在请求处理之前进行拦截
* 从请求头或URL参数中获取token验证其有效性并将用户ID保存到ThreadLocal中 * 从请求头或URL参数中获取token验证其有效性并将用户ID保存到ThreadLocal中
@@ -43,15 +50,40 @@ public class TokenInterceptor implements HandlerInterceptor {
// 验证 token 是否有效(包括是否过期) // 验证 token 是否有效(包括是否过期)
if (!JwtUtil.validateToken(token)) { if (!JwtUtil.validateToken(token)) {
log.error("Token 无效或已过期"); log.error("Token无效或已过期");
response.setStatus(401); 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; return false;
} }
// 解析 token // 解析 token
String userId = JwtUtil.getSubjectFromToken(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); log.info("用户: {}", userId);
// 保存 userId 到 ThreadLocal // 保存 userId 到 ThreadLocal
UserContext.setUserId(Long.valueOf(userId)); UserContext.setUserId(Long.valueOf(userId));
return true; return true;

View File

@@ -18,4 +18,11 @@ public interface CommentsMapper {
// 根据动态ID查询评论列表 // 根据动态ID查询评论列表
@Select("SELECT * FROM comments WHERE post_id = #{post_id} ORDER BY created_at DESC") @Select("SELECT * FROM comments WHERE post_id = #{post_id} ORDER BY created_at DESC")
List<Comments> getCommentsByPostId(@Param("post_id") Long post_id); List<Comments> getCommentsByPostId(@Param("post_id") Long post_id);
/**
* 根据动态ID批量删除评论
* @param postIds
* @return
*/
int deleteCommentsByPostIds(@Param("postIds") List<Long> postIds);
} }

View File

@@ -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);
}

View File

@@ -12,4 +12,12 @@ public interface PostFavoriteMapper {
List<Long> selectUserIDByPostID(@Param("postId") Long postId); List<Long> selectUserIDByPostID(@Param("postId") Long postId);
int addPostFavorite(PostFavorite postFavorite); int addPostFavorite(PostFavorite postFavorite);
int deletePostFavorite(@Param("postId") Long postId); int deletePostFavorite(@Param("postId") Long postId);
/**
* 批量删除收藏
* @param postIds
* @return
*/
int deleteFavoritesByPostIds(@Param("postIds") List<Long> postIds);
} }

View File

@@ -4,6 +4,8 @@ import com.bao.dating.pojo.entity.PostLike;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper @Mapper
public interface PostLikeMapper { public interface PostLikeMapper {
/** /**
@@ -31,4 +33,12 @@ public interface PostLikeMapper {
* @return * @return
*/ */
int deleteByPostIdAndUserId(@Param("postId") Long postId, @Param("userId") Long userId); int deleteByPostIdAndUserId(@Param("postId") Long postId, @Param("userId") Long userId);
/**
* 批量删除点赞记录
*
* @param postIds
* @return
*/
int deleteLikesByPostIds(@Param("postIds") List<Long> postIds);
} }

View File

@@ -6,43 +6,47 @@ import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
/**
* 动态Mapper
*
* @author KilLze lanyangyang-yzx
*/
@Mapper @Mapper
public interface PostMapper { public interface PostMapper {
/** /**
* 插入动态 * 插入动态
* *
* @param post * @param post 动态
*/ */
void insert(Post post); void insert(Post post);
/** /**
* 根据ID删除动 * 根据ID修改动态状
* *
* @param postIds 动态ID * @param postIds 动态ID
*/ */
int deletePostByIds(List<Long> postIds); int updatePublicById(@Param("postIds") List<Long> postIds, @Param("userId") Long userId);
/** /**
* 根据ID查询动态 * 根据ID查询动态
* *
* @param postId * @param postId 动态ID
* @return * @return 动态
*/ */
Post selectById(Long postId); Post selectById(Long postId);
/** /**
* 根据ID更新动态 * 根据ID更新动态
* *
* @param post * @param post 动态
* @return
*/ */
void updateById(Post post); void updateById(Post post);
/** /**
* 查询点赞数 * 查询点赞数
* *
* @param postId * @param postId 动态ID
* @return * @return 点赞数
*/ */
int selectLikeCount(Long postId); int selectLikeCount(Long postId);
@@ -72,8 +76,8 @@ public interface PostMapper {
/** /**
* 查询点赞数 * 查询点赞数
* *
* @param postId * @param postId 动态ID
* @return * @return 点赞数
*/ */
int selectFavoriteCount(Long postId); int selectFavoriteCount(Long postId);

View File

@@ -1,10 +1,13 @@
package com.bao.dating.mapper; package com.bao.dating.mapper;
import com.bao.dating.pojo.dto.UserInfoUpdateDTO; import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
import com.bao.dating.pojo.entity.Post;
import com.bao.dating.pojo.entity.User; import com.bao.dating.pojo.entity.User;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper
* @author KilLze
*/
@Mapper @Mapper
public interface UserMapper { public interface UserMapper {
@@ -26,7 +29,7 @@ public interface UserMapper {
/** /**
* 更新用户信息 * 更新用户信息
* @param userInfoUpdateDTO * @param userInfoUpdateDTO 用户信息更新参数
*/ */
void updateUserInfoByUserId(UserInfoUpdateDTO userInfoUpdateDTO); void updateUserInfoByUserId(UserInfoUpdateDTO userInfoUpdateDTO);

View File

@@ -7,9 +7,10 @@ import java.util.List;
/** /**
* 动态数据传输对象 * 动态数据传输对象
* @author KilLze
*/ */
@Data @Data
public class PostRequestDTO implements Serializable { public class PostRequestDTO implements Serializable{
private String content; private String content;
private List<String> mediaOssKeys; private List<String> mediaOssKeys;
private List<String> tags; private List<String> tags;

View File

@@ -2,13 +2,19 @@ package com.bao.dating.pojo.dto;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/**
* 用户信息更新数据传输对象
* @author KilLze
*/
@Data @Data
public class UserInfoUpdateDTO { public class UserInfoUpdateDTO implements Serializable {
private Long userId; private Long userId;
private String userName;
private String nickname; private String nickname;
private String avatarUrl; private String avatarUrl;
private String backgroundUrl; private String backgroundUrl;

View File

@@ -6,6 +6,7 @@ import java.io.Serializable;
/** /**
* 用户登录数据传输对象 * 用户登录数据传输对象
* @author KilLze
*/ */
@Data @Data
public class UserLoginDTO implements Serializable { public class UserLoginDTO implements Serializable {

View File

@@ -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;
}

View File

@@ -8,6 +8,7 @@ import java.util.List;
/** /**
* 修改内容查询返回数据 * 修改内容查询返回数据
* @author KilLze
*/ */
@Data @Data
public class PostEditVO implements Serializable { public class PostEditVO implements Serializable {

View File

@@ -2,16 +2,19 @@ package com.bao.dating.pojo.vo;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/** /**
* 用户信息VO * 用户信息VO
* @author KilLze
*/ */
@Data @Data
public class UserInfoVO { public class UserInfoVO implements Serializable {
private Long userId; private Long userId;
private String userName;
private String nickname; private String nickname;
private String avatarUrl; private String avatarUrl;
private String backgroundUrl; private String backgroundUrl;

View File

@@ -5,6 +5,7 @@ import java.io.Serializable;
/** /**
* 登录返回数据 * 登录返回数据
* @author KilLze
*/ */
@Data @Data
public class UserLoginVO implements Serializable { public class UserLoginVO implements Serializable {

View File

@@ -7,6 +7,10 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
/**
* 动态服务
* @author bao KilLze lanyangyang-yzx yang
*/
public interface PostService { public interface PostService {
/** /**
* 上传媒体文件 * 上传媒体文件
@@ -23,7 +27,7 @@ public interface PostService {
Post createPost(PostRequestDTO postRequestDTO); Post createPost(PostRequestDTO postRequestDTO);
/** /**
* 批量删除动态 * 批量删除动态(将动态状态改为已删除)
* @param postIds 动态ID * @param postIds 动态ID
* @return 删除的动态对象 * @return 删除的动态对象
*/ */
@@ -33,6 +37,7 @@ public interface PostService {
/** /**
* 查询动态详情(用于编辑) * 查询动态详情(用于编辑)
* @param postId 动态ID * @param postId 动态ID
* @return 动态详情
*/ */
PostEditVO getPostForEdit(Long postId); PostEditVO getPostForEdit(Long postId);

View File

@@ -2,13 +2,14 @@ package com.bao.dating.service;
import com.bao.dating.pojo.dto.UserInfoUpdateDTO; import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
import com.bao.dating.pojo.dto.UserLoginDTO; import com.bao.dating.pojo.dto.UserLoginDTO;
import com.bao.dating.pojo.vo.PostEditVO;
import com.bao.dating.pojo.vo.UserInfoVO; import com.bao.dating.pojo.vo.UserInfoVO;
import com.bao.dating.pojo.vo.UserLoginVO; import com.bao.dating.pojo.vo.UserLoginVO;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List; /**
* 用户服务接口
* @author KilLze
*/
public interface UserService { public interface UserService {
/** /**
* 登录 * 登录
@@ -17,9 +18,17 @@ public interface UserService {
*/ */
UserLoginVO userLogin(UserLoginDTO userLoginDTO); UserLoginVO userLogin(UserLoginDTO userLoginDTO);
/**
* 退出登录
* @param token 登录凭证
* @return 注册结果
*/
void logout(String token);
/** /**
* 查询个人信息 * 查询个人信息
* @param userId 动态ID * @param userId 动态ID
* @return 个人信息
*/ */
UserInfoVO getUserInfo(Long userId); UserInfoVO getUserInfo(Long userId);
@@ -40,6 +49,7 @@ public interface UserService {
/** /**
* 更新用户信息 * 更新用户信息
* @param userInfoUpdateDTO 用户信息 * @param userInfoUpdateDTO 用户信息
* @return 更新后的用户信息
*/ */
void updateUserInfo(UserInfoUpdateDTO userInfoUpdateDTO); UserInfoVO updateUserInfo(UserInfoUpdateDTO userInfoUpdateDTO);
} }

View File

@@ -0,0 +1,31 @@
package com.bao.dating.service;
/**
* 验证码服务接口
* @author KilLze
*/
public interface VerificationCodeService {
/**
* 发送邮箱验证码
* @param email 邮箱地址
* @return 是否发送成功
*/
boolean sendEmailCode(String email);
/**
* 验证邮箱验证码
* @param email 邮箱地址
* @param code 验证码
* @return 是否验证成功
*/
boolean verifyEmailCode(String email, String code);
/**
* 生成验证码
* @param length 验证码长度默认6位
* @return 验证码字符串
*/
String generateCode(int length);
}

View File

@@ -2,18 +2,24 @@ package com.bao.dating.service.impl;
import com.bao.dating.common.aliyun.GreenImageScan; import com.bao.dating.common.aliyun.GreenImageScan;
import com.bao.dating.common.aliyun.GreenTextScan; import com.bao.dating.common.aliyun.GreenTextScan;
import com.bao.dating.common.result.GreenAuditResult;
import com.bao.dating.context.UserContext; 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.mapper.PostMapper;
import com.bao.dating.pojo.dto.PostRequestDTO; import com.bao.dating.pojo.dto.PostRequestDTO;
import com.bao.dating.pojo.entity.Post; import com.bao.dating.pojo.entity.Post;
import com.bao.dating.pojo.vo.PostEditVO; import com.bao.dating.pojo.vo.PostEditVO;
import com.bao.dating.service.PostService; import com.bao.dating.service.PostService;
import com.bao.dating.common.aliyun.AliOssUtil; import com.bao.dating.common.aliyun.AliOssUtil;
import com.bao.dating.service.UserService;
import com.bao.dating.util.FileUtil; import com.bao.dating.util.FileUtil;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
@@ -25,7 +31,7 @@ import java.util.*;
/** /**
* 动态服务实现类 * 动态服务实现类
* *
* @author KilLze * @author KilLze yang
*/ */
@Service @Service
public class PostServiceImpl implements PostService { public class PostServiceImpl implements PostService {
@@ -42,6 +48,15 @@ public class PostServiceImpl implements PostService {
@Autowired @Autowired
private PostMapper postMapper; private PostMapper postMapper;
@Autowired
private PostLikeMapper postLikeMapper;
@Autowired
private PostFavoriteMapper postFavoriteMapper;
@Autowired
private CommentsMapper commentsMapper;
/** /**
* 上传媒体文件 * 上传媒体文件
* @param files 媒体文件数组 * @param files 媒体文件数组
@@ -144,10 +159,10 @@ public class PostServiceImpl implements PostService {
String imageSuggestion = (String) imageResult.get("suggestion"); String imageSuggestion = (String) imageResult.get("suggestion");
// 根据审核结果设置状态 // 根据审核结果设置状态
if ("block".equals(textSuggestion) || "block".equals(imageSuggestion)) { if (GreenAuditResult.BLOCK.equals(textSuggestion) || GreenAuditResult.BLOCK.equals(imageSuggestion)) {
// 审核未通过,允许用户修改 // 审核未通过,允许用户修改
post.setIsPublic(2); post.setIsPublic(2);
} else if ("review".equals(textSuggestion) || "review".equals(imageSuggestion)) { } else if (GreenAuditResult.REVIEW.equals(textSuggestion) || GreenAuditResult.REVIEW.equals(imageSuggestion)) {
// 待审核,需人工审核 // 待审核,需人工审核
post.setIsPublic(1); post.setIsPublic(1);
} else { } else {
@@ -156,10 +171,10 @@ public class PostServiceImpl implements PostService {
} }
} else { } else {
// 只有文本内容的情况 // 只有文本内容的情况
if ("block".equals(textSuggestion)) { if (GreenAuditResult.BLOCK.equals(textSuggestion)) {
// 审核未通过,允许用户修改 // 审核未通过,允许用户修改
post.setIsPublic(2); post.setIsPublic(2);
} else if ("review".equals(textSuggestion)) { } else if (GreenAuditResult.REVIEW.equals(textSuggestion)) {
// 待审核,需人工审核 // 待审核,需人工审核
post.setIsPublic(1); post.setIsPublic(1);
} else { } else {
@@ -176,7 +191,7 @@ public class PostServiceImpl implements PostService {
} }
/** /**
* 批量删除动态 * 批量删除动态(将动态状态改为已删除)
* *
* @param postIds 动态ID * @param postIds 动态ID
* @return 删除的动态对象 * @return 删除的动态对象
@@ -187,19 +202,21 @@ public class PostServiceImpl implements PostService {
// 判断用户权限 // 判断用户权限
Long userId = UserContext.getUserId(); Long userId = UserContext.getUserId();
// 遍历所有要删除的帖子ID验证权限 if (CollectionUtils.isEmpty(postIds)) {
for (Long postId : postIds) { return 0;
Post post = postMapper.selectById(postId);
if (post == null) {
throw new RuntimeException("动态不存在");
}
// 验证用户权限
if (post.getUserId() == null || !post.getUserId().equals(userId)) {
throw new RuntimeException("无权限删除此动态");
}
} }
// 批量删除动态 int affected = postMapper.updatePublicById(postIds, userId);
return postMapper.deletePostByIds(postIds);
if (affected == 0) {
throw new RuntimeException("未删除任何动态,可能无权限或动态不存在");
}
// 删除动态下的评论、点赞、收藏
commentsMapper.deleteCommentsByPostIds(postIds);
postLikeMapper.deleteLikesByPostIds(postIds);
postFavoriteMapper.deleteFavoritesByPostIds(postIds);
return affected;
} }
/** /**
@@ -268,10 +285,10 @@ public class PostServiceImpl implements PostService {
String imageSuggestion = (String) imageResult.get("suggestion"); String imageSuggestion = (String) imageResult.get("suggestion");
// 根据审核结果设置状态 // 根据审核结果设置状态
if ("block".equals(textSuggestion) || "block".equals(imageSuggestion)) { if (GreenAuditResult.BLOCK.equals(textSuggestion) || GreenAuditResult.BLOCK.equals(imageSuggestion)) {
// 审核未通过,允许用户修改 // 审核未通过,允许用户修改
post.setIsPublic(2); post.setIsPublic(2);
} else if ("review".equals(textSuggestion) || "review".equals(imageSuggestion)) { } else if (GreenAuditResult.REVIEW.equals(textSuggestion) || GreenAuditResult.REVIEW.equals(imageSuggestion)) {
// 待审核,需人工审核 // 待审核,需人工审核
post.setIsPublic(1); post.setIsPublic(1);
} else { } else {
@@ -280,10 +297,10 @@ public class PostServiceImpl implements PostService {
} }
} else { } else {
// 只有文本内容的情况 // 只有文本内容的情况
if ("block".equals(textSuggestion)) { if (GreenAuditResult.BLOCK.equals(textSuggestion)) {
// 审核未通过,允许用户修改 // 审核未通过,允许用户修改
post.setIsPublic(2); post.setIsPublic(2);
} else if ("review".equals(textSuggestion)) { } else if (GreenAuditResult.REVIEW.equals(textSuggestion)) {
// 待审核,需人工审核 // 待审核,需人工审核
post.setIsPublic(1); post.setIsPublic(1);
} else { } else {

View File

@@ -3,33 +3,37 @@ package com.bao.dating.service.impl;
import com.bao.dating.common.aliyun.AliOssUtil; import com.bao.dating.common.aliyun.AliOssUtil;
import com.bao.dating.common.aliyun.GreenImageScan; import com.bao.dating.common.aliyun.GreenImageScan;
import com.bao.dating.common.aliyun.GreenTextScan; 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.context.UserContext;
import com.bao.dating.mapper.UserMapper; import com.bao.dating.mapper.UserMapper;
import com.bao.dating.pojo.dto.UserInfoUpdateDTO; import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
import com.bao.dating.pojo.dto.UserLoginDTO; import com.bao.dating.pojo.dto.UserLoginDTO;
import com.bao.dating.pojo.entity.User; import com.bao.dating.pojo.entity.User;
import com.bao.dating.pojo.vo.PostEditVO;
import com.bao.dating.pojo.vo.UserInfoVO; import com.bao.dating.pojo.vo.UserInfoVO;
import com.bao.dating.pojo.vo.UserLoginVO; import com.bao.dating.pojo.vo.UserLoginVO;
import com.bao.dating.service.UserService; import com.bao.dating.service.UserService;
import com.bao.dating.util.FileUtil; import com.bao.dating.util.FileUtil;
import com.bao.dating.util.JwtUtil; import com.bao.dating.util.JwtUtil;
import com.bao.dating.util.MD5Util; import com.bao.dating.util.MD5Util;
import io.jsonwebtoken.Claims;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.util.*;
import java.util.ArrayList; import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* 用户服务实现类
*
* @author KilLze
*/
@Service @Service
public class UserServiceImpl implements UserService { public class UserServiceImpl implements UserService {
@@ -42,13 +46,16 @@ public class UserServiceImpl implements UserService {
@Autowired @Autowired
private GreenImageScan greenImageScan; private GreenImageScan greenImageScan;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired @Autowired
private UserMapper userMapper; private UserMapper userMapper;
/** /**
* 用户登录 * 用户登录
* *
* @param userLoginDTO * @param userLoginDTO 登录参数
* @return 登录信息 * @return 登录信息
*/ */
@Override @Override
@@ -73,6 +80,15 @@ public class UserServiceImpl implements UserService {
} }
// 生成token // 生成token
String token = JwtUtil.generateToken(String.valueOf(user.getUserId())); 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 userLoginVO = new UserLoginVO();
userLoginVO.setUserId(user.getUserId()); userLoginVO.setUserId(user.getUserId());
@@ -81,10 +97,33 @@ public class UserServiceImpl implements UserService {
return userLoginVO; 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);
}
/** /**
* 获取用户信息 * 获取用户信息
* *
* @param userId * @param userId 用户ID
* @return 用户信息 * @return 用户信息
*/ */
@Override @Override
@@ -98,6 +137,12 @@ public class UserServiceImpl implements UserService {
return userInfoVO; return userInfoVO;
} }
/**
* 上传头像接口
*
* @param file 头像文件
* @return 上传后的文件URL
*/
@Override @Override
public String uploadAvatar(MultipartFile file) { public String uploadAvatar(MultipartFile file) {
// 参数校验 // 参数校验
@@ -111,12 +156,11 @@ public class UserServiceImpl implements UserService {
} }
String fileType = FileUtil.getFileType(originalFilename); String fileType = FileUtil.getFileType(originalFilename);
if (!"image".equals(fileType)) { if (!AliOssResult.IMAGE.equals(fileType)) {
throw new RuntimeException("仅支持图片上传"); throw new RuntimeException("仅支持图片上传");
} }
//生成 OSS 路径 //生成 OSS 路径
String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
String extension = FileUtil.getFileExtension(originalFilename); String extension = FileUtil.getFileExtension(originalFilename);
String fileName = UUID.randomUUID().toString().replace("-", "") + "." + extension; String fileName = UUID.randomUUID().toString().replace("-", "") + "." + extension;
Long userId = UserContext.getUserId(); Long userId = UserContext.getUserId();
@@ -137,6 +181,12 @@ public class UserServiceImpl implements UserService {
} }
} }
/**
* 上传背景图片
*
* @param file 背景图片文件
* @return 上传后的文件URL
*/
@Override @Override
public String uploadBackground(MultipartFile file) { public String uploadBackground(MultipartFile file) {
// 参数校验 // 参数校验
@@ -150,7 +200,7 @@ public class UserServiceImpl implements UserService {
} }
String fileType = FileUtil.getFileType(originalFilename); String fileType = FileUtil.getFileType(originalFilename);
if (!"image".equals(fileType)) { if (!AliOssResult.IMAGE.equals(fileType)) {
throw new RuntimeException("仅支持图片上传"); throw new RuntimeException("仅支持图片上传");
} }
@@ -178,11 +228,10 @@ public class UserServiceImpl implements UserService {
/** /**
* 更新用户信息 * 更新用户信息
* *
* @param userInfoUpdateDTO * @param userInfoUpdateDTO 用户信息更新参数
* @return 用户信息
*/ */
@Override @Override
public void updateUserInfo(UserInfoUpdateDTO userInfoUpdateDTO) { public UserInfoVO updateUserInfo(UserInfoUpdateDTO userInfoUpdateDTO) {
Long userId = userInfoUpdateDTO.getUserId(); Long userId = userInfoUpdateDTO.getUserId();
User user = userMapper.selectByUserId(userId); User user = userMapper.selectByUserId(userId);
if (user == null) { if (user == null) {
@@ -214,10 +263,10 @@ public class UserServiceImpl implements UserService {
String suggestion = (String) textResult.get("suggestion"); String suggestion = (String) textResult.get("suggestion");
if ("block".equals(suggestion)) { if (GreenAuditResult.BLOCK.equals(suggestion)) {
throw new RuntimeException("用户信息包含违规内容,修改失败"); throw new RuntimeException("用户信息包含违规内容,修改失败");
} }
if ("review".equals(suggestion)) { if (GreenAuditResult.REVIEW.equals(suggestion)) {
throw new RuntimeException("用户信息需要人工审核,暂无法修改"); throw new RuntimeException("用户信息需要人工审核,暂无法修改");
} }
} }
@@ -240,17 +289,26 @@ public class UserServiceImpl implements UserService {
String suggestion = (String) imageResult.get("suggestion"); String suggestion = (String) imageResult.get("suggestion");
if ("block".equals(suggestion)) { if (GreenAuditResult.BLOCK.equals(suggestion)) {
throw new RuntimeException("头像或背景图不合规,修改失败"); throw new RuntimeException("头像或背景图不合规,修改失败");
} }
if ("review".equals(suggestion)) { if (GreenAuditResult.REVIEW.equals(suggestion)) {
throw new RuntimeException("头像或背景图需要人工审核,暂无法修改"); throw new RuntimeException("头像或背景图需要人工审核,暂无法修改");
} }
} }
// 默认昵称兜底
if (userInfoUpdateDTO.getNickname() == null || userInfoUpdateDTO.getNickname().trim().isEmpty()) {
userInfoUpdateDTO.setNickname(user.getUserName());
}
userInfoUpdateDTO.setUpdatedAt(LocalDateTime.now()); userInfoUpdateDTO.setUpdatedAt(LocalDateTime.now());
// 更新数据库 // 更新数据库
userMapper.updateUserInfoByUserId(userInfoUpdateDTO); userMapper.updateUserInfoByUserId(userInfoUpdateDTO);
// 封装返回结果
User updatedUser = userMapper.selectByUserId(userInfoUpdateDTO.getUserId());
UserInfoVO userInfoVO = new UserInfoVO();
BeanUtils.copyProperties(updatedUser, userInfoVO);
return userInfoVO;
} }
} }

View File

@@ -0,0 +1,122 @@
package com.bao.dating.service.impl;
import com.bao.dating.service.VerificationCodeService;
import com.bao.dating.util.EmailUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 验证码服务实现类
* @author KilLze
*/
@Slf4j
@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {
@Autowired
private EmailUtil emailUtil;
@Autowired
private StringRedisTemplate redisTemplate;
/**
* Redis中验证码的key前缀
*/
private static final String CODE_KEY_PREFIX = "email:code:";
/**
* 验证码过期时间(分钟)
*/
private static final long CODE_EXPIRE_MINUTES = 10;
/**
* 验证码长度
*/
private static final int CODE_LENGTH = 6;
/**
* 发送邮箱验证码
* @param email 邮箱地址
* @return 是否发送成功
*/
@Override
public boolean sendEmailCode(String email) {
try {
// 生成验证码
String code = generateCode(CODE_LENGTH);
// 存储到Redis设置过期时间
String key = CODE_KEY_PREFIX + email;
redisTemplate.opsForValue().set(key, code, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES);
// 发送邮件
boolean sendResult = emailUtil.sendVerificationCode(email, code);
if (sendResult) {
log.info("邮箱验证码发送成功,邮箱:{},验证码:{}", email, code);
return true;
} else {
// 如果发送失败删除Redis中的验证码
redisTemplate.delete(key);
log.error("邮箱验证码发送失败,邮箱:{}", email);
return false;
}
} catch (Exception e) {
log.error("发送邮箱验证码异常,邮箱:{},异常信息:{}", email, e.getMessage(), e);
return false;
}
}
/**
* 验证邮箱验证码
* @param email 邮箱地址
* @param code 验证码
* @return 是否验证成功
*/
@Override
public boolean verifyEmailCode(String email, String code) {
try {
String key = CODE_KEY_PREFIX + email;
String storedCode = redisTemplate.opsForValue().get(key);
if (storedCode == null) {
log.warn("验证码不存在或已过期,邮箱:{}", email);
return false;
}
if (storedCode.equals(code)) {
// 验证成功后,删除验证码(防止重复使用)
redisTemplate.delete(key);
log.info("邮箱验证码验证成功,邮箱:{}", email);
return true;
} else {
log.warn("邮箱验证码错误,邮箱:{},输入的验证码:{},正确的验证码:{}", email, code, storedCode);
return false;
}
} catch (Exception e) {
log.error("验证邮箱验证码异常,邮箱:{},异常信息:{}", email, e.getMessage(), e);
return false;
}
}
/**
* 生成验证码
* @param length 验证码长度
* @return 验证码字符串
*/
@Override
public String generateCode(int length) {
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < length; i++) {
code.append(random.nextInt(10)); // 生成0-9的随机数字
}
return code.toString();
}
}

View File

@@ -1,5 +1,7 @@
package com.bao.dating.util; package com.bao.dating.util;
import com.bao.dating.common.result.FileResult;
/** /**
* 文件工具类 * 文件工具类
* @author KilLze * @author KilLze
@@ -13,9 +15,9 @@ public class FileUtil {
public static String getFileType(String fileUrl) { public static String getFileType(String fileUrl) {
String extension = getFileExtension(fileUrl); String extension = getFileExtension(fileUrl);
if (extension.equals("jpg") || extension.equals("jpeg") || extension.equals("png") || extension.equals("gif")) { if (FileResult.JPG.equals(extension) || FileResult.JPEG.equals(extension) || FileResult.PNG.equals(extension) || FileResult.GIF.equals(extension)) {
return "image"; return "image";
} else if (extension.equals("mp4") || extension.equals("avi") || extension.equals("mov")) { } else if (FileResult.MP4.equals(extension) || FileResult.AVI.equals(extension) || FileResult.MOV.equals(extension)) {
return "video"; return "video";
} }
return "unknown"; return "unknown";

View File

@@ -2,14 +2,19 @@ server:
port: 8080 port: 8080
spring: spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
datasource: datasource:
url: jdbc:mysql://110.42.41.177:3306/dating?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 url: jdbc:mysql://110.42.41.177:3306/dating?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root username: root
password: JoyeeServe2025 password: JoyeeServe2025
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
redis: redis:
host: 127.0.0.1 host: 110.42.41.177
port: 6379 port: 6389
password: "" password: ""
database: 0 database: 0
timeout: 10000 timeout: 10000

View File

@@ -0,0 +1,15 @@
<?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.bao.dating.mapper.CommentsMapper">
<!-- 批量删除动态下的所有评论 -->
<delete id="deleteCommentsByPostIds">
DELETE FROM comments
WHERE post_id IN
<foreach collection="postIds" item="postId" open="(" close=")" separator=",">
#{postId}
</foreach>
</delete>
</mapper>

View File

@@ -14,4 +14,14 @@
<select id="selectUserIDByPostID" resultType="java.lang.Long"> <select id="selectUserIDByPostID" resultType="java.lang.Long">
SELECT user_id FROM post_favorite WHERE post_id = #{postId} SELECT user_id FROM post_favorite WHERE post_id = #{postId}
</select> </select>
<!--批量删除动态收藏-->
<delete id="deleteFavoritesByPostIds">
DELETE FROM post_favorite
WHERE post_id IN
<foreach collection="postIds" item="postId" open="(" close=")" separator=",">
#{postId}
</foreach>
</delete>
</mapper> </mapper>

View File

@@ -14,4 +14,14 @@
<delete id="deleteByPostIdAndUserId"> <delete id="deleteByPostIdAndUserId">
delete from dating.post_like where post_id = #{postId} and user_id = #{userId} delete from dating.post_like where post_id = #{postId} and user_id = #{userId}
</delete> </delete>
<!--批量删除点赞记录-->
<delete id="deleteLikesByPostIds">
DELETE FROM post_like
WHERE post_id IN
<foreach collection="postIds" item="postId" open="(" close=")" separator=",">
#{postId}
</foreach>
</delete>
</mapper> </mapper>

View File

@@ -28,25 +28,19 @@
#{isPublic}, 0, 0, #{createdAt}, #{updatedAt}) #{isPublic}, 0, 0, #{createdAt}, #{updatedAt})
</insert> </insert>
<!--动态删除--> <!--修改动态状态-->
<delete id="deletePostByIds"> <update id="updatePublicById">
DELETE FROM post WHERE post_id IN UPDATE post
<set>
is_public = 3,
updated_at = NOW()
</set>
WHERE post_id IN
<foreach item="postId" index="index" collection="postIds" separator="," open="(" close=")"> <foreach item="postId" index="index" collection="postIds" separator="," open="(" close=")">
#{postId} #{postId}
</foreach> </foreach>
</delete> AND user_id = #{userId}
<!--删除收藏记录--> </update>
<delete id="1">
DELETE FROM post_favorite WHERE post_id = #{postId}
</delete>
<!--删除点赞记录-->
<delete id="2">
DELETE FROM post_like WHERE post_id = #{postId}
</delete>
<!--动态评论删除-->
<delete id="3">
DELETE FROM comments WHERE post_id = #{postId}
</delete>
<!--动态查询--> <!--动态查询-->
<resultMap id="PostResultMap" type="com.bao.dating.pojo.entity.Post"> <resultMap id="PostResultMap" type="com.bao.dating.pojo.entity.Post">
@@ -82,10 +76,10 @@
<update id="updateById"> <update id="updateById">
UPDATE post UPDATE post
<set> <set>
<if test="content != null"> <if test="content != null and content != '' ">
content = #{content}, content = #{content},
</if> </if>
<if test="tags != null"> <if test="tags != null and tags != '' ">
tags = #{tags, typeHandler=com.bao.dating.handler.ListToVarcharTypeHandler}, tags = #{tags, typeHandler=com.bao.dating.handler.ListToVarcharTypeHandler},
</if> </if>
<if test="mediaOssKeys != null"> <if test="mediaOssKeys != null">

View File

@@ -18,6 +18,7 @@
<!--根据用户id查询用户信息--> <!--根据用户id查询用户信息-->
<resultMap id="UserResultMap" type="com.bao.dating.pojo.entity.User"> <resultMap id="UserResultMap" type="com.bao.dating.pojo.entity.User">
<id property="userId" column="user_id"/> <id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="nickname" column="nickname"/> <result property="nickname" column="nickname"/>
<result property="avatarUrl" column="avatar_url"/> <result property="avatarUrl" column="avatar_url"/>
<result property="backgroundUrl" column="background_url"/> <result property="backgroundUrl" column="background_url"/>
@@ -31,6 +32,7 @@
<select id="selectByUserId" resultMap="UserResultMap"> <select id="selectByUserId" resultMap="UserResultMap">
SELECT SELECT
user_id, user_id,
user_name,
nickname, nickname,
avatar_url, avatar_url,
background_url, background_url,

View File

@@ -0,0 +1,304 @@
package com.bao.dating.common.aliyun;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* OSS图片上传测试类
* @author KilLze
*/
@SpringBootTest
public class OssUploadTest {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 测试上传本地图片文件
*/
@Test
public void testUploadLocalImage() {
try {
// 本地图片文件路径(请修改为实际存在的图片路径)
String localImagePath = "D:/image/image.jpg"; // 请修改为实际路径
File imageFile = new File(localImagePath);
if (!imageFile.exists()) {
System.out.println("图片文件不存在: " + localImagePath);
System.out.println("请修改localImagePath为实际存在的图片路径");
return;
}
// 读取文件字节
byte[] imageBytes = Files.readAllBytes(Paths.get(localImagePath));
// 生成OSS对象名称使用时间戳和UUID确保唯一性
String objectName = generateObjectName("test", "jpg");
// 上传到OSS
String fileUrl = aliOssUtil.upload(imageBytes, objectName);
System.out.println("========== 本地图片上传测试 ==========");
System.out.println("本地文件路径: " + localImagePath);
System.out.println("文件大小: " + imageBytes.length + " 字节");
System.out.println("OSS对象名称: " + objectName);
System.out.println("文件访问URL: " + fileUrl);
System.out.println("上传结果: 成功");
System.out.println("=====================================");
} catch (IOException e) {
System.out.println("上传失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 测试从网络URL下载图片并上传到OSS
*/
@Test
public void testUploadImageFromUrl() {
try {
// 网络图片URL请修改为实际的图片URL
String imageUrl = "https://example.com/image.jpg"; // 请修改为实际URL
System.out.println("========== 网络图片上传测试 ==========");
System.out.println("开始下载图片: " + imageUrl);
// 从URL下载图片
URL url = new URL(imageUrl);
try (InputStream inputStream = url.openStream()) {
byte[] imageBytes = readAllBytes(inputStream);
// 从URL中提取文件扩展名
String extension = getFileExtensionFromUrl(imageUrl);
if (extension.isEmpty()) {
extension = "jpg"; // 默认扩展名
}
// 生成OSS对象名称
String objectName = generateObjectName("download", extension);
// 上传到OSS
String fileUrl = aliOssUtil.upload(imageBytes, objectName);
System.out.println("图片下载成功");
System.out.println("文件大小: " + imageBytes.length + " 字节");
System.out.println("OSS对象名称: " + objectName);
System.out.println("文件访问URL: " + fileUrl);
System.out.println("上传结果: 成功");
}
System.out.println("=====================================");
} catch (Exception e) {
System.out.println("上传失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 测试上传不同格式的图片
*/
@Test
public void testUploadDifferentImageFormats() {
String[] imagePaths = {
"D:/test/image1.jpg", // 请修改为实际路径
"D:/test/image2.png", // 请修改为实际路径
"D:/test/image3.gif" // 请修改为实际路径
};
System.out.println("========== 多格式图片上传测试 ==========");
for (String imagePath : imagePaths) {
try {
File imageFile = new File(imagePath);
if (!imageFile.exists()) {
System.out.println("跳过不存在的文件: " + imagePath);
continue;
}
// 读取文件
byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
// 获取文件扩展名
String extension = getFileExtension(imagePath);
// 生成OSS对象名称
String objectName = generateObjectName("format-test", extension);
// 上传
String fileUrl = aliOssUtil.upload(imageBytes, objectName);
System.out.println("\n文件: " + imagePath);
System.out.println("格式: " + extension);
System.out.println("大小: " + imageBytes.length + " 字节");
System.out.println("URL: " + fileUrl);
System.out.println("状态: 成功");
} catch (Exception e) {
System.out.println("\n文件: " + imagePath);
System.out.println("状态: 失败 - " + e.getMessage());
}
}
System.out.println("\n=====================================");
}
/**
* 测试上传图片到指定目录
*/
@Test
public void testUploadImageToDirectory() {
try {
// 本地图片文件路径
String localImagePath = "D:/test/image.jpg"; // 请修改为实际路径
File imageFile = new File(localImagePath);
if (!imageFile.exists()) {
System.out.println("图片文件不存在: " + localImagePath);
return;
}
// 读取文件
byte[] imageBytes = Files.readAllBytes(Paths.get(localImagePath));
// 上传到指定目录例如images/2025/12/24/xxx.jpg
String directory = "images/" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String fileName = UUID.randomUUID().toString() + ".jpg";
String objectName = directory + "/" + fileName;
// 上传
String fileUrl = aliOssUtil.upload(imageBytes, objectName);
System.out.println("========== 目录上传测试 ==========");
System.out.println("目录: " + directory);
System.out.println("文件名: " + fileName);
System.out.println("完整路径: " + objectName);
System.out.println("文件访问URL: " + fileUrl);
System.out.println("上传结果: 成功");
System.out.println("=================================");
} catch (Exception e) {
System.out.println("上传失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 测试使用FileInputStream上传
*/
@Test
public void testUploadWithFileInputStream() {
try {
// 本地图片文件路径
String localImagePath = "D:/test/image.jpg"; // 请修改为实际路径
File imageFile = new File(localImagePath);
if (!imageFile.exists()) {
System.out.println("图片文件不存在: " + localImagePath);
return;
}
// 使用FileInputStream读取文件
try (FileInputStream fis = new FileInputStream(imageFile)) {
byte[] imageBytes = new byte[(int) imageFile.length()];
fis.read(imageBytes);
// 生成OSS对象名称
String objectName = generateObjectName("stream", "jpg");
// 上传
String fileUrl = aliOssUtil.upload(imageBytes, objectName);
System.out.println("========== FileInputStream上传测试 ==========");
System.out.println("文件路径: " + localImagePath);
System.out.println("文件大小: " + imageBytes.length + " 字节");
System.out.println("OSS对象名称: " + objectName);
System.out.println("文件访问URL: " + fileUrl);
System.out.println("上传结果: 成功");
System.out.println("===========================================");
}
} catch (Exception e) {
System.out.println("上传失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 生成OSS对象名称
* @param prefix 前缀
* @param extension 文件扩展名
* @return 对象名称
*/
private String generateObjectName(String prefix, String extension) {
// 格式: prefix/yyyyMMdd/HHmmss-uuid.extension
String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd/HHmmss"));
String uuid = UUID.randomUUID().toString().substring(0, 8);
return String.format("%s/%s-%s.%s", prefix, dateTime, uuid, extension);
}
/**
* 从文件路径获取扩展名
* @param filePath 文件路径
* @return 扩展名
*/
private String getFileExtension(String filePath) {
int lastDot = filePath.lastIndexOf('.');
if (lastDot > 0 && lastDot < filePath.length() - 1) {
return filePath.substring(lastDot + 1).toLowerCase();
}
return "jpg"; // 默认扩展名
}
/**
* 从URL获取文件扩展名
* @param url URL地址
* @return 扩展名
*/
private String getFileExtensionFromUrl(String url) {
try {
// 移除查询参数
String path = url.split("\\?")[0];
int lastDot = path.lastIndexOf('.');
int lastSlash = path.lastIndexOf('/');
if (lastDot > lastSlash && lastDot < path.length() - 1) {
return path.substring(lastDot + 1).toLowerCase();
}
} catch (Exception e) {
// 忽略异常
}
return "jpg"; // 默认扩展名
}
/**
* 从InputStream读取所有字节兼容方法
* @param inputStream 输入流
* @return 字节数组
* @throws IOException IO异常
*/
private byte[] readAllBytes(InputStream inputStream) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[8192]; // 8KB缓冲区
int nRead;
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
}
}

View File

@@ -0,0 +1,91 @@
package com.bao.dating.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 验证码服务测试类
* @author KilLze
*/
@SpringBootTest
public class VerificationCodeServiceTest {
@Autowired
private VerificationCodeService verificationCodeService;
/**
* 测试发送邮箱验证码
*/
@Test
public void testSendEmailCode() {
String email = "test@example.com"; // 请修改为实际邮箱地址
System.out.println("========== 发送邮箱验证码测试 ==========");
System.out.println("邮箱地址: " + email);
boolean result = verificationCodeService.sendEmailCode(email);
System.out.println("发送结果: " + (result ? "成功" : "失败"));
System.out.println("=====================================");
}
/**
* 测试验证邮箱验证码
*/
@Test
public void testVerifyEmailCode() {
String email = "test@example.com"; // 请修改为实际邮箱地址
String code = "123456"; // 请修改为实际收到的验证码
System.out.println("========== 验证邮箱验证码测试 ==========");
System.out.println("邮箱地址: " + email);
System.out.println("验证码: " + code);
boolean result = verificationCodeService.verifyEmailCode(email, code);
System.out.println("验证结果: " + (result ? "成功" : "失败"));
System.out.println("=====================================");
}
/**
* 测试完整流程:发送验证码 -> 验证验证码
*/
@Test
public void testCompleteFlow() {
String email = "test@example.com"; // 请修改为实际邮箱地址
System.out.println("========== 完整流程测试 ==========");
System.out.println("邮箱地址: " + email);
// 1. 发送验证码
System.out.println("\n1. 发送验证码...");
boolean sendResult = verificationCodeService.sendEmailCode(email);
System.out.println("发送结果: " + (sendResult ? "成功" : "失败"));
if (sendResult) {
// 2. 等待用户输入验证码这里模拟实际应该从控制台或API获取
System.out.println("\n2. 请查看邮箱获取验证码,然后手动测试验证功能");
System.out.println(" 使用 testVerifyEmailCode() 方法进行验证");
}
System.out.println("\n=====================================");
}
/**
* 测试生成验证码
*/
@Test
public void testGenerateCode() {
System.out.println("========== 生成验证码测试 ==========");
// 测试不同长度的验证码
for (int length = 4; length <= 8; length++) {
String code = verificationCodeService.generateCode(length);
System.out.println(length + "位验证码: " + code);
}
System.out.println("=====================================");
}
}

View File

@@ -75,3 +75,4 @@ public class EmailAndSmsTest {