Merge remote-tracking branch 'origin/dev'

# Conflicts:
#	README.md
This commit is contained in:
疯狂的狮子li 2021-09-28 09:48:35 +08:00
commit 85762f7c92
165 changed files with 4448 additions and 4210 deletions

View File

@ -4,16 +4,19 @@
[![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)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-3.1.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-3.2.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![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-17](https://img.shields.io/badge/JDK-17-green.svg)]()
RuoYi-Vue-Plus 是基于 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重构权限鉴权(仅供学习不推荐上生产) |
| 单体分支 | RuoYi-Vue-Plus-fast | [fast分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/) | 单体应用结构 |
| 原框架 | RuoYi-Vue | [RuoYi-Vue官网](http://ruoyi.vip/) | 定期同步需要的功能 |
| 前端开发框架 | Vue、Element UI | [Element UI官网](https://element.eleme.cn/#/zh-CN) | |
| 后端开发框架 | SpringBoot | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn) | |
@ -24,10 +27,12 @@ RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼
| 数据库框架 | Mybatis-Plus | [Mybatis-Plus文档](https://baomidou.com/guide/) | 快速 CRUD 增加开发效率 |
| 数据库框架 | p6spy | [p6spy官网](https://p6spy.readthedocs.io/) | 更强劲的 SQL 分析 |
| 多数据源框架 | dynamic-datasource | [dynamic-ds文档](https://www.kancloud.cn/tracy5546/dynamic-datasource/content) | 支持主从与多种类数据库异构 |
| Redis客户端 | Redisson | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) | 性能强劲、API丰富 |
| 序列化框架 | Jackson | [Jackson官网](https://github.com/FasterXML/jackson) | 统一使用 jackson 高效可靠 |
| 网络框架 | Feign、OkHttp3 | [Feign官网](https://github.com/OpenFeign/feign) | 接口化管理 HTTP 请求 |
| Redis客户端 | Redisson | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) | 支持单机、集群配置 |
| 分布式限流 | Redisson | [Redisson文档](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) | 全局、请求IP、集群ID 多种限流 |
| 分布式锁 | Lock4j | [Lock4j官网](https://gitee.com/baomidou/lock4j) | 注解锁、工具锁 多种多样 |
| 分布式幂等 | Lock4j | [Lock4j文档](https://gitee.com/baomidou/lock4j) | 基于分布式锁实现 |
| 文件存储 | Minio | [Minio文档](https://docs.min.io/) | 本地存储 |
| 文件存储 | 七牛、阿里、腾讯 | [OSS使用文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4359146&doc_id=1469725) | 云存储 |
| 监控框架 | SpringBoot-Admin | [SpringBoot-Admin文档](https://codecentric.github.io/spring-boot-admin/current/) | 全方位服务监控 |
@ -67,6 +72,7 @@ RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼
* 同步升级 RuoYi-Vue
* GitHub 地址 [RuoYi-Vue-Plus-github](https://github.com/JavaLionLi/RuoYi-Vue-Plus)
* 单模块 fast 分支 [RuoYi-Vue-Plus-fast](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/)
* satoken 分支 [RuoYi-Vue-Plus-satoken](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/)
* 用户扩展项目 [扩展项目列表](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
## 加群与捐献

View File

@ -103,7 +103,7 @@ services:
ipv4_address: 172.30.0.54
ruoyi-server1:
image: "ruoyi/ruoyi-server:3.1.0"
image: "ruoyi/ruoyi-server:3.2.0"
container_name: ruoyi-server1
environment:
# 时区上海
@ -118,7 +118,7 @@ services:
ipv4_address: 172.30.0.60
ruoyi-server2:
image: "ruoyi/ruoyi-server:3.1.0"
image: "ruoyi/ruoyi-server:3.2.0"
container_name: ruoyi-server2
environment:
# 时区上海
@ -133,7 +133,7 @@ services:
ipv4_address: 172.30.0.61
ruoyi-monitor-admin:
image: "ruoyi/ruoyi-monitor-admin:3.1.0"
image: "ruoyi/ruoyi-monitor-admin:3.2.0"
container_name: ruoyi-monitor-admin
environment:
# 时区上海

100
pom.xml
View File

@ -6,40 +6,44 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-vue-plus</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus后台管理系统</description>
<properties>
<ruoyi-vue-plus.version>3.1.0</ruoyi-vue-plus.version>
<spring-boot.version>2.5.4</spring-boot.version>
<ruoyi-vue-plus.version>3.2.0</ruoyi-vue-plus.version>
<spring-boot.version>2.5.5</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<druid.version>1.2.6</druid.version>
<knife4j.version>3.0.3</knife4j.version>
<swagger-annotations.version>1.5.22</swagger-annotations.version>
<poi.version>4.1.2</poi.version>
<easyexcel.version>2.2.10</easyexcel.version>
<easyexcel.version>2.2.11</easyexcel.version>
<velocity.version>1.7</velocity.version>
<jwt.version>0.9.1</jwt.version>
<mybatis-plus.version>3.4.3.3</mybatis-plus.version>
<mybatis-plus.version>3.4.3.4</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.7.11</hutool.version>
<hutool.version>5.7.13</hutool.version>
<feign.version>3.0.3</feign.version>
<feign-okhttp.version>11.6</feign-okhttp.version>
<okhttp.version>4.9.1</okhttp.version>
<spring-boot-admin.version>2.5.1</spring-boot-admin.version>
<redisson.version>3.16.2</redisson.version>
<redisson.version>3.16.3</redisson.version>
<lock4j.version>2.2.1</lock4j.version>
<dynamic-ds.version>3.4.1</dynamic-ds.version>
<!-- jdk11 缺失依赖 jaxb-->
<jaxb.version>3.0.1</jaxb.version>
<!-- OSS 配置 -->
<qiniu.version>7.8.0</qiniu.version>
<aliyun.oss.version>3.13.1</aliyun.oss.version>
<qcloud.cos.version>5.6.51</qcloud.cos.version>
<qcloud.cos.version>5.6.55</qcloud.cos.version>
<minio.version>8.3.0</minio.version>
<!-- docker 配置 -->
@ -48,7 +52,7 @@
<docker.namespace>ruoyi</docker.namespace>
<docker.plugin.version>1.2.2</docker.plugin.version>
</properties>
<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>
@ -73,6 +77,18 @@
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
<exclusions>
<exclusion>
<artifactId>swagger-annotations</artifactId>
<groupId>io.swagger</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
</dependency>
<!-- excel工具 -->
@ -86,6 +102,16 @@
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- velocity代码生成使用模板 -->
@ -136,6 +162,12 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${feign.version}</version>
<exclusions>
<exclusion>
<artifactId>feign-core</artifactId>
<groupId>io.github.openfeign</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -239,7 +271,12 @@
<dependencies>
<!-- jdk11 缺失依赖 jaxb-->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
</dependencies>
<build>
@ -318,48 +355,9 @@
<properties>
<profiles.active>prod</profiles.active>
<logging.level>warn</logging.level>
<endpoints.include>health,info</endpoints.include>
<endpoints.include>health, info, logfile</endpoints.include>
</properties>
</profile>
<!-- jdk多版本配置 -->
<profile>
<id>jdk8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<java.version>1.8</java.version>
</properties>
</profile>
<profile>
<id>jdk11</id>
<activation>
<jdk>11</jdk>
</activation>
<properties>
<java.version>11</java.version>
<jaxb.version>3.0.1</jaxb.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- jdk11 缺失依赖 jaxb-->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--jaxb-->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
</project>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
@ -36,6 +36,11 @@
<artifactId>ruoyi-framework</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
<!-- 定时任务-->
<dependency>
<groupId>com.ruoyi</groupId>
@ -82,7 +87,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
@ -110,4 +115,4 @@
</plugins>
</build>
</project>
</project>

View File

@ -6,8 +6,8 @@ import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.system.service.SysLoginService;
import com.ruoyi.system.service.SysPermissionService;
import com.ruoyi.system.service.ISysMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;

View File

@ -25,14 +25,14 @@ import javax.validation.constraints.NotNull;
import java.util.Arrays;
/**
* 存储配置Controller
* 对象存储配置Controller
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
@Validated
@Api(value = "存储配置控制器", tags = {"存储配置管理"})
@Api(value = "对象存储配置控制器", tags = {"对象存储配置管理"})
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@RestController
@RequestMapping("/system/oss/config")
@ -41,9 +41,9 @@ public class SysOssConfigController extends BaseController {
private final ISysOssConfigService iSysOssConfigService;
/**
* 查询存储配置列表
* 查询对象存储配置列表
*/
@ApiOperation("查询存储配置列表")
@ApiOperation("查询对象存储配置列表")
@PreAuthorize("@ss.hasPermi('system:oss:list')")
@GetMapping("/list")
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo) {
@ -51,9 +51,9 @@ public class SysOssConfigController extends BaseController {
}
/**
* 获取存储配置详细信息
* 获取对象存储配置详细信息
*/
@ApiOperation("获取存储配置详细信息")
@ApiOperation("获取对象存储配置详细信息")
@PreAuthorize("@ss.hasPermi('system:oss:query')")
@GetMapping("/{ossConfigId}")
public AjaxResult<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
@ -62,11 +62,11 @@ public class SysOssConfigController extends BaseController {
}
/**
* 新增存储配置
* 新增对象存储配置
*/
@ApiOperation("新增存储配置")
@ApiOperation("新增对象存储配置")
@PreAuthorize("@ss.hasPermi('system:oss:add')")
@Log(title = "存储配置", businessType = BusinessType.INSERT)
@Log(title = "对象存储配置", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public AjaxResult<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
@ -74,11 +74,11 @@ public class SysOssConfigController extends BaseController {
}
/**
* 修改存储配置
* 修改对象存储配置
*/
@ApiOperation("修改存储配置")
@ApiOperation("修改对象存储配置")
@PreAuthorize("@ss.hasPermi('system:oss:edit')")
@Log(title = "存储配置", businessType = BusinessType.UPDATE)
@Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public AjaxResult<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
@ -86,11 +86,11 @@ public class SysOssConfigController extends BaseController {
}
/**
* 删除存储配置
* 删除对象存储配置
*/
@ApiOperation("删除存储配置")
@ApiOperation("删除对象存储配置")
@PreAuthorize("@ss.hasPermi('system:oss:remove')")
@Log(title = "存储配置", businessType = BusinessType.DELETE)
@Log(title = "对象存储配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossConfigIds}")
public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossConfigIds) {
@ -101,7 +101,7 @@ public class SysOssConfigController extends BaseController {
* 状态修改
*/
@PreAuthorize("@ss.hasPermi('system:oss:edit')")
@Log(title = "存储状态修改", businessType = BusinessType.UPDATE)
@Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public AjaxResult changeStatus(@RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));

View File

@ -3,6 +3,7 @@ package com.ruoyi.web.controller.system;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.annotation.Log;
@ -49,7 +50,7 @@ import java.util.Map;
* @author Lion Li
*/
@Validated
@Api(value = "OSS存储控制器", tags = {"OSS存储管理"})
@Api(value = "OSS对象存储控制器", tags = {"OSS对象存储管理"})
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@RestController
@RequestMapping("/system/oss")
@ -59,9 +60,9 @@ public class SysOssController extends BaseController {
private final ISysConfigService iSysConfigService;
/**
* 查询OSS存储列表
* 查询OSS对象存储列表
*/
@ApiOperation("查询OSS存储列表")
@ApiOperation("查询OSS对象存储列表")
@PreAuthorize("@ss.hasPermi('system:oss:list')")
@GetMapping("/list")
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo) {
@ -69,14 +70,14 @@ public class SysOssController extends BaseController {
}
/**
* 上传OSS存储
* 上传OSS对象存储
*/
@ApiOperation("上传OSS存储")
@ApiOperation("上传OSS对象存储")
@ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "文件", dataType = "java.io.File", required = true),
})
@PreAuthorize("@ss.hasPermi('system:oss:upload')")
@Log(title = "OSS存储", businessType = BusinessType.INSERT)
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@RepeatSubmit
@PostMapping("/upload")
public AjaxResult<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
@ -90,7 +91,7 @@ public class SysOssController extends BaseController {
return AjaxResult.success(map);
}
@ApiOperation("下载OSS存储")
@ApiOperation("下载OSS对象存储")
@PreAuthorize("@ss.hasPermi('system:oss:download')")
@GetMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
@ -101,18 +102,27 @@ public class SysOssController extends BaseController {
response.reset();
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
FileUtils.setAttachmentResponseHeader(response, URLEncoder.encode(sysOss.getOriginalName(), StandardCharsets.UTF_8.toString()));
FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
long data = HttpUtil.download(sysOss.getUrl(), response.getOutputStream(), false);
long data;
try {
data = HttpUtil.download(sysOss.getUrl(), response.getOutputStream(), false);
} catch (HttpException e) {
if (e.getMessage().contains("403")) {
throw new ServiceException("无读取权限, 请在对应的OSS开启'公有读'权限!");
} else {
throw new ServiceException(e.getMessage());
}
}
response.setContentLength(Convert.toInt(data));
}
/**
* 删除OSS云存储
* 删除OSS对象存储
*/
@ApiOperation("删除OSS云存储")
@ApiOperation("删除OSS对象存储")
@PreAuthorize("@ss.hasPermi('system:oss:remove')")
@Log(title = "OSS云存储" , businessType = BusinessType.DELETE)
@Log(title = "OSS对象存储" , businessType = BusinessType.DELETE)
@DeleteMapping("/{ossIds}")
public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
@ -124,7 +134,7 @@ public class SysOssController extends BaseController {
*/
@ApiOperation("变更图片列表预览状态")
@PreAuthorize("@ss.hasPermi('system:oss:edit')")
@Log(title = "OSS存储" , businessType = BusinessType.UPDATE)
@Log(title = "OSS对象存储" , businessType = BusinessType.UPDATE)
@PutMapping("/changePreviewListResource")
public AjaxResult<Void> changePreviewListResource(@RequestBody String body) {
Map<String, Boolean> map = JsonUtils.parseMap(body);

View File

@ -7,9 +7,9 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.service.ISysOssService;
import com.ruoyi.system.service.ISysUserService;

View File

@ -4,7 +4,7 @@ import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.SysRegisterService;
import com.ruoyi.system.service.SysRegisterService;
import com.ruoyi.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;

View File

@ -9,13 +9,13 @@ import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.SysPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;

View File

@ -70,7 +70,7 @@ spring:
config:
multi-statement-allow: true
--- # redis 配置
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring:
redis:
# 地址
@ -118,6 +118,67 @@ redisson:
# DNS监测时间间隔单位毫秒
dnsMonitoringInterval: 5000
#--- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
#spring:
# redis:
# cluster:
# nodes:
# - 192.168.0.100:6379
# - 192.168.0.101:6379
# - 192.168.0.102:6379
# # 密码
# password:
# # 连接超时时间
# timeout: 10s
# # 是否开启ssl
# ssl: false
#
#redisson:
# # 线程池数量
# threads: 16
# # Netty线程池数量
# nettyThreads: 32
# # 传输模式
# transportMode: "NIO"
# # 集群配置
# clusterServersConfig:
# # 客户端名称
# clientName: ${ruoyi.name}
# # master最小空闲连接数
# masterConnectionMinimumIdleSize: 32
# # master连接池大小
# masterConnectionPoolSize: 64
# # slave最小空闲连接数
# slaveConnectionMinimumIdleSize: 32
# # slave连接池大小
# slaveConnectionPoolSize: 64
# # 连接空闲超时,单位:毫秒
# idleConnectionTimeout: 10000
# # ping连接间隔
# pingConnectionInterval: 1000
# # 命令等待超时,单位:毫秒
# timeout: 3000
# # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
# retryAttempts: 3
# # 命令重试发送时间间隔,单位:毫秒
# retryInterval: 1500
# # 从可用服务器的内部列表中排除 Redis Slave 重新连接尝试的间隔。
# failedSlaveReconnectionInterval: 3000
# # 发布和订阅连接池最小空闲连接数
# subscriptionConnectionMinimumIdleSize: 1
# # 发布和订阅连接池大小
# subscriptionConnectionPoolSize: 50
# # 单个连接最大订阅数量
# subscriptionsPerConnection: 5
# # 扫描间隔
# scanInterval: 1000
# # DNS监测时间间隔单位毫秒
# dnsMonitoringInterval: 5000
# # 读取模式
# readMode: "SLAVE"
# # 订阅模式
# subscriptionMode: "MASTER"
--- # 监控配置
spring:
boot:

View File

@ -70,7 +70,7 @@ spring:
config:
multi-statement-allow: true
--- # redis 配置
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring:
redis:
# 地址
@ -118,6 +118,67 @@ redisson:
# DNS监测时间间隔单位毫秒
dnsMonitoringInterval: 5000
#--- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
#spring:
# redis:
# cluster:
# nodes:
# - 192.168.0.100:6379
# - 192.168.0.101:6379
# - 192.168.0.102:6379
# # 密码
# password:
# # 连接超时时间
# timeout: 10s
# # 是否开启ssl
# ssl: false
#
#redisson:
# # 线程池数量
# threads: 16
# # Netty线程池数量
# nettyThreads: 32
# # 传输模式
# transportMode: "NIO"
# # 集群配置
# clusterServersConfig:
# # 客户端名称
# clientName: ${ruoyi.name}
# # master最小空闲连接数
# masterConnectionMinimumIdleSize: 32
# # master连接池大小
# masterConnectionPoolSize: 64
# # slave最小空闲连接数
# slaveConnectionMinimumIdleSize: 32
# # slave连接池大小
# slaveConnectionPoolSize: 64
# # 连接空闲超时,单位:毫秒
# idleConnectionTimeout: 10000
# # ping连接间隔
# pingConnectionInterval: 1000
# # 命令等待超时,单位:毫秒
# timeout: 3000
# # 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
# retryAttempts: 3
# # 命令重试发送时间间隔,单位:毫秒
# retryInterval: 1500
# # 从可用服务器的内部列表中排除 Redis Slave 重新连接尝试的间隔。
# failedSlaveReconnectionInterval: 3000
# # 发布和订阅连接池最小空闲连接数
# subscriptionConnectionMinimumIdleSize: 1
# # 发布和订阅连接池大小
# subscriptionConnectionPoolSize: 50
# # 单个连接最大订阅数量
# subscriptionsPerConnection: 5
# # 扫描间隔
# scanInterval: 1000
# # DNS监测时间间隔单位毫秒
# dnsMonitoringInterval: 5000
# # 读取模式
# readMode: "SLAVE"
# # 订阅模式
# subscriptionMode: "MASTER"
--- # 监控配置
spring:
boot:

View File

@ -106,10 +106,32 @@ token:
# 令牌有效期默认30分钟
expireTime: 30
# security配置
security:
# 登出路径
logout-url: /logout
# 匿名路径
anonymous:
- /login
- /register
- /captchaImage
# swagger 文档配置
- /doc.html
- /swagger-resources/**
- /webjars/**
- /*/api-docs
# druid 监控配置
- /druid/**
# actuator 监控配置
- /actuator
- /actuator/**
# 用户放行
permit-all:
# 重复提交
repeat-submit:
# 全局间隔时间(毫秒)
intervalTime: 1000
interval: 5000
# MyBatisPlus配置
# https://baomidou.com/config/
@ -221,6 +243,11 @@ swagger:
name: Lion Li
email: crazylionli@163.com
url: https://gitee.com/JavaLionLi/RuoYi-Vue-Plus
groups:
- name: 演示案例
basePackage: com.ruoyi.demo
- name: 系统模块
basePackage: com.ruoyi.admin
# 防止XSS攻击
xss:
@ -244,7 +271,7 @@ thread-pool:
# 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300
# 线程池对拒绝任务(无线程可用)的处理策略
# CALLER_RUNS_POLICY 等待
# CALLER_RUNS_POLICY 调用方执行
# DISCARD_OLDEST_POLICY 放弃最旧的
# DISCARD_POLICY 丢弃
# ABORT_POLICY 中止

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -131,6 +131,11 @@
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
@ -147,22 +152,10 @@
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
<!-- sql性能分析插件 -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -38,4 +38,9 @@ public @interface Log
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
}

View File

@ -20,10 +20,15 @@ import java.util.concurrent.TimeUnit;
public @interface RepeatSubmit {
/**
* 默认使用全局配置
* 间隔时间(ms)小于此时间视为重复提交
*/
int intervalTime() default 0;
int interval() default 5000;
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 提示消息
*/
String message() default "不允许重复提交,请稍后再试";
}

View File

@ -44,16 +44,21 @@ public class GenConstants
public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
"bit", "bigint", "float", "double", "decimal" };
/** 页面不需要添加字段 */
public static final String[] COLUMNNAME_NOT_ADD = { "create_by", "create_time", "del_flag", "update_by",
"update_time", "version" };
/** 页面不需要编辑字段 */
public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };
public static final String[] COLUMNNAME_NOT_EDIT = { "create_by", "create_time", "del_flag", "update_by",
"update_time", "version" };
/** 页面不需要显示的列表字段 */
public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by",
"update_time" };
"update_time", "version" };
/** 页面不需要查询字段 */
public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by",
"update_time", "remark" };
"update_time", "remark", "version" };
/** Entity基类字段 */
public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };

View File

@ -1,5 +1,6 @@
package com.ruoyi.common.core.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -62,6 +63,7 @@ public class BaseEntity implements Serializable {
/**
* 请求参数
*/
@JsonIgnore
@ApiModelProperty(value = "请求参数")
private Map<String, Object> params = new HashMap<>();

View File

@ -0,0 +1,107 @@
package com.ruoyi.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* 操作日志记录表 oper_log
*
* @author ruoyi
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class OperLogDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
private Long operId;
/**
* 操作模块
*/
private String title;
/**
* 业务类型0其它 1新增 2修改 3删除
*/
private Integer businessType;
/**
* 业务类型数组
*/
private Integer[] businessTypes;
/**
* 请求方法
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作类别0其它 1后台用户 2手机端用户
*/
private Integer operatorType;
/**
* 操作人员
*/
private String operName;
/**
* 部门名称
*/
private String deptName;
/**
* 请求url
*/
private String operUrl;
/**
* 操作地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态0正常 1异常
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private Date operTime;
}

View File

@ -90,7 +90,7 @@ public class SysDictData implements Serializable {
* 状态0正常 1停用
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_common_status")
@ExcelDictFormat(dictType = "sys_normal_disable")
private String status;
/**

View File

@ -57,7 +57,7 @@ public class SysDictType implements Serializable {
* 状态0正常 1停用
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_common_status")
@ExcelDictFormat(dictType = "sys_normal_disable")
private String status;
/**

View File

@ -66,6 +66,11 @@ public class SysMenu implements Serializable {
@Size(min = 0, max = 200, message = "组件路径不能超过255个字符")
private String component;
/**
* 路由参数
*/
private String query;
/**
* 是否为外链0是 1否
*/

View File

@ -91,11 +91,6 @@ public class SysUser implements Serializable {
return password;
}
/**
* 盐加密
*/
private String salt;
/**
* 帐号状态0正常 1停用
*/

View File

@ -1,260 +0,0 @@
package com.ruoyi.common.core.redis;
import com.google.common.collect.Lists;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* spring redis 工具类
*
* @author shenxinquan
* @see com.ruoyi.common.utils.RedisUtils
* @deprecated 3.2.0 删除此类
**/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
@Deprecated
public class RedisCache {
@Autowired
private RedissonClient redissonClient;
/**
* 发布通道消息
*
* @param channelKey 通道key
* @param msg 发送数据
* @param consumer 自定义处理
*/
public <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
RTopic topic = redissonClient.getTopic(channelKey);
topic.publish(msg);
consumer.accept(msg);
}
public <T> void publish(String channelKey, T msg) {
RTopic topic = redissonClient.getTopic(channelKey);
topic.publish(msg);
}
/**
* 订阅通道接收消息
*
* @param channelKey 通道key
* @param clazz 消息类型
* @param consumer 自定义处理
*/
public <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
RTopic topic = redissonClient.getTopic(channelKey);
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
}
/**
* 缓存基本的对象IntegerString实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redissonClient.getBucket(key).set(value);
}
/**
* 缓存基本的对象IntegerString实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
RBucket<T> result = redissonClient.getBucket(key);
result.set(value);
result.expire(timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
RBucket rBucket = redissonClient.getBucket(key);
return rBucket.expire(timeout, unit);
}
/**
* 获得缓存的基本对象
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
RBucket<T> rBucket = redissonClient.getBucket(key);
return rBucket.get();
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redissonClient.getBucket(key).delete();
}
/* */
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public void deleteObject(final Collection collection) {
RBatch batch = redissonClient.createBatch();
collection.forEach(t->{
batch.getBucket(t.toString()).deleteAsync();
});
batch.execute();
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> boolean setCacheList(final String key, final List<T> dataList) {
RList<T> rList = redissonClient.getList(key);
return rList.addAll(dataList);
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
RList<T> rList = redissonClient.getList(key);
return rList.readAll();
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
RSet<T> rSet = redissonClient.getSet(key);
return rSet.addAll(dataSet);
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
RSet<T> rSet = redissonClient.getSet(key);
return rSet.readAll();
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
RMap<String, T> rMap = redissonClient.getMap(key);
rMap.putAll(dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
RMap<String, T> rMap = redissonClient.getMap(key);
return rMap.getAll(rMap.keySet());
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
RMap<String, T> rMap = redissonClient.getMap(key);
rMap.put(hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
RMap<String, T> rMap = redissonClient.getMap(key);
return rMap.get(hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <K,V> Map<K,V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
RMap<K,V> rMap = redissonClient.getMap(key);
return rMap.getAll(hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
Iterable<String> iterable = redissonClient.getKeys().getKeysByPattern(pattern);
return Lists.newArrayList(iterable);
}
}

View File

@ -0,0 +1,9 @@
package com.ruoyi.common.core.service;
import javax.servlet.http.HttpServletRequest;
public interface LogininforService {
void recordLogininfor(String username, String status, String message,
HttpServletRequest request, Object... args);
}

View File

@ -0,0 +1,9 @@
package com.ruoyi.common.core.service;
import com.ruoyi.common.core.domain.dto.OperLogDTO;
import org.springframework.scheduling.annotation.Async;
public interface OperLogService {
@Async
void recordOper(OperLogDTO operLogDTO);
}

View File

@ -0,0 +1,69 @@
package com.ruoyi.common.core.service;
import com.ruoyi.common.core.domain.model.LoginUser;
import javax.servlet.http.HttpServletRequest;
/**
* token验证处理
*
* @author Lion Li
*/
public interface TokenService {
/**
* 获取用户身份信息
*
* @return 用户信息
*/
LoginUser getLoginUser(HttpServletRequest request);
/**
* 设置用户身份信息
*/
void setLoginUser(LoginUser loginUser);
/**
* 删除用户身份信息
*/
void delLoginUser(String token);
/**
* 创建令牌
*
* @param loginUser 用户信息
* @return 令牌
*/
String createToken(LoginUser loginUser);
/**
* 验证令牌有效期相差不足20分钟自动刷新缓存
*
* @param loginUser
* @return 令牌
*/
void verifyToken(LoginUser loginUser);
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
void refreshToken(LoginUser loginUser);
/**
* 设置用户代理信息
*
* @param loginUser 登录信息
*/
void setUserAgent(LoginUser loginUser);
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
String getUsernameFromToken(String token);
}

View File

@ -16,5 +16,10 @@ public enum LimitType
/**
* 根据请求者IP进行限流
*/
IP
IP,
/**
* 实例限流(集群多后端实例)
*/
CLUSTER
}

View File

@ -15,7 +15,7 @@ import java.util.concurrent.ThreadPoolExecutor;
@AllArgsConstructor
public enum ThreadPoolRejectedPolicy {
CALLER_RUNS_POLICY("等待", ThreadPoolExecutor.CallerRunsPolicy.class),
CALLER_RUNS_POLICY("调用方执行", ThreadPoolExecutor.CallerRunsPolicy.class),
DISCARD_OLDEST_POLICY("放弃最旧的", ThreadPoolExecutor.DiscardOldestPolicy.class),
DISCARD_POLICY("丢弃", ThreadPoolExecutor.DiscardPolicy.class),
ABORT_POLICY("中止", ThreadPoolExecutor.AbortPolicy.class);

View File

@ -1,4 +1,4 @@
package com.ruoyi.framework.config.properties;
package com.ruoyi.common.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -25,6 +25,32 @@ public class RedisUtils {
private static RedissonClient client = SpringUtils.getBean(RedissonClient.class);
/**
* 限流
*
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
RRateLimiter rateLimiter = client.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits();
} else {
return -1L;
}
}
/**
* 获取实例id
*/
public static String getClientId() {
return client.getId();
}
/**
* 发布通道消息
*

View File

@ -35,6 +35,7 @@ public class FileUtils extends FileUtil
.append(percentEncodedFileName);
response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);
}
/**

View File

@ -27,6 +27,9 @@ public class AddressUtils {
public static String getRealAddressByIP(String ip) {
String address = UNKNOWN;
if (StringUtils.isBlank(ip)){
return address;
}
// 内网不查询
ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
if (NetUtil.isInnerIP(ip)) {

View File

@ -46,7 +46,7 @@ public class ExcelUtil {
response.reset();
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
FileUtils.setAttachmentResponseHeader(response, URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()));
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
ServletOutputStream os = response.getOutputStream();
EasyExcel.write(os, clazz)

View File

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

View File

@ -34,6 +34,9 @@ public class RedisCacheController {
* 如果没有,就调用方法,然后把结果缓存起来
* 这个注解一般用在查询方法上
*
* 重点说明: 缓存注解严谨与其他筛选数据功能一起使用
* 例如: 数据权限注解 会造成 缓存击穿 数据不一致问题
*
* cacheNames 为配置文件内 groupId
*/
@ApiOperation("测试 @Cacheable")

View File

@ -0,0 +1,58 @@
package com.ruoyi.demo.controller;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.LimitType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试分布式限流样例
*
* @author Lion Li
*/
@Api(value = "测试分布式限流样例", tags = {"测试分布式限流样例"})
@Slf4j
@RestController
@RequestMapping("/demo/rateLimiter")
public class RedisRateLimiterController {
/**
* 测试全局限流
* 全局影响
*/
@ApiOperation("测试全局限流")
@RateLimiter(count = 2, time = 10)
@GetMapping("/test")
public AjaxResult<String> test(String value){
return AjaxResult.success("操作成功",value);
}
/**
* 测试请求IP限流
* 同一IP请求受影响
*/
@ApiOperation("测试请求IP限流")
@RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
@GetMapping("/testip")
public AjaxResult<String> testip(String value){
return AjaxResult.success("操作成功",value);
}
/**
* 测试集群实例限流
* 启动两个后端服务互不影响
*/
@ApiOperation("测试集群实例限流")
@RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
@GetMapping("/testcluster")
public AjaxResult<String> testcluster(String value){
return AjaxResult.success("操作成功",value);
}
}

View File

@ -96,7 +96,7 @@ public class TestDemoController extends BaseController {
@ApiOperation("新增测试单表")
@PreAuthorize("@ss.hasPermi('demo:demo:add')")
@Log(title = "测试单表", businessType = BusinessType.INSERT)
@RepeatSubmit(intervalTime = 2, timeUnit = TimeUnit.SECONDS)
@RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "不允许重复提交")
@PostMapping()
public AjaxResult<Void> add(@Validated(AddGroup.class) @RequestBody TestDemoBo bo) {
return toAjax(iTestDemoService.insertByBo(bo) ? 1 : 0);

View File

@ -75,7 +75,11 @@ public class TestDemoServiceImpl extends ServicePlusImpl<TestDemoMapper, TestDem
public Boolean insertByBo(TestDemoBo bo) {
TestDemo add = BeanUtil.toBean(bo, TestDemo.class);
validEntityBeforeSave(add);
return save(add);
boolean flag = save(add);
if (flag) {
bo.setId(add.getId());
}
return flag;
}
@Override

View File

@ -54,7 +54,11 @@ public class TestTreeServiceImpl extends ServicePlusImpl<TestTreeMapper, TestTre
public Boolean insertByBo(TestTreeBo bo) {
TestTree add = BeanUtil.toBean(bo, TestTree.class);
validEntityBeforeSave(add);
return save(add);
boolean flag = save(add);
if (flag) {
bo.setId(add.getId());
}
return flag;
}
@Override

View File

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

View File

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

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -61,12 +61,23 @@
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
<!-- sql性能分析插件 -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
</dependency>
<!-- 系统模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
<artifactId>ruoyi-common</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -9,14 +9,10 @@ import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
/**
@ -58,23 +54,13 @@ public class DataScopeAspect {
*/
public static final String DATA_SCOPE = "dataScope";
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)")
public void dataScopePointCut() {
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable {
@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable {
clearDataScope(point);
handleDataScope(point);
handleDataScope(point, controllerDataScope);
}
protected void handleDataScope(final JoinPoint joinPoint) {
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null) {
return;
}
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser)) {
@ -133,20 +119,6 @@ public class DataScopeAspect {
}
}
/**
* 是否存在注解如果存在就获取
*/
private DataScope getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(DataScope.class);
}
return null;
}
/**
* 拼接权限sql前先清空params.dataScope参数防止注入
*/

View File

@ -6,33 +6,21 @@ import com.ruoyi.common.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 多数据源处理
*
* @author ruoyi
* @author Lion Li
*/
@Aspect
@Order(-500)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
+ "|| @within(com.ruoyi.common.annotation.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
@Around("@annotation(dataSource) || @within(dataSource)")
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
if (StringUtils.isNotNull(dataSource)) {
DynamicDataSourceContextHolder.poll();
String source = dataSource.value().getSource();
@ -47,16 +35,4 @@ public class DataSourceAspect {
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}

View File

@ -1,7 +1,9 @@
package com.ruoyi.framework.aspectj;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.dto.OperLogDTO;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.service.OperLogService;
import com.ruoyi.common.enums.BusinessStatus;
import com.ruoyi.common.enums.HttpMethod;
import com.ruoyi.common.utils.JsonUtils;
@ -9,17 +11,11 @@ import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.web.service.AsyncService;
import com.ruoyi.system.domain.SysOperLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
@ -27,81 +23,58 @@ import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
/**
* 操作日志记录处理
*
* @author ruoyi
* @author Lion Li
*/
@Slf4j
@Aspect
@Component
public class LogAspect
{
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.annotation.Log)")
public void logPointCut()
{
}
public class LogAspect {
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
{
handleLog(joinPoint, null, jsonResult);
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e)
{
handleLog(joinPoint, e, null);
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
{
try
{
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null)
{
return;
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
try {
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
OperLogDTO operLog = new OperLogDTO();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ServletUtils.getClientIP();
operLog.setOperIp(ip);
// 返回参数
operLog.setJsonResult(JsonUtils.toJsonString(jsonResult));
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null)
{
if (loginUser != null) {
operLog.setOperName(loginUser.getUsername());
}
if (e != null)
{
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
@ -112,12 +85,10 @@ public class LogAspect
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 保存数据库
SpringUtils.getBean(AsyncService.class).recordOper(operLog);
}
catch (Exception exp)
{
SpringUtils.getBean(OperLogService.class).recordOper(operLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
@ -128,12 +99,11 @@ public class LogAspect
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception
{
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogDTO operLog, Object jsonResult) throws Exception {
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
@ -141,11 +111,14 @@ public class LogAspect
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request参数和值
if (log.isSaveRequestData())
{
if (log.isSaveRequestData()) {
// 获取参数的信息传入到数据库中
setRequestValue(joinPoint, operLog);
}
// 是否需要保存response参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
}
}
/**
@ -154,50 +127,32 @@ public class LogAspect
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
{
private void setRequestValue(JoinPoint joinPoint, OperLogDTO operLog) throws Exception {
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
{
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
}
else
{
} else {
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
/**
* 是否存在注解如果存在就获取
*/
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
{
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
return method.getAnnotation(Log.class);
}
return null;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
private String argsArrayToString(Object[] paramsArray) {
StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray) {
if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
params.append(JsonUtils.toJsonString(o)).append(" ");
}
}
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
try {
params.append(JsonUtils.toJsonString(o)).append(" ");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return params.toString().trim();
}
@ -209,27 +164,21 @@ public class LogAspect
* @return 如果是需要过滤的对象则返回true否则返回false
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o)
{
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray())
{
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
}
else if (Collection.class.isAssignableFrom(clazz))
{
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
}
else if (Map.class.isAssignableFrom(clazz))
{
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;

View File

@ -3,114 +3,63 @@ package com.ruoyi.framework.aspectj;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.enums.LimitType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.RedisUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.redisson.api.RateType;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/**
* 限流处理
*
* @author ruoyi
* @author Lion Li
*/
@Slf4j
@Aspect
@Component
public class RateLimiterAspect
{
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
public class RateLimiterAspect {
private RedisTemplate<Object, Object> redisTemplate;
private RedisScript<Long> limitScript;
@Autowired
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
{
this.redisTemplate = redisTemplate;
}
@Autowired
public void setLimitScript(RedisScript<Long> limitScript)
{
this.limitScript = limitScript;
}
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.annotation.RateLimiter)")
public void rateLimiterPointCut()
{
}
@Before("rateLimiterPointCut()")
public void doBefore(JoinPoint point) throws Throwable
{
RateLimiter rateLimiter = getAnnotationRateLimiter(point);
String key = rateLimiter.key();
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
int time = rateLimiter.time();
int count = rateLimiter.count();
String combineKey = getCombineKey(rateLimiter, point);
List<Object> keys = Collections.singletonList(combineKey);
try
{
Long number = redisTemplate.execute(limitScript, keys, count, time);
if (StringUtils.isNull(number) || number.intValue() > count)
{
try {
RateType rateType = RateType.OVERALL;
if (rateLimiter.limitType() == LimitType.CLUSTER) {
rateType = RateType.PER_CLIENT;
}
long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
if (number == -1) {
throw new ServiceException("访问过于频繁,请稍后再试");
}
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
}
catch (ServiceException e)
{
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
} catch (ServiceException e) {
throw e;
}
catch (Exception e)
{
} catch (Exception e) {
throw new RuntimeException("服务器限流异常,请稍后再试");
}
}
/**
* 是否存在注解如果存在就获取
*/
private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint)
{
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
return method.getAnnotation(RateLimiter.class);
}
return null;
}
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
{
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP)
{
stringBuffer.append(ServletUtils.getClientIP());
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP) {
// 获取请求ip
stringBuffer.append(ServletUtils.getClientIP()).append("-");
} else if (rateLimiter.limitType() == LimitType.CLUSTER){
// 获取客户端实例id
stringBuffer.append(RedisUtils.getClientId()).append("-");
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName());
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
return stringBuffer.toString();
}
}

View File

@ -0,0 +1,69 @@
package com.ruoyi.framework.aspectj;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.properties.TokenProperties;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.RepeatSubmitProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* 防止重复提交
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Aspect
@Component
public class RepeatSubmitAspect {
private final TokenProperties tokenProperties;
private final RepeatSubmitProperties repeatSubmitProperties;
private final LockTemplate lockTemplate;
@Before("@annotation(repeatSubmit)")
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
// 如果注解不为0 则使用注解数值
long interval = repeatSubmitProperties.getInterval();
if (repeatSubmit.interval() > 0) {
interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
}
if (interval < 1000) {
throw new ServiceException("重复提交间隔时间不能小于'1'秒");
}
HttpServletRequest request = ServletUtils.getRequest();
String nowParams = StrUtil.join(",", point.getArgs());
// 请求地址作为存放cache的key值
String url = request.getRequestURI();
// 唯一值没有消息头则使用请求地址
String submitKey = request.getHeader(tokenProperties.getHeader());
if (StringUtils.isEmpty(submitKey)) {
submitKey = url;
}
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
// 唯一标识指定key + 消息头
String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
LockInfo lock = lockTemplate.lock(cacheRepeatKey, interval, interval / 2);
if (lock == null) {
throw new ServiceException(repeatSubmit.message());
}
}
}

View File

@ -1,5 +1,6 @@
package com.ruoyi.framework.config;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
@ -41,10 +42,13 @@ public class AsyncConfig extends AsyncConfigurerSupport {
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
throwable.printStackTrace();
throw new ServiceException(
"Exception message - " + throwable.getMessage()
+ ", Method name - " + method.getName()
+ ", Parameter value - " + Arrays.toString(objects));
StringBuilder sb = new StringBuilder();
sb.append("Exception message - ").append(throwable.getMessage())
.append(", Method name - ").append(method.getName());
if (ArrayUtil.isNotEmpty(objects)) {
sb.append(", Parameter value - ").append(Arrays.toString(objects));
}
throw new ServiceException(sb.toString());
};
}

View File

@ -1,6 +1,6 @@
package com.ruoyi.framework.config;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.RedissonProperties;
import lombok.extern.slf4j.Slf4j;
@ -18,7 +18,6 @@ import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.io.IOException;
import java.util.HashMap;
@ -58,23 +57,52 @@ public class RedisConfig extends CachingConfigurerSupport {
.setTransportMode(redissonProperties.getTransportMode());
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
// 使用单机模式
config.useSingleServer()
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setDatabase(redisProperties.getDatabase())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(singleServerConfig.getTimeout())
.setRetryAttempts(singleServerConfig.getRetryAttempts())
.setRetryInterval(singleServerConfig.getRetryInterval())
.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setDatabase(redisProperties.getDatabase())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(singleServerConfig.getTimeout())
.setRetryAttempts(singleServerConfig.getRetryAttempts())
.setRetryInterval(singleServerConfig.getRetryInterval())
.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
}
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 使用集群模式
config.useClusterServers()
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(clusterServersConfig.getTimeout())
.setRetryAttempts(clusterServersConfig.getRetryAttempts())
.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;
@ -95,32 +123,4 @@ public class RedisConfig extends CachingConfigurerSupport {
return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
}
@Bean
public DefaultRedisScript<Long> limitScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 限流脚本
*/
private String limitScriptText() {
return StrUtil.builder()
.append("local key = KEYS[1]\n")
.append("local count = tonumber(ARGV[1])\n")
.append("local time = tonumber(ARGV[2])\n")
.append("local current = redis.call('get', key);\n")
.append("if current and tonumber(current) > count then\n")
.append(" return current;\n")
.append("end\n")
.append("current = redis.call('incr', key)\n")
.append("if tonumber(current) == 1 then\n")
.append(" redis.call('expire', key, time)\n")
.append("end\n")
.append("return current;")
.toString();
}
}

View File

@ -1,52 +1,35 @@
package com.ruoyi.framework.config;
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 通用配置
*
* @author ruoyi
* @author Lion Li
*/
@Configuration
public class ResourcesConfig implements WebMvcConfigurer
{
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
public class ResourcesConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
}
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter()
{
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法

View File

@ -1,5 +1,6 @@
package com.ruoyi.framework.config;
import com.ruoyi.framework.config.properties.SecurityProperties;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
@ -20,7 +21,7 @@ import org.springframework.web.filter.CorsFilter;
/**
* spring security配置
*
*
* @author ruoyi
*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@ -31,7 +32,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@ -49,13 +50,16 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
@Autowired
private SecurityProperties securityProperties;
/**
* 解决 无法直接注入 AuthenticationManager
*
@ -96,8 +100,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/",
@ -106,19 +108,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/doc.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// Spring Boot Actuator 的安全配置
.antMatchers("/actuator").anonymous()
.antMatchers("/actuator/**").anonymous()
.antMatchers(securityProperties.getAnonymous()).anonymous()
.antMatchers(securityProperties.getPermitAll()).permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
httpSecurity.logout().logoutUrl(securityProperties.getLogoutUrl()).logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter

View File

@ -1,32 +0,0 @@
package com.ruoyi.framework.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.ServletUtils;
/**
* 服务相关配置
*
* @author ruoyi
*/
@Component
public class ServerConfig
{
/**
* 获取完整的请求路径包括域名端口上下文访问路径
*
* @return 服务地址
*/
public String getUrl()
{
HttpServletRequest request = ServletUtils.getRequest();
return getDomain(request);
}
public static String getDomain(HttpServletRequest request)
{
StringBuffer url = request.getRequestURL();
String contextPath = request.getServletContext().getContextPath();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
}
}

View File

@ -1,11 +1,12 @@
package com.ruoyi.framework.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.ruoyi.common.properties.TokenProperties;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.SwaggerProperties;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
@ -15,6 +16,7 @@ import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@ -27,82 +29,92 @@ import java.util.List;
@EnableKnife4j
public class SwaggerConfig {
@Autowired
private SwaggerProperties swaggerProperties;
@Autowired
private SwaggerProperties swaggerProperties;
/**
* 创建API
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.enable(swaggerProperties.getEnabled())
// 用来创建该API的基本信息展示在文档的页面中自定义展示的信息
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的swagger注解
// .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
/* 设置安全模式swagger可以设置访问token */
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
.pathMapping(swaggerProperties.getPathMapping());
}
@Autowired
private TokenProperties tokenProperties;
/**
* 安全模式这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
}
/**
* 创建API
*/
@PostConstruct
public void createRestApi() {
for (SwaggerProperties.Groups group : swaggerProperties.getGroups()) {
String basePackage = group.getBasePackage();
Docket docket = new Docket(DocumentationType.OAS_30)
.enable(swaggerProperties.getEnabled())
// 用来创建该API的基本信息展示在文档的页面中自定义展示的信息
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api用这种方式更灵活
//.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的swagger注解
.apis(RequestHandlerSelectors.basePackage(basePackage))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.groupName(group.getName())
// 设置安全模式swagger可以设置访问token
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
.pathMapping(swaggerProperties.getPathMapping());
String beanName = StringUtils.substringAfterLast(basePackage, ".") + "Docket";
SpringUtils.registerBean(beanName, docket);
}
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}
/**
* 安全模式这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
String header = tokenProperties.getHeader();
apiKeyList.add(new ApiKey(header, header, In.HEADER.toValue()));
return apiKeyList;
}
/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}
/**
* 添加摘要信息
*/
private ApiInfo apiInfo() {
// 用ApiInfoBuilder进行定制
SwaggerProperties.Contact contact = swaggerProperties.getContact();
return new ApiInfoBuilder()
// 设置标题
.title(swaggerProperties.getTitle())
// 描述
.description(swaggerProperties.getDescription())
// 作者信息
.contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail()))
// 版本
.version(swaggerProperties.getVersion())
.build();
}
/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference(tokenProperties.getHeader(), authorizationScopes));
return securityReferences;
}
/**
* 添加摘要信息
*/
private ApiInfo apiInfo() {
// 用ApiInfoBuilder进行定制
SwaggerProperties.Contact contact = swaggerProperties.getContact();
return new ApiInfoBuilder()
// 设置标题
.title(swaggerProperties.getTitle())
// 描述
.description(swaggerProperties.getDescription())
// 作者信息
.contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail()))
// 版本
.version(swaggerProperties.getVersion())
.build();
}
}

View File

@ -2,6 +2,8 @@ package com.ruoyi.framework.config.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.redisson.config.TransportMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ -38,6 +40,11 @@ public class RedissonProperties {
*/
private SingleServerConfig singleServerConfig;
/**
* 集群服务配置
*/
private ClusterServersConfig clusterServersConfig;
/**
* 缓存组
*/
@ -104,6 +111,102 @@ public class RedissonProperties {
}
@Data
@NoArgsConstructor
public static class ClusterServersConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* master最小空闲连接数
*/
private int masterConnectionMinimumIdleSize;
/**
* master连接池大小
*/
private int masterConnectionPoolSize;
/**
* slave最小空闲连接数
*/
private int slaveConnectionMinimumIdleSize;
/**
* slave连接池大小
*/
private int slaveConnectionPoolSize;
/**
* 连接空闲超时单位毫秒
*/
private int idleConnectionTimeout;
/**
* ping超时
*/
private int pingConnectionInterval;
/**
* 命令等待超时单位毫秒
*/
private int timeout;
/**
* 如果尝试在此限制之内发送成功则开始启用 timeout 计时
*/
private int retryAttempts;
/**
* 命令重试发送时间间隔单位毫秒
*/
private int retryInterval;
/**
* 错误重试次数
*/
private int failedSlaveReconnectionInterval;
/**
* 发布和订阅连接池最小空闲连接数
*/
private int subscriptionConnectionMinimumIdleSize;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
/**
* 单个连接最大订阅数量
*/
private int subscriptionsPerConnection;
/**
* 扫描间隔
*/
private int scanInterval;
/**
* DNS监测时间间隔单位毫秒
*/
private int dnsMonitoringInterval;
/**
* 读取模式
*/
private ReadMode readMode;
/**
* 订阅模式
*/
private SubscriptionMode subscriptionMode;
}
@Data
@NoArgsConstructor
public static class CacheGroup {

View File

@ -17,6 +17,6 @@ public class RepeatSubmitProperties {
/**
* 间隔时间(毫秒)
*/
private int intervalTime;
private int interval;
}

View File

@ -0,0 +1,32 @@
package com.ruoyi.framework.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Security 配置属性
*
* @author Lion Li
*/
@Data
@Component
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {
/**
* 退出登录url
*/
private String logoutUrl;
/**
* 匿名放行路径
*/
private String[] anonymous;
/**
* 用户任意访问放行路径
*/
private String[] permitAll;
}

View File

@ -5,6 +5,8 @@ import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* swagger 配置属性
*
@ -41,23 +43,46 @@ public class SwaggerProperties {
*/
private Contact contact;
/**
* 组配置
*/
private List<Groups> groups;
@Data
@NoArgsConstructor
public static class Contact{
public static class Contact {
/**
* 联系人
**/
*/
private String name;
/**
* 联系人url
**/
*/
private String url;
/**
* 联系人email
**/
*/
private String email;
}
@Data
@NoArgsConstructor
public static class Groups {
/**
* 组名
*/
private String name;
/**
* 基础包路径
*/
private String basePackage;
}
}

View File

@ -1,50 +0,0 @@
package com.ruoyi.framework.interceptor;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.ServletUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* 防止重复提交拦截器
*
* 移除继承 HandlerInterceptorAdapter 过期类
* 改为实现 HandlerInterceptor 接口(官方推荐写法)
*
* @author Lion Li
*/
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null) {
if (this.isRepeatSubmit(annotation, request)) {
AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
ServletUtils.renderString(response, JsonUtils.toJsonString(ajaxResult));
return false;
}
}
return true;
} else {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
/**
* 验证是否重复提交由子类实现具体的防重复提交的规则
*/
public abstract boolean isRepeatSubmit(RepeatSubmit annotation, HttpServletRequest request);
}

View File

@ -1,114 +0,0 @@
package com.ruoyi.framework.interceptor.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.RedisUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.RepeatSubmitProperties;
import com.ruoyi.framework.config.properties.TokenProperties;
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 判断请求url和数据是否和上一次相同
* 如果和上次相同则是重复提交表单
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
public final String REPEAT_PARAMS = "repeatParams";
public final String REPEAT_TIME = "repeatTime";
private final TokenProperties tokenProperties;
private final RepeatSubmitProperties repeatSubmitProperties;
@SuppressWarnings("unchecked")
@Override
public boolean isRepeatSubmit(RepeatSubmit repeatSubmit, HttpServletRequest request) {
// 如果注解不为0 则使用注解数值
long intervalTime = repeatSubmitProperties.getIntervalTime();
if (repeatSubmit.intervalTime() > 0) {
intervalTime = repeatSubmit.timeUnit().toMillis(repeatSubmit.intervalTime());
}
String nowParams = "";
if (request instanceof RepeatedlyRequestWrapper) {
RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
try {
nowParams = IoUtil.readUtf8(repeatedlyRequest.getInputStream());
} catch (IOException e) {
log.warn("读取流出现问题!");
}
}
// body参数为空获取Parameter的数据
if (StringUtils.isEmpty(nowParams)) {
nowParams = JsonUtils.toJsonString(request.getParameterMap());
}
Map<String, Object> nowDataMap = new HashMap<String, Object>();
nowDataMap.put(REPEAT_PARAMS, nowParams);
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
// 请求地址作为存放cache的key值
String url = request.getRequestURI();
// 唯一值没有消息头则使用请求地址
String submitKey = request.getHeader(tokenProperties.getHeader());
if (StringUtils.isEmpty(submitKey)) {
submitKey = url;
}
// 唯一标识指定key + 消息头
String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
Object sessionObj = RedisUtils.getCacheObject(cacheRepeatKey);
if (sessionObj != null) {
Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
if (sessionMap.containsKey(url)) {
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, intervalTime)) {
return true;
}
}
}
Map<String, Object> cacheMap = new HashMap<String, Object>();
cacheMap.put(url, nowDataMap);
RedisUtils.setCacheObject(cacheRepeatKey, cacheMap, Convert.toInt(intervalTime), TimeUnit.MILLISECONDS);
return false;
}
/**
* 判断参数是否相同
*/
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
String preParams = (String) preMap.get(REPEAT_PARAMS);
return nowParams.equals(preParams);
}
/**
* 判断两次间隔时间
*/
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, long intervalTime) {
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
return (time1 - time2) < intervalTime;
}
}

View File

@ -57,7 +57,7 @@ public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
try {
loginUser = SecurityUtils.getLoginUser();
} catch (Exception e) {
log.error("自动注入警告 => 用户未登录");
log.warn("自动注入警告 => 用户未登录");
return null;
}
return loginUser.getUsername();

View File

@ -1,9 +1,9 @@
package com.ruoyi.framework.security.filter;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@ -33,8 +33,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
throws ServletException, IOException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

View File

@ -4,11 +4,11 @@ import cn.hutool.http.HttpStatus;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.service.LogininforService;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.AsyncService;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
@ -31,7 +31,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
private TokenService tokenService;
@Autowired
private AsyncService asyncService;
private LogininforService asyncService;
/**
* 退出处理

View File

@ -1,97 +0,0 @@
package com.ruoyi.framework.web.service;
import com.ruoyi.common.utils.StringUtils;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.service.ISysLogininforService;
import com.ruoyi.system.service.ISysOperLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* 异步工厂产生任务用
*
* @author Lion Li
*/
@Slf4j(topic = "sys-user")
@Async
@Component
public class AsyncService {
@Autowired
private ISysLogininforService iSysLogininforService;
@Autowired
private ISysOperLogService iSysOperLogService;
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
*/
public void recordLogininfor(final String username, final String status, final String message,
HttpServletRequest request, final Object... args) {
final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
final String ip = ServletUtils.getClientIP(request);
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(getBlock(ip));
s.append(address);
s.append(getBlock(username));
s.append(getBlock(status));
s.append(getBlock(message));
// 打印信息到日志
log.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOs().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
logininfor.setStatus(Constants.SUCCESS);
} else if (Constants.LOGIN_FAIL.equals(status)) {
logininfor.setStatus(Constants.FAIL);
}
// 插入数据
iSysLogininforService.insertLogininfor(logininfor);
}
/**
* 操作日志记录
*
* @param operLog 操作日志信息
*/
public void recordOper(final SysOperLog operLog) {
// 远程查询操作地点
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
iSysOperLogService.insertOperlog(operLog);
}
private String getBlock(Object msg) {
if (msg == null) {
msg = "";
}
return "[" + msg.toString() + "]";
}
}

View File

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

View File

@ -33,8 +33,7 @@ public class GenUtils
/**
* 初始化列属性字段
*/
public static void initColumnField(GenTableColumn column, GenTable table)
{
public static void initColumnField(GenTableColumn column, GenTable table) {
String dataType = getDbType(column.getColumnType());
String columnName = column.getColumnName();
column.setTableId(table.getTableId());
@ -44,48 +43,48 @@ public class GenUtils
// 设置默认类型
column.setJavaType(GenConstants.TYPE_STRING);
if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType))
{
if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) {
// 字符串长度超过500设置为文本域
Integer columnLength = getColumnLength(column.getColumnType());
String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
column.setHtmlType(htmlType);
}
else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType))
{
} else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
column.setJavaType(GenConstants.TYPE_DATE);
column.setHtmlType(GenConstants.HTML_DATETIME);
}
else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType))
{
} else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
column.setHtmlType(GenConstants.HTML_INPUT);
// 如果是浮点型 统一用BigDecimal
String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0)
{
if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
}
// 如果是整形
else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10)
{
else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) {
column.setJavaType(GenConstants.TYPE_INTEGER);
}
// 长整形
else
{
else {
column.setJavaType(GenConstants.TYPE_LONG);
}
}
// 插入字段默认所有字段都需要插入
column.setIsInsert(GenConstants.REQUIRE);
// 主键不需要添加
if (!arraysContains(GenConstants.COLUMNNAME_NOT_ADD, columnName) && !column.isPk()) {
column.setIsInsert(GenConstants.REQUIRE);
}
// 编辑字段
if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk())
// 编辑需要主键
if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName))
{
column.setIsEdit(GenConstants.REQUIRE);
}
// 编辑需要的设置必选
if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName))
{
column.setIsRequired(GenConstants.REQUIRE);
}
// 列表字段
if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk())
{

View File

@ -16,7 +16,7 @@ import java.util.Map;
/**
* 模板处理工具类
*
*
* @author ruoyi
*/
public class VelocityUtils
@ -61,6 +61,7 @@ public class VelocityUtils
velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
velocityContext.put("columns", genTable.getColumns());
velocityContext.put("table", genTable);
velocityContext.put("dicts", getDicts(genTable));
setMenuVelocityContext(velocityContext, genTable);
if (GenConstants.TPL_TREE.equals(tplCategory))
{
@ -245,7 +246,7 @@ public class VelocityUtils
/**
* 根据列类型获取导入包
*
*
* @param genTable 业务表对象
* @return 返回需要导入的包列表
*/
@ -273,6 +274,27 @@ public class VelocityUtils
return importList;
}
/**
* 根据列类型获取字典组
*
* @param genTable 业务表对象
* @return 返回字典组
*/
public static String getDicts(GenTable genTable)
{
List<GenTableColumn> columns = genTable.getColumns();
List<String> dicts = new ArrayList<String>();
for (GenTableColumn column : columns)
{
if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
column.getHtmlType(), new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO }))
{
dicts.add("'" + column.getDictType() + "'");
}
}
return StringUtils.join(dicts, ", ");
}
/**
* 获取权限前缀
*

View File

@ -2,8 +2,6 @@ package ${packageName}.mapper;
import ${packageName}.domain.${ClassName};
import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
import com.ruoyi.common.core.mybatisplus.cache.MybatisPlusRedisCache;
import org.apache.ibatis.annotations.CacheNamespace;
/**
* ${functionName}Mapper接口

View File

@ -79,7 +79,12 @@ public class ${ClassName}ServiceImpl extends ServicePlusImpl<${ClassName}Mapper,
public Boolean insertByBo(${ClassName}Bo bo) {
${ClassName} add = BeanUtil.toBean(bo, ${ClassName}.class);
validEntityBeforeSave(add);
return save(add);
boolean flag = save(add);
#set($pk=$pkColumn.javaField.substring(0,1).toUpperCase() + ${pkColumn.javaField.substring(1)})
if (flag) {
bo.set$pk(add.get$pk());
}
return flag;
}
@Override

View File

@ -27,14 +27,8 @@ public class ${ClassName}Vo {
private static final long serialVersionUID = 1L;
/**
* $pkColumn.columnComment
*/
@ApiModelProperty("$pkColumn.columnComment")
private ${pkColumn.javaType} ${pkColumn.javaField};
#foreach ($column in $columns)
#if($column.isList && $column.isPk!=1)
#if($column.isList)
/**
* $column.columnComment
*/

View File

@ -25,10 +25,10 @@
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
<el-option
v-for="dict in ${column.javaField}Options"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
@ -108,7 +108,7 @@
#elseif($column.list && $column.dictType && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template slot-scope="scope">
<dict-tag :options="${javaField}Options" :value="scope.row.${javaField}"/>
<dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
</template>
</el-table-column>
#elseif($column.list && "" != $javaField)
@ -183,10 +183,10 @@
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option
v-for="dict in ${field}Options"
:key="dict.dictValue"
:label="dict.dictLabel"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.dictValue)"#else:value="dict.dictValue"#end
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
></el-option>
</el-select>
@ -201,10 +201,10 @@
<el-form-item label="${comment}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${field}Options"
:key="dict.dictValue"
:label="dict.dictValue">
{{dict.dictLabel}}
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
@ -218,11 +218,11 @@
<el-form-item label="${comment}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${field}Options"
:key="dict.dictValue"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.dictValue)"#else:label="dict.dictValue"#end
v-for="dict in dict.type.${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
>{{dict.dictLabel}}</el-radio>
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
@ -263,6 +263,9 @@ import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "${BusinessName}",
#if(${dicts} != '')
dicts: [${dicts}],
#end
components: {
Treeselect
},
@ -283,16 +286,7 @@ export default {
// 是否显示弹出层
open: false,
#foreach ($column in $columns)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if(${column.dictType} && ${column.dictType} != '')
// $comment字典
${column.javaField}Options: [],
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
// $comment时间范围
daterange${AttrName}: [],
@ -330,13 +324,6 @@ export default {
},
created() {
this.getList();
#foreach ($column in $columns)
#if(${column.dictType} && ${column.dictType} != '')
this.getDicts("${column.dictType}").then(response => {
this.${column.javaField}Options = response.data;
});
#end
#end
},
methods: {
/** 查询${functionName}列表 */
@ -464,7 +451,7 @@ export default {
#end
if (this.form.${pkColumn.javaField} != null) {
update${BusinessName}(this.form).then(response => {
this.msgSuccess("修改成功");
this.#[[$modal]]#.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
@ -472,7 +459,7 @@ export default {
});
} else {
add${BusinessName}(this.form).then(response => {
this.msgSuccess("新增成功");
this.#[[$modal]]#.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
@ -484,19 +471,15 @@ export default {
},
/** 删除按钮操作 */
handleDelete(row) {
this.$confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.loading = true;
return del${BusinessName}(row.${pkColumn.javaField});
}).then(() => {
this.loading = false;
this.getList();
this.msgSuccess("删除成功");
this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(() => {
this.loading = true;
return del${BusinessName}(row.${pkColumn.javaField});
}).then(() => {
this.loading = false;
this.getList();
this.#[[$modal]]#.msgSuccess("删除成功");
}).finally(() => {
this.loading = false;
this.loading = false;
});
}
}

View File

@ -25,10 +25,10 @@
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
<el-option
v-for="dict in ${column.javaField}Options"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
@ -137,7 +137,7 @@
#elseif($column.list && $column.dictType && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template slot-scope="scope">
<dict-tag :options="${javaField}Options" :value="scope.row.${javaField}"/>
<dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
</template>
</el-table-column>
#elseif($column.list && "" != $javaField)
@ -163,7 +163,7 @@
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
@ -205,10 +205,10 @@
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option
v-for="dict in ${field}Options"
:key="dict.dictValue"
:label="dict.dictLabel"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.dictValue)"#else:value="dict.dictValue"#end
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
></el-option>
</el-select>
@ -223,10 +223,10 @@
<el-form-item label="${comment}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${field}Options"
:key="dict.dictValue"
:label="dict.dictValue">
{{dict.dictLabel}}
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
@ -240,11 +240,11 @@
<el-form-item label="${comment}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${field}Options"
:key="dict.dictValue"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.dictValue)"#else:label="dict.dictValue"#end
v-for="dict in dict.type.${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
>{{dict.dictLabel}}</el-radio>
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
@ -315,6 +315,9 @@ import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${Busin
export default {
name: "${BusinessName}",
#if(${dicts} != '')
dicts: [${dicts}],
#end
data() {
return {
// 按钮loading
@ -348,16 +351,7 @@ export default {
// 是否显示弹出层
open: false,
#foreach ($column in $columns)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if(${column.dictType} && ${column.dictType} != '')
// $comment字典
${column.javaField}Options: [],
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
// $comment时间范围
daterange${AttrName}: [],
@ -397,13 +391,6 @@ export default {
},
created() {
this.getList();
#foreach ($column in $columns)
#if(${column.dictType} && ${column.dictType} != '')
this.getDicts("${column.dictType}").then(response => {
this.${column.javaField}Options = response.data;
});
#end
#end
},
methods: {
/** 查询${functionName}列表 */
@ -519,7 +506,7 @@ export default {
#end
if (this.form.${pkColumn.javaField} != null) {
update${BusinessName}(this.form).then(response => {
this.msgSuccess("修改成功");
this.#[[$modal]]#.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
@ -527,7 +514,7 @@ export default {
});
} else {
add${BusinessName}(this.form).then(response => {
this.msgSuccess("新增成功");
this.#[[$modal]]#.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
@ -540,19 +527,15 @@ export default {
/** 删除按钮操作 */
handleDelete(row) {
const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids;
this.$confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.loading = true;
return del${BusinessName}(${pkColumn.javaField}s);
}).then(() => {
this.loading = false;
this.getList();
this.msgSuccess("删除成功");
this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(() => {
this.loading = true;
return del${BusinessName}(${pkColumn.javaField}s);
}).then(() => {
this.loading = false;
this.getList();
this.#[[$modal]]#.msgSuccess("删除成功");
}).finally(() => {
this.loading = false;
this.loading = false;
});
},
#if($table.sub)
@ -574,7 +557,7 @@ export default {
/** ${subTable.functionName}删除按钮操作 */
handleDelete${subClassName}() {
if (this.checked${subClassName}.length == 0) {
this.msgError("请先选择要删除的${subTable.functionName}数据");
this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
} else {
const ${subclassName}List = this.${subclassName}List;
const checked${subClassName} = this.checked${subClassName};
@ -590,7 +573,7 @@ export default {
#end
/** 导出按钮操作 */
handleExport() {
this.downLoadExcel('/${moduleName}/${businessName}/export', this.queryParams);
this.#[[$download]]#.excel('/${moduleName}/${businessName}/export', this.queryParams);
}
}
};

View File

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

View File

@ -4,7 +4,7 @@ import java.util.Arrays;
import java.util.List;
/**
* 存储常量
* 对象存储常量
*
* @author Lion Li
*/
@ -16,7 +16,7 @@ public class CloudConstant {
public static final String SYS_OSS_KEY = "sys_oss:";
/**
* 存储配置KEY
* 对象存储配置KEY
*/
public static final String CLOUD_STORAGE_CONFIG_KEY = "CloudStorageConfig";

View File

@ -9,7 +9,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 存储服务商枚举
* 对象存储服务商枚举
*
* @author Lion Li
*/

View File

@ -5,7 +5,7 @@ import lombok.Data;
import java.util.Date;
/**
* OSS存储 配置属性
* OSS对象存储 配置属性
*
* @author Lion Li
*/

View File

@ -5,7 +5,7 @@ import com.ruoyi.oss.entity.UploadResult;
import java.io.InputStream;
/**
* 存储策略
* 对象存储策略
*
* @author Lion Li
*/

View File

@ -11,7 +11,7 @@ import com.ruoyi.oss.service.ICloudStorageStrategy;
import java.io.InputStream;
/**
* 存储策略(支持七牛阿里云腾讯云minio)
* 对象存储策略(支持七牛阿里云腾讯云minio)
*
* @author Lion Li
*/

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -37,4 +37,4 @@
</dependencies>
</project>
</project>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.1.0</version>
<version>3.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -31,4 +31,4 @@
</dependencies>
</project>
</project>

View File

@ -9,7 +9,7 @@ import java.io.Serializable;
import java.util.Date;
/**
* OSS存储对象
* OSS对象存储对象
*
* @author Lion Li
*/
@ -23,7 +23,7 @@ public class SysOss implements Serializable {
/**
* 存储主键
* 对象存储主键
*/
@TableId(value = "oss_id", type = IdType.AUTO)
private Long ossId;

View File

@ -9,7 +9,7 @@ import java.util.Date;
import java.math.BigDecimal;
/**
* 存储配置对象 sys_oss_config
* 对象存储配置对象 sys_oss_config
*
* @author ruoyi
* @date 2021-08-11

View File

@ -7,13 +7,13 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* OSS存储分页查询对象 sys_oss
* OSS对象存储分页查询对象 sys_oss
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("OSS存储分页查询对象")
@ApiModel("OSS对象存储分页查询对象")
public class SysOssBo extends BaseEntity {
/**

View File

@ -13,7 +13,7 @@ import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* 存储配置业务对象 sys_oss_config
* 对象存储配置业务对象 sys_oss_config
*
* @author Lion Li
* @author 孤舟烟雨
@ -22,7 +22,7 @@ import javax.validation.constraints.Size;
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("存储配置业务对象")
@ApiModel("对象存储配置业务对象")
public class SysOssConfigBo extends BaseEntity {
/**

View File

@ -41,6 +41,11 @@ public class RouterVo {
*/
private String component;
/**
* 路由参数 {"id": 1, "name": "ry"}
*/
private String query;
/**
* 当你一个路由下面的 children 声明的路由大于1个时自动会变成嵌套的模式--如组件页面
*/

View File

@ -8,14 +8,14 @@ import lombok.Data;
/**
* 存储配置视图对象 sys_oss_config
* 对象存储配置视图对象 sys_oss_config
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
@Data
@ApiModel("存储配置视图对象")
@ApiModel("对象存储配置视图对象")
@ExcelIgnoreUnannotated
public class SysOssConfigVo {

View File

@ -7,20 +7,20 @@ import lombok.Data;
import java.util.Date;
/**
* OSS存储视图对象 sys_oss
* OSS对象存储视图对象 sys_oss
*
* @author Lion Li
*/
@Data
@ApiModel("OSS存储视图对象")
@ApiModel("OSS对象存储视图对象")
public class SysOssVo {
private static final long serialVersionUID = 1L;
/**
* 存储主键
* 对象存储主键
*/
@ApiModelProperty("存储主键")
@ApiModelProperty("对象存储主键")
private Long ossId;
/**

View File

@ -4,7 +4,7 @@ import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
import com.ruoyi.system.domain.SysOssConfig;
/**
* 存储配置Mapper接口
* 对象存储配置Mapper接口
*
* @author Lion Li
* @author 孤舟烟雨

View File

@ -9,7 +9,7 @@ import com.ruoyi.system.domain.vo.SysOssConfigVo;
import java.util.Collection;
/**
* 存储配置Service接口
* 对象存储配置Service接口
*
* @author Lion Li
* @author 孤舟烟雨
@ -29,15 +29,15 @@ public interface ISysOssConfigService extends IServicePlus<SysOssConfig, SysOssC
/**
* 根据新增业务对象插入存储配置
* @param bo 存储配置新增业务对象
* 根据新增业务对象插入对象存储配置
* @param bo 对象存储配置新增业务对象
* @return
*/
Boolean insertByBo(SysOssConfigBo bo);
/**
* 根据编辑业务对象修改存储配置
* @param bo 存储配置编辑业务对象
* 根据编辑业务对象修改对象存储配置
* @param bo 对象存储配置编辑业务对象
* @return
*/
Boolean updateByBo(SysOssConfigBo bo);

View File

@ -1,4 +1,4 @@
package com.ruoyi.framework.web.service;
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser;
@ -15,12 +15,15 @@ import java.util.Set;
* @author ruoyi
*/
@Service("ss")
public class PermissionService
{
/** 所有权限标识 */
public class PermissionService {
/**
* 所有权限标识
*/
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
/**
* 管理员角色权限标识
*/
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
@ -33,15 +36,12 @@ public class PermissionService
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
public boolean hasPermi(String permission) {
if (StringUtils.isEmpty(permission)) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
return false;
}
return hasPermissions(loginUser.getPermissions(), permission);
@ -53,8 +53,7 @@ public class PermissionService
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
public boolean lacksPermi(String permission) {
return hasPermi(permission) != true;
}
@ -64,22 +63,17 @@ public class PermissionService
* @param permissions PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String permissions)
{
if (StringUtils.isEmpty(permissions))
{
public boolean hasAnyPermi(String permissions) {
if (StringUtils.isEmpty(permissions)) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
return false;
}
Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(PERMISSION_DELIMETER))
{
if (permission != null && hasPermissions(authorities, permission))
{
for (String permission : permissions.split(PERMISSION_DELIMETER)) {
if (permission != null && hasPermissions(authorities, permission)) {
return true;
}
}
@ -92,22 +86,17 @@ public class PermissionService
* @param role 角色字符串
* @return 用户是否具备某角色
*/
public boolean hasRole(String role)
{
if (StringUtils.isEmpty(role))
{
public boolean hasRole(String role) {
if (StringUtils.isEmpty(role)) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
return false;
}
for (SysRole sysRole : loginUser.getUser().getRoles())
{
for (SysRole sysRole : loginUser.getUser().getRoles()) {
String roleKey = sysRole.getRoleKey();
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
{
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {
return true;
}
}
@ -120,8 +109,7 @@ public class PermissionService
* @param role 角色名称
* @return 用户是否不具备某角色
*/
public boolean lacksRole(String role)
{
public boolean lacksRole(String role) {
return hasRole(role) != true;
}
@ -131,21 +119,16 @@ public class PermissionService
* @param roles ROLE_NAMES_DELIMETER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String roles)
{
if (StringUtils.isEmpty(roles))
{
public boolean hasAnyRoles(String roles) {
if (StringUtils.isEmpty(roles)) {
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
return false;
}
for (String role : roles.split(ROLE_DELIMETER))
{
if (hasRole(role))
{
for (String role : roles.split(ROLE_DELIMETER)) {
if (hasRole(role)) {
return true;
}
}
@ -156,11 +139,10 @@ public class PermissionService
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Set<String> permissions, String permission)
{
private boolean hasPermissions(Set<String> permissions, String permission) {
return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}
}

View File

@ -1,8 +1,10 @@
package com.ruoyi.framework.web.service;
package com.ruoyi.system.service;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.service.LogininforService;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
@ -11,14 +13,12 @@ import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.RedisUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@ -28,66 +28,57 @@ import javax.servlet.http.HttpServletRequest;
*
* @author ruoyi
*/
@Component
public class SysLoginService
{
@Service
public class SysLoginService {
@Autowired
private TokenService tokenService;
@Resource
private AuthenticationManager authenticationManager;
@Autowired
@Autowired
private ISysUserService userService;
@Autowired
private ISysConfigService configService;
@Autowired
private ISysConfigService configService;
@Autowired
private AsyncService asyncService;
@Autowired
private LogininforService asyncService;
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public String login(String username, String password, String code, String uuid)
{
HttpServletRequest request = ServletUtils.getRequest();
boolean captchaOnOff = configService.selectCaptchaOnOff();
public String login(String username, String password, String code, String uuid) {
HttpServletRequest request = ServletUtils.getRequest();
boolean captchaOnOff = configService.selectCaptchaOnOff();
// 验证码开关
if (captchaOnOff)
{
if (captchaOnOff) {
validateCaptcha(username, code, uuid, request);
}
// 用户验证
Authentication authentication = null;
try
{
try {
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
} catch (Exception e) {
if (e instanceof BadCredentialsException) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
throw new UserPasswordNotMatchException();
}
else
{
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
} else {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
throw new ServiceException(e.getMessage());
}
}
asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUser());
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
}
@ -96,32 +87,34 @@ public class SysLoginService
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = RedisUtils.getCacheObject(verifyKey);
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
throw new CaptchaException();
}
if (captcha == null) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(SysUser user)
{
user.setLoginIp(ServletUtils.getClientIP());
user.setLoginDate(DateUtils.getNowDate());
user.setUpdateBy(user.getUserName());
userService.updateUserProfile(user);
public void recordLoginInfo(Long userId) {
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(ServletUtils.getClientIP());
sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser);
}
}

View File

@ -1,21 +1,20 @@
package com.ruoyi.framework.web.service;
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.entity.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysRoleService;
/**
* 用户权限处理
*
*
* @author ruoyi
*/
@Component
public class SysPermissionService
{
@Service
public class SysPermissionService {
@Autowired
private ISysRoleService roleService;
@ -24,20 +23,16 @@ public class SysPermissionService
/**
* 获取角色数据权限
*
*
* @param user 用户信息
* @return 角色权限信息
*/
public Set<String> getRolePermission(SysUser user)
{
public Set<String> getRolePermission(SysUser user) {
Set<String> roles = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin())
{
if (user.isAdmin()) {
roles.add("admin");
}
else
{
} else {
roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
}
return roles;
@ -45,20 +40,16 @@ public class SysPermissionService
/**
* 获取菜单数据权限
*
*
* @param user 用户信息
* @return 菜单权限信息
*/
public Set<String> getMenuPermission(SysUser user)
{
public Set<String> getMenuPermission(SysUser user) {
Set<String> perms = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin())
{
if (user.isAdmin()) {
perms.add("*:*:*");
}
else
{
} else {
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
}
return perms;

View File

@ -1,84 +1,67 @@
package com.ruoyi.framework.web.service;
package com.ruoyi.system.service;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.core.service.LogininforService;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.utils.*;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* 注册校验方法
*
* @author ruoyi
*/
@Component
public class SysRegisterService
{
@Service
public class SysRegisterService {
@Autowired
private ISysUserService userService;
@Autowired
private ISysConfigService configService;
@Autowired
private AsyncService asyncService;
@Autowired
private LogininforService asyncService;
/**
* 注册
*/
public String register(RegisterBody registerBody)
{
public String register(RegisterBody registerBody) {
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
boolean captchaOnOff = configService.selectCaptchaOnOff();
// 验证码开关
if (captchaOnOff)
{
if (captchaOnOff) {
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
}
if (StringUtils.isEmpty(username))
{
if (StringUtils.isEmpty(username)) {
msg = "用户名不能为空";
}
else if (StringUtils.isEmpty(password))
{
} else if (StringUtils.isEmpty(password)) {
msg = "用户密码不能为空";
}
else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
} else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
msg = "账户长度必须在2到20个字符之间";
}
else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
} else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
msg = "密码长度必须在5到20个字符之间";
}
else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username)))
{
} else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username))) {
msg = "保存用户'" + username + "'失败,注册账号已存在";
}
else
{
} else {
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword()));
boolean regFlag = userService.registerUser(sysUser);
if (!regFlag)
{
if (!regFlag) {
msg = "注册失败,请联系系统管理人员";
}
else
{
asyncService.recordLogininfor(username, Constants.REGISTER,
} else {
asyncService.recordLogininfor(username, Constants.REGISTER,
MessageUtils.message("user.register.success"), ServletUtils.getRequest());
}
}
@ -89,21 +72,18 @@ public class SysRegisterService
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid)
{
public void validateCaptcha(String username, String code, String uuid) {
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null)
{
if (captcha == null) {
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha))
{
if (!code.equalsIgnoreCase(captcha)) {
throw new CaptchaException();
}
}

View File

@ -1,15 +1,24 @@
package com.ruoyi.system.service.impl;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.LogininforService;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.mapper.SysLogininforMapper;
import com.ruoyi.system.service.ISysLogininforService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ -20,8 +29,62 @@ import java.util.Map;
*
* @author ruoyi
*/
@Slf4j
@Service
public class SysLogininforServiceImpl extends ServicePlusImpl<SysLogininforMapper, SysLogininfor, SysLogininfor> implements ISysLogininforService {
public class SysLogininforServiceImpl extends ServicePlusImpl<SysLogininforMapper, SysLogininfor, SysLogininfor> implements ISysLogininforService, LogininforService {
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
*/
@Async
@Override
public void recordLogininfor(final String username, final String status, final String message,
HttpServletRequest request, final Object... args) {
final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
final String ip = ServletUtils.getClientIP(request);
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(getBlock(ip));
s.append(address);
s.append(getBlock(username));
s.append(getBlock(status));
s.append(getBlock(message));
// 打印信息到日志
log.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOs().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
logininfor.setStatus(Constants.SUCCESS);
} else if (Constants.LOGIN_FAIL.equals(status)) {
logininfor.setStatus(Constants.FAIL);
}
// 插入数据
insertLogininfor(logininfor);
}
private String getBlock(Object msg) {
if (msg == null) {
msg = "";
}
return "[" + msg.toString() + "]";
}
@Override
public TableDataInfo<SysLogininfor> selectPageLogininforList(SysLogininfor logininfor) {

View File

@ -135,6 +135,7 @@ public class SysMenuServiceImpl extends ServicePlusImpl<SysMenuMapper, SysMenu,
router.setName(getRouteName(menu));
router.setPath(getRouterPath(menu));
router.setComponent(getComponent(menu));
router.setQuery(menu.getQuery());
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
List<SysMenu> cMenus = menu.getChildren();
if (!cMenus.isEmpty() && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {

View File

@ -1,14 +1,19 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.OperLogService;
import com.ruoyi.common.core.domain.dto.OperLogDTO;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.mapper.SysOperLogMapper;
import com.ruoyi.system.service.ISysOperLogService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@ -22,7 +27,21 @@ import java.util.Map;
* @author ruoyi
*/
@Service
public class SysOperLogServiceImpl extends ServicePlusImpl<SysOperLogMapper, SysOperLog, SysOperLog> implements ISysOperLogService {
public class SysOperLogServiceImpl extends ServicePlusImpl<SysOperLogMapper, SysOperLog, SysOperLog> implements ISysOperLogService, OperLogService {
/**
* 操作日志记录
*
* @param operLogDTO 操作日志信息
*/
@Async
@Override
public void recordOper(final OperLogDTO operLogDTO) {
SysOperLog operLog = BeanUtil.toBean(operLogDTO, SysOperLog.class);
// 远程查询操作地点
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
insertOperlog(operLog);
}
@Override
public TableDataInfo<SysOperLog> selectPageOperLogList(SysOperLog operLog) {

View File

@ -31,7 +31,7 @@ import java.util.Collection;
import java.util.List;
/**
* 存储配置Service业务层处理
* 对象存储配置Service业务层处理
*
* @author Lion Li
* @author 孤舟烟雨

View File

@ -1,20 +1,21 @@
package com.ruoyi.framework.web.service;
package com.ruoyi.system.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.properties.TokenProperties;
import com.ruoyi.common.utils.RedisUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.framework.config.properties.TokenProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
@ -26,8 +27,8 @@ import java.util.concurrent.TimeUnit;
*
* @author Lion Li
*/
@Component
public class TokenService {
@Service
public class TokenServiceImpl implements TokenService {
protected static final long MILLIS_SECOND = 1000;
@ -43,6 +44,7 @@ public class TokenService {
*
* @return 用户信息
*/
@Override
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
@ -64,6 +66,7 @@ public class TokenService {
/**
* 设置用户身份信息
*/
@Override
public void setLoginUser(LoginUser loginUser) {
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
refreshToken(loginUser);
@ -73,6 +76,7 @@ public class TokenService {
/**
* 删除用户身份信息
*/
@Override
public void delLoginUser(String token) {
if (StringUtils.isNotEmpty(token)) {
String userKey = getTokenKey(token);
@ -86,6 +90,7 @@ public class TokenService {
* @param loginUser 用户信息
* @return 令牌
*/
@Override
public String createToken(LoginUser loginUser) {
String token = IdUtil.fastUUID();
loginUser.setToken(token);
@ -103,6 +108,7 @@ public class TokenService {
* @param loginUser
* @return 令牌
*/
@Override
public void verifyToken(LoginUser loginUser) {
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
@ -116,6 +122,7 @@ public class TokenService {
*
* @param loginUser 登录信息
*/
@Override
public void refreshToken(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE);
@ -129,6 +136,7 @@ public class TokenService {
*
* @param loginUser 登录信息
*/
@Override
public void setUserAgent(LoginUser loginUser) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
@ -170,6 +178,7 @@ public class TokenService {
* @param token 令牌
* @return 用户名
*/
@Override
public String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.getSubject();

View File

@ -1,28 +1,27 @@
package com.ruoyi.framework.web.service;
package com.ruoyi.system.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.SysPermissionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 用户验证处理
*
* @author ruoyi
*/
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private ISysUserService userService;
@ -31,21 +30,15 @@ public class UserDetailsServiceImpl implements UserDetailsService
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user))
{
if (StringUtils.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
@ -53,8 +46,7 @@ public class UserDetailsServiceImpl implements UserDetailsService
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user)
{
public UserDetails createLoginUser(SysUser user) {
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}

View File

@ -12,7 +12,8 @@
<result property="orderNum" column="order_num"/>
<result property="path" column="path"/>
<result property="component" column="component"/>
<result property="isFrame" column="is_frame"/>
<result property="query" column="query"/>
<result property="isFrame" column="is_frame"/>
<result property="isCache" column="is_cache"/>
<result property="menuType" column="menu_type"/>
<result property="visible" column="visible"/>
@ -33,6 +34,7 @@
order_num,
path,
component,
query,
is_frame,
is_cache,
menu_type,
@ -51,7 +53,8 @@
m.menu_name,
m.path,
m.component,
m.visible,
m.query,
m.visible,
m.status,
ifnull(m.perms, '') as perms,
m.is_frame,
@ -67,7 +70,7 @@
</select>
<select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status,
select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.visible, m.status,
ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
@ -92,6 +95,7 @@
m.menu_name,
m.path,
m.component,
m.query,
m.visible,
m.status,
ifnull(m.perms, '') as perms,

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