refactor: 统一注释规范与代码风格,优化分层结构

This commit is contained in:
Ray.Hao
2026-06-28 19:04:21 +08:00
parent 8fd648c2e0
commit 49e3d7a71b
30 changed files with 215 additions and 286 deletions

26
pom.xml
View File

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

View File

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

View File

@@ -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;
/** /**
* 认证控制层 * 认证控制层

View File

@@ -5,7 +5,7 @@ import java.lang.annotation.*;
/** /**
* 数据权限注解 * 数据权限注解
* *
* @author zc * @author Ray.Hao
* @since 2.0.0 * @since 2.0.0
*/ */
@Documented @Documented

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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:{}";

View File

@@ -6,7 +6,7 @@ import lombok.Getter;
/** /**
* 环境枚举 * 环境枚举
* *
* @author Ray * @author Ray.Hao
* @since 4.0.0 * @since 4.0.0
*/ */
@Getter @Getter

View File

@@ -6,18 +6,17 @@ import lombok.Getter;
/** /**
* 状态枚举 * 状态枚举
* *
* @author haoxr * @author Ray.Hao
* @since 2022/10/14 * @since 2022/10/14
*/ */
@Getter @Getter
public enum StatusEnum implements IBaseEnum<Integer> { public enum StatusEnum implements IBaseEnum<Integer> {
ENABLE(1, "启用"), ENABLE(1, "启用"),
DISABLE (0, "禁用"); DISABLE(0, "禁用");
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) {

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

@@ -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
@@ -147,7 +146,7 @@ public class SecurityConfig {
return new ProviderManager( return new ProviderManager(
daoAuthenticationProvider, daoAuthenticationProvider,
smsAuthenticationProvider, smsAuthenticationProvider,
wxMaAuthenticationProvider wxMaAuthenticationProvider
); );
} }

View File

@@ -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 = "\\[\"(.*?)\"]+";

View File

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

View File

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

View File

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

View File

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

View File

@@ -48,6 +48,11 @@ public class Menu extends BaseEntity {
*/ */
private String component; private String component;
/**
* 外链地址
*/
private String externalUrl;
/** /**
* 权限标识 * 权限标识
*/ */

View File

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

View File

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

View File

@@ -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);
/** /**
* 删除用户 * 删除用户

View File

@@ -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)
);
}
/** /**
* 获取用户选项列表 * 获取用户选项列表
* *

View File

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