Merge branch 'master' of https://gitee.com/youlaiorg/youlai-boot
This commit is contained in:
12
pom.xml
12
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>youlai-boot</artifactId>
|
||||
<version>2.18.0</version>
|
||||
<version>2.18.1</version>
|
||||
<description>基于 Java 17 + SpringBoot 3 + Spring Security 构建的权限管理系统。</description>
|
||||
|
||||
<parent>
|
||||
@@ -20,18 +20,18 @@
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
|
||||
<hutool.version>5.8.27</hutool.version>
|
||||
<hutool.version>5.8.34</hutool.version>
|
||||
|
||||
<mysql-connector-j.version>9.1.0</mysql-connector-j.version>
|
||||
<druid.version>1.2.23</druid.version>
|
||||
<druid.version>1.2.24</druid.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
|
||||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.version>
|
||||
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
|
||||
|
||||
<xxl-job.version>2.4.1</xxl-job.version>
|
||||
<xxl-job.version>2.4.2</xxl-job.version>
|
||||
|
||||
<easyexcel.version>3.2.1</easyexcel.version>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<aliyun-sdk-oss.version>3.16.3</aliyun-sdk-oss.version>
|
||||
|
||||
<!-- redisson 分布式锁 -->
|
||||
<redisson.version>3.30.0</redisson.version>
|
||||
<redisson.version>3.40.2</redisson.version>
|
||||
|
||||
<!-- 自动代码生成 -->
|
||||
<mybatis-plus-generator.version>3.5.6</mybatis-plus-generator.version>
|
||||
|
||||
@@ -132,14 +132,14 @@ INSERT INTO `sys_dict_data` VALUES (12, 'notice_level', 'H', '高', 'danger', 1,
|
||||
DROP TABLE IF EXISTS `sys_log`;
|
||||
CREATE TABLE `sys_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`module` varchar(50) CHARACTER SET utf8mb4 NOT NULL COMMENT '日志模块',
|
||||
`module` varchar(50) NOT NULL COMMENT '日志模块',
|
||||
`request_method` varchar(64) NOT NULL DEFAULT '' COMMENT '请求方式',
|
||||
`request_params` text COMMENT '请求参数(批量请求参数可能会超过text)',
|
||||
`response_content` mediumtext COMMENT '返回参数',
|
||||
`content` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '日志内容',
|
||||
`content` varchar(255) NOT NULL COMMENT '日志内容',
|
||||
`request_uri` varchar(255) DEFAULT NULL COMMENT '请求路径',
|
||||
`method` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '方法名',
|
||||
`ip` varchar(45) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT 'IP地址',
|
||||
`method` varchar(255) DEFAULT NULL COMMENT '方法名',
|
||||
`ip` varchar(45) DEFAULT NULL COMMENT 'IP地址',
|
||||
`province` varchar(100) DEFAULT NULL COMMENT '省份',
|
||||
`city` varchar(100) DEFAULT NULL COMMENT '城市',
|
||||
`execution_time` bigint DEFAULT NULL COMMENT '执行时间(ms)',
|
||||
@@ -153,10 +153,6 @@ CREATE TABLE `sys_log` (
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC COMMENT='系统日志表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sys_log
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_menu
|
||||
-- ----------------------------
|
||||
@@ -273,9 +269,9 @@ CREATE TABLE `sys_role` (
|
||||
`status` tinyint(1) NULL DEFAULT 1 COMMENT '角色状态(1-正常 0-停用)',
|
||||
`data_scope` tinyint NULL DEFAULT NULL COMMENT '数据权限(0-所有数据 1-部门及子部门数据 2-本部门数据3-本人数据)',
|
||||
`create_by` bigint NULL DEFAULT NULL COMMENT '创建人 ID',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint NULL DEFAULT NULL COMMENT '更新人ID',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`is_deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE COMMENT '角色名称唯一索引',
|
||||
@@ -414,7 +410,7 @@ CREATE TABLE `sys_user` (
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`update_by` bigint NULL DEFAULT NULL COMMENT '修改人ID',
|
||||
`is_deleted` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
|
||||
`open_id` char(28) DEFAULT NULL COMMENT '微信 openid',
|
||||
`openid` char(28) DEFAULT NULL COMMENT '微信 openid',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `login_name`(`username` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC;
|
||||
@@ -443,28 +439,6 @@ INSERT INTO `sys_user_role` VALUES (1, 1);
|
||||
INSERT INTO `sys_user_role` VALUES (2, 2);
|
||||
INSERT INTO `sys_user_role` VALUES (3, 3);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_log`;
|
||||
CREATE TABLE `sys_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '日志模块',
|
||||
`content` varchar(255) NOT NULL COMMENT '日志内容',
|
||||
`request_uri` varchar(255) COLLATE utf8_general_ci DEFAULT NULL COMMENT '请求路径',
|
||||
`ip` varchar(45) DEFAULT NULL COMMENT 'IP地址',
|
||||
`province` varchar(100) COLLATE utf8_general_ci DEFAULT NULL COMMENT '省份',
|
||||
`city` varchar(100) COLLATE utf8_general_ci DEFAULT NULL COMMENT '城市',
|
||||
`execution_time` bigint DEFAULT NULL COMMENT '执行时间(ms)',
|
||||
`browser` varchar(100) COLLATE utf8_general_ci DEFAULT NULL COMMENT '浏览器',
|
||||
`browser_version` varchar(100) COLLATE utf8_general_ci DEFAULT NULL COMMENT '浏览器版本',
|
||||
`os` varchar(100) COLLATE utf8_general_ci DEFAULT NULL COMMENT '终端系统',
|
||||
`create_by` bigint DEFAULT NULL COMMENT '创建人ID',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除标识(1-已删除 0-未删除)',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC COMMENT='系统日志表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for gen_config
|
||||
-- ----------------------------
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-- ----------------------------
|
||||
-- 1. 创建数据库
|
||||
-- ----------------------------
|
||||
CREATE DATABASE IF NOT EXISTS youlai_boot DEFAULT CHARACTER SET utf8mb4 DEFAULT ;
|
||||
CREATE DATABASE IF NOT EXISTS youlai_boot DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
@@ -227,9 +227,9 @@
|
||||
`status` tinyint(1) NULL DEFAULT 1 COMMENT '角色状态(1-正常 0-停用)',
|
||||
`data_scope` tinyint NULL DEFAULT NULL COMMENT '数据权限(0-所有数据 1-部门及子部门数据 2-本部门数据3-本人数据)',
|
||||
`create_by` bigint NULL DEFAULT NULL COMMENT '创建人 ID',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint NULL DEFAULT NULL COMMENT '更新人ID',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`is_deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE COMMENT '角色名称唯一索引',
|
||||
@@ -367,7 +367,7 @@
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`update_by` bigint NULL DEFAULT NULL COMMENT '修改人ID',
|
||||
`is_deleted` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
|
||||
`open_id` char(28) DEFAULT NULL COMMENT '微信 openid',
|
||||
`openid` char(28) DEFAULT NULL COMMENT '微信 openid',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `login_name`(`username` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
@@ -39,4 +39,9 @@ public interface SecurityConstants {
|
||||
* 微信登录路径
|
||||
*/
|
||||
String WECHAT_LOGIN_PATH = "/api/v1/auth/wechat-login";
|
||||
|
||||
/**
|
||||
* 角色前缀 Spring Security 的 authorities 角色前缀,用于区分角色和权限
|
||||
*/
|
||||
String ROLE_PREFIX = "ROLE_";
|
||||
}
|
||||
|
||||
@@ -30,85 +30,97 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 全局系统异常处理器
|
||||
* <p>
|
||||
* 调整异常处理的HTTP状态码,丰富异常处理类型
|
||||
*
|
||||
* @author Gadfly
|
||||
* @since 2020-02-25 13:54
|
||||
**/
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理绑定异常
|
||||
* <p>
|
||||
* 当请求参数绑定到对象时发生错误,会抛出 BindException 异常。
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(BindException e) {
|
||||
log.error("BindException:{}", e.getMessage());
|
||||
String msg = e.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg);
|
||||
return Result.failed(ResultCode.USER_REQUEST_PARAMETER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* RequestParam参数的校验
|
||||
*
|
||||
* @param e
|
||||
* @param <T>
|
||||
* @return
|
||||
* 处理 @RequestParam 参数校验异常
|
||||
* <p>
|
||||
* 当请求参数在校验过程中发生违反约束条件的异常时(如 @RequestParam 验证不通过),
|
||||
* 会捕获到 ConstraintViolationException 异常。
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(ConstraintViolationException e) {
|
||||
log.error("ConstraintViolationException:{}", e.getMessage());
|
||||
String msg = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg);
|
||||
return Result.failed(ResultCode.INVALID_USER_INPUT, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* RequestBody参数的校验
|
||||
*
|
||||
* @param e
|
||||
* @param <T>
|
||||
* @return
|
||||
* 处理方法参数校验异常
|
||||
* <p>
|
||||
* 当使用 @Valid 或 @Validated 注解对方法参数进行验证时,如果验证失败,
|
||||
* 会抛出 MethodArgumentNotValidException 异常。
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(MethodArgumentNotValidException e) {
|
||||
log.error("MethodArgumentNotValidException:{}", e.getMessage());
|
||||
String msg = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg);
|
||||
return Result.failed(ResultCode.INVALID_USER_INPUT, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理接口不存在的异常
|
||||
* <p>
|
||||
* 当客户端请求一个不存在的路径时,会抛出 NoHandlerFoundException 异常。
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public <T> Result<T> processException(NoHandlerFoundException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.RESOURCE_NOT_FOUND);
|
||||
return Result.failed(ResultCode.INTERFACE_NOT_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* MissingServletRequestParameterException
|
||||
* 处理缺少请求参数的异常
|
||||
* <p>
|
||||
* 当请求缺少必需的参数时,会抛出 MissingServletRequestParameterException 异常。
|
||||
*/
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(MissingServletRequestParameterException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.PARAM_IS_NULL);
|
||||
return Result.failed(ResultCode.REQUEST_REQUIRED_PARAMETER_IS_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* MethodArgumentTypeMismatchException
|
||||
* 处理方法参数类型不匹配的异常
|
||||
* <p>
|
||||
* 当请求参数类型不匹配时,会抛出 MethodArgumentTypeMismatchException 异常。
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(MethodArgumentTypeMismatchException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.PARAM_ERROR, "类型错误");
|
||||
return Result.failed(ResultCode.PARAMETER_FORMAT_MISMATCH, "类型错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* ServletException
|
||||
* 处理 Servlet 异常
|
||||
* <p>
|
||||
* 当 Servlet 处理请求时发生异常时,会抛出 ServletException 异常。
|
||||
*/
|
||||
@ExceptionHandler(ServletException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@@ -117,6 +129,11 @@ public class GlobalExceptionHandler {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
* <p>
|
||||
* 当方法接收到非法参数时,会抛出 IllegalArgumentException 异常。
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
@@ -124,6 +141,11 @@ public class GlobalExceptionHandler {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 JSON 处理异常
|
||||
* <p>
|
||||
* 当处理 JSON 数据时发生错误,会抛出 JsonProcessingException 异常。
|
||||
*/
|
||||
@ExceptionHandler(JsonProcessingException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleJsonProcessingException(JsonProcessingException e) {
|
||||
@@ -132,7 +154,9 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* HttpMessageNotReadableException
|
||||
* 处理请求体不可读的异常
|
||||
* <p>
|
||||
* 当请求体不可读时,会抛出 HttpMessageNotReadableException 异常。
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@@ -146,6 +170,11 @@ public class GlobalExceptionHandler {
|
||||
return Result.failed(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理类型不匹配异常
|
||||
* <p>
|
||||
* 当方法参数类型不匹配时,会抛出 TypeMismatchException 异常。
|
||||
*/
|
||||
@ExceptionHandler(TypeMismatchException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(TypeMismatchException e) {
|
||||
@@ -153,18 +182,28 @@ public class GlobalExceptionHandler {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SQL 语法错误异常
|
||||
* <p>
|
||||
* 当 SQL 语法错误时,会抛出 BadSqlGrammarException 异常。
|
||||
*/
|
||||
@ExceptionHandler(BadSqlGrammarException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public <T> Result<T> handleBadSqlGrammarException(BadSqlGrammarException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorMsg = e.getMessage();
|
||||
if (StrUtil.isNotBlank(errorMsg) && errorMsg.contains("denied to user")) {
|
||||
return Result.failed(ResultCode.FORBIDDEN_OPERATION);
|
||||
return Result.failed(ResultCode.ACCESS_UNAUTHORIZED);
|
||||
} else {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SQL 语法错误异常
|
||||
* <p>
|
||||
* 当 SQL 语法错误时,会抛出 SQLSyntaxErrorException 异常。
|
||||
*/
|
||||
@ExceptionHandler(SQLSyntaxErrorException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public <T> Result<T> processSQLSyntaxErrorException(SQLSyntaxErrorException e) {
|
||||
@@ -172,7 +211,11 @@ public class GlobalExceptionHandler {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
* <p>
|
||||
* 当业务逻辑发生错误时,会抛出 BusinessException 异常。
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleBizException(BusinessException e) {
|
||||
@@ -183,9 +226,14 @@ public class GlobalExceptionHandler {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有未捕获的异常
|
||||
* <p>
|
||||
* 当发生未捕获的异常时,会抛出 Exception 异常。
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleException(Exception e) throws Exception{
|
||||
public <T> Result<T> handleException(Exception e) throws Exception {
|
||||
// 将 Spring Security 异常继续抛出,以便交给自定义处理器处理
|
||||
if (e instanceof AccessDeniedException
|
||||
|| e instanceof AuthenticationException) {
|
||||
|
||||
@@ -32,11 +32,11 @@ public class Result<T> implements Serializable {
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed() {
|
||||
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), ResultCode.SYSTEM_EXECUTION_ERROR.getMsg(), null);
|
||||
return result(ResultCode.SYSTEM_ERROR.getCode(), ResultCode.SYSTEM_ERROR.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(String msg) {
|
||||
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), msg, null);
|
||||
return result(ResultCode.SYSTEM_ERROR.getCode(), msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> judge(boolean status) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.io.Serializable;
|
||||
* <p>
|
||||
* 参考阿里巴巴开发手册响应码规范
|
||||
*
|
||||
* @author Ray
|
||||
* @author Ray.Hao
|
||||
* @since 2020/6/23
|
||||
**/
|
||||
@AllArgsConstructor
|
||||
@@ -19,57 +19,183 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
|
||||
SUCCESS("00000", "一切ok"),
|
||||
|
||||
/** 一级宏观错误码 */
|
||||
USER_ERROR("A0001", "用户端错误"),
|
||||
REPEAT_SUBMIT_ERROR("A0002", "您的请求已提交,请不要重复提交或等待片刻再尝试。"),
|
||||
|
||||
USER_LOGIN_ERROR("A0200", "用户登录异常"),
|
||||
/** 二级宏观错误码 */
|
||||
USER_REGISTRATION_ERROR("A0100", "用户注册错误"),
|
||||
USER_NOT_AGREE_PRIVACY_AGREEMENT("A0101", "用户未同意隐私协议"),
|
||||
REGISTRATION_COUNTRY_OR_REGION_RESTRICTED("A0102", "注册国家或地区受限"),
|
||||
|
||||
USER_NOT_EXIST("A0201", "用户不存在"),
|
||||
USER_ACCOUNT_LOCKED("A0202", "用户账户被冻结"),
|
||||
USER_ACCOUNT_INVALID("A0203", "用户账户已作废"),
|
||||
USERNAME_VERIFICATION_FAILED("A0110", "用户名校验失败"),
|
||||
USERNAME_ALREADY_EXISTS("A0111", "用户名已存在"),
|
||||
USERNAME_CONTAINS_SENSITIVE_WORDS("A0112", "用户名包含敏感词"),
|
||||
USERNAME_CONTAINS_SPECIAL_CHARACTERS("A0113", "用户名包含特殊字符"),
|
||||
|
||||
USERNAME_OR_PASSWORD_ERROR("A0210", "用户名或密码错误"),
|
||||
PASSWORD_ENTER_EXCEED_LIMIT("A0211", "用户输入密码次数超限"),
|
||||
CLIENT_AUTHENTICATION_FAILED("A0212", "客户端认证失败"),
|
||||
PASSWORD_VERIFICATION_FAILED("A0120", "密码校验失败"),
|
||||
PASSWORD_LENGTH_NOT_ENOUGH("A0121", "密码长度不够"),
|
||||
PASSWORD_STRENGTH_NOT_ENOUGH("A0122", "密码强度不够"),
|
||||
|
||||
VERIFY_CODE_TIMEOUT("A0213", "验证码已过期"),
|
||||
VERIFY_CODE_ERROR("A0214", "验证码错误"),
|
||||
VERIFICATION_CODE_INPUT_ERROR("A0130", "校验码输入错误"),
|
||||
SMS_VERIFICATION_CODE_INPUT_ERROR("A0131", "短信校验码输入错误"),
|
||||
EMAIL_VERIFICATION_CODE_INPUT_ERROR("A0132", "邮件校验码输入错误"),
|
||||
VOICE_VERIFICATION_CODE_INPUT_ERROR("A0133", "语音校验码输入错误"),
|
||||
|
||||
TOKEN_INVALID("A0230", "token无效或已过期"),
|
||||
REFRESH_TOKEN_INVALID("A0231", "刷新token无效或已过期"),
|
||||
USER_CERTIFICATE_EXCEPTION("A0140", "用户证件异常"),
|
||||
USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证<EFBFBD><EFBFBD>类型未选择"),
|
||||
MAINLAND_ID_NUMBER_VERIFICATION_ILLEGAL("A0142", "大陆身份证编号校验非法"),
|
||||
|
||||
TOKEN_ACCESS_FORBIDDEN("A0232", "token已被禁止访问"),
|
||||
USER_BASIC_INFORMATION_VERIFICATION_FAILED("A0150", "用户基本信息校验失败"),
|
||||
PHONE_FORMAT_VERIFICATION_FAILED("A0151", "手机格式校验失败"),
|
||||
ADDRESS_FORMAT_VERIFICATION_FAILED("A0152", "地址格式校验失败"),
|
||||
EMAIL_FORMAT_VERIFICATION_FAILED("A0153", "邮箱格式校验失败"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_LOGIN_EXCEPTION("A0200", "用户登录异常"),
|
||||
USER_ACCOUNT_FROZEN("A0201", "用户账户被冻结"),
|
||||
USER_ACCOUNT_ABOLISHED("A0202", "用户账户已作废"),
|
||||
|
||||
AUTHORIZED_ERROR("A0300", "访问权限异常"),
|
||||
USER_PASSWORD_ERROR("A0210", "用户名或密码错误"),
|
||||
USER_INPUT_PASSWORD_ERROR_LIMIT_EXCEEDED("A0211", "用户输入密码错误次数超限"),
|
||||
|
||||
USER_IDENTITY_VERIFICATION_FAILED("A0220", "用户身份校验失败"),
|
||||
USER_FINGERPRINT_RECOGNITION_FAILED("A0221", "用户指纹识别失败"),
|
||||
USER_FACE_RECOGNITION_FAILED("A0222", "用户面容识别失败"),
|
||||
USER_NOT_AUTHORIZED_THIRD_PARTY_LOGIN("A0223", "用户未获得第三方登录授权"),
|
||||
|
||||
ACCESS_TOKEN_INVALID("A0230", "访问令牌无效或已过期"),
|
||||
REFRESH_TOKEN_INVALID("A0231", "刷新令牌无效或已过期"),
|
||||
|
||||
// 验证码错误
|
||||
USER_VERIFICATION_CODE_ERROR("A0240", "用户验证码错误"),
|
||||
USER_VERIFICATION_CODE_ATTEMPT_LIMIT_EXCEEDED("A0241", "用户验证码尝试次数超限"),
|
||||
USER_VERIFICATION_CODE_EXPIRED("A0242", "用户验证码过期"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
ACCESS_PERMISSION_EXCEPTION("A0300", "访问权限异常"),
|
||||
ACCESS_UNAUTHORIZED("A0301", "访问未授权"),
|
||||
FORBIDDEN_OPERATION("A0302", "演示环境禁止新增、修改和删除数据,请本地部署后测试"),
|
||||
AUTHORIZATION_IN_PROGRESS("A0302", "正在授权中"),
|
||||
USER_AUTHORIZATION_APPLICATION_REJECTED("A0303", "用户授权申请被拒绝"),
|
||||
|
||||
ACCESS_OBJECT_PRIVACY_SETTINGS_BLOCKED("A0310", "因访问对象隐私设置被拦截"),
|
||||
AUTHORIZATION_EXPIRED("A0311", "授权已过期"),
|
||||
NO_PERMISSION_TO_USE_API("A0312", "无权限使用 API"),
|
||||
|
||||
PARAM_ERROR("A0400", "用户请求参数错误"),
|
||||
RESOURCE_NOT_FOUND("A0401", "请求资源不存在"),
|
||||
PARAM_IS_NULL("A0410", "请求必填参数为空"),
|
||||
USER_ACCESS_BLOCKED("A0320", "用户访问被拦截"),
|
||||
BLACKLISTED_USER("A0321", "黑名单用户"),
|
||||
ACCOUNT_FROZEN("A0322", "账号被冻结"),
|
||||
ILLEGAL_IP_ADDRESS("A0323", "非法 IP 地址"),
|
||||
GATEWAY_ACCESS_RESTRICTED("A0324", "网关访问受限"),
|
||||
REGION_BLACKLIST("A0325", "地域黑名单"),
|
||||
|
||||
USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"),
|
||||
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"),
|
||||
USER_UPLOAD_FILE_SIZE_EXCEEDS("A0702", "用户上传文件太大"),
|
||||
USER_UPLOAD_IMAGE_SIZE_EXCEEDS("A0703", "用户上传图片太大"),
|
||||
SERVICE_ARREARS("A0330", "服务已欠费"),
|
||||
|
||||
SYSTEM_EXECUTION_ERROR("B0001", "系统执行出错"),
|
||||
USER_SIGNATURE_EXCEPTION("A0340", "用户签名异常"),
|
||||
RSA_SIGNATURE_ERROR("A0341", "RSA 签名错误"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_REQUEST_PARAMETER_ERROR("A0400", "用户请求参数错误"),
|
||||
CONTAINS_ILLEGAL_MALICIOUS_REDIRECT_LINK("A0401", "包含非法恶意跳转链接"),
|
||||
INVALID_USER_INPUT("A0402", "无效的用户输入"),
|
||||
|
||||
REQUEST_REQUIRED_PARAMETER_IS_EMPTY("A0410", "请求必填参数为空"),
|
||||
|
||||
REQUEST_PARAMETER_VALUE_EXCEEDS_ALLOWED_RANGE("A0420", "请求参数值超出允许的范围"),
|
||||
PARAMETER_FORMAT_MISMATCH("A0421", "参数格式不匹配"),
|
||||
|
||||
USER_INPUT_CONTENT_ILLEGAL("A0430", "用户输入内容非法"),
|
||||
CONTAINS_PROHIBITED_SENSITIVE_WORDS("A0431", "包含违禁敏感词"),
|
||||
|
||||
USER_OPERATION_EXCEPTION("A0440", "用户操作异常"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_REQUEST_SERVICE_EXCEPTION("A0500", "用户请求服务异常"),
|
||||
REQUEST_LIMIT_EXCEEDED("A0501", "请求次数超出限制"),
|
||||
REQUEST_CONCURRENCY_LIMIT_EXCEEDED("A0502", "请求并发数超出限制"),
|
||||
USER_OPERATION_PLEASE_WAIT("A0503", "用户操作请等待"),
|
||||
WEBSOCKET_CONNECTION_EXCEPTION("A0504", "WebSocket 连接异常"),
|
||||
WEBSOCKET_CONNECTION_DISCONNECTED("A0505", "WebSocket 连接断开"),
|
||||
USER_DUPLICATE_REQUEST("A0506", "用户重复请求"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_RESOURCE_EXCEPTION("A0600", "用户资源异常"),
|
||||
ACCOUNT_BALANCE_INSUFFICIENT("A0601", "账户余额不足"),
|
||||
USER_DISK_SPACE_INSUFFICIENT("A0602", "用户磁盘空间不足"),
|
||||
USER_MEMORY_SPACE_INSUFFICIENT("A0603", "用户内存空间不足"),
|
||||
USER_OSS_CAPACITY_INSUFFICIENT("A0604", "用户 OSS 容量不足"),
|
||||
USER_QUOTA_EXHAUSTED("A0605", "用户配额已用光"),
|
||||
USER_RESOURCE_NOT_FOUND("A0606", "用户资源不存在"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_UPLOAD_FILE_EXCEPTION("A0700", "用户上传文件异常"),
|
||||
USER_UPLOAD_FILE_TYPE_MISMATCH("A0701", "用户上传文件类型不匹配"),
|
||||
USER_UPLOAD_FILE_TOO_LARGE("A0702", "用户上传文件太大"),
|
||||
USER_UPLOAD_IMAGE_TOO_LARGE("A0703", "用户上传图片太大"),
|
||||
USER_UPLOAD_VIDEO_TOO_LARGE("A0704", "用户上传视频太大"),
|
||||
USER_UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "用户上传压缩文件太大"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_CURRENT_VERSION_EXCEPTION("A0800", "用户当前版本异常"),
|
||||
USER_INSTALLED_VERSION_NOT_MATCH_SYSTEM("A0801", "用户安装版本与系统不匹配"),
|
||||
USER_INSTALLED_VERSION_TOO_LOW("A0802", "用户安装版本过低"),
|
||||
USER_INSTALLED_VERSION_TOO_HIGH("A0803", "用户安装版本过高"),
|
||||
USER_INSTALLED_VERSION_EXPIRED("A0804", "用户安装版本已过期"),
|
||||
USER_API_REQUEST_VERSION_NOT_MATCH("A0805", "用户 API 请求版本不匹配"),
|
||||
USER_API_REQUEST_VERSION_TOO_HIGH("A0806", "用户 API 请求版本过高"),
|
||||
USER_API_REQUEST_VERSION_TOO_LOW("A0807", "用户 API 请求版本过低"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_PRIVACY_NOT_AUTHORIZED("A0900", "用户隐私未授权"),
|
||||
USER_PRIVACY_NOT_SIGNED("A0901", "用户隐私未签署"),
|
||||
USER_CAMERA_NOT_AUTHORIZED("A0903", "用户相机未授权"),
|
||||
USER_PHOTO_LIBRARY_NOT_AUTHORIZED("A0904", "用户图片库未授权"),
|
||||
USER_FILE_NOT_AUTHORIZED("A0905", "用户文件未授权"),
|
||||
USER_LOCATION_INFORMATION_NOT_AUTHORIZED("A0906", "用户位置信息未授权"),
|
||||
USER_CONTACTS_NOT_AUTHORIZED("A0907", "用户通讯录未授权"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_DEVICE_EXCEPTION("A1000", "用户设备异常"),
|
||||
USER_CAMERA_EXCEPTION("A1001", "用户相机异常"),
|
||||
USER_MICROPHONE_EXCEPTION("A1002", "用户麦克风异常"),
|
||||
USER_EARPIECE_EXCEPTION("A1003", "用户听筒异常"),
|
||||
USER_SPEAKER_EXCEPTION("A1004", "用户扬声器异常"),
|
||||
USER_GPS_POSITIONING_EXCEPTION("A1005", "用户 GPS 定位异常"),
|
||||
|
||||
/** 一级宏观错误码 */
|
||||
SYSTEM_ERROR("B0001", "系统执行出错"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"),
|
||||
SYSTEM_ORDER_PROCESSING_TIMEOUT("B0100", "系统订单处理超时"),
|
||||
|
||||
SYSTEM_DISASTER_RECOVERY_TRIGGER("B0200", "系统容灾功能被触发"),
|
||||
FLOW_LIMITING("B0210", "系统限流,请稍后再试"),
|
||||
DEGRADATION("B0220", "系统功能降级"),
|
||||
/** 二级宏观错误码 */
|
||||
SYSTEM_DISASTER_RECOVERY_FUNCTION_TRIGGERED("B0200", "系统容灾功能被触发"),
|
||||
|
||||
SYSTEM_RESOURCE_ERROR("B0300", "系统资源异常"),
|
||||
SYSTEM_RESOURCE_EXHAUSTION("B0310", "系统资源耗尽"),
|
||||
SYSTEM_RESOURCE_ACCESS_ERROR("B0320", "系统资源访问异常"),
|
||||
SYSTEM_READ_DISK_FILE_ERROR("B0321", "系统读取磁盘文件失败"),
|
||||
SYSTEM_RATE_LIMITING("B0210", "系统限流"),
|
||||
|
||||
CALL_THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"),
|
||||
SYSTEM_FUNCTION_DEGRADATION("B0220", "系统功能降级"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
SYSTEM_RESOURCE_EXCEPTION("B0300", "系统资源异常"),
|
||||
SYSTEM_RESOURCE_EXHAUSTED("B0310", "系统资源耗尽"),
|
||||
SYSTEM_DISK_SPACE_EXHAUSTED("B0311", "系统磁盘空间耗尽"),
|
||||
SYSTEM_MEMORY_EXHAUSTED("B0312", "系统内存耗尽"),
|
||||
FILE_HANDLE_EXHAUSTED("B0313", "文件句柄耗尽"),
|
||||
SYSTEM_CONNECTION_POOL_EXHAUSTED("B0314", "系统连接池耗尽"),
|
||||
SYSTEM_THREAD_POOL_EXHAUSTED("B0315", "系统线程池耗尽"),
|
||||
|
||||
SYSTEM_RESOURCE_ACCESS_EXCEPTION("B0320", "系统资源访问异常"),
|
||||
SYSTEM_READ_DISK_FILE_FAILED("B0321", "系统读取磁盘文件失败"),
|
||||
|
||||
|
||||
/** 一级宏观错误码 */
|
||||
THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错"),
|
||||
|
||||
RPC_SERVICE_ERROR("C0110", "RPC 服务出错"),
|
||||
RPC_SERVICE_NOT_FOUND("C0111", "RPC 服务未找到"),
|
||||
RPC_SERVICE_NOT_REGISTERED("C0112", "RPC 服务未注册"),
|
||||
INTERFACE_NOT_EXIST("C0113", "接口不存在"),
|
||||
|
||||
MESSAGE_SERVICE_ERROR("C0120", "消息服务出错"),
|
||||
@@ -78,12 +204,56 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
MESSAGE_SUBSCRIPTION_ERROR("C0123", "消息订阅出错"),
|
||||
MESSAGE_GROUP_NOT_FOUND("C0124", "消息分组未查到"),
|
||||
|
||||
DATABASE_ERROR("C0300", "数据库服务出错"),
|
||||
DATABASE_TABLE_NOT_EXIST("C0311", "表不存在"),
|
||||
DATABASE_COLUMN_NOT_EXIST("C0312", "列不存在"),
|
||||
DATABASE_DUPLICATE_COLUMN_NAME("C0321", "多表关联中存在多个相同名称的列"),
|
||||
CACHE_SERVICE_ERROR("C0130", "缓存服务出错"),
|
||||
KEY_LENGTH_EXCEEDS_LIMIT("C0131", "key 长度超过限制"),
|
||||
VALUE_LENGTH_EXCEEDS_LIMIT("C0132", "value 长度超过限制"),
|
||||
STORAGE_CAPACITY_FULL("C0133", "存储容量已满"),
|
||||
UNSUPPORTED_DATA_FORMAT("C0134", "不支持的数据格式"),
|
||||
|
||||
CONFIGURATION_SERVICE_ERROR("C0140", "配置服务出错"),
|
||||
|
||||
NETWORK_RESOURCE_SERVICE_ERROR("C0150", "网络资源服务出错"),
|
||||
VPN_SERVICE_ERROR("C0151", "VPN 服务出错"),
|
||||
CDN_SERVICE_ERROR("C0152", "CDN 服务出错"),
|
||||
DOMAIN_NAME_RESOLUTION_SERVICE_ERROR("C0153", "域名解析服务出错"),
|
||||
GATEWAY_SERVICE_ERROR("C0154", "网关服务出错"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
THIRD_PARTY_SYSTEM_EXECUTION_TIMEOUT("C0200", "第三方系统执行超时"),
|
||||
|
||||
RPC_EXECUTION_TIMEOUT("C0210", "RPC 执行超时"),
|
||||
|
||||
MESSAGE_DELIVERY_TIMEOUT("C0220", "消息投递超时"),
|
||||
|
||||
CACHE_SERVICE_TIMEOUT("C0230", "缓存服务超时"),
|
||||
|
||||
CONFIGURATION_SERVICE_TIMEOUT("C0240", "配置服务超时"),
|
||||
|
||||
DATABASE_SERVICE_TIMEOUT("C0250", "数据库服务超时"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
DATABASE_SERVICE_ERROR("C0300", "数据库服务出错"),
|
||||
|
||||
TABLE_NOT_EXIST("C0311", "表不存在"),
|
||||
COLUMN_NOT_EXIST("C0312", "列不存在"),
|
||||
|
||||
MULTIPLE_SAME_NAME_COLUMNS_IN_MULTI_TABLE_ASSOCIATION("C0321", "多表关联中存在多个相同名称的列"),
|
||||
|
||||
DATABASE_DEADLOCK("C0331", "数据库死锁"),
|
||||
DATABASE_PRIMARY_KEY_CONFLICT("C0341", "主键冲突");
|
||||
|
||||
PRIMARY_KEY_CONFLICT("C0341", "主键冲突"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
THIRD_PARTY_DISASTER_RECOVERY_SYSTEM_TRIGGERED("C0400", "第三方容灾系统被触发"),
|
||||
THIRD_PARTY_SYSTEM_RATE_LIMITING("C0401", "第三方系统限流"),
|
||||
THIRD_PARTY_FUNCTION_DEGRADATION("C0402", "第三方功能降级"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
NOTIFICATION_SERVICE_ERROR("C0500", "通知服务出错"),
|
||||
SMS_REMINDER_SERVICE_FAILED("C0501", "短信提醒服务失败"),
|
||||
VOICE_REMINDER_SERVICE_FAILED("C0502", "语音提醒服务失败"),
|
||||
EMAIL_REMINDER_SERVICE_FAILED("C0503", "邮件提醒服务失败");
|
||||
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
@@ -114,6 +284,6 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return SYSTEM_EXECUTION_ERROR; // 默认系统执行错误
|
||||
return SYSTEM_ERROR; // 默认系统执行错误
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,8 @@ public class ResponseUtils {
|
||||
public static void writeErrMsg(HttpServletResponse response, ResultCode resultCode) {
|
||||
// 根据不同的结果码设置HTTP状态
|
||||
int status = switch (resultCode) {
|
||||
case ACCESS_UNAUTHORIZED, TOKEN_INVALID,REFRESH_TOKEN_INVALID -> HttpStatus.UNAUTHORIZED.value();
|
||||
case TOKEN_ACCESS_FORBIDDEN -> HttpStatus.FORBIDDEN.value();
|
||||
case ACCESS_UNAUTHORIZED, ACCESS_TOKEN_INVALID , REFRESH_TOKEN_INVALID
|
||||
-> HttpStatus.UNAUTHORIZED.value();
|
||||
default -> HttpStatus.BAD_REQUEST.value();
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
/**
|
||||
* mybatis-plus 自动配置类
|
||||
*
|
||||
* @author haoxr
|
||||
* @author Ray.Hao
|
||||
* @since 2022/7/2
|
||||
*/
|
||||
@Configuration
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
/**
|
||||
* Redis 缓存配置
|
||||
*
|
||||
* @author Ray
|
||||
* @author Ray.Hao
|
||||
* @since 2023/12/4
|
||||
*/
|
||||
@EnableCaching
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
/**
|
||||
* Redis 配置
|
||||
*
|
||||
* @author Ray
|
||||
* @author Ray.Hao
|
||||
* @since 2023/5/15
|
||||
*/
|
||||
@Configuration
|
||||
|
||||
@@ -55,7 +55,7 @@ public class RepeatSubmitAspect {
|
||||
RLock lock = redissonClient.getLock(resubmitLockKey);
|
||||
boolean lockResult = lock.tryLock(0, expire, TimeUnit.SECONDS); // 获取锁失败,直接返回 false
|
||||
if (!lockResult) {
|
||||
throw new BusinessException(ResultCode.REPEAT_SUBMIT_ERROR); // 抛出重复提交提示信息
|
||||
throw new BusinessException(ResultCode.USER_DUPLICATE_REQUEST); // 抛出重复提交提示信息
|
||||
}
|
||||
}
|
||||
return pjp.proceed();
|
||||
|
||||
@@ -73,7 +73,7 @@ public class RateLimiterFilter extends OncePerRequestFilter {
|
||||
@NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||
String ip = IPUtils.getIpAddr(request);
|
||||
if (rateLimit(ip)) {
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.FLOW_LIMITING);
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.REQUEST_CONCURRENCY_LIMIT_EXCEEDED);
|
||||
return;
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
|
||||
@@ -4,18 +4,20 @@ import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.util.ResponseUtils;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 认证异常处理
|
||||
*
|
||||
* @author haoxr
|
||||
* @author Ray.Hao
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
@@ -25,15 +27,14 @@ public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
int status = response.getStatus();
|
||||
if (status == HttpServletResponse.SC_NOT_FOUND) {
|
||||
// 资源不存在
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.RESOURCE_NOT_FOUND);
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_RESOURCE_NOT_FOUND);
|
||||
} else {
|
||||
|
||||
if(authException instanceof BadCredentialsException){
|
||||
if (authException instanceof UsernameNotFoundException || authException instanceof BadCredentialsException) {
|
||||
// 用户名或密码错误
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USERNAME_OR_PASSWORD_ERROR);
|
||||
}else {
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_PASSWORD_ERROR);
|
||||
} else {
|
||||
// 未认证或者token过期
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID);
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
|
||||
String verifyCodeKey = request.getParameter(CAPTCHA_KEY_PARAM_NAME);
|
||||
String cacheVerifyCode = (String) redisTemplate.opsForValue().get(SecurityConstants.CAPTCHA_CODE_PREFIX + verifyCodeKey);
|
||||
if (cacheVerifyCode == null) {
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_TIMEOUT);
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_VERIFICATION_CODE_EXPIRED);
|
||||
} else {
|
||||
// 验证码比对
|
||||
if (codeGenerator.verify(cacheVerifyCode, captchaCode)) {
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_ERROR);
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -48,7 +48,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
// 校验 JWT Token ,包括验签和是否过期
|
||||
boolean isValidate = jwtTokenService.validateToken(token);
|
||||
if (!isValidate) {
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID);
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
return;
|
||||
}
|
||||
// 将 Token 解析为 Authentication 对象,并设置到 Spring Security 上下文中
|
||||
@@ -57,7 +57,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SecurityContextHolder.clearContext();
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID);
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
return;
|
||||
}
|
||||
// Token有效或无Token时继续执行过滤链
|
||||
|
||||
@@ -2,9 +2,9 @@ package com.youlai.boot.core.security.model;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
@@ -12,51 +12,76 @@ import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Spring Security 用户对象
|
||||
* Spring Security 用户认证对象
|
||||
* <p>
|
||||
* 封装了用户的基本信息和权限信息,供 Spring Security 进行用户认证与授权。
|
||||
* 实现了 {@link UserDetails} 接口,提供用户的核心信息。
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 3.0.0
|
||||
* @author Ray.Hao
|
||||
* @version 3.0.0
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class SysUserDetails implements UserDetails {
|
||||
|
||||
@Getter
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 账号是否启用(true:启用,false:禁用)
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
private Collection<SimpleGrantedAuthority> authorities;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 数据权限范围
|
||||
*/
|
||||
private Integer dataScope;
|
||||
|
||||
/**
|
||||
* 用户角色权限集合
|
||||
*/
|
||||
private Collection<SimpleGrantedAuthority> authorities;
|
||||
|
||||
/**
|
||||
* 构造函数:根据用户认证信息初始化用户详情对象
|
||||
*
|
||||
* @param user 用户认证信息对象 {@link UserAuthInfo}
|
||||
*/
|
||||
public SysUserDetails(UserAuthInfo user) {
|
||||
this.userId = user.getUserId();
|
||||
Set<String> roles = user.getRoles();
|
||||
Set<SimpleGrantedAuthority> authorities;
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
authorities = roles.stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role)) // 标识角色
|
||||
.collect(Collectors.toSet());
|
||||
} else {
|
||||
authorities = Collections.emptySet();
|
||||
}
|
||||
this.authorities = authorities;
|
||||
this.username = user.getUsername();
|
||||
this.password = user.getPassword();
|
||||
this.enabled = ObjectUtil.equal(user.getStatus(), 1);
|
||||
this.deptId = user.getDeptId();
|
||||
this.dataScope = user.getDataScope();
|
||||
|
||||
// 初始化角色权限集合
|
||||
this.authorities = CollectionUtil.isNotEmpty(user.getRoles())
|
||||
? user.getRoles().stream()
|
||||
// 角色名加上前缀 "ROLE_",用于区分角色 (ROLE_ADMIN) 和权限 (user:add)
|
||||
.map(role -> new SimpleGrantedAuthority(SecurityConstants.ROLE_PREFIX + role))
|
||||
.collect(Collectors.toSet())
|
||||
: Collections.emptySet();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.youlai.boot.core.security.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.core.security.model.SysUserDetails;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -83,22 +84,22 @@ public class SecurityUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户角色集合
|
||||
* 获取角色集合
|
||||
*
|
||||
* @return 角色集合
|
||||
*/
|
||||
public static Set<String> getRoles() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null) {
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
if (CollectionUtil.isNotEmpty(authorities)) {
|
||||
return authorities.stream().filter(item -> item.getAuthority().startsWith("ROLE_"))
|
||||
.map(item -> StrUtil.removePrefix(item.getAuthority(), "ROLE_"))
|
||||
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
|
||||
.map(Authentication::getAuthorities)
|
||||
.filter(CollectionUtil::isNotEmpty)
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
// 筛选角色,authorities 中的角色都是以 ROLE_ 开头
|
||||
.filter(authority -> authority.startsWith(SecurityConstants.ROLE_PREFIX))
|
||||
.map(authority -> StrUtil.removePrefix(authority, SecurityConstants.ROLE_PREFIX))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否超级管理员
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.youlai.boot.shared.file.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.youlai.boot.shared.file.model.FileInfo;
|
||||
import com.youlai.boot.shared.file.service.FileService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 本地存储服务类
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2024-12-09 17:11
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "oss.type", havingValue = "local")
|
||||
@ConfigurationProperties(prefix = "oss.local")
|
||||
@RequiredArgsConstructor
|
||||
@Data
|
||||
public class LocalFileService implements FileService {
|
||||
|
||||
@Value("${oss.local.storage-path}")
|
||||
private String storagePath;
|
||||
|
||||
@Override
|
||||
public FileInfo uploadFile(MultipartFile file) {
|
||||
// 生成文件名(日期文件夹)
|
||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String folder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + File.separator;
|
||||
String fileName = uuid + "." + suffix;
|
||||
String filePrefix = storagePath.endsWith(File.separator) ? storagePath : storagePath + File.separator;
|
||||
// try-with-resource 语法糖自动释放流
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
// 上传文件
|
||||
FileUtil.writeFromStream(inputStream, filePrefix +folder+ fileName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("文件上传失败");
|
||||
}
|
||||
// 获取文件访问路径,因为这里是本地存储,所以直接返回文件的相对路径,需要前端自行处理访问前缀
|
||||
String fileUrl = File.separator +folder+File.separator + fileName;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setName(fileName);
|
||||
fileInfo.setUrl(fileUrl);
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFile(String filePath) {
|
||||
//判断文件是否为空
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 判断filepath是否为文件夹
|
||||
if (FileUtil.isDirectory(storagePath + filePath)) {
|
||||
// 禁止删除文件夹
|
||||
return false;
|
||||
}
|
||||
// 删除文件
|
||||
return FileUtil.del(storagePath + filePath);
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,8 @@ public class MenuController {
|
||||
|
||||
@Operation(summary = "菜单路由列表")
|
||||
@GetMapping("/routes")
|
||||
public Result<List<RouteVO>> listRoutes() {
|
||||
Set<String> roles = SecurityUtils.getRoles();
|
||||
List<RouteVO> routeList = menuService.listRoutes(roles);
|
||||
public Result<List<RouteVO>> getCurrentUserRoutes() {
|
||||
List<RouteVO> routeList = menuService.getCurrentUserRoutes();
|
||||
return Result.success(routeList);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.youlai.boot.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.boot.system.model.bo.RouteBO;
|
||||
import com.youlai.boot.system.model.entity.Menu;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@@ -20,7 +19,9 @@ public interface MenuMapper extends BaseMapper<Menu> {
|
||||
|
||||
/**
|
||||
* 获取菜单路由列表
|
||||
*
|
||||
* @param roleCodes 角色编码集合
|
||||
*/
|
||||
List<RouteBO> listRoutes(Set<String> roles);
|
||||
List<Menu> getMenusByRoleCodes(Set<String> roleCodes);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.youlai.boot.system.model.bo;
|
||||
|
||||
import com.youlai.boot.system.enums.MenuTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 路由
|
||||
*/
|
||||
@Data
|
||||
public class RouteBO {
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 父菜单ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 菜单类型(1-菜单 2-目录 3-外链 4-按钮)
|
||||
*/
|
||||
private MenuTypeEnum type;
|
||||
|
||||
/**
|
||||
* 路由名称(Vue Router 中定义的路由名称)
|
||||
*/
|
||||
private String routeName;
|
||||
|
||||
/**
|
||||
* 路由路径(Vue Router 中定义的 URL 路径)
|
||||
*/
|
||||
private String routePath;
|
||||
|
||||
/**
|
||||
* 组件路径(vue页面完整路径,省略.vue后缀)
|
||||
*/
|
||||
private String component;
|
||||
|
||||
/**
|
||||
* 权限标识
|
||||
*/
|
||||
private String perm;
|
||||
|
||||
/**
|
||||
* 显示状态(1:显示;0:隐藏)
|
||||
*/
|
||||
private Integer visible;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 菜单图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 跳转路径
|
||||
*/
|
||||
private String redirect;
|
||||
|
||||
/**
|
||||
* 【目录】只有一个子路由是否始终显示(1:是 0:否)
|
||||
*/
|
||||
private Integer alwaysShow;
|
||||
|
||||
/**
|
||||
* 【菜单】是否开启页面缓存(1:是 0:否)
|
||||
*/
|
||||
private Integer keepAlive;
|
||||
|
||||
/**
|
||||
* 【菜单】路由参数
|
||||
*/
|
||||
private String params;
|
||||
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public interface MenuService extends IService<Menu> {
|
||||
/**
|
||||
* 获取路由列表
|
||||
*/
|
||||
List<RouteVO> listRoutes( Set<String> roles);
|
||||
List<RouteVO> getCurrentUserRoutes();
|
||||
|
||||
/**
|
||||
* 修改菜单显示状态
|
||||
|
||||
@@ -10,9 +10,9 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.converter.MenuConverter;
|
||||
import com.youlai.boot.system.mapper.MenuMapper;
|
||||
import com.youlai.boot.system.model.bo.RouteBO;
|
||||
import com.youlai.boot.shared.codegen.model.entity.GenConfig;
|
||||
import com.youlai.boot.system.model.entity.Menu;
|
||||
import com.youlai.boot.system.model.form.MenuForm;
|
||||
@@ -35,9 +35,9 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 菜单业务实现类
|
||||
* 菜单服务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @author Ray.Hao
|
||||
* @since 2020/11/06
|
||||
*/
|
||||
@Service
|
||||
@@ -142,13 +142,22 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
|
||||
* 获取菜单路由列表
|
||||
*/
|
||||
@Override
|
||||
public List<RouteVO> listRoutes(Set<String> roles) {
|
||||
public List<RouteVO> getCurrentUserRoutes() {
|
||||
|
||||
if (CollectionUtil.isEmpty(roles)) {
|
||||
Set<String> roleCodes = SecurityUtils.getRoles();
|
||||
|
||||
if (CollectionUtil.isEmpty(roleCodes)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<RouteBO> menuList = this.baseMapper.listRoutes(roles);
|
||||
List<Menu> menuList;
|
||||
if (SecurityUtils.isRoot()) {
|
||||
// 超级管理员获取所有菜单
|
||||
menuList = this.list(new LambdaQueryWrapper<Menu>().ne(
|
||||
Menu::getType, MenuTypeEnum.BUTTON.getValue()
|
||||
));
|
||||
} else {
|
||||
menuList = this.baseMapper.getMenusByRoleCodes(roleCodes);
|
||||
}
|
||||
return buildRoutes(SystemConstants.ROOT_NODE_ID, menuList);
|
||||
}
|
||||
|
||||
@@ -159,10 +168,10 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
|
||||
* @param menuList 菜单列表
|
||||
* @return 路由层级列表
|
||||
*/
|
||||
private List<RouteVO> buildRoutes(Long parentId, List<RouteBO> menuList) {
|
||||
private List<RouteVO> buildRoutes(Long parentId, List<Menu> menuList) {
|
||||
List<RouteVO> routeList = new ArrayList<>();
|
||||
|
||||
for (RouteBO menu : menuList) {
|
||||
for (Menu menu : menuList) {
|
||||
if (menu.getParentId().equals(parentId)) {
|
||||
RouteVO routeVO = toRouteVo(menu);
|
||||
List<RouteVO> children = buildRoutes(menu.getId(), menuList);
|
||||
@@ -179,34 +188,34 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
|
||||
/**
|
||||
* 根据RouteBO创建RouteVO
|
||||
*/
|
||||
private RouteVO toRouteVo(RouteBO routeBO) {
|
||||
private RouteVO toRouteVo(Menu menu) {
|
||||
RouteVO routeVO = new RouteVO();
|
||||
// 获取路由名称
|
||||
String routeName = routeBO.getRouteName();
|
||||
String routeName = menu.getRouteName();
|
||||
if (StrUtil.isBlank(routeName)) {
|
||||
// 路由 name 需要驼峰,首字母大写
|
||||
routeName = StringUtils.capitalize(StrUtil.toCamelCase(routeBO.getRoutePath(), '-'));
|
||||
routeName = StringUtils.capitalize(StrUtil.toCamelCase(menu.getRoutePath(), '-'));
|
||||
}
|
||||
// 根据name路由跳转 this.$router.push({name:xxx})
|
||||
routeVO.setName(routeName);
|
||||
|
||||
// 根据path路由跳转 this.$router.push({path:xxx})
|
||||
routeVO.setPath(routeBO.getRoutePath());
|
||||
routeVO.setRedirect(routeBO.getRedirect());
|
||||
routeVO.setComponent(routeBO.getComponent());
|
||||
routeVO.setPath(menu.getRoutePath());
|
||||
routeVO.setRedirect(menu.getRedirect());
|
||||
routeVO.setComponent(menu.getComponent());
|
||||
|
||||
RouteVO.Meta meta = new RouteVO.Meta();
|
||||
meta.setTitle(routeBO.getName());
|
||||
meta.setIcon(routeBO.getIcon());
|
||||
meta.setHidden(StatusEnum.DISABLE.getValue().equals(routeBO.getVisible()));
|
||||
meta.setTitle(menu.getName());
|
||||
meta.setIcon(menu.getIcon());
|
||||
meta.setHidden(StatusEnum.DISABLE.getValue().equals(menu.getVisible()));
|
||||
// 【菜单】是否开启页面缓存
|
||||
if (MenuTypeEnum.MENU.equals(routeBO.getType())
|
||||
&& ObjectUtil.equals(routeBO.getKeepAlive(), 1)) {
|
||||
if (MenuTypeEnum.MENU.equals(menu.getType())
|
||||
&& ObjectUtil.equals(menu.getKeepAlive(), 1)) {
|
||||
meta.setKeepAlive(true);
|
||||
}
|
||||
meta.setAlwaysShow(ObjectUtil.equals(routeBO.getAlwaysShow(), 1));
|
||||
meta.setAlwaysShow(ObjectUtil.equals(menu.getAlwaysShow(), 1));
|
||||
|
||||
String paramsJson = routeBO.getParams();
|
||||
String paramsJson = menu.getParams();
|
||||
// 将 JSON 字符串转换为 Map<String, String>
|
||||
if (StrUtil.isNotBlank(paramsJson)) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
@@ -241,7 +250,7 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
|
||||
|
||||
menuForm.setComponent(null);
|
||||
}
|
||||
if (Objects.equals(menuForm.getParentId(), menuForm.getId())){
|
||||
if (Objects.equals(menuForm.getParentId(), menuForm.getId())) {
|
||||
throw new RuntimeException("父级菜单不能为当前菜单");
|
||||
}
|
||||
Menu entity = menuConverter.toEntity(menuForm);
|
||||
@@ -262,7 +271,7 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
|
||||
.eq(Menu::getRouteName, entity.getRouteName())
|
||||
.ne(menuForm.getId() != null, Menu::getId, menuForm.getId())
|
||||
), "路由名称已存在");
|
||||
}else{
|
||||
} else {
|
||||
// 其他类型时 给路由名称赋值为空
|
||||
entity.setRouteName(null);
|
||||
}
|
||||
@@ -281,6 +290,7 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
|
||||
|
||||
/**
|
||||
* 更新子菜单树路径
|
||||
*
|
||||
* @param id 当前菜单ID
|
||||
* @param treePath 当前菜单树路径
|
||||
*/
|
||||
|
||||
@@ -89,6 +89,7 @@ security:
|
||||
ignore-urls:
|
||||
- /v3/api-docs/**
|
||||
- /doc.html
|
||||
- ${springdoc.swagger-ui.path}
|
||||
- /swagger-resources/**
|
||||
- /webjars/**
|
||||
- /swagger-ui/**
|
||||
@@ -122,7 +123,10 @@ oss:
|
||||
access-key-secret: your-access-key-secret
|
||||
# 存储桶名称
|
||||
bucket-name: default
|
||||
|
||||
# 本地存储
|
||||
local:
|
||||
# 文件存储路径 请注意下,mac用户请使用 /Users/your-username/your-path/,否则会有权限问题,windows用户请使用 D:/your-path/
|
||||
storage-path: /Users/theo/home/
|
||||
# 短信配置
|
||||
sms:
|
||||
# 阿里云短信
|
||||
|
||||
@@ -88,6 +88,7 @@ security:
|
||||
ignore-urls:
|
||||
- /v3/api-docs/**
|
||||
- /doc.html
|
||||
- ${springdoc.swagger-ui.path}
|
||||
- /swagger-resources/**
|
||||
- /webjars/**
|
||||
- /swagger-ui/**
|
||||
@@ -121,7 +122,10 @@ oss:
|
||||
access-key-secret: your-access-key-secret
|
||||
# 存储桶名称
|
||||
bucket-name: default
|
||||
|
||||
# 本地存储
|
||||
local:
|
||||
# 文件存储路径 请注意下,mac用户请使用 /Users/your-username/your-path/,否则会有权限问题,windows用户请使用 D:/your-path/
|
||||
storage-path: /Users/theo/home/
|
||||
# 短信配置
|
||||
sms:
|
||||
# 阿里云短信
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
|
||||
<configuration>
|
||||
|
||||
<!-- SpringBoot默认logback的配置 -->
|
||||
<!-- Spring Boot 默认 logback 的配置 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
|
||||
<property name="LOG_HOME" value="/logs/${APP_NAME}"/>
|
||||
|
||||
<!--1. 输出到控制台-->
|
||||
<!-- 1. 输出到控制台-->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- <withJansi>true</withJansi>-->
|
||||
<!--此日志appender是为开发使用,只配置最低级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
|
||||
<!--此日志 appender 是为开发使用,只配置最低级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
|
||||
@@ -4,26 +4,8 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.youlai.boot.system.mapper.MenuMapper">
|
||||
|
||||
<!-- 菜单路由映射 -->
|
||||
<resultMap id="RouteMap" type="com.youlai.boot.system.model.bo.RouteBO">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="name" column="name" jdbcType="VARCHAR"/>
|
||||
<result property="parentId" column="parent_id" jdbcType="BIGINT"/>
|
||||
<result property="routeName" column="route_name" jdbcType="VARCHAR"/>
|
||||
<result property="routePath" column="route_path" jdbcType="VARCHAR"/>
|
||||
<result property="component" column="component" jdbcType="VARCHAR"/>
|
||||
<result property="redirect" column="redirect" jdbcType="VARCHAR"/>
|
||||
<result property="icon" column="icon" jdbcType="VARCHAR"/>
|
||||
<result property="sort" column="sort" jdbcType="INTEGER"/>
|
||||
<result property="visible" column="visible" jdbcType="BOOLEAN"/>
|
||||
<result property="type" column="type" jdbcType="OTHER"/>
|
||||
<result property="alwaysShow" column="always_show" jdbcType="INTEGER"/>
|
||||
<result property="keepAlive" column="keep_alive" jdbcType="INTEGER"/>
|
||||
<result property="params" column="params" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 获取路由列表 -->
|
||||
<select id="listRoutes" resultMap="RouteMap">
|
||||
<select id="getMenusByRoleCodes" resultType="com.youlai.boot.system.model.entity.Menu">
|
||||
SELECT
|
||||
DISTINCT t1.id,
|
||||
t1.name,
|
||||
@@ -45,15 +27,17 @@
|
||||
INNER JOIN sys_role t3 ON t2.role_id = t3.id AND t3.status = 1 AND t3.is_deleted = 0
|
||||
WHERE
|
||||
t1.type != '${@com.youlai.boot.system.enums.MenuTypeEnum@BUTTON.getValue()}'
|
||||
<if test="roles != null and roles.size() > 0">
|
||||
<!-- ROOT 可查看所有菜单 -->
|
||||
<if test="!roles.contains('ROOT')">
|
||||
<choose>
|
||||
<when test="roleCodes != null and roleCodes.size() > 0">
|
||||
AND t3.code IN
|
||||
<foreach collection="roles" item="role" open="(" close=")" separator=",">
|
||||
#{role}
|
||||
<foreach item="roleCode" open="(" close=")" separator="," collection="roleCodes" >
|
||||
#{roleCode}
|
||||
</foreach>
|
||||
</if>
|
||||
</if>
|
||||
</when>
|
||||
<otherwise>
|
||||
AND 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
ORDER BY
|
||||
t1.sort
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user