发布 v3.5.0-release 正式版

This commit is contained in:
疯狂的狮子li 2021-12-28 11:00:10 +08:00
parent 5857db7c5a
commit 55e3e93199
184 changed files with 3602 additions and 1516 deletions

View File

@ -4,26 +4,27 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br> <br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-3.4.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus) [![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-3.5.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.5-blue.svg)]() [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.5-blue.svg)]()
[![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]() [![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
[![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]() [![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
> RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼容原框架) > RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群` 场景全方位升级(不兼容原框架)
> 系统演示: [传送门](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/系统演示?sort_id=4836388) > 系统演示: [传送门](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/系统演示?sort_id=4836388)
| 功能介绍 | 使用技术 | 文档地址 | 特性注意事项 | | 功能介绍 | 使用技术 | 文档地址 | 特性注意事项 |
|---|---|---|---| |---|---|---|---|
| 当前框架 | RuoYi-Vue-Plus | [RuoYi-Vue-Plus文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) | 重写RuoYi-Vue全方位升级(不兼容原框架) | | 当前框架 | RuoYi-Vue-Plus | [RuoYi-Vue-Plus文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) | 重写RuoYi-Vue全方位升级(不兼容原框架) |
| satoken分支 | RuoYi-Vue-Plus-satoken | [satoken分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/) | 使用satoken重构权限鉴权(公测 可尝试上生产) | | satoken分支 | RuoYi-Vue-Plus-satoken | [satoken分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/) | 高可读性 扩展性(推荐使用) |
| 单体分支 | RuoYi-Vue-Plus-fast | [fast分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/) | 单体应用结构 | | 单体分支 | RuoYi-Vue-Plus-fast | [fast分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/) | 单体应用结构 |
| Vue3分支 | RuoYi-Vue-Plus-UI | [UI地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus-UI) | 由于组件还未完善 仅供学习 |
| 原框架 | RuoYi-Vue | [RuoYi-Vue官网](http://ruoyi.vip/) | 定期同步需要的功能 | | 原框架 | RuoYi-Vue | [RuoYi-Vue官网](http://ruoyi.vip/) | 定期同步需要的功能 |
| 前端开发框架 | Vue、Element UI | [Element UI官网](https://element.eleme.cn/#/zh-CN) | | | 前端开发框架 | Vue、Element UI | [Element UI官网](https://element.eleme.cn/#/zh-CN) | |
| 后端开发框架 | SpringBoot | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn) | | | 后端开发框架 | SpringBoot | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn) | |
| 容器框架 | Undertow | [Undertow官网](https://undertow.io/) | 基于 Netty 的高性能容器 | | 容器框架 | Undertow | [Undertow官网](https://undertow.io/) | 基于 XNIO 的高性能容器 |
| 权限认证框架 | Spring Security、Jwt | [SpringSecurity官网](https://spring.io/projects/spring-security#learn) | 支持多终端认证系统 | | 权限认证框架 | Spring Security、Jwt | [SpringSecurity官网](https://spring.io/projects/spring-security#learn) | 支持多终端认证系统 |
| 权限认证框架 | Sa-Token、Jwt | [Sa-Token官网](https://sa-token.dev33.cn/) | 强解耦、强扩展 |
| 关系数据库 | MySQL | [MySQL官网](https://dev.mysql.com/) | 适配 8.X 最低 5.7 | | 关系数据库 | MySQL | [MySQL官网](https://dev.mysql.com/) | 适配 8.X 最低 5.7 |
| 缓存数据库 | Redis | [Redis官网](https://redis.io/) | 适配 6.X 最低 4.X | | 缓存数据库 | Redis | [Redis官网](https://redis.io/) | 适配 6.X 最低 4.X |
| 数据库框架 | Mybatis-Plus | [Mybatis-Plus文档](https://baomidou.com/guide/) | 快速 CRUD 增加开发效率 | | 数据库框架 | Mybatis-Plus | [Mybatis-Plus文档](https://baomidou.com/guide/) | 快速 CRUD 增加开发效率 |

81
pom.xml
View File

@ -6,15 +6,15 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<version>3.4.0</version> <version>3.5.0</version>
<name>RuoYi-Vue-Plus</name> <name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url> <url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus后台管理系统</description> <description>RuoYi-Vue-Plus后台管理系统</description>
<properties> <properties>
<ruoyi-vue-plus.version>3.4.0</ruoyi-vue-plus.version> <ruoyi-vue-plus.version>3.5.0</ruoyi-vue-plus.version>
<spring-boot.version>2.5.7</spring-boot.version> <spring-boot.version>2.5.8</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
@ -24,27 +24,27 @@
<swagger-annotations.version>1.5.22</swagger-annotations.version> <swagger-annotations.version>1.5.22</swagger-annotations.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<easyexcel.version>2.2.11</easyexcel.version> <easyexcel.version>2.2.11</easyexcel.version>
<cglib.version>3.3.0</cglib.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version>
<mybatis-plus.version>3.4.3.4</mybatis-plus.version> <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.7.16</hutool.version> <hutool.version>5.7.18</hutool.version>
<okhttp.version>4.9.2</okhttp.version> <okhttp.version>4.9.2</okhttp.version>
<spring-boot-admin.version>2.5.4</spring-boot-admin.version> <spring-boot-admin.version>2.5.5</spring-boot-admin.version>
<redisson.version>3.16.4</redisson.version> <redisson.version>3.16.7</redisson.version>
<lock4j.version>2.2.1</lock4j.version> <lock4j.version>2.2.1</lock4j.version>
<dynamic-ds.version>3.4.1</dynamic-ds.version> <dynamic-ds.version>3.5.0</dynamic-ds.version>
<tlog.version>1.3.4</tlog.version> <tlog.version>1.3.6</tlog.version>
<xxl-job.version>2.3.0</xxl-job.version> <xxl-job.version>2.3.0</xxl-job.version>
<!-- jdk11 缺失依赖 jaxb--> <!-- jdk11 缺失依赖 jaxb-->
<jaxb.version>3.0.1</jaxb.version> <jaxb.version>3.0.1</jaxb.version>
<!-- OSS 配置 --> <!-- OSS 配置 -->
<qiniu.version>7.8.0</qiniu.version> <qiniu.version>7.9.0</qiniu.version>
<aliyun.oss.version>3.13.1</aliyun.oss.version> <aliyun.oss.version>3.13.1</aliyun.oss.version>
<qcloud.cos.version>5.6.58</qcloud.cos.version> <qcloud.cos.version>5.6.58</qcloud.cos.version>
<minio.version>8.3.3</minio.version> <minio.version>8.3.4</minio.version>
<!-- docker 配置 --> <!-- docker 配置 -->
<docker.registry.url>localhost</docker.registry.url> <docker.registry.url>localhost</docker.registry.url>
@ -115,6 +115,12 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib.version}</version>
</dependency>
<!-- velocity代码生成使用模板 --> <!-- velocity代码生成使用模板 -->
<dependency> <dependency>
<groupId>org.apache.velocity</groupId> <groupId>org.apache.velocity</groupId>
@ -122,13 +128,6 @@
<version>${velocity.version}</version> <version>${velocity.version}</version>
</dependency> </dependency>
<!-- Token生成与解析-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!-- jdk11 缺失依赖 jaxb--> <!-- jdk11 缺失依赖 jaxb-->
<dependency> <dependency>
<groupId>com.sun.xml.bind</groupId> <groupId>com.sun.xml.bind</groupId>
@ -162,7 +161,31 @@
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-jwt</artifactId>
<version>${hutool.version}</version> <version>${hutool.version}</version>
</dependency> </dependency>
@ -204,29 +227,13 @@
<dependency> <dependency>
<groupId>com.yomahub</groupId> <groupId>com.yomahub</groupId>
<artifactId>tlog-spring-boot-configuration</artifactId> <artifactId>tlog-web-spring-boot-starter</artifactId>
<version>${tlog.version}</version> <version>${tlog.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.yomahub</groupId> <groupId>com.yomahub</groupId>
<artifactId>tlog-webroot</artifactId> <artifactId>tlog-xxljob-spring-boot-starter</artifactId>
<version>${tlog.version}</version>
<exclusions>
<exclusion>
<artifactId>javassist</artifactId>
<groupId>org.javassist</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-xxl-job</artifactId>
<version>${tlog.version}</version> <version>${tlog.version}</version>
</dependency> </dependency>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.4.0</version> <version>3.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-extend</artifactId> <artifactId>ruoyi-extend</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-extend</artifactId> <artifactId>ruoyi-extend</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.4.0</version> <version>3.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -2,7 +2,6 @@ package com.ruoyi.monitor.admin.config;
import de.codecentric.boot.admin.server.config.AdminServerProperties; import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@ -15,7 +14,6 @@ import org.springframework.security.web.authentication.SavedRequestAwareAuthenti
*/ */
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final String adminContextPath; private final String adminContextPath;
@ -34,8 +32,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授予对所有静态资产和登录页面的公共访问权限 //授予对所有静态资产和登录页面的公共访问权限
.antMatchers(adminContextPath + "/assets/**").permitAll() .antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll() .antMatchers(adminContextPath + "/login").permitAll()
.antMatchers("/actuator").anonymous() .antMatchers("/actuator").permitAll()
.antMatchers("/actuator/**").anonymous() .antMatchers("/actuator/**").permitAll()
//必须对每个其他请求进行身份验证 //必须对每个其他请求进行身份验证
.anyRequest().authenticated().and() .anyRequest().authenticated().and()
//配置登录和注销 //配置登录和注销

View File

@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>ruoyi-extend</artifactId> <artifactId>ruoyi-extend</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.4.0</version> <version>3.5.0</version>
</parent> </parent>
<artifactId>ruoyi-xxl-job-admin</artifactId> <artifactId>ruoyi-xxl-job-admin</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
@ -102,6 +102,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration> <configuration>
<nonFilteredFileExtensions> <nonFilteredFileExtensions>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension> <nonFilteredFileExtension>ttf</nonFilteredFileExtension>

View File

@ -3,8 +3,8 @@ package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.core.util.FtlUtil; import com.xxl.job.admin.core.util.FtlUtil;
import com.xxl.job.admin.core.util.I18nUtil; import com.xxl.job.admin.core.util.I18nUtil;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -17,7 +17,7 @@ import java.util.HashMap;
* @author xuxueli 2015-12-12 18:09:04 * @author xuxueli 2015-12-12 18:09:04
*/ */
@Component @Component
public class CookieInterceptor extends HandlerInterceptorAdapter { public class CookieInterceptor implements AsyncHandlerInterceptor {
@Override @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@ -37,7 +37,7 @@ public class CookieInterceptor extends HandlerInterceptorAdapter {
modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName())); modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
} }
super.postHandle(request, response, handler, modelAndView); AsyncHandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
} }
} }

View File

@ -6,7 +6,7 @@ import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.service.LoginService; import com.xxl.job.admin.service.LoginService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -18,7 +18,7 @@ import javax.servlet.http.HttpServletResponse;
* @author xuxueli 2015-12-12 18:09:04 * @author xuxueli 2015-12-12 18:09:04
*/ */
@Component @Component
public class PermissionInterceptor extends HandlerInterceptorAdapter { public class PermissionInterceptor implements AsyncHandlerInterceptor {
@Resource @Resource
private LoginService loginService; private LoginService loginService;
@ -27,7 +27,7 @@ public class PermissionInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) { if (!(handler instanceof HandlerMethod)) {
return super.preHandle(request, response, handler); return AsyncHandlerInterceptor.super.preHandle(request, response, handler);
} }
// if need login // if need login
@ -53,7 +53,7 @@ public class PermissionInterceptor extends HandlerInterceptorAdapter {
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser); request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
} }
return super.preHandle(request, response, handler); return AsyncHandlerInterceptor.super.preHandle(request, response, handler);
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "ruoyi-vue-plus", "name": "ruoyi-vue-plus",
"version": "3.4.0", "version": "3.5.0",
"description": "RuoYi-Vue-Plus后台管理系统", "description": "RuoYi-Vue-Plus后台管理系统",
"author": "LionLi", "author": "LionLi",
"license": "MIT", "license": "MIT",
@ -38,7 +38,7 @@
"dependencies": { "dependencies": {
"@riophae/vue-treeselect": "0.4.0", "@riophae/vue-treeselect": "0.4.0",
"axios": "0.24.0", "axios": "0.24.0",
"clipboard": "2.0.6", "clipboard": "2.0.8",
"core-js": "3.19.1", "core-js": "3.19.1",
"echarts": "4.9.0", "echarts": "4.9.0",
"element-ui": "2.15.6", "element-ui": "2.15.6",
@ -65,7 +65,9 @@
"@vue/cli-plugin-eslint": "4.4.6", "@vue/cli-plugin-eslint": "4.4.6",
"@vue/cli-service": "4.4.6", "@vue/cli-service": "4.4.6",
"babel-eslint": "10.1.0", "babel-eslint": "10.1.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "4.1.0", "chalk": "4.1.0",
"compression-webpack-plugin": "5.0.2",
"connect": "3.6.6", "connect": "3.6.6",
"eslint": "7.15.0", "eslint": "7.15.0",
"eslint-plugin-vue": "7.2.0", "eslint-plugin-vue": "7.2.0",

View File

@ -2,7 +2,7 @@
<el-form size="small"> <el-form size="small">
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
允许的通配符[, - * / L M] 允许的通配符[, - * ? / L W]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -15,23 +15,23 @@
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="31" /> - <el-input-number v-model='cycle01' :min="1" :max="30" /> -
<el-input-number v-model='cycle02' :min="0" :max="31" /> <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="31" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="0" :max="31" /> 号开始 <el-input-number v-model='average01' :min="1" :max="30" /> 号开始
<el-input-number v-model='average02' :min="0" :max="31" /> 日执行一次 <el-input-number v-model='average02' :min="1" :max="31 - average01 || 1" /> 日执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="5"> <el-radio v-model='radioValue' :label="5">
每月 每月
<el-input-number v-model='workday' :min="0" :max="31" /> 号最近的那个工作日 <el-input-number v-model='workday' :min="1" :max="31" /> 号最近的那个工作日
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -72,31 +72,22 @@ export default {
// //
radioChange() { radioChange() {
('day rachange'); ('day rachange');
if (this.radioValue === 1) { if (this.radioValue !== 2 && this.cron.week !== '?') {
this.$emit('update', 'day', '*', 'day'); this.$emit('update', 'week', '?', 'day')
this.$emit('update', 'week', '?', 'day');
this.$emit('update', 'month', '*', 'day');
} else {
if (this.cron.hour === '*') {
this.$emit('update', 'hour', '0', 'day');
}
if (this.cron.min === '*') {
this.$emit('update', 'min', '0', 'day');
}
if (this.cron.second === '*') {
this.$emit('update', 'second', '0', 'day');
}
} }
switch (this.radioValue) { switch (this.radioValue) {
case 1:
this.$emit('update', 'day', '*');
break;
case 2: case 2:
this.$emit('update', 'day', '?'); this.$emit('update', 'day', '?');
break; break;
case 3: case 3:
this.$emit('update', 'day', this.cycle01 + '-' + this.cycle02); this.$emit('update', 'day', this.cycleTotal);
break; break;
case 4: case 4:
this.$emit('update', 'day', this.average01 + '/' + this.average02); this.$emit('update', 'day', this.averageTotal);
break; break;
case 5: case 5:
this.$emit('update', 'day', this.workday + 'W'); this.$emit('update', 'day', this.workday + 'W');
@ -125,7 +116,7 @@ export default {
// //
workdayChange() { workdayChange() {
if (this.radioValue == '5') { if (this.radioValue == '5') {
this.$emit('update', 'day', this.workday + 'W'); this.$emit('update', 'day', this.workdayCheck + 'W');
} }
}, },
// checkbox // checkbox
@ -133,19 +124,10 @@ export default {
if (this.radioValue == '7') { if (this.radioValue == '7') {
this.$emit('update', 'day', this.checkboxString); this.$emit('update', 'day', this.checkboxString);
} }
}, }
// week
weekChange() {
//weekday?
if (this.cron.week == '?' && this.radioValue == '2') {
this.radioValue = '1';
} else if (this.cron.week !== '?' && this.radioValue != '2') {
this.radioValue = '2';
}
},
}, },
watch: { watch: {
"radioValue": "radioChange", 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'workdayCheck': 'workdayChange', 'workdayCheck': 'workdayChange',
@ -154,20 +136,20 @@ export default {
computed: { computed: {
// //
cycleTotal: function () { cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, 1, 31) const cycle01 = this.checkNum(this.cycle01, 1, 30)
this.cycle02 = this.checkNum(this.cycle02, 1, 31) const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 31, 31)
return this.cycle01 + '-' + this.cycle02; return cycle01 + '-' + cycle02;
}, },
// //
averageTotal: function () { averageTotal: function () {
this.average01 = this.checkNum(this.average01, 1, 31) const average01 = this.checkNum(this.average01, 1, 30)
this.average02 = this.checkNum(this.average02, 1, 31) const average02 = this.checkNum(this.average02, 1, 31 - average01 || 0)
return this.average01 + '/' + this.average02; return average01 + '/' + average02;
}, },
// //
workdayCheck: function () { workdayCheck: function () {
this.workday = this.checkNum(this.workday, 1, 31) const workday = this.checkNum(this.workday, 1, 31)
return this.workday; return workday;
}, },
// checkbox // checkbox
checkboxString: function () { checkboxString: function () {

View File

@ -9,16 +9,16 @@
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="60" /> - <el-input-number v-model='cycle01' :min="0" :max="22" /> -
<el-input-number v-model='cycle02' :min="0" :max="60" /> 小时 <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="23" /> 小时
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="60" /> 小时开始 <el-input-number v-model='average01' :min="0" :max="22" /> 小时开始
<el-input-number v-model='average02' :min="0" :max="60" /> 小时执行一次 <el-input-number v-model='average02' :min="1" :max="23 - average01 || 0" /> 小时执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -51,23 +51,15 @@ export default {
methods: { methods: {
// //
radioChange() { radioChange() {
if (this.radioValue === 1) {
this.$emit('update', 'hour', '*', 'hour');
this.$emit('update', 'day', '*', 'hour');
} else {
if (this.cron.min === '*') {
this.$emit('update', 'min', '0', 'hour');
}
if (this.cron.second === '*') {
this.$emit('update', 'second', '0', 'hour');
}
}
switch (this.radioValue) { switch (this.radioValue) {
case 1:
this.$emit('update', 'hour', '*')
break;
case 2: case 2:
this.$emit('update', 'hour', this.cycle01 + '-' + this.cycle02); this.$emit('update', 'hour', this.cycleTotal);
break; break;
case 3: case 3:
this.$emit('update', 'hour', this.average01 + '/' + this.average02); this.$emit('update', 'hour', this.averageTotal);
break; break;
case 4: case 4:
this.$emit('update', 'hour', this.checkboxString); this.$emit('update', 'hour', this.checkboxString);
@ -94,7 +86,7 @@ export default {
} }
}, },
watch: { watch: {
"radioValue": "radioChange", 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange' 'checkboxString': 'checkboxChange'
@ -102,15 +94,15 @@ export default {
computed: { computed: {
// //
cycleTotal: function () { cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, 0, 23) const cycle01 = this.checkNum(this.cycle01, 0, 22)
this.cycle02 = this.checkNum(this.cycle02, 0, 23) const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 23)
return this.cycle01 + '-' + this.cycle02; return cycle01 + '-' + cycle02;
}, },
// //
averageTotal: function () { averageTotal: function () {
this.average01 = this.checkNum(this.average01, 0, 23) const average01 = this.checkNum(this.average01, 0, 22)
this.average02 = this.checkNum(this.average02, 1, 23) const average02 = this.checkNum(this.average02, 1, 23 - average01 || 0)
return this.average01 + '/' + this.average02; return average01 + '/' + average02;
}, },
// checkbox // checkbox
checkboxString: function () { checkboxString: function () {

View File

@ -2,7 +2,12 @@
<div> <div>
<el-tabs type="border-card"> <el-tabs type="border-card">
<el-tab-pane label="秒" v-if="shouldHide('second')"> <el-tab-pane label="秒" v-if="shouldHide('second')">
<CrontabSecond @update="updateCrontabValue" :check="checkNumber" ref="cronsecond" /> <CrontabSecond
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronsecond"
/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="分钟" v-if="shouldHide('min')"> <el-tab-pane label="分钟" v-if="shouldHide('min')">
@ -268,7 +273,7 @@ export default {
insValue = 5; insValue = 5;
} else { } else {
this.$refs[refName].checkboxList = value.split(","); this.$refs[refName].checkboxList = value.split(",");
insValue = 7; insValue = 6;
} }
} else if (name == "year") { } else if (name == "year") {
if (value == "") { if (value == "") {

View File

@ -9,16 +9,16 @@
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="60" /> - <el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="0" :max="60" /> 分钟 <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="60" /> 分钟开始 <el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始
<el-input-number v-model='average02' :min="0" :max="60" /> 分钟执行一次 <el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -52,19 +52,15 @@ export default {
methods: { methods: {
// //
radioChange() { radioChange() {
if (this.radioValue !== 1 && this.cron.second === '*') {
this.$emit('update', 'second', '0', 'min');
}
switch (this.radioValue) { switch (this.radioValue) {
case 1: case 1:
this.$emit('update', 'min', '*', 'min'); this.$emit('update', 'min', '*', 'min');
this.$emit('update', 'hour', '*', 'min');
break; break;
case 2: case 2:
this.$emit('update', 'min', this.cycle01 + '-' + this.cycle02, 'min'); this.$emit('update', 'min', this.cycleTotal, 'min');
break; break;
case 3: case 3:
this.$emit('update', 'min', this.average01 + '/' + this.average02, 'min'); this.$emit('update', 'min', this.averageTotal, 'min');
break; break;
case 4: case 4:
this.$emit('update', 'min', this.checkboxString, 'min'); this.$emit('update', 'min', this.checkboxString, 'min');
@ -92,7 +88,7 @@ export default {
}, },
watch: { watch: {
"radioValue": "radioChange", 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange', 'checkboxString': 'checkboxChange',
@ -100,15 +96,15 @@ export default {
computed: { computed: {
// //
cycleTotal: function () { cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, 0, 59) const cycle01 = this.checkNum(this.cycle01, 0, 58)
this.cycle02 = this.checkNum(this.cycle02, 0, 59) const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
return this.cycle01 + '-' + this.cycle02; return cycle01 + '-' + cycle02;
}, },
// //
averageTotal: function () { averageTotal: function () {
this.average01 = this.checkNum(this.average01, 0, 59) const average01 = this.checkNum(this.average01, 0, 58)
this.average02 = this.checkNum(this.average02, 1, 59) const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
return this.average01 + '/' + this.average02; return average01 + '/' + average02;
}, },
// checkbox // checkbox
checkboxString: function () { checkboxString: function () {

View File

@ -9,16 +9,16 @@
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="1" :max="12" /> - <el-input-number v-model='cycle01' :min="1" :max="11" /> -
<el-input-number v-model='cycle02' :min="1" :max="12" /> <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="12" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="1" :max="12" /> 月开始 <el-input-number v-model='average01' :min="1" :max="11" /> 月开始
<el-input-number v-model='average02' :min="1" :max="12" /> 月月执行一次 <el-input-number v-model='average02' :min="1" :max="12 - average01 || 0" /> 月月执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -51,29 +51,15 @@ export default {
methods: { methods: {
// //
radioChange() { radioChange() {
if (this.radioValue === 1) {
this.$emit('update', 'month', '*');
this.$emit('update', 'year', '*');
} else {
if (this.cron.day === '*') {
this.$emit('update', 'day', '0', 'month');
}
if (this.cron.hour === '*') {
this.$emit('update', 'hour', '0', 'month');
}
if (this.cron.min === '*') {
this.$emit('update', 'min', '0', 'month');
}
if (this.cron.second === '*') {
this.$emit('update', 'second', '0', 'month');
}
}
switch (this.radioValue) { switch (this.radioValue) {
case 1:
this.$emit('update', 'month', '*');
break;
case 2: case 2:
this.$emit('update', 'month', this.cycle01 + '-' + this.cycle02); this.$emit('update', 'month', this.cycleTotal);
break; break;
case 3: case 3:
this.$emit('update', 'month', this.average01 + '/' + this.average02); this.$emit('update', 'month', this.averageTotal);
break; break;
case 4: case 4:
this.$emit('update', 'month', this.checkboxString); this.$emit('update', 'month', this.checkboxString);
@ -100,7 +86,7 @@ export default {
} }
}, },
watch: { watch: {
"radioValue": "radioChange", 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange' 'checkboxString': 'checkboxChange'
@ -108,15 +94,15 @@ export default {
computed: { computed: {
// //
cycleTotal: function () { cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, 1, 12) const cycle01 = this.checkNum(this.cycle01, 1, 11)
this.cycle02 = this.checkNum(this.cycle02, 1, 12) const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 12)
return this.cycle01 + '-' + this.cycle02; return cycle01 + '-' + cycle02;
}, },
// //
averageTotal: function () { averageTotal: function () {
this.average01 = this.checkNum(this.average01, 1, 12) const average01 = this.checkNum(this.average01, 1, 11)
this.average02 = this.checkNum(this.average02, 1, 12) const average02 = this.checkNum(this.average02, 1, 12 - average01 || 0)
return this.average01 + '/' + this.average02; return average01 + '/' + average02;
}, },
// checkbox // checkbox
checkboxString: function () { checkboxString: function () {

View File

@ -179,7 +179,7 @@ export default {
// X // X
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week'); let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
// //
if (thisWeek == 0) { if (thisWeek == 1) {
// //
DD++; DD++;
thisDD = DD < 10 ? '0' + DD : DD; thisDD = DD < 10 ? '0' + DD : DD;
@ -187,7 +187,7 @@ export default {
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD -= 3; DD -= 3;
} }
} else if (thisWeek == 6) { } else if (thisWeek == 7) {
// 61 // 61
if (this.dayRuleSup !== 1) { if (this.dayRuleSup !== 1) {
DD--; DD--;
@ -200,7 +200,7 @@ export default {
// //
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week'); let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
// dayRuleSup // dayRuleSup
if (Array.indexOf(this.dayRuleSup, thisWeek) < 0) { if (this.dayRuleSup.indexOf(thisWeek) < 0) {
// //
if (Di == DDate.length - 1) { if (Di == DDate.length - 1) {
resetDay(); resetDay();
@ -385,7 +385,7 @@ export default {
} else if (rule.indexOf('#') >= 0) { } else if (rule.indexOf('#') >= 0) {
this.dayRule = 'assWeek'; this.dayRule = 'assWeek';
let matchRule = rule.match(/[0-9]{1}/g); let matchRule = rule.match(/[0-9]{1}/g);
this.dayRuleSup = [Number(matchRule[0]), Number(matchRule[1])]; this.dayRuleSup = [Number(matchRule[1]), Number(matchRule[0])];
this.dateArr[3] = [1]; this.dateArr[3] = [1];
if (this.dayRuleSup[1] == 7) { if (this.dayRuleSup[1] == 7) {
this.dayRuleSup[1] = 0; this.dayRuleSup[1] = 0;
@ -401,14 +401,6 @@ export default {
this.dayRule = 'weekDay'; this.dayRule = 'weekDay';
this.dayRuleSup = this.getAssignArr(rule) this.dayRuleSup = this.getAssignArr(rule)
} }
// weekDay70week0
if (this.dayRule == 'weekDay') {
for (let i = 0; i < this.dayRuleSup.length; i++) {
if (this.dayRuleSup[i] == 7) {
this.dayRuleSup[i] = 0;
}
}
}
} }
}, },
// ""- // ""-
@ -543,14 +535,15 @@ export default {
if (type == undefined) { if (type == undefined) {
return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s); return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
} else if (type == 'week') { } else if (type == 'week') {
return week; // quartz 1
return week + 1;
} }
}, },
// //
checkDate(value) { checkDate(value) {
let time = new Date(value); let time = new Date(value);
let format = this.formatDate(time) let format = this.formatDate(time)
return value == format ? true : false; return value === format;
} }
}, },
watch: { watch: {

View File

@ -9,16 +9,16 @@
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="60" /> - <el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="0" :max="60" /> <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="60" /> 秒开始 <el-input-number v-model='average01' :min="0" :max="58" /> 秒开始
<el-input-number v-model='average02' :min="0" :max="60" /> 秒执行一次 <el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 秒执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -54,13 +54,12 @@ export default {
switch (this.radioValue) { switch (this.radioValue) {
case 1: case 1:
this.$emit('update', 'second', '*', 'second'); this.$emit('update', 'second', '*', 'second');
this.$emit('update', 'min', '*', 'second');
break; break;
case 2: case 2:
this.$emit('update', 'second', this.cycle01 + '-' + this.cycle02); this.$emit('update', 'second', this.cycleTotal);
break; break;
case 3: case 3:
this.$emit('update', 'second', this.average01 + '/' + this.average02); this.$emit('update', 'second', this.averageTotal);
break; break;
case 4: case 4:
this.$emit('update', 'second', this.checkboxString); this.$emit('update', 'second', this.checkboxString);
@ -84,25 +83,10 @@ export default {
if (this.radioValue == '4') { if (this.radioValue == '4') {
this.$emit('update', 'second', this.checkboxString); this.$emit('update', 'second', this.checkboxString);
} }
},
othChange() {
//
let ins = this.cron.second
('反解析 second', ins);
if (ins === '*') {
this.radioValue = 1;
} else if (ins.indexOf('-') > -1) {
this.radioValue = 2
} else if (ins.indexOf('/') > -1) {
this.radioValue = 3
} else {
this.radioValue = 4
this.checkboxList = ins.split(',')
}
} }
}, },
watch: { watch: {
"radioValue": "radioChange", 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange', 'checkboxString': 'checkboxChange',
@ -113,15 +97,15 @@ export default {
computed: { computed: {
// //
cycleTotal: function () { cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, 0, 59) const cycle01 = this.checkNum(this.cycle01, 0, 58)
this.cycle02 = this.checkNum(this.cycle02, 0, 59) const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
return this.cycle01 + '-' + this.cycle02; return cycle01 + '-' + cycle02;
}, },
// //
averageTotal: function () { averageTotal: function () {
this.average01 = this.checkNum(this.average01, 0, 59) const average01 = this.checkNum(this.average01, 0, 58)
this.average02 = this.checkNum(this.average02, 1, 59) const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
return this.average01 + '/' + this.average02; return average01 + '/' + average02;
}, },
// checkbox // checkbox
checkboxString: function () { checkboxString: function () {

View File

@ -2,7 +2,7 @@
<el-form size='small'> <el-form size='small'>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
允许的通配符[, - * / L #] 允许的通配符[, - * ? / L #]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -15,8 +15,25 @@
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
周期从星期 周期从星期
<el-input-number v-model='cycle01' :min="1" :max="7" /> - <el-select clearable v-model="cycle01">
<el-input-number v-model='cycle02' :min="1" :max="7" /> <el-option
v-for="(item,index) of weekList"
:key="index"
:label="item.value"
:value="item.key"
:disabled="item.key === 1"
>{{item.value}}</el-option>
</el-select>
-
<el-select clearable v-model="cycle02">
<el-option
v-for="(item,index) of weekList"
:key="index"
:label="item.value"
:value="item.key"
:disabled="item.key < cycle01 && item.key !== 1"
>{{item.value}}</el-option>
</el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -24,14 +41,18 @@
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="4" /> 周的星期 <el-input-number v-model='average01' :min="1" :max="4" /> 周的星期
<el-input-number v-model='average02' :min="1" :max="7" /> <el-select clearable v-model="average02">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option>
</el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="5"> <el-radio v-model='radioValue' :label="5">
本月最后一个星期 本月最后一个星期
<el-input-number v-model='weekday' :min="1" :max="7" /> <el-select clearable v-model="weekday">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option>
</el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -39,7 +60,7 @@
<el-radio v-model='radioValue' :label="6"> <el-radio v-model='radioValue' :label="6">
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="(item,index) of weekList" :key="index" :value="index+1">{{item}}</el-option> <el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="String(item.key)">{{item.value}}</el-option>
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -52,13 +73,42 @@ export default {
data() { data() {
return { return {
radioValue: 2, radioValue: 2,
weekday: 1, weekday: 2,
cycle01: 1, cycle01: 2,
cycle02: 2, cycle02: 3,
average01: 1, average01: 1,
average02: 1, average02: 2,
checkboxList: [], checkboxList: [],
weekList: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], weekList: [
{
key: 2,
value: '星期一'
},
{
key: 3,
value: '星期二'
},
{
key: 4,
value: '星期三'
},
{
key: 5,
value: '星期四'
},
{
key: 6,
value: '星期五'
},
{
key: 7,
value: '星期六'
},
{
key: 1,
value: '星期日'
}
],
checkNum: this.$options.propsData.check checkNum: this.$options.propsData.check
} }
}, },
@ -67,45 +117,30 @@ export default {
methods: { methods: {
// //
radioChange() { radioChange() {
if (this.radioValue === 1) { if (this.radioValue !== 2 && this.cron.day !== '?') {
this.$emit('update', 'week', '*'); this.$emit('update', 'day', '?', 'week');
this.$emit('update', 'year', '*');
} else {
if (this.cron.month === '*') {
this.$emit('update', 'month', '0', 'week');
}
if (this.cron.day === '*') {
this.$emit('update', 'day', '0', 'week');
}
if (this.cron.hour === '*') {
this.$emit('update', 'hour', '0', 'week');
}
if (this.cron.min === '*') {
this.$emit('update', 'min', '0', 'week');
}
if (this.cron.second === '*') {
this.$emit('update', 'second', '0', 'week');
}
} }
switch (this.radioValue) { switch (this.radioValue) {
case 1:
this.$emit('update', 'week', '*');
break;
case 2: case 2:
this.$emit('update', 'week', '?'); this.$emit('update', 'week', '?');
break; break;
case 3: case 3:
this.$emit('update', 'week', this.cycle01 + '-' + this.cycle02); this.$emit('update', 'week', this.cycleTotal);
break; break;
case 4: case 4:
this.$emit('update', 'week', this.average01 + '#' + this.average02); this.$emit('update', 'week', this.averageTotal);
break; break;
case 5: case 5:
this.$emit('update', 'week', this.weekday + 'L'); this.$emit('update', 'week', this.weekdayCheck + 'L');
break; break;
case 6: case 6:
this.$emit('update', 'week', this.checkboxString); this.$emit('update', 'week', this.checkboxString);
break; break;
} }
}, },
// radio
// //
cycleChange() { cycleChange() {
@ -133,7 +168,7 @@ export default {
}, },
}, },
watch: { watch: {
"radioValue": "radioChange", 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'weekdayCheck': 'weekdayChange', 'weekdayCheck': 'weekdayChange',
@ -150,7 +185,7 @@ export default {
averageTotal: function () { averageTotal: function () {
this.average01 = this.checkNum(this.average01, 1, 4) this.average01 = this.checkNum(this.average01, 1, 4)
this.average02 = this.checkNum(this.average02, 1, 7) this.average02 = this.checkNum(this.average02, 1, 7)
return this.average01 + '#' + this.average02; return this.average02 + '#' + this.average01;
}, },
// //
weekdayCheck: function () { weekdayCheck: function () {

View File

@ -15,16 +15,16 @@
<el-form-item> <el-form-item>
<el-radio :label="3" v-model='radioValue'> <el-radio :label="3" v-model='radioValue'>
周期从 周期从
<el-input-number v-model='cycle01' :min='fullYear' /> - <el-input-number v-model='cycle01' :min='fullYear' :max="2098" /> -
<el-input-number v-model='cycle02' :min='fullYear' /> <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : fullYear + 1" :max="2099" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio :label="4" v-model='radioValue'> <el-radio :label="4" v-model='radioValue'>
<el-input-number v-model='average01' :min='fullYear' /> 年开始 <el-input-number v-model='average01' :min='fullYear' :max="2098"/> 年开始
<el-input-number v-model='average02' :min='fullYear' /> 年执行一次 <el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear" /> 年执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
@ -59,21 +59,6 @@ export default {
methods: { methods: {
// //
radioChange() { radioChange() {
if (this.cron.month === '*') {
this.$emit('update', 'month', '0', 'year');
}
if (this.cron.day === '*') {
this.$emit('update', 'day', '0', 'year');
}
if (this.cron.hour === '*') {
this.$emit('update', 'hour', '0', 'year');
}
if (this.cron.min === '*') {
this.$emit('update', 'min', '0', 'year');
}
if (this.cron.second === '*') {
this.$emit('update', 'second', '0', 'year');
}
switch (this.radioValue) { switch (this.radioValue) {
case 1: case 1:
this.$emit('update', 'year', ''); this.$emit('update', 'year', '');
@ -82,10 +67,10 @@ export default {
this.$emit('update', 'year', '*'); this.$emit('update', 'year', '*');
break; break;
case 3: case 3:
this.$emit('update', 'year', this.cycle01 + '-' + this.cycle02); this.$emit('update', 'year', this.cycleTotal);
break; break;
case 4: case 4:
this.$emit('update', 'year', this.average01 + '/' + this.average02); this.$emit('update', 'year', this.averageTotal);
break; break;
case 5: case 5:
this.$emit('update', 'year', this.checkboxString); this.$emit('update', 'year', this.checkboxString);
@ -112,7 +97,7 @@ export default {
} }
}, },
watch: { watch: {
"radioValue": "radioChange", 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange' 'checkboxString': 'checkboxChange'
@ -120,15 +105,15 @@ export default {
computed: { computed: {
// //
cycleTotal: function () { cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, this.fullYear, this.fullYear + 100) const cycle01 = this.checkNum(this.cycle01, this.fullYear, 2098)
this.cycle02 = this.checkNum(this.cycle02, this.fullYear + 1, this.fullYear + 101) const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : this.fullYear + 1, 2099)
return this.cycle01 + '-' + this.cycle02; return cycle01 + '-' + cycle02;
}, },
// //
averageTotal: function () { averageTotal: function () {
this.average01 = this.checkNum(this.average01, this.fullYear, this.fullYear + 100) const average01 = this.checkNum(this.average01, this.fullYear, 2098)
this.average02 = this.checkNum(this.average02, 1, 10) const average02 = this.checkNum(this.average02, 1, 2099 - average01 || this.fullYear)
return this.average01 + '/' + this.average02; return average01 + '/' + average02;
}, },
// checkbox // checkbox
checkboxString: function () { checkboxString: function () {
@ -139,6 +124,8 @@ export default {
mounted: function () { mounted: function () {
// //
this.fullYear = Number(new Date().getFullYear()); this.fullYear = Number(new Date().getFullYear());
this.cycle01 = this.fullYear
this.average01 = this.fullYear
} }
} }
</script> </script>

View File

@ -0,0 +1,67 @@
<template>
<el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="[`${realSrc}`]">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
name: 'ImagePreview',
props: {
src: {
type: String,
required: true
},
width: {
type: [Number, String],
default: ''
},
height: {
type: [Number, String],
default: ''
}
},
computed: {
realSrc() {
if (isExternal(this.src)) {
return this.src
}
return process.env.VUE_APP_BASE_API + this.src
},
realWidth() {
return typeof this.width == 'string' ? this.width : `${this.width}px`
},
realHeight() {
return typeof this.height == 'string' ? this.height : `${this.height}px`
}
}
}
</script>
<style lang="scss" scoped>
.el-image {
border-radius: 5px;
background-color: #ebeef5;
box-shadow: 0 0 5px 1px #ccc;
::v-deep .el-image__inner {
transition: all 0.3s;
cursor: pointer;
&:hover {
transform: scale(1.2);
}
}
::v-deep .image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: #909399;
font-size: 30px;
}
}
</style>

View File

@ -3,10 +3,12 @@ import hasPermi from './permission/hasPermi'
import dialogDrag from './dialog/drag' import dialogDrag from './dialog/drag'
import dialogDragWidth from './dialog/dragWidth' import dialogDragWidth from './dialog/dragWidth'
import dialogDragHeight from './dialog/dragHeight' import dialogDragHeight from './dialog/dragHeight'
import clipboard from './module/clipboard'
const install = function(Vue) { const install = function(Vue) {
Vue.directive('hasRole', hasRole) Vue.directive('hasRole', hasRole)
Vue.directive('hasPermi', hasPermi) Vue.directive('hasPermi', hasPermi)
Vue.directive('clipboard', clipboard)
Vue.directive('dialogDrag', dialogDrag) Vue.directive('dialogDrag', dialogDrag)
Vue.directive('dialogDragWidth', dialogDragWidth) Vue.directive('dialogDragWidth', dialogDragWidth)
Vue.directive('dialogDragHeight', dialogDragHeight) Vue.directive('dialogDragHeight', dialogDragHeight)

View File

@ -0,0 +1,54 @@
/**
* v-clipboard 文字复制剪贴
* Copyright (c) 2021 ruoyi
*/
import Clipboard from 'clipboard'
export default {
bind(el, binding, vnode) {
switch (binding.arg) {
case 'success':
el._vClipBoard_success = binding.value;
break;
case 'error':
el._vClipBoard_error = binding.value;
break;
default: {
const clipboard = new Clipboard(el, {
text: () => binding.value,
action: () => binding.arg === 'cut' ? 'cut' : 'copy'
});
clipboard.on('success', e => {
const callback = el._vClipBoard_success;
callback && callback(e);
});
clipboard.on('error', e => {
const callback = el._vClipBoard_error;
callback && callback(e);
});
el._vClipBoard = clipboard;
}
}
},
update(el, binding) {
if (binding.arg === 'success') {
el._vClipBoard_success = binding.value;
} else if (binding.arg === 'error') {
el._vClipBoard_error = binding.value;
} else {
el._vClipBoard.text = function () { return binding.value; };
el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy';
}
},
unbind(el, binding) {
if (!el._vClipboard) return
if (binding.arg === 'success') {
delete el._vClipBoard_success;
} else if (binding.arg === 'error') {
delete el._vClipBoard_error;
} else {
el._vClipBoard.destroy();
delete el._vClipBoard;
}
}
}

View File

@ -29,6 +29,8 @@ import Editor from "@/components/Editor"
import FileUpload from "@/components/FileUpload" import FileUpload from "@/components/FileUpload"
// 图片上传组件 // 图片上传组件
import ImageUpload from "@/components/ImageUpload" import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
// 字典标签组件 // 字典标签组件
import DictTag from '@/components/DictTag' import DictTag from '@/components/DictTag'
// 头部标签组件 // 头部标签组件
@ -54,6 +56,7 @@ Vue.component('RightToolbar', RightToolbar)
Vue.component('Editor', Editor) Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload) Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload) Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.use(directive) Vue.use(directive)
Vue.use(plugins) Vue.use(plugins)

View File

@ -1,14 +1,17 @@
import axios from 'axios' import axios from 'axios'
import { Message } from 'element-ui' import {Loading, Message} from 'element-ui'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { blobValidate } from "@/utils/ruoyi"; import { blobValidate } from "@/utils/ruoyi";
const baseURL = process.env.VUE_APP_BASE_API const baseURL = process.env.VUE_APP_BASE_API
let downloadLoadingInstance;
export default { export default {
oss(ossId) { oss(ossId) {
var url = baseURL + '/system/oss/download/' + ossId var url = baseURL + '/system/oss/download/' + ossId
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
axios({ axios({
method: 'get', method: 'get',
url: url, url: url,
@ -20,8 +23,13 @@ export default {
const blob = new Blob([res.data], { type: 'application/octet-stream' }) const blob = new Blob([res.data], { type: 'application/octet-stream' })
this.saveAs(blob, decodeURI(res.headers['download-filename'])) this.saveAs(blob, decodeURI(res.headers['download-filename']))
} else { } else {
Message.error('无效的会话,或者会话已过期,请重新登录。'); this.printErrMsg(res.data);
} }
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
Message.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
}) })
}, },
zip(url, name) { zip(url, name) {
@ -37,12 +45,18 @@ export default {
const blob = new Blob([res.data], { type: 'application/zip' }) const blob = new Blob([res.data], { type: 'application/zip' })
this.saveAs(blob, name) this.saveAs(blob, name)
} else { } else {
Message.error('无效的会话,或者会话已过期,请重新登录。'); this.printErrMsg(res.data);
} }
}) })
}, },
saveAs(text, name, opts) { saveAs(text, name, opts) {
saveAs(text, name, opts); saveAs(text, name, opts);
},
async printErrMsg(data) {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
Message.error(errMsg);
} }
} }

View File

@ -59,6 +59,14 @@ export default {
type: "warning", type: "warning",
}) })
}, },
// 提交内容
prompt(content) {
return MessageBox.prompt(content, "系统提示", {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: "warning",
})
},
// 打开遮罩层 // 打开遮罩层
loading(content) { loading(content) {
loadingInstance = Loading.service({ loadingInstance = Loading.service({

View File

@ -17,6 +17,8 @@ import Layout from '@/layout'
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题 * name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
* roles: ['admin', 'common'] // 访问路由的角色权限
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
* meta : { * meta : {
noCache: true // 如果设置为true则不会被 <keep-alive> 缓存(默认 false) noCache: true // 如果设置为true则不会被 <keep-alive> 缓存(默认 false)
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
@ -35,28 +37,28 @@ export const constantRoutes = [
children: [ children: [
{ {
path: '/redirect/:path(.*)', path: '/redirect/:path(.*)',
component: (resolve) => require(['@/views/redirect'], resolve) component: () => import('@/views/redirect')
} }
] ]
}, },
{ {
path: '/login', path: '/login',
component: (resolve) => require(['@/views/login'], resolve), component: () => import('@/views/login'),
hidden: true hidden: true
}, },
{ {
path: '/register', path: '/register',
component: (resolve) => require(['@/views/register'], resolve), component: () => import('@/views/register'),
hidden: true hidden: true
}, },
{ {
path: '/404', path: '/404',
component: (resolve) => require(['@/views/error/404'], resolve), component: () => import('@/views/error/404'),
hidden: true hidden: true
}, },
{ {
path: '/401', path: '/401',
component: (resolve) => require(['@/views/error/401'], resolve), component: () => import('@/views/error/401'),
hidden: true hidden: true
}, },
{ {
@ -66,7 +68,7 @@ export const constantRoutes = [
children: [ children: [
{ {
path: 'index', path: 'index',
component: (resolve) => require(['@/views/index'], resolve), component: () => import('@/views/index'),
name: 'Index', name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true } meta: { title: '首页', icon: 'dashboard', affix: true }
} }
@ -80,20 +82,25 @@ export const constantRoutes = [
children: [ children: [
{ {
path: 'profile', path: 'profile',
component: (resolve) => require(['@/views/system/user/profile/index'], resolve), component: () => import('@/views/system/user/profile/index'),
name: 'Profile', name: 'Profile',
meta: { title: '个人中心', icon: 'user' } meta: { title: '个人中心', icon: 'user' }
} }
] ]
}, }
]
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes = [
{ {
path: '/system/user-auth', path: '/system/user-auth',
component: Layout, component: Layout,
hidden: true, hidden: true,
permissions: ['system:user:edit'],
children: [ children: [
{ {
path: 'role/:userId(\\d+)', path: 'role/:userId(\\d+)',
component: (resolve) => require(['@/views/system/user/authRole'], resolve), component: () => import('@/views/system/user/authRole'),
name: 'AuthRole', name: 'AuthRole',
meta: { title: '分配角色', activeMenu: '/system/user' } meta: { title: '分配角色', activeMenu: '/system/user' }
} }
@ -103,10 +110,11 @@ export const constantRoutes = [
path: '/system/role-auth', path: '/system/role-auth',
component: Layout, component: Layout,
hidden: true, hidden: true,
permissions: ['system:role:edit'],
children: [ children: [
{ {
path: 'user/:roleId(\\d+)', path: 'user/:roleId(\\d+)',
component: (resolve) => require(['@/views/system/role/authUser'], resolve), component: () => import('@/views/system/role/authUser'),
name: 'AuthUser', name: 'AuthUser',
meta: { title: '分配用户', activeMenu: '/system/role' } meta: { title: '分配用户', activeMenu: '/system/role' }
} }
@ -116,10 +124,11 @@ export const constantRoutes = [
path: '/system/dict-data', path: '/system/dict-data',
component: Layout, component: Layout,
hidden: true, hidden: true,
permissions: ['system:dict:list'],
children: [ children: [
{ {
path: 'index/:dictId(\\d+)', path: 'index/:dictId(\\d+)',
component: (resolve) => require(['@/views/system/dict/data'], resolve), component: () => import('@/views/system/dict/data'),
name: 'Data', name: 'Data',
meta: { title: '字典数据', activeMenu: '/system/dict' } meta: { title: '字典数据', activeMenu: '/system/dict' }
} }
@ -129,12 +138,13 @@ export const constantRoutes = [
path: '/system/oss-config', path: '/system/oss-config',
component: Layout, component: Layout,
hidden: true, hidden: true,
permissions: ['system:oss:list'],
children: [ children: [
{ {
path: 'index', path: 'index',
component: (resolve) => require(['@/views/system/oss/config'], resolve), component: () => import('@/views/system/oss/config'),
name: 'OssConfig', name: 'OssConfig',
meta: { title: '配置管理', activeMenu: '/system/oss'} meta: { title: '配置管理', activeMenu: '/system/oss' }
} }
] ]
}, },
@ -142,10 +152,11 @@ export const constantRoutes = [
path: '/tool/gen-edit', path: '/tool/gen-edit',
component: Layout, component: Layout,
hidden: true, hidden: true,
permissions: ['tool:gen:edit'],
children: [ children: [
{ {
path: 'index', path: 'index',
component: (resolve) => require(['@/views/tool/gen/editTable'], resolve), component: () => import('@/views/tool/gen/editTable'),
name: 'GenEdit', name: 'GenEdit',
meta: { title: '修改生成配置', activeMenu: '/tool/gen' } meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
} }

View File

@ -1,4 +1,5 @@
import { constantRoutes } from '@/router' import auth from '@/plugins/auth'
import router, { constantRoutes, dynamicRoutes } from '@/router'
import { getRouters } from '@/api/menu' import { getRouters } from '@/api/menu'
import Layout from '@/layout/index' import Layout from '@/layout/index'
import ParentView from '@/components/ParentView' import ParentView from '@/components/ParentView'
@ -42,7 +43,9 @@ const permission = {
const rdata = JSON.parse(JSON.stringify(res.data)) const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata) const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true) const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
router.addRoutes(asyncRoutes);
commit('SET_ROUTES', rewriteRoutes) commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes) commit('SET_DEFAULT_ROUTES', sidebarRoutes)
@ -106,6 +109,23 @@ function filterChildren(childrenMap, lastRouter = false) {
return children return children
} }
// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {
const res = []
routes.forEach(route => {
if (route.permissions) {
if (auth.hasPermiOr(route.permissions)) {
res.push(route)
}
} else if (route.roles) {
if (auth.hasRoleOr(route.roles)) {
res.push(route)
}
}
})
return res
}
export const loadView = (view) => { export const loadView = (view) => {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
return (resolve) => require([`@/views/${view}`], resolve) return (resolve) => require([`@/views/${view}`], resolve)

View File

@ -110,7 +110,10 @@ export function download(url, params, filename) {
const blob = new Blob([data]) const blob = new Blob([data])
saveAs(blob, filename) saveAs(blob, filename)
} else { } else {
Message.error('无效的会话,或者会话已过期,请重新登录。'); const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
Message.error(errMsg);
} }
downloadLoadingInstance.close(); downloadLoadingInstance.close();
}).catch((r) => { }).catch((r) => {

View File

@ -101,6 +101,62 @@
<span>更新日志</span> <span>更新日志</span>
</div> </div>
<el-collapse accordion> <el-collapse accordion>
<el-collapse-item title="v3.5.0 - 2021-12-28">
<ol>
<li>[重大更新] 重写数据权限实现</li>
<li>[重磅更新] 重构分页 简化使用</li>
<li>[重磅更新] 用户登录 支持校验错误次数锁定登录</li>
<li>[重磅更新] 增加 jdbc 批处理参数 大幅提升批量操作性能 对原生语句与 MP 均有效</li>
<li>update springboot 2.5.7 => 2.5.8 升级预防 log4j2 问题</li>
<li>update springboot-admin 2.5.4 => 2.5.5</li>
<li>update hutool 5.7.16 => 5.7.18</li>
<li>update redisson 3.16.4 => 3.16.7</li>
<li>update dynamic-ds 3.4.1 => 3.5.0</li>
<li>update qiniu 7.8.0 => 7.9.0</li>
<li>update minio 8.3.3 => 8.3.4</li>
<li>update tlog 1.3.4 => 1.3.6 启用 tlog 自动配置</li>
<li>update clipboard 2.0.6 => 2.0.8</li>
<li>update 多数据源切换标注过期 3.6.0 移除 推荐使用原生注解</li>
<li>update 通用权限服务 迁移回 ruoyi-framework 模块</li>
<li>update 使用 hutool-jwt 替换老旧 jjwt 依赖</li>
<li>update 调整 OSS 表字段内容长度</li>
<li>update LoginUser 增加角色缓存 优化角色权限代码</li>
<li>update 使用 Cglib 重构 BeanCopyUtils 性能优异</li>
<li>update 禁止所有工具类实例化 优化代码书写规范</li>
<li>update 优化查询用户的角色组岗位组代码</li>
<li>update 更新 RedisUtils 返回客户端实例</li>
<li>update 修改 健康检查权限 改为用户放行 提高安全性</li>
<li>update hutool 工具 改为单包引入 减少无用依赖</li>
<li>update ServicePlusImpl 功能 下沉到 BaseMapperPlus</li>
<li>update 去除 jdk17 标签 由于很多组件还未适配 导致一些问题</li>
<li>udpate 代码生成预览支持复制内容</li>
<li>update 用户导入提示溢出则显示滚动条</li>
<li>update 路由支持单独配置菜单或角色权限</li>
<li>update 优化web拦截器 使用原生接口处理 默认非生产环境开启</li>
<li>update 调整监控依赖 common 迁移到 framework</li>
<li>add 新增 Vue3 分支 代码生成模板(由于组件还未完善 仅供学习)</li>
<li>add 增加 RedisUtils 注册监听器方法</li>
<li>add 增加 自定义 Xss 校验注解 用户导入增加 Bean 校验</li>
<li>add oss下载增加 loading </li>
<li>add 新增图片预览组件</li>
<li>add 集成compression-webpack-plugin插件实现打包Gzip压缩</li>
<li>add 新增 SqlUtils 检查关键字方法</li>
<li>fix 修复 集群雪花id重复问题 使用网卡信息绑定生成</li>
<li>fix 修复 count 语法异常</li>
<li>fix 修复更改密码问题</li>
<li>fix 修复sql关键字处理 防止解析器报错</li>
<li>fix 修复 TreeBuildUtils 顶节点不为 0 问题</li>
<li>fix 修复 SysOssConfig 主键类型错误</li>
<li>fix 修复代码生成 导出注解错误</li>
<li>fix 修复 redisson 集群模式 路径未匹配协议头问题</li>
<li>fix 修复打包后字体图标偶现的乱码问题</li>
<li>fix 修复版本差异导致的懒加载报错问题</li>
<li>fix 修复代码生成字典组重复问题</li>
<li>remove 删除 jjwt 无用依赖</li>
<li>remove 移除过期 用户导入</li>
<li>remove 移除过期工具 DictUtils</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.4.0 - 2021-11-29"> <el-collapse-item title="v3.4.0 - 2021-11-29">
<ol> <ol>
<li>update [重磅更新] 重构 Excel 导入 支持 Validator 校验 支持自定义监听器</li> <li>update [重磅更新] 重构 Excel 导入 支持 Validator 校验 支持自定义监听器</li>
@ -588,7 +644,7 @@ export default {
data() { data() {
return { return {
// //
version: "3.4.0", version: "3.5.0",
}; };
}, },
methods: { methods: {

View File

@ -111,7 +111,7 @@ export default {
}, },
/** 强退按钮操作 */ /** 强退按钮操作 */
handleForceLogout(row) { handleForceLogout(row) {
this.$modal.confirm('是否确认强退名称为"' + row.userName + '"的数据项').then(function() { this.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户').then(function() {
return forceLogout(row.tokenId); return forceLogout(row.tokenId);
}).then(() => { }).then(() => {
this.getList(); this.getList();

View File

@ -128,7 +128,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" v-if="form.menuType != 'F'"> <el-col :span="24" v-if="form.menuType != 'F'">
<el-form-item label="菜单图标"> <el-form-item label="菜单图标" prop="icon">
<el-popover <el-popover
placement="bottom-start" placement="bottom-start"
width="460" width="460"

View File

@ -9,7 +9,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8" :offset="2"> <el-col :span="8" :offset="2">
<el-form-item label="登录账号" prop="phonenumber"> <el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled /> <el-input v-model="form.userName" disabled />
</el-form-item> </el-form-item>
</el-col> </el-col>

View File

@ -596,7 +596,7 @@ export default {
cancelButtonText: "取消", cancelButtonText: "取消",
closeOnClickModal: false, closeOnClickModal: false,
inputPattern: /^.{5,20}$/, inputPattern: /^.{5,20}$/,
inputErrorMessage: "用户密码长度必须介于 5 和 20 之间", inputErrorMessage: "用户密码长度必须介于 5 和 20 之间"
}).then(({ value }) => { }).then(({ value }) => {
resetUserPwd(row.userId, value).then(response => { resetUserPwd(row.userId, value).then(response => {
this.$modal.msgSuccess("修改成功,新密码是:" + value); this.$modal.msgSuccess("修改成功,新密码是:" + value);
@ -663,7 +663,7 @@ export default {
this.upload.open = false; this.upload.open = false;
this.upload.isUploading = false; this.upload.isUploading = false;
this.$refs.upload.clearFiles(); this.$refs.upload.clearFiles();
this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true }); this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
this.getList(); this.getList();
}, },
// //

View File

@ -11,7 +11,6 @@
<el-input placeholder="请输入" v-model="info.tableComment" /> <el-input placeholder="请输入" v-model="info.tableComment" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="实体类名称" prop="className"> <el-form-item label="实体类名称" prop="className">
<el-input placeholder="请输入" v-model="info.className" /> <el-input placeholder="请输入" v-model="info.className" />
@ -30,9 +29,9 @@
</el-row> </el-row>
</el-form> </el-form>
</template> </template>
<script> <script>
export default { export default {
name: "BasicInfoForm",
props: { props: {
info: { info: {
type: Object, type: Object,

View File

@ -124,6 +124,7 @@
</el-form> </el-form>
</el-card> </el-card>
</template> </template>
<script> <script>
import { getGenTable, updateGenTable } from "@/api/tool/gen"; import { getGenTable, updateGenTable } from "@/api/tool/gen";
import { optionselect as getDictOptionselect } from "@/api/system/dict/type"; import { optionselect as getDictOptionselect } from "@/api/system/dict/type";

View File

@ -11,7 +11,6 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="packageName"> <el-form-item prop="packageName">
<span slot="label"> <span slot="label">
@ -213,12 +212,12 @@
</el-row> </el-row>
</el-form> </el-form>
</template> </template>
<script> <script>
import Treeselect from "@riophae/vue-treeselect"; import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default { export default {
name: "BasicInfoForm",
components: { Treeselect }, components: { Treeselect },
props: { props: {
info: { info: {

View File

@ -169,7 +169,8 @@
:name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))" :name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
:key="key" :key="key"
> >
<pre><code class="hljs" v-html="highlightedCode(value, key)"></code></pre> <el-link :underline="false" icon="el-icon-document-copy" v-clipboard:copy="value" v-clipboard:success="clipboardSuccess" style="float:right">复制</el-link>
<pre><code class="hljs" v-html="highlightedCode(value, key)"></code></pre>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-dialog> </el-dialog>
@ -306,6 +307,10 @@ export default {
const result = hljs.highlight(language, code || "", true); const result = hljs.highlight(language, code || "", true);
return result.value || '&nbsp;'; return result.value || '&nbsp;';
}, },
/** 复制代码成功 */
clipboardSuccess(){
this.$modal.msgSuccess("复制成功");
},
// //
handleSelectionChange(selection) { handleSelectionChange(selection) {
this.ids = selection.map(item => item.tableId); this.ids = selection.map(item => item.tableId);

View File

@ -5,6 +5,8 @@ function resolve(dir) {
return path.join(__dirname, dir) return path.join(__dirname, dir)
} }
const CompressionPlugin = require('compression-webpack-plugin')
const name = process.env.VUE_APP_TITLE || 'RuoYi-Vue-Plus后台管理系统' // 网页标题 const name = process.env.VUE_APP_TITLE || 'RuoYi-Vue-Plus后台管理系统' // 网页标题
const port = process.env.port || process.env.npm_config_port || 80 // 端口 const port = process.env.port || process.env.npm_config_port || 80 // 端口
@ -42,13 +44,29 @@ module.exports = {
}, },
disableHostCheck: true disableHostCheck: true
}, },
css: {
loaderOptions: {
sass: {
sassOptions: { outputStyle: "expanded" }
}
}
},
configureWebpack: { configureWebpack: {
name: name, name: name,
resolve: { resolve: {
alias: { alias: {
'@': resolve('src') '@': resolve('src')
} }
} },
plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionPlugin({
test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: '[path].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8 // 压缩率小于1才会压缩
})
],
}, },
chainWebpack(config) { chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test config.plugins.delete('preload') // TODO: need test

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.4.0</version> <version>3.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>
@ -33,12 +33,6 @@
<artifactId>spring-boot-starter-undertow</artifactId> <artifactId>spring-boot-starter-undertow</artifactId>
</dependency> </dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--阿里数据库连接池 --> <!--阿里数据库连接池 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
@ -68,6 +62,11 @@
<artifactId>swagger-annotations</artifactId> <artifactId>swagger-annotations</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</dependency>
<!-- Mysql驱动包 --> <!-- Mysql驱动包 -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
@ -108,12 +107,6 @@
<artifactId>easyexcel</artifactId> <artifactId>easyexcel</artifactId>
</dependency> </dependency>
<!--Token生成与解析-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- jdk11 缺失依赖 jaxb--> <!-- jdk11 缺失依赖 jaxb-->
<dependency> <dependency>
<groupId>com.sun.xml.bind</groupId> <groupId>com.sun.xml.bind</groupId>
@ -148,7 +141,27 @@
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-jwt</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@ -189,17 +202,12 @@
<dependency> <dependency>
<groupId>com.yomahub</groupId> <groupId>com.yomahub</groupId>
<artifactId>tlog-spring-boot-configuration</artifactId> <artifactId>tlog-web-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.yomahub</groupId> <groupId>com.yomahub</groupId>
<artifactId>tlog-webroot</artifactId> <artifactId>tlog-xxljob-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-xxl-job</artifactId>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -0,0 +1,26 @@
package com.ruoyi.common.annotation;
import java.lang.annotation.*;
/**
* 数据权限
*
* @author Lion Li
* @version 3.5.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataColumn {
/**
* 占位符关键字
*/
String key() default "deptName";
/**
* 占位符替换值
*/
String value() default "dept_id";
}

View File

@ -0,0 +1,18 @@
package com.ruoyi.common.annotation;
import java.lang.annotation.*;
/**
* 数据权限组
*
* @author Lion Li
* @version 3.5.0
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
DataColumn[] value();
}

View File

@ -6,11 +6,14 @@ import java.lang.annotation.*;
* 数据权限过滤注解 * 数据权限过滤注解
* *
* @author ruoyi * @author ruoyi
* @deprecated 3.6.0 移除 {@link com.ruoyi.common.annotation.DataPermission}
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Deprecated
public @interface DataScope { public @interface DataScope {
/** /**
* 部门表的别名 * 部门表的别名
*/ */
@ -25,4 +28,5 @@ public @interface DataScope {
* 是否过滤用户权限 * 是否过滤用户权限
*/ */
boolean isUser() default false; boolean isUser() default false;
} }

View File

@ -10,11 +10,13 @@ import java.lang.annotation.*;
* 优先级先方法后类如果方法覆盖了类上的数据源类型以方法的为准否则以类上的为准 * 优先级先方法后类如果方法覆盖了类上的数据源类型以方法的为准否则以类上的为准
* *
* @author ruoyi * @author ruoyi
* @deprecated 3.6.0 移除 使用原生注解处理 方法更全 {@link com.baomidou.dynamic.datasource.annotation.DS}
*/ */
@Target({ElementType.METHOD, ElementType.TYPE}) @Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Inherited @Inherited
@Deprecated
public @interface DataSource { public @interface DataSource {
/** /**
* 切换数据源名称 * 切换数据源名称

View File

@ -1,136 +1,151 @@
package com.ruoyi.common.constant; package com.ruoyi.common.constant;
import io.jsonwebtoken.Claims;
/** /**
* 通用常量信息 * 通用常量信息
* *
* @author ruoyi * @author ruoyi
*/ */
public class Constants { public interface Constants {
/** /**
* UTF-8 字符集 * UTF-8 字符集
*/ */
public static final String UTF8 = "UTF-8"; String UTF8 = "UTF-8";
/** /**
* GBK 字符集 * GBK 字符集
*/ */
public static final String GBK = "GBK"; String GBK = "GBK";
/** /**
* http请求 * http请求
*/ */
public static final String HTTP = "http://"; String HTTP = "http://";
/** /**
* https请求 * https请求
*/ */
public static final String HTTPS = "https://"; String HTTPS = "https://";
/** /**
* 通用成功标识 * 通用成功标识
*/ */
public static final String SUCCESS = "0"; String SUCCESS = "0";
/** /**
* 通用失败标识 * 通用失败标识
*/ */
public static final String FAIL = "1"; String FAIL = "1";
/** /**
* 登录成功 * 登录成功
*/ */
public static final String LOGIN_SUCCESS = "Success"; String LOGIN_SUCCESS = "Success";
/** /**
* 注销 * 注销
*/ */
public static final String LOGOUT = "Logout"; String LOGOUT = "Logout";
/** /**
* 注册 * 注册
*/ */
public static final String REGISTER = "Register"; String REGISTER = "Register";
/** /**
* 登录失败 * 登录失败
*/ */
public static final String LOGIN_FAIL = "Error"; String LOGIN_FAIL = "Error";
/** /**
* 验证码 redis key * 验证码 redis key
*/ */
public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; String CAPTCHA_CODE_KEY = "captcha_codes:";
/** /**
* 登录用户 redis key * 登录用户 redis key
*/ */
public static final String LOGIN_TOKEN_KEY = "login_tokens:"; String LOGIN_TOKEN_KEY = "login_tokens:";
/** /**
* 防重提交 redis key * 防重提交 redis key
*/ */
public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; String REPEAT_SUBMIT_KEY = "repeat_submit:";
/** /**
* 限流 redis key * 限流 redis key
*/ */
public static final String RATE_LIMIT_KEY = "rate_limit:"; String RATE_LIMIT_KEY = "rate_limit:";
/** /**
* 验证码有效期分钟 * 验证码有效期分钟
*/ */
public static final Integer CAPTCHA_EXPIRATION = 2; Integer CAPTCHA_EXPIRATION = 2;
/**
* 登陆错误 redis key
*/
String LOGIN_ERROR = "login_error:";
/**
* 登录错误次数
*/
Integer LOGIN_ERROR_NUMBER = 5;
/**
* 登录错误限制时间(分钟)
*/
Integer LOGIN_ERROR_LIMIT_TIME = 10;
/** /**
* 令牌 * 令牌
*/ */
public static final String TOKEN = "token"; String TOKEN = "token";
/** /**
* 令牌前缀 * 令牌前缀
*/ */
public static final String TOKEN_PREFIX = "Bearer "; String TOKEN_PREFIX = "Bearer ";
/** /**
* 令牌前缀 * 令牌前缀
*/ */
public static final String LOGIN_USER_KEY = "login_user_key"; String LOGIN_USER_KEY = "login_user_key";
/** /**
* 用户ID * 用户ID
*/ */
public static final String JWT_USERID = "userid"; String JWT_USERID = "userid";
/** /**
* 用户名称 * 用户名称
*/ */
public static final String JWT_USERNAME = Claims.SUBJECT; String JWT_USERNAME = "sub";
/** /**
* 用户头像 * 用户头像
*/ */
public static final String JWT_AVATAR = "avatar"; String JWT_AVATAR = "avatar";
/** /**
* 创建时间 * 创建时间
*/ */
public static final String JWT_CREATED = "created"; String JWT_CREATED = "created";
/** /**
* 用户权限 * 用户权限
*/ */
public static final String JWT_AUTHORITIES = "authorities"; String JWT_AUTHORITIES = "authorities";
/** /**
* 参数管理 cache key * 参数管理 cache key
*/ */
public static final String SYS_CONFIG_KEY = "sys_config:"; String SYS_CONFIG_KEY = "sys_config:";
/** /**
* 字典管理 cache key * 字典管理 cache key
*/ */
public static final String SYS_DICT_KEY = "sys_dict:"; String SYS_DICT_KEY = "sys_dict:";
} }

View File

@ -5,184 +5,184 @@ package com.ruoyi.common.constant;
* *
* @author ruoyi * @author ruoyi
*/ */
public class GenConstants { public interface GenConstants {
/** /**
* 单表增删改查 * 单表增删改查
*/ */
public static final String TPL_CRUD = "crud"; String TPL_CRUD = "crud";
/** /**
* 树表增删改查 * 树表增删改查
*/ */
public static final String TPL_TREE = "tree"; String TPL_TREE = "tree";
/** /**
* 主子表增删改查 * 主子表增删改查
*/ */
public static final String TPL_SUB = "sub"; String TPL_SUB = "sub";
/** /**
* 树编码字段 * 树编码字段
*/ */
public static final String TREE_CODE = "treeCode"; String TREE_CODE = "treeCode";
/** /**
* 树父编码字段 * 树父编码字段
*/ */
public static final String TREE_PARENT_CODE = "treeParentCode"; String TREE_PARENT_CODE = "treeParentCode";
/** /**
* 树名称字段 * 树名称字段
*/ */
public static final String TREE_NAME = "treeName"; String TREE_NAME = "treeName";
/** /**
* 上级菜单ID字段 * 上级菜单ID字段
*/ */
public static final String PARENT_MENU_ID = "parentMenuId"; String PARENT_MENU_ID = "parentMenuId";
/** /**
* 上级菜单名称字段 * 上级菜单名称字段
*/ */
public static final String PARENT_MENU_NAME = "parentMenuName"; String PARENT_MENU_NAME = "parentMenuName";
/** /**
* 数据库字符串类型 * 数据库字符串类型
*/ */
public static final String[] COLUMNTYPE_STR = {"char", "varchar", "nvarchar", "varchar2"}; String[] COLUMNTYPE_STR = {"char", "varchar", "nvarchar", "varchar2"};
/** /**
* 数据库文本类型 * 数据库文本类型
*/ */
public static final String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext"}; String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext"};
/** /**
* 数据库时间类型 * 数据库时间类型
*/ */
public static final String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp"}; String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp"};
/** /**
* 数据库数字类型 * 数据库数字类型
*/ */
public static final String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer", String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer",
"bit", "bigint", "float", "double", "decimal"}; "bit", "bigint", "float", "double", "decimal"};
/** /**
* BO对象 不需要添加字段 * BO对象 不需要添加字段
*/ */
public static final String[] COLUMNNAME_NOT_ADD = {"create_by", "create_time", "del_flag", "update_by", String[] COLUMNNAME_NOT_ADD = {"create_by", "create_time", "del_flag", "update_by",
"update_time", "version"}; "update_time", "version"};
/** /**
* BO对象 不需要编辑字段 * BO对象 不需要编辑字段
*/ */
public static final String[] COLUMNNAME_NOT_EDIT = {"create_by", "create_time", "del_flag", "update_by", String[] COLUMNNAME_NOT_EDIT = {"create_by", "create_time", "del_flag", "update_by",
"update_time", "version"}; "update_time", "version"};
/** /**
* VO对象 不需要返回字段 * VO对象 不需要返回字段
*/ */
public static final String[] COLUMNNAME_NOT_LIST = {"create_by", "create_time", "del_flag", "update_by", String[] COLUMNNAME_NOT_LIST = {"create_by", "create_time", "del_flag", "update_by",
"update_time", "version"}; "update_time", "version"};
/** /**
* BO对象 不需要查询字段 * BO对象 不需要查询字段
*/ */
public static final String[] COLUMNNAME_NOT_QUERY = {"id", "create_by", "create_time", "del_flag", "update_by", String[] COLUMNNAME_NOT_QUERY = {"id", "create_by", "create_time", "del_flag", "update_by",
"update_time", "remark", "version"}; "update_time", "remark", "version"};
/** /**
* Entity基类字段 * Entity基类字段
*/ */
public static final String[] BASE_ENTITY = {"createBy", "createTime", "updateBy", "updateTime"}; String[] BASE_ENTITY = {"createBy", "createTime", "updateBy", "updateTime"};
/** /**
* Tree基类字段 * Tree基类字段
*/ */
public static final String[] TREE_ENTITY = {"parentName", "parentId", "children"}; String[] TREE_ENTITY = {"parentName", "parentId", "children"};
/** /**
* 文本框 * 文本框
*/ */
public static final String HTML_INPUT = "input"; String HTML_INPUT = "input";
/** /**
* 文本域 * 文本域
*/ */
public static final String HTML_TEXTAREA = "textarea"; String HTML_TEXTAREA = "textarea";
/** /**
* 下拉框 * 下拉框
*/ */
public static final String HTML_SELECT = "select"; String HTML_SELECT = "select";
/** /**
* 单选框 * 单选框
*/ */
public static final String HTML_RADIO = "radio"; String HTML_RADIO = "radio";
/** /**
* 复选框 * 复选框
*/ */
public static final String HTML_CHECKBOX = "checkbox"; String HTML_CHECKBOX = "checkbox";
/** /**
* 日期控件 * 日期控件
*/ */
public static final String HTML_DATETIME = "datetime"; String HTML_DATETIME = "datetime";
/** /**
* 图片上传控件 * 图片上传控件
*/ */
public static final String HTML_IMAGE_UPLOAD = "imageUpload"; String HTML_IMAGE_UPLOAD = "imageUpload";
/** /**
* 文件上传控件 * 文件上传控件
*/ */
public static final String HTML_FILE_UPLOAD = "fileUpload"; String HTML_FILE_UPLOAD = "fileUpload";
/** /**
* 富文本控件 * 富文本控件
*/ */
public static final String HTML_EDITOR = "editor"; String HTML_EDITOR = "editor";
/** /**
* 字符串类型 * 字符串类型
*/ */
public static final String TYPE_STRING = "String"; String TYPE_STRING = "String";
/** /**
* 整型 * 整型
*/ */
public static final String TYPE_INTEGER = "Integer"; String TYPE_INTEGER = "Integer";
/** /**
* 长整型 * 长整型
*/ */
public static final String TYPE_LONG = "Long"; String TYPE_LONG = "Long";
/** /**
* 浮点型 * 浮点型
*/ */
public static final String TYPE_DOUBLE = "Double"; String TYPE_DOUBLE = "Double";
/** /**
* 高精度计算类型 * 高精度计算类型
*/ */
public static final String TYPE_BIGDECIMAL = "BigDecimal"; String TYPE_BIGDECIMAL = "BigDecimal";
/** /**
* 时间类型 * 时间类型
*/ */
public static final String TYPE_DATE = "Date"; String TYPE_DATE = "Date";
/** /**
* 模糊查询 * 模糊查询
*/ */
public static final String QUERY_LIKE = "LIKE"; String QUERY_LIKE = "LIKE";
/** /**
* 需要 * 需要
*/ */
public static final String REQUIRE = "1"; String REQUIRE = "1";
} }

View File

@ -5,108 +5,108 @@ package com.ruoyi.common.constant;
* *
* @author ruoyi * @author ruoyi
*/ */
public class UserConstants { public interface UserConstants {
/** /**
* 平台内系统用户的唯一标志 * 平台内系统用户的唯一标志
*/ */
public static final String SYS_USER = "SYS_USER"; String SYS_USER = "SYS_USER";
/** /**
* 正常状态 * 正常状态
*/ */
public static final String NORMAL = "0"; String NORMAL = "0";
/** /**
* 异常状态 * 异常状态
*/ */
public static final String EXCEPTION = "1"; String EXCEPTION = "1";
/** /**
* 用户封禁状态 * 用户封禁状态
*/ */
public static final String USER_DISABLE = "1"; String USER_DISABLE = "1";
/** /**
* 角色封禁状态 * 角色封禁状态
*/ */
public static final String ROLE_DISABLE = "1"; String ROLE_DISABLE = "1";
/** /**
* 部门正常状态 * 部门正常状态
*/ */
public static final String DEPT_NORMAL = "0"; String DEPT_NORMAL = "0";
/** /**
* 部门停用状态 * 部门停用状态
*/ */
public static final String DEPT_DISABLE = "1"; String DEPT_DISABLE = "1";
/** /**
* 字典正常状态 * 字典正常状态
*/ */
public static final String DICT_NORMAL = "0"; String DICT_NORMAL = "0";
/** /**
* 是否为系统默认 * 是否为系统默认
*/ */
public static final String YES = "Y"; String YES = "Y";
/** /**
* 是否菜单外链 * 是否菜单外链
*/ */
public static final String YES_FRAME = "0"; String YES_FRAME = "0";
/** /**
* 是否菜单外链 * 是否菜单外链
*/ */
public static final String NO_FRAME = "1"; String NO_FRAME = "1";
/** /**
* 菜单类型目录 * 菜单类型目录
*/ */
public static final String TYPE_DIR = "M"; String TYPE_DIR = "M";
/** /**
* 菜单类型菜单 * 菜单类型菜单
*/ */
public static final String TYPE_MENU = "C"; String TYPE_MENU = "C";
/** /**
* 菜单类型按钮 * 菜单类型按钮
*/ */
public static final String TYPE_BUTTON = "F"; String TYPE_BUTTON = "F";
/** /**
* Layout组件标识 * Layout组件标识
*/ */
public final static String LAYOUT = "Layout"; String LAYOUT = "Layout";
/** /**
* ParentView组件标识 * ParentView组件标识
*/ */
public final static String PARENT_VIEW = "ParentView"; String PARENT_VIEW = "ParentView";
/** /**
* InnerLink组件标识 * InnerLink组件标识
*/ */
public final static String INNER_LINK = "InnerLink"; String INNER_LINK = "InnerLink";
/** /**
* 校验返回结果码 * 校验返回结果码
*/ */
public final static String UNIQUE = "0"; String UNIQUE = "0";
public final static String NOT_UNIQUE = "1"; String NOT_UNIQUE = "1";
/** /**
* 用户名长度限制 * 用户名长度限制
*/ */
public static final int USERNAME_MIN_LENGTH = 2; int USERNAME_MIN_LENGTH = 2;
public static final int USERNAME_MAX_LENGTH = 20; int USERNAME_MAX_LENGTH = 20;
/** /**
* 密码长度限制 * 密码长度限制
*/ */
public static final int PASSWORD_MIN_LENGTH = 5; int PASSWORD_MIN_LENGTH = 5;
public static final int PASSWORD_MAX_LENGTH = 20; int PASSWORD_MAX_LENGTH = 20;
} }

View File

@ -0,0 +1,93 @@
package com.ruoyi.common.core.domain;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.sql.SqlUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 分页查询实体类
*
* @author Lion Li
*/
@Data
@Accessors(chain = true)
public class PageQuery implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分页大小
*/
@ApiModelProperty("分页大小")
private Integer pageSize;
/**
* 当前页数
*/
@ApiModelProperty("当前页数")
private Integer pageNum;
/**
* 排序列
*/
@ApiModelProperty("排序列")
private String orderByColumn;
/**
* 排序的方向desc或者asc
*/
@ApiModelProperty(value = "排序的方向", example = "asc,desc")
private String isAsc;
/**
* 当前记录起始索引 默认值
*/
public static final int DEFAULT_PAGE_NUM = 1;
/**
* 每页显示记录数 默认值 默认查全部
*/
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
public <T> Page<T> build() {
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
if (pageNum <= 0) {
pageNum = DEFAULT_PAGE_NUM;
}
Page<T> page = new Page<>(pageNum, pageSize);
OrderItem orderItem = buildOrderItem();
if (ObjectUtil.isNotNull(orderItem)) {
page.addOrder(orderItem);
}
return page;
}
private OrderItem buildOrderItem() {
// 兼容前端排序类型
if ("ascending".equals(isAsc)) {
isAsc = "asc";
} else if ("descending".equals(isAsc)) {
isAsc = "desc";
}
if (StringUtils.isNotBlank(orderByColumn)) {
String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
orderBy = StringUtils.toUnderScoreCase(orderBy);
if ("asc".equals(isAsc)) {
return OrderItem.asc(orderBy);
} else if ("desc".equals(isAsc)) {
return OrderItem.desc(orderBy);
}
}
return null;
}
}

View File

@ -1,5 +1,6 @@
package com.ruoyi.common.core.domain.entity; package com.ruoyi.common.core.domain.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.TreeEntity; import com.ruoyi.common.core.domain.TreeEntity;
@ -65,6 +66,7 @@ public class SysMenu extends TreeEntity {
* 路由参数 * 路由参数
*/ */
@ApiModelProperty(value = "路由参数") @ApiModelProperty(value = "路由参数")
@TableField("`query`")
private String query; private String query;
/** /**

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.xss.Xss;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ -48,15 +49,17 @@ public class SysUser extends BaseEntity {
* 用户账号 * 用户账号
*/ */
@ApiModelProperty(value = "用户账号") @ApiModelProperty(value = "用户账号")
@NotBlank(message = "用户账号不能为空") @Xss(message = "用户账号不能包含脚本字符")
@NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
private String userName; private String userName;
/** /**
* 用户昵称 * 用户昵称
*/ */
@ApiModelProperty(value = "用户昵称") @ApiModelProperty(value = "用户昵称")
@Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") @Xss(message = "用户昵称不能包含脚本字符")
@Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
private String nickName; private String nickName;
/** /**
@ -162,7 +165,7 @@ public class SysUser extends BaseEntity {
private Long[] postIds; private Long[] postIds;
/** /**
* 角色ID * 数据权限 当前角色ID
*/ */
@ApiModelProperty(value = "角色ID") @ApiModelProperty(value = "角色ID")
@TableField(exist = false) @TableField(exist = false)

View File

@ -69,9 +69,14 @@ public class LoginUser implements UserDetails {
private String os; private String os;
/** /**
* 权限列表 * 菜单权限
*/ */
private Set<String> permissions; private Set<String> menuPermissions;
/**
* 角色权限
*/
private Set<String> rolePermissions;
/** /**
* 用户名 * 用户名
@ -83,20 +88,6 @@ public class LoginUser implements UserDetails {
*/ */
private String password; private String password;
public LoginUser(String username, String password, Set<String> permissions) {
this.username = username;
this.password = password;
this.permissions = permissions;
}
public LoginUser(Long userId, Long deptId, String username, String password, Set<String> permissions) {
this.userId = userId;
this.deptId = deptId;
this.username = username;
this.password = password;
this.permissions = permissions;
}
@JsonIgnore @JsonIgnore
@Override @Override
public String getPassword() { public String getPassword() {

View File

@ -1,9 +1,18 @@
package com.ruoyi.common.core.mybatisplus.core; package com.ruoyi.common.core.mybatisplus.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.BeanCopyUtils;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map;
/** /**
* 自定义 Mapper 接口, 实现 自定义扩展 * 自定义 Mapper 接口, 实现 自定义扩展
@ -18,4 +27,72 @@ public interface BaseMapperPlus<T> extends BaseMapper<T> {
*/ */
int insertAll(@Param("list") Collection<T> batchList); int insertAll(@Param("list") Collection<T> batchList);
/**
* 根据 ID 查询
*/
default <V> V selectVoById(Serializable id, Class<V> voClass){
T obj = this.selectById(id);
if (ObjectUtil.isNull(obj)) {
return null;
}
return BeanCopyUtils.copy(obj, voClass);
}
/**
* 查询根据ID 批量查询
*/
default <V> List<V> selectVoBatchIds(Collection<? extends Serializable> idList, Class<V> voClass){
List<T> list = this.selectBatchIds(idList);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtils.copyList(list, voClass);
}
/**
* 查询根据 columnMap 条件
*/
default <V> List<V> selectVoByMap(Map<String, Object> map, Class<V> voClass){
List<T> list = this.selectByMap(map);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtils.copyList(list, voClass);
}
/**
* 根据 entity 条件查询一条记录
*/
default <V> V selectVoOne(Wrapper<T> wrapper, Class<V> voClass) {
T obj = this.selectOne(wrapper);
if (ObjectUtil.isNull(obj)) {
return null;
}
return BeanCopyUtils.copy(obj, voClass);
}
/**
* 根据 entity 条件查询全部记录
*/
default <V> List<V> selectVoList(Wrapper<T> wrapper, Class<V> voClass) {
List<T> list = this.selectList(wrapper);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtils.copyList(list, voClass);
}
/**
* 分页查询VO
*/
default <V, P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<V> voClass) {
IPage<T> pageData = this.selectPage(page, wrapper);
IPage<V> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
if (CollUtil.isEmpty(pageData.getRecords())) {
return (P) voPage;
}
voPage.setRecords(BeanCopyUtils.copyList(pageData.getRecords(), voClass));
return (P) voPage;
}
} }

View File

@ -1,7 +1,7 @@
package com.ruoyi.common.core.mybatisplus.core; package com.ruoyi.common.core.mybatisplus.core;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.core.page.PagePlus; import com.ruoyi.common.core.page.PagePlus;
@ -23,14 +23,9 @@ public interface IServicePlus<T, V> extends IService<T> {
/** /**
* @param id 主键id * @param id 主键id
* @param copyOptions copy条件
* @return V对象 * @return V对象
*/ */
V getVoById(Serializable id, CopyOptions copyOptions); V getVoById(Serializable id);
default V getVoById(Serializable id) {
return getVoById(id, new CopyOptions());
}
/** /**
* @param convertor 自定义转换器 * @param convertor 自定义转换器
@ -41,14 +36,9 @@ public interface IServicePlus<T, V> extends IService<T> {
/** /**
* @param idList id列表 * @param idList id列表
* @param copyOptions copy条件
* @return V对象 * @return V对象
*/ */
List<V> listVoByIds(Collection<? extends Serializable> idList, CopyOptions copyOptions); List<V> listVoByIds(Collection<? extends Serializable> idList);
default List<V> listVoByIds(Collection<? extends Serializable> idList) {
return listVoByIds(idList, new CopyOptions());
}
/** /**
* @param convertor 自定义转换器 * @param convertor 自定义转换器
@ -64,14 +54,9 @@ public interface IServicePlus<T, V> extends IService<T> {
/** /**
* @param columnMap 表字段 map 对象 * @param columnMap 表字段 map 对象
* @param copyOptions copy条件
* @return V对象 * @return V对象
*/ */
List<V> listVoByMap(Map<String, Object> columnMap, CopyOptions copyOptions); List<V> listVoByMap(Map<String, Object> columnMap);
default List<V> listVoByMap(Map<String, Object> columnMap) {
return listVoByMap(columnMap, new CopyOptions());
}
/** /**
* @param convertor 自定义转换器 * @param convertor 自定义转换器
@ -87,14 +72,9 @@ public interface IServicePlus<T, V> extends IService<T> {
/** /**
* @param queryWrapper 查询条件 * @param queryWrapper 查询条件
* @param copyOptions copy条件
* @return V对象 * @return V对象
*/ */
V getVoOne(Wrapper<T> queryWrapper, CopyOptions copyOptions); V getVoOne(Wrapper<T> queryWrapper);
default V getVoOne(Wrapper<T> queryWrapper) {
return getVoOne(queryWrapper, new CopyOptions());
}
/** /**
* @param convertor 自定义转换器 * @param convertor 自定义转换器
@ -105,14 +85,9 @@ public interface IServicePlus<T, V> extends IService<T> {
/** /**
* @param queryWrapper 查询条件 * @param queryWrapper 查询条件
* @param copyOptions copy条件
* @return V对象 * @return V对象
*/ */
List<V> listVo(Wrapper<T> queryWrapper, CopyOptions copyOptions); List<V> listVo(Wrapper<T> queryWrapper);
default List<V> listVo(Wrapper<T> queryWrapper) {
return listVo(queryWrapper, new CopyOptions());
}
/** /**
* @param convertor 自定义转换器 * @param convertor 自定义转换器
@ -139,31 +114,36 @@ public interface IServicePlus<T, V> extends IService<T> {
/** /**
* @param page 分页对象 * @param page 分页对象
* @param queryWrapper 查询条件 * @param queryWrapper 查询条件
* @param copyOptions copy条件
* @return V对象 * @return V对象
* @deprecated 3.6.0 移除 请使用 {@link ServicePlusImpl#pageVo(IPage, Wrapper)}
*/ */
PagePlus<T, V> pageVo(PagePlus<T, V> page, Wrapper<T> queryWrapper, CopyOptions copyOptions); @Deprecated
PagePlus<T, V> pageVo(PagePlus<T, V> page, Wrapper<T> queryWrapper);
default PagePlus<T, V> pageVo(PagePlus<T, V> page, Wrapper<T> queryWrapper) { /**
return pageVo(page, queryWrapper, new CopyOptions()); * @param convertor 自定义转换器
} * @deprecated 3.6.0 移除 请使用 {@link ServicePlusImpl#pageVo(IPage, Wrapper)}
*/
/** @Deprecated
* @param convertor 自定义转换器
*/
default PagePlus<T, V> pageVo(PagePlus<T, V> page, Wrapper<T> queryWrapper, default PagePlus<T, V> pageVo(PagePlus<T, V> page, Wrapper<T> queryWrapper,
Function<Collection<T>, List<V>> convertor) { Function<Collection<T>, List<V>> convertor) {
PagePlus<T, V> result = getBaseMapper().selectPage(page, queryWrapper); PagePlus<T, V> result = getBaseMapper().selectPage(page, queryWrapper);
return result.setRecordsVo(convertor.apply(result.getRecords())); return result.setRecordsVo(convertor.apply(result.getRecords()));
} }
/**
* @deprecated 3.6.0 移除 请使用 {@link ServicePlusImpl#pageVo(IPage, Wrapper)}
*/
@Deprecated
default PagePlus<T, V> pageVo(PagePlus<T, V> page) { default PagePlus<T, V> pageVo(PagePlus<T, V> page) {
return pageVo(page, Wrappers.emptyWrapper()); return pageVo(page, Wrappers.emptyWrapper());
} }
/** /**
* @param convertor 自定义转换器 * @param convertor 自定义转换器
* @deprecated 3.6.0 移除 请使用 {@link ServicePlusImpl#pageVo(IPage, Wrapper)}
*/ */
@Deprecated
default PagePlus<T, V> pageVo(PagePlus<T, V> page, Function<Collection<T>, List<V>> convertor) { default PagePlus<T, V> pageVo(PagePlus<T, V> page, Function<Collection<T>, List<V>> convertor) {
return pageVo(page, Wrappers.emptyWrapper(), convertor); return pageVo(page, Wrappers.emptyWrapper(), convertor);
} }

View File

@ -1,9 +1,9 @@
package com.ruoyi.common.core.mybatisplus.core; package com.ruoyi.common.core.mybatisplus.core;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert; import com.baomidou.mybatisplus.core.toolkit.Assert;
@ -161,81 +161,66 @@ public class ServicePlusImpl<M extends BaseMapperPlus<T>, T, V> extends ServiceI
/** /**
* 根据 ID 查询 * 根据 ID 查询
*
* @param id 主键ID
*/ */
@Override @Override
public V getVoById(Serializable id, CopyOptions copyOptions) { public V getVoById(Serializable id) {
T t = getBaseMapper().selectById(id); return getBaseMapper().selectVoById(id, voClass);
return BeanCopyUtils.oneCopy(t, copyOptions, voClass);
} }
/** /**
* 查询根据ID 批量查询 * 查询根据ID 批量查询
*
* @param idList 主键ID列表
*/ */
@Override @Override
public List<V> listVoByIds(Collection<? extends Serializable> idList, CopyOptions copyOptions) { public List<V> listVoByIds(Collection<? extends Serializable> idList) {
List<T> list = getBaseMapper().selectBatchIds(idList); return getBaseMapper().selectVoBatchIds(idList, voClass);
if (list == null) {
return null;
}
return BeanCopyUtils.listCopy(list, copyOptions, voClass);
} }
/** /**
* 查询根据 columnMap 条件 * 查询根据 columnMap 条件
*
* @param columnMap 表字段 map 对象
*/ */
@Override @Override
public List<V> listVoByMap(Map<String, Object> columnMap, CopyOptions copyOptions) { public List<V> listVoByMap(Map<String, Object> columnMap) {
List<T> list = getBaseMapper().selectByMap(columnMap); return getBaseMapper().selectVoByMap(columnMap, voClass);
if (list == null) {
return null;
}
return BeanCopyUtils.listCopy(list, copyOptions, voClass);
} }
/** /**
* 根据 Wrapper查询一条记录 <br/> * 根据 Wrapper查询一条记录 <br/>
* <p>结果集如果是多个会抛出异常随机取一条加上限制条件 wrapper.last("LIMIT 1")</p> * <p>结果集如果是多个会抛出异常随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/ */
@Override @Override
public V getVoOne(Wrapper<T> queryWrapper, CopyOptions copyOptions) { public V getVoOne(Wrapper<T> queryWrapper) {
T t = getOne(queryWrapper, true); return getBaseMapper().selectVoOne(queryWrapper, voClass);
return BeanCopyUtils.oneCopy(t, copyOptions, voClass);
} }
/** /**
* 查询列表 * 查询列表
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/ */
@Override @Override
public List<V> listVo(Wrapper<T> queryWrapper, CopyOptions copyOptions) { public List<V> listVo(Wrapper<T> queryWrapper) {
List<T> list = getBaseMapper().selectList(queryWrapper); return getBaseMapper().selectVoList(queryWrapper, voClass);
if (list == null) {
return null;
}
return BeanCopyUtils.listCopy(list, copyOptions, voClass);
} }
/** /**
* 翻页查询 * 翻页查询
* * @deprecated 3.6.0 移除 请使用 {@link #pageVo(IPage, Wrapper)}
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类
*/ */
@Override @Override
public PagePlus<T, V> pageVo(PagePlus<T, V> page, Wrapper<T> queryWrapper, CopyOptions copyOptions) { @Deprecated
public PagePlus<T, V> pageVo(PagePlus<T, V> page, Wrapper<T> queryWrapper) {
PagePlus<T, V> result = getBaseMapper().selectPage(page, queryWrapper); PagePlus<T, V> result = getBaseMapper().selectPage(page, queryWrapper);
List<V> volist = BeanCopyUtils.listCopy(result.getRecords(), copyOptions, voClass); List<V> volist = BeanCopyUtils.copyList(result.getRecords(), voClass);
result.setRecordsVo(volist); result.setRecordsVo(volist);
return result; return result;
} }
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类
*/
public <P extends IPage<V>> P pageVo(IPage<T> page, Wrapper<T> queryWrapper) {
return getBaseMapper().selectVoPage(page, queryWrapper, voClass);
}
} }

View File

@ -16,9 +16,11 @@ import java.util.List;
* @param <T> 数据库实体 * @param <T> 数据库实体
* @param <K> vo实体 * @param <K> vo实体
* @author Lion Li * @author Lion Li
* @deprecated 3.6.0 删除 请使用 {@link com.ruoyi.common.core.domain.PageQuery#build()}
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
@Deprecated
public class PagePlus<T,K> implements IPage<T> { public class PagePlus<T,K> implements IPage<T> {
/** /**

View File

@ -1,5 +1,7 @@
package com.ruoyi.common.core.page; package com.ruoyi.common.core.page;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ -57,4 +59,29 @@ public class TableDataInfo<T> implements Serializable {
this.total = total; this.total = total;
} }
public static <T> TableDataInfo<T> build(IPage<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(page.getRecords());
rspData.setTotal(page.getTotal());
return rspData;
}
public static <T> TableDataInfo<T> build(List<T> list) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(list.size());
return rspData;
}
public static <T> TableDataInfo<T> build() {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
return rspData;
}
} }

View File

@ -0,0 +1,72 @@
package com.ruoyi.common.enums;
import com.ruoyi.common.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 数据权限类型
*
* 语法支持 spel 模板表达式
*
* 内置数据 user 当前用户 内容参考 SysUser
* 如需扩展数据 可使用 {@link com.ruoyi.common.helper.DataPermissionHelper} 操作
* 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService
* 如需扩展更多自定义服务 可以参考 sdss 自行编写
*
* @author Lion Li
* @version 3.5.0
*/
@Getter
@AllArgsConstructor
public enum DataScopeType {
/**
* 全部数据权限
*/
ALL("1", "", ""),
/**
* 自定数据权限
*/
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", ""),
/**
* 部门数据权限
*/
DEPT("3", " #{#deptName} = #{#user.deptId} ", ""),
/**
* 部门及以下数据权限
*/
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", ""),
/**
* 仅本人数据权限
*/
SELF("5", " #{#userName} = #{#user.userId} " , " 1 = 0 ");
private final String code;
/**
* 语法 采用 spel 模板表达式
*/
private final String sqlTemplate;
/**
* 不满足 sqlTemplate 则填充
*/
private final String elseSql;
public static DataScopeType findCode(String code) {
if (StringUtils.isBlank(code)) {
return null;
}
for (DataScopeType type : values()) {
if (type.getCode().equals(code)) {
return type;
}
}
return null;
}
}

View File

@ -7,8 +7,10 @@ import lombok.Getter;
* 数据源 * 数据源
* *
* @author Lion Li * @author Lion Li
* @deprecated 3.6.0 移除
*/ */
@AllArgsConstructor @AllArgsConstructor
@Deprecated
public enum DataSourceType { public enum DataSourceType {
/** /**
* 主库 * 主库

View File

@ -5,7 +5,7 @@ import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException; import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.exception.ExcelDataConvertException; import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.fastjson.JSON; import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.ValidatorUtils; import com.ruoyi.common.utils.ValidatorUtils;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -84,7 +84,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
@Override @Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap; this.headMap = headMap;
log.debug("解析到一条表头数据: {}", JSON.toJSONString(headMap)); log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap));
} }
@Override @Override

View File

@ -9,6 +9,6 @@ public class CaptchaException extends UserException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public CaptchaException() { public CaptchaException() {
super("user.jcaptcha.error", null); super("user.jcaptcha.error");
} }
} }

View File

@ -9,6 +9,6 @@ public class CaptchaExpireException extends UserException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public CaptchaExpireException() { public CaptchaExpireException() {
super("user.jcaptcha.expire", null); super("user.jcaptcha.expire");
} }
} }

View File

@ -10,7 +10,7 @@ import com.ruoyi.common.exception.base.BaseException;
public class UserException extends BaseException { public class UserException extends BaseException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public UserException(String code, Object[] args) { public UserException(String code, Object... args) {
super("user", code, args, null); super("user", code, args, null);
} }
} }

View File

@ -9,6 +9,6 @@ public class UserPasswordNotMatchException extends UserException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public UserPasswordNotMatchException() { public UserPasswordNotMatchException() {
super("user.password.not.match", null); super("user.password.not.match");
} }
} }

View File

@ -0,0 +1,45 @@
package com.ruoyi.common.helper;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.utils.ServletUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* 数据权限助手
*
* @author Lion Li
* @version 3.5.0
*/
@SuppressWarnings("unchecked cast")
public class DataPermissionHelper {
private static final String DATA_PERMISSION_KEY = "data:permission";
public static <T> T getVariable(String key) {
Map<String, Object> context = getContext();
return (T) context.get(key);
}
public static void setVariable(String key, Object value) {
Map<String, Object> context = getContext();
context.put(key, value);
}
public static Map<String, Object> getContext() {
HttpServletRequest request = ServletUtils.getRequest();
Object attribute = request.getAttribute(DATA_PERMISSION_KEY);
if (ObjectUtil.isNull(attribute)) {
request.setAttribute(DATA_PERMISSION_KEY, new HashMap<>());
attribute = request.getAttribute(DATA_PERMISSION_KEY);
}
if (attribute instanceof Map) {
return (Map<String, Object>) attribute;
}
throw new NullPointerException("data permission context type exception");
}
}

View File

@ -1,66 +1,120 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import cn.hutool.core.bean.copier.BeanCopier;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.cglib.CglibUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.Map;
/** /**
* bean深拷贝工具 * bean深拷贝工具(基于 cglib 性能优异)
* *
* @author Lion Li * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanCopyUtils { public class BeanCopyUtils {
/** /**
* 单对象基于class创建拷贝 * 单对象基于class创建拷贝
* *
* @param source 数据来源实体 * @param source 数据来源实体
* @param copyOptions copy条件 * @param desc 描述对象 转换后的对象
* @param desc 描述对象 转换后的对象
* @return desc * @return desc
*/ */
public static <T, V> V oneCopy(T source, CopyOptions copyOptions, Class<V> desc) { public static <T, V> V copy(T source, Class<V> desc) {
V v = ReflectUtil.newInstanceIfPossible(desc); if (ObjectUtil.isNull(source)) {
return oneCopy(source, copyOptions, v); return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
return CglibUtil.copy(source, desc);
} }
/** /**
* 单对象基于对象创建拷贝 * 单对象基于对象创建拷贝
* *
* @param source 数据来源实体 * @param source 数据来源实体
* @param copyOptions copy条件 * @param desc 转换后的对象
* @param desc 转换后的对象
* @return desc * @return desc
*/ */
public static <T, V> V oneCopy(T source, CopyOptions copyOptions, V desc) { public static <T, V> V copy(T source, V desc) {
if (ObjectUtil.isNull(source)) { if (ObjectUtil.isNull(source)) {
return null; return null;
} }
return BeanCopier.create(source, desc, copyOptions).copy(); if (ObjectUtil.isNull(desc)) {
return null;
}
CglibUtil.copy(source, desc);
return desc;
} }
/** /**
* 列表对象基于class创建拷贝 * 列表对象基于class创建拷贝
* *
* @param sourceList 数据来源实体列表 * @param sourceList 数据来源实体列表
* @param copyOptions copy条件 * @param desc 描述对象 转换后的对象
* @param desc 描述对象 转换后的对象
* @return desc * @return desc
*/ */
public static <T, V> List<V> listCopy(List<T> sourceList, CopyOptions copyOptions, Class<V> desc) { public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) {
if (ObjectUtil.isNull(sourceList)) { if (ObjectUtil.isNull(sourceList)) {
return null; return null;
} }
if (CollUtil.isEmpty(sourceList)) { if (CollUtil.isEmpty(sourceList)) {
return CollUtil.newArrayList(); return CollUtil.newArrayList();
} }
return sourceList.stream() return CglibUtil.copyList(sourceList, () -> ReflectUtil.newInstanceIfPossible(desc));
.map(source -> oneCopy(source, copyOptions, desc)) }
.collect(Collectors.toList());
/**
* bean拷贝到map
*
* @param bean 数据来源实体
* @return map对象
*/
public static <T> Map<String, Object> copyToMap(T bean) {
if (ObjectUtil.isNull(bean)) {
return null;
}
return CglibUtil.toMap(bean);
}
/**
* map拷贝到bean
*
* @param map 数据来源
* @param beanClass bean类
* @return bean对象
*/
public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(beanClass)) {
return null;
}
return CglibUtil.toBean(map, beanClass);
}
/**
* map拷贝到bean
*
* @param map 数据来源
* @param bean bean对象
* @return bean对象
*/
public static <T> T mapToBean(Map<String, Object> map, T bean) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(bean)) {
return null;
}
return CglibUtil.fillBean(map, bean);
} }
} }

View File

@ -1,5 +1,7 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
@ -12,6 +14,7 @@ import java.util.Date;
* *
* @author ruoyi * @author ruoyi
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DateUtils extends org.apache.commons.lang3.time.DateUtils { public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
public static String YYYY = "yyyy"; public static String YYYY = "yyyy";

View File

@ -1,158 +0,0 @@
package com.ruoyi.common.utils;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysDictData;
import java.util.Collection;
import java.util.List;
/**
* 字典工具类
*
* @author ruoyi
* @deprecated 3.5.0 版本删除 迁移至 {@link com.ruoyi.common.core.service.DictService}
*/
@Deprecated
public class DictUtils {
/**
* 分隔符
*/
public static final String SEPARATOR = ",";
/**
* 设置字典缓存
*
* @param key 参数键
* @param dictDatas 字典数据列表
*/
public static void setDictCache(String key, List<SysDictData> dictDatas) {
RedisUtils.setCacheObject(getCacheKey(key), dictDatas);
}
/**
* 获取字典缓存
*
* @param key 参数键
* @return dictDatas 字典数据列表
*/
public static List<SysDictData> getDictCache(String key) {
List<SysDictData> dictDatas = RedisUtils.getCacheObject(getCacheKey(key));
if (StringUtils.isNotNull(dictDatas)) {
return dictDatas;
}
return null;
}
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @return 字典标签
*/
public static String getDictLabel(String dictType, String dictValue) {
return getDictLabel(dictType, dictValue, SEPARATOR);
}
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @return 字典值
*/
public static String getDictValue(String dictType, String dictLabel) {
return getDictValue(dictType, dictLabel, SEPARATOR);
}
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @param separator 分隔符
* @return 字典标签
*/
public static String getDictLabel(String dictType, String dictValue, String separator) {
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.containsAny(dictValue, separator) && CollUtil.isNotEmpty(datas)) {
for (SysDictData dict : datas) {
for (String value : dictValue.split(separator)) {
if (value.equals(dict.getDictValue())) {
propertyString.append(dict.getDictLabel() + separator);
break;
}
}
}
} else {
for (SysDictData dict : datas) {
if (dictValue.equals(dict.getDictValue())) {
return dict.getDictLabel();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @param separator 分隔符
* @return 字典值
*/
public static String getDictValue(String dictType, String dictLabel, String separator) {
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.containsAny(dictLabel, separator) && CollUtil.isNotEmpty(datas)) {
for (SysDictData dict : datas) {
for (String label : dictLabel.split(separator)) {
if (label.equals(dict.getDictLabel())) {
propertyString.append(dict.getDictValue() + separator);
break;
}
}
}
} else {
for (SysDictData dict : datas) {
if (dictLabel.equals(dict.getDictLabel())) {
return dict.getDictValue();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 删除指定字典缓存
*
* @param key 字典键
*/
public static void removeDictCache(String key) {
RedisUtils.deleteObject(getCacheKey(key));
}
/**
* 清空字典缓存
*/
public static void clearDictCache() {
Collection<String> keys = RedisUtils.keys(Constants.SYS_DICT_KEY + "*");
RedisUtils.deleteObject(keys);
}
/**
* 设置cache key
*
* @param configKey 参数键
* @return 缓存键key
*/
public static String getCacheKey(String configKey) {
return Constants.SYS_DICT_KEY + configKey;
}
}

View File

@ -21,14 +21,18 @@ import java.util.Map;
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils { public class JsonUtils {
private static ObjectMapper objectMapper = SpringUtils.getBean(ObjectMapper.class); private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
public static ObjectMapper getObjectMapper() {
return OBJECT_MAPPER;
}
public static String toJsonString(Object object) { public static String toJsonString(Object object) {
if (StringUtils.isNull(object)) { if (StringUtils.isNull(object)) {
return null; return null;
} }
try { try {
return objectMapper.writeValueAsString(object); return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -39,7 +43,7 @@ public class JsonUtils {
return null; return null;
} }
try { try {
return objectMapper.readValue(text, clazz); return OBJECT_MAPPER.readValue(text, clazz);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -50,7 +54,7 @@ public class JsonUtils {
return null; return null;
} }
try { try {
return objectMapper.readValue(bytes, clazz); return OBJECT_MAPPER.readValue(bytes, clazz);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -61,7 +65,7 @@ public class JsonUtils {
return null; return null;
} }
try { try {
return objectMapper.readValue(text, typeReference); return OBJECT_MAPPER.readValue(text, typeReference);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -72,7 +76,7 @@ public class JsonUtils {
return null; return null;
} }
try { try {
return objectMapper.readValue(text, new TypeReference<Map<String, T>>() { return OBJECT_MAPPER.readValue(text, new TypeReference<Map<String, T>>() {
}); });
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -84,7 +88,7 @@ public class JsonUtils {
return new ArrayList<>(); return new ArrayList<>();
} }
try { try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -1,15 +1,21 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
/** /**
* 获取i18n资源文件 * 获取i18n资源文件
* *
* @author ruoyi * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MessageUtils { public class MessageUtils {
private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
/** /**
* 根据消息键和参数 获取消息 委托给spring messageSource * 根据消息键和参数 获取消息 委托给spring messageSource
* *
@ -18,7 +24,6 @@ public class MessageUtils {
* @return 获取国际化翻译值 * @return 获取国际化翻译值
*/ */
public static String message(String code, Object... args) { public static String message(String code, Object... args) {
MessageSource messageSource = SpringUtils.getBean(MessageSource.class); return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
} }
} }

View File

@ -2,11 +2,15 @@ package com.ruoyi.common.utils;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.PagePlus; import com.ruoyi.common.core.page.PagePlus;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.sql.SqlUtil; import com.ruoyi.common.utils.sql.SqlUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
@ -14,37 +18,46 @@ import java.util.List;
* 分页工具 * 分页工具
* *
* @author Lion Li * @author Lion Li
* @deprecated 3.6.0 删除 请使用 {@link PageQuery} {@link TableDataInfo}
*/ */
@Deprecated
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class PageUtils { public class PageUtils {
/** /**
* 当前记录起始索引 * 当前记录起始索引
*/ */
@Deprecated
public static final String PAGE_NUM = "pageNum"; public static final String PAGE_NUM = "pageNum";
/** /**
* 每页显示记录数 * 每页显示记录数
*/ */
@Deprecated
public static final String PAGE_SIZE = "pageSize"; public static final String PAGE_SIZE = "pageSize";
/** /**
* 排序列 * 排序列
*/ */
@Deprecated
public static final String ORDER_BY_COLUMN = "orderByColumn"; public static final String ORDER_BY_COLUMN = "orderByColumn";
/** /**
* 排序的方向 "desc" 或者 "asc". * 排序的方向 "desc" 或者 "asc".
*/ */
@Deprecated
public static final String IS_ASC = "isAsc"; public static final String IS_ASC = "isAsc";
/** /**
* 当前记录起始索引 默认值 * 当前记录起始索引 默认值
*/ */
@Deprecated
public static final int DEFAULT_PAGE_NUM = 1; public static final int DEFAULT_PAGE_NUM = 1;
/** /**
* 每页显示记录数 默认值 默认查全部 * 每页显示记录数 默认值 默认查全部
*/ */
@Deprecated
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE; public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
/** /**
@ -53,7 +66,10 @@ public class PageUtils {
* @param <T> domain 实体 * @param <T> domain 实体
* @param <K> vo 实体 * @param <K> vo 实体
* @return 分页对象 * @return 分页对象
* @deprecated 3.6.0 删除 请使用 {@link PageQuery#build()}
* 由于使用 Servlet 获取只能从 param 获取 灵活性降低 故将传参操作交给用户
*/ */
@Deprecated
public static <T, K> PagePlus<T, K> buildPagePlus() { public static <T, K> PagePlus<T, K> buildPagePlus() {
Integer pageNum = ServletUtils.getParameterToInt(PAGE_NUM, DEFAULT_PAGE_NUM); Integer pageNum = ServletUtils.getParameterToInt(PAGE_NUM, DEFAULT_PAGE_NUM);
Integer pageSize = ServletUtils.getParameterToInt(PAGE_SIZE, DEFAULT_PAGE_SIZE); Integer pageSize = ServletUtils.getParameterToInt(PAGE_SIZE, DEFAULT_PAGE_SIZE);
@ -70,6 +86,7 @@ public class PageUtils {
return page; return page;
} }
@Deprecated
public static <T> Page<T> buildPage() { public static <T> Page<T> buildPage() {
return buildPage(null, null); return buildPage(null, null);
} }
@ -79,7 +96,10 @@ public class PageUtils {
* *
* @param <T> domain 实体 * @param <T> domain 实体
* @return 分页对象 * @return 分页对象
* @deprecated 3.6.0 删除 请使用 {@link PageQuery#build()}
* 由于使用 Servlet 获取只能从 param 获取 灵活性降低 故将传参操作交给用户
*/ */
@Deprecated
public static <T> Page<T> buildPage(String defaultOrderByColumn, String defaultIsAsc) { public static <T> Page<T> buildPage(String defaultOrderByColumn, String defaultIsAsc) {
Integer pageNum = ServletUtils.getParameterToInt(PAGE_NUM, DEFAULT_PAGE_NUM); Integer pageNum = ServletUtils.getParameterToInt(PAGE_NUM, DEFAULT_PAGE_NUM);
Integer pageSize = ServletUtils.getParameterToInt(PAGE_SIZE, DEFAULT_PAGE_SIZE); Integer pageSize = ServletUtils.getParameterToInt(PAGE_SIZE, DEFAULT_PAGE_SIZE);
@ -115,6 +135,15 @@ public class PageUtils {
return null; return null;
} }
/**
* 构建 MP 普通分页对象
*
* @param <T> domain 实体
* @return 分页对象
* @deprecated 3.6.0 删除 请使用 {@link PageQuery#build()}
* 由于使用 Servlet 获取只能从 param 获取 灵活性降低 故将传参操作交给用户
*/
@Deprecated
public static <T, K> TableDataInfo<K> buildDataInfo(PagePlus<T, K> page) { public static <T, K> TableDataInfo<K> buildDataInfo(PagePlus<T, K> page) {
TableDataInfo<K> rspData = new TableDataInfo<>(); TableDataInfo<K> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK); rspData.setCode(HttpStatus.HTTP_OK);
@ -124,6 +153,10 @@ public class PageUtils {
return rspData; return rspData;
} }
/**
* @deprecated 3.6.0 删除 请使用 {@link TableDataInfo#build(IPage)}
*/
@Deprecated
public static <T> TableDataInfo<T> buildDataInfo(Page<T> page) { public static <T> TableDataInfo<T> buildDataInfo(Page<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>(); TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK); rspData.setCode(HttpStatus.HTTP_OK);
@ -133,6 +166,10 @@ public class PageUtils {
return rspData; return rspData;
} }
/**
* @deprecated 3.6.0 删除 请使用 {@link TableDataInfo#build(List)}
*/
@Deprecated
public static <T> TableDataInfo<T> buildDataInfo(List<T> list) { public static <T> TableDataInfo<T> buildDataInfo(List<T> list) {
TableDataInfo<T> rspData = new TableDataInfo<>(); TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK); rspData.setCode(HttpStatus.HTTP_OK);
@ -142,6 +179,10 @@ public class PageUtils {
return rspData; return rspData;
} }
/**
* @deprecated 3.6.0 删除 请使用 {@link TableDataInfo#build()}
*/
@Deprecated
public static <T> TableDataInfo<T> buildDataInfo() { public static <T> TableDataInfo<T> buildDataInfo() {
TableDataInfo<T> rspData = new TableDataInfo<>(); TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK); rspData.setCode(HttpStatus.HTTP_OK);

View File

@ -1,6 +1,6 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import com.google.common.collect.Lists; import cn.hutool.core.collection.IterUtil;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -23,7 +23,7 @@ import java.util.function.Consumer;
@SuppressWarnings(value = {"unchecked", "rawtypes"}) @SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedisUtils { public class RedisUtils {
private static RedissonClient client = SpringUtils.getBean(RedissonClient.class); private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
/** /**
* 限流 * 限流
@ -35,7 +35,7 @@ public class RedisUtils {
* @return -1 表示失败 * @return -1 表示失败
*/ */
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) { public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
RRateLimiter rateLimiter = client.getRateLimiter(key); RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS); rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) { if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits(); return rateLimiter.availablePermits();
@ -45,10 +45,10 @@ public class RedisUtils {
} }
/** /**
* 获取实例id * 获取客户端实例
*/ */
public static String getClientId() { public static RedissonClient getClient() {
return client.getId(); return CLIENT;
} }
/** /**
@ -59,13 +59,13 @@ public class RedisUtils {
* @param consumer 自定义处理 * @param consumer 自定义处理
*/ */
public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) { public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
RTopic topic = client.getTopic(channelKey); RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg); topic.publish(msg);
consumer.accept(msg); consumer.accept(msg);
} }
public static <T> void publish(String channelKey, T msg) { public static <T> void publish(String channelKey, T msg) {
RTopic topic = client.getTopic(channelKey); RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg); topic.publish(msg);
} }
@ -77,7 +77,7 @@ public class RedisUtils {
* @param consumer 自定义处理 * @param consumer 自定义处理
*/ */
public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) { public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
RTopic topic = client.getTopic(channelKey); RTopic topic = CLIENT.getTopic(channelKey);
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg)); topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
} }
@ -94,13 +94,13 @@ public class RedisUtils {
/** /**
* 缓存基本的对象保留当前对象 TTL 有效期 * 缓存基本的对象保留当前对象 TTL 有效期
* *
* @param key 缓存的键值 * @param key 缓存的键值
* @param value 缓存的值 * @param value 缓存的值
* @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90) * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
* @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案 * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
*/ */
public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) { public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
RBucket<Object> bucket = client.getBucket(key); RBucket<Object> bucket = CLIENT.getBucket(key);
if (isSaveTtl) { if (isSaveTtl) {
try { try {
bucket.setAndKeepTTL(value); bucket.setAndKeepTTL(value);
@ -123,11 +123,24 @@ public class RedisUtils {
* @param timeUnit 时间颗粒度 * @param timeUnit 时间颗粒度
*/ */
public static <T> void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) { public static <T> void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) {
RBucket<T> result = client.getBucket(key); RBucket<T> result = CLIENT.getBucket(key);
result.set(value); result.set(value);
result.expire(timeout, timeUnit); result.expire(timeout, timeUnit);
} }
/**
* 注册对象监听器
*
* key 监听器需开启 `notify-keyspace-events` redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addObjectListener(final String key, final ObjectListener listener) {
RBucket<T> result = CLIENT.getBucket(key);
result.addListener(listener);
}
/** /**
* 设置有效时间 * 设置有效时间
* *
@ -148,7 +161,7 @@ public class RedisUtils {
* @return true=设置成功false=设置失败 * @return true=设置成功false=设置失败
*/ */
public static boolean expire(final String key, final long timeout, final TimeUnit unit) { public static boolean expire(final String key, final long timeout, final TimeUnit unit) {
RBucket rBucket = client.getBucket(key); RBucket rBucket = CLIENT.getBucket(key);
return rBucket.expire(timeout, unit); return rBucket.expire(timeout, unit);
} }
@ -159,7 +172,7 @@ public class RedisUtils {
* @return 缓存键值对应的数据 * @return 缓存键值对应的数据
*/ */
public static <T> T getCacheObject(final String key) { public static <T> T getCacheObject(final String key) {
RBucket<T> rBucket = client.getBucket(key); RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.get(); return rBucket.get();
} }
@ -170,29 +183,26 @@ public class RedisUtils {
* @return 剩余存活时间 * @return 剩余存活时间
*/ */
public static <T> long getTimeToLive(final String key) { public static <T> long getTimeToLive(final String key) {
RBucket<T> rBucket = client.getBucket(key); RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.remainTimeToLive(); return rBucket.remainTimeToLive();
} }
/** /**
* 删除单个对象 * 删除单个对象
* *
* @param key * @param key 缓存的键值
*/ */
public static boolean deleteObject(final String key) { public static boolean deleteObject(final String key) {
return client.getBucket(key).delete(); return CLIENT.getBucket(key).delete();
} }
/* */
/** /**
* 删除集合对象 * 删除集合对象
* *
* @param collection 多个对象 * @param collection 多个对象
* @return
*/ */
public static void deleteObject(final Collection collection) { public static void deleteObject(final Collection collection) {
RBatch batch = client.createBatch(); RBatch batch = CLIENT.createBatch();
collection.forEach(t -> { collection.forEach(t -> {
batch.getBucket(t.toString()).deleteAsync(); batch.getBucket(t.toString()).deleteAsync();
}); });
@ -207,10 +217,23 @@ public class RedisUtils {
* @return 缓存的对象 * @return 缓存的对象
*/ */
public static <T> boolean setCacheList(final String key, final List<T> dataList) { public static <T> boolean setCacheList(final String key, final List<T> dataList) {
RList<T> rList = client.getList(key); RList<T> rList = CLIENT.getList(key);
return rList.addAll(dataList); return rList.addAll(dataList);
} }
/**
* 注册List监听器
*
* key 监听器需开启 `notify-keyspace-events` redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addListListener(final String key, final ObjectListener listener) {
RList<T> rList = CLIENT.getList(key);
rList.addListener(listener);
}
/** /**
* 获得缓存的list对象 * 获得缓存的list对象
* *
@ -218,7 +241,7 @@ public class RedisUtils {
* @return 缓存键值对应的数据 * @return 缓存键值对应的数据
*/ */
public static <T> List<T> getCacheList(final String key) { public static <T> List<T> getCacheList(final String key) {
RList<T> rList = client.getList(key); RList<T> rList = CLIENT.getList(key);
return rList.readAll(); return rList.readAll();
} }
@ -230,42 +253,68 @@ public class RedisUtils {
* @return 缓存数据的对象 * @return 缓存数据的对象
*/ */
public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) { public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
RSet<T> rSet = client.getSet(key); RSet<T> rSet = CLIENT.getSet(key);
return rSet.addAll(dataSet); return rSet.addAll(dataSet);
} }
/**
* 注册Set监听器
*
* key 监听器需开启 `notify-keyspace-events` redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addSetListener(final String key, final ObjectListener listener) {
RSet<T> rSet = CLIENT.getSet(key);
rSet.addListener(listener);
}
/** /**
* 获得缓存的set * 获得缓存的set
* *
* @param key * @param key 缓存的key
* @return * @return set对象
*/ */
public static <T> Set<T> getCacheSet(final String key) { public static <T> Set<T> getCacheSet(final String key) {
RSet<T> rSet = client.getSet(key); RSet<T> rSet = CLIENT.getSet(key);
return rSet.readAll(); return rSet.readAll();
} }
/** /**
* 缓存Map * 缓存Map
* *
* @param key * @param key 缓存的键值
* @param dataMap * @param dataMap 缓存的数据
*/ */
public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) { public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) { if (dataMap != null) {
RMap<String, T> rMap = client.getMap(key); RMap<String, T> rMap = CLIENT.getMap(key);
rMap.putAll(dataMap); rMap.putAll(dataMap);
} }
} }
/**
* 注册Map监听器
*
* key 监听器需开启 `notify-keyspace-events` redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addMapListener(final String key, final ObjectListener listener) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.addListener(listener);
}
/** /**
* 获得缓存的Map * 获得缓存的Map
* *
* @param key * @param key 缓存的键值
* @return * @return map对象
*/ */
public static <T> Map<String, T> getCacheMap(final String key) { public static <T> Map<String, T> getCacheMap(final String key) {
RMap<String, T> rMap = client.getMap(key); RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.getAll(rMap.keySet()); return rMap.getAll(rMap.keySet());
} }
@ -277,7 +326,7 @@ public class RedisUtils {
* @param value * @param value
*/ */
public static <T> void setCacheMapValue(final String key, final String hKey, final T value) { public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
RMap<String, T> rMap = client.getMap(key); RMap<String, T> rMap = CLIENT.getMap(key);
rMap.put(hKey, value); rMap.put(hKey, value);
} }
@ -289,7 +338,7 @@ public class RedisUtils {
* @return Hash中的对象 * @return Hash中的对象
*/ */
public static <T> T getCacheMapValue(final String key, final String hKey) { public static <T> T getCacheMapValue(final String key, final String hKey) {
RMap<String, T> rMap = client.getMap(key); RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.get(hKey); return rMap.get(hKey);
} }
@ -301,7 +350,7 @@ public class RedisUtils {
* @return Hash中的对象 * @return Hash中的对象
*/ */
public static <T> T delCacheMapValue(final String key, final String hKey) { public static <T> T delCacheMapValue(final String key, final String hKey) {
RMap<String, T> rMap = client.getMap(key); RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.remove(hKey); return rMap.remove(hKey);
} }
@ -313,7 +362,7 @@ public class RedisUtils {
* @return Hash对象集合 * @return Hash对象集合
*/ */
public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) { public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
RMap<K, V> rMap = client.getMap(key); RMap<K, V> rMap = CLIENT.getMap(key);
return rMap.getAll(hKeys); return rMap.getAll(hKeys);
} }
@ -324,7 +373,7 @@ public class RedisUtils {
* @return 对象列表 * @return 对象列表
*/ */
public static Collection<String> keys(final String pattern) { public static Collection<String> keys(final String pattern) {
Iterable<String> iterable = client.getKeys().getKeysByPattern(pattern); Iterable<String> iterable = CLIENT.getKeys().getKeysByPattern(pattern);
return Lists.newArrayList(iterable); return IterUtil.toList(iterable);
} }
} }

View File

@ -3,6 +3,8 @@ package com.ruoyi.common.utils;
import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpStatus;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@ -12,6 +14,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
* *
* @author ruoyi * @author ruoyi
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SecurityUtils { public class SecurityUtils {
/** /**

View File

@ -3,6 +3,8 @@ package com.ruoyi.common.utils;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpStatus;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
@ -19,6 +21,7 @@ import java.nio.charset.StandardCharsets;
* *
* @author ruoyi * @author ruoyi
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ServletUtils extends ServletUtil { public class ServletUtils extends ServletUtil {
/** /**

View File

@ -7,6 +7,8 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.*; import java.util.*;
@ -15,6 +17,7 @@ import java.util.*;
* *
* @author Lion Li * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringUtils extends org.apache.commons.lang3.StringUtils { public class StringUtils extends org.apache.commons.lang3.StringUtils {
/** /**

View File

@ -1,7 +1,8 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import org.slf4j.Logger; import lombok.AccessLevel;
import org.slf4j.LoggerFactory; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*; import java.util.concurrent.*;
@ -10,8 +11,9 @@ import java.util.concurrent.*;
* *
* @author ruoyi * @author ruoyi
*/ */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Threads { public class Threads {
private static final Logger logger = LoggerFactory.getLogger(Threads.class);
/** /**
* sleep等待,单位为毫秒 * sleep等待,单位为毫秒
@ -38,7 +40,7 @@ public class Threads {
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
pool.shutdownNow(); pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
logger.info("Pool did not terminate"); log.info("Pool did not terminate");
} }
} }
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
@ -67,7 +69,7 @@ public class Threads {
} }
} }
if (t != null) { if (t != null) {
logger.error(t.getMessage(), t); log.error(t.getMessage(), t);
} }
} }
} }

View File

@ -4,6 +4,8 @@ import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig; import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil; import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.lang.tree.parser.NodeParser; import cn.hutool.core.lang.tree.parser.NodeParser;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
@ -12,6 +14,7 @@ import java.util.List;
* *
* @author Lion Li * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TreeBuildUtils extends TreeUtil { public class TreeBuildUtils extends TreeUtil {
/** /**
@ -19,13 +22,8 @@ public class TreeBuildUtils extends TreeUtil {
*/ */
public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label"); public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
/** public static <T> List<Tree<Long>> build(List<T> list, Long parentId, NodeParser<T, Long> nodeParser) {
* 默认树父节点id return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
*/
public static final Long DEFAULT_PARENT_ID = 0L;
public static <T> List<Tree<Long>> build(List<T> list, NodeParser<T, Long> nodeParser) {
return TreeUtil.build(list, DEFAULT_PARENT_ID, DEFAULT_CONFIG, nodeParser);
} }
} }

View File

@ -1,8 +1,11 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException; import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator; import javax.validation.Validator;
import java.util.Set; import java.util.Set;
@ -11,9 +14,10 @@ import java.util.Set;
* *
* @author Lion Li * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ValidatorUtils { public class ValidatorUtils {
private static final Validator VALID = Validation.buildDefaultValidatorFactory().getValidator(); private static final Validator VALID = SpringUtils.getBean(Validator.class);
public static <T> void validate(T object, Class<?>... groups) { public static <T> void validate(T object, Class<?>... groups) {
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups); Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);

View File

@ -1,6 +1,8 @@
package com.ruoyi.common.utils.file; package com.ruoyi.common.utils.file;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -12,6 +14,7 @@ import java.nio.charset.StandardCharsets;
* *
* @author Lion Li * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils extends FileUtil { public class FileUtils extends FileUtil {
/** /**

View File

@ -7,6 +7,8 @@ import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.JsonUtils; import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.Map; import java.util.Map;
@ -17,6 +19,7 @@ import java.util.Map;
* @author Lion Li * @author Lion Li
*/ */
@Slf4j @Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AddressUtils { public class AddressUtils {
// IP地址查询 // IP地址查询

View File

@ -9,6 +9,8 @@ import com.ruoyi.common.excel.ExcelListener;
import com.ruoyi.common.excel.ExcelResult; import com.ruoyi.common.excel.ExcelResult;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.FileUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -21,6 +23,7 @@ import java.util.List;
* *
* @author Lion Li * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil { public class ExcelUtil {
/** /**

View File

@ -2,6 +2,8 @@ package com.ruoyi.common.utils.reflect;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -11,6 +13,7 @@ import java.lang.reflect.Method;
* @author Lion Li * @author Lion Li
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReflectUtils extends ReflectUtil { public class ReflectUtils extends ReflectUtil {
private static final String SETTER_PREFIX = "set"; private static final String SETTER_PREFIX = "set";

View File

@ -2,17 +2,26 @@ package com.ruoyi.common.utils.sql;
import com.ruoyi.common.exception.UtilException; import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/** /**
* sql操作工具类 * sql操作工具类
* *
* @author ruoyi * @author ruoyi
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SqlUtil { public class SqlUtil {
/**
* 定义常用的 sql关键字
*/
public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
/** /**
* 仅支持字母数字下划线空格逗号小数点支持多个字段排序 * 仅支持字母数字下划线空格逗号小数点支持多个字段排序
*/ */
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/** /**
* 检查字符防止注入绕过 * 检查字符防止注入绕过
@ -30,4 +39,19 @@ public class SqlUtil {
public static boolean isValidOrderBySql(String value) { public static boolean isValidOrderBySql(String value) {
return value.matches(SQL_PATTERN); return value.matches(SQL_PATTERN);
} }
/**
* SQL关键字检查
*/
public static void filterKeyword(String value) {
if (StringUtils.isEmpty(value)) {
return;
}
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords) {
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
throw new UtilException("参数存在SQL注入风险");
}
}
}
} }

View File

@ -0,0 +1,26 @@
package com.ruoyi.common.xss;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义xss校验注解
*
* @author Lion Li
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Constraint(validatedBy = {XssValidator.class})
public @interface Xss {
String message() default "不允许任何脚本运行";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,21 @@
package com.ruoyi.common.xss;
import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HtmlUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 自定义xss校验注解实现
*
* @author Lion Li
*/
public class XssValidator implements ConstraintValidator<Xss, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);
}
}

View File

@ -33,10 +33,12 @@ public class TestBatchController extends BaseController {
/** /**
* 新增批量方法 可完美替代 saveBatch 秒级插入上万数据 (对mysql负荷较大) * 新增批量方法 可完美替代 saveBatch 秒级插入上万数据 (对mysql负荷较大)
*
* 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度
*/ */
@ApiOperation(value = "新增批量方法") @ApiOperation(value = "新增批量方法")
@PostMapping("/add") @PostMapping("/add")
// @DataSource(DataSourceType.SLAVE) // @DS("slave")
public AjaxResult<Void> add() { public AjaxResult<Void> add() {
List<TestDemo> list = new ArrayList<>(); List<TestDemo> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
@ -47,10 +49,12 @@ public class TestBatchController extends BaseController {
/** /**
* 新增或更新 可完美替代 saveOrUpdateBatch 高性能 * 新增或更新 可完美替代 saveOrUpdateBatch 高性能
*
* 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度
*/ */
@ApiOperation(value = "新增或更新批量方法") @ApiOperation(value = "新增或更新批量方法")
@PostMapping("/addOrUpdate") @PostMapping("/addOrUpdate")
// @DataSource(DataSourceType.SLAVE) // @DS("slave")
public AjaxResult<Void> addOrUpdate() { public AjaxResult<Void> addOrUpdate() {
List<TestDemo> list = new ArrayList<>(); List<TestDemo> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
@ -72,7 +76,7 @@ public class TestBatchController extends BaseController {
*/ */
@ApiOperation(value = "删除批量方法") @ApiOperation(value = "删除批量方法")
@DeleteMapping() @DeleteMapping()
// @DataSource(DataSourceType.SLAVE) // @DS("slave")
public AjaxResult<Void> remove() { public AjaxResult<Void> remove() {
return toAjax(iTestDemoService.remove(new LambdaQueryWrapper<TestDemo>() return toAjax(iTestDemoService.remove(new LambdaQueryWrapper<TestDemo>()
.eq(TestDemo::getOrderNum, -1L)) ? 1 : 0); .eq(TestDemo::getOrderNum, -1L)) ? 1 : 0);

View File

@ -5,6 +5,7 @@ import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit; import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.AddGroup; import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup; import com.ruoyi.common.core.validate.EditGroup;
@ -54,8 +55,8 @@ public class TestDemoController extends BaseController {
@ApiOperation("查询测试单表列表") @ApiOperation("查询测试单表列表")
@PreAuthorize("@ss.hasPermi('demo:demo:list')") @PreAuthorize("@ss.hasPermi('demo:demo:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<TestDemoVo> list(@Validated(QueryGroup.class) TestDemoBo bo) { public TableDataInfo<TestDemoVo> list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
return iTestDemoService.queryPageList(bo); return iTestDemoService.queryPageList(bo, pageQuery);
} }
/** /**
@ -64,8 +65,8 @@ public class TestDemoController extends BaseController {
@ApiOperation("自定义分页查询") @ApiOperation("自定义分页查询")
@PreAuthorize("@ss.hasPermi('demo:demo:list')") @PreAuthorize("@ss.hasPermi('demo:demo:list')")
@GetMapping("/page") @GetMapping("/page")
public TableDataInfo<TestDemoVo> page(@Validated(QueryGroup.class) TestDemoBo bo) { public TableDataInfo<TestDemoVo> page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
return iTestDemoService.customPageList(bo); return iTestDemoService.customPageList(bo, pageQuery);
} }
@ApiOperation("导入测试-校验") @ApiOperation("导入测试-校验")

View File

@ -1,16 +1,15 @@
package com.ruoyi.demo.domain.bo; package com.ruoyi.demo.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup; import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup; import com.ruoyi.common.core.validate.EditGroup;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import javax.validation.constraints.*;
import java.util.Date; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import com.ruoyi.common.core.domain.BaseEntity;
/** /**
* 测试单表业务对象 test_demo * 测试单表业务对象 test_demo
@ -66,29 +65,4 @@ public class TestDemoBo extends BaseEntity {
@NotBlank(message = "值不能为空", groups = { AddGroup.class, EditGroup.class }) @NotBlank(message = "值不能为空", groups = { AddGroup.class, EditGroup.class })
private String value; private String value;
/**
* 分页大小
*/
@ApiModelProperty("分页大小")
private Integer pageSize;
/**
* 当前页数
*/
@ApiModelProperty("当前页数")
private Integer pageNum;
/**
* 排序列
*/
@ApiModelProperty("排序列")
private String orderByColumn;
/**
* 排序的方向desc或者asc
*/
@ApiModelProperty(value = "排序的方向", example = "asc,desc")
private String isAsc;
} }

View File

@ -51,29 +51,4 @@ public class TestTreeBo extends TreeEntity {
@NotBlank(message = "树节点名不能为空", groups = { AddGroup.class, EditGroup.class }) @NotBlank(message = "树节点名不能为空", groups = { AddGroup.class, EditGroup.class })
private String treeName; private String treeName;
/**
* 分页大小
*/
@ApiModelProperty("分页大小")
private Integer pageSize;
/**
* 当前页数
*/
@ApiModelProperty("当前页数")
private Integer pageNum;
/**
* 排序列
*/
@ApiModelProperty("排序列")
private String orderByColumn;
/**
* 排序的方向desc或者asc
*/
@ApiModelProperty(value = "排序的方向", example = "asc,desc")
private String isAsc;
} }

View File

@ -1,12 +1,20 @@
package com.ruoyi.demo.mapper; package com.ruoyi.demo.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.annotation.DataColumn;
import com.ruoyi.common.annotation.DataPermission;
import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus; import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
import com.ruoyi.demo.domain.TestDemo; import com.ruoyi.demo.domain.TestDemo;
import com.ruoyi.demo.domain.vo.TestDemoVo; import com.ruoyi.demo.domain.vo.TestDemoVo;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/** /**
* 测试单表Mapper接口 * 测试单表Mapper接口
* *
@ -15,6 +23,37 @@ import org.apache.ibatis.annotations.Param;
*/ */
public interface TestDemoMapper extends BaseMapperPlus<TestDemo> { public interface TestDemoMapper extends BaseMapperPlus<TestDemo> {
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
Page<TestDemoVo> customPageList(@Param("page") Page<TestDemo> page, @Param("ew") Wrapper<TestDemo> wrapper); Page<TestDemoVo> customPageList(@Param("page") Page<TestDemo> page, @Param("ew") Wrapper<TestDemo> wrapper);
@Override
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
<P extends IPage<TestDemo>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
@Override
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
List<TestDemo> selectList(@Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
@Override
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
int updateById(@Param(Constants.ENTITY) TestDemo entity);
@Override
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
} }

View File

@ -1,5 +1,7 @@
package com.ruoyi.demo.mapper; package com.ruoyi.demo.mapper;
import com.ruoyi.common.annotation.DataColumn;
import com.ruoyi.common.annotation.DataPermission;
import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus; import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
import com.ruoyi.demo.domain.TestTree; import com.ruoyi.demo.domain.TestTree;
@ -9,6 +11,10 @@ import com.ruoyi.demo.domain.TestTree;
* @author Lion Li * @author Lion Li
* @date 2021-07-26 * @date 2021-07-26
*/ */
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
public interface TestTreeMapper extends BaseMapperPlus<TestTree> { public interface TestTreeMapper extends BaseMapperPlus<TestTree> {
} }

View File

@ -1,5 +1,6 @@
package com.ruoyi.demo.service; package com.ruoyi.demo.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.demo.domain.TestDemo; import com.ruoyi.demo.domain.TestDemo;
import com.ruoyi.demo.domain.vo.TestDemoVo; import com.ruoyi.demo.domain.vo.TestDemoVo;
import com.ruoyi.demo.domain.bo.TestDemoBo; import com.ruoyi.demo.domain.bo.TestDemoBo;
@ -26,12 +27,12 @@ public interface ITestDemoService extends IServicePlus<TestDemo, TestDemoVo> {
/** /**
* 查询列表 * 查询列表
*/ */
TableDataInfo<TestDemoVo> queryPageList(TestDemoBo bo); TableDataInfo<TestDemoVo> queryPageList(TestDemoBo bo, PageQuery pageQuery);
/** /**
* 自定义分页查询 * 自定义分页查询
*/ */
TableDataInfo<TestDemoVo> customPageList(TestDemoBo bo); TableDataInfo<TestDemoVo> customPageList(TestDemoBo bo, PageQuery pageQuery);
/** /**
* 查询列表 * 查询列表

View File

@ -1,15 +1,13 @@
package com.ruoyi.demo.service.impl; package com.ruoyi.demo.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import com.ruoyi.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl; import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.core.page.PagePlus;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.PageUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.demo.domain.TestDemo; import com.ruoyi.demo.domain.TestDemo;
import com.ruoyi.demo.domain.bo.TestDemoBo; import com.ruoyi.demo.domain.bo.TestDemoBo;
import com.ruoyi.demo.domain.vo.TestDemoVo; import com.ruoyi.demo.domain.vo.TestDemoVo;
@ -35,24 +33,23 @@ public class TestDemoServiceImpl extends ServicePlusImpl<TestDemoMapper, TestDem
return getVoById(id); return getVoById(id);
} }
@DataScope(isUser = true)
@Override @Override
public TableDataInfo<TestDemoVo> queryPageList(TestDemoBo bo) { public TableDataInfo<TestDemoVo> queryPageList(TestDemoBo bo, PageQuery pageQuery) {
PagePlus<TestDemo, TestDemoVo> result = pageVo(PageUtils.buildPagePlus(), buildQueryWrapper(bo)); LambdaQueryWrapper<TestDemo> lqw = buildQueryWrapper(bo);
return PageUtils.buildDataInfo(result); Page<TestDemoVo> result = pageVo(pageQuery.build(), lqw);
return TableDataInfo.build(result);
} }
/** /**
* 自定义分页查询 * 自定义分页查询
*/ */
@DataScope(isUser = true)
@Override @Override
public TableDataInfo<TestDemoVo> customPageList(TestDemoBo bo) { public TableDataInfo<TestDemoVo> customPageList(TestDemoBo bo, PageQuery pageQuery) {
Page<TestDemoVo> result = baseMapper.customPageList(PageUtils.buildPage(), buildQueryWrapper(bo)); LambdaQueryWrapper<TestDemo> lqw = buildQueryWrapper(bo);
return PageUtils.buildDataInfo(result); Page<TestDemoVo> result = baseMapper.customPageList(pageQuery.build(), lqw);
return TableDataInfo.build(result);
} }
@DataScope(isUser = true)
@Override @Override
public List<TestDemoVo> queryList(TestDemoBo bo) { public List<TestDemoVo> queryList(TestDemoBo bo) {
return listVo(buildQueryWrapper(bo)); return listVo(buildQueryWrapper(bo));
@ -60,14 +57,11 @@ public class TestDemoServiceImpl extends ServicePlusImpl<TestDemoMapper, TestDem
private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) { private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) {
Map<String, Object> params = bo.getParams(); Map<String, Object> params = bo.getParams();
Object dataScope = params.get("dataScope");
LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery(); LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey()); lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey());
lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue()); lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
TestDemo::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); TestDemo::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
lqw.apply(dataScope != null && StringUtils.isNotBlank(dataScope.toString()),
dataScope != null ? dataScope.toString() : null);
return lqw; return lqw;
} }

View File

@ -3,7 +3,6 @@ package com.ruoyi.demo.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl; import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.demo.domain.TestTree; import com.ruoyi.demo.domain.TestTree;
@ -23,7 +22,7 @@ import java.util.Map;
* @author Lion Li * @author Lion Li
* @date 2021-07-26 * @date 2021-07-26
*/ */
//@DataSource(DataSourceType.SLAVE) // 切换从库查询 // @DS("slave") // 切换从库查询
@Service @Service
public class TestTreeServiceImpl extends ServicePlusImpl<TestTreeMapper, TestTree, TestTreeVo> implements ITestTreeService { public class TestTreeServiceImpl extends ServicePlusImpl<TestTreeMapper, TestTree, TestTreeVo> implements ITestTreeService {
@ -32,22 +31,19 @@ public class TestTreeServiceImpl extends ServicePlusImpl<TestTreeMapper, TestTre
return getVoById(id); return getVoById(id);
} }
// @DataSource(DataSourceType.SLAVE) // 切换从库查询 // @DS("slave") // 切换从库查询
@DataScope(isUser = true)
@Override @Override
public List<TestTreeVo> queryList(TestTreeBo bo) { public List<TestTreeVo> queryList(TestTreeBo bo) {
return listVo(buildQueryWrapper(bo)); LambdaQueryWrapper<TestTree> lqw = buildQueryWrapper(bo);
return listVo(lqw);
} }
private LambdaQueryWrapper<TestTree> buildQueryWrapper(TestTreeBo bo) { private LambdaQueryWrapper<TestTree> buildQueryWrapper(TestTreeBo bo) {
Map<String, Object> params = bo.getParams(); Map<String, Object> params = bo.getParams();
Object dataScope = params.get("dataScope");
LambdaQueryWrapper<TestTree> lqw = Wrappers.lambdaQuery(); LambdaQueryWrapper<TestTree> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName()); lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
TestTree::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); TestTree::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
lqw.apply(dataScope != null && StringUtils.isNotBlank(dataScope.toString()),
dataScope != null ? dataScope.toString() : null);
return lqw; return lqw;
} }

View File

@ -0,0 +1,108 @@
package com.ruoyi.framework.interceptor;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.ruoyi.framework.handler.PlusDataPermissionHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* 数据权限拦截器
*
* @author Lion Li
* @version 3.5.0
*/
public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 检查忽略注解
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
return;
}
// 检查是否无效 无数据权限注解
if (dataPermissionHandler.isInvalid(ms.getId())) {
return;
}
// 解析 sql 分配对应方法
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
}
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpSh.mappedStatement();
SqlCommandType sct = ms.getSqlCommandType();
if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
return;
}
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
}
}
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
SelectBody selectBody = select.getSelectBody();
if (selectBody instanceof PlainSelect) {
this.setWhere((PlainSelect) selectBody, (String) obj);
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOperationList = (SetOperationList) selectBody;
List<SelectBody> selectBodyList = setOperationList.getSelects();
selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
}
}
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
if (null != sqlSegment) {
update.setWhere(sqlSegment);
}
}
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
if (null != sqlSegment) {
delete.setWhere(sqlSegment);
}
}
/**
* 设置 where 条件
*
* @param plainSelect 查询对象
* @param mappedStatementId 执行方法id
*/
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
if (null != sqlSegment) {
plainSelect.setWhere(sqlSegment);
}
}
}

View File

@ -1,48 +1,55 @@
package com.ruoyi.framework.Interceptor; package com.ruoyi.framework.interceptor;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TransmittableThreadLocal;
import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
import com.ruoyi.common.utils.JsonUtils; import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.yomahub.tlog.context.TLogContext; import com.ruoyi.common.utils.spring.SpringUtils;
import com.yomahub.tlog.web.interceptor.AbsTLogWebHandlerMethodInterceptor;
import com.yomahub.tlog.web.wrapper.RequestWrapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.lang3.time.StopWatch;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.util.Map; import java.util.Map;
/** /**
* 重写Tlog web的调用时间统计拦截器 * web的调用时间统计拦截器
* dev环境有效
* *
* @author Lion Li * @author Lion Li
* @since 3.3.0 * @since 3.3.0
*/ */
@Slf4j @Slf4j
public class PlusWebInvokeTimeInterceptor extends AbsTLogWebHandlerMethodInterceptor { public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
private final TransmittableThreadLocal<StopWatch> invokeTimeTL = new TransmittableThreadLocal<>(); private final TransmittableThreadLocal<StopWatch> invokeTimeTL = new TransmittableThreadLocal<>();
@Override @Override
public boolean preHandleByHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (TLogContext.enableInvokeTimePrint()) { if (!"prod".equals(SpringUtils.getActiveProfile())) {
String url = request.getMethod() + " " + request.getRequestURI(); String url = request.getMethod() + " " + request.getRequestURI();
// 打印请求参数 // 打印请求参数
if (isJsonRequest(request)) { if (isJsonRequest(request)) {
String jsonParam = new RequestWrapper(request).getBodyString(); String jsonParam = "";
log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam); if (request instanceof RepeatedlyRequestWrapper) {
BufferedReader reader = request.getReader();
jsonParam = IoUtil.read(reader);
}
log.debug("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
} else { } else {
Map<String, String[]> parameterMap = request.getParameterMap(); Map<String, String[]> parameterMap = request.getParameterMap();
if (MapUtil.isNotEmpty(parameterMap)) { if (MapUtil.isNotEmpty(parameterMap)) {
String parameters = JsonUtils.toJsonString(parameterMap); String parameters = JsonUtils.toJsonString(parameterMap);
log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters); log.debug("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
} else { } else {
log.info("[PLUS]开始请求 => URL[{}],无参数", url); log.debug("[PLUS]开始请求 => URL[{}],无参数", url);
} }
} }
@ -54,16 +61,16 @@ public class PlusWebInvokeTimeInterceptor extends AbsTLogWebHandlerMethodInterce
} }
@Override @Override
public void postHandleByHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
} }
@Override @Override
public void afterCompletionByHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (TLogContext.enableInvokeTimePrint()) { if (!"prod".equals(SpringUtils.getActiveProfile())) {
StopWatch stopWatch = invokeTimeTL.get(); StopWatch stopWatch = invokeTimeTL.get();
stopWatch.stop(); stopWatch.stop();
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime()); log.debug("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
invokeTimeTL.remove(); invokeTimeTL.remove();
} }
} }

View File

@ -8,22 +8,21 @@ import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.service.UserService; import com.ruoyi.common.core.service.UserService;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Map;
/** /**
* 数据过滤处理 * 数据过滤处理
* *
* @author Lion Li * @author Lion Li
* @deprecated 3.6.0 移除 {@link com.ruoyi.framework.handler.PlusDataPermissionHandler}
*/ */
@Aspect @Aspect
@Component @Component
@Deprecated
public class DataScopeAspect { public class DataScopeAspect {
/** /**
@ -137,9 +136,6 @@ public class DataScopeAspect {
if (params instanceof BaseEntity) { if (params instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) params; BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, sql); baseEntity.getParams().put(DATA_SCOPE, sql);
} else {
Map<String, Object> invoke = ReflectUtils.invokeGetter(params, "params");
invoke.put(DATA_SCOPE, sql);
} }
} }
} }

View File

@ -18,10 +18,12 @@ import java.util.Objects;
* 多数据源处理 * 多数据源处理
* *
* @author Lion Li * @author Lion Li
* @deprecated 3.6.0 移除 使用原生方法处理 功能更全
*/ */
@Aspect @Aspect
@Order(-500) @Order(-500)
@Component @Component
@Deprecated
public class DataSourceAspect { public class DataSourceAspect {
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"

View File

@ -54,7 +54,7 @@ public class RateLimiterAspect {
stringBuffer.append(ServletUtils.getClientIP()).append("-"); stringBuffer.append(ServletUtils.getClientIP()).append("-");
} else if (rateLimiter.limitType() == LimitType.CLUSTER){ } else if (rateLimiter.limitType() == LimitType.CLUSTER){
// 获取客户端实例id // 获取客户端实例id
stringBuffer.append(RedisUtils.getClientId()).append("-"); stringBuffer.append(RedisUtils.getClient().getId()).append("-");
} }
MethodSignature signature = (MethodSignature) point.getSignature(); MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod(); Method method = signature.getMethod();

View File

@ -1,6 +1,9 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.injector.ISqlInjector; import com.baomidou.mybatisplus.core.injector.ISqlInjector;
@ -10,6 +13,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInt
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.ruoyi.common.core.mybatisplus.methods.InsertAll; import com.ruoyi.common.core.mybatisplus.methods.InsertAll;
import com.ruoyi.framework.handler.CreateAndUpdateMetaObjectHandler; import com.ruoyi.framework.handler.CreateAndUpdateMetaObjectHandler;
import com.ruoyi.framework.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -30,6 +34,8 @@ public class MybatisPlusConfig {
@Bean @Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() { public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限处理
interceptor.addInnerInterceptor(dataPermissionInterceptor());
// 分页插件 // 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor()); interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 乐观锁插件 // 乐观锁插件
@ -37,6 +43,13 @@ public class MybatisPlusConfig {
return interceptor; return interceptor;
} }
/**
* 数据权限拦截器
*/
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
return new PlusDataPermissionInterceptor();
}
/** /**
* 分页插件自动识别数据库类型 * 分页插件自动识别数据库类型
*/ */
@ -79,24 +92,33 @@ public class MybatisPlusConfig {
}; };
} }
/**
* 使用网卡信息绑定雪花生成器
* 防止集群雪花ID重复
*/
@Bean
public IdentifierGenerator idGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
/** /**
* PaginationInnerInterceptor 分页插件自动识别数据库类型 * PaginationInnerInterceptor 分页插件自动识别数据库类型
* https://baomidou.com/guide/interceptor-pagination.html * https://baomidou.com/pages/97710a/
* OptimisticLockerInnerInterceptor 乐观锁插件 * OptimisticLockerInnerInterceptor 乐观锁插件
* https://baomidou.com/guide/interceptor-optimistic-locker.html * https://baomidou.com/pages/0d93c0/
* MetaObjectHandler 元对象字段填充控制器 * MetaObjectHandler 元对象字段填充控制器
* https://baomidou.com/guide/auto-fill-metainfo.html * https://baomidou.com/pages/4c6bcf/
* ISqlInjector sql注入器 * ISqlInjector sql注入器
* https://baomidou.com/guide/sql-injector.html * https://baomidou.com/pages/42ea4a/
* BlockAttackInnerInterceptor 如果是对全表的删除或更新操作就会终止该操作 * BlockAttackInnerInterceptor 如果是对全表的删除或更新操作就会终止该操作
* https://baomidou.com/guide/interceptor-block-attack.html * https://baomidou.com/pages/f9a237/
* IllegalSQLInnerInterceptor sql性能规范插件(垃圾SQL拦截) * IllegalSQLInnerInterceptor sql性能规范插件(垃圾SQL拦截)
* IdentifierGenerator 自定义主键策略 * IdentifierGenerator 自定义主键策略
* https://baomidou.com/guide/id-generator.html * https://baomidou.com/pages/568eb2/
* TenantLineInnerInterceptor 多租户插件 * TenantLineInnerInterceptor 多租户插件
* https://baomidou.com/guide/interceptor-tenant-line.html * https://baomidou.com/pages/aef2f2/
* DynamicTableNameInnerInterceptor 动态表名插件 * DynamicTableNameInnerInterceptor 动态表名插件
* https://baomidou.com/guide/interceptor-dynamic-table-name.html * https://baomidou.com/pages/2a45ff/
*/ */
} }

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
/** /**
* redis配置 * redis配置
@ -34,158 +35,164 @@ import java.util.Map;
@EnableCaching @EnableCaching
public class RedisConfig extends CachingConfigurerSupport { public class RedisConfig extends CachingConfigurerSupport {
private static final String REDIS_PROTOCOL_PREFIX = "redis://"; private static final String REDIS_PROTOCOL_PREFIX = "redis://";
private static final String REDISS_PROTOCOL_PREFIX = "rediss://"; private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
@Autowired @Autowired
private RedisProperties redisProperties; private RedisProperties redisProperties;
@Autowired @Autowired
private RedissonProperties redissonProperties; private RedissonProperties redissonProperties;
@Bean(destroyMethod = "shutdown") @Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(RedissonClient.class) @ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redisson() throws IOException { public RedissonClient redisson() throws IOException {
String prefix = REDIS_PROTOCOL_PREFIX; String prefix = REDIS_PROTOCOL_PREFIX;
if (redisProperties.isSsl()) { if (redisProperties.isSsl()) {
prefix = REDISS_PROTOCOL_PREFIX; prefix = REDISS_PROTOCOL_PREFIX;
} }
Config config = new Config(); Config config = new Config();
config.setThreads(redissonProperties.getThreads()) config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads()) .setNettyThreads(redissonProperties.getNettyThreads())
.setCodec(JsonJacksonCodec.INSTANCE) .setCodec(JsonJacksonCodec.INSTANCE)
.setTransportMode(redissonProperties.getTransportMode()); .setTransportMode(redissonProperties.getTransportMode());
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNotNull(singleServerConfig)) { if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式 // 使用单机模式
config.useSingleServer() config.useSingleServer()
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort()) .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue()) .setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setDatabase(redisProperties.getDatabase()) .setDatabase(redisProperties.getDatabase())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null) .setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(singleServerConfig.getTimeout()) .setTimeout(singleServerConfig.getTimeout())
.setRetryAttempts(singleServerConfig.getRetryAttempts()) .setRetryAttempts(singleServerConfig.getRetryAttempts())
.setRetryInterval(singleServerConfig.getRetryInterval()) .setRetryInterval(singleServerConfig.getRetryInterval())
.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection()) .setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
.setClientName(singleServerConfig.getClientName()) .setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize()) .setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize()) .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize()) .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize()) .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval()); .setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
} }
// 集群配置方式 参考下方注释 // 集群配置方式 参考下方注释
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig(); RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) { if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 使用集群模式 // 使用集群模式
config.useClusterServers() String finalPrefix = prefix;
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue()) List<String> nodes = redisProperties.getCluster().getNodes()
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null) .stream()
.setTimeout(clusterServersConfig.getTimeout()) .map(node -> finalPrefix + node)
.setRetryAttempts(clusterServersConfig.getRetryAttempts()) .collect(Collectors.toList());
.setRetryInterval(clusterServersConfig.getRetryInterval())
.setSubscriptionsPerConnection(clusterServersConfig.getSubscriptionsPerConnection())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setPingConnectionInterval(clusterServersConfig.getPingConnectionInterval())
.setSubscriptionConnectionMinimumIdleSize(clusterServersConfig.getSubscriptionConnectionMinimumIdleSize())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setDnsMonitoringInterval(clusterServersConfig.getDnsMonitoringInterval())
.setFailedSlaveReconnectionInterval(clusterServersConfig.getFailedSlaveReconnectionInterval())
.setScanInterval(clusterServersConfig.getScanInterval())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode())
.setNodeAddresses(redisProperties.getCluster().getNodes());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("初始化 redis 配置");
return redissonClient;
}
/** config.useClusterServers()
* 整合spring-cache .setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
*/ .setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
@Bean .setTimeout(clusterServersConfig.getTimeout())
public CacheManager cacheManager(RedissonClient redissonClient) { .setRetryAttempts(clusterServersConfig.getRetryAttempts())
List<RedissonProperties.CacheGroup> cacheGroup = redissonProperties.getCacheGroup(); .setRetryInterval(clusterServersConfig.getRetryInterval())
Map<String, CacheConfig> config = new HashMap<>(); .setSubscriptionsPerConnection(clusterServersConfig.getSubscriptionsPerConnection())
for (RedissonProperties.CacheGroup group : cacheGroup) { .setClientName(clusterServersConfig.getClientName())
CacheConfig cacheConfig = new CacheConfig(group.getTtl(), group.getMaxIdleTime()); .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
cacheConfig.setMaxSize(group.getMaxSize()); .setPingConnectionInterval(clusterServersConfig.getPingConnectionInterval())
config.put(group.getGroupId(), cacheConfig); .setSubscriptionConnectionMinimumIdleSize(clusterServersConfig.getSubscriptionConnectionMinimumIdleSize())
} .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE); .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
} .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setDnsMonitoringInterval(clusterServersConfig.getDnsMonitoringInterval())
.setFailedSlaveReconnectionInterval(clusterServersConfig.getFailedSlaveReconnectionInterval())
.setScanInterval(clusterServersConfig.getScanInterval())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode())
.setNodeAddresses(nodes);
}
RedissonClient redissonClient = Redisson.create(config);
log.info("初始化 redis 配置");
return redissonClient;
}
/** /**
* redis集群配置 yml * 整合spring-cache
* */
* --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉) @Bean
* spring: public CacheManager cacheManager(RedissonClient redissonClient) {
* redis: List<RedissonProperties.CacheGroup> cacheGroup = redissonProperties.getCacheGroup();
* cluster: Map<String, CacheConfig> config = new HashMap<>();
* nodes: for (RedissonProperties.CacheGroup group : cacheGroup) {
* - 192.168.0.100:6379 CacheConfig cacheConfig = new CacheConfig(group.getTtl(), group.getMaxIdleTime());
* - 192.168.0.101:6379 cacheConfig.setMaxSize(group.getMaxSize());
* - 192.168.0.102:6379 config.put(group.getGroupId(), cacheConfig);
* # 密码 }
* password: return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
* # 连接超时时间 }
* timeout: 10s
* # 是否开启ssl /**
* ssl: false * redis集群配置 yml
* *
* redisson: * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
* # 线程池数量 * spring:
* threads: 16 * redis:
* # Netty线程池数量 * cluster:
* nettyThreads: 32 * nodes:
* # 传输模式 * - 192.168.0.100:6379
* transportMode: "NIO" * - 192.168.0.101:6379
* # 集群配置 * - 192.168.0.102:6379
* clusterServersConfig: * # 密码
* # 客户端名称 * password:
* clientName: ${ruoyi.name} * # 连接超时时间
* # master最小空闲连接数 * timeout: 10s
* masterConnectionMinimumIdleSize: 32 * # 是否开启ssl
* # master连接池大小 * ssl: false
* masterConnectionPoolSize: 64 *
* # slave最小空闲连接数 * redisson:
* slaveConnectionMinimumIdleSize: 32 * # 线程池数量
* # slave连接池大小 * threads: 16
* slaveConnectionPoolSize: 64 * # Netty线程池数量
* # 连接空闲超时单位毫秒 * nettyThreads: 32
* idleConnectionTimeout: 10000 * # 传输模式
* # ping连接间隔 * transportMode: "NIO"
* pingConnectionInterval: 1000 * # 集群配置
* # 命令等待超时单位毫秒 * clusterServersConfig:
* timeout: 3000 * # 客户端名称
* # 如果尝试在此限制之内发送成功则开始启用 timeout 计时 * clientName: ${ruoyi.name}
* retryAttempts: 3 * # master最小空闲连接数
* # 命令重试发送时间间隔单位毫秒 * masterConnectionMinimumIdleSize: 32
* retryInterval: 1500 * # master连接池大小
* # 从可用服务器的内部列表中排除 Redis Slave 重新连接尝试的间隔 * masterConnectionPoolSize: 64
* failedSlaveReconnectionInterval: 3000 * # slave最小空闲连接数
* # 发布和订阅连接池最小空闲连接数 * slaveConnectionMinimumIdleSize: 32
* subscriptionConnectionMinimumIdleSize: 1 * # slave连接池大小
* # 发布和订阅连接池大小 * slaveConnectionPoolSize: 64
* subscriptionConnectionPoolSize: 50 * # 连接空闲超时单位毫秒
* # 单个连接最大订阅数量 * idleConnectionTimeout: 10000
* subscriptionsPerConnection: 5 * # ping连接间隔
* # 扫描间隔 * pingConnectionInterval: 1000
* scanInterval: 1000 * # 命令等待超时单位毫秒
* # DNS监测时间间隔单位毫秒 * timeout: 3000
* dnsMonitoringInterval: 5000 * # 如果尝试在此限制之内发送成功则开始启用 timeout 计时
* # 读取模式 * retryAttempts: 3
* readMode: "SLAVE" * # 命令重试发送时间间隔单位毫秒
* # 订阅模式 * retryInterval: 1500
* subscriptionMode: "MASTER" * # 从可用服务器的内部列表中排除 Redis Slave 重新连接尝试的间隔
*/ * failedSlaveReconnectionInterval: 3000
* # 发布和订阅连接池最小空闲连接数
* subscriptionConnectionMinimumIdleSize: 1
* # 发布和订阅连接池大小
* subscriptionConnectionPoolSize: 50
* # 单个连接最大订阅数量
* subscriptionsPerConnection: 5
* # 扫描间隔
* scanInterval: 1000
* # DNS监测时间间隔单位毫秒
* dnsMonitoringInterval: 5000
* # 读取模式
* readMode: "SLAVE"
* # 订阅模式
* subscriptionMode: "MASTER"
*/
} }

View File

@ -1,6 +1,6 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import com.ruoyi.framework.Interceptor.PlusWebInvokeTimeInterceptor; import com.ruoyi.framework.interceptor.PlusWebInvokeTimeInterceptor;
import com.yomahub.tlog.web.interceptor.TLogWebInterceptor; import com.yomahub.tlog.web.interceptor.TLogWebInterceptor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@ -1,13 +1,8 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import com.yomahub.tlog.core.aop.AspectLogAop; import com.yomahub.tlog.springboot.TLogWebAutoConfiguration;
import com.yomahub.tlog.spring.TLogPropertyInit; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import com.yomahub.tlog.spring.TLogSpringAware;
import com.yomahub.tlog.springboot.property.TLogProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
/** /**
* 整合 TLog 框架配置 * 整合 TLog 框架配置
@ -15,29 +10,9 @@ import org.springframework.core.annotation.Order;
* @author Lion Li * @author Lion Li
* @since 3.3.0 * @since 3.3.0
*/ */
@Order(-999)
@Configuration @Configuration
@Import(TLogProperty.class) // 排除 web 自动配置 自定义实现
@EnableAutoConfiguration(exclude = TLogWebAutoConfiguration.class)
public class TLogConfig { public class TLogConfig {
@Bean
public TLogPropertyInit tLogPropertyInit(TLogProperty tLogProperty) {
TLogPropertyInit tLogPropertyInit = new TLogPropertyInit();
tLogPropertyInit.setPattern(tLogProperty.getPattern());
tLogPropertyInit.setEnableInvokeTimePrint(tLogProperty.enableInvokeTimePrint());
tLogPropertyInit.setIdGenerator(tLogProperty.getIdGenerator());
tLogPropertyInit.setMdcEnable(tLogProperty.getMdcEnable());
return tLogPropertyInit;
}
@Bean
public TLogSpringAware tLogSpringAware(){
return new TLogSpringAware();
}
@Bean
public AspectLogAop aspectLogAop() {
return new AspectLogAop();
}
} }

Some files were not shown because too many files have changed in this diff Show More