瀏覽代碼

first commit

master
做个有用的人 9 月之前
當前提交
f8c3a9e794
共有 100 個檔案被更改,包括 9539 行新增0 行删除
  1. +47
    -0
      .gitignore
  2. +20
    -0
      LICENSE
  3. +18
    -0
      README.md
  4. +195
    -0
      pom.xml
  5. 二進制
      ruoyi-admin/bin/gop-sdk-Java1701065323525.jar
  6. +83
    -0
      ruoyi-admin/pom.xml
  7. +30
    -0
      ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
  8. +18
    -0
      ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java
  9. +50
    -0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java
  10. +43
    -0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
  11. +183
    -0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java
  12. +125
    -0
      ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java
  13. +1
    -0
      ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties
  14. +54
    -0
      ruoyi-admin/src/main/resources/application-druid.yml
  15. +90
    -0
      ruoyi-admin/src/main/resources/application.yml
  16. +24
    -0
      ruoyi-admin/src/main/resources/banner.txt
  17. +38
    -0
      ruoyi-admin/src/main/resources/i18n/messages.properties
  18. +93
    -0
      ruoyi-admin/src/main/resources/logback.xml
  19. +20
    -0
      ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml
  20. +119
    -0
      ruoyi-common/pom.xml
  21. +19
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java
  22. +28
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java
  23. +51
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
  24. +40
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
  25. +31
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
  26. +122
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
  27. +44
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
  28. +160
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
  29. +117
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
  30. +94
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
  31. +50
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java
  32. +78
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
  33. +168
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java
  34. +216
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java
  35. +118
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
  36. +115
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java
  37. +79
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java
  38. +77
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java
  39. +203
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
  40. +167
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
  41. +90
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
  42. +259
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
  43. +233
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
  44. +56
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
  45. +69
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
  46. +11
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java
  47. +101
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java
  48. +85
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java
  49. +56
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java
  50. +86
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java
  51. +1006
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java
  52. +92
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java
  53. +20
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java
  54. +64
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java
  55. +19
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java
  56. +36
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java
  57. +20
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java
  58. +24
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java
  59. +30
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java
  60. +15
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java
  61. +58
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java
  62. +74
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java
  63. +26
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java
  64. +97
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java
  65. +19
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java
  66. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java
  67. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java
  68. +61
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java
  69. +80
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java
  70. +34
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java
  71. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java
  72. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java
  73. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java
  74. +18
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java
  75. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java
  76. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java
  77. +16
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java
  78. +24
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java
  79. +52
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java
  80. +76
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java
  81. +75
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java
  82. +111
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java
  83. +114
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java
  84. +191
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
  85. +39
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java
  86. +18
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java
  87. +26
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java
  88. +35
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java
  89. +218
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java
  90. +638
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
  91. +99
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java
  92. +110
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java
  93. +24
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java
  94. +76
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java
  95. +232
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java
  96. +291
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
  97. +99
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java
  98. +59
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
  99. +167
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java
  100. +570
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java

+ 47
- 0
.gitignore 查看文件

@@ -0,0 +1,47 @@
######################################################################
# Build Tools

.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar

target/
!.mvn/wrapper/maven-wrapper.jar

######################################################################
# IDE

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### JRebel ###
rebel.xml

### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/

######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp

!*/build/*.java
!*/build/*.html
!*/build/*.xml

+ 20
- 0
LICENSE 查看文件

@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2018 RuoYi

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 18
- 0
README.md 查看文件

@@ -0,0 +1,18 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">对接速卖通API v3.8.7</h1>
<h4 align="center">基于SpringBoot结合速卖通sdk对接速卖通api</h4>

## 平台简介
* 后端采用Spring Boot
*
## 内置功能

1. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
2. 系统接口:根据业务代码自动生成相关的api接口文档。
3. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
4. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。

## swagger
http://localhost:8080/swagger-ui/index.html

+ 195
- 0
pom.xml 查看文件

@@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.8.7</version>

<name>ruoyi</name>
<url>http://www.ruoyi.vip</url>
<description>若依管理系统</description>
<properties>
<ruoyi.version>3.8.7</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<druid.version>1.2.20</druid.version>
<bitwalker.version>1.21</bitwalker.version>
<swagger.version>3.0.0</swagger.version>
<kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>1.4.7</pagehelper.boot.version>
<fastjson.version>2.0.43</fastjson.version>
<oshi.version>6.4.11</oshi.version>
<commons.io.version>2.13.0</commons.io.version>
<commons.collections.version>3.2.2</commons.collections.version>
<poi.version>4.1.2</poi.version>
<velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version>
<hutool.version>5.8.20</hutool.version>
<lombok.version>1.18.28</lombok.version>
</properties>

<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>

<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.15</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>

<!-- 解析客户端操作系统、浏览器等 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>${bitwalker.version}</version>
</dependency>

<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.boot.version}</version>
</dependency>

<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>${oshi.version}</version>
</dependency>

<!-- Swagger3依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>

<!-- collections工具类 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons.collections.version}</version>
</dependency>

<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson.version}</version>
</dependency>

<!-- 核心模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 系统模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${ruoyi.version}</version>
</dependency>

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<modules>
<module>ruoyi-admin</module>
<module>ruoyi-framework</module>
<module>ruoyi-system</module>
<module>ruoyi-common</module>
</modules>
<packaging>pom</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

</project>

二進制
ruoyi-admin/bin/gop-sdk-Java1701065323525.jar 查看文件


+ 83
- 0
ruoyi-admin/pom.xml 查看文件

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-admin</artifactId>

<description>
web服务入口
</description>

<dependencies>

<!-- spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>

<!-- swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>

<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.2</version>
</dependency>

<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- 核心模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>

</project>

+ 30
- 0
ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java 查看文件

@@ -0,0 +1,30 @@
package com.ruoyi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

/**
* 启动程序
*
* @author ruoyi
*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class RuoYiApplication
{
public static void main(String[] args)
{
// System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoYiApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}

+ 18
- 0
ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java 查看文件

@@ -0,0 +1,18 @@
package com.ruoyi;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
* web容器中进行部署
*
* @author ruoyi
*/
public class RuoYiServletInitializer extends SpringBootServletInitializer
{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
{
return application.sources(RuoYiApplication.class);
}
}

+ 50
- 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java 查看文件

@@ -0,0 +1,50 @@
package com.ruoyi.web.controller.monitor;

import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.service.ISysOperLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* 操作日志记录
*
* @author ruoyi
*/
@RestController
@RequestMapping("/monitor/operlog")
public class SysOperlogController extends BaseController
{
@Autowired
private ISysOperLogService operLogService;

@Log(title = "用户头像", businessType = BusinessType.SELECT)
@GetMapping("/list")
public TableDataInfo list(SysOperLog operLog)
{
startPage();
List<SysOperLog> list = operLogService.selectOperLogList(operLog);
return getDataTable(list);
}

@Log(title = "操作日志", businessType = BusinessType.DELETE)
@DeleteMapping("/{operIds}")
public AjaxResult remove(@PathVariable Long[] operIds)
{
return toAjax(operLogService.deleteOperLogByIds(operIds));
}

@Log(title = "操作日志", businessType = BusinessType.CLEAN)
@DeleteMapping("/clean")
public AjaxResult clean()
{
operLogService.cleanOperLog();
return success();
}
}

+ 43
- 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java 查看文件

@@ -0,0 +1,43 @@
package com.ruoyi.web.controller.system;

import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.service.ISysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* 用户信息
*
* @author ruoyi
*/
@RestController
@Api("用户信息管理")
@RequestMapping("/system/user")
public class SysUserController extends BaseController
{
@Autowired
private ISysUserService userService;

/**
* 获取用户列表
*/
@Log(title = "用户管理", businessType = BusinessType.SELECT)
@ApiOperation("获取用户列表")
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
}

+ 183
- 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java 查看文件

@@ -0,0 +1,183 @@
package com.ruoyi.web.controller.tool;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;

/**
* swagger 用户测试方法
*
* @author ruoyi
*/
@Api("用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
{
private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
{
users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
}

@ApiOperation("获取用户列表")
@GetMapping("/list")
public R<List<UserEntity>> userList()
{
List<UserEntity> userList = new ArrayList<UserEntity>(users.values());
return R.ok(userList);
}

@ApiOperation("获取用户详细")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@GetMapping("/{userId}")
public R<UserEntity> getUser(@PathVariable Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
return R.ok(users.get(userId));
}
else
{
return R.fail("用户不存在");
}
}

@ApiOperation("新增用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
@ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
@ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
})
@PostMapping("/save")
public R<String> save(UserEntity user)
{
if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
{
return R.fail("用户ID不能为空");
}
users.put(user.getUserId(), user);
return R.ok();
}

@ApiOperation("更新用户")
@PutMapping("/update")
public R<String> update(@RequestBody UserEntity user)
{
if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
{
return R.fail("用户ID不能为空");
}
if (users.isEmpty() || !users.containsKey(user.getUserId()))
{
return R.fail("用户不存在");
}
users.remove(user.getUserId());
users.put(user.getUserId(), user);
return R.ok();
}

@ApiOperation("删除用户信息")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@DeleteMapping("/{userId}")
public R<String> delete(@PathVariable Integer userId)
{
if (!users.isEmpty() && users.containsKey(userId))
{
users.remove(userId);
return R.ok();
}
else
{
return R.fail("用户不存在");
}
}
}

@ApiModel(value = "UserEntity", description = "用户实体")
class UserEntity
{
@ApiModelProperty("用户ID")
private Integer userId;

@ApiModelProperty("用户名称")
private String username;

@ApiModelProperty("用户密码")
private String password;

@ApiModelProperty("用户手机")
private String mobile;

public UserEntity()
{

}

public UserEntity(Integer userId, String username, String password, String mobile)
{
this.userId = userId;
this.username = username;
this.password = password;
this.mobile = mobile;
}

public Integer getUserId()
{
return userId;
}

public void setUserId(Integer userId)
{
this.userId = userId;
}

public String getUsername()
{
return username;
}

public void setUsername(String username)
{
this.username = username;
}

public String getPassword()
{
return password;
}

public void setPassword(String password)
{
this.password = password;
}

public String getMobile()
{
return mobile;
}

public void setMobile(String mobile)
{
this.mobile = mobile;
}
}

+ 125
- 0
ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java 查看文件

@@ -0,0 +1,125 @@
package com.ruoyi.web.core.config;

import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.config.RuoYiConfig;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

/**
* Swagger2的接口配置
*
* @author ruoyi
*/
@Configuration
public class SwaggerConfig
{
/** 系统基础配置 */
@Autowired
private RuoYiConfig ruoyiConfig;

/** 是否开启swagger */
@Value("${swagger.enabled}")
private boolean enabled;

/** 设置请求的统一前缀 */
@Value("${swagger.pathMapping}")
private String pathMapping;

/**
* 创建API
*/
@Bean
public Docket createRestApi()
{
return new Docket(DocumentationType.OAS_30)
// 是否启用Swagger
.enable(enabled)
// 用来创建该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(pathMapping);
}

/**
* 安全模式,这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes()
{
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
}

/**
* 安全上下文
*/
private List<SecurityContext> securityContexts()
{
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}

/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth()
{
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}

/**
* 添加摘要信息
*/
private ApiInfo apiInfo()
{
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("标题:若依管理系统_接口文档")
// 描述
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
// 作者信息
.contact(new Contact(ruoyiConfig.getName(), null, null))
// 版本
.version("版本号:" + ruoyiConfig.getVersion())
.build();
}
}

+ 1
- 0
ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties 查看文件

@@ -0,0 +1 @@
restart.include.json=/com.alibaba.fastjson2.*.jar

+ 54
- 0
ruoyi-admin/src/main/resources/application-druid.yml 查看文件

@@ -0,0 +1,54 @@
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.10.136:3306/eshop_sds?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: eshop
password: eshop123
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoyi
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true

+ 90
- 0
ruoyi-admin/src/main/resources/application.yml 查看文件

@@ -0,0 +1,90 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.8.7
# 版权年份
copyrightYear: 2023
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
profile: D:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math

# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
port: 8080
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数,默认为100
accept-count: 1000
threads:
# tomcat最大线程数,默认为200
max: 800
# Tomcat启动初始化的线程数,默认值10
min-spare: 100

# 日志配置
logging:
level:
com.ruoyi: debug
org.springframework: warn

# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true

# MyBatis配置
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml

# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql

# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /

# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*

+ 24
- 0
ruoyi-admin/src/main/resources/banner.txt 查看文件

@@ -0,0 +1,24 @@
Application Version: ${ruoyi.version}
Spring Boot Version: ${spring-boot.version}
////////////////////////////////////////////////////////////////////
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
////////////////////////////////////////////////////////////////////

+ 38
- 0
ruoyi-admin/src/main/resources/i18n/messages.properties 查看文件

@@ -0,0 +1,38 @@
#错误消息
not.null=* 必须填写
user.jcaptcha.error=验证码错误
user.jcaptcha.expire=验证码已失效
user.not.exists=用户不存在/密码错误
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
user.password.delete=对不起,您的账号已被删除
user.blocked=用户已封禁,请联系管理员
role.blocked=角色已封禁,请联系管理员
login.blocked=很遗憾,访问IP已被列入系统黑名单
user.logout.success=退出成功

length.not.valid=长度必须在{min}到{max}个字符之间

user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
user.register.success=注册成功
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录

##文件上传消息
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
upload.filename.exceed.length=上传的文件名最长{0}个字符

##权限
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 93
- 0
ruoyi-admin/src/main/resources/logback.xml 查看文件

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/home/ruoyi/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用户访问日志输出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.ruoyi" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />

<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>

+ 20
- 0
ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml 查看文件

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局参数 -->
<settings>
<!-- 使全局的映射器启用或禁用缓存 -->
<setting name="cacheEnabled" value="true" />
<!-- 允许JDBC 支持自动生成主键 -->
<setting name="useGeneratedKeys" value="true" />
<!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
<setting name="defaultExecutorType" value="SIMPLE" />
<!-- 指定 MyBatis 所用日志的具体实现 -->
<setting name="logImpl" value="SLF4J" />
<!-- 使用驼峰命名法转换字段 -->
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> -->
</settings>
</configuration>

+ 119
- 0
ruoyi-common/pom.xml 查看文件

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>ruoyi-common</artifactId>

<description>
common通用工具
</description>

<dependencies>

<!-- Spring框架基本的核心工具 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>

<!-- SpringWeb模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>

<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

<!-- 自定义验证注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>

<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>

<!-- io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>

<!-- yml解析器 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>

<!-- Jaxb -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>

<!-- pool 对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<!-- 解析客户端操作系统、浏览器等 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
</dependency>

<!-- servlet包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>

<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>

<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

</dependencies>

</project>

+ 19
- 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java 查看文件

@@ -0,0 +1,19 @@
package com.ruoyi.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 匿名访问不鉴权注解
*
* @author ruoyi
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous
{
}

+ 28
- 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java 查看文件

@@ -0,0 +1,28 @@
package com.ruoyi.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.ruoyi.common.enums.DataSourceType;

/**
* 自定义多数据源切换注解
*
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*
* @author ruoyi
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}

+ 51
- 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java 查看文件

@@ -0,0 +1,51 @@
package com.ruoyi.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.OperatorType;

/**
* 自定义操作日志记录注解
*
* @author ruoyi
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模块
*/
public String title() default "";

/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;

/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;

/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;

/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;

/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}

+ 40
- 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java 查看文件

@@ -0,0 +1,40 @@
package com.ruoyi.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.enums.LimitType;

/**
* 限流注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter
{
/**
* 限流key
*/
public String key() default CacheConstants.RATE_LIMIT_KEY;

/**
* 限流时间,单位秒
*/
public int time() default 60;

/**
* 限流次数
*/
public int count() default 100;

/**
* 限流类型
*/
public LimitType limitType() default LimitType.DEFAULT;
}

+ 31
- 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java 查看文件

@@ -0,0 +1,31 @@
package com.ruoyi.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义注解防止表单重复提交
*
* @author ruoyi
*
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
public int interval() default 5000;

/**
* 提示消息
*/
public String message() default "不允许重复提交,请稍候再试";
}

+ 122
- 0
ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java 查看文件

@@ -0,0 +1,122 @@
package com.ruoyi.common.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* 读取项目相关配置
*
* @author ruoyi
*/
@Component
@ConfigurationProperties(prefix = "ruoyi")
public class RuoYiConfig
{
/** 项目名称 */
private String name;

/** 版本 */
private String version;

/** 版权年份 */
private String copyrightYear;

/** 上传路径 */
private static String profile;

/** 获取地址开关 */
private static boolean addressEnabled;

/** 验证码类型 */
private static String captchaType;

public String getName()
{
return name;
}

public void setName(String name)
{
this.name = name;
}

public String getVersion()
{
return version;
}

public void setVersion(String version)
{
this.version = version;
}

public String getCopyrightYear()
{
return copyrightYear;
}

public void setCopyrightYear(String copyrightYear)
{
this.copyrightYear = copyrightYear;
}

public static String getProfile()
{
return profile;
}

public void setProfile(String profile)
{
RuoYiConfig.profile = profile;
}

public static boolean isAddressEnabled()
{
return addressEnabled;
}

public void setAddressEnabled(boolean addressEnabled)
{
RuoYiConfig.addressEnabled = addressEnabled;
}

public static String getCaptchaType() {
return captchaType;
}

public void setCaptchaType(String captchaType) {
RuoYiConfig.captchaType = captchaType;
}

/**
* 获取导入上传路径
*/
public static String getImportPath()
{
return getProfile() + "/import";
}

/**
* 获取头像上传路径
*/
public static String getAvatarPath()
{
return getProfile() + "/avatar";
}

/**
* 获取下载路径
*/
public static String getDownloadPath()
{
return getProfile() + "/download/";
}

/**
* 获取上传路径
*/
public static String getUploadPath()
{
return getProfile() + "/upload";
}
}

+ 44
- 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java 查看文件

@@ -0,0 +1,44 @@
package com.ruoyi.common.constant;

/**
* 缓存的key 常量
*
* @author ruoyi
*/
public class CacheConstants
{
/**
* 登录用户 redis key
*/
public static final String LOGIN_TOKEN_KEY = "login_tokens:";

/**
* 验证码 redis key
*/
public static final String CAPTCHA_CODE_KEY = "captcha_codes:";

/**
* 参数管理 cache key
*/
public static final String SYS_CONFIG_KEY = "sys_config:";

/**
* 字典管理 cache key
*/
public static final String SYS_DICT_KEY = "sys_dict:";

/**
* 防重提交 redis key
*/
public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";

/**
* 限流 redis key
*/
public static final String RATE_LIMIT_KEY = "rate_limit:";

/**
* 登录账户密码错误次数 redis key
*/
public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}

+ 160
- 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java 查看文件

@@ -0,0 +1,160 @@
package com.ruoyi.common.constant;

/**
* 通用常量信息
*
* @author ruoyi
*/
public class Constants
{
/**
* UTF-8 字符集
*/
public static final String UTF8 = "UTF-8";

/**
* GBK 字符集
*/
public static final String GBK = "GBK";

/**
* www主域
*/
public static final String WWW = "www.";

/**
* http请求
*/
public static final String HTTP = "http://";

/**
* https请求
*/
public static final String HTTPS = "https://";

/**
* 通用成功标识
*/
public static final String SUCCESS = "0";

/**
* 通用失败标识
*/
public static final String FAIL = "1";

/**
* 登录成功
*/
public static final String LOGIN_SUCCESS = "Success";

/**
* 注销
*/
public static final String LOGOUT = "Logout";

/**
* 注册
*/
public static final String REGISTER = "Register";

/**
* 登录失败
*/
public static final String LOGIN_FAIL = "Error";

/**
* 所有权限标识
*/
public static final String ALL_PERMISSION = "*:*:*";

/**
* 管理员角色权限标识
*/
public static final String SUPER_ADMIN = "admin";

/**
* 角色权限分隔符
*/
public static final String ROLE_DELIMETER = ",";

/**
* 权限标识分隔符
*/
public static final String PERMISSION_DELIMETER = ",";

/**
* 验证码有效期(分钟)
*/
public static final Integer CAPTCHA_EXPIRATION = 2;

/**
* 令牌
*/
public static final String TOKEN = "token";

/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";

/**
* 令牌前缀
*/
public static final String LOGIN_USER_KEY = "login_user_key";

/**
* 用户ID
*/
public static final String JWT_USERID = "userid";

/**
* 用户头像
*/
public static final String JWT_AVATAR = "avatar";

/**
* 创建时间
*/
public static final String JWT_CREATED = "created";

/**
* 用户权限
*/
public static final String JWT_AUTHORITIES = "authorities";

/**
* 资源映射路径 前缀
*/
public static final String RESOURCE_PREFIX = "/profile";

/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi:";

/**
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap:";

/**
* LDAPS 远程方法调用
*/
public static final String LOOKUP_LDAPS = "ldaps:";

/**
* 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
*/
public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };

/**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
*/
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };

/**
* 定时任务违规的字符
*/
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
}

+ 117
- 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java 查看文件

@@ -0,0 +1,117 @@
package com.ruoyi.common.constant;

/**
* 代码生成通用常量
*
* @author ruoyi
*/
public class GenConstants
{
/** 单表(增删改查) */
public static final String TPL_CRUD = "crud";

/** 树表(增删改查) */
public static final String TPL_TREE = "tree";

/** 主子表(增删改查) */
public static final String TPL_SUB = "sub";

/** 树编码字段 */
public static final String TREE_CODE = "treeCode";

/** 树父编码字段 */
public static final String TREE_PARENT_CODE = "treeParentCode";

/** 树名称字段 */
public static final String TREE_NAME = "treeName";

/** 上级菜单ID字段 */
public static final String PARENT_MENU_ID = "parentMenuId";

/** 上级菜单名称字段 */
public static final String PARENT_MENU_NAME = "parentMenuName";

/** 数据库字符串类型 */
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };

/** 数据库文本类型 */
public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" };

/** 数据库时间类型 */
public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" };

/** 数据库数字类型 */
public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
"bit", "bigint", "float", "double", "decimal" };

/** 页面不需要编辑字段 */
public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };

/** 页面不需要显示的列表字段 */
public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by",
"update_time" };

/** 页面不需要查询字段 */
public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by",
"update_time", "remark" };

/** Entity基类字段 */
public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };

/** Tree基类字段 */
public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" };

/** 文本框 */
public static final String HTML_INPUT = "input";

/** 文本域 */
public static final String HTML_TEXTAREA = "textarea";

/** 下拉框 */
public static final String HTML_SELECT = "select";

/** 单选框 */
public static final String HTML_RADIO = "radio";

/** 复选框 */
public static final String HTML_CHECKBOX = "checkbox";

/** 日期控件 */
public static final String HTML_DATETIME = "datetime";

/** 图片上传控件 */
public static final String HTML_IMAGE_UPLOAD = "imageUpload";

/** 文件上传控件 */
public static final String HTML_FILE_UPLOAD = "fileUpload";

/** 富文本控件 */
public static final String HTML_EDITOR = "editor";

/** 字符串类型 */
public static final String TYPE_STRING = "String";

/** 整型 */
public static final String TYPE_INTEGER = "Integer";

/** 长整型 */
public static final String TYPE_LONG = "Long";

/** 浮点型 */
public static final String TYPE_DOUBLE = "Double";

/** 高精度计算类型 */
public static final String TYPE_BIGDECIMAL = "BigDecimal";

/** 时间类型 */
public static final String TYPE_DATE = "Date";

/** 模糊查询 */
public static final String QUERY_LIKE = "LIKE";

/** 相等查询 */
public static final String QUERY_EQ = "EQ";

/** 需要 */
public static final String REQUIRE = "1";
}

+ 94
- 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java 查看文件

@@ -0,0 +1,94 @@
package com.ruoyi.common.constant;

/**
* 返回状态码
*
* @author ruoyi
*/
public class HttpStatus
{
/**
* 操作成功
*/
public static final int SUCCESS = 200;

/**
* 对象创建成功
*/
public static final int CREATED = 201;

/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;

/**
* 操作已经执行成功,但是没有返回数据
*/
public static final int NO_CONTENT = 204;

/**
* 资源已被移除
*/
public static final int MOVED_PERM = 301;

/**
* 重定向
*/
public static final int SEE_OTHER = 303;

/**
* 资源没有被修改
*/
public static final int NOT_MODIFIED = 304;

/**
* 参数列表错误(缺少,格式不匹配)
*/
public static final int BAD_REQUEST = 400;

/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;

/**
* 访问受限,授权过期
*/
public static final int FORBIDDEN = 403;

/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;

/**
* 不允许的http方法
*/
public static final int BAD_METHOD = 405;

/**
* 资源冲突,或者资源被锁
*/
public static final int CONFLICT = 409;

/**
* 不支持的数据,媒体类型
*/
public static final int UNSUPPORTED_TYPE = 415;

/**
* 系统内部错误
*/
public static final int ERROR = 500;

/**
* 接口未实现
*/
public static final int NOT_IMPLEMENTED = 501;

/**
* 系统警告消息
*/
public static final int WARN = 601;
}

+ 50
- 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java 查看文件

@@ -0,0 +1,50 @@
package com.ruoyi.common.constant;

/**
* 任务调度通用常量
*
* @author ruoyi
*/
public class ScheduleConstants
{
public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";

/** 执行目标key */
public static final String TASK_PROPERTIES = "TASK_PROPERTIES";

/** 默认 */
public static final String MISFIRE_DEFAULT = "0";

/** 立即触发执行 */
public static final String MISFIRE_IGNORE_MISFIRES = "1";

/** 触发一次执行 */
public static final String MISFIRE_FIRE_AND_PROCEED = "2";

/** 不触发立即执行 */
public static final String MISFIRE_DO_NOTHING = "3";

public enum Status
{
/**
* 正常
*/
NORMAL("0"),
/**
* 暂停
*/
PAUSE("1");

private String value;

private Status(String value)
{
this.value = value;
}

public String getValue()
{
return value;
}
}
}

+ 78
- 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java 查看文件

@@ -0,0 +1,78 @@
package com.ruoyi.common.constant;

/**
* 用户常量信息
*
* @author ruoyi
*/
public class UserConstants
{
/**
* 平台内系统用户的唯一标志
*/
public static final String SYS_USER = "SYS_USER";

/** 正常状态 */
public static final String NORMAL = "0";

/** 异常状态 */
public static final String EXCEPTION = "1";

/** 用户封禁状态 */
public static final String USER_DISABLE = "1";

/** 角色封禁状态 */
public static final String ROLE_DISABLE = "1";

/** 部门正常状态 */
public static final String DEPT_NORMAL = "0";

/** 部门停用状态 */
public static final String DEPT_DISABLE = "1";

/** 字典正常状态 */
public static final String DICT_NORMAL = "0";

/** 是否为系统默认(是) */
public static final String YES = "Y";

/** 是否菜单外链(是) */
public static final String YES_FRAME = "0";

/** 是否菜单外链(否) */
public static final String NO_FRAME = "1";

/** 菜单类型(目录) */
public static final String TYPE_DIR = "M";

/** 菜单类型(菜单) */
public static final String TYPE_MENU = "C";

/** 菜单类型(按钮) */
public static final String TYPE_BUTTON = "F";

/** Layout组件标识 */
public final static String LAYOUT = "Layout";
/** ParentView组件标识 */
public final static String PARENT_VIEW = "ParentView";

/** InnerLink组件标识 */
public final static String INNER_LINK = "InnerLink";

/** 校验是否唯一的返回标识 */
public final static boolean UNIQUE = true;
public final static boolean NOT_UNIQUE = false;

/**
* 用户名长度限制
*/
public static final int USERNAME_MIN_LENGTH = 2;
public static final int USERNAME_MAX_LENGTH = 20;

/**
* 密码长度限制
*/
public static final int PASSWORD_MIN_LENGTH = 5;
public static final int PASSWORD_MAX_LENGTH = 20;
}

+ 168
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java 查看文件

@@ -0,0 +1,168 @@
package com.ruoyi.common.core.controller;

import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.sql.SqlUtil;

/**
* web层通用数据处理
*
* @author ruoyi
*/
public class BaseController
{
protected final Logger logger = LoggerFactory.getLogger(this.getClass());

/**
* 将前台传递过来的日期格式的字符串,自动转化为Date类型
*/
@InitBinder
public void initBinder(WebDataBinder binder)
{
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
{
@Override
public void setAsText(String text)
{
setValue(DateUtils.parseDate(text));
}
});
}

/**
* 设置请求分页数据
*/
protected void startPage()
{
PageUtils.startPage();
}

/**
* 设置请求排序数据
*/
protected void startOrderBy()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
{
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
PageHelper.orderBy(orderBy);
}
}

/**
* 清理分页的线程变量
*/
protected void clearPage()
{
PageUtils.clearPage();
}

/**
* 响应请求分页数据
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TableDataInfo getDataTable(List<?> list)
{
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(new PageInfo(list).getTotal());
return rspData;
}

/**
* 返回成功
*/
public AjaxResult success()
{
return AjaxResult.success();
}

/**
* 返回失败消息
*/
public AjaxResult error()
{
return AjaxResult.error();
}

/**
* 返回成功消息
*/
public AjaxResult success(String message)
{
return AjaxResult.success(message);
}
/**
* 返回成功消息
*/
public AjaxResult success(Object data)
{
return AjaxResult.success(data);
}

/**
* 返回失败消息
*/
public AjaxResult error(String message)
{
return AjaxResult.error(message);
}

/**
* 返回警告消息
*/
public AjaxResult warn(String message)
{
return AjaxResult.warn(message);
}

/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected AjaxResult toAjax(int rows)
{
return rows > 0 ? AjaxResult.success() : AjaxResult.error();
}

/**
* 响应返回结果
*
* @param result 结果
* @return 操作结果
*/
protected AjaxResult toAjax(boolean result)
{
return result ? success() : error();
}

/**
* 页面跳转
*/
public String redirect(String url)
{
return StringUtils.format("redirect:{}", url);
}
}

+ 216
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java 查看文件

@@ -0,0 +1,216 @@
package com.ruoyi.common.core.domain;

import java.util.HashMap;
import java.util.Objects;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.utils.StringUtils;

/**
* 操作消息提醒
*
* @author ruoyi
*/
public class AjaxResult extends HashMap<String, Object>
{
private static final long serialVersionUID = 1L;

/** 状态码 */
public static final String CODE_TAG = "code";

/** 返回内容 */
public static final String MSG_TAG = "msg";

/** 数据对象 */
public static final String DATA_TAG = "data";

/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult()
{
}

/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}

/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (StringUtils.isNotNull(data))
{
super.put(DATA_TAG, data);
}
}

/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success()
{
return AjaxResult.success("操作成功");
}

/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data)
{
return AjaxResult.success("操作成功", data);
}

/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg)
{
return AjaxResult.success(msg, null);
}

/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data)
{
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}

/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult warn(String msg)
{
return AjaxResult.warn(msg, null);
}

/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult warn(String msg, Object data)
{
return new AjaxResult(HttpStatus.WARN, msg, data);
}

/**
* 返回错误消息
*
* @return 错误消息
*/
public static AjaxResult error()
{
return AjaxResult.error("操作失败");
}

/**
* 返回错误消息
*
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(String msg)
{
return AjaxResult.error(msg, null);
}

/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 错误消息
*/
public static AjaxResult error(String msg, Object data)
{
return new AjaxResult(HttpStatus.ERROR, msg, data);
}

/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(int code, String msg)
{
return new AjaxResult(code, msg, null);
}

/**
* 是否为成功消息
*
* @return 结果
*/
public boolean isSuccess()
{
return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
}

/**
* 是否为警告消息
*
* @return 结果
*/
public boolean isWarn()
{
return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
}

/**
* 是否为错误消息
*
* @return 结果
*/
public boolean isError()
{
return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
}

/**
* 方便链式调用
*
* @param key 键
* @param value 值
* @return 数据对象
*/
@Override
public AjaxResult put(String key, Object value)
{
super.put(key, value);
return this;
}
}

+ 118
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java 查看文件

@@ -0,0 +1,118 @@
package com.ruoyi.common.core.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

/**
* Entity基类
*
* @author ruoyi
*/
public class BaseEntity implements Serializable
{
private static final long serialVersionUID = 1L;

/** 搜索值 */
@JsonIgnore
private String searchValue;

/** 创建者 */
private String createBy;

/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;

/** 更新者 */
private String updateBy;

/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;

/** 备注 */
private String remark;

/** 请求参数 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, Object> params;

public String getSearchValue()
{
return searchValue;
}

public void setSearchValue(String searchValue)
{
this.searchValue = searchValue;
}

public String getCreateBy()
{
return createBy;
}

public void setCreateBy(String createBy)
{
this.createBy = createBy;
}

public Date getCreateTime()
{
return createTime;
}

public void setCreateTime(Date createTime)
{
this.createTime = createTime;
}

public String getUpdateBy()
{
return updateBy;
}

public void setUpdateBy(String updateBy)
{
this.updateBy = updateBy;
}

public Date getUpdateTime()
{
return updateTime;
}

public void setUpdateTime(Date updateTime)
{
this.updateTime = updateTime;
}

public String getRemark()
{
return remark;
}

public void setRemark(String remark)
{
this.remark = remark;
}

public Map<String, Object> getParams()
{
if (params == null)
{
params = new HashMap<>();
}
return params;
}

public void setParams(Map<String, Object> params)
{
this.params = params;
}
}

+ 115
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java 查看文件

@@ -0,0 +1,115 @@
package com.ruoyi.common.core.domain;

import java.io.Serializable;
import com.ruoyi.common.constant.HttpStatus;

/**
* 响应信息主体
*
* @author ruoyi
*/
public class R<T> implements Serializable
{
private static final long serialVersionUID = 1L;

/** 成功 */
public static final int SUCCESS = HttpStatus.SUCCESS;

/** 失败 */
public static final int FAIL = HttpStatus.ERROR;

private int code;

private String msg;

private T data;

public static <T> R<T> ok()
{
return restResult(null, SUCCESS, "操作成功");
}

public static <T> R<T> ok(T data)
{
return restResult(data, SUCCESS, "操作成功");
}

public static <T> R<T> ok(T data, String msg)
{
return restResult(data, SUCCESS, msg);
}

public static <T> R<T> fail()
{
return restResult(null, FAIL, "操作失败");
}

public static <T> R<T> fail(String msg)
{
return restResult(null, FAIL, msg);
}

public static <T> R<T> fail(T data)
{
return restResult(data, FAIL, "操作失败");
}

public static <T> R<T> fail(T data, String msg)
{
return restResult(data, FAIL, msg);
}

public static <T> R<T> fail(int code, String msg)
{
return restResult(null, code, msg);
}

private static <T> R<T> restResult(T data, int code, String msg)
{
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}

public int getCode()
{
return code;
}

public void setCode(int code)
{
this.code = code;
}

public String getMsg()
{
return msg;
}

public void setMsg(String msg)
{
this.msg = msg;
}

public T getData()
{
return data;
}

public void setData(T data)
{
this.data = data;
}

public static <T> Boolean isError(R<T> ret)
{
return !isSuccess(ret);
}

public static <T> Boolean isSuccess(R<T> ret)
{
return R.SUCCESS == ret.getCode();
}
}

+ 79
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java 查看文件

@@ -0,0 +1,79 @@
package com.ruoyi.common.core.domain;

import java.util.ArrayList;
import java.util.List;

/**
* Tree基类
*
* @author ruoyi
*/
public class TreeEntity extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 父菜单名称 */
private String parentName;

/** 父菜单ID */
private Long parentId;

/** 显示顺序 */
private Integer orderNum;

/** 祖级列表 */
private String ancestors;

/** 子部门 */
private List<?> children = new ArrayList<>();

public String getParentName()
{
return parentName;
}

public void setParentName(String parentName)
{
this.parentName = parentName;
}

public Long getParentId()
{
return parentId;
}

public void setParentId(Long parentId)
{
this.parentId = parentId;
}

public Integer getOrderNum()
{
return orderNum;
}

public void setOrderNum(Integer orderNum)
{
this.orderNum = orderNum;
}

public String getAncestors()
{
return ancestors;
}

public void setAncestors(String ancestors)
{
this.ancestors = ancestors;
}

public List<?> getChildren()
{
return children;
}

public void setChildren(List<?> children)
{
this.children = children;
}
}

+ 77
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java 查看文件

@@ -0,0 +1,77 @@
package com.ruoyi.common.core.domain;

import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysMenu;

/**
* Treeselect树结构实体类
*
* @author ruoyi
*/
public class TreeSelect implements Serializable
{
private static final long serialVersionUID = 1L;

/** 节点ID */
private Long id;

/** 节点名称 */
private String label;

/** 子节点 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<TreeSelect> children;

public TreeSelect()
{

}

public TreeSelect(SysDept dept)
{
this.id = dept.getDeptId();
this.label = dept.getDeptName();
this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}

public TreeSelect(SysMenu menu)
{
this.id = menu.getMenuId();
this.label = menu.getMenuName();
this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}

public Long getId()
{
return id;
}

public void setId(Long id)
{
this.id = id;
}

public String getLabel()
{
return label;
}

public void setLabel(String label)
{
this.label = label;
}

public List<TreeSelect> getChildren()
{
return children;
}

public void setChildren(List<TreeSelect> children)
{
this.children = children;
}
}

+ 203
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java 查看文件

@@ -0,0 +1,203 @@
package com.ruoyi.common.core.domain.entity;

import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;

/**
* 部门表 sys_dept
*
* @author ruoyi
*/
public class SysDept extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 部门ID */
private Long deptId;

/** 父部门ID */
private Long parentId;

/** 祖级列表 */
private String ancestors;

/** 部门名称 */
private String deptName;

/** 显示顺序 */
private Integer orderNum;

/** 负责人 */
private String leader;

/** 联系电话 */
private String phone;

/** 邮箱 */
private String email;

/** 部门状态:0正常,1停用 */
private String status;

/** 删除标志(0代表存在 2代表删除) */
private String delFlag;

/** 父部门名称 */
private String parentName;
/** 子部门 */
private List<SysDept> children = new ArrayList<SysDept>();

public Long getDeptId()
{
return deptId;
}

public void setDeptId(Long deptId)
{
this.deptId = deptId;
}

public Long getParentId()
{
return parentId;
}

public void setParentId(Long parentId)
{
this.parentId = parentId;
}

public String getAncestors()
{
return ancestors;
}

public void setAncestors(String ancestors)
{
this.ancestors = ancestors;
}

@NotBlank(message = "部门名称不能为空")
@Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符")
public String getDeptName()
{
return deptName;
}

public void setDeptName(String deptName)
{
this.deptName = deptName;
}

@NotNull(message = "显示顺序不能为空")
public Integer getOrderNum()
{
return orderNum;
}

public void setOrderNum(Integer orderNum)
{
this.orderNum = orderNum;
}

public String getLeader()
{
return leader;
}

public void setLeader(String leader)
{
this.leader = leader;
}

@Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符")
public String getPhone()
{
return phone;
}

public void setPhone(String phone)
{
this.phone = phone;
}

@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
public String getEmail()
{
return email;
}

public void setEmail(String email)
{
this.email = email;
}

public String getStatus()
{
return status;
}

public void setStatus(String status)
{
this.status = status;
}

public String getDelFlag()
{
return delFlag;
}

public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}

public String getParentName()
{
return parentName;
}

public void setParentName(String parentName)
{
this.parentName = parentName;
}

public List<SysDept> getChildren()
{
return children;
}

public void setChildren(List<SysDept> children)
{
this.children = children;
}

@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("deptId", getDeptId())
.append("parentId", getParentId())
.append("ancestors", getAncestors())
.append("deptName", getDeptName())
.append("orderNum", getOrderNum())
.append("leader", getLeader())
.append("phone", getPhone())
.append("email", getEmail())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

+ 167
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java 查看文件

@@ -0,0 +1,167 @@
package com.ruoyi.common.core.domain.entity;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.BaseEntity;

/**
* 字典数据表 sys_dict_data
*
* @author ruoyi
*/
public class SysDictData extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 字典编码 */
private Long dictCode;

/** 字典排序 */
private Long dictSort;

/** 字典标签 */
private String dictLabel;

/** 字典键值 */
private String dictValue;

/** 字典类型 */
private String dictType;

/** 样式属性(其他样式扩展) */
private String cssClass;

/** 表格字典样式 */
private String listClass;

/** 是否默认(Y是 N否) */
private String isDefault;

/** 状态(0正常 1停用) */
private String status;

public Long getDictCode()
{
return dictCode;
}

public void setDictCode(Long dictCode)
{
this.dictCode = dictCode;
}

public Long getDictSort()
{
return dictSort;
}

public void setDictSort(Long dictSort)
{
this.dictSort = dictSort;
}

@NotBlank(message = "字典标签不能为空")
@Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符")
public String getDictLabel()
{
return dictLabel;
}

public void setDictLabel(String dictLabel)
{
this.dictLabel = dictLabel;
}

@NotBlank(message = "字典键值不能为空")
@Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符")
public String getDictValue()
{
return dictValue;
}

public void setDictValue(String dictValue)
{
this.dictValue = dictValue;
}

@NotBlank(message = "字典类型不能为空")
@Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符")
public String getDictType()
{
return dictType;
}

public void setDictType(String dictType)
{
this.dictType = dictType;
}

@Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符")
public String getCssClass()
{
return cssClass;
}

public void setCssClass(String cssClass)
{
this.cssClass = cssClass;
}

public String getListClass()
{
return listClass;
}

public void setListClass(String listClass)
{
this.listClass = listClass;
}

public boolean getDefault()
{
return UserConstants.YES.equals(this.isDefault);
}

public String getIsDefault()
{
return isDefault;
}

public void setIsDefault(String isDefault)
{
this.isDefault = isDefault;
}

public String getStatus()
{
return status;
}

public void setStatus(String status)
{
this.status = status;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("dictCode", getDictCode())
.append("dictSort", getDictSort())
.append("dictLabel", getDictLabel())
.append("dictValue", getDictValue())
.append("dictType", getDictType())
.append("cssClass", getCssClass())
.append("listClass", getListClass())
.append("isDefault", getIsDefault())
.append("status", getStatus())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

+ 90
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java 查看文件

@@ -0,0 +1,90 @@
package com.ruoyi.common.core.domain.entity;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;

/**
* 字典类型表 sys_dict_type
*
* @author ruoyi
*/
public class SysDictType extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 字典主键 */
private Long dictId;

/** 字典名称 */
private String dictName;

/** 字典类型 */
private String dictType;

/** 状态(0正常 1停用) */
private String status;

public Long getDictId()
{
return dictId;
}

public void setDictId(Long dictId)
{
this.dictId = dictId;
}

@NotBlank(message = "字典名称不能为空")
@Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符")
public String getDictName()
{
return dictName;
}

public void setDictName(String dictName)
{
this.dictName = dictName;
}

@NotBlank(message = "字典类型不能为空")
@Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符")
@Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
public String getDictType()
{
return dictType;
}

public void setDictType(String dictType)
{
this.dictType = dictType;
}

public String getStatus()
{
return status;
}

public void setStatus(String status)
{
this.status = status;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("dictId", getDictId())
.append("dictName", getDictName())
.append("dictType", getDictType())
.append("status", getStatus())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

+ 259
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java 查看文件

@@ -0,0 +1,259 @@
package com.ruoyi.common.core.domain.entity;

import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;

/**
* 菜单权限表 sys_menu
*
* @author ruoyi
*/
public class SysMenu extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 菜单ID */
private Long menuId;

/** 菜单名称 */
private String menuName;

/** 父菜单名称 */
private String parentName;

/** 父菜单ID */
private Long parentId;

/** 显示顺序 */
private Integer orderNum;

/** 路由地址 */
private String path;

/** 组件路径 */
private String component;

/** 路由参数 */
private String query;

/** 是否为外链(0是 1否) */
private String isFrame;

/** 是否缓存(0缓存 1不缓存) */
private String isCache;

/** 类型(M目录 C菜单 F按钮) */
private String menuType;

/** 显示状态(0显示 1隐藏) */
private String visible;
/** 菜单状态(0正常 1停用) */
private String status;

/** 权限字符串 */
private String perms;

/** 菜单图标 */
private String icon;

/** 子菜单 */
private List<SysMenu> children = new ArrayList<SysMenu>();

public Long getMenuId()
{
return menuId;
}

public void setMenuId(Long menuId)
{
this.menuId = menuId;
}

@NotBlank(message = "菜单名称不能为空")
@Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符")
public String getMenuName()
{
return menuName;
}

public void setMenuName(String menuName)
{
this.menuName = menuName;
}

public String getParentName()
{
return parentName;
}

public void setParentName(String parentName)
{
this.parentName = parentName;
}

public Long getParentId()
{
return parentId;
}

public void setParentId(Long parentId)
{
this.parentId = parentId;
}

@NotNull(message = "显示顺序不能为空")
public Integer getOrderNum()
{
return orderNum;
}

public void setOrderNum(Integer orderNum)
{
this.orderNum = orderNum;
}

@Size(min = 0, max = 200, message = "路由地址不能超过200个字符")
public String getPath()
{
return path;
}

public void setPath(String path)
{
this.path = path;
}

@Size(min = 0, max = 200, message = "组件路径不能超过255个字符")
public String getComponent()
{
return component;
}

public void setComponent(String component)
{
this.component = component;
}

public String getQuery()
{
return query;
}

public void setQuery(String query)
{
this.query = query;
}

public String getIsFrame()
{
return isFrame;
}

public void setIsFrame(String isFrame)
{
this.isFrame = isFrame;
}

public String getIsCache()
{
return isCache;
}

public void setIsCache(String isCache)
{
this.isCache = isCache;
}

@NotBlank(message = "菜单类型不能为空")
public String getMenuType()
{
return menuType;
}

public void setMenuType(String menuType)
{
this.menuType = menuType;
}

public String getVisible()
{
return visible;
}

public void setVisible(String visible)
{
this.visible = visible;
}

public String getStatus()
{
return status;
}

public void setStatus(String status)
{
this.status = status;
}

@Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符")
public String getPerms()
{
return perms;
}

public void setPerms(String perms)
{
this.perms = perms;
}

public String getIcon()
{
return icon;
}

public void setIcon(String icon)
{
this.icon = icon;
}

public List<SysMenu> getChildren()
{
return children;
}

public void setChildren(List<SysMenu> children)
{
this.children = children;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("menuId", getMenuId())
.append("menuName", getMenuName())
.append("parentId", getParentId())
.append("orderNum", getOrderNum())
.append("path", getPath())
.append("component", getComponent())
.append("isFrame", getIsFrame())
.append("IsCache", getIsCache())
.append("menuType", getMenuType())
.append("visible", getVisible())
.append("status ", getStatus())
.append("perms", getPerms())
.append("icon", getIcon())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

+ 233
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java 查看文件

@@ -0,0 +1,233 @@
package com.ruoyi.common.core.domain.entity;

import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;

/**
* 角色表 sys_role
*
* @author ruoyi
*/
public class SysRole extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 角色ID */
private Long roleId;

/** 角色名称 */
private String roleName;

/** 角色权限 */
private String roleKey;

/** 角色排序 */
private Integer roleSort;

/** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */
private String dataScope;

/** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
private boolean menuCheckStrictly;

/** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
private boolean deptCheckStrictly;

/** 角色状态(0正常 1停用) */
private String status;

/** 删除标志(0代表存在 2代表删除) */
private String delFlag;

/** 用户是否存在此角色标识 默认不存在 */
private boolean flag = false;

/** 菜单组 */
private Long[] menuIds;

/** 部门组(数据权限) */
private Long[] deptIds;

/** 角色菜单权限 */
private Set<String> permissions;

public SysRole()
{

}

public SysRole(Long roleId)
{
this.roleId = roleId;
}

public Long getRoleId()
{
return roleId;
}

public void setRoleId(Long roleId)
{
this.roleId = roleId;
}

public boolean isAdmin()
{
return isAdmin(this.roleId);
}

public static boolean isAdmin(Long roleId)
{
return roleId != null && 1L == roleId;
}

@NotBlank(message = "角色名称不能为空")
@Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符")
public String getRoleName()
{
return roleName;
}

public void setRoleName(String roleName)
{
this.roleName = roleName;
}

@NotBlank(message = "权限字符不能为空")
@Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符")
public String getRoleKey()
{
return roleKey;
}

public void setRoleKey(String roleKey)
{
this.roleKey = roleKey;
}

@NotNull(message = "显示顺序不能为空")
public Integer getRoleSort()
{
return roleSort;
}

public void setRoleSort(Integer roleSort)
{
this.roleSort = roleSort;
}

public String getDataScope()
{
return dataScope;
}

public void setDataScope(String dataScope)
{
this.dataScope = dataScope;
}

public boolean isMenuCheckStrictly()
{
return menuCheckStrictly;
}

public void setMenuCheckStrictly(boolean menuCheckStrictly)
{
this.menuCheckStrictly = menuCheckStrictly;
}

public boolean isDeptCheckStrictly()
{
return deptCheckStrictly;
}

public void setDeptCheckStrictly(boolean deptCheckStrictly)
{
this.deptCheckStrictly = deptCheckStrictly;
}

public String getStatus()
{
return status;
}

public void setStatus(String status)
{
this.status = status;
}

public String getDelFlag()
{
return delFlag;
}

public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}

public boolean isFlag()
{
return flag;
}

public void setFlag(boolean flag)
{
this.flag = flag;
}

public Long[] getMenuIds()
{
return menuIds;
}

public void setMenuIds(Long[] menuIds)
{
this.menuIds = menuIds;
}

public Long[] getDeptIds()
{
return deptIds;
}

public void setDeptIds(Long[] deptIds)
{
this.deptIds = deptIds;
}

public Set<String> getPermissions()
{
return permissions;
}

public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}

@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("roleId", getRoleId())
.append("roleName", getRoleName())
.append("roleKey", getRoleKey())
.append("roleSort", getRoleSort())
.append("dataScope", getDataScope())
.append("menuCheckStrictly", isMenuCheckStrictly())
.append("deptCheckStrictly", isDeptCheckStrictly())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

+ 56
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java 查看文件

@@ -0,0 +1,56 @@
package com.ruoyi.common.core.domain.entity;

import com.ruoyi.common.core.domain.BaseEntity;

import java.util.Date;

/**
* 用户对象 sys_user
*
* @author ruoyi
*/
public class SysUser extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 用户ID */
private Long userId;

private Long deptId;

/** 用户账号 */
private String userName;

/** 用户昵称 */
private String nickName;

/** 用户邮箱 */
private String email;

/** 手机号码 */
private String phonenumber;

/** 用户性别 */
private String sex;

/** 用户头像 */
private String avatar;

/** 密码 */
private String password;

/** 帐号状态(0正常 1停用) */
private String status;

/** 删除标志(0代表存在 2代表删除) */
private String delFlag;

/** 最后登录IP */
private String loginIp;

/** 最后登录时间 */
private Date loginDate;

/** 角色ID */
private Long roleId;
}

+ 69
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java 查看文件

@@ -0,0 +1,69 @@
package com.ruoyi.common.core.domain.model;

/**
* 用户登录对象
*
* @author ruoyi
*/
public class LoginBody
{
/**
* 用户名
*/
private String username;

/**
* 用户密码
*/
private String password;

/**
* 验证码
*/
private String code;

/**
* 唯一标识
*/
private String uuid;

public String getUsername()
{
return username;
}

public void setUsername(String username)
{
this.username = username;
}

public String getPassword()
{
return password;
}

public void setPassword(String password)
{
this.password = password;
}

public String getCode()
{
return code;
}

public void setCode(String code)
{
this.code = code;
}

public String getUuid()
{
return uuid;
}

public void setUuid(String uuid)
{
this.uuid = uuid;
}
}

+ 11
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java 查看文件

@@ -0,0 +1,11 @@
package com.ruoyi.common.core.domain.model;

/**
* 用户注册对象
*
* @author ruoyi
*/
public class RegisterBody extends LoginBody
{

}

+ 101
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java 查看文件

@@ -0,0 +1,101 @@
package com.ruoyi.common.core.page;

import com.ruoyi.common.utils.StringUtils;

/**
* 分页数据
*
* @author ruoyi
*/
public class PageDomain
{
/** 当前记录起始索引 */
private Integer pageNum;

/** 每页显示记录数 */
private Integer pageSize;

/** 排序列 */
private String orderByColumn;

/** 排序的方向desc或者asc */
private String isAsc = "asc";

/** 分页参数合理化 */
private Boolean reasonable = true;

public String getOrderBy()
{
if (StringUtils.isEmpty(orderByColumn))
{
return "";
}
return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc;
}

public Integer getPageNum()
{
return pageNum;
}

public void setPageNum(Integer pageNum)
{
this.pageNum = pageNum;
}

public Integer getPageSize()
{
return pageSize;
}

public void setPageSize(Integer pageSize)
{
this.pageSize = pageSize;
}

public String getOrderByColumn()
{
return orderByColumn;
}

public void setOrderByColumn(String orderByColumn)
{
this.orderByColumn = orderByColumn;
}

public String getIsAsc()
{
return isAsc;
}

public void setIsAsc(String isAsc)
{
if (StringUtils.isNotEmpty(isAsc))
{
// 兼容前端排序类型
if ("ascending".equals(isAsc))
{
isAsc = "asc";
}
else if ("descending".equals(isAsc))
{
isAsc = "desc";
}
this.isAsc = isAsc;
}
}

public Boolean getReasonable()
{
if (StringUtils.isNull(reasonable))
{
return Boolean.TRUE;
}
return reasonable;
}

public void setReasonable(Boolean reasonable)
{
this.reasonable = reasonable;
}
}

+ 85
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java 查看文件

@@ -0,0 +1,85 @@
package com.ruoyi.common.core.page;

import java.io.Serializable;
import java.util.List;

/**
* 表格分页数据对象
*
* @author ruoyi
*/
public class TableDataInfo implements Serializable
{
private static final long serialVersionUID = 1L;

/** 总记录数 */
private long total;

/** 列表数据 */
private List<?> rows;

/** 消息状态码 */
private int code;

/** 消息内容 */
private String msg;

/**
* 表格数据对象
*/
public TableDataInfo()
{
}

/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<?> list, int total)
{
this.rows = list;
this.total = total;
}

public long getTotal()
{
return total;
}

public void setTotal(long total)
{
this.total = total;
}

public List<?> getRows()
{
return rows;
}

public void setRows(List<?> rows)
{
this.rows = rows;
}

public int getCode()
{
return code;
}

public void setCode(int code)
{
this.code = code;
}

public String getMsg()
{
return msg;
}

public void setMsg(String msg)
{
this.msg = msg;
}
}

+ 56
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java 查看文件

@@ -0,0 +1,56 @@
package com.ruoyi.common.core.page;

import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.ServletUtils;

/**
* 表格数据处理
*
* @author ruoyi
*/
public class TableSupport
{
/**
* 当前记录起始索引
*/
public static final String PAGE_NUM = "pageNum";

/**
* 每页显示记录数
*/
public static final String PAGE_SIZE = "pageSize";

/**
* 排序列
*/
public static final String ORDER_BY_COLUMN = "orderByColumn";

/**
* 排序的方向 "desc" 或者 "asc".
*/
public static final String IS_ASC = "isAsc";

/**
* 分页参数合理化
*/
public static final String REASONABLE = "reasonable";

/**
* 封装分页对象
*/
public static PageDomain getPageDomain()
{
PageDomain pageDomain = new PageDomain();
pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1));
pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10));
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
return pageDomain;
}

public static PageDomain buildPageRequest()
{
return getPageDomain();
}
}

+ 86
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java 查看文件

@@ -0,0 +1,86 @@
package com.ruoyi.common.core.text;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import com.ruoyi.common.utils.StringUtils;

/**
* 字符集工具类
*
* @author ruoyi
*/
public class CharsetKit
{
/** ISO-8859-1 */
public static final String ISO_8859_1 = "ISO-8859-1";
/** UTF-8 */
public static final String UTF_8 = "UTF-8";
/** GBK */
public static final String GBK = "GBK";

/** ISO-8859-1 */
public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);
/** UTF-8 */
public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);
/** GBK */
public static final Charset CHARSET_GBK = Charset.forName(GBK);

/**
* 转换为Charset对象
*
* @param charset 字符集,为空则返回默认字符集
* @return Charset
*/
public static Charset charset(String charset)
{
return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
}

/**
* 转换字符串的字符集编码
*
* @param source 字符串
* @param srcCharset 源字符集,默认ISO-8859-1
* @param destCharset 目标字符集,默认UTF-8
* @return 转换后的字符集
*/
public static String convert(String source, String srcCharset, String destCharset)
{
return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
}

/**
* 转换字符串的字符集编码
*
* @param source 字符串
* @param srcCharset 源字符集,默认ISO-8859-1
* @param destCharset 目标字符集,默认UTF-8
* @return 转换后的字符集
*/
public static String convert(String source, Charset srcCharset, Charset destCharset)
{
if (null == srcCharset)
{
srcCharset = StandardCharsets.ISO_8859_1;
}

if (null == destCharset)
{
destCharset = StandardCharsets.UTF_8;
}

if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset))
{
return source;
}
return new String(source.getBytes(srcCharset), destCharset);
}

/**
* @return 系统字符集编码
*/
public static String systemCharset()
{
return Charset.defaultCharset().name();
}
}

+ 1006
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java
文件差異過大導致無法顯示
查看文件


+ 92
- 0
ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java 查看文件

@@ -0,0 +1,92 @@
package com.ruoyi.common.core.text;

import com.ruoyi.common.utils.StringUtils;

/**
* 字符串格式化
*
* @author ruoyi
*/
public class StrFormatter
{
public static final String EMPTY_JSON = "{}";
public static final char C_BACKSLASH = '\\';
public static final char C_DELIM_START = '{';
public static final char C_DELIM_END = '}';

/**
* 格式化字符串<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* 例:<br>
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param strPattern 字符串模板
* @param argArray 参数列表
* @return 结果
*/
public static String format(final String strPattern, final Object... argArray)
{
if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray))
{
return strPattern;
}
final int strPatternLength = strPattern.length();

// 初始化定义好的长度以获得更好的性能
StringBuilder sbuf = new StringBuilder(strPatternLength + 50);

int handledPosition = 0;
int delimIndex;// 占位符所在位置
for (int argIndex = 0; argIndex < argArray.length; argIndex++)
{
delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
if (delimIndex == -1)
{
if (handledPosition == 0)
{
return strPattern;
}
else
{ // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
sbuf.append(strPattern, handledPosition, strPatternLength);
return sbuf.toString();
}
}
else
{
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH)
{
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH)
{
// 转义符之前还有一个转义符,占位符依旧有效
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
}
else
{
// 占位符被转义
argIndex--;
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(C_DELIM_START);
handledPosition = delimIndex + 1;
}
}
else
{
// 正常占位符
sbuf.append(strPattern, handledPosition, delimIndex);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
}
}
}
// 加入最后一个占位符后所有的字符
sbuf.append(strPattern, handledPosition, strPattern.length());

return sbuf.toString();
}
}

+ 20
- 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java 查看文件

@@ -0,0 +1,20 @@
package com.ruoyi.common.enums;

/**
* 操作状态
*
* @author ruoyi
*
*/
public enum BusinessStatus
{
/**
* 成功
*/
SUCCESS,

/**
* 失败
*/
FAIL,
}

+ 64
- 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java 查看文件

@@ -0,0 +1,64 @@
package com.ruoyi.common.enums;

/**
* 业务操作类型
*
* @author ruoyi
*/
public enum BusinessType
{
/**
* 其它
*/
SELECT,

/**
* 其它
*/
OTHER,

/**
* 新增
*/
INSERT,

/**
* 修改
*/
UPDATE,

/**
* 删除
*/
DELETE,

/**
* 授权
*/
GRANT,

/**
* 导出
*/
EXPORT,

/**
* 导入
*/
IMPORT,

/**
* 强退
*/
FORCE,

/**
* 生成代码
*/
GENCODE,
/**
* 清空数据
*/
CLEAN,
}

+ 19
- 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java 查看文件

@@ -0,0 +1,19 @@
package com.ruoyi.common.enums;

/**
* 数据源
*
* @author ruoyi
*/
public enum DataSourceType
{
/**
* 主库
*/
MASTER,

/**
* 从库
*/
SLAVE
}

+ 36
- 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java 查看文件

@@ -0,0 +1,36 @@
package com.ruoyi.common.enums;

import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;

/**
* 请求方式
*
* @author ruoyi
*/
public enum HttpMethod
{
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;

private static final Map<String, HttpMethod> mappings = new HashMap<>(16);

static
{
for (HttpMethod httpMethod : values())
{
mappings.put(httpMethod.name(), httpMethod);
}
}

@Nullable
public static HttpMethod resolve(@Nullable String method)
{
return (method != null ? mappings.get(method) : null);
}

public boolean matches(String method)
{
return (this == resolve(method));
}
}

+ 20
- 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java 查看文件

@@ -0,0 +1,20 @@
package com.ruoyi.common.enums;

/**
* 限流类型
*
* @author ruoyi
*/

public enum LimitType
{
/**
* 默认策略全局限流
*/
DEFAULT,

/**
* 根据请求者IP进行限流
*/
IP
}

+ 24
- 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java 查看文件

@@ -0,0 +1,24 @@
package com.ruoyi.common.enums;

/**
* 操作人类别
*
* @author ruoyi
*/
public enum OperatorType
{
/**
* 其它
*/
OTHER,

/**
* 后台用户
*/
MANAGE,

/**
* 手机端用户
*/
MOBILE
}

+ 30
- 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java 查看文件

@@ -0,0 +1,30 @@
package com.ruoyi.common.enums;

/**
* 用户状态
*
* @author ruoyi
*/
public enum UserStatus
{
OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");

private final String code;
private final String info;

UserStatus(String code, String info)
{
this.code = code;
this.info = info;
}

public String getCode()
{
return code;
}

public String getInfo()
{
return info;
}
}

+ 15
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java 查看文件

@@ -0,0 +1,15 @@
package com.ruoyi.common.exception;

/**
* 演示模式异常
*
* @author ruoyi
*/
public class DemoModeException extends RuntimeException
{
private static final long serialVersionUID = 1L;

public DemoModeException()
{
}
}

+ 58
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java 查看文件

@@ -0,0 +1,58 @@
package com.ruoyi.common.exception;

/**
* 全局异常
*
* @author ruoyi
*/
public class GlobalException extends RuntimeException
{
private static final long serialVersionUID = 1L;

/**
* 错误提示
*/
private String message;

/**
* 错误明细,内部调试错误
*
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
private String detailMessage;

/**
* 空构造方法,避免反序列化问题
*/
public GlobalException()
{
}

public GlobalException(String message)
{
this.message = message;
}

public String getDetailMessage()
{
return detailMessage;
}

public GlobalException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}

@Override
public String getMessage()
{
return message;
}

public GlobalException setMessage(String message)
{
this.message = message;
return this;
}
}

+ 74
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java 查看文件

@@ -0,0 +1,74 @@
package com.ruoyi.common.exception;

/**
* 业务异常
*
* @author ruoyi
*/
public final class ServiceException extends RuntimeException
{
private static final long serialVersionUID = 1L;

/**
* 错误码
*/
private Integer code;

/**
* 错误提示
*/
private String message;

/**
* 错误明细,内部调试错误
*
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
private String detailMessage;

/**
* 空构造方法,避免反序列化问题
*/
public ServiceException()
{
}

public ServiceException(String message)
{
this.message = message;
}

public ServiceException(String message, Integer code)
{
this.message = message;
this.code = code;
}

public String getDetailMessage()
{
return detailMessage;
}

@Override
public String getMessage()
{
return message;
}

public Integer getCode()
{
return code;
}

public ServiceException setMessage(String message)
{
this.message = message;
return this;
}

public ServiceException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}
}

+ 26
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java 查看文件

@@ -0,0 +1,26 @@
package com.ruoyi.common.exception;

/**
* 工具类异常
*
* @author ruoyi
*/
public class UtilException extends RuntimeException
{
private static final long serialVersionUID = 8247610319171014183L;

public UtilException(Throwable e)
{
super(e.getMessage(), e);
}

public UtilException(String message)
{
super(message);
}

public UtilException(String message, Throwable throwable)
{
super(message, throwable);
}
}

+ 97
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java 查看文件

@@ -0,0 +1,97 @@
package com.ruoyi.common.exception.base;

import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;

/**
* 基础异常
*
* @author ruoyi
*/
public class BaseException extends RuntimeException
{
private static final long serialVersionUID = 1L;

/**
* 所属模块
*/
private String module;

/**
* 错误码
*/
private String code;

/**
* 错误码对应的参数
*/
private Object[] args;

/**
* 错误消息
*/
private String defaultMessage;

public BaseException(String module, String code, Object[] args, String defaultMessage)
{
this.module = module;
this.code = code;
this.args = args;
this.defaultMessage = defaultMessage;
}

public BaseException(String module, String code, Object[] args)
{
this(module, code, args, null);
}

public BaseException(String module, String defaultMessage)
{
this(module, null, null, defaultMessage);
}

public BaseException(String code, Object[] args)
{
this(null, code, args, null);
}

public BaseException(String defaultMessage)
{
this(null, null, null, defaultMessage);
}

@Override
public String getMessage()
{
String message = null;
if (!StringUtils.isEmpty(code))
{
message = MessageUtils.message(code, args);
}
if (message == null)
{
message = defaultMessage;
}
return message;
}

public String getModule()
{
return module;
}

public String getCode()
{
return code;
}

public Object[] getArgs()
{
return args;
}

public String getDefaultMessage()
{
return defaultMessage;
}
}

+ 19
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java 查看文件

@@ -0,0 +1,19 @@
package com.ruoyi.common.exception.file;

import com.ruoyi.common.exception.base.BaseException;

/**
* 文件信息异常类
*
* @author ruoyi
*/
public class FileException extends BaseException
{
private static final long serialVersionUID = 1L;

public FileException(String code, Object[] args)
{
super("file", code, args, null);
}

}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.file;

/**
* 文件名称超长限制异常类
*
* @author ruoyi
*/
public class FileNameLengthLimitExceededException extends FileException
{
private static final long serialVersionUID = 1L;

public FileNameLengthLimitExceededException(int defaultFileNameLength)
{
super("upload.filename.exceed.length", new Object[] { defaultFileNameLength });
}
}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.file;

/**
* 文件名大小限制异常类
*
* @author ruoyi
*/
public class FileSizeLimitExceededException extends FileException
{
private static final long serialVersionUID = 1L;

public FileSizeLimitExceededException(long defaultMaxSize)
{
super("upload.exceed.maxSize", new Object[] { defaultMaxSize });
}
}

+ 61
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java 查看文件

@@ -0,0 +1,61 @@
package com.ruoyi.common.exception.file;

import java.io.PrintStream;
import java.io.PrintWriter;

/**
* 文件上传异常类
*
* @author ruoyi
*/
public class FileUploadException extends Exception
{

private static final long serialVersionUID = 1L;

private final Throwable cause;

public FileUploadException()
{
this(null, null);
}

public FileUploadException(final String msg)
{
this(msg, null);
}

public FileUploadException(String msg, Throwable cause)
{
super(msg);
this.cause = cause;
}

@Override
public void printStackTrace(PrintStream stream)
{
super.printStackTrace(stream);
if (cause != null)
{
stream.println("Caused by:");
cause.printStackTrace(stream);
}
}

@Override
public void printStackTrace(PrintWriter writer)
{
super.printStackTrace(writer);
if (cause != null)
{
writer.println("Caused by:");
cause.printStackTrace(writer);
}
}

@Override
public Throwable getCause()
{
return cause;
}
}

+ 80
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java 查看文件

@@ -0,0 +1,80 @@
package com.ruoyi.common.exception.file;

import java.util.Arrays;

/**
* 文件上传 误异常类
*
* @author ruoyi
*/
public class InvalidExtensionException extends FileUploadException
{
private static final long serialVersionUID = 1L;

private String[] allowedExtension;
private String extension;
private String filename;

public InvalidExtensionException(String[] allowedExtension, String extension, String filename)
{
super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式");
this.allowedExtension = allowedExtension;
this.extension = extension;
this.filename = filename;
}

public String[] getAllowedExtension()
{
return allowedExtension;
}

public String getExtension()
{
return extension;
}

public String getFilename()
{
return filename;
}

public static class InvalidImageExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;

public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}

public static class InvalidFlashExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;

public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}

public static class InvalidMediaExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;

public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}

public static class InvalidVideoExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;

public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}
}

+ 34
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java 查看文件

@@ -0,0 +1,34 @@
package com.ruoyi.common.exception.job;

/**
* 计划策略异常
*
* @author ruoyi
*/
public class TaskException extends Exception
{
private static final long serialVersionUID = 1L;

private Code code;

public TaskException(String msg, Code code)
{
this(msg, code, null);
}

public TaskException(String msg, Code code, Exception nestedEx)
{
super(msg, nestedEx);
this.code = code;
}

public Code getCode()
{
return code;
}

public enum Code
{
TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE
}
}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;

/**
* 黑名单IP异常类
*
* @author ruoyi
*/
public class BlackListException extends UserException
{
private static final long serialVersionUID = 1L;

public BlackListException()
{
super("login.blocked", null);
}
}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;

/**
* 验证码错误异常类
*
* @author ruoyi
*/
public class CaptchaException extends UserException
{
private static final long serialVersionUID = 1L;

public CaptchaException()
{
super("user.jcaptcha.error", null);
}
}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;

/**
* 验证码失效异常类
*
* @author ruoyi
*/
public class CaptchaExpireException extends UserException
{
private static final long serialVersionUID = 1L;

public CaptchaExpireException()
{
super("user.jcaptcha.expire", null);
}
}

+ 18
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java 查看文件

@@ -0,0 +1,18 @@
package com.ruoyi.common.exception.user;

import com.ruoyi.common.exception.base.BaseException;

/**
* 用户信息异常类
*
* @author ruoyi
*/
public class UserException extends BaseException
{
private static final long serialVersionUID = 1L;

public UserException(String code, Object[] args)
{
super("user", code, args, null);
}
}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;

/**
* 用户不存在异常类
*
* @author ruoyi
*/
public class UserNotExistsException extends UserException
{
private static final long serialVersionUID = 1L;

public UserNotExistsException()
{
super("user.not.exists", null);
}
}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;

/**
* 用户密码不正确或不符合规范异常类
*
* @author ruoyi
*/
public class UserPasswordNotMatchException extends UserException
{
private static final long serialVersionUID = 1L;

public UserPasswordNotMatchException()
{
super("user.password.not.match", null);
}
}

+ 16
- 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java 查看文件

@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;

/**
* 用户错误最大次数异常类
*
* @author ruoyi
*/
public class UserPasswordRetryLimitExceedException extends UserException
{
private static final long serialVersionUID = 1L;

public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime)
{
super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime });
}
}

+ 24
- 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java 查看文件

@@ -0,0 +1,24 @@
package com.ruoyi.common.filter;

import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;

/**
* 排除JSON敏感属性
*
* @author ruoyi
*/
public class PropertyPreExcludeFilter extends SimplePropertyPreFilter
{
public PropertyPreExcludeFilter()
{
}

public PropertyPreExcludeFilter addExcludes(String... filters)
{
for (int i = 0; i < filters.length; i++)
{
this.getExcludes().add(filters[i]);
}
return this;
}
}

+ 52
- 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java 查看文件

@@ -0,0 +1,52 @@
package com.ruoyi.common.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils;

/**
* Repeatable 过滤器
*
* @author ruoyi
*/
public class RepeatableFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest
&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
{
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (null == requestWrapper)
{
chain.doFilter(request, response);
}
else
{
chain.doFilter(requestWrapper, response);
}
}

@Override
public void destroy()
{

}
}

+ 76
- 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java 查看文件

@@ -0,0 +1,76 @@
package com.ruoyi.common.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper;
import com.ruoyi.common.constant.Constants;

/**
* 构建可重复读取inputStream的request
*
* @author ruoyi
*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
{
private final byte[] body;

public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
{
super(request);
request.setCharacterEncoding(Constants.UTF8);
response.setCharacterEncoding(Constants.UTF8);

body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8);
}

@Override
public BufferedReader getReader() throws IOException
{
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException
{
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream()
{
@Override
public int read() throws IOException
{
return bais.read();
}

@Override
public int available() throws IOException
{
return body.length;
}

@Override
public boolean isFinished()
{
return false;
}

@Override
public boolean isReady()
{
return false;
}

@Override
public void setReadListener(ReadListener readListener)
{

}
};
}
}

+ 75
- 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java 查看文件

@@ -0,0 +1,75 @@
package com.ruoyi.common.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.enums.HttpMethod;

/**
* 防止XSS攻击的过滤器
*
* @author ruoyi
*/
public class XssFilter implements Filter
{
/**
* 排除链接
*/
public List<String> excludes = new ArrayList<>();

@Override
public void init(FilterConfig filterConfig) throws ServletException
{
String tempExcludes = filterConfig.getInitParameter("excludes");
if (StringUtils.isNotEmpty(tempExcludes))
{
String[] url = tempExcludes.split(",");
for (int i = 0; url != null && i < url.length; i++)
{
excludes.add(url[i]);
}
}
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (handleExcludeURL(req, resp))
{
chain.doFilter(request, response);
return;
}
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}

private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response)
{
String url = request.getServletPath();
String method = request.getMethod();
// GET DELETE 不过滤
if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method))
{
return true;
}
return StringUtils.matches(url, excludes);
}

@Override
public void destroy()
{

}
}

+ 111
- 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java 查看文件

@@ -0,0 +1,111 @@
package com.ruoyi.common.filter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.html.EscapeUtil;

/**
* XSS过滤处理
*
* @author ruoyi
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
{
/**
* @param request
*/
public XssHttpServletRequestWrapper(HttpServletRequest request)
{
super(request);
}

@Override
public String[] getParameterValues(String name)
{
String[] values = super.getParameterValues(name);
if (values != null)
{
int length = values.length;
String[] escapesValues = new String[length];
for (int i = 0; i < length; i++)
{
// 防xss攻击和过滤前后空格
escapesValues[i] = EscapeUtil.clean(values[i]).trim();
}
return escapesValues;
}
return super.getParameterValues(name);
}

@Override
public ServletInputStream getInputStream() throws IOException
{
// 非json类型,直接返回
if (!isJsonRequest())
{
return super.getInputStream();
}

// 为空,直接返回
String json = IOUtils.toString(super.getInputStream(), "utf-8");
if (StringUtils.isEmpty(json))
{
return super.getInputStream();
}

// xss过滤
json = EscapeUtil.clean(json).trim();
byte[] jsonBytes = json.getBytes("utf-8");
final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes);
return new ServletInputStream()
{
@Override
public boolean isFinished()
{
return true;
}

@Override
public boolean isReady()
{
return true;
}

@Override
public int available() throws IOException
{
return jsonBytes.length;
}

@Override
public void setReadListener(ReadListener readListener)
{
}

@Override
public int read() throws IOException
{
return bis.read();
}
};
}

/**
* 是否是Json请求
*
* @param request
*/
public boolean isJsonRequest()
{
String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
}
}

+ 114
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java 查看文件

@@ -0,0 +1,114 @@
package com.ruoyi.common.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
* 精确的浮点数运算
*
* @author ruoyi
*/
public class Arith
{

/** 默认除法运算精度 */
private static final int DEF_DIV_SCALE = 10;

/** 这个类不能实例化 */
private Arith()
{
}

/**
* 提供精确的加法运算。
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}

/**
* 提供精确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}

/**
* 提供精确的乘法运算。
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2)
{
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}

/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2)
{
return div(v1, v2, DEF_DIV_SCALE);
}

/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale)
{
if (scale < 0)
{
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
if (b1.compareTo(BigDecimal.ZERO) == 0)
{
return BigDecimal.ZERO.doubleValue();
}
return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}

/**
* 提供精确的小数位四舍五入处理。
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale)
{
if (scale < 0)
{
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = BigDecimal.ONE;
return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
}

+ 191
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java 查看文件

@@ -0,0 +1,191 @@
package com.ruoyi.common.utils;

import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;

/**
* 时间工具类
*
* @author ruoyi
*/
public class DateUtils extends org.apache.commons.lang3.time.DateUtils
{
public static String YYYY = "yyyy";

public static String YYYY_MM = "yyyy-MM";

public static String YYYY_MM_DD = "yyyy-MM-dd";

public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

private static String[] parsePatterns = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};

/**
* 获取当前Date型日期
*
* @return Date() 当前日期
*/
public static Date getNowDate()
{
return new Date();
}

/**
* 获取当前日期, 默认格式为yyyy-MM-dd
*
* @return String
*/
public static String getDate()
{
return dateTimeNow(YYYY_MM_DD);
}

public static final String getTime()
{
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}

public static final String dateTimeNow()
{
return dateTimeNow(YYYYMMDDHHMMSS);
}

public static final String dateTimeNow(final String format)
{
return parseDateToStr(format, new Date());
}

public static final String dateTime(final Date date)
{
return parseDateToStr(YYYY_MM_DD, date);
}

public static final String parseDateToStr(final String format, final Date date)
{
return new SimpleDateFormat(format).format(date);
}

public static final Date dateTime(final String format, final String ts)
{
try
{
return new SimpleDateFormat(format).parse(ts);
}
catch (ParseException e)
{
throw new RuntimeException(e);
}
}

/**
* 日期路径 即年/月/日 如2018/08/08
*/
public static final String datePath()
{
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}

/**
* 日期路径 即年/月/日 如20180808
*/
public static final String dateTime()
{
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}

/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str)
{
if (str == null)
{
return null;
}
try
{
return parseDate(str.toString(), parsePatterns);
}
catch (ParseException e)
{
return null;
}
}

/**
* 获取服务器启动时间
*/
public static Date getServerStartDate()
{
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}

/**
* 计算相差天数
*/
public static int differentDaysByMillisecond(Date date1, Date date2)
{
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
}

/**
* 计算时间差
*
* @param endDate 最后时间
* @param startTime 开始时间
* @return 时间差(天/小时/分钟)
*/
public static String timeDistance(Date endDate, Date startTime)
{
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - startTime.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "天" + hour + "小时" + min + "分钟";
}

/**
* 增加 LocalDateTime ==> Date
*/
public static Date toDate(LocalDateTime temporalAccessor)
{
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}

/**
* 增加 LocalDate ==> Date
*/
public static Date toDate(LocalDate temporalAccessor)
{
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
}

+ 39
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java 查看文件

@@ -0,0 +1,39 @@
package com.ruoyi.common.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.commons.lang3.exception.ExceptionUtils;

/**
* 错误信息处理类。
*
* @author ruoyi
*/
public class ExceptionUtil
{
/**
* 获取exception的详细错误信息。
*/
public static String getExceptionMessage(Throwable e)
{
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw, true));
return sw.toString();
}

public static String getRootErrorMessage(Exception e)
{
Throwable root = ExceptionUtils.getRootCause(e);
root = (root == null ? e : root);
if (root == null)
{
return "";
}
String msg = root.getMessage();
if (msg == null)
{
return "null";
}
return StringUtils.defaultString(msg);
}
}

+ 18
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java 查看文件

@@ -0,0 +1,18 @@
package com.ruoyi.common.utils;

/**
* 处理并记录日志文件
*
* @author ruoyi
*/
public class LogUtils
{
public static String getBlock(Object msg)
{
if (msg == null)
{
msg = "";
}
return "[" + msg.toString() + "]";
}
}

+ 26
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java 查看文件

@@ -0,0 +1,26 @@
package com.ruoyi.common.utils;

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import com.ruoyi.common.utils.spring.SpringUtils;

/**
* 获取i18n资源文件
*
* @author ruoyi
*/
public class MessageUtils
{
/**
* 根据消息键和参数 获取消息 委托给spring messageSource
*
* @param code 消息键
* @param args 参数
* @return 获取国际化翻译值
*/
public static String message(String code, Object... args)
{
MessageSource messageSource = SpringUtils.getBean(MessageSource.class);
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
}
}

+ 35
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java 查看文件

@@ -0,0 +1,35 @@
package com.ruoyi.common.utils;

import com.github.pagehelper.PageHelper;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.sql.SqlUtil;

/**
* 分页工具类
*
* @author ruoyi
*/
public class PageUtils extends PageHelper
{
/**
* 设置请求分页数据
*/
public static void startPage()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}

/**
* 清理分页的线程变量
*/
public static void clearPage()
{
PageHelper.clearPage();
}
}

+ 218
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java 查看文件

@@ -0,0 +1,218 @@
package com.ruoyi.common.utils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.text.Convert;

/**
* 客户端工具类
*
* @author ruoyi
*/
public class ServletUtils
{
/**
* 获取String参数
*/
public static String getParameter(String name)
{
return getRequest().getParameter(name);
}

/**
* 获取String参数
*/
public static String getParameter(String name, String defaultValue)
{
return Convert.toStr(getRequest().getParameter(name), defaultValue);
}

/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name)
{
return Convert.toInt(getRequest().getParameter(name));
}

/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name, Integer defaultValue)
{
return Convert.toInt(getRequest().getParameter(name), defaultValue);
}

/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name)
{
return Convert.toBool(getRequest().getParameter(name));
}

/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name, Boolean defaultValue)
{
return Convert.toBool(getRequest().getParameter(name), defaultValue);
}

/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String[]> getParams(ServletRequest request)
{
final Map<String, String[]> map = request.getParameterMap();
return Collections.unmodifiableMap(map);
}

/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String> getParamMap(ServletRequest request)
{
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet())
{
params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
}
return params;
}

/**
* 获取request
*/
public static HttpServletRequest getRequest()
{
return getRequestAttributes().getRequest();
}

/**
* 获取response
*/
public static HttpServletResponse getResponse()
{
return getRequestAttributes().getResponse();
}

/**
* 获取session
*/
public static HttpSession getSession()
{
return getRequest().getSession();
}

public static ServletRequestAttributes getRequestAttributes()
{
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}

/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
*/
public static void renderString(HttpServletResponse response, String string)
{
try
{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
}

/**
* 是否是Ajax异步请求
*
* @param request
*/
public static boolean isAjaxRequest(HttpServletRequest request)
{
String accept = request.getHeader("accept");
if (accept != null && accept.contains("application/json"))
{
return true;
}

String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest"))
{
return true;
}

String uri = request.getRequestURI();
if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml"))
{
return true;
}

String ajax = request.getParameter("__ajax");
return StringUtils.inStringIgnoreCase(ajax, "json", "xml");
}

/**
* 内容编码
*
* @param str 内容
* @return 编码后的内容
*/
public static String urlEncode(String str)
{
try
{
return URLEncoder.encode(str, Constants.UTF8);
}
catch (UnsupportedEncodingException e)
{
return StringUtils.EMPTY;
}
}

/**
* 内容解码
*
* @param str 内容
* @return 解码后的内容
*/
public static String urlDecode(String str)
{
try
{
return URLDecoder.decode(str, Constants.UTF8);
}
catch (UnsupportedEncodingException e)
{
return StringUtils.EMPTY;
}
}
}

+ 638
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java 查看文件

@@ -0,0 +1,638 @@
package com.ruoyi.common.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.util.AntPathMatcher;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.text.StrFormatter;

/**
* 字符串工具类
*
* @author ruoyi
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils
{
/** 空字符串 */
private static final String NULLSTR = "";

/** 下划线 */
private static final char SEPARATOR = '_';

/**
* 获取参数不为空值
*
* @param value defaultValue 要判断的value
* @return value 返回值
*/
public static <T> T nvl(T value, T defaultValue)
{
return value != null ? value : defaultValue;
}

/**
* * 判断一个Collection是否为空, 包含List,Set,Queue
*
* @param coll 要判断的Collection
* @return true:为空 false:非空
*/
public static boolean isEmpty(Collection<?> coll)
{
return isNull(coll) || coll.isEmpty();
}

/**
* * 判断一个Collection是否非空,包含List,Set,Queue
*
* @param coll 要判断的Collection
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Collection<?> coll)
{
return !isEmpty(coll);
}

/**
* * 判断一个对象数组是否为空
*
* @param objects 要判断的对象数组
** @return true:为空 false:非空
*/
public static boolean isEmpty(Object[] objects)
{
return isNull(objects) || (objects.length == 0);
}

/**
* * 判断一个对象数组是否非空
*
* @param objects 要判断的对象数组
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Object[] objects)
{
return !isEmpty(objects);
}

/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true:为空 false:非空
*/
public static boolean isEmpty(Map<?, ?> map)
{
return isNull(map) || map.isEmpty();
}

/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Map<?, ?> map)
{
return !isEmpty(map);
}

/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true:为空 false:非空
*/
public static boolean isEmpty(String str)
{
return isNull(str) || NULLSTR.equals(str.trim());
}

/**
* * 判断一个字符串是否为非空串
*
* @param str String
* @return true:非空串 false:空串
*/
public static boolean isNotEmpty(String str)
{
return !isEmpty(str);
}

/**
* * 判断一个对象是否为空
*
* @param object Object
* @return true:为空 false:非空
*/
public static boolean isNull(Object object)
{
return object == null;
}

/**
* * 判断一个对象是否非空
*
* @param object Object
* @return true:非空 false:空
*/
public static boolean isNotNull(Object object)
{
return !isNull(object);
}

/**
* * 判断一个对象是否是数组类型(Java基本型别的数组)
*
* @param object 对象
* @return true:是数组 false:不是数组
*/
public static boolean isArray(Object object)
{
return isNotNull(object) && object.getClass().isArray();
}

/**
* 去空格
*/
public static String trim(String str)
{
return (str == null ? "" : str.trim());
}

/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @return 结果
*/
public static String substring(final String str, int start)
{
if (str == null)
{
return NULLSTR;
}

if (start < 0)
{
start = str.length() + start;
}

if (start < 0)
{
start = 0;
}
if (start > str.length())
{
return NULLSTR;
}

return str.substring(start);
}

/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @param end 结束
* @return 结果
*/
public static String substring(final String str, int start, int end)
{
if (str == null)
{
return NULLSTR;
}

if (end < 0)
{
end = str.length() + end;
}
if (start < 0)
{
start = str.length() + start;
}

if (end > str.length())
{
end = str.length();
}

if (start > end)
{
return NULLSTR;
}

if (start < 0)
{
start = 0;
}
if (end < 0)
{
end = 0;
}

return str.substring(start, end);
}

/**
* 判断是否为空,并且不是空白字符
*
* @param str 要判断的value
* @return 结果
*/
public static boolean hasText(String str)
{
return (str != null && !str.isEmpty() && containsText(str));
}

private static boolean containsText(CharSequence str)
{
int strLen = str.length();
for (int i = 0; i < strLen; i++)
{
if (!Character.isWhitespace(str.charAt(i)))
{
return true;
}
}
return false;
}

/**
* 格式化文本, {} 表示占位符<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* 例:<br>
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param params 参数值
* @return 格式化后的文本
*/
public static String format(String template, Object... params)
{
if (isEmpty(params) || isEmpty(template))
{
return template;
}
return StrFormatter.format(template, params);
}

/**
* 是否为http(s)://开头
*
* @param link 链接
* @return 结果
*/
public static boolean ishttp(String link)
{
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
}

/**
* 字符串转set
*
* @param str 字符串
* @param sep 分隔符
* @return set集合
*/
public static final Set<String> str2Set(String str, String sep)
{
return new HashSet<String>(str2List(str, sep, true, false));
}

/**
* 字符串转list
*
* @param str 字符串
* @param sep 分隔符
* @param filterBlank 过滤纯空白
* @param trim 去掉首尾空白
* @return list集合
*/
public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim)
{
List<String> list = new ArrayList<String>();
if (StringUtils.isEmpty(str))
{
return list;
}

// 过滤空白字符串
if (filterBlank && StringUtils.isBlank(str))
{
return list;
}
String[] split = str.split(sep);
for (String string : split)
{
if (filterBlank && StringUtils.isBlank(string))
{
continue;
}
if (trim)
{
string = string.trim();
}
list.add(string);
}

return list;
}

/**
* 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
*
* @param collection 给定的集合
* @param array 给定的数组
* @return boolean 结果
*/
public static boolean containsAny(Collection<String> collection, String... array)
{
if (isEmpty(collection) || isEmpty(array))
{
return false;
}
else
{
for (String str : array)
{
if (collection.contains(str))
{
return true;
}
}
return false;
}
}

/**
* 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
*
* @param cs 指定字符串
* @param searchCharSequences 需要检查的字符串数组
* @return 是否包含任意一个字符串
*/
public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences)
{
if (isEmpty(cs) || isEmpty(searchCharSequences))
{
return false;
}
for (CharSequence testStr : searchCharSequences)
{
if (containsIgnoreCase(cs, testStr))
{
return true;
}
}
return false;
}

/**
* 驼峰转下划线命名
*/
public static String toUnderScoreCase(String str)
{
if (str == null)
{
return null;
}
StringBuilder sb = new StringBuilder();
// 前置字符是否大写
boolean preCharIsUpperCase = true;
// 当前字符是否大写
boolean curreCharIsUpperCase = true;
// 下一字符是否大写
boolean nexteCharIsUpperCase = true;
for (int i = 0; i < str.length(); i++)
{
char c = str.charAt(i);
if (i > 0)
{
preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
}
else
{
preCharIsUpperCase = false;
}

curreCharIsUpperCase = Character.isUpperCase(c);

if (i < (str.length() - 1))
{
nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
}

if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase)
{
sb.append(SEPARATOR);
}
else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase)
{
sb.append(SEPARATOR);
}
sb.append(Character.toLowerCase(c));
}

return sb.toString();
}

/**
* 是否包含字符串
*
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inStringIgnoreCase(String str, String... strs)
{
if (str != null && strs != null)
{
for (String s : strs)
{
if (str.equalsIgnoreCase(trim(s)))
{
return true;
}
}
}
return false;
}

/**
* 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
*
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String convertToCamelCase(String name)
{
StringBuilder result = new StringBuilder();
// 快速检查
if (name == null || name.isEmpty())
{
// 没必要转换
return "";
}
else if (!name.contains("_"))
{
// 不含下划线,仅将首字母大写
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
// 用下划线将原始字符串分割
String[] camels = name.split("_");
for (String camel : camels)
{
// 跳过原始字符串中开头、结尾的下换线或双重下划线
if (camel.isEmpty())
{
continue;
}
// 首字母大写
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
return result.toString();
}

/**
* 驼峰式命名法
* 例如:user_name->userName
*/
public static String toCamelCase(String s)
{
if (s == null)
{
return null;
}
if (s.indexOf(SEPARATOR) == -1)
{
return s;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++)
{
char c = s.charAt(i);

if (c == SEPARATOR)
{
upperCase = true;
}
else if (upperCase)
{
sb.append(Character.toUpperCase(c));
upperCase = false;
}
else
{
sb.append(c);
}
}
return sb.toString();
}

/**
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matches(String str, List<String> strs)
{
if (isEmpty(str) || isEmpty(strs))
{
return false;
}
for (String pattern : strs)
{
if (isMatch(pattern, str))
{
return true;
}
}
return false;
}

/**
* 判断url是否与规则配置:
* ? 表示单个字符;
* * 表示一层路径内的任意字符串,不可跨层级;
* ** 表示任意层路径;
*
* @param pattern 匹配规则
* @param url 需要匹配的url
* @return
*/
public static boolean isMatch(String pattern, String url)
{
AntPathMatcher matcher = new AntPathMatcher();
return matcher.match(pattern, url);
}

@SuppressWarnings("unchecked")
public static <T> T cast(Object obj)
{
return (T) obj;
}

/**
* 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
*
* @param num 数字对象
* @param size 字符串指定长度
* @return 返回数字的字符串格式,该字符串为指定长度。
*/
public static final String padl(final Number num, final int size)
{
return padl(num.toString(), size, '0');
}

/**
* 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
*
* @param s 原始字符串
* @param size 字符串指定长度
* @param c 用于补齐的字符
* @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
*/
public static final String padl(final String s, final int size, final char c)
{
final StringBuilder sb = new StringBuilder(size);
if (s != null)
{
final int len = s.length();
if (s.length() <= size)
{
for (int i = size - len; i > 0; i--)
{
sb.append(c);
}
sb.append(s);
}
else
{
return s.substring(len - size, len);
}
}
else
{
for (int i = size; i > 0; i--)
{
sb.append(c);
}
}
return sb.toString();
}
}

+ 99
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java 查看文件

@@ -0,0 +1,99 @@
package com.ruoyi.common.utils;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 线程相关工具类.
*
* @author ruoyi
*/
public class Threads
{
private static final Logger logger = LoggerFactory.getLogger(Threads.class);

/**
* sleep等待,单位为毫秒
*/
public static void sleep(long milliseconds)
{
try
{
Thread.sleep(milliseconds);
}
catch (InterruptedException e)
{
return;
}
}

/**
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍然超時,則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
public static void shutdownAndAwaitTermination(ExecutorService pool)
{
if (pool != null && !pool.isShutdown())
{
pool.shutdown();
try
{
if (!pool.awaitTermination(120, TimeUnit.SECONDS))
{
pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS))
{
logger.info("Pool did not terminate");
}
}
}
catch (InterruptedException ie)
{
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t)
{
if (t == null && r instanceof Future<?>)
{
try
{
Future<?> future = (Future<?>) r;
if (future.isDone())
{
future.get();
}
}
catch (CancellationException ce)
{
t = ce;
}
catch (ExecutionException ee)
{
t = ee.getCause();
}
catch (InterruptedException ie)
{
Thread.currentThread().interrupt();
}
}
if (t != null)
{
logger.error(t.getMessage(), t);
}
}
}

+ 110
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java 查看文件

@@ -0,0 +1,110 @@
package com.ruoyi.common.utils.bean;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Bean 工具类
*
* @author ruoyi
*/
public class BeanUtils extends org.springframework.beans.BeanUtils
{
/** Bean方法名中属性名开始的下标 */
private static final int BEAN_METHOD_PROP_INDEX = 3;

/** * 匹配getter方法的正则表达式 */
private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)");

/** * 匹配setter方法的正则表达式 */
private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)");

/**
* Bean属性复制工具方法。
*
* @param dest 目标对象
* @param src 源对象
*/
public static void copyBeanProp(Object dest, Object src)
{
try
{
copyProperties(src, dest);
}
catch (Exception e)
{
e.printStackTrace();
}
}

/**
* 获取对象的setter方法。
*
* @param obj 对象
* @return 对象的setter方法列表
*/
public static List<Method> getSetterMethods(Object obj)
{
// setter方法列表
List<Method> setterMethods = new ArrayList<Method>();

// 获取所有方法
Method[] methods = obj.getClass().getMethods();

// 查找setter方法

for (Method method : methods)
{
Matcher m = SET_PATTERN.matcher(method.getName());
if (m.matches() && (method.getParameterTypes().length == 1))
{
setterMethods.add(method);
}
}
// 返回setter方法列表
return setterMethods;
}

/**
* 获取对象的getter方法。
*
* @param obj 对象
* @return 对象的getter方法列表
*/

public static List<Method> getGetterMethods(Object obj)
{
// getter方法列表
List<Method> getterMethods = new ArrayList<Method>();
// 获取所有方法
Method[] methods = obj.getClass().getMethods();
// 查找getter方法
for (Method method : methods)
{
Matcher m = GET_PATTERN.matcher(method.getName());
if (m.matches() && (method.getParameterTypes().length == 0))
{
getterMethods.add(method);
}
}
// 返回getter方法列表
return getterMethods;
}

/**
* 检查Bean方法名中的属性名是否相等。<br>
* 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。
*
* @param m1 方法名1
* @param m2 方法名2
* @return 属性名一样返回true,否则返回false
*/

public static boolean isMethodPropEquals(String m1, String m2)
{
return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX));
}
}

+ 24
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java 查看文件

@@ -0,0 +1,24 @@
package com.ruoyi.common.utils.bean;

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;

/**
* bean对象属性验证
*
* @author ruoyi
*/
public class BeanValidators
{
public static void validateWithException(Validator validator, Object object, Class<?>... groups)
throws ConstraintViolationException
{
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty())
{
throw new ConstraintViolationException(constraintViolations);
}
}
}

+ 76
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java 查看文件

@@ -0,0 +1,76 @@
package com.ruoyi.common.utils.file;

import java.io.File;
import org.apache.commons.lang3.StringUtils;

/**
* 文件类型工具类
*
* @author ruoyi
*/
public class FileTypeUtils
{
/**
* 获取文件类型
* <p>
* 例如: ruoyi.txt, 返回: txt
*
* @param file 文件名
* @return 后缀(不含".")
*/
public static String getFileType(File file)
{
if (null == file)
{
return StringUtils.EMPTY;
}
return getFileType(file.getName());
}

/**
* 获取文件类型
* <p>
* 例如: ruoyi.txt, 返回: txt
*
* @param fileName 文件名
* @return 后缀(不含".")
*/
public static String getFileType(String fileName)
{
int separatorIndex = fileName.lastIndexOf(".");
if (separatorIndex < 0)
{
return "";
}
return fileName.substring(separatorIndex + 1).toLowerCase();
}

/**
* 获取文件类型
*
* @param photoByte 文件字节码
* @return 后缀(不含".")
*/
public static String getFileExtendName(byte[] photoByte)
{
String strFileExtendName = "JPG";
if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
{
strFileExtendName = "GIF";
}
else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
{
strFileExtendName = "JPG";
}
else if ((photoByte[0] == 66) && (photoByte[1] == 77))
{
strFileExtendName = "BMP";
}
else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
{
strFileExtendName = "PNG";
}
return strFileExtendName;
}
}

+ 232
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java 查看文件

@@ -0,0 +1,232 @@
package com.ruoyi.common.utils.file;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Objects;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException;
import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.Seq;

/**
* 文件上传工具类
*
* @author ruoyi
*/
public class FileUploadUtils
{
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

/**
* 默认的文件名最大长度 100
*/
public static final int DEFAULT_FILE_NAME_LENGTH = 100;

/**
* 默认上传的地址
*/
private static String defaultBaseDir = RuoYiConfig.getProfile();

public static void setDefaultBaseDir(String defaultBaseDir)
{
FileUploadUtils.defaultBaseDir = defaultBaseDir;
}

public static String getDefaultBaseDir()
{
return defaultBaseDir;
}

/**
* 以默认配置进行文件上传
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public static final String upload(MultipartFile file) throws IOException
{
try
{
return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}

/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upload(String baseDir, MultipartFile file) throws IOException
{
try
{
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}

/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}

assertAllowed(file, allowedExtension);

String fileName = extractFilename(file);

String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}

/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}

public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
{
File desc = new File(uploadDir + File.separator + fileName);

if (!desc.exists())
{
if (!desc.getParentFile().exists())
{
desc.getParentFile().mkdirs();
}
}
return desc;
}

public static final String getPathFileName(String uploadDir, String fileName) throws IOException
{
int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
}

/**
* 文件大小校验
*
* @param file 上传的文件
* @return
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws InvalidExtensionException
*/
public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, InvalidExtensionException
{
long size = file.getSize();
if (size > DEFAULT_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}

String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
{
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
{
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
{
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
{
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
{
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
fileName);
}
else
{
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}

/**
* 判断MIME类型是否是允许的MIME类型
*
* @param extension
* @param allowedExtension
* @return
*/
public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
{
for (String str : allowedExtension)
{
if (str.equalsIgnoreCase(extension))
{
return true;
}
}
return false;
}

/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static final String getExtension(MultipartFile file)
{
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension))
{
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
}

+ 291
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java 查看文件

@@ -0,0 +1,291 @@
package com.ruoyi.common.utils.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import org.apache.commons.io.FilenameUtils;

/**
* 文件处理工具类
*
* @author ruoyi
*/
public class FileUtils
{
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";

/**
* 输出指定文件的byte数组
*
* @param filePath 文件路径
* @param os 输出流
* @return
*/
public static void writeBytes(String filePath, OutputStream os) throws IOException
{
FileInputStream fis = null;
try
{
File file = new File(filePath);
if (!file.exists())
{
throw new FileNotFoundException(filePath);
}
fis = new FileInputStream(file);
byte[] b = new byte[1024];
int length;
while ((length = fis.read(b)) > 0)
{
os.write(b, 0, length);
}
}
catch (IOException e)
{
throw e;
}
finally
{
IOUtils.close(os);
IOUtils.close(fis);
}
}

/**
* 写数据到文件中
*
* @param data 数据
* @return 目标文件
* @throws IOException IO异常
*/
public static String writeImportBytes(byte[] data) throws IOException
{
return writeBytes(data, RuoYiConfig.getImportPath());
}

/**
* 写数据到文件中
*
* @param data 数据
* @param uploadDir 目标文件
* @return 目标文件
* @throws IOException IO异常
*/
public static String writeBytes(byte[] data, String uploadDir) throws IOException
{
FileOutputStream fos = null;
String pathName = "";
try
{
String extension = getFileExtendName(data);
pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
fos = new FileOutputStream(file);
fos.write(data);
}
finally
{
IOUtils.close(fos);
}
return FileUploadUtils.getPathFileName(uploadDir, pathName);
}

/**
* 删除文件
*
* @param filePath 文件
* @return
*/
public static boolean deleteFile(String filePath)
{
boolean flag = false;
File file = new File(filePath);
// 路径为文件且不为空则进行删除
if (file.isFile() && file.exists())
{
flag = file.delete();
}
return flag;
}

/**
* 文件名称验证
*
* @param filename 文件名称
* @return true 正常 false 非法
*/
public static boolean isValidFilename(String filename)
{
return filename.matches(FILENAME_PATTERN);
}

/**
* 检查文件是否可下载
*
* @param resource 需要下载的文件
* @return true 正常 false 非法
*/
public static boolean checkAllowDownload(String resource)
{
// 禁止目录上跳级别
if (StringUtils.contains(resource, ".."))
{
return false;
}

// 检查允许下载的文件规则
if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
{
return true;
}

// 不在允许下载的文件规则
return false;
}

/**
* 下载文件名重新编码
*
* @param request 请求对象
* @param fileName 文件名
* @return 编码后的文件名
*/
public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
{
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE"))
{
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
else if (agent.contains("Firefox"))
{
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
}
else if (agent.contains("Chrome"))
{
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
else
{
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}

/**
* 下载文件名重新编码
*
* @param response 响应对象
* @param realFileName 真实文件名
*/
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
{
String percentEncodedFileName = percentEncode(realFileName);

StringBuilder contentDispositionValue = new StringBuilder();
contentDispositionValue.append("attachment; filename=")
.append(percentEncodedFileName)
.append(";")
.append("filename*=")
.append("utf-8''")
.append(percentEncodedFileName);

response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);
}

/**
* 百分号编码工具方法
*
* @param s 需要百分号编码的字符串
* @return 百分号编码后的字符串
*/
public static String percentEncode(String s) throws UnsupportedEncodingException
{
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
return encode.replaceAll("\\+", "%20");
}

/**
* 获取图像后缀
*
* @param photoByte 图像数据
* @return 后缀名
*/
public static String getFileExtendName(byte[] photoByte)
{
String strFileExtendName = "jpg";
if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
{
strFileExtendName = "gif";
}
else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
{
strFileExtendName = "jpg";
}
else if ((photoByte[0] == 66) && (photoByte[1] == 77))
{
strFileExtendName = "bmp";
}
else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
{
strFileExtendName = "png";
}
return strFileExtendName;
}

/**
* 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png
*
* @param fileName 路径名称
* @return 没有文件路径的名称
*/
public static String getName(String fileName)
{
if (fileName == null)
{
return null;
}
int lastUnixPos = fileName.lastIndexOf('/');
int lastWindowsPos = fileName.lastIndexOf('\\');
int index = Math.max(lastUnixPos, lastWindowsPos);
return fileName.substring(index + 1);
}

/**
* 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi
*
* @param fileName 路径名称
* @return 没有文件路径和后缀的名称
*/
public static String getNameNotSuffix(String fileName)
{
if (fileName == null)
{
return null;
}
String baseName = FilenameUtils.getBaseName(fileName);
return baseName;
}
}

+ 99
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java 查看文件

@@ -0,0 +1,99 @@
package com.ruoyi.common.utils.file;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;

/**
* 图片处理工具类
*
* @author ruoyi
*/
public class ImageUtils
{
private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);

public static byte[] getImage(String imagePath)
{
InputStream is = getFile(imagePath);
try
{
return IOUtils.toByteArray(is);
}
catch (Exception e)
{
log.error("图片加载异常 {}", e);
return null;
}
finally
{
IOUtils.closeQuietly(is);
}
}

public static InputStream getFile(String imagePath)
{
try
{
byte[] result = readFile(imagePath);
result = Arrays.copyOf(result, result.length);
return new ByteArrayInputStream(result);
}
catch (Exception e)
{
log.error("获取图片异常 {}", e);
}
return null;
}

/**
* 读取文件为字节数据
*
* @param url 地址
* @return 字节数据
*/
public static byte[] readFile(String url)
{
InputStream in = null;
try
{
if (url.startsWith("http"))
{
// 网络地址
URL urlObj = new URL(url);
URLConnection urlConnection = urlObj.openConnection();
urlConnection.setConnectTimeout(30 * 1000);
urlConnection.setReadTimeout(60 * 1000);
urlConnection.setDoInput(true);
in = urlConnection.getInputStream();
}
else
{
// 本机地址
String localPath = RuoYiConfig.getProfile();
String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX);
in = new FileInputStream(downloadPath);
}
return IOUtils.toByteArray(in);
}
catch (Exception e)
{
log.error("获取文件路径异常 {}", e);
return null;
}
finally
{
IOUtils.closeQuietly(in);
}
}
}

+ 59
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java 查看文件

@@ -0,0 +1,59 @@
package com.ruoyi.common.utils.file;

/**
* 媒体类型工具类
*
* @author ruoyi
*/
public class MimeTypeUtils
{
public static final String IMAGE_PNG = "image/png";

public static final String IMAGE_JPG = "image/jpg";

public static final String IMAGE_JPEG = "image/jpeg";

public static final String IMAGE_BMP = "image/bmp";

public static final String IMAGE_GIF = "image/gif";
public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };

public static final String[] FLASH_EXTENSION = { "swf", "flv" };

public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb" };

public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };

public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf
"pdf" };

public static String getExtension(String prefix)
{
switch (prefix)
{
case IMAGE_PNG:
return "png";
case IMAGE_JPG:
return "jpg";
case IMAGE_JPEG:
return "jpeg";
case IMAGE_BMP:
return "bmp";
case IMAGE_GIF:
return "gif";
default:
return "";
}
}
}

+ 167
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java 查看文件

@@ -0,0 +1,167 @@
package com.ruoyi.common.utils.html;

import com.ruoyi.common.utils.StringUtils;

/**
* 转义和反转义工具类
*
* @author ruoyi
*/
public class EscapeUtil
{
public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";

private static final char[][] TEXT = new char[64][];

static
{
for (int i = 0; i < 64; i++)
{
TEXT[i] = new char[] { (char) i };
}

// special HTML characters
TEXT['\''] = "&#039;".toCharArray(); // 单引号
TEXT['"'] = "&#34;".toCharArray(); // 双引号
TEXT['&'] = "&#38;".toCharArray(); // &符
TEXT['<'] = "&#60;".toCharArray(); // 小于号
TEXT['>'] = "&#62;".toCharArray(); // 大于号
}

/**
* 转义文本中的HTML字符为安全的字符
*
* @param text 被转义的文本
* @return 转义后的文本
*/
public static String escape(String text)
{
return encode(text);
}

/**
* 还原被转义的HTML特殊字符
*
* @param content 包含转义符的HTML内容
* @return 转换后的字符串
*/
public static String unescape(String content)
{
return decode(content);
}

/**
* 清除所有HTML标签,但是不删除标签内的内容
*
* @param content 文本
* @return 清除标签后的文本
*/
public static String clean(String content)
{
return new HTMLFilter().filter(content);
}

/**
* Escape编码
*
* @param text 被编码的文本
* @return 编码后的字符
*/
private static String encode(String text)
{
if (StringUtils.isEmpty(text))
{
return StringUtils.EMPTY;
}

final StringBuilder tmp = new StringBuilder(text.length() * 6);
char c;
for (int i = 0; i < text.length(); i++)
{
c = text.charAt(i);
if (c < 256)
{
tmp.append("%");
if (c < 16)
{
tmp.append("0");
}
tmp.append(Integer.toString(c, 16));
}
else
{
tmp.append("%u");
if (c <= 0xfff)
{
// issue#I49JU8@Gitee
tmp.append("0");
}
tmp.append(Integer.toString(c, 16));
}
}
return tmp.toString();
}

/**
* Escape解码
*
* @param content 被转义的内容
* @return 解码后的字符串
*/
public static String decode(String content)
{
if (StringUtils.isEmpty(content))
{
return content;
}

StringBuilder tmp = new StringBuilder(content.length());
int lastPos = 0, pos = 0;
char ch;
while (lastPos < content.length())
{
pos = content.indexOf("%", lastPos);
if (pos == lastPos)
{
if (content.charAt(pos + 1) == 'u')
{
ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16);
tmp.append(ch);
lastPos = pos + 6;
}
else
{
ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16);
tmp.append(ch);
lastPos = pos + 3;
}
}
else
{
if (pos == -1)
{
tmp.append(content.substring(lastPos));
lastPos = content.length();
}
else
{
tmp.append(content.substring(lastPos, pos));
lastPos = pos;
}
}
}
return tmp.toString();
}

public static void main(String[] args)
{
String html = "<script>alert(1);</script>";
String escape = EscapeUtil.escape(html);
// String html = "<scr<script>ipt>alert(\"XSS\")</scr<script>ipt>";
// String html = "<123";
// String html = "123>";
System.out.println("clean: " + EscapeUtil.clean(html));
System.out.println("escape: " + escape);
System.out.println("unescape: " + EscapeUtil.unescape(escape));
}
}

+ 570
- 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java 查看文件

@@ -0,0 +1,570 @@
package com.ruoyi.common.utils.html;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* HTML过滤器,用于去除XSS漏洞隐患。
*
* @author ruoyi
*/
public final class HTMLFilter
{
/**
* regex flag union representing /si modifiers in php
**/
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
private static final Pattern P_END_ARROW = Pattern.compile("^>");
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_AMP = Pattern.compile("&");
private static final Pattern P_QUOTE = Pattern.compile("\"");
private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");

// @xxx could grow large... maybe use sesat's ReferenceMap
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();

/**
* set of allowed html elements, along with allowed attributes for each element
**/
private final Map<String, List<String>> vAllowed;
/**
* counts of open tags for each (allowable) html element
**/
private final Map<String, Integer> vTagCounts = new HashMap<>();

/**
* html elements which must always be self-closing (e.g. "<img />")
**/
private final String[] vSelfClosingTags;
/**
* html elements which must always have separate opening and closing tags (e.g. "<b></b>")
**/
private final String[] vNeedClosingTags;
/**
* set of disallowed html elements
**/
private final String[] vDisallowed;
/**
* attributes which should be checked for valid protocols
**/
private final String[] vProtocolAtts;
/**
* allowed protocols
**/
private final String[] vAllowedProtocols;
/**
* tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
**/
private final String[] vRemoveBlanks;
/**
* entities allowed within html markup
**/
private final String[] vAllowedEntities;
/**
* flag determining whether comments are allowed in input String.
*/
private final boolean stripComment;
private final boolean encodeQuotes;
/**
* flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "<b text </b>"
* becomes "<b> text </b>"). If set to false, unbalanced angle brackets will be html escaped.
*/
private final boolean alwaysMakeTags;

/**
* Default constructor.
*/
public HTMLFilter()
{
vAllowed = new HashMap<>();

final ArrayList<String> a_atts = new ArrayList<>();
a_atts.add("href");
a_atts.add("target");
vAllowed.put("a", a_atts);

final ArrayList<String> img_atts = new ArrayList<>();
img_atts.add("src");
img_atts.add("width");
img_atts.add("height");
img_atts.add("alt");
vAllowed.put("img", img_atts);

final ArrayList<String> no_atts = new ArrayList<>();
vAllowed.put("b", no_atts);
vAllowed.put("strong", no_atts);
vAllowed.put("i", no_atts);
vAllowed.put("em", no_atts);

vSelfClosingTags = new String[] { "img" };
vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" };
vDisallowed = new String[] {};
vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp.
vProtocolAtts = new String[] { "src", "href" };
vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" };
vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" };
stripComment = true;
encodeQuotes = true;
alwaysMakeTags = false;
}

/**
* Map-parameter configurable constructor.
*
* @param conf map containing configuration. keys match field names.
*/
@SuppressWarnings("unchecked")
public HTMLFilter(final Map<String, Object> conf)
{

assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";

vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
vDisallowed = (String[]) conf.get("vDisallowed");
vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
vProtocolAtts = (String[]) conf.get("vProtocolAtts");
vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
vAllowedEntities = (String[]) conf.get("vAllowedEntities");
stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
}

private void reset()
{
vTagCounts.clear();
}

// ---------------------------------------------------------------
// my versions of some PHP library functions
public static String chr(final int decimal)
{
return String.valueOf((char) decimal);
}

public static String htmlSpecialChars(final String s)
{
String result = s;
result = regexReplace(P_AMP, "&amp;", result);
result = regexReplace(P_QUOTE, "&quot;", result);
result = regexReplace(P_LEFT_ARROW, "&lt;", result);
result = regexReplace(P_RIGHT_ARROW, "&gt;", result);
return result;
}

// ---------------------------------------------------------------

/**
* given a user submitted input String, filter out any invalid or restricted html.
*
* @param input text (i.e. submitted by a user) than may contain html
* @return "clean" version of input, with only valid, whitelisted html elements allowed
*/
public String filter(final String input)
{
reset();
String s = input;

s = escapeComments(s);

s = balanceHTML(s);

s = checkTags(s);

s = processRemoveBlanks(s);

// s = validateEntities(s);

return s;
}

public boolean isAlwaysMakeTags()
{
return alwaysMakeTags;
}

public boolean isStripComments()
{
return stripComment;
}

private String escapeComments(final String s)
{
final Matcher m = P_COMMENTS.matcher(s);
final StringBuffer buf = new StringBuffer();
if (m.find())
{
final String match = m.group(1); // (.*?)
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
}
m.appendTail(buf);

return buf.toString();
}

private String balanceHTML(String s)
{
if (alwaysMakeTags)
{
//
// try and form html
//
s = regexReplace(P_END_ARROW, "", s);
// 不追加结束标签
s = regexReplace(P_BODY_TO_END, "<$1>", s);
s = regexReplace(P_XML_CONTENT, "$1<$2", s);

}
else
{
//
// escape stray brackets
//
s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);

//
// the last regexp causes '<>' entities to appear
// (we need to do a lookahead assertion so that the last bracket can
// be used in the next pass of the regexp)
//
s = regexReplace(P_BOTH_ARROWS, "", s);
}

return s;
}

private String checkTags(String s)
{
Matcher m = P_TAGS.matcher(s);

final StringBuffer buf = new StringBuffer();
while (m.find())
{
String replaceStr = m.group(1);
replaceStr = processTag(replaceStr);
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
}
m.appendTail(buf);

// these get tallied in processTag
// (remember to reset before subsequent calls to filter method)
final StringBuilder sBuilder = new StringBuilder(buf.toString());
for (String key : vTagCounts.keySet())
{
for (int ii = 0; ii < vTagCounts.get(key); ii++)
{
sBuilder.append("</").append(key).append(">");
}
}
s = sBuilder.toString();

return s;
}

private String processRemoveBlanks(final String s)
{
String result = s;
for (String tag : vRemoveBlanks)
{
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag))
{
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
}
result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
if (!P_REMOVE_SELF_BLANKS.containsKey(tag))
{
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
}
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
}

return result;
}

private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s)
{
Matcher m = regex_pattern.matcher(s);
return m.replaceAll(replacement);
}

private String processTag(final String s)
{
// ending tags
Matcher m = P_END_TAG.matcher(s);
if (m.find())
{
final String name = m.group(1).toLowerCase();
if (allowed(name))
{
if (!inArray(name, vSelfClosingTags))
{
if (vTagCounts.containsKey(name))
{
vTagCounts.put(name, vTagCounts.get(name) - 1);
return "</" + name + ">";
}
}
}
}

// starting tags
m = P_START_TAG.matcher(s);
if (m.find())
{
final String name = m.group(1).toLowerCase();
final String body = m.group(2);
String ending = m.group(3);

// debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
if (allowed(name))
{
final StringBuilder params = new StringBuilder();

final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
final List<String> paramNames = new ArrayList<>();
final List<String> paramValues = new ArrayList<>();
while (m2.find())
{
paramNames.add(m2.group(1)); // ([a-z0-9]+)
paramValues.add(m2.group(3)); // (.*?)
}
while (m3.find())
{
paramNames.add(m3.group(1)); // ([a-z0-9]+)
paramValues.add(m3.group(3)); // ([^\"\\s']+)
}

String paramName, paramValue;
for (int ii = 0; ii < paramNames.size(); ii++)
{
paramName = paramNames.get(ii).toLowerCase();
paramValue = paramValues.get(ii);

// debug( "paramName='" + paramName + "'" );
// debug( "paramValue='" + paramValue + "'" );
// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );

if (allowedAttribute(name, paramName))
{
if (inArray(paramName, vProtocolAtts))
{
paramValue = processParamProtocol(paramValue);
}
params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\"");
}
}

if (inArray(name, vSelfClosingTags))
{
ending = " /";
}

if (inArray(name, vNeedClosingTags))
{
ending = "";
}

if (ending == null || ending.length() < 1)
{
if (vTagCounts.containsKey(name))
{
vTagCounts.put(name, vTagCounts.get(name) + 1);
}
else
{
vTagCounts.put(name, 1);
}
}
else
{
ending = " /";
}
return "<" + name + params + ending + ">";
}
else
{
return "";
}
}

// comments
m = P_COMMENT.matcher(s);
if (!stripComment && m.find())
{
return "<" + m.group() + ">";
}

return "";
}

private String processParamProtocol(String s)
{
s = decodeEntities(s);
final Matcher m = P_PROTOCOL.matcher(s);
if (m.find())
{
final String protocol = m.group(1);
if (!inArray(protocol, vAllowedProtocols))
{
// bad protocol, turn into local anchor link instead
s = "#" + s.substring(protocol.length() + 1);
if (s.startsWith("#//"))
{
s = "#" + s.substring(3);
}
}
}

return s;
}

private String decodeEntities(String s)
{
StringBuffer buf = new StringBuffer();

Matcher m = P_ENTITY.matcher(s);
while (m.find())
{
final String match = m.group(1);
final int decimal = Integer.decode(match).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();

buf = new StringBuffer();
m = P_ENTITY_UNICODE.matcher(s);
while (m.find())
{
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();

buf = new StringBuffer();
m = P_ENCODE.matcher(s);
while (m.find())
{
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();

s = validateEntities(s);
return s;
}

private String validateEntities(final String s)
{
StringBuffer buf = new StringBuffer();

// validate entities throughout the string
Matcher m = P_VALID_ENTITIES.matcher(s);
while (m.find())
{
final String one = m.group(1); // ([^&;]*)
final String two = m.group(2); // (?=(;|&|$))
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
}
m.appendTail(buf);

return encodeQuotes(buf.toString());
}

private String encodeQuotes(final String s)
{
if (encodeQuotes)
{
StringBuffer buf = new StringBuffer();
Matcher m = P_VALID_QUOTES.matcher(s);
while (m.find())
{
final String one = m.group(1); // (>|^)
final String two = m.group(2); // ([^<]+?)
final String three = m.group(3); // (<|$)
// 不替换双引号为&quot;,防止json格式无效 regexReplace(P_QUOTE, "&quot;", two)
m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three));
}
m.appendTail(buf);
return buf.toString();
}
else
{
return s;
}
}

private String checkEntity(final String preamble, final String term)
{

return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&amp;" + preamble;
}

private boolean isValidEntity(final String entity)
{
return inArray(entity, vAllowedEntities);
}

private static boolean inArray(final String s, final String[] array)
{
for (String item : array)
{
if (item != null && item.equals(s))
{
return true;
}
}
return false;
}

private boolean allowed(final String name)
{
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
}

private boolean allowedAttribute(final String name, final String paramName)
{
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
}
}

部分文件因文件數量過多而無法顯示

Loading…
取消
儲存