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) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br> <br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-3.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)]() [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.5-blue.svg)]()
[![JDK-8+](https://img.shields.io/badge/JDK-8+-green.svg)]() [![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
[![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]() [![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼容原框架) RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼容原框架)
| 功能介绍 | 使用技术 | 文档地址 | 特性注意事项 | | 功能介绍 | 使用技术 | 文档地址 | 特性注意事项 |
|---|---|---|---| |---|---|---|---|
| 当前框架 | RuoYi-Vue-Plus | [RuoYi-Vue-Plus文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) | 重写RuoYi-Vue全方位升级(不兼容原框架) | | 当前框架 | RuoYi-Vue-Plus | [RuoYi-Vue-Plus文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) | 重写RuoYi-Vue全方位升级(不兼容原框架) |
| satoken分支 | RuoYi-Vue-Plus-satoken | [satoken分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/) | 使用satoken重构权限鉴权(仅供学习不推荐上生产) |
| 单体分支 | RuoYi-Vue-Plus-fast | [fast分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/) | 单体应用结构 |
| 原框架 | RuoYi-Vue | [RuoYi-Vue官网](http://ruoyi.vip/) | 定期同步需要的功能 | | 原框架 | RuoYi-Vue | [RuoYi-Vue官网](http://ruoyi.vip/) | 定期同步需要的功能 |
| 前端开发框架 | Vue、Element UI | [Element UI官网](https://element.eleme.cn/#/zh-CN) | | | 前端开发框架 | Vue、Element UI | [Element UI官网](https://element.eleme.cn/#/zh-CN) | |
| 后端开发框架 | SpringBoot | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn) | | | 后端开发框架 | SpringBoot | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn) | |
@ -24,10 +27,12 @@ RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼
| 数据库框架 | Mybatis-Plus | [Mybatis-Plus文档](https://baomidou.com/guide/) | 快速 CRUD 增加开发效率 | | 数据库框架 | Mybatis-Plus | [Mybatis-Plus文档](https://baomidou.com/guide/) | 快速 CRUD 增加开发效率 |
| 数据库框架 | p6spy | [p6spy官网](https://p6spy.readthedocs.io/) | 更强劲的 SQL 分析 | | 数据库框架 | p6spy | [p6spy官网](https://p6spy.readthedocs.io/) | 更强劲的 SQL 分析 |
| 多数据源框架 | dynamic-datasource | [dynamic-ds文档](https://www.kancloud.cn/tracy5546/dynamic-datasource/content) | 支持主从与多种类数据库异构 | | 多数据源框架 | 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 高效可靠 | | 序列化框架 | Jackson | [Jackson官网](https://github.com/FasterXML/jackson) | 统一使用 jackson 高效可靠 |
| 网络框架 | Feign、OkHttp3 | [Feign官网](https://github.com/OpenFeign/feign) | 接口化管理 HTTP 请求 | | 网络框架 | 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) | 注解锁、工具锁 多种多样 |
| 分布式幂等 | Lock4j | [Lock4j文档](https://gitee.com/baomidou/lock4j) | 基于分布式锁实现 |
| 文件存储 | Minio | [Minio文档](https://docs.min.io/) | 本地存储 | | 文件存储 | Minio | [Minio文档](https://docs.min.io/) | 本地存储 |
| 文件存储 | 七牛、阿里、腾讯 | [OSS使用文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4359146&doc_id=1469725) | 云存储 | | 文件存储 | 七牛、阿里、腾讯 | [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/) | 全方位服务监控 | | 监控框架 | SpringBoot-Admin | [SpringBoot-Admin文档](https://codecentric.github.io/spring-boot-admin/current/) | 全方位服务监控 |
@ -67,6 +72,7 @@ RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级(不兼
* 同步升级 RuoYi-Vue * 同步升级 RuoYi-Vue
* GitHub 地址 [RuoYi-Vue-Plus-github](https://github.com/JavaLionLi/RuoYi-Vue-Plus) * 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/) * 单模块 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) * 用户扩展项目 [扩展项目列表](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 ipv4_address: 172.30.0.54
ruoyi-server1: ruoyi-server1:
image: "ruoyi/ruoyi-server:3.1.0" image: "ruoyi/ruoyi-server:3.2.0"
container_name: ruoyi-server1 container_name: ruoyi-server1
environment: environment:
# 时区上海 # 时区上海
@ -118,7 +118,7 @@ services:
ipv4_address: 172.30.0.60 ipv4_address: 172.30.0.60
ruoyi-server2: ruoyi-server2:
image: "ruoyi/ruoyi-server:3.1.0" image: "ruoyi/ruoyi-server:3.2.0"
container_name: ruoyi-server2 container_name: ruoyi-server2
environment: environment:
# 时区上海 # 时区上海
@ -133,7 +133,7 @@ services:
ipv4_address: 172.30.0.61 ipv4_address: 172.30.0.61
ruoyi-monitor-admin: ruoyi-monitor-admin:
image: "ruoyi/ruoyi-monitor-admin:3.1.0" image: "ruoyi/ruoyi-monitor-admin:3.2.0"
container_name: ruoyi-monitor-admin container_name: ruoyi-monitor-admin
environment: environment:
# 时区上海 # 时区上海

96
pom.xml
View File

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

View File

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

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.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginBody; import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.service.SysLoginService; import com.ruoyi.system.service.SysLoginService;
import com.ruoyi.framework.web.service.SysPermissionService; import com.ruoyi.system.service.SysPermissionService;
import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysMenuService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;

View File

@ -25,14 +25,14 @@ import javax.validation.constraints.NotNull;
import java.util.Arrays; import java.util.Arrays;
/** /**
* 存储配置Controller * 对象存储配置Controller
* *
* @author Lion Li * @author Lion Li
* @author 孤舟烟雨 * @author 孤舟烟雨
* @date 2021-08-13 * @date 2021-08-13
*/ */
@Validated @Validated
@Api(value = "存储配置控制器", tags = {"存储配置管理"}) @Api(value = "对象存储配置控制器", tags = {"对象存储配置管理"})
@RequiredArgsConstructor(onConstructor_ = @Autowired) @RequiredArgsConstructor(onConstructor_ = @Autowired)
@RestController @RestController
@RequestMapping("/system/oss/config") @RequestMapping("/system/oss/config")
@ -41,9 +41,9 @@ public class SysOssConfigController extends BaseController {
private final ISysOssConfigService iSysOssConfigService; private final ISysOssConfigService iSysOssConfigService;
/** /**
* 查询存储配置列表 * 查询对象存储配置列表
*/ */
@ApiOperation("查询存储配置列表") @ApiOperation("查询对象存储配置列表")
@PreAuthorize("@ss.hasPermi('system:oss:list')") @PreAuthorize("@ss.hasPermi('system:oss:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo) { 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')") @PreAuthorize("@ss.hasPermi('system:oss:query')")
@GetMapping("/{ossConfigId}") @GetMapping("/{ossConfigId}")
public AjaxResult<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空") public AjaxResult<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
@ -62,11 +62,11 @@ public class SysOssConfigController extends BaseController {
} }
/** /**
* 新增存储配置 * 新增对象存储配置
*/ */
@ApiOperation("新增存储配置") @ApiOperation("新增对象存储配置")
@PreAuthorize("@ss.hasPermi('system:oss:add')") @PreAuthorize("@ss.hasPermi('system:oss:add')")
@Log(title = "存储配置", businessType = BusinessType.INSERT) @Log(title = "对象存储配置", businessType = BusinessType.INSERT)
@RepeatSubmit() @RepeatSubmit()
@PostMapping() @PostMapping()
public AjaxResult<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) { 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')") @PreAuthorize("@ss.hasPermi('system:oss:edit')")
@Log(title = "存储配置", businessType = BusinessType.UPDATE) @Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
@RepeatSubmit() @RepeatSubmit()
@PutMapping() @PutMapping()
public AjaxResult<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) { 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')") @PreAuthorize("@ss.hasPermi('system:oss:remove')")
@Log(title = "存储配置", businessType = BusinessType.DELETE) @Log(title = "对象存储配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossConfigIds}") @DeleteMapping("/{ossConfigIds}")
public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空") public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossConfigIds) { @PathVariable Long[] ossConfigIds) {
@ -101,7 +101,7 @@ public class SysOssConfigController extends BaseController {
* 状态修改 * 状态修改
*/ */
@PreAuthorize("@ss.hasPermi('system:oss:edit')") @PreAuthorize("@ss.hasPermi('system:oss:edit')")
@Log(title = "存储状态修改", businessType = BusinessType.UPDATE) @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus") @PutMapping("/changeStatus")
public AjaxResult changeStatus(@RequestBody SysOssConfigBo bo) { public AjaxResult changeStatus(@RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateOssConfigStatus(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.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
@ -49,7 +50,7 @@ import java.util.Map;
* @author Lion Li * @author Lion Li
*/ */
@Validated @Validated
@Api(value = "OSS存储控制器", tags = {"OSS存储管理"}) @Api(value = "OSS对象存储控制器", tags = {"OSS对象存储管理"})
@RequiredArgsConstructor(onConstructor_ = @Autowired) @RequiredArgsConstructor(onConstructor_ = @Autowired)
@RestController @RestController
@RequestMapping("/system/oss") @RequestMapping("/system/oss")
@ -59,9 +60,9 @@ public class SysOssController extends BaseController {
private final ISysConfigService iSysConfigService; private final ISysConfigService iSysConfigService;
/** /**
* 查询OSS存储列表 * 查询OSS对象存储列表
*/ */
@ApiOperation("查询OSS存储列表") @ApiOperation("查询OSS对象存储列表")
@PreAuthorize("@ss.hasPermi('system:oss:list')") @PreAuthorize("@ss.hasPermi('system:oss:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo) { 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({ @ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "文件", dataType = "java.io.File", required = true), @ApiImplicitParam(name = "file", value = "文件", dataType = "java.io.File", required = true),
}) })
@PreAuthorize("@ss.hasPermi('system:oss:upload')") @PreAuthorize("@ss.hasPermi('system:oss:upload')")
@Log(title = "OSS存储", businessType = BusinessType.INSERT) @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@RepeatSubmit @RepeatSubmit
@PostMapping("/upload") @PostMapping("/upload")
public AjaxResult<Map<String, String>> upload(@RequestPart("file") MultipartFile file) { public AjaxResult<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
@ -90,7 +91,7 @@ public class SysOssController extends BaseController {
return AjaxResult.success(map); return AjaxResult.success(map);
} }
@ApiOperation("下载OSS存储") @ApiOperation("下载OSS对象存储")
@PreAuthorize("@ss.hasPermi('system:oss:download')") @PreAuthorize("@ss.hasPermi('system:oss:download')")
@GetMapping("/download/{ossId}") @GetMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException { public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
@ -101,18 +102,27 @@ public class SysOssController extends BaseController {
response.reset(); response.reset();
response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); 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"); 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)); response.setContentLength(Convert.toInt(data));
} }
/** /**
* 删除OSS云存储 * 删除OSS对象存储
*/ */
@ApiOperation("删除OSS云存储") @ApiOperation("删除OSS对象存储")
@PreAuthorize("@ss.hasPermi('system:oss:remove')") @PreAuthorize("@ss.hasPermi('system:oss:remove')")
@Log(title = "OSS云存储" , businessType = BusinessType.DELETE) @Log(title = "OSS对象存储" , businessType = BusinessType.DELETE)
@DeleteMapping("/{ossIds}") @DeleteMapping("/{ossIds}")
public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空") public AjaxResult<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) { @PathVariable Long[] ossIds) {
@ -124,7 +134,7 @@ public class SysOssController extends BaseController {
*/ */
@ApiOperation("变更图片列表预览状态") @ApiOperation("变更图片列表预览状态")
@PreAuthorize("@ss.hasPermi('system:oss:edit')") @PreAuthorize("@ss.hasPermi('system:oss:edit')")
@Log(title = "OSS存储" , businessType = BusinessType.UPDATE) @Log(title = "OSS对象存储" , businessType = BusinessType.UPDATE)
@PutMapping("/changePreviewListResource") @PutMapping("/changePreviewListResource")
public AjaxResult<Void> changePreviewListResource(@RequestBody String body) { public AjaxResult<Void> changePreviewListResource(@RequestBody String body) {
Map<String, Boolean> map = JsonUtils.parseMap(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.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.domain.SysOss; import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.service.ISysOssService; import com.ruoyi.system.service.ISysOssService;
import com.ruoyi.system.service.ISysUserService; 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.AjaxResult;
import com.ruoyi.common.core.domain.model.RegisterBody; import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.utils.StringUtils; 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 com.ruoyi.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping; 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.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.core.service.TokenService;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil; 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.domain.SysUserRole;
import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.SysPermissionService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;

View File

@ -70,7 +70,7 @@ spring:
config: config:
multi-statement-allow: true multi-statement-allow: true
--- # redis 配置 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring: spring:
redis: redis:
# 地址 # 地址
@ -118,6 +118,67 @@ redisson:
# DNS监测时间间隔单位毫秒 # DNS监测时间间隔单位毫秒
dnsMonitoringInterval: 5000 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: spring:
boot: boot:

View File

@ -70,7 +70,7 @@ spring:
config: config:
multi-statement-allow: true multi-statement-allow: true
--- # redis 配置 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring: spring:
redis: redis:
# 地址 # 地址
@ -118,6 +118,67 @@ redisson:
# DNS监测时间间隔单位毫秒 # DNS监测时间间隔单位毫秒
dnsMonitoringInterval: 5000 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: spring:
boot: boot:

View File

@ -106,10 +106,32 @@ token:
# 令牌有效期默认30分钟 # 令牌有效期默认30分钟
expireTime: 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: repeat-submit:
# 全局间隔时间(毫秒) # 全局间隔时间(毫秒)
intervalTime: 1000 interval: 5000
# MyBatisPlus配置 # MyBatisPlus配置
# https://baomidou.com/config/ # https://baomidou.com/config/
@ -221,6 +243,11 @@ swagger:
name: Lion Li name: Lion Li
email: crazylionli@163.com email: crazylionli@163.com
url: https://gitee.com/JavaLionLi/RuoYi-Vue-Plus url: https://gitee.com/JavaLionLi/RuoYi-Vue-Plus
groups:
- name: 演示案例
basePackage: com.ruoyi.demo
- name: 系统模块
basePackage: com.ruoyi.admin
# 防止XSS攻击 # 防止XSS攻击
xss: xss:
@ -244,7 +271,7 @@ thread-pool:
# 线程池维护线程所允许的空闲时间 # 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300 keepAliveSeconds: 300
# 线程池对拒绝任务(无线程可用)的处理策略 # 线程池对拒绝任务(无线程可用)的处理策略
# CALLER_RUNS_POLICY 等待 # CALLER_RUNS_POLICY 调用方执行
# DISCARD_OLDEST_POLICY 放弃最旧的 # DISCARD_OLDEST_POLICY 放弃最旧的
# DISCARD_POLICY 丢弃 # DISCARD_POLICY 丢弃
# ABORT_POLICY 中止 # ABORT_POLICY 中止

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.1.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -131,6 +131,11 @@
<artifactId>knife4j-spring-boot-starter</artifactId> <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
@ -147,22 +152,10 @@
<artifactId>redisson-spring-boot-starter</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
</dependency> </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> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId> <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

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

View File

@ -20,10 +20,15 @@ import java.util.concurrent.TimeUnit;
public @interface RepeatSubmit { public @interface RepeatSubmit {
/** /**
* 默认使用全局配置 * 间隔时间(ms)小于此时间视为重复提交
*/ */
int intervalTime() default 0; int interval() default 5000;
TimeUnit timeUnit() default TimeUnit.MILLISECONDS; 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", public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
"bit", "bigint", "float", "double", "decimal" }; "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", 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", public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by",
"update_time", "remark" }; "update_time", "remark", "version" };
/** Entity基类字段 */ /** Entity基类字段 */
public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };

View File

@ -1,5 +1,6 @@
package com.ruoyi.common.core.domain; package com.ruoyi.common.core.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -62,6 +63,7 @@ public class BaseEntity implements Serializable {
/** /**
* 请求参数 * 请求参数
*/ */
@JsonIgnore
@ApiModelProperty(value = "请求参数") @ApiModelProperty(value = "请求参数")
private Map<String, Object> params = new HashMap<>(); 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停用 * 状态0正常 1停用
*/ */
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class) @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_common_status") @ExcelDictFormat(dictType = "sys_normal_disable")
private String status; private String status;
/** /**

View File

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

View File

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

View File

@ -91,11 +91,6 @@ public class SysUser implements Serializable {
return password; return password;
} }
/**
* 盐加密
*/
private String salt;
/** /**
* 帐号状态0正常 1停用 * 帐号状态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 IP,
/**
* 实例限流(集群多后端实例)
*/
CLUSTER
} }

View File

@ -15,7 +15,7 @@ import java.util.concurrent.ThreadPoolExecutor;
@AllArgsConstructor @AllArgsConstructor
public enum ThreadPoolRejectedPolicy { public enum ThreadPoolRejectedPolicy {
CALLER_RUNS_POLICY("等待", ThreadPoolExecutor.CallerRunsPolicy.class), CALLER_RUNS_POLICY("调用方执行", ThreadPoolExecutor.CallerRunsPolicy.class),
DISCARD_OLDEST_POLICY("放弃最旧的", ThreadPoolExecutor.DiscardOldestPolicy.class), DISCARD_OLDEST_POLICY("放弃最旧的", ThreadPoolExecutor.DiscardOldestPolicy.class),
DISCARD_POLICY("丢弃", ThreadPoolExecutor.DiscardPolicy.class), DISCARD_POLICY("丢弃", ThreadPoolExecutor.DiscardPolicy.class),
ABORT_POLICY("中止", ThreadPoolExecutor.AbortPolicy.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 lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -25,6 +25,32 @@ public class RedisUtils {
private static RedissonClient client = SpringUtils.getBean(RedissonClient.class); 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); .append(percentEncodedFileName);
response.setHeader("Content-disposition", contentDispositionValue.toString()); 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) { public static String getRealAddressByIP(String ip) {
String address = UNKNOWN; 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); ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
if (NetUtil.isInnerIP(ip)) { if (NetUtil.isInnerIP(ip)) {

View File

@ -46,7 +46,7 @@ public class ExcelUtil {
response.reset(); response.reset();
response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); 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"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
ServletOutputStream os = response.getOutputStream(); ServletOutputStream os = response.getOutputStream();
EasyExcel.write(os, clazz) EasyExcel.write(os, clazz)

View File

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

View File

@ -34,6 +34,9 @@ public class RedisCacheController {
* 如果没有,就调用方法,然后把结果缓存起来 * 如果没有,就调用方法,然后把结果缓存起来
* 这个注解一般用在查询方法上 * 这个注解一般用在查询方法上
* *
* 重点说明: 缓存注解严谨与其他筛选数据功能一起使用
* 例如: 数据权限注解 会造成 缓存击穿 数据不一致问题
*
* cacheNames 为配置文件内 groupId * cacheNames 为配置文件内 groupId
*/ */
@ApiOperation("测试 @Cacheable") @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("新增测试单表") @ApiOperation("新增测试单表")
@PreAuthorize("@ss.hasPermi('demo:demo:add')") @PreAuthorize("@ss.hasPermi('demo:demo:add')")
@Log(title = "测试单表", businessType = BusinessType.INSERT) @Log(title = "测试单表", businessType = BusinessType.INSERT)
@RepeatSubmit(intervalTime = 2, timeUnit = TimeUnit.SECONDS) @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "不允许重复提交")
@PostMapping() @PostMapping()
public AjaxResult<Void> add(@Validated(AddGroup.class) @RequestBody TestDemoBo bo) { public AjaxResult<Void> add(@Validated(AddGroup.class) @RequestBody TestDemoBo bo) {
return toAjax(iTestDemoService.insertByBo(bo) ? 1 : 0); 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) { public Boolean insertByBo(TestDemoBo bo) {
TestDemo add = BeanUtil.toBean(bo, TestDemo.class); TestDemo add = BeanUtil.toBean(bo, TestDemo.class);
validEntityBeforeSave(add); validEntityBeforeSave(add);
return save(add); boolean flag = save(add);
if (flag) {
bo.setId(add.getId());
}
return flag;
} }
@Override @Override

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.1.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -61,10 +61,21 @@
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-starter</artifactId>
</dependency> </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> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId> <artifactId>ruoyi-common</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -9,14 +9,10 @@ import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils; import com.ruoyi.common.utils.reflect.ReflectUtils;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
/** /**
@ -58,23 +54,13 @@ public class DataScopeAspect {
*/ */
public static final String DATA_SCOPE = "dataScope"; public static final String DATA_SCOPE = "dataScope";
// 配置织入点 @Before("@annotation(controllerDataScope)")
@Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)") public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable {
public void dataScopePointCut() {
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable {
clearDataScope(point); clearDataScope(point);
handleDataScope(point); handleDataScope(point, controllerDataScope);
} }
protected void handleDataScope(final JoinPoint joinPoint) { protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null) {
return;
}
// 获取当前的用户 // 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser)) { 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参数防止注入 * 拼接权限sql前先清空params.dataScope参数防止注入
*/ */

View File

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

View File

@ -3,114 +3,63 @@ package com.ruoyi.framework.aspectj;
import com.ruoyi.common.annotation.RateLimiter; import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.enums.LimitType; import com.ruoyi.common.enums.LimitType;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.RedisUtils;
import com.ruoyi.common.utils.ServletUtils; 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.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature; import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger; import org.redisson.api.RateType;
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.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/** /**
* 限流处理 * 限流处理
* *
* @author ruoyi * @author Lion Li
*/ */
@Slf4j
@Aspect @Aspect
@Component @Component
public class RateLimiterAspect public class RateLimiterAspect {
{
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
private RedisTemplate<Object, Object> redisTemplate; @Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
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();
int time = rateLimiter.time(); int time = rateLimiter.time();
int count = rateLimiter.count(); int count = rateLimiter.count();
String combineKey = getCombineKey(rateLimiter, point); String combineKey = getCombineKey(rateLimiter, point);
List<Object> keys = Collections.singletonList(combineKey); try {
try RateType rateType = RateType.OVERALL;
{ if (rateLimiter.limitType() == LimitType.CLUSTER) {
Long number = redisTemplate.execute(limitScript, keys, count, time); rateType = RateType.PER_CLIENT;
if (StringUtils.isNull(number) || number.intValue() > count) }
{ long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
if (number == -1) {
throw new ServiceException("访问过于频繁,请稍后再试"); throw new ServiceException("访问过于频繁,请稍后再试");
} }
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key); log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
} } catch (ServiceException e) {
catch (ServiceException e)
{
throw e; throw e;
} } catch (Exception e) {
catch (Exception e)
{
throw new RuntimeException("服务器限流异常,请稍后再试"); throw new RuntimeException("服务器限流异常,请稍后再试");
} }
} }
/** public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
* 是否存在注解如果存在就获取 StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
*/ if (rateLimiter.limitType() == LimitType.IP) {
private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint) // 获取请求ip
{ stringBuffer.append(ServletUtils.getClientIP()).append("-");
Signature signature = joinPoint.getSignature(); } else if (rateLimiter.limitType() == LimitType.CLUSTER){
MethodSignature methodSignature = (MethodSignature) signature; // 获取客户端实例id
Method method = methodSignature.getMethod(); stringBuffer.append(RedisUtils.getClientId()).append("-");
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());
} }
MethodSignature signature = (MethodSignature) point.getSignature(); MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod(); Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass(); Class<?> targetClass = method.getDeclaringClass();
stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName()); stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
return stringBuffer.toString(); 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; package com.ruoyi.framework.config;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -41,10 +42,13 @@ public class AsyncConfig extends AsyncConfigurerSupport {
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> { return (throwable, method, objects) -> {
throwable.printStackTrace(); throwable.printStackTrace();
throw new ServiceException( StringBuilder sb = new StringBuilder();
"Exception message - " + throwable.getMessage() sb.append("Exception message - ").append(throwable.getMessage())
+ ", Method name - " + method.getName() .append(", Method name - ").append(method.getName());
+ ", Parameter value - " + Arrays.toString(objects)); 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; 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.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.RedissonProperties; import com.ruoyi.framework.config.properties.RedissonProperties;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -18,7 +18,6 @@ import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
@ -58,23 +57,52 @@ public class RedisConfig extends CachingConfigurerSupport {
.setTransportMode(redissonProperties.getTransportMode()); .setTransportMode(redissonProperties.getTransportMode());
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
// 使用单机模式 if (ObjectUtil.isNotNull(singleServerConfig)) {
config.useSingleServer() // 使用单机模式
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort()) config.useSingleServer()
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue()) .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setDatabase(redisProperties.getDatabase()) .setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null) .setDatabase(redisProperties.getDatabase())
.setTimeout(singleServerConfig.getTimeout()) .setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setRetryAttempts(singleServerConfig.getRetryAttempts()) .setTimeout(singleServerConfig.getTimeout())
.setRetryInterval(singleServerConfig.getRetryInterval()) .setRetryAttempts(singleServerConfig.getRetryAttempts())
.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection()) .setRetryInterval(singleServerConfig.getRetryInterval())
.setClientName(singleServerConfig.getClientName()) .setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) .setClientName(singleServerConfig.getClientName())
.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize()) .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize()) .setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize()) .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize()) .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval()); .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); RedissonClient redissonClient = Redisson.create(config);
log.info("初始化 redis 配置"); log.info("初始化 redis 配置");
return redissonClient; return redissonClient;
@ -95,32 +123,4 @@ public class RedisConfig extends CachingConfigurerSupport {
return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE); 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; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; 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.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/** /**
* 通用配置 * 通用配置
* *
* @author ruoyi * @author Lion Li
*/ */
@Configuration @Configuration
public class ResourcesConfig implements WebMvcConfigurer public class ResourcesConfig implements WebMvcConfigurer {
{
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) public void addResourceHandlers(ResourceHandlerRegistry registry) {
{
}
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
} }
/** /**
* 跨域配置 * 跨域配置
*/ */
@Bean @Bean
public CorsFilter corsFilter() public CorsFilter corsFilter() {
{
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); config.setAllowCredentials(true);
// 设置访问源地址 // 设置访问源地址
config.addAllowedOriginPattern("*"); config.addAllowedOriginPattern("*");
// 设置访问源请求头 // 设置访问源请求头
config.addAllowedHeader("*"); config.addAllowedHeader("*");
// 设置访问源请求方法 // 设置访问源请求方法

View File

@ -1,5 +1,6 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import com.ruoyi.framework.config.properties.SecurityProperties;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
@ -56,6 +57,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired @Autowired
private CorsFilter corsFilter; private CorsFilter corsFilter;
@Autowired
private SecurityProperties securityProperties;
/** /**
* 解决 无法直接注入 AuthenticationManager * 解决 无法直接注入 AuthenticationManager
* *
@ -96,8 +100,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求 // 过滤请求
.authorizeRequests() .authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage").anonymous()
.antMatchers( .antMatchers(
HttpMethod.GET, HttpMethod.GET,
"/", "/",
@ -106,19 +108,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
"/**/*.css", "/**/*.css",
"/**/*.js" "/**/*.js"
).permitAll() ).permitAll()
.antMatchers("/doc.html").anonymous() .antMatchers(securityProperties.getAnonymous()).anonymous()
.antMatchers("/swagger-resources/**").anonymous() .antMatchers(securityProperties.getPermitAll()).permitAll()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// Spring Boot Actuator 的安全配置
.antMatchers("/actuator").anonymous()
.antMatchers("/actuator/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证 // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated() .anyRequest().authenticated()
.and() .and()
.headers().frameOptions().disable(); .headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); httpSecurity.logout().logoutUrl(securityProperties.getLogoutUrl()).logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter // 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter // 添加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; package com.ruoyi.framework.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; 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 com.ruoyi.framework.config.properties.SwaggerProperties;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In; import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.PathSelectors;
@ -15,6 +16,7 @@ import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
import javax.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -27,82 +29,92 @@ import java.util.List;
@EnableKnife4j @EnableKnife4j
public class SwaggerConfig { public class SwaggerConfig {
@Autowired @Autowired
private SwaggerProperties swaggerProperties; private SwaggerProperties swaggerProperties;
/** @Autowired
* 创建API private TokenProperties tokenProperties;
*/
@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());
}
/** /**
* 安全模式这里指定token通过Authorization头请求头传递 * 创建API
*/ */
private List<SecurityScheme> securitySchemes() { @PostConstruct
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>(); public void createRestApi() {
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); for (SwaggerProperties.Groups group : swaggerProperties.getGroups()) {
return apiKeyList; 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);
}
}
/** /**
* 安全上下文 * 安全模式这里指定token通过Authorization头请求头传递
*/ */
private List<SecurityContext> securityContexts() { private List<SecurityScheme> securitySchemes() {
List<SecurityContext> securityContexts = new ArrayList<>(); List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
securityContexts.add( String header = tokenProperties.getHeader();
SecurityContext.builder() apiKeyList.add(new ApiKey(header, header, In.HEADER.toValue()));
.securityReferences(defaultAuth()) return apiKeyList;
.operationSelector(o -> o.requestMappingPattern().matches("/.*")) }
.build());
return securityContexts;
}
/** /**
* 默认的安全上引用 * 安全上下文
*/ */
private List<SecurityReference> defaultAuth() { private List<SecurityContext> securityContexts() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); List<SecurityContext> securityContexts = new ArrayList<>();
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; securityContexts.add(
authorizationScopes[0] = authorizationScope; SecurityContext.builder()
List<SecurityReference> securityReferences = new ArrayList<>(); .securityReferences(defaultAuth())
securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
return securityReferences; .build());
} return securityContexts;
}
/** /**
* 添加摘要信息 * 默认的安全上引用
*/ */
private ApiInfo apiInfo() { private List<SecurityReference> defaultAuth() {
// 用ApiInfoBuilder进行定制 AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
SwaggerProperties.Contact contact = swaggerProperties.getContact(); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
return new ApiInfoBuilder() authorizationScopes[0] = authorizationScope;
// 设置标题 List<SecurityReference> securityReferences = new ArrayList<>();
.title(swaggerProperties.getTitle()) securityReferences.add(new SecurityReference(tokenProperties.getHeader(), authorizationScopes));
// 描述 return securityReferences;
.description(swaggerProperties.getDescription()) }
// 作者信息
.contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail())) /**
// 版本 * 添加摘要信息
.version(swaggerProperties.getVersion()) */
.build(); 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.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.redisson.config.TransportMode; import org.redisson.config.TransportMode;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -38,6 +40,11 @@ public class RedissonProperties {
*/ */
private SingleServerConfig singleServerConfig; 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 @Data
@NoArgsConstructor @NoArgsConstructor
public static class CacheGroup { 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.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
/** /**
* swagger 配置属性 * swagger 配置属性
* *
@ -41,23 +43,46 @@ public class SwaggerProperties {
*/ */
private Contact contact; private Contact contact;
/**
* 组配置
*/
private List<Groups> groups;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
public static class Contact{ public static class Contact {
/** /**
* 联系人 * 联系人
**/ */
private String name; private String name;
/** /**
* 联系人url * 联系人url
**/ */
private String url; private String url;
/** /**
* 联系人email * 联系人email
**/ */
private String 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 { try {
loginUser = SecurityUtils.getLoginUser(); loginUser = SecurityUtils.getLoginUser();
} catch (Exception e) { } catch (Exception e) {
log.error("自动注入警告 => 用户未登录"); log.warn("自动注入警告 => 用户未登录");
return null; return null;
} }
return loginUser.getUsername(); return loginUser.getUsername();

View File

@ -1,9 +1,9 @@
package com.ruoyi.framework.security.filter; package com.ruoyi.framework.security.filter;
import com.ruoyi.common.core.domain.model.LoginUser; 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.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -33,8 +33,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
throws ServletException, IOException throws ServletException, IOException
{ {
LoginUser loginUser = tokenService.getLoginUser(request); LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
{
tokenService.verifyToken(loginUser); tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 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.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser; 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.JsonUtils;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; 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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -31,7 +31,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
private TokenService tokenService; private TokenService tokenService;
@Autowired @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> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.1.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <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 dataType = getDbType(column.getColumnType());
String columnName = column.getColumnName(); String columnName = column.getColumnName();
column.setTableId(table.getTableId()); column.setTableId(table.getTableId());
@ -44,48 +43,48 @@ public class GenUtils
// 设置默认类型 // 设置默认类型
column.setJavaType(GenConstants.TYPE_STRING); 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设置为文本域 // 字符串长度超过500设置为文本域
Integer columnLength = getColumnLength(column.getColumnType()); Integer columnLength = getColumnLength(column.getColumnType());
String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
column.setHtmlType(htmlType); column.setHtmlType(htmlType);
} } else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType))
{
column.setJavaType(GenConstants.TYPE_DATE); column.setJavaType(GenConstants.TYPE_DATE);
column.setHtmlType(GenConstants.HTML_DATETIME); column.setHtmlType(GenConstants.HTML_DATETIME);
} } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType))
{
column.setHtmlType(GenConstants.HTML_INPUT); column.setHtmlType(GenConstants.HTML_INPUT);
// 如果是浮点型 统一用BigDecimal // 如果是浮点型 统一用BigDecimal
String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); 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); 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); column.setJavaType(GenConstants.TYPE_INTEGER);
} }
// 长整形 // 长整形
else else {
{
column.setJavaType(GenConstants.TYPE_LONG); 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); column.setIsEdit(GenConstants.REQUIRE);
} }
// 编辑需要的设置必选
if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName))
{
column.setIsRequired(GenConstants.REQUIRE);
}
// 列表字段 // 列表字段
if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk())
{ {

View File

@ -61,6 +61,7 @@ public class VelocityUtils
velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
velocityContext.put("columns", genTable.getColumns()); velocityContext.put("columns", genTable.getColumns());
velocityContext.put("table", genTable); velocityContext.put("table", genTable);
velocityContext.put("dicts", getDicts(genTable));
setMenuVelocityContext(velocityContext, genTable); setMenuVelocityContext(velocityContext, genTable);
if (GenConstants.TPL_TREE.equals(tplCategory)) if (GenConstants.TPL_TREE.equals(tplCategory))
{ {
@ -273,6 +274,27 @@ public class VelocityUtils
return importList; 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 ${packageName}.domain.${ClassName};
import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus; import com.ruoyi.common.core.mybatisplus.core.BaseMapperPlus;
import com.ruoyi.common.core.mybatisplus.cache.MybatisPlusRedisCache;
import org.apache.ibatis.annotations.CacheNamespace;
/** /**
* ${functionName}Mapper接口 * ${functionName}Mapper接口

View File

@ -79,7 +79,12 @@ public class ${ClassName}ServiceImpl extends ServicePlusImpl<${ClassName}Mapper,
public Boolean insertByBo(${ClassName}Bo bo) { public Boolean insertByBo(${ClassName}Bo bo) {
${ClassName} add = BeanUtil.toBean(bo, ${ClassName}.class); ${ClassName} add = BeanUtil.toBean(bo, ${ClassName}.class);
validEntityBeforeSave(add); 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 @Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import com.ruoyi.system.domain.vo.SysOssConfigVo;
import java.util.Collection; import java.util.Collection;
/** /**
* 存储配置Service接口 * 对象存储配置Service接口
* *
* @author Lion Li * @author Lion Li
* @author 孤舟烟雨 * @author 孤舟烟雨
@ -29,15 +29,15 @@ public interface ISysOssConfigService extends IServicePlus<SysOssConfig, SysOssC
/** /**
* 根据新增业务对象插入存储配置 * 根据新增业务对象插入对象存储配置
* @param bo 存储配置新增业务对象 * @param bo 对象存储配置新增业务对象
* @return * @return
*/ */
Boolean insertByBo(SysOssConfigBo bo); Boolean insertByBo(SysOssConfigBo bo);
/** /**
* 根据编辑业务对象修改存储配置 * 根据编辑业务对象修改对象存储配置
* @param bo 存储配置编辑业务对象 * @param bo 对象存储配置编辑业务对象
* @return * @return
*/ */
Boolean updateByBo(SysOssConfigBo bo); 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.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
@ -15,12 +15,15 @@ import java.util.Set;
* @author ruoyi * @author ruoyi
*/ */
@Service("ss") @Service("ss")
public class PermissionService public class PermissionService {
{ /**
/** 所有权限标识 */ * 所有权限标识
*/
private static final String ALL_PERMISSION = "*:*:*"; private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */ /**
* 管理员角色权限标识
*/
private static final String SUPER_ADMIN = "admin"; private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ","; private static final String ROLE_DELIMETER = ",";
@ -33,15 +36,12 @@ public class PermissionService
* @param permission 权限字符串 * @param permission 权限字符串
* @return 用户是否具备某权限 * @return 用户是否具备某权限
*/ */
public boolean hasPermi(String permission) public boolean hasPermi(String permission) {
{ if (StringUtils.isEmpty(permission)) {
if (StringUtils.isEmpty(permission))
{
return false; return false;
} }
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
{
return false; return false;
} }
return hasPermissions(loginUser.getPermissions(), permission); return hasPermissions(loginUser.getPermissions(), permission);
@ -53,8 +53,7 @@ public class PermissionService
* @param permission 权限字符串 * @param permission 权限字符串
* @return 用户是否不具备某权限 * @return 用户是否不具备某权限
*/ */
public boolean lacksPermi(String permission) public boolean lacksPermi(String permission) {
{
return hasPermi(permission) != true; return hasPermi(permission) != true;
} }
@ -64,22 +63,17 @@ public class PermissionService
* @param permissions PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 * @param permissions PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限 * @return 用户是否具有以下任意一个权限
*/ */
public boolean hasAnyPermi(String permissions) public boolean hasAnyPermi(String permissions) {
{ if (StringUtils.isEmpty(permissions)) {
if (StringUtils.isEmpty(permissions))
{
return false; return false;
} }
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
{
return false; return false;
} }
Set<String> authorities = loginUser.getPermissions(); Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(PERMISSION_DELIMETER)) for (String permission : permissions.split(PERMISSION_DELIMETER)) {
{ if (permission != null && hasPermissions(authorities, permission)) {
if (permission != null && hasPermissions(authorities, permission))
{
return true; return true;
} }
} }
@ -92,22 +86,17 @@ public class PermissionService
* @param role 角色字符串 * @param role 角色字符串
* @return 用户是否具备某角色 * @return 用户是否具备某角色
*/ */
public boolean hasRole(String role) public boolean hasRole(String role) {
{ if (StringUtils.isEmpty(role)) {
if (StringUtils.isEmpty(role))
{
return false; return false;
} }
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
{
return false; return false;
} }
for (SysRole sysRole : loginUser.getUser().getRoles()) for (SysRole sysRole : loginUser.getUser().getRoles()) {
{
String roleKey = sysRole.getRoleKey(); 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; return true;
} }
} }
@ -120,8 +109,7 @@ public class PermissionService
* @param role 角色名称 * @param role 角色名称
* @return 用户是否不具备某角色 * @return 用户是否不具备某角色
*/ */
public boolean lacksRole(String role) public boolean lacksRole(String role) {
{
return hasRole(role) != true; return hasRole(role) != true;
} }
@ -131,21 +119,16 @@ public class PermissionService
* @param roles ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @param roles ROLE_NAMES_DELIMETER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色 * @return 用户是否具有以下任意一个角色
*/ */
public boolean hasAnyRoles(String roles) public boolean hasAnyRoles(String roles) {
{ if (StringUtils.isEmpty(roles)) {
if (StringUtils.isEmpty(roles))
{
return false; return false;
} }
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
{
return false; return false;
} }
for (String role : roles.split(ROLE_DELIMETER)) for (String role : roles.split(ROLE_DELIMETER)) {
{ if (hasRole(role)) {
if (hasRole(role))
{
return true; return true;
} }
} }
@ -156,11 +139,10 @@ public class PermissionService
* 判断是否包含权限 * 判断是否包含权限
* *
* @param permissions 权限列表 * @param permissions 权限列表
* @param permission 权限字符串 * @param permission 权限字符串
* @return 用户是否具备某权限 * @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)); 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.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; 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.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException; 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.MessageUtils;
import com.ruoyi.common.utils.RedisUtils; import com.ruoyi.common.utils.RedisUtils;
import com.ruoyi.common.utils.ServletUtils; 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.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -28,66 +28,57 @@ import javax.servlet.http.HttpServletRequest;
* *
* @author ruoyi * @author ruoyi
*/ */
@Component @Service
public class SysLoginService public class SysLoginService {
{
@Autowired @Autowired
private TokenService tokenService; private TokenService tokenService;
@Resource @Resource
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
@Autowired @Autowired
private ISysUserService userService; private ISysUserService userService;
@Autowired @Autowired
private ISysConfigService configService; private ISysConfigService configService;
@Autowired @Autowired
private AsyncService asyncService; private LogininforService asyncService;
/** /**
* 登录验证 * 登录验证
* *
* @param username 用户名 * @param username 用户名
* @param password 密码 * @param password 密码
* @param code 验证码 * @param code 验证码
* @param uuid 唯一标识 * @param uuid 唯一标识
* @return 结果 * @return 结果
*/ */
public String login(String username, String password, String code, String uuid) public String login(String username, String password, String code, String uuid) {
{ HttpServletRequest request = ServletUtils.getRequest();
HttpServletRequest request = ServletUtils.getRequest(); boolean captchaOnOff = configService.selectCaptchaOnOff();
boolean captchaOnOff = configService.selectCaptchaOnOff();
// 验证码开关 // 验证码开关
if (captchaOnOff) if (captchaOnOff) {
{
validateCaptcha(username, code, uuid, request); validateCaptcha(username, code, uuid, request);
} }
// 用户验证 // 用户验证
Authentication authentication = null; Authentication authentication = null;
try try {
{
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password)); .authenticate(new UsernamePasswordAuthenticationToken(username, password));
} } catch (Exception e) {
catch (Exception e) if (e instanceof BadCredentialsException) {
{ asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
if (e instanceof BadCredentialsException)
{
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
throw new UserPasswordNotMatchException(); throw new UserPasswordNotMatchException();
} } else {
else asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
{
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
throw new ServiceException(e.getMessage()); 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(); LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUser()); recordLoginInfo(loginUser.getUserId());
// 生成token // 生成token
return tokenService.createToken(loginUser); return tokenService.createToken(loginUser);
} }
@ -96,32 +87,34 @@ public class SysLoginService
* 校验验证码 * 校验验证码
* *
* @param username 用户名 * @param username 用户名
* @param code 验证码 * @param code 验证码
* @param uuid 唯一标识 * @param uuid 唯一标识
* @return 结果 * @return 结果
*/ */
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) { public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
if (captcha == null) { if (captcha == null) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request); asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
throw new CaptchaExpireException(); throw new CaptchaExpireException();
} }
if (!code.equalsIgnoreCase(captcha)) { if (!code.equalsIgnoreCase(captcha)) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request); asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
throw new CaptchaException(); throw new CaptchaException();
} }
} }
/** /**
* 记录登录信息 * 记录登录信息
*
* @param userId 用户ID
*/ */
public void recordLoginInfo(SysUser user) public void recordLoginInfo(Long userId) {
{ SysUser sysUser = new SysUser();
user.setLoginIp(ServletUtils.getClientIP()); sysUser.setUserId(userId);
user.setLoginDate(DateUtils.getNowDate()); sysUser.setLoginIp(ServletUtils.getClientIP());
user.setUpdateBy(user.getUserName()); sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(user); 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.HashSet;
import java.util.Set; 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 * @author ruoyi
*/ */
@Component @Service
public class SysPermissionService public class SysPermissionService {
{
@Autowired @Autowired
private ISysRoleService roleService; private ISysRoleService roleService;
@ -28,16 +27,12 @@ public class SysPermissionService
* @param user 用户信息 * @param user 用户信息
* @return 角色权限信息 * @return 角色权限信息
*/ */
public Set<String> getRolePermission(SysUser user) public Set<String> getRolePermission(SysUser user) {
{
Set<String> roles = new HashSet<String>(); Set<String> roles = new HashSet<String>();
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin()) {
{
roles.add("admin"); roles.add("admin");
} } else {
else
{
roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
} }
return roles; return roles;
@ -49,16 +44,12 @@ public class SysPermissionService
* @param user 用户信息 * @param user 用户信息
* @return 菜单权限信息 * @return 菜单权限信息
*/ */
public Set<String> getMenuPermission(SysUser user) public Set<String> getMenuPermission(SysUser user) {
{
Set<String> perms = new HashSet<String>(); Set<String> perms = new HashSet<String>();
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin()) {
{
perms.add("*:*:*"); perms.add("*:*:*");
} } else {
else
{
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
} }
return perms; 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.Constants;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.RegisterBody; 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.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException; import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.utils.*; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Service;
/** /**
* 注册校验方法 * 注册校验方法
* *
* @author ruoyi * @author ruoyi
*/ */
@Component @Service
public class SysRegisterService public class SysRegisterService {
{
@Autowired @Autowired
private ISysUserService userService; private ISysUserService userService;
@Autowired @Autowired
private ISysConfigService configService; private ISysConfigService configService;
@Autowired @Autowired
private AsyncService asyncService; private LogininforService asyncService;
/** /**
* 注册 * 注册
*/ */
public String register(RegisterBody registerBody) public String register(RegisterBody registerBody) {
{
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
boolean captchaOnOff = configService.selectCaptchaOnOff(); boolean captchaOnOff = configService.selectCaptchaOnOff();
// 验证码开关 // 验证码开关
if (captchaOnOff) if (captchaOnOff) {
{
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
} }
if (StringUtils.isEmpty(username)) if (StringUtils.isEmpty(username)) {
{
msg = "用户名不能为空"; msg = "用户名不能为空";
} } else if (StringUtils.isEmpty(password)) {
else if (StringUtils.isEmpty(password))
{
msg = "用户密码不能为空"; msg = "用户密码不能为空";
} } else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
else if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
msg = "账户长度必须在2到20个字符之间"; msg = "账户长度必须在2到20个字符之间";
} } else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
msg = "密码长度必须在5到20个字符之间"; msg = "密码长度必须在5到20个字符之间";
} } else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username))) {
else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username)))
{
msg = "保存用户'" + username + "'失败,注册账号已存在"; msg = "保存用户'" + username + "'失败,注册账号已存在";
} } else {
else
{
SysUser sysUser = new SysUser(); SysUser sysUser = new SysUser();
sysUser.setUserName(username); sysUser.setUserName(username);
sysUser.setNickName(username); sysUser.setNickName(username);
sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword()));
boolean regFlag = userService.registerUser(sysUser); boolean regFlag = userService.registerUser(sysUser);
if (!regFlag) if (!regFlag) {
{
msg = "注册失败,请联系系统管理人员"; msg = "注册失败,请联系系统管理人员";
} } else {
else asyncService.recordLogininfor(username, Constants.REGISTER,
{
asyncService.recordLogininfor(username, Constants.REGISTER,
MessageUtils.message("user.register.success"), ServletUtils.getRequest()); MessageUtils.message("user.register.success"), ServletUtils.getRequest());
} }
} }
@ -89,21 +72,18 @@ public class SysRegisterService
* 校验验证码 * 校验验证码
* *
* @param username 用户名 * @param username 用户名
* @param code 验证码 * @param code 验证码
* @param uuid 唯一标识 * @param uuid 唯一标识
* @return 结果 * @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 verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
if (captcha == null) if (captcha == null) {
{
throw new CaptchaExpireException(); throw new CaptchaExpireException();
} }
if (!code.equalsIgnoreCase(captcha)) if (!code.equalsIgnoreCase(captcha)) {
{
throw new CaptchaException(); throw new CaptchaException();
} }
} }

View File

@ -1,15 +1,24 @@
package com.ruoyi.system.service.impl; 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.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.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.core.page.TableDataInfo; 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.PageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.mapper.SysLogininforMapper; import com.ruoyi.system.mapper.SysLogininforMapper;
import com.ruoyi.system.service.ISysLogininforService; import com.ruoyi.system.service.ISysLogininforService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -20,8 +29,62 @@ import java.util.Map;
* *
* @author ruoyi * @author ruoyi
*/ */
@Slf4j
@Service @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 @Override
public TableDataInfo<SysLogininfor> selectPageLogininforList(SysLogininfor logininfor) { public TableDataInfo<SysLogininfor> selectPageLogininforList(SysLogininfor logininfor) {

View File

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

View File

@ -1,14 +1,19 @@
package com.ruoyi.system.service.impl; package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl; import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.core.page.TableDataInfo; 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.PageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.mapper.SysOperLogMapper; import com.ruoyi.system.mapper.SysOperLogMapper;
import com.ruoyi.system.service.ISysOperLogService; import com.ruoyi.system.service.ISysOperLogService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Arrays; import java.util.Arrays;
@ -22,7 +27,21 @@ import java.util.Map;
* @author ruoyi * @author ruoyi
*/ */
@Service @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 @Override
public TableDataInfo<SysOperLog> selectPageOperLogList(SysOperLog operLog) { public TableDataInfo<SysOperLog> selectPageOperLogList(SysOperLog operLog) {

View File

@ -31,7 +31,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
* 存储配置Service业务层处理 * 对象存储配置Service业务层处理
* *
* @author Lion Li * @author Lion Li
* @author 孤舟烟雨 * @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.core.util.IdUtil;
import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil; import cn.hutool.http.useragent.UserAgentUtil;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.model.LoginUser; 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.RedisUtils;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.framework.config.properties.TokenProperties;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.HashMap; import java.util.HashMap;
@ -26,8 +27,8 @@ import java.util.concurrent.TimeUnit;
* *
* @author Lion Li * @author Lion Li
*/ */
@Component @Service
public class TokenService { public class TokenServiceImpl implements TokenService {
protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_SECOND = 1000;
@ -43,6 +44,7 @@ public class TokenService {
* *
* @return 用户信息 * @return 用户信息
*/ */
@Override
public LoginUser getLoginUser(HttpServletRequest request) { public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌 // 获取请求携带的令牌
String token = getToken(request); String token = getToken(request);
@ -64,6 +66,7 @@ public class TokenService {
/** /**
* 设置用户身份信息 * 设置用户身份信息
*/ */
@Override
public void setLoginUser(LoginUser loginUser) { public void setLoginUser(LoginUser loginUser) {
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
refreshToken(loginUser); refreshToken(loginUser);
@ -73,6 +76,7 @@ public class TokenService {
/** /**
* 删除用户身份信息 * 删除用户身份信息
*/ */
@Override
public void delLoginUser(String token) { public void delLoginUser(String token) {
if (StringUtils.isNotEmpty(token)) { if (StringUtils.isNotEmpty(token)) {
String userKey = getTokenKey(token); String userKey = getTokenKey(token);
@ -86,6 +90,7 @@ public class TokenService {
* @param loginUser 用户信息 * @param loginUser 用户信息
* @return 令牌 * @return 令牌
*/ */
@Override
public String createToken(LoginUser loginUser) { public String createToken(LoginUser loginUser) {
String token = IdUtil.fastUUID(); String token = IdUtil.fastUUID();
loginUser.setToken(token); loginUser.setToken(token);
@ -103,6 +108,7 @@ public class TokenService {
* @param loginUser * @param loginUser
* @return 令牌 * @return 令牌
*/ */
@Override
public void verifyToken(LoginUser loginUser) { public void verifyToken(LoginUser loginUser) {
long expireTime = loginUser.getExpireTime(); long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
@ -116,6 +122,7 @@ public class TokenService {
* *
* @param loginUser 登录信息 * @param loginUser 登录信息
*/ */
@Override
public void refreshToken(LoginUser loginUser) { public void refreshToken(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE); loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE);
@ -129,6 +136,7 @@ public class TokenService {
* *
* @param loginUser 登录信息 * @param loginUser 登录信息
*/ */
@Override
public void setUserAgent(LoginUser loginUser) { public void setUserAgent(LoginUser loginUser) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP(); String ip = ServletUtils.getClientIP();
@ -170,6 +178,7 @@ public class TokenService {
* @param token 令牌 * @param token 令牌
* @return 用户名 * @return 用户名
*/ */
@Override
public String getUsernameFromToken(String token) { public String getUsernameFromToken(String token) {
Claims claims = parseToken(token); Claims claims = parseToken(token);
return claims.getSubject(); 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.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService; 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 * @author ruoyi
*/ */
@Slf4j
@Service @Service
public class UserDetailsServiceImpl implements UserDetailsService public class UserDetailsServiceImpl implements UserDetailsService {
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired @Autowired
private ISysUserService userService; private ISysUserService userService;
@ -31,21 +30,15 @@ public class UserDetailsServiceImpl implements UserDetailsService
private SysPermissionService permissionService; private SysPermissionService permissionService;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
{
SysUser user = userService.selectUserByUserName(username); SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user)) if (StringUtils.isNull(user)) {
{
log.info("登录用户:{} 不存在.", username); log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在"); throw new ServiceException("登录用户:" + username + " 不存在");
} } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username); log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
} } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", username); log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用"); throw new ServiceException("对不起,您的账号:" + username + " 已停用");
} }
@ -53,8 +46,7 @@ public class UserDetailsServiceImpl implements UserDetailsService
return createLoginUser(user); return createLoginUser(user);
} }
public UserDetails createLoginUser(SysUser user) public UserDetails createLoginUser(SysUser user) {
{
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(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="orderNum" column="order_num"/>
<result property="path" column="path"/> <result property="path" column="path"/>
<result property="component" column="component"/> <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="isCache" column="is_cache"/>
<result property="menuType" column="menu_type"/> <result property="menuType" column="menu_type"/>
<result property="visible" column="visible"/> <result property="visible" column="visible"/>
@ -33,6 +34,7 @@
order_num, order_num,
path, path,
component, component,
query,
is_frame, is_frame,
is_cache, is_cache,
menu_type, menu_type,
@ -51,7 +53,8 @@
m.menu_name, m.menu_name,
m.path, m.path,
m.component, m.component,
m.visible, m.query,
m.visible,
m.status, m.status,
ifnull(m.perms, '') as perms, ifnull(m.perms, '') as perms,
m.is_frame, m.is_frame,
@ -67,7 +70,7 @@
</select> </select>
<select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult"> <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 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 from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id left join sys_role_menu rm on m.menu_id = rm.menu_id
@ -92,6 +95,7 @@
m.menu_name, m.menu_name,
m.path, m.path,
m.component, m.component,
m.query,
m.visible, m.visible,
m.status, m.status,
ifnull(m.perms, '') as perms, ifnull(m.perms, '') as perms,

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