refactor: 统一注释规范与代码风格,优化分层结构
This commit is contained in:
26
pom.xml
26
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.youlai</groupId>
|
<groupId>com.youlai</groupId>
|
||||||
<artifactId>youlai-boot</artifactId>
|
<artifactId>youlai-boot</artifactId>
|
||||||
<version>4.3.1</version>
|
<version>4.3.2</version>
|
||||||
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
|
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
<druid.version>1.2.24</druid.version>
|
<druid.version>1.2.24</druid.version>
|
||||||
<!-- Spring Boot 4.x 必须使用更新的版本 -->
|
<!-- Spring Boot 4.x 必须使用更新的版本 -->
|
||||||
<mybatis-plus.version>3.5.15</mybatis-plus.version>
|
<mybatis-plus.version>3.5.15</mybatis-plus.version>
|
||||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
|
||||||
|
|
||||||
<knife4j.version>4.5.0</knife4j.version>
|
<knife4j.version>4.5.0</knife4j.version>
|
||||||
|
<springdoc.version>2.8.9</springdoc.version>
|
||||||
|
|
||||||
<mapstruct.version>1.6.3</mapstruct.version>
|
<mapstruct.version>1.6.3</mapstruct.version>
|
||||||
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
|
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
<ip2region.version>2.7.0</ip2region.version>
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
|
|
||||||
<!-- 阿里云短信 -->
|
<!-- 阿里云短信 -->
|
||||||
<aliyun.java.sdk.core.version>4.7.6</aliyun.java.sdk.core.version>
|
<aliyun-sdk-core.version>4.7.6</aliyun-sdk-core.version>
|
||||||
<aliyun.java.sdk.dysmsapi.version>2.2.1</aliyun.java.sdk.dysmsapi.version>
|
<aliyun-sdk-dysmsapi.version>2.2.1</aliyun-sdk-dysmsapi.version>
|
||||||
|
|
||||||
<caffeine.version>2.9.3</caffeine.version>
|
<caffeine.version>2.9.3</caffeine.version>
|
||||||
|
|
||||||
@@ -64,7 +64,6 @@
|
|||||||
|
|
||||||
<weixin-java-miniapp.version>4.8.1.B</weixin-java-miniapp.version>
|
<weixin-java-miniapp.version>4.8.1.B</weixin-java-miniapp.version>
|
||||||
|
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -81,7 +80,7 @@
|
|||||||
<version>${hutool.version}</version>
|
<version>${hutool.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- TransmittableThreadLocal: 支持异步场景的租户上下文传递 -->
|
<!-- TransmittableThreadLocal -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>transmittable-thread-local</artifactId>
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
@@ -178,7 +177,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
<version>2.8.9</version>
|
<version>${springdoc.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- MapStruct 对象映射 -->
|
<!-- MapStruct 对象映射 -->
|
||||||
@@ -201,7 +200,7 @@
|
|||||||
<version>${xxl-job.version}</version>
|
<version>${xxl-job.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Excel 工具(EasyExcel-PLus ) -->
|
<!-- Excel 工具(EasyExcel-Plus) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.idev.excel</groupId>
|
<groupId>cn.idev.excel</groupId>
|
||||||
<artifactId>fastexcel</artifactId>
|
<artifactId>fastexcel</artifactId>
|
||||||
@@ -253,13 +252,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||||
<version>${aliyun.java.sdk.core.version}</version>
|
<version>${aliyun-sdk-core.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
|
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
|
||||||
<version>${aliyun.java.sdk.dysmsapi.version}</version>
|
<version>${aliyun-sdk-dysmsapi.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 本地缓存 -->
|
<!-- 本地缓存 -->
|
||||||
@@ -276,13 +275,6 @@
|
|||||||
<version>${weixin-java-miniapp.version}</version>
|
<version>${weixin-java-miniapp.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 动态多数据源 -->
|
|
||||||
<!--<dependency>
|
|
||||||
<groupId>com.baomidou</groupId>
|
|
||||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
|
||||||
<version>${dynamic-datasource.version}</version>
|
|
||||||
</dependency>-->
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
* 应用启动类
|
* 应用启动类
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 0.0.1
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证控制层
|
* 认证控制层
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.lang.annotation.*;
|
|||||||
/**
|
/**
|
||||||
* 数据权限注解
|
* 数据权限注解
|
||||||
*
|
*
|
||||||
* @author zc
|
* @author Ray.Hao
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@Documented
|
@Documented
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package com.youlai.boot.common.annotation;
|
|||||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志注解
|
* 日志注解
|
||||||
*
|
*
|
||||||
* @author Ray
|
* @author Ray.Hao
|
||||||
* @since 2024/6/25
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import java.util.concurrent.TimeUnit;
|
|||||||
/**
|
/**
|
||||||
* 接口限流
|
* 接口限流
|
||||||
* <p>标注在 Controller 方法上,基于 Redis 计数窗口实现</p>
|
* <p>标注在 Controller 方法上,基于 Redis 计数窗口实现</p>
|
||||||
|
*
|
||||||
|
* @author Ray.Hao
|
||||||
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
package com.youlai.boot.common.aspect;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.hutool.crypto.digest.DigestUtil;
|
|
||||||
import com.youlai.boot.common.constant.RedisConstants;
|
|
||||||
import com.youlai.boot.common.constant.SecurityConstants;
|
|
||||||
import com.youlai.boot.common.result.ResultCode;
|
|
||||||
import com.youlai.boot.common.exception.BusinessException;
|
|
||||||
import com.youlai.boot.common.annotation.RepeatSubmit;
|
|
||||||
import com.youlai.boot.common.util.IPUtils;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
import org.redisson.api.RLock;
|
|
||||||
import org.redisson.api.RedissonClient;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 防重复提交切面
|
|
||||||
*
|
|
||||||
* @author Ray.Hao
|
|
||||||
* @since 2.3.0
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
public class RepeatSubmitAspect {
|
|
||||||
|
|
||||||
private final RedissonClient redissonClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 防重复提交切点
|
|
||||||
*/
|
|
||||||
@Pointcut("@annotation(repeatSubmit)")
|
|
||||||
public void repeatSubmitPointCut(RepeatSubmit repeatSubmit) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 环绕通知:处理防重复提交逻辑
|
|
||||||
*/
|
|
||||||
@Around(value = "repeatSubmitPointCut(repeatSubmit)", argNames = "pjp,repeatSubmit")
|
|
||||||
public Object handleRepeatSubmit(ProceedingJoinPoint pjp, RepeatSubmit repeatSubmit) throws Throwable {
|
|
||||||
String lockKey = buildLockKey();
|
|
||||||
|
|
||||||
int expire = repeatSubmit.expire();
|
|
||||||
RLock lock = redissonClient.getLock(lockKey);
|
|
||||||
|
|
||||||
boolean locked = lock.tryLock(0, expire, TimeUnit.SECONDS);
|
|
||||||
if (!locked) {
|
|
||||||
throw new BusinessException(ResultCode.DUPLICATE_SUBMISSION);
|
|
||||||
}
|
|
||||||
return pjp.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成防重复提交锁的 key
|
|
||||||
* @return 锁的 key
|
|
||||||
*/
|
|
||||||
private String buildLockKey() {
|
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
|
||||||
// 用户唯一标识
|
|
||||||
String userIdentifier = getUserIdentifier(request);
|
|
||||||
// 请求唯一标识 = 请求方法 + 请求路径 + 请求参数(严谨的做法)
|
|
||||||
String requestIdentifier = StrUtil.join(":", request.getMethod(), request.getRequestURI());
|
|
||||||
return StrUtil.format(RedisConstants.Lock.RESUBMIT, userIdentifier, requestIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户唯一标识
|
|
||||||
* 1. 从请求头中获取 Token,使用 SHA-256 加密 Token 作为用户唯一标识
|
|
||||||
* 2. 如果 Token 为空,使用 IP 作为用户唯一标识
|
|
||||||
*
|
|
||||||
* @param request 请求对象
|
|
||||||
* @return 用户唯一标识
|
|
||||||
*/
|
|
||||||
private String getUserIdentifier(HttpServletRequest request) {
|
|
||||||
// 用户身份唯一标识
|
|
||||||
String userIdentifier;
|
|
||||||
// 从请求头中获取 Token
|
|
||||||
String tokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
|
||||||
if (StrUtil.isNotBlank(tokenHeader) && tokenHeader.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX)) {
|
|
||||||
String rawToken = tokenHeader.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length()); // 去掉 Bearer 后的 Token
|
|
||||||
userIdentifier = DigestUtil.sha256Hex(rawToken); // 使用 SHA-256 加密 Token 作为用户唯一标识
|
|
||||||
} else {
|
|
||||||
userIdentifier = IPUtils.getIpAddr(request); // 使用 IP 作为用户唯一标识
|
|
||||||
}
|
|
||||||
return userIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.youlai.boot.common.base;
|
package com.youlai.boot.common.base;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.*;
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -14,8 +17,8 @@ import java.time.LocalDateTime;
|
|||||||
*
|
*
|
||||||
* <p>实体类的基类,包含了实体类的公共属性,如创建时间、更新时间、逻辑删除标识等</p>
|
* <p>实体类的基类,包含了实体类的公共属性,如创建时间、更新时间、逻辑删除标识等</p>
|
||||||
*
|
*
|
||||||
* @author Ray
|
* @author Ray.Hao
|
||||||
* @since 2024/6/23
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class BaseEntity implements Serializable {
|
public class BaseEntity implements Serializable {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.youlai.boot.common.base;
|
package com.youlai.boot.common.base;
|
||||||
|
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@@ -9,8 +8,8 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* 枚举通用接口
|
* 枚举通用接口
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author Ray.Hao
|
||||||
* @since 2022/3/27 12:06
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
public interface IBaseEnum<T> {
|
public interface IBaseEnum<T> {
|
||||||
|
|
||||||
@@ -37,12 +36,12 @@ public interface IBaseEnum<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据文本标签获取值
|
* 根据值获取标签
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value 枚举值
|
||||||
* @param clazz
|
* @param clazz 枚举类型
|
||||||
* @param <E>
|
* @param <E> 枚举
|
||||||
* @return
|
* @return 标签文本
|
||||||
*/
|
*/
|
||||||
static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
|
static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
|
||||||
Objects.requireNonNull(value);
|
Objects.requireNonNull(value);
|
||||||
@@ -59,14 +58,13 @@ public interface IBaseEnum<T> {
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据文本标签获取值
|
* 根据标签获取值
|
||||||
*
|
*
|
||||||
* @param label
|
* @param label 标签文本
|
||||||
* @param clazz
|
* @param clazz 枚举类型
|
||||||
* @param <E>
|
* @param <E> 枚举
|
||||||
* @return
|
* @return 枚举值
|
||||||
*/
|
*/
|
||||||
static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
|
static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
|
||||||
Objects.requireNonNull(label);
|
Objects.requireNonNull(label);
|
||||||
@@ -84,5 +82,4 @@ public interface IBaseEnum<T> {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package com.youlai.boot.common.constant;
|
|||||||
* Redis 常量
|
* Redis 常量
|
||||||
*
|
*
|
||||||
* @author Theo
|
* @author Theo
|
||||||
* @since 2024-7-29 11:46:08
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
public interface RedisConstants {
|
public interface RedisConstants {
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ public interface RedisConstants {
|
|||||||
String REFRESH_TOKEN_USER = "auth:token:refresh:{}";
|
String REFRESH_TOKEN_USER = "auth:token:refresh:{}";
|
||||||
// 用户与访问令牌的映射(userId -> accessToken)
|
// 用户与访问令牌的映射(userId -> accessToken)
|
||||||
String USER_ACCESS_TOKEN = "auth:user:access:{}";
|
String USER_ACCESS_TOKEN = "auth:user:access:{}";
|
||||||
// 用户与刷新令牌的映射(userId -> refreshToken
|
// 用户与刷新令牌的映射(userId -> refreshToken))
|
||||||
String USER_REFRESH_TOKEN = "auth:user:refresh:{}";
|
String USER_REFRESH_TOKEN = "auth:user:refresh:{}";
|
||||||
// 已撤销 Token 的 JTI(单端退出/会话注销):如果 jti 在撤销列表中,则 Token 立即无效
|
// 已撤销 Token 的 JTI(单端退出/会话注销):如果 jti 在撤销列表中,则 Token 立即无效
|
||||||
String BLACKLIST_TOKEN = "auth:token:blacklist:{}";
|
String BLACKLIST_TOKEN = "auth:token:blacklist:{}";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import lombok.Getter;
|
|||||||
/**
|
/**
|
||||||
* 环境枚举
|
* 环境枚举
|
||||||
*
|
*
|
||||||
* @author Ray
|
* @author Ray.Hao
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import lombok.Getter;
|
|||||||
/**
|
/**
|
||||||
* 状态枚举
|
* 状态枚举
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author Ray.Hao
|
||||||
* @since 2022/10/14
|
* @since 2022/10/14
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@@ -17,7 +17,6 @@ public enum StatusEnum implements IBaseEnum<Integer> {
|
|||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
|
|
||||||
|
|
||||||
private final String label;
|
private final String label;
|
||||||
|
|
||||||
StatusEnum(Integer value, String label) {
|
StatusEnum(Integer value, String label) {
|
||||||
|
|||||||
@@ -20,13 +20,11 @@ public class BusinessException extends RuntimeException {
|
|||||||
this.resultCode = errorCode;
|
this.resultCode = errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public BusinessException(IResultCode errorCode, String message) {
|
public BusinessException(IResultCode errorCode, String message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.resultCode = errorCode;
|
this.resultCode = errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public BusinessException(String message, Throwable cause) {
|
public BusinessException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import java.io.Serializable;
|
|||||||
* 统一响应结构体
|
* 统一响应结构体
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2022/1/30
|
* @since 4.3.1
|
||||||
**/
|
**/
|
||||||
@Data
|
@Data
|
||||||
public class Result<T> implements Serializable {
|
public class Result<T> implements Serializable {
|
||||||
@@ -64,10 +64,6 @@ public class Result<T> implements Serializable {
|
|||||||
return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), data);
|
return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> Result<T> result(IResultCode resultCode, T data) {
|
|
||||||
return result(resultCode.getCode(), resultCode.getMsg(), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Result<T> result(String code, String msg, T data) {
|
private static <T> Result<T> result(String code, String msg, T data) {
|
||||||
Result<T> result = new Result<>();
|
Result<T> result = new Result<>();
|
||||||
result.setCode(code);
|
result.setCode(code);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import java.io.Serializable;
|
|||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2020/6/23
|
* @since 4.3.1
|
||||||
**/
|
**/
|
||||||
public enum ResultCode implements IResultCode, Serializable {
|
public enum ResultCode implements IResultCode, Serializable {
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mybatis-plus 配置类
|
* MyBatis-Plus 配置类
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2022/7/2
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
@@ -38,7 +38,7 @@ public class MybatisConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动填充数据库创建人、创建时间、更新人、更新时间
|
* 自动填充创建时间和更新时间
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public GlobalConfig globalConfig() {
|
public GlobalConfig globalConfig() {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package com.youlai.boot.framework.mybatis.handler;
|
package com.youlai.boot.framework.mybatis.handler;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mybatis-plus 字段自动填充
|
* MyBatis-Plus 字段自动填充
|
||||||
|
*
|
||||||
|
* @author Ray.Hao
|
||||||
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AutoFillMetaObjectHandler implements MetaObjectHandler {
|
public class AutoFillMetaObjectHandler implements MetaObjectHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import com.youlai.boot.framework.security.model.SysUserDetails;
|
|||||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sf.jsqlparser.expression.*;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||||
@@ -30,8 +31,8 @@ import java.util.List;
|
|||||||
* <p>
|
* <p>
|
||||||
* 使用 JSQLParser 构建 SQL 条件,避免字符串拼接,提高代码安全性和可读性。
|
* 使用 JSQLParser 构建 SQL 条件,避免字符串拼接,提高代码安全性和可读性。
|
||||||
*
|
*
|
||||||
* @author zc
|
* @author Ray.Hao
|
||||||
* @since 2021-12-10 13:28
|
* @since 2021/12/10
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MyDataPermissionHandler implements DataPermissionHandler {
|
public class MyDataPermissionHandler implements DataPermissionHandler {
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import com.youlai.boot.framework.security.token.TokenManager;
|
|||||||
import com.youlai.boot.framework.security.service.SysUserDetailsService;
|
import com.youlai.boot.framework.security.service.SysUserDetailsService;
|
||||||
import com.youlai.boot.system.service.UserService;
|
import com.youlai.boot.system.service.UserService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.ProviderManager;
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
@@ -35,7 +34,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
* Spring Security 配置类
|
* Spring Security 配置类
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2023/2/17
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.youlai.boot.framework.web.advice;
|
package com.youlai.boot.framework.web.advice;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import tools.jackson.core.JacksonException;
|
|
||||||
import com.youlai.boot.common.exception.BusinessException;
|
import com.youlai.boot.common.exception.BusinessException;
|
||||||
import com.youlai.boot.common.result.Result;
|
import com.youlai.boot.common.result.Result;
|
||||||
import com.youlai.boot.common.result.ResultCode;
|
import com.youlai.boot.common.result.ResultCode;
|
||||||
@@ -25,6 +24,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
|
|||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||||
|
import tools.jackson.core.JacksonException;
|
||||||
|
|
||||||
import java.sql.SQLIntegrityConstraintViolationException;
|
import java.sql.SQLIntegrityConstraintViolationException;
|
||||||
import java.sql.SQLSyntaxErrorException;
|
import java.sql.SQLSyntaxErrorException;
|
||||||
@@ -34,18 +34,14 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局系统异常处理器
|
* 全局系统异常处理器
|
||||||
* <p>
|
*
|
||||||
* 调整异常处理的HTTP状态码,丰富异常处理类型
|
* @author Ray.Hao
|
||||||
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理绑定异常
|
|
||||||
* <p>
|
|
||||||
* 当请求参数绑定到对象时发生错误,会抛出 BindException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(BindException.class)
|
@ExceptionHandler(BindException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(BindException e) {
|
public <T> Result<T> processException(BindException e) {
|
||||||
@@ -54,12 +50,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.USER_REQUEST_PARAMETER_ERROR, msg);
|
return Result.failed(ResultCode.USER_REQUEST_PARAMETER_ERROR, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 @RequestParam 参数校验异常
|
|
||||||
* <p>
|
|
||||||
* 当请求参数在校验过程中发生违反约束条件的异常时(如 @RequestParam 验证不通过),
|
|
||||||
* 会捕获到 ConstraintViolationException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(ConstraintViolationException.class)
|
@ExceptionHandler(ConstraintViolationException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(ConstraintViolationException e) {
|
public <T> Result<T> processException(ConstraintViolationException e) {
|
||||||
@@ -68,12 +58,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.INVALID_USER_INPUT, msg);
|
return Result.failed(ResultCode.INVALID_USER_INPUT, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理方法参数校验异常
|
|
||||||
* <p>
|
|
||||||
* 当使用 @Valid 或 @Validated 注解对方法参数进行验证时,如果验证失败,
|
|
||||||
* 会抛出 MethodArgumentNotValidException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(MethodArgumentNotValidException e) {
|
public <T> Result<T> processException(MethodArgumentNotValidException e) {
|
||||||
@@ -82,11 +66,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.INVALID_USER_INPUT, msg);
|
return Result.failed(ResultCode.INVALID_USER_INPUT, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理接口不存在的异常
|
|
||||||
* <p>
|
|
||||||
* 当客户端请求一个不存在的路径时,会抛出 NoHandlerFoundException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(NoHandlerFoundException.class)
|
@ExceptionHandler(NoHandlerFoundException.class)
|
||||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
public <T> Result<T> processException(NoHandlerFoundException e) {
|
public <T> Result<T> processException(NoHandlerFoundException e) {
|
||||||
@@ -94,11 +73,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.INTERFACE_NOT_EXIST);
|
return Result.failed(ResultCode.INTERFACE_NOT_EXIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理缺少请求参数的异常
|
|
||||||
* <p>
|
|
||||||
* 当请求缺少必需的参数时,会抛出 MissingServletRequestParameterException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(MissingServletRequestParameterException e) {
|
public <T> Result<T> processException(MissingServletRequestParameterException e) {
|
||||||
@@ -106,11 +80,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.REQUEST_REQUIRED_PARAMETER_IS_EMPTY);
|
return Result.failed(ResultCode.REQUEST_REQUIRED_PARAMETER_IS_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理方法参数类型不匹配的异常
|
|
||||||
* <p>
|
|
||||||
* 当请求参数类型不匹配时,会抛出 MethodArgumentTypeMismatchException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(MethodArgumentTypeMismatchException e) {
|
public <T> Result<T> processException(MethodArgumentTypeMismatchException e) {
|
||||||
@@ -118,11 +87,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.PARAMETER_FORMAT_MISMATCH, "类型错误");
|
return Result.failed(ResultCode.PARAMETER_FORMAT_MISMATCH, "类型错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 Servlet 异常
|
|
||||||
* <p>
|
|
||||||
* 当 Servlet 处理请求时发生异常时,会抛出 ServletException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(ServletException.class)
|
@ExceptionHandler(ServletException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(ServletException e) {
|
public <T> Result<T> processException(ServletException e) {
|
||||||
@@ -130,11 +94,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(e.getMessage());
|
return Result.failed(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理非法参数异常
|
|
||||||
* <p>
|
|
||||||
* 当方法接收到非法参数时,会抛出 IllegalArgumentException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(IllegalArgumentException.class)
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> handleIllegalArgumentException(IllegalArgumentException e) {
|
public <T> Result<T> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||||
@@ -142,11 +101,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(e.getMessage());
|
return Result.failed(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 JSON 处理异常
|
|
||||||
* <p>
|
|
||||||
* 当处理 JSON 数据时发生错误,会抛出 JacksonException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(JacksonException.class)
|
@ExceptionHandler(JacksonException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> handleJacksonException(JacksonException e) {
|
public <T> Result<T> handleJacksonException(JacksonException e) {
|
||||||
@@ -154,11 +108,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(e.getMessage());
|
return Result.failed(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理请求体不可读的异常
|
|
||||||
* <p>
|
|
||||||
* 当请求体不可读时,会抛出 HttpMessageNotReadableException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(HttpMessageNotReadableException e) {
|
public <T> Result<T> processException(HttpMessageNotReadableException e) {
|
||||||
@@ -171,11 +120,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(errorMessage);
|
return Result.failed(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理类型不匹配异常
|
|
||||||
* <p>
|
|
||||||
* 当方法参数类型不匹配时,会抛出 TypeMismatchException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(TypeMismatchException.class)
|
@ExceptionHandler(TypeMismatchException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> processException(TypeMismatchException e) {
|
public <T> Result<T> processException(TypeMismatchException e) {
|
||||||
@@ -183,11 +127,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(e.getMessage());
|
return Result.failed(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SQL 语法错误异常
|
|
||||||
* <p>
|
|
||||||
* 当 SQL 语法错误时,会抛出 BadSqlGrammarException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(BadSqlGrammarException.class)
|
@ExceptionHandler(BadSqlGrammarException.class)
|
||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
public <T> Result<T> handleBadSqlGrammarException(BadSqlGrammarException e) {
|
public <T> Result<T> handleBadSqlGrammarException(BadSqlGrammarException e) {
|
||||||
@@ -198,11 +137,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.DATABASE_EXECUTION_ERROR);
|
return Result.failed(ResultCode.DATABASE_EXECUTION_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SQL 语法错误异常
|
|
||||||
* <p>
|
|
||||||
* 当 SQL 语法错误时,会抛出 SQLSyntaxErrorException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(SQLSyntaxErrorException.class)
|
@ExceptionHandler(SQLSyntaxErrorException.class)
|
||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
public <T> Result<T> processSQLSyntaxErrorException(SQLSyntaxErrorException e) {
|
public <T> Result<T> processSQLSyntaxErrorException(SQLSyntaxErrorException e) {
|
||||||
@@ -210,12 +144,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.DATABASE_EXECUTION_SYNTAX_ERROR);
|
return Result.failed(ResultCode.DATABASE_EXECUTION_SYNTAX_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SQL 违反了完整性约束
|
|
||||||
* <p>
|
|
||||||
* 当 SQL 违反了完整性约束时,会抛出 SQLIntegrityConstraintViolationException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
|
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
|
||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
public <T> Result<T> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
|
public <T> Result<T> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
|
||||||
@@ -223,22 +151,12 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(ResultCode.INTEGRITY_CONSTRAINT_VIOLATION);
|
return Result.failed(ResultCode.INTEGRITY_CONSTRAINT_VIOLATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 Token 无效异常
|
|
||||||
* <p>
|
|
||||||
* 当 access_token 或 refresh_token 过期/无效时,返回 401。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(TokenInvalidException.class)
|
@ExceptionHandler(TokenInvalidException.class)
|
||||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||||
public <T> Result<T> handleTokenInvalidException(TokenInvalidException e) {
|
public <T> Result<T> handleTokenInvalidException(TokenInvalidException e) {
|
||||||
return Result.failed(e.getResultCode());
|
return Result.failed(e.getResultCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理业务异常
|
|
||||||
* <p>
|
|
||||||
* 当业务逻辑发生错误时,会抛出 BusinessException 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(BusinessException.class)
|
@ExceptionHandler(BusinessException.class)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public <T> Result<T> handleBizException(BusinessException e) {
|
public <T> Result<T> handleBizException(BusinessException e) {
|
||||||
@@ -249,15 +167,9 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(e.getMessage());
|
return Result.failed(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有未捕获的异常
|
|
||||||
* <p>
|
|
||||||
* 当发生未捕获的异常时,会抛出 Exception 异常。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
public <T> Result<T> handleException(Exception e) throws Exception {
|
public <T> Result<T> handleException(Exception e) throws Exception {
|
||||||
// 将 Spring Security 异常继续抛出,以便交给自定义处理器处理
|
|
||||||
if (e instanceof AccessDeniedException
|
if (e instanceof AccessDeniedException
|
||||||
|| e instanceof AuthenticationException) {
|
|| e instanceof AuthenticationException) {
|
||||||
throw e;
|
throw e;
|
||||||
@@ -266,12 +178,6 @@ public class GlobalExceptionHandler {
|
|||||||
return Result.failed(e.getLocalizedMessage());
|
return Result.failed(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 传参类型错误时,用于消息转换
|
|
||||||
*
|
|
||||||
* @param throwable 异常
|
|
||||||
* @return 错误信息
|
|
||||||
*/
|
|
||||||
private String convertMessage(Throwable throwable) {
|
private String convertMessage(Throwable throwable) {
|
||||||
String error = throwable.toString();
|
String error = throwable.toString();
|
||||||
String regulation = "\\[\"(.*?)\"]+";
|
String regulation = "\\[\"(.*?)\"]+";
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ import java.util.concurrent.TimeUnit;
|
|||||||
/**
|
/**
|
||||||
* 接口限流切面
|
* 接口限流切面
|
||||||
* <p>对 @RateLimit 标注的方法做 Redis 计数,超阈值则拒绝</p>
|
* <p>对 @RateLimit 标注的方法做 Redis 计数,超阈值则拒绝</p>
|
||||||
|
*
|
||||||
|
* @author Ray.Hao
|
||||||
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.youlai.boot.framework.web.aspect;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
|
import com.youlai.boot.common.constant.RedisConstants;
|
||||||
|
import com.youlai.boot.common.constant.SecurityConstants;
|
||||||
|
import com.youlai.boot.common.result.ResultCode;
|
||||||
|
import com.youlai.boot.common.exception.BusinessException;
|
||||||
|
import com.youlai.boot.common.annotation.RepeatSubmit;
|
||||||
|
import com.youlai.boot.common.util.IPUtils;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.redisson.api.RLock;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防重复提交切面
|
||||||
|
*
|
||||||
|
* @author Ray.Hao
|
||||||
|
* @since 4.3.1
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RepeatSubmitAspect {
|
||||||
|
|
||||||
|
private final RedissonClient redissonClient;
|
||||||
|
|
||||||
|
@Pointcut("@annotation(repeatSubmit)")
|
||||||
|
public void repeatSubmitPointCut(RepeatSubmit repeatSubmit) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Around(value = "repeatSubmitPointCut(repeatSubmit)", argNames = "pjp,repeatSubmit")
|
||||||
|
public Object handleRepeatSubmit(ProceedingJoinPoint pjp, RepeatSubmit repeatSubmit) throws Throwable {
|
||||||
|
String lockKey = buildLockKey();
|
||||||
|
|
||||||
|
int expire = repeatSubmit.expire();
|
||||||
|
RLock lock = redissonClient.getLock(lockKey);
|
||||||
|
|
||||||
|
boolean locked = lock.tryLock(0, expire, TimeUnit.SECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
throw new BusinessException(ResultCode.DUPLICATE_SUBMISSION);
|
||||||
|
}
|
||||||
|
return pjp.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成防重复提交锁的 key
|
||||||
|
*/
|
||||||
|
private String buildLockKey() {
|
||||||
|
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||||
|
String userIdentifier = getUserIdentifier(request);
|
||||||
|
String requestIdentifier = StrUtil.join(":", request.getMethod(), request.getRequestURI());
|
||||||
|
return StrUtil.format(RedisConstants.Lock.RESUBMIT, userIdentifier, requestIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户唯一标识
|
||||||
|
* 1. 从请求头中获取 Token,使用 SHA-256 加密 Token 作为用户唯一标识
|
||||||
|
* 2. 如果 Token 为空,使用 IP 作为用户唯一标识
|
||||||
|
*/
|
||||||
|
private String getUserIdentifier(HttpServletRequest request) {
|
||||||
|
String userIdentifier;
|
||||||
|
String tokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||||
|
if (StrUtil.isNotBlank(tokenHeader) && tokenHeader.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX)) {
|
||||||
|
String rawToken = tokenHeader.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length());
|
||||||
|
userIdentifier = DigestUtil.sha256Hex(rawToken);
|
||||||
|
} else {
|
||||||
|
userIdentifier = IPUtils.getIpAddr(request);
|
||||||
|
}
|
||||||
|
return userIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作日志线程池
|
* 操作日志线程池
|
||||||
* <p>core=1 确保日志按序写入,max=2 峰值扩容,queue=1000 缓冲突发流量,
|
|
||||||
* CallerRunsPolicy 保证满载时由调用线程兜底执行不丢数据。</p>
|
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 0.0.1
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class OperationLogExecutorConfig {
|
public class OperationLogExecutorConfig {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package com.youlai.boot.system.controller;
|
|||||||
|
|
||||||
import cn.idev.excel.EasyExcel;
|
import cn.idev.excel.EasyExcel;
|
||||||
import cn.idev.excel.ExcelWriter;
|
import cn.idev.excel.ExcelWriter;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
||||||
import com.youlai.boot.common.annotation.Log;
|
import com.youlai.boot.common.annotation.Log;
|
||||||
import com.youlai.boot.common.annotation.RepeatSubmit;
|
import com.youlai.boot.common.annotation.RepeatSubmit;
|
||||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||||
|
import com.youlai.boot.common.enums.StatusEnum;
|
||||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||||
import com.youlai.boot.common.model.Option;
|
import com.youlai.boot.common.model.Option;
|
||||||
import com.youlai.boot.common.result.ExcelResult;
|
import com.youlai.boot.common.result.ExcelResult;
|
||||||
@@ -14,9 +14,14 @@ import com.youlai.boot.common.result.PageResult;
|
|||||||
import com.youlai.boot.common.result.Result;
|
import com.youlai.boot.common.result.Result;
|
||||||
import com.youlai.boot.common.util.ExcelUtils;
|
import com.youlai.boot.common.util.ExcelUtils;
|
||||||
import com.youlai.boot.system.listener.UserImportListener;
|
import com.youlai.boot.system.listener.UserImportListener;
|
||||||
import com.youlai.boot.system.model.form.*;
|
import com.youlai.boot.system.model.form.EmailUpdateForm;
|
||||||
|
import com.youlai.boot.system.model.form.MobileUpdateForm;
|
||||||
|
import com.youlai.boot.system.model.form.PasswordUpdateForm;
|
||||||
|
import com.youlai.boot.system.model.form.PasswordVerifyForm;
|
||||||
|
import com.youlai.boot.system.model.form.UserForm;
|
||||||
|
import com.youlai.boot.system.model.form.UserImportForm;
|
||||||
|
import com.youlai.boot.system.model.form.UserProfileForm;
|
||||||
import com.youlai.boot.system.model.vo.UserExportVO;
|
import com.youlai.boot.system.model.vo.UserExportVO;
|
||||||
import com.youlai.boot.system.model.entity.SysUser;
|
|
||||||
import com.youlai.boot.system.model.query.UserQuery;
|
import com.youlai.boot.system.model.query.UserQuery;
|
||||||
import com.youlai.boot.system.model.vo.CurrentUserVO;
|
import com.youlai.boot.system.model.vo.CurrentUserVO;
|
||||||
import com.youlai.boot.system.model.vo.UserPageVO;
|
import com.youlai.boot.system.model.vo.UserPageVO;
|
||||||
@@ -32,7 +37,16 @@ import jakarta.validation.Valid;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -46,7 +60,7 @@ import java.util.List;
|
|||||||
* 用户控制层
|
* 用户控制层
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2022/10/16
|
* @since 4.3.1
|
||||||
*/
|
*/
|
||||||
@Tag(name = "02.用户接口")
|
@Tag(name = "02.用户接口")
|
||||||
@RestController
|
@RestController
|
||||||
@@ -120,12 +134,9 @@ public class UserController {
|
|||||||
@Parameter(description = "用户ID") @PathVariable Long userId,
|
@Parameter(description = "用户ID") @PathVariable Long userId,
|
||||||
@Parameter(description = "用户状态(1:启用;0:禁用)") @RequestParam Integer status
|
@Parameter(description = "用户状态(1:启用;0:禁用)") @RequestParam Integer status
|
||||||
) {
|
) {
|
||||||
boolean result = userService.update(new LambdaUpdateWrapper<SysUser>()
|
boolean result = userService.updateUserStatus(userId, status);
|
||||||
.eq(SysUser::getId, userId)
|
|
||||||
.set(SysUser::getStatus, status)
|
|
||||||
);
|
|
||||||
// 用户禁用时立即失效其会话
|
// 用户禁用时立即失效其会话
|
||||||
if (result && status == 0) {
|
if (result && StatusEnum.DISABLE.getValue().equals(status)) {
|
||||||
tokenManager.invalidateUserSessions(userId);
|
tokenManager.invalidateUserSessions(userId);
|
||||||
}
|
}
|
||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ public class Menu extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String component;
|
private String component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外链地址
|
||||||
|
*/
|
||||||
|
private String externalUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限标识
|
* 权限标识
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ public class MenuForm {
|
|||||||
@Schema(description = "组件路径(vue页面完整路径,省略.vue后缀)")
|
@Schema(description = "组件路径(vue页面完整路径,省略.vue后缀)")
|
||||||
private String component;
|
private String component;
|
||||||
|
|
||||||
|
@Schema(description = "外链地址")
|
||||||
|
private String externalUrl;
|
||||||
|
|
||||||
@Schema(description = "权限标识")
|
@Schema(description = "权限标识")
|
||||||
private String perm;
|
private String perm;
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ public class MenuVO {
|
|||||||
@Schema(description = "组件路径")
|
@Schema(description = "组件路径")
|
||||||
private String component;
|
private String component;
|
||||||
|
|
||||||
|
@Schema(description = "外链地址")
|
||||||
|
private String externalUrl;
|
||||||
|
|
||||||
@Schema(description = "菜单排序(数字越小排名越靠前)")
|
@Schema(description = "菜单排序(数字越小排名越靠前)")
|
||||||
private Integer sort;
|
private Integer sort;
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,15 @@ public interface UserService extends IService<SysUser> {
|
|||||||
*/
|
*/
|
||||||
boolean updateUser(Long userId, UserForm userForm);
|
boolean updateUser(Long userId, UserForm userForm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户状态
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param status 状态值
|
||||||
|
* @return {@link Boolean} 是否修改成功
|
||||||
|
*/
|
||||||
|
boolean updateUserStatus(Long userId, Integer status);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除用户
|
* 删除用户
|
||||||
|
|||||||
@@ -664,6 +664,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, SysUser> implements
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateUserStatus(Long userId, Integer status) {
|
||||||
|
return this.update(new LambdaUpdateWrapper<SysUser>()
|
||||||
|
.eq(SysUser::getId, userId)
|
||||||
|
.set(SysUser::getStatus, status)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户选项列表
|
* 获取用户选项列表
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -162,14 +162,16 @@ springdoc:
|
|||||||
packages-to-scan: # 扫描的 Controller 包,限制只生成指定包的接口文档
|
packages-to-scan: # 扫描的 Controller 包,限制只生成指定包的接口文档
|
||||||
- com.youlai.boot.auth.controller
|
- com.youlai.boot.auth.controller
|
||||||
- com.youlai.boot.system.controller
|
- com.youlai.boot.system.controller
|
||||||
- com.youlai.boot.module.file.controller
|
- com.youlai.boot.file.controller
|
||||||
- com.youlai.boot.module.codegen.controller
|
- com.youlai.boot.codegen.controller
|
||||||
default-flat-param-object: true # 将对象参数扁平化显示在文档中
|
- com.youlai.boot.message.controller
|
||||||
|
default-flat-param-object: true
|
||||||
|
|
||||||
# knife4j 配置
|
# knife4j 配置
|
||||||
knife4j:
|
knife4j:
|
||||||
enable: true # 是否启用 Knife4j 增强功能
|
enable: true
|
||||||
production: false # 是否启用生产环境保护(true=生产环境隐藏文档,false=开发环境可访问)
|
# 生产环境设置为 true 关闭文档
|
||||||
|
production: false
|
||||||
setting:
|
setting:
|
||||||
language: zh_cn
|
language: zh_cn
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user