ly 4 years ago
commit
1f283586b1
100 changed files with 14037 additions and 0 deletions
  1. 43 0
      master/.gitignore
  2. 20 0
      master/LICENSE
  3. 82 0
      master/README.md
  4. 12 0
      master/bin/clean.bat
  5. 12 0
      master/bin/package.bat
  6. 14 0
      master/bin/run-tomcat.bat
  7. BIN
      master/doc/若依环境使用手册.docx
  8. 438 0
      master/pom.xml
  9. 86 0
      master/ry.sh
  10. 189 0
      master/sql/quartz.sql
  11. 1048 0
      master/sql/ry_20201021.sql
  12. 33 0
      master/src/main/java/com/ruoyi/RuoYiApplication.java
  13. 18 0
      master/src/main/java/com/ruoyi/RuoYiServletInitializer.java
  14. 131 0
      master/src/main/java/com/ruoyi/common/constant/Constants.java
  15. 103 0
      master/src/main/java/com/ruoyi/common/constant/GenConstants.java
  16. 89 0
      master/src/main/java/com/ruoyi/common/constant/HttpStatus.java
  17. 50 0
      master/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java
  18. 60 0
      master/src/main/java/com/ruoyi/common/constant/UserConstants.java
  19. 484 0
      master/src/main/java/com/ruoyi/common/core/lang/UUID.java
  20. 86 0
      master/src/main/java/com/ruoyi/common/core/text/CharsetKit.java
  21. 999 0
      master/src/main/java/com/ruoyi/common/core/text/Convert.java
  22. 92 0
      master/src/main/java/com/ruoyi/common/core/text/StrFormatter.java
  23. 36 0
      master/src/main/java/com/ruoyi/common/enums/HttpMethod.java
  24. 30 0
      master/src/main/java/com/ruoyi/common/enums/UserStatus.java
  25. 97 0
      master/src/main/java/com/ruoyi/common/exception/BaseException.java
  26. 43 0
      master/src/main/java/com/ruoyi/common/exception/CustomException.java
  27. 15 0
      master/src/main/java/com/ruoyi/common/exception/DemoModeException.java
  28. 26 0
      master/src/main/java/com/ruoyi/common/exception/UtilException.java
  29. 19 0
      master/src/main/java/com/ruoyi/common/exception/file/FileException.java
  30. 16 0
      master/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java
  31. 16 0
      master/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java
  32. 71 0
      master/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java
  33. 34 0
      master/src/main/java/com/ruoyi/common/exception/job/TaskException.java
  34. 16 0
      master/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java
  35. 16 0
      master/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java
  36. 18 0
      master/src/main/java/com/ruoyi/common/exception/user/UserException.java
  37. 16 0
      master/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java
  38. 52 0
      master/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java
  39. 72 0
      master/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java
  40. 97 0
      master/src/main/java/com/ruoyi/common/filter/XssFilter.java
  41. 105 0
      master/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java
  42. 32 0
      master/src/main/java/com/ruoyi/common/sendEmail/IMailService.java
  43. 130 0
      master/src/main/java/com/ruoyi/common/sendEmail/IMailServiceImpl.java
  44. 150 0
      master/src/main/java/com/ruoyi/common/thread/SendEmailThead.java
  45. 37 0
      master/src/main/java/com/ruoyi/common/thread/SpecDevYlgdThread.java
  46. 47 0
      master/src/main/java/com/ruoyi/common/thread/SpecDevYlrqThread.java
  47. 59 0
      master/src/main/java/com/ruoyi/common/thread/SpecOverLifeThead.java
  48. 26 0
      master/src/main/java/com/ruoyi/common/thread/TaskThread.java
  49. 114 0
      master/src/main/java/com/ruoyi/common/utils/Arith.java
  50. 230 0
      master/src/main/java/com/ruoyi/common/utils/DateUtils.java
  51. 183 0
      master/src/main/java/com/ruoyi/common/utils/DictUtils.java
  52. 40 0
      master/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java
  53. 51 0
      master/src/main/java/com/ruoyi/common/utils/IdUtils.java
  54. 18 0
      master/src/main/java/com/ruoyi/common/utils/LogUtils.java
  55. 26 0
      master/src/main/java/com/ruoyi/common/utils/MessageUtils.java
  56. 90 0
      master/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
  57. 159 0
      master/src/main/java/com/ruoyi/common/utils/ServletUtils.java
  58. 43 0
      master/src/main/java/com/ruoyi/common/utils/SpringContextUtils.java
  59. 459 0
      master/src/main/java/com/ruoyi/common/utils/StringUtils.java
  60. 99 0
      master/src/main/java/com/ruoyi/common/utils/Threads.java
  61. 228 0
      master/src/main/java/com/ruoyi/common/utils/VerifyCodeUtils.java
  62. 110 0
      master/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java
  63. 364 0
      master/src/main/java/com/ruoyi/common/utils/document/ChartUtil.java
  64. 59 0
      master/src/main/java/com/ruoyi/common/utils/document/DocumentHandler.java
  65. 113 0
      master/src/main/java/com/ruoyi/common/utils/document/PDFTemplateUtil.java
  66. 35 0
      master/src/main/java/com/ruoyi/common/utils/export/EncodeTools.java
  67. 20 0
      master/src/main/java/com/ruoyi/common/utils/export/JxlsTemplate.java
  68. 38 0
      master/src/main/java/com/ruoyi/common/utils/export/JxlsTemplateEnum.java
  69. 74 0
      master/src/main/java/com/ruoyi/common/utils/export/JxlsView.java
  70. 307 0
      master/src/main/java/com/ruoyi/common/utils/file/ExcelUtils.java
  71. 229 0
      master/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java
  72. 142 0
      master/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
  73. 55 0
      master/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
  74. 155 0
      master/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java
  75. 570 0
      master/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java
  76. 51 0
      master/src/main/java/com/ruoyi/common/utils/http/HttpContextUtils.java
  77. 55 0
      master/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java
  78. 262 0
      master/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java
  79. 56 0
      master/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java
  80. 195 0
      master/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
  81. 107 0
      master/src/main/java/com/ruoyi/common/utils/job/AbstractQuartzJob.java
  82. 63 0
      master/src/main/java/com/ruoyi/common/utils/job/CronUtils.java
  83. 182 0
      master/src/main/java/com/ruoyi/common/utils/job/JobInvokeUtil.java
  84. 21 0
      master/src/main/java/com/ruoyi/common/utils/job/QuartzDisallowConcurrentExecution.java
  85. 19 0
      master/src/main/java/com/ruoyi/common/utils/job/QuartzJobExecution.java
  86. 113 0
      master/src/main/java/com/ruoyi/common/utils/job/ScheduleUtils.java
  87. 89 0
      master/src/main/java/com/ruoyi/common/utils/poi/CustomXWPFDocument.java
  88. 1052 0
      master/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
  89. 39 0
      master/src/main/java/com/ruoyi/common/utils/poi/POIConstants.java
  90. 30 0
      master/src/main/java/com/ruoyi/common/utils/poi/PoiWordTitle.java
  91. 777 0
      master/src/main/java/com/ruoyi/common/utils/poi/PoiWordTools.java
  92. 406 0
      master/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java
  93. 291 0
      master/src/main/java/com/ruoyi/common/utils/sign/Base64.java
  94. 66 0
      master/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java
  95. 146 0
      master/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java
  96. 37 0
      master/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java
  97. 122 0
      master/src/main/java/com/ruoyi/config/ActivitiConfig.java
  98. 34 0
      master/src/main/java/com/ruoyi/config/MyWebAppConfiguration.java
  99. 223 0
      master/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java
  100. 225 0
      master/src/main/java/com/ruoyi/framework/aspectj/DataScopePlantAspect.java

+ 43 - 0
master/.gitignore

@@ -0,0 +1,43 @@
+######################################################################
+# 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
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml

+ 20 - 0
master/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.

+ 82 - 0
master/README.md

@@ -0,0 +1,82 @@
+## 平台简介
+
+* 本仓库为RuoYi-Vue的Oracle版本,保持同步更新。
+* 配套前端代码地址[RuoYi-Vue-UI](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。
+* 前端采用Vue、Element UI。
+* 后端采用Spring Boot、Spring Security、Redis & Jwt。
+* 权限认证使用Jwt,支持多终端认证系统。
+* 支持加载动态权限菜单,多方式轻松权限控制。
+* 高效率开发,使用代码生成器可以一键生成前后端代码。
+* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
+* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)  
+* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)  
+
+## 内置功能
+
+1.  用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+2.  部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+3.  岗位管理:配置系统用户所属担任职务。
+4.  菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+5.  角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+6.  字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+7.  参数管理:对系统动态配置常用参数。
+8.  通知公告:系统通知公告信息发布维护。
+9.  操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+10. 登录日志:系统登录日志记录查询包含登录异常。
+11. 在线用户:当前系统中活跃用户状态监控。
+12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
+13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
+14. 系统接口:根据业务代码自动生成相关的api接口文档。
+15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
+16. 在线构建器:拖动表单元素生成相应的HTML代码。
+17. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
+
+## 在线体验
+
+- admin/admin123  
+- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
+
+演示地址:http://vue.ruoyi.vip  
+文档地址:http://doc.ruoyi.vip
+
+## 演示图
+
+<table>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td>
+    </tr>
+	<tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
+    </tr>	 
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
+    </tr>
+	<tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
+    </tr>
+	<tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-6d73c2140ce694e3de4c05035fdc1868d4c.png"/></td>
+    </tr>
+</table>
+
+
+## 若依前后端分离交流群
+
+QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) 点击按钮入群。

+ 12 - 0
master/bin/clean.bat

@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [ÐÅÏ¢] ÇåÀíÉú³É·¾¶¡£
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean
+
+pause

+ 12 - 0
master/bin/package.bat

@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 打包Web工程,生成war/jar包文件。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean package -Dmaven.test.skip=true
+
+pause

+ 14 - 0
master/bin/run-tomcat.bat

@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [ÐÅÏ¢] ʹÓà Spring Boot Tomcat ÔËÐÐ Web ¹¤³Ì¡£
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+title %cd%
+set MAVEN_OPTS=%MAVEN_OPTS% -Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+call mvn clean spring-boot:run -Dmaven.test.skip=true -U
+
+pause

BIN
master/doc/若依环境使用手册.docx


+ 438 - 0
master/pom.xml

@@ -0,0 +1,438 @@
+<?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.cpms</groupId>
+	<artifactId>cpms</artifactId>
+	<version>3.2.0</version>
+	<packaging>war</packaging>
+
+	<name>cpms</name>
+	<description>cpms管理系统</description>
+
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>2.1.17.RELEASE</version>
+		<relativePath />
+	</parent>
+
+	<properties>
+		<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>
+		<mybatis.spring.boot.starter.version>2.1.3</mybatis.spring.boot.starter.version>
+		<pagehelper.spring.boot.starter.version>1.3.0</pagehelper.spring.boot.starter.version>
+		<fastjson.version>1.2.74</fastjson.version>
+		<druid.version>1.2.2</druid.version>
+		<commons.io.version>2.5</commons.io.version>
+		<commons.fileupload.version>1.3.3</commons.fileupload.version>
+		<bitwalker.version>1.19</bitwalker.version>
+		<jwt.version>0.9.1</jwt.version>
+		<kaptcha.version>2.3.2</kaptcha.version>
+		<swagger.version>2.9.2</swagger.version>
+		<poi.version>3.17</poi.version>
+		<oshi.version>5.2.5</oshi.version>
+        <jna.version>5.5.0</jna.version>
+		<velocity.version>1.7</velocity.version>
+		<oracle.version>11.2.0.3</oracle.version>
+		<lombok.version>1.18.4</lombok.version>
+		<mybatisplus.version>3.3.1</mybatisplus.version>
+		<jxls.version>2.4.2</jxls.version>
+		<jxls-poi.version>1.1.0</jxls-poi.version>
+	</properties>
+
+	<dependencies>
+
+		<!-- SpringBoot 核心包 -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter</artifactId>
+		</dependency>
+
+		<!-- SpringBoot 测试 -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+
+		<!-- SpringBoot 拦截器 -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-aop</artifactId>
+		</dependency>
+
+		<!-- SpringBoot Web容器 -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+
+		<!-- spring-boot-devtools -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-devtools</artifactId>
+			<optional>true</optional> <!-- 表示依赖不会传递 -->
+		</dependency>
+
+		<!-- spring security 安全认证 -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+
+		<!-- redis 缓存操作 -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-redis</artifactId>
+		</dependency>
+
+		<!-- pool 对象池 -->
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-pool2</artifactId>
+		</dependency>
+
+		<!--oracle驱动-->
+		<dependency>
+			<groupId>com.oracle</groupId>
+			<artifactId>ojdbc6</artifactId>
+			<version>${oracle.version}</version>
+		</dependency>
+
+		<!-- SpringBoot集成mybatis框架 -->
+		<dependency>
+			<groupId>org.mybatis.spring.boot</groupId>
+			<artifactId>mybatis-spring-boot-starter</artifactId>
+			<version>${mybatis.spring.boot.starter.version}</version>
+		</dependency>
+
+		<!-- pagehelper 分页插件 -->
+		<dependency>
+			<groupId>com.github.pagehelper</groupId>
+			<artifactId>pagehelper-spring-boot-starter</artifactId>
+			<version>${pagehelper.spring.boot.starter.version}</version>
+		</dependency>
+
+		<!--阿里数据库连接池 -->
+		<dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+		<!--常用工具类 -->
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>
+
+		<!--io常用工具类 -->
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>${commons.io.version}</version>
+		</dependency>
+
+		<!--文件上传工具类 -->
+		<dependency>
+			<groupId>commons-fileupload</groupId>
+			<artifactId>commons-fileupload</artifactId>
+			<version>${commons.fileupload.version}</version>
+		</dependency>
+
+		<!-- 解析客户端操作系统、浏览器等 -->
+		<dependency>
+			<groupId>eu.bitwalker</groupId>
+			<artifactId>UserAgentUtils</artifactId>
+			<version>${bitwalker.version}</version>
+		</dependency>
+
+		<!-- 阿里JSON解析器 -->
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>${fastjson.version}</version>
+		</dependency>
+
+		<!--Spring框架基本的核心工具-->
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-context-support</artifactId>
+		</dependency>
+
+        <!--Token生成与解析-->
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt</artifactId>
+			<version>${jwt.version}</version>
+		</dependency>
+
+		<!-- swagger2-->
+		<dependency>
+			<groupId>io.springfox</groupId>
+			<artifactId>springfox-swagger2</artifactId>
+			<version>${swagger.version}</version>
+			<exclusions>
+			    <exclusion>
+			        <groupId>io.swagger</groupId>
+			        <artifactId>swagger-annotations</artifactId>
+			    </exclusion>
+			    <exclusion>
+			        <groupId>io.swagger</groupId>
+			        <artifactId>swagger-models</artifactId>
+			    </exclusion>
+			</exclusions>
+		</dependency>
+
+		<!--防止进入swagger页面报类型转换错误,排除2.9.2中的引用,手动增加1.5.21版本-->
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>1.5.21</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+            <version>1.5.21</version>
+        </dependency>
+
+		<!-- swagger2-UI-->
+		<dependency>
+			<groupId>io.springfox</groupId>
+			<artifactId>springfox-swagger-ui</artifactId>
+			<version>${swagger.version}</version>
+		</dependency>
+
+        <!-- 获取系统信息 -->
+		<dependency>
+			<groupId>com.github.oshi</groupId>
+			<artifactId>oshi-core</artifactId>
+			<version>${oshi.version}</version>
+		</dependency>
+
+		<!-- excel工具 -->
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi-ooxml</artifactId>
+<!--			<version>${poi.version}</version>-->
+			<version>4.1.2</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.jxls</groupId>
+			<artifactId>jxls</artifactId>
+			<version>${jxls.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.jxls</groupId>
+			<artifactId>jxls-poi</artifactId>
+			<version>${jxls-poi.version}</version>
+		</dependency>
+
+		<!--velocity代码生成使用模板 -->
+		<dependency>
+			<groupId>org.apache.velocity</groupId>
+			<artifactId>velocity</artifactId>
+			<version>${velocity.version}</version>
+		</dependency>
+
+        <!-- 定时任务 -->
+		<dependency>
+			<groupId>org.quartz-scheduler</groupId>
+			<artifactId>quartz</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>com.mchange</groupId>
+					<artifactId>c3p0</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+
+		<!--验证码 -->
+		<dependency>
+			<groupId>com.github.penggle</groupId>
+			<artifactId>kaptcha</artifactId>
+			<version>${kaptcha.version}</version>
+			<exclusions>
+			    <exclusion>
+			        <artifactId>javax.servlet-api</artifactId>
+			        <groupId>javax.servlet</groupId>
+			    </exclusion>
+			</exclusions>
+		</dependency>
+
+		<!--lombok-->
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<version>${lombok.version}</version>
+		</dependency>
+
+		<!--mybatisplus-->
+		<dependency>
+			<groupId>com.baomidou</groupId>
+			<artifactId>mybatis-plus-boot-starter</artifactId>
+			<version>${mybatisplus.version}</version>
+			<exclusions>
+				<exclusion>
+					<groupId>com.baomidou</groupId>
+					<artifactId>mybatis-plus-generator</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-mail</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.github.ulisesbocchio</groupId>
+			<artifactId>jasypt-spring-boot-starter</artifactId>
+			<version>2.1.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi</artifactId>
+			<version>4.1.2</version>
+		</dependency>
+		<!--<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi-ooxml</artifactId>
+			<version>3.17</version>
+		</dependency>-->
+		<!--<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>ooxml-schemas</artifactId>
+			<version>1.1</version>
+		</dependency>-->
+
+		<!--审批流-->
+		<dependency>
+			<groupId>org.activiti</groupId>
+			<artifactId>activiti-engine</artifactId>
+			<version>6.0.0</version>
+			<exclusions>
+				<exclusion>
+					<groupId>org.mybatis</groupId>
+					<artifactId>mybatis</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.activiti</groupId>
+			<artifactId>activiti-spring</artifactId>
+			<version>6.0.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-test</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter</artifactId>
+			<version>RELEASE</version>
+			<scope>compile</scope>
+		</dependency>
+		<!--framework-->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-freemarker</artifactId>
+			<version>2.1.2.RELEASE</version>
+		</dependency>
+		<dependency>
+			<groupId>com.itextpdf</groupId>
+			<artifactId>itextpdf</artifactId>
+			<version>5.4.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.itextpdf.tool</groupId>
+			<artifactId>xmlworker</artifactId>
+			<version>5.4.1</version>
+		</dependency>
+		<dependency>
+			<groupId>com.itextpdf</groupId>
+			<artifactId>itext-asian</artifactId>
+			<version>5.2.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.xhtmlrenderer</groupId>
+			<artifactId>flying-saucer-pdf</artifactId>
+			<version>9.0.3</version>
+		</dependency>
+		<!--jpinyin-->
+		<dependency>
+			<groupId>com.github.stuxuhai</groupId>
+			<artifactId>jpinyin</artifactId>
+			<version>1.1.8</version>
+		</dependency>
+		<!-- jfreechart -->
+		<dependency>
+			<groupId>org.jfree</groupId>
+			<artifactId>jfreechart</artifactId>
+			<version>1.5.0</version>
+		</dependency>
+
+		<!-- pinyin4j 汉语转拼音 -->
+		<dependency>
+			<groupId>com.belerweb</groupId>
+			<artifactId>pinyin4j</artifactId>
+			<version>2.5.1</version>
+		</dependency>
+
+	</dependencies>
+
+	<build>
+		<finalName>${project.artifactId}</finalName>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<configuration>
+					<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+	<repositories>
+		<!--阿里云镜像仓库-->
+		<repository>
+			<id>public</id>
+			<name>aliyun nexus</name>
+			<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+			<releases>
+				<enabled>true</enabled>
+			</releases>
+		</repository>
+		<!--oracle驱动没有发布到中央仓库,只能从此仓库下载-->
+		<repository>
+			<id>jeecg</id>
+			<name>jeecg Repository</name>
+			<url>http://maven.jeewx.com/nexus/content/repositories/jeecg</url>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</repository>
+	</repositories>
+
+	<pluginRepositories>
+		<pluginRepository>
+			<id>public</id>
+			<name>aliyun nexus</name>
+			<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+			<releases>
+				<enabled>true</enabled>
+			</releases>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</pluginRepository>
+	</pluginRepositories>
+
+</project>

+ 86 - 0
master/ry.sh

@@ -0,0 +1,86 @@
+#!/bin/bash
+
+AppName=ruoyi.jar
+
+#JVM参数
+JVM_OPTS="-Dname=$AppName  -Duser.timezone=Asia/Shanghai -Xms512M -Xmx512M -XX:PermSize=256M -XX:MaxPermSize=512M -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps  -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
+APP_HOME=`pwd`
+LOG_PATH=$APP_HOME/logs/$AppName.log
+
+if [ "$1" = "" ];
+then
+    echo -e "\033[0;31m 未输入操作名 \033[0m  \033[0;34m {start|stop|restart|status} \033[0m"
+    exit 1
+fi
+
+if [ "$AppName" = "" ];
+then
+    echo -e "\033[0;31m 未输入应用名 \033[0m"
+    exit 1
+fi
+
+function start()
+{
+    PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
+
+	if [ x"$PID" != x"" ]; then
+	    echo "$AppName is running..."
+	else
+		nohup java -jar  $JVM_OPTS target/$AppName > /dev/null 2>&1 &
+		echo "Start $AppName success..."
+	fi
+}
+
+function stop()
+{
+    echo "Stop $AppName"
+	
+	PID=""
+	query(){
+		PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
+	}
+
+	query
+	if [ x"$PID" != x"" ]; then
+		kill -TERM $PID
+		echo "$AppName (pid:$PID) exiting..."
+		while [ x"$PID" != x"" ]
+		do
+			sleep 1
+			query
+		done
+		echo "$AppName exited."
+	else
+		echo "$AppName already stopped."
+	fi
+}
+
+function restart()
+{
+    stop
+    sleep 2
+    start
+}
+
+function status()
+{
+    PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l`
+    if [ $PID != 0 ];then
+        echo "$AppName is running..."
+    else
+        echo "$AppName is not running..."
+    fi
+}
+
+case $1 in
+    start)
+    start;;
+    stop)
+    stop;;
+    restart)
+    restart;;
+    status)
+    status;;
+    *)
+
+esac

+ 189 - 0
master/sql/quartz.sql

@@ -0,0 +1,189 @@
+-- ----------------------------
+-- 1、存储每一个已配置的 jobDetail 的详细信息
+-- ----------------------------
+create table qrtz_job_details (
+    sched_name varchar2(120) not null,
+    job_name  varchar2(200) not null,
+    job_group varchar2(200) not null,
+    description varchar2(250) null,
+    job_class_name   varchar2(250) not null,
+    is_durable varchar2(1) not null,
+    is_nonconcurrent varchar2(1) not null,
+    is_update_data varchar2(1) not null,
+    requests_recovery varchar2(1) not null,
+    job_data blob null,
+    constraint qrtz_job_details_pk primary key (sched_name,job_name,job_group)
+);
+
+-- ----------------------------
+-- 2、 存储已配置的 Trigger 的信息
+-- ----------------------------
+create table qrtz_triggers (
+    sched_name varchar2(120) not null,
+    trigger_name varchar2(200) not null,
+    trigger_group varchar2(200) not null,
+    job_name  varchar2(200) not null,
+    job_group varchar2(200) not null,
+    description varchar2(250) null,
+    next_fire_time number(13) null,
+    prev_fire_time number(13) null,
+    priority number(13) null,
+    trigger_state varchar2(16) not null,
+    trigger_type varchar2(8) not null,
+    start_time number(13) not null,
+    end_time number(13) null,
+    calendar_name varchar2(200) null,
+    misfire_instr number(2) null,
+    job_data blob null,
+    constraint qrtz_triggers_pk primary key (sched_name,trigger_name,trigger_group),
+    constraint qrtz_trigger_to_jobs_fk foreign key (sched_name,job_name,job_group)
+      references qrtz_job_details(sched_name,job_name,job_group)
+);
+
+-- ----------------------------
+-- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数
+-- ----------------------------
+create table qrtz_simple_triggers (
+    sched_name varchar2(120) not null,
+    trigger_name varchar2(200) not null,
+    trigger_group varchar2(200) not null,
+    repeat_count number(7) not null,
+    repeat_interval number(12) not null,
+    times_triggered number(10) not null,
+    constraint qrtz_simple_trig_pk primary key (sched_name,trigger_name,trigger_group),
+    constraint qrtz_simple_trig_to_trig_fk foreign key (sched_name,trigger_name,trigger_group)
+      references qrtz_triggers(sched_name,trigger_name,trigger_group)
+);
+
+-- ----------------------------
+-- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息
+-- ---------------------------- 
+create table qrtz_cron_triggers (
+    sched_name varchar2(120) not null,
+    trigger_name varchar2(200) not null,
+    trigger_group varchar2(200) not null,
+    cron_expression varchar2(120) not null,
+    time_zone_id varchar2(80),
+    constraint qrtz_cron_trig_pk primary key (sched_name,trigger_name,trigger_group),
+    constraint qrtz_cron_trig_to_trig_fk foreign key (sched_name,trigger_name,trigger_group)
+      references qrtz_triggers(sched_name,trigger_name,trigger_group)
+);
+
+-- ----------------------------
+-- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)
+-- ---------------------------- 
+create table qrtz_blob_triggers (
+    sched_name varchar2(120) not null,
+    trigger_name varchar2(200) not null,
+    trigger_group varchar2(200) not null,
+    blob_data blob null,
+    constraint qrtz_blob_trig_pk primary key (sched_name,trigger_name,trigger_group),
+    constraint qrtz_blob_trig_to_trig_fk foreign key (sched_name,trigger_name,trigger_group)
+      references qrtz_triggers(sched_name,trigger_name,trigger_group)
+);
+
+-- ----------------------------
+-- 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围
+-- ---------------------------- 
+create table qrtz_calendars (
+    sched_name varchar2(120) not null,
+    calendar_name  varchar2(200) not null,
+    calendar blob not null,
+    constraint qrtz_calendars_pk primary key (sched_name,calendar_name)
+);
+
+-- ----------------------------
+-- 7、 存储已暂停的 Trigger 组的信息
+-- ---------------------------- 
+create table qrtz_paused_trigger_grps (
+    sched_name varchar2(120) not null,
+    trigger_group  varchar2(200) not null,
+    constraint qrtz_paused_trig_grps_pk primary key (sched_name,trigger_group)
+);
+
+-- ----------------------------
+-- 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息
+-- ---------------------------- 
+create table qrtz_fired_triggers (
+    sched_name varchar2(120) not null,
+    entry_id varchar2(95) not null,
+    trigger_name varchar2(200) not null,
+    trigger_group varchar2(200) not null,
+    instance_name varchar2(200) not null,
+    fired_time number(13) not null,
+    sched_time number(13) not null,
+    priority number(13) not null,
+    state varchar2(16) not null,
+    job_name varchar2(200) null,
+    job_group varchar2(200) null,
+    is_nonconcurrent varchar2(1) null,
+    requests_recovery varchar2(1) null,
+    constraint qrtz_fired_trigger_pk primary key (sched_name,entry_id)
+);
+
+-- ----------------------------
+-- 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例
+-- ---------------------------- 
+create table qrtz_scheduler_state (
+    sched_name varchar2(120) not null,
+    instance_name varchar2(200) not null,
+    last_checkin_time number(13) not null,
+    checkin_interval number(13) not null,
+    constraint qrtz_scheduler_state_pk primary key (sched_name,instance_name)
+);
+
+-- ----------------------------
+-- 10、 存储程序的悲观锁的信息(假如使用了悲观锁)
+-- ---------------------------- 
+create table qrtz_locks (
+    sched_name varchar2(120) not null,
+    lock_name  varchar2(40) not null,
+    constraint qrtz_locks_pk primary key (sched_name,lock_name)
+);
+
+create table qrtz_simprop_triggers (
+    sched_name varchar2(120) not null,
+    trigger_name varchar2(200) not null,
+    trigger_group varchar2(200) not null,
+    str_prop_1 varchar2(512) null,
+    str_prop_2 varchar2(512) null,
+    str_prop_3 varchar2(512) null,
+    int_prop_1 number(10) null,
+    int_prop_2 number(10) null,
+    long_prop_1 number(13) null,
+    long_prop_2 number(13) null,
+    dec_prop_1 numeric(13,4) null,
+    dec_prop_2 numeric(13,4) null,
+    bool_prop_1 varchar2(1) null,
+    bool_prop_2 varchar2(1) null,
+    constraint qrtz_simprop_trig_pk primary key (sched_name,trigger_name,trigger_group),
+    constraint qrtz_simprop_trig_to_trig_fk foreign key (sched_name,trigger_name,trigger_group)
+      references qrtz_triggers(sched_name,trigger_name,trigger_group)
+);
+
+
+create index idx_qrtz_j_req_recovery on qrtz_job_details(sched_name,requests_recovery);
+create index idx_qrtz_j_grp on qrtz_job_details(sched_name,job_group);
+
+create index idx_qrtz_t_j on qrtz_triggers(sched_name,job_name,job_group);
+create index idx_qrtz_t_jg on qrtz_triggers(sched_name,job_group);
+create index idx_qrtz_t_c on qrtz_triggers(sched_name,calendar_name);
+create index idx_qrtz_t_g on qrtz_triggers(sched_name,trigger_group);
+create index idx_qrtz_t_state on qrtz_triggers(sched_name,trigger_state);
+create index idx_qrtz_t_n_state on qrtz_triggers(sched_name,trigger_name,trigger_group,trigger_state);
+create index idx_qrtz_t_n_g_state on qrtz_triggers(sched_name,trigger_group,trigger_state);
+create index idx_qrtz_t_next_fire_time on qrtz_triggers(sched_name,next_fire_time);
+create index idx_qrtz_t_nft_st on qrtz_triggers(sched_name,trigger_state,next_fire_time);
+create index idx_qrtz_t_nft_misfire on qrtz_triggers(sched_name,misfire_instr,next_fire_time);
+create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(sched_name,misfire_instr,next_fire_time,trigger_state);
+create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(sched_name,misfire_instr,next_fire_time,trigger_group,trigger_state);
+
+create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(sched_name,instance_name);
+create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(sched_name,instance_name,requests_recovery);
+create index idx_qrtz_ft_j_g on qrtz_fired_triggers(sched_name,job_name,job_group);
+create index idx_qrtz_ft_jg on qrtz_fired_triggers(sched_name,job_group);
+create index idx_qrtz_ft_t_g on qrtz_fired_triggers(sched_name,trigger_name,trigger_group);
+
+create index idx_qrtz_ft_tg on qrtz_fired_triggers(sched_name,trigger_group);
+
+commit;

+ 1048 - 0
master/sql/ry_20201021.sql

@@ -0,0 +1,1048 @@
+-- ----------------------------
+-- 1、部门表
+-- ----------------------------
+create sequence seq_sys_dept
+ increment by 1
+ start with 200
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_dept (
+  dept_id           number(20)      not null,
+  parent_id         number(20)      default 0,
+  ancestors         varchar2(50)    default '',
+  dept_name         varchar2(30)    default '',
+  order_num         number(4)       default 0,
+  leader            varchar2(20)    default null,
+  phone             varchar2(11)    default null,
+  email             varchar2(50)    default null,
+  status            char(1)         default '0',
+  del_flag          char(1)         default '0',
+  create_by         varchar2(64)    default '',
+  create_time 	    date,
+  update_by         varchar2(64)    default '',
+  update_time       date
+);
+
+alter table sys_dept add constraint pk_sys_dept primary key (dept_id);
+
+comment on table  sys_dept              is '部门信息表';
+comment on column sys_dept.dept_id      is '部门主键seq_sys_dept.nextval';
+comment on column sys_dept.parent_id    is '父部门id';
+comment on column sys_dept.ancestors    is '祖级列表';
+comment on column sys_dept.dept_name    is '部门名称';
+comment on column sys_dept.order_num    is '显示顺序';
+comment on column sys_dept.leader       is '负责人';
+comment on column sys_dept.phone        is '联系电话';
+comment on column sys_dept.email        is '邮箱';
+comment on column sys_dept.status       is '部门状态(0正常 1停用)';
+comment on column sys_dept.del_flag     is '删除标志(0代表存在 2代表删除)';
+comment on column sys_dept.create_by    is '创建者';
+comment on column sys_dept.create_time  is '创建时间';
+comment on column sys_dept.update_by    is '更新者';
+comment on column sys_dept.update_time  is '更新时间';
+
+-- ----------------------------
+-- 初始化-部门表数据
+-- ----------------------------
+insert into sys_dept values(100,  0,   '0',          '若依科技',   0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(101,  100, '0,100',      '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(102,  100, '0,100',      '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(103,  101, '0,100,101',  '研发部门',   1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(104,  101, '0,100,101',  '市场部门',   2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(105,  101, '0,100,101',  '测试部门',   3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(106,  101, '0,100,101',  '财务部门',   4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(107,  101, '0,100,101',  '运维部门',   5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(108,  102, '0,100,102',  '市场部门',   1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+insert into sys_dept values(109,  102, '0,100,102',  '财务部门',   2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate, '', null);
+
+-- ----------------------------
+-- 2、用户信息表
+-- ----------------------------
+create sequence seq_sys_user
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_user (
+  user_id           number(20)      not null,
+  dept_id           number(20)      default null,
+  user_name         varchar2(30)    not null,
+  nick_name         varchar2(30)    default '',
+  user_type         varchar2(2)     default '00',
+  email             varchar2(50)    default '',
+  phonenumber       varchar2(11)    default '',
+  sex               char(1)         default '0',
+  avatar            varchar2(100)   default '',
+  password          varchar2(100)   default '',
+  status            char(1)         default '0',
+  del_flag          char(1)         default '0',
+  login_ip          varchar2(50)    default '',
+  login_date        date,
+  create_by         varchar2(64),
+  create_time 	    date,
+  update_by         varchar2(64)    default '',
+  update_time       date,
+  remark            varchar2(500)   default ''
+);
+
+alter table sys_user add constraint pk_sys_user primary key (user_id);
+
+comment on table  sys_user              is '用户信息表';
+comment on column sys_user.user_id      is '用户主键seq_sys_user.nextval';
+comment on column sys_user.dept_id      is '部门ID';
+comment on column sys_user.user_name    is '用户账号';
+comment on column sys_user.nick_name    is '用户昵称';
+comment on column sys_user.user_type    is '用户类型(00系统用户 01注册用户)';
+comment on column sys_user.email        is '用户邮箱';
+comment on column sys_user.phonenumber  is '手机号码';
+comment on column sys_user.sex          is '用户性别(0男 1女 2未知)';
+comment on column sys_user.avatar       is '头像路径';
+comment on column sys_user.password     is '密码';
+comment on column sys_user.status       is '帐号状态(0正常 1停用)';
+comment on column sys_user.del_flag     is '删除标志(0代表存在 2代表删除)';
+comment on column sys_user.login_ip     is '最后登录IP';
+comment on column sys_user.login_date   is '最后登录时间';
+comment on column sys_user.create_by    is '创建者';
+comment on column sys_user.create_time  is '创建时间';
+comment on column sys_user.update_by    is '更新者';
+comment on column sys_user.update_time  is '更新时间';
+comment on column sys_user.remark       is '备注';
+
+-- ----------------------------
+-- 初始化-用户信息表数据
+-- ----------------------------
+insert into sys_user values(1,  103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate, 'admin', sysdate, '', null, '管理员');
+insert into sys_user values(2,  105, 'ry',    '若依', '00', 'ry@qq.com',  '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate, 'admin', sysdate, '', null, '测试员');
+
+
+-- ----------------------------
+-- 3、岗位信息表
+-- ----------------------------
+create sequence seq_sys_post
+ increment by 1
+ start with 10
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_post
+(
+  post_id           number(20)      not null,
+  post_code         varchar2(64)    not null,
+  post_name         varchar2(50)    not null,
+  post_sort         number(4)       not null,
+  status            char(1)         not null,
+  create_by         varchar2(64)    default '',
+  create_time       date,
+  update_by         varchar2(64)    default '',
+  update_time       date,
+  remark            varchar2(500)
+);
+
+alter table sys_post add constraint pk_sys_post primary key (post_id);
+
+comment on table  sys_post              is '岗位信息表';
+comment on column sys_post.post_id      is '岗位主键seq_sys_post.nextval';
+comment on column sys_post.post_code    is '岗位编码';
+comment on column sys_post.post_name    is '岗位名称';
+comment on column sys_post.post_sort    is '显示顺序';
+comment on column sys_post.status       is '状态(0正常 1停用)';
+comment on column sys_post.create_by    is '创建者';
+comment on column sys_post.create_time  is '创建时间';
+comment on column sys_post.update_by    is '更新者';
+comment on column sys_post.update_time  is '更新时间';
+comment on column sys_post.remark       is '备注';
+
+-- ----------------------------
+-- 初始化-岗位信息表数据
+-- ----------------------------
+insert into sys_post values(1, 'ceo',  '董事长',    1, '0', 'admin', sysdate, '', null, '');
+insert into sys_post values(2, 'se',   '项目经理',  2, '0', 'admin', sysdate, '', null, '');
+insert into sys_post values(3, 'hr',   '人力资源',  3, '0', 'admin', sysdate, '', null, '');
+insert into sys_post values(4, 'user', '普通员工',  4, '0', 'admin', sysdate, '', null, '');
+
+
+-- ----------------------------
+-- 4、角色信息表
+-- ----------------------------
+create sequence seq_sys_role
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_role (
+  role_id              number(20)      not null,
+  role_name            varchar2(30)    not null,
+  role_key             varchar2(100)   not null,
+  role_sort            number(4)       not null,
+  data_scope           char(1)         default '1',
+  menu_check_strictly  number(1)       default 1,
+  dept_check_strictly  number(1)       default 1,
+  status               char(1)         not null,
+  del_flag             char(1)         default '0',
+  create_by            varchar2(64)    default '',
+  create_time          date,
+  update_by            varchar2(64)    default '',
+  update_time          date,
+  remark               varchar2(500)   default null
+);
+
+alter table sys_role add constraint pk_sys_role primary key (role_id);
+
+comment on table  sys_role                       is '角色信息表';
+comment on column sys_role.role_id               is '角色主键seq_sys_post.nextval';
+comment on column sys_role.role_name             is '角色名称';
+comment on column sys_role.role_key              is '角色权限字符串';
+comment on column sys_role.role_sort             is '显示顺序';
+comment on column sys_role.data_scope            is '数据范围(1:全部数据权限 2:自定数据权限)';
+comment on column sys_role.menu_check_strictly   is '菜单树选择项是否关联显示';
+comment on column sys_role.dept_check_strictly   is '部门树选择项是否关联显示';
+comment on column sys_role.status                is '角色状态(0正常 1停用)';
+comment on column sys_role.del_flag              is '删除标志(0代表存在 2代表删除)';
+comment on column sys_role.create_by             is '创建者';
+comment on column sys_role.create_time           is '创建时间';
+comment on column sys_role.update_by             is '更新者';
+comment on column sys_role.update_time           is '更新时间';
+comment on column sys_role.remark                is '备注';
+
+-- ----------------------------
+-- 初始化-角色信息表数据
+-- ----------------------------
+insert into sys_role values('1', '超级管理员', 'admin',  1, 1, 1, 1, '0', '0', 'admin', sysdate, '', null, '超级管理员');
+insert into sys_role values('2', '普通角色',   'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate, '', null, '普通角色');
+
+
+-- ----------------------------
+-- 5、菜单权限表
+-- ----------------------------
+create sequence seq_sys_menu
+ increment by 1
+ start with 2000
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_menu (
+  menu_id           number(20)      not null,
+  menu_name         varchar2(50)    not null,
+  parent_id         number(20)      default 0,
+  order_num         number(4)       default 0,
+  path              varchar(200)    default '',
+  component         varchar(255)    default null,
+  is_frame          number(1)       default 1,
+  is_cache          number(1)       default 0,
+  menu_type         char(1)         default '',
+  visible           char(1)         default 0,
+  status            char(1)         default 0,
+  perms             varchar2(100)   default null,
+  icon              varchar2(100)   default '#',
+  create_by         varchar2(64)    default '',
+  create_time       date,
+  update_by         varchar2(64)    default '',
+  update_time       date ,
+  remark            varchar2(500)   default ''
+);
+
+alter table sys_menu add constraint pk_sys_menu primary key (menu_id);
+
+comment on table  sys_menu              is '菜单权限表';
+comment on column sys_menu.menu_id      is '菜单主键seq_sys_post.nextval';
+comment on column sys_menu.menu_name    is '菜单名称';
+comment on column sys_menu.parent_id    is '父菜单ID';
+comment on column sys_menu.order_num    is '显示顺序';
+comment on column sys_menu.path         is '请求地址';
+comment on column sys_menu.component    is '路由地址';
+comment on column sys_menu.is_frame     is '是否为外链(0是 1否)';
+comment on column sys_menu.is_cache     is '是否缓存(0缓存 1不缓存)';
+comment on column sys_menu.menu_type    is '菜单类型(M目录 C菜单 F按钮)';
+comment on column sys_menu.visible      is '菜单状态(0显示 1隐藏)';
+comment on column sys_menu.status       is '菜单状态(0正常 1停用)';
+comment on column sys_menu.perms        is '权限标识';
+comment on column sys_menu.icon         is '菜单图标';
+comment on column sys_menu.create_by    is '创建者';
+comment on column sys_menu.create_time  is '创建时间';
+comment on column sys_menu.update_by    is '更新者';
+comment on column sys_menu.update_time  is '更新时间';
+comment on column sys_menu.remark       is '备注';
+
+-- ----------------------------
+-- 初始化-菜单信息表数据
+-- ----------------------------
+-- 一级菜单
+insert into sys_menu values('1', '系统管理', '0', '1', 'system',           null,   1, 0, 'M', '0', '0', '', 'system',   'admin', sysdate,'', null,'系统管理目录');
+insert into sys_menu values('2', '系统监控', '0', '2', 'monitor',          null,   1, 0, 'M', '0', '0', '', 'monitor',  'admin', sysdate,'', null,'系统监控目录');
+insert into sys_menu values('3', '系统工具', '0', '3', 'tool',             null,   1, 0, 'M', '0', '0', '', 'tool',     'admin', sysdate,'', null,'系统工具目录');
+insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null ,  0, 0, 'M', '0', '0', '', 'guide',    'admin', sysdate,'', null,'若依官网地址');
+-- 二级菜单
+insert into sys_menu values('100',  '用户管理', '1',   '1', 'user',       'system/user/index',        1, 0, 'C', '0', '0', 'system:user:list',        'user',          'admin', sysdate,'', null,'用户管理菜单');
+insert into sys_menu values('101',  '角色管理', '1',   '2', 'role',       'system/role/index',        1, 0, 'C', '0', '0', 'system:role:list',        'peoples',       'admin', sysdate,'', null,'角色管理菜单');
+insert into sys_menu values('102',  '菜单管理', '1',   '3', 'menu',       'system/menu/index',        1, 0, 'C', '0', '0', 'system:menu:list',        'tree-table',    'admin', sysdate,'', null,'菜单管理菜单');
+insert into sys_menu values('103',  '部门管理', '1',   '4', 'dept',       'system/dept/index',        1, 0, 'C', '0', '0', 'system:dept:list',        'tree',          'admin', sysdate,'', null,'部门管理菜单');
+insert into sys_menu values('104',  '岗位管理', '1',   '5', 'post',       'system/post/index',        1, 0, 'C', '0', '0', 'system:post:list',        'post',          'admin', sysdate,'', null,'岗位管理菜单');
+insert into sys_menu values('105',  '字典管理', '1',   '6', 'dict',       'system/dict/index',        1, 0, 'C', '0', '0', 'system:dict:list',        'dict',          'admin', sysdate,'', null,'字典管理菜单');
+insert into sys_menu values('106',  '参数设置', '1',   '7', 'config',     'system/config/index',      1, 0, 'C', '0', '0', 'system:config:list',      'edit',          'admin', sysdate,'', null,'参数设置菜单');
+insert into sys_menu values('107',  '通知公告', '1',   '8', 'notice',     'system/notice/index',      1, 0, 'C', '0', '0', 'system:notice:list',      'message',       'admin', sysdate,'', null,'通知公告菜单');
+insert into sys_menu values('108',  '日志管理', '1',   '9', 'log',        'system/log/index',         1, 0, 'M', '0', '0', '',                        'log',           'admin', sysdate,'', null,'日志管理菜单');
+insert into sys_menu values('109',  '在线用户', '2',   '1', 'online',     'monitor/online/index',     1, 0, 'C', '0', '0', 'monitor:online:list',     'online',        'admin', sysdate,'', null,'在线用户菜单');
+insert into sys_menu values('110',  '定时任务', '2',   '2', 'job',        'monitor/job/index',        1, 0, 'C', '0', '0', 'monitor:job:list',        'job',           'admin', sysdate,'', null,'定时任务菜单');
+insert into sys_menu values('111',  '数据监控', '2',   '3', 'druid',      'monitor/druid/index',      1, 0, 'C', '0', '0', 'monitor:druid:list',      'druid',         'admin', sysdate,'', null,'数据监控菜单');
+insert into sys_menu values('112',  '服务监控', '2',   '4', 'server',     'monitor/server/index',     1, 0, 'C', '0', '0', 'monitor:server:list',     'server',        'admin', sysdate,'', null,'服务监控菜单');
+insert into sys_menu values('113',  '表单构建', '3',   '1', 'build',      'tool/build/index',         1, 0, 'C', '0', '0', 'tool:build:list',         'build',         'admin', sysdate,'', null,'表单构建菜单');
+insert into sys_menu values('114',  '代码生成', '3',   '2', 'gen',        'tool/gen/index',           1, 0, 'C', '0', '0', 'tool:gen:list',           'code',          'admin', sysdate,'', null,'代码生成菜单');
+insert into sys_menu values('115',  '系统接口', '3',   '3', 'swagger',    'tool/swagger/index',       1, 0, 'C', '0', '0', 'tool:swagger:list',       'swagger',       'admin', sysdate,'', null,'系统接口菜单');
+-- 三级菜单
+insert into sys_menu values('500',  '操作日志', '108', '1', 'operlog',    'monitor/operlog/index',    1, 0, 'C', '0', '0', 'monitor:operlog:list',    'form',          'admin', sysdate,'', null,'操作日志菜单');
+insert into sys_menu values('501',  '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor',    'admin', sysdate,'', null,'登录日志菜单');
+-- 用户管理按钮
+insert into sys_menu values('1001', '用户查询', '100', '1',  '', '', 1, 0, 'F', '0', '0', 'system:user:query',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1002', '用户新增', '100', '2',  '', '', 1, 0, 'F', '0', '0', 'system:user:add',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1003', '用户修改', '100', '3',  '', '', 1, 0, 'F', '0', '0', 'system:user:edit',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1004', '用户删除', '100', '4',  '', '', 1, 0, 'F', '0', '0', 'system:user:remove',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1005', '用户导出', '100', '5',  '', '', 1, 0, 'F', '0', '0', 'system:user:export',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1006', '用户导入', '100', '6',  '', '', 1, 0, 'F', '0', '0', 'system:user:import',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1007', '重置密码', '100', '7',  '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd',       '#', 'admin', sysdate,'', null,'');
+-- 角色管理按钮
+insert into sys_menu values('1008', '角色查询', '101', '1',  '', '', 1, 0, 'F', '0', '0', 'system:role:query',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1009', '角色新增', '101', '2',  '', '', 1, 0, 'F', '0', '0', 'system:role:add',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1010', '角色修改', '101', '3',  '', '', 1, 0, 'F', '0', '0', 'system:role:edit',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1011', '角色删除', '101', '4',  '', '', 1, 0, 'F', '0', '0', 'system:role:remove',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1012', '角色导出', '101', '5',  '', '', 1, 0, 'F', '0', '0', 'system:role:export',         '#', 'admin', sysdate,'', null,'');
+-- 菜单管理按钮
+insert into sys_menu values('1013', '菜单查询', '102', '1',  '', '', 1, 0, 'F', '0', '0', 'system:menu:query',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1014', '菜单新增', '102', '2',  '', '', 1, 0, 'F', '0', '0', 'system:menu:add',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1015', '菜单修改', '102', '3',  '', '', 1, 0, 'F', '0', '0', 'system:menu:edit',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1016', '菜单删除', '102', '4',  '', '', 1, 0, 'F', '0', '0', 'system:menu:remove',         '#', 'admin', sysdate,'', null,'');
+-- 部门管理按钮
+insert into sys_menu values('1017', '部门查询', '103', '1',  '', '', 1, 0, 'F', '0', '0', 'system:dept:query',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1018', '部门新增', '103', '2',  '', '', 1, 0, 'F', '0', '0', 'system:dept:add',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1019', '部门修改', '103', '3',  '', '', 1, 0, 'F', '0', '0', 'system:dept:edit',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1020', '部门删除', '103', '4',  '', '', 1, 0, 'F', '0', '0', 'system:dept:remove',         '#', 'admin', sysdate,'', null,'');
+-- 岗位管理按钮
+insert into sys_menu values('1021', '岗位查询', '104', '1',  '', '', 1, 0, 'F', '0', '0', 'system:post:query',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1022', '岗位新增', '104', '2',  '', '', 1, 0, 'F', '0', '0', 'system:post:add',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1023', '岗位修改', '104', '3',  '', '', 1, 0, 'F', '0', '0', 'system:post:edit',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1024', '岗位删除', '104', '4',  '', '', 1, 0, 'F', '0', '0', 'system:post:remove',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1025', '岗位导出', '104', '5',  '', '', 1, 0, 'F', '0', '0', 'system:post:export',         '#', 'admin', sysdate,'', null,'');
+-- 字典管理按钮
+insert into sys_menu values('1026', '字典查询', '105', '1', '#', '', 1, 0, 'F', '0', '0', 'system:dict:query',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1027', '字典新增', '105', '2', '#', '', 1, 0, 'F', '0', '0', 'system:dict:add',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1028', '字典修改', '105', '3', '#', '', 1, 0, 'F', '0', '0', 'system:dict:edit',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1029', '字典删除', '105', '4', '#', '', 1, 0, 'F', '0', '0', 'system:dict:remove',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1030', '字典导出', '105', '5', '#', '', 1, 0, 'F', '0', '0', 'system:dict:export',         '#', 'admin', sysdate,'', null,'');
+-- 参数设置按钮
+insert into sys_menu values('1031', '参数查询', '106', '1', '#', '', 1, 0, 'F', '0', '0', 'system:config:query',        '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1032', '参数新增', '106', '2', '#', '', 1, 0, 'F', '0', '0', 'system:config:add',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1033', '参数修改', '106', '3', '#', '', 1, 0, 'F', '0', '0', 'system:config:edit',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1034', '参数删除', '106', '4', '#', '', 1, 0, 'F', '0', '0', 'system:config:remove',       '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1035', '参数导出', '106', '5', '#', '', 1, 0, 'F', '0', '0', 'system:config:export',       '#', 'admin', sysdate,'', null,'');
+-- 通知公告按钮
+insert into sys_menu values('1036', '公告查询', '107', '1', '#', '', 1, 0, 'F', '0', '0', 'system:notice:query',        '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1037', '公告新增', '107', '2', '#', '', 1, 0, 'F', '0', '0', 'system:notice:add',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1038', '公告修改', '107', '3', '#', '', 1, 0, 'F', '0', '0', 'system:notice:edit',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1039', '公告删除', '107', '4', '#', '', 1, 0, 'F', '0', '0', 'system:notice:remove',       '#', 'admin', sysdate,'', null,'');
+-- 操作日志按钮
+insert into sys_menu values('1040', '操作查询', '500', '1', '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query',      '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1041', '操作删除', '500', '2', '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove',     '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1042', '日志导出', '500', '4', '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export',     '#', 'admin', sysdate,'', null,'');
+-- 登录日志按钮
+insert into sys_menu values('1043', '登录查询', '501', '1', '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query',   '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1044', '登录删除', '501', '2', '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove',  '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1045', '日志导出', '501', '3', '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export',  '#', 'admin', sysdate,'', null,'');
+-- 在线用户按钮
+insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', 1, 0, 'F', '0', '0', 'monitor:online:query',       '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate,'', null,'');
+-- 定时任务按钮
+insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', 1, 0, 'F', '0', '0', 'monitor:job:query',          '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', 1, 0, 'F', '0', '0', 'monitor:job:add',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', 1, 0, 'F', '0', '0', 'monitor:job:edit',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', 1, 0, 'F', '0', '0', 'monitor:job:remove',         '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus',   '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1054', '任务导出', '110', '7', '#', '', 1, 0, 'F', '0', '0', 'monitor:job:export',         '#', 'admin', sysdate,'', null,'');
+-- 代码生成按钮
+insert into sys_menu values('1055', '生成查询', '114', '1', '#', '', 1, 0, 'F', '0', '0', 'tool:gen:query',             '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1056', '生成修改', '114', '2', '#', '', 1, 0, 'F', '0', '0', 'tool:gen:edit',              '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1057', '生成删除', '114', '3', '#', '', 1, 0, 'F', '0', '0', 'tool:gen:remove',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1058', '导入代码', '114', '2', '#', '', 1, 0, 'F', '0', '0', 'tool:gen:import',            '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1059', '预览代码', '114', '4', '#', '', 1, 0, 'F', '0', '0', 'tool:gen:preview',           '#', 'admin', sysdate,'', null,'');
+insert into sys_menu values('1060', '生成代码', '114', '5', '#', '', 1, 0, 'F', '0', '0', 'tool:gen:code',              '#', 'admin', sysdate,'', null,'');
+
+
+-- ----------------------------
+-- 6、用户和角色关联表  用户N-1角色
+-- ----------------------------
+create table sys_user_role (
+  user_id 	number(20)  not null,
+  role_id 	number(20)  not null
+);
+
+alter table sys_user_role add constraint pk_sys_user_role primary key (user_id, role_id);
+
+comment on table  sys_user_role              is '用户和角色关联表';
+comment on column sys_user_role.user_id      is '用户ID';
+comment on column sys_user_role.role_id      is '角色ID';
+
+-- ----------------------------
+-- 初始化-用户和角色关联表数据
+-- ----------------------------
+insert into sys_user_role values ('1', '1');
+insert into sys_user_role values ('2', '2');
+
+
+-- ----------------------------
+-- 7、角色和菜单关联表  角色1-N菜单
+-- ----------------------------
+create table sys_role_menu (
+  role_id 	number(20)  not null,
+  menu_id 	number(20)  not null
+);
+
+alter table sys_role_menu add constraint pk_sys_role_menu primary key (role_id, menu_id);
+
+comment on table  sys_role_menu              is '角色和菜单关联表';
+comment on column sys_role_menu.role_id      is '角色ID';
+comment on column sys_role_menu.menu_id      is '菜单ID';
+
+-- ----------------------------
+-- 初始化-角色和菜单关联表数据
+-- ----------------------------
+insert into sys_role_menu values ('2', '1');
+insert into sys_role_menu values ('2', '2');
+insert into sys_role_menu values ('2', '3');
+insert into sys_role_menu values ('2', '4');
+insert into sys_role_menu values ('2', '100');
+insert into sys_role_menu values ('2', '101');
+insert into sys_role_menu values ('2', '102');
+insert into sys_role_menu values ('2', '103');
+insert into sys_role_menu values ('2', '104');
+insert into sys_role_menu values ('2', '105');
+insert into sys_role_menu values ('2', '106');
+insert into sys_role_menu values ('2', '107');
+insert into sys_role_menu values ('2', '108');
+insert into sys_role_menu values ('2', '109');
+insert into sys_role_menu values ('2', '110');
+insert into sys_role_menu values ('2', '111');
+insert into sys_role_menu values ('2', '112');
+insert into sys_role_menu values ('2', '113');
+insert into sys_role_menu values ('2', '114');
+insert into sys_role_menu values ('2', '115');
+insert into sys_role_menu values ('2', '500');
+insert into sys_role_menu values ('2', '501');
+insert into sys_role_menu values ('2', '1000');
+insert into sys_role_menu values ('2', '1001');
+insert into sys_role_menu values ('2', '1002');
+insert into sys_role_menu values ('2', '1003');
+insert into sys_role_menu values ('2', '1004');
+insert into sys_role_menu values ('2', '1005');
+insert into sys_role_menu values ('2', '1006');
+insert into sys_role_menu values ('2', '1007');
+insert into sys_role_menu values ('2', '1008');
+insert into sys_role_menu values ('2', '1009');
+insert into sys_role_menu values ('2', '1010');
+insert into sys_role_menu values ('2', '1011');
+insert into sys_role_menu values ('2', '1012');
+insert into sys_role_menu values ('2', '1013');
+insert into sys_role_menu values ('2', '1014');
+insert into sys_role_menu values ('2', '1015');
+insert into sys_role_menu values ('2', '1016');
+insert into sys_role_menu values ('2', '1017');
+insert into sys_role_menu values ('2', '1018');
+insert into sys_role_menu values ('2', '1019');
+insert into sys_role_menu values ('2', '1020');
+insert into sys_role_menu values ('2', '1021');
+insert into sys_role_menu values ('2', '1022');
+insert into sys_role_menu values ('2', '1023');
+insert into sys_role_menu values ('2', '1024');
+insert into sys_role_menu values ('2', '1025');
+insert into sys_role_menu values ('2', '1026');
+insert into sys_role_menu values ('2', '1027');
+insert into sys_role_menu values ('2', '1028');
+insert into sys_role_menu values ('2', '1029');
+insert into sys_role_menu values ('2', '1030');
+insert into sys_role_menu values ('2', '1031');
+insert into sys_role_menu values ('2', '1032');
+insert into sys_role_menu values ('2', '1033');
+insert into sys_role_menu values ('2', '1034');
+insert into sys_role_menu values ('2', '1035');
+insert into sys_role_menu values ('2', '1036');
+insert into sys_role_menu values ('2', '1037');
+insert into sys_role_menu values ('2', '1038');
+insert into sys_role_menu values ('2', '1039');
+insert into sys_role_menu values ('2', '1040');
+insert into sys_role_menu values ('2', '1041');
+insert into sys_role_menu values ('2', '1042');
+insert into sys_role_menu values ('2', '1043');
+insert into sys_role_menu values ('2', '1044');
+insert into sys_role_menu values ('2', '1045');
+insert into sys_role_menu values ('2', '1046');
+insert into sys_role_menu values ('2', '1047');
+insert into sys_role_menu values ('2', '1048');
+insert into sys_role_menu values ('2', '1049');
+insert into sys_role_menu values ('2', '1050');
+insert into sys_role_menu values ('2', '1051');
+insert into sys_role_menu values ('2', '1052');
+insert into sys_role_menu values ('2', '1053');
+insert into sys_role_menu values ('2', '1054');
+insert into sys_role_menu values ('2', '1055');
+insert into sys_role_menu values ('2', '1056');
+insert into sys_role_menu values ('2', '1057');
+insert into sys_role_menu values ('2', '1058');
+insert into sys_role_menu values ('2', '1059');
+insert into sys_role_menu values ('2', '1060');
+
+-- ----------------------------
+-- 8、角色和部门关联表  角色1-N部门
+-- ----------------------------
+create table sys_role_dept (
+  role_id 	number(20)  not null,
+  dept_id 	number(20)  not null
+);
+
+alter table sys_role_dept add constraint pk_sys_role_dept primary key (role_id, dept_id);
+
+comment on table  sys_role_dept              is '角色和部门关联表';
+comment on column sys_role_dept.role_id      is '角色ID';
+comment on column sys_role_dept.dept_id      is '部门ID';
+
+-- ----------------------------
+-- 初始化-角色和部门关联表数据
+-- ----------------------------
+insert into sys_role_dept values ('2', '100');
+insert into sys_role_dept values ('2', '101');
+insert into sys_role_dept values ('2', '105');
+
+-- ----------------------------
+-- 9、用户与岗位关联表  用户1-N岗位
+-- ----------------------------
+create table sys_user_post
+(
+	user_id number(20)  not null,
+	post_id number(20)  not null
+);
+
+alter table sys_user_post add constraint pk_sys_user_post primary key (user_id, post_id);
+
+comment on table  sys_user_post              is '用户与岗位关联表';
+comment on column sys_user_post.user_id      is '用户ID';
+comment on column sys_user_post.post_id      is '岗位ID';
+
+-- ----------------------------
+-- 初始化-用户与岗位关联表数据
+-- ----------------------------
+insert into sys_user_post values ('1', '1');
+insert into sys_user_post values ('2', '2');
+
+
+-- ----------------------------
+-- 10、操作日志记录
+-- ----------------------------
+create sequence seq_sys_oper_log
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_oper_log (
+  oper_id           number(20)      not null ,
+  title             varchar2(50)    default '',
+  business_type     number(2)       default 0,
+  method            varchar2(100)   default '',
+  request_method    varchar(10)     default '',
+  operator_type     number(1)       default 0,
+  oper_name         varchar2(50)    default '',
+  dept_name         varchar2(50)    default '',
+  oper_url          varchar2(255) 	default '',
+  oper_ip           varchar2(50)    default '',
+  oper_location     varchar2(255)   default '',
+  oper_param        varchar2(2000)  default '',
+  json_result       varchar2(2000)  default '',
+  status            number(1)       default 0,
+  error_msg         varchar2(2000)  default '' ,
+  oper_time         date
+);
+
+alter table sys_oper_log add constraint pk_sys_oper_log primary key (oper_id);
+
+comment on table  sys_oper_log                is '操作日志记录';
+comment on column sys_oper_log.oper_id        is '日志主键seq_sys_oper_log.nextval';
+comment on column sys_oper_log.title          is '模块标题';
+comment on column sys_oper_log.business_type  is '业务类型(0其它 1新增 2修改 3删除)';
+comment on column sys_oper_log.method         is '方法名称';
+comment on column sys_oper_log.request_method is '请求方式';
+comment on column sys_oper_log.operator_type  is '操作类别(0其它 1后台用户 2手机端用户)';
+comment on column sys_oper_log.oper_name      is '操作人员';
+comment on column sys_oper_log.dept_name      is '部门名称';
+comment on column sys_oper_log.oper_url       is '请求URL';
+comment on column sys_oper_log.oper_ip        is '主机地址';
+comment on column sys_oper_log.oper_location  is '操作地点';
+comment on column sys_oper_log.oper_param     is '请求参数';
+comment on column sys_oper_log.json_result    is '返回参数';
+comment on column sys_oper_log.status         is '操作状态(0正常 1异常)';
+comment on column sys_oper_log.error_msg      is '错误消息';
+comment on column sys_oper_log.oper_time      is '操作时间';
+
+
+-- ----------------------------
+-- 11、字典类型表
+-- ----------------------------
+create sequence seq_sys_dict_type
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_dict_type
+(
+  dict_id           number(20)      not null,
+  dict_name         varchar2(100)   default '',
+  dict_type         varchar2(100)   default '',
+  status            char(1)         default '0',
+  create_by         varchar2(64)    default '',
+  create_time       date,
+  update_by         varchar2(64)    default '',
+  update_time       date,
+  remark            varchar2(500)   default null
+);
+
+alter table sys_dict_type add constraint pk_sys_dict_type primary key (dict_id);
+create unique index sys_dict_type_index1 on sys_dict_type (dict_type);
+
+comment on table  sys_dict_type               is '字典类型表';
+comment on column sys_dict_type.dict_id       is '字典主键seq_sys_dict_type.nextval';
+comment on column sys_dict_type.dict_name     is '字典名称';
+comment on column sys_dict_type.dict_type     is '字典类型';
+comment on column sys_dict_type.status        is '状态(0正常 1停用)';
+comment on column sys_dict_type.create_by     is '创建者';
+comment on column sys_dict_type.create_time   is '创建时间';
+comment on column sys_dict_type.update_by     is '更新者';
+comment on column sys_dict_type.update_time   is '更新时间';
+comment on column sys_dict_type.remark        is '备注';
+
+insert into sys_dict_type values(1,  '用户性别', 'sys_user_sex',        '0', 'admin', sysdate, '', null, '用户性别列表');
+insert into sys_dict_type values(2,  '菜单状态', 'sys_show_hide',       '0', 'admin', sysdate, '', null, '菜单状态列表');
+insert into sys_dict_type values(3,  '系统开关', 'sys_normal_disable',  '0', 'admin', sysdate, '', null, '系统开关列表');
+insert into sys_dict_type values(4,  '任务状态', 'sys_job_status',      '0', 'admin', sysdate, '', null, '任务状态列表');
+insert into sys_dict_type values(5,  '任务分组', 'sys_job_group',       '0', 'admin', sysdate, '', null, '任务分组列表');
+insert into sys_dict_type values(6,  '系统是否', 'sys_yes_no',          '0', 'admin', sysdate, '', null, '系统是否列表');
+insert into sys_dict_type values(7,  '通知类型', 'sys_notice_type',     '0', 'admin', sysdate, '', null, '通知类型列表');
+insert into sys_dict_type values(8,  '通知状态', 'sys_notice_status',   '0', 'admin', sysdate, '', null, '通知状态列表');
+insert into sys_dict_type values(9,  '操作类型', 'sys_oper_type',       '0', 'admin', sysdate, '', null, '操作类型列表');
+insert into sys_dict_type values(10, '系统状态', 'sys_common_status',   '0', 'admin', sysdate, '', null, '登录状态列表');
+
+
+-- ----------------------------
+-- 12、字典数据表
+-- ----------------------------
+create sequence seq_sys_dict_data
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_dict_data
+(
+  dict_code        number(20)      not null,
+  dict_sort        number(4)       default 0,
+  dict_label       varchar2(100)   default '',
+  dict_value       varchar2(100)   default '',
+  dict_type        varchar2(100)   default '',
+  css_class        varchar2(100)   default null,
+  list_class       varchar2(100)   default null,
+  is_default       char(1)         default 'N',
+  status           char(1)         default '0',
+  create_by        varchar2(64)    default '',
+  create_time      date,
+  update_by        varchar2(64)    default '',
+  update_time      date,
+  remark           varchar2(500)   default null
+);
+
+alter table sys_dict_data add constraint pk_sys_dict_data primary key (dict_code);
+
+comment on table  sys_dict_data               is '字典数据表';
+comment on column sys_dict_data.dict_code     is '字典主键seq_sys_dict_data.nextval';
+comment on column sys_dict_data.dict_sort     is '字典排序';
+comment on column sys_dict_data.dict_label    is '字典标签';
+comment on column sys_dict_data.dict_value    is '字典键值';
+comment on column sys_dict_data.dict_type     is '字典类型';
+comment on column sys_dict_data.css_class     is '样式属性(其他样式扩展)';
+comment on column sys_dict_data.list_class    is '表格回显样式';
+comment on column sys_dict_data.is_default    is '是否默认(Y是 N否)';
+comment on column sys_dict_data.status        is '状态(0正常 1停用)';
+comment on column sys_dict_data.create_by     is '创建者';
+comment on column sys_dict_data.create_time   is '创建时间';
+comment on column sys_dict_data.update_by     is '更新者';
+comment on column sys_dict_data.update_time   is '更新时间';
+comment on column sys_dict_data.remark        is '备注';
+
+insert into sys_dict_data values(1,  1,  '男',       '0',       'sys_user_sex',        '',   '',        'Y', '0', 'admin', sysdate, '', null, '性别男');
+insert into sys_dict_data values(2,  2,  '女',       '1',       'sys_user_sex',        '',   '',        'N', '0', 'admin', sysdate, '', null, '性别女');
+insert into sys_dict_data values(3,  3,  '未知',     '2',       'sys_user_sex',        '',   '',        'N', '0', 'admin', sysdate, '', null, '性别未知');
+insert into sys_dict_data values(4,  1,  '显示',     '0',       'sys_show_hide',       '',   'primary', 'Y', '0', 'admin', sysdate, '', null, '显示菜单');
+insert into sys_dict_data values(5,  2,  '隐藏',     '1',       'sys_show_hide',       '',   'danger',  'N', '0', 'admin', sysdate, '', null, '隐藏菜单');
+insert into sys_dict_data values(6,  1,  '正常',     '0',       'sys_normal_disable',  '',   'primary', 'Y', '0', 'admin', sysdate, '', null, '正常状态');
+insert into sys_dict_data values(7,  2,  '停用',     '1',       'sys_normal_disable',  '',   'danger',  'N', '0', 'admin', sysdate, '', null, '停用状态');
+insert into sys_dict_data values(8,  1,  '正常',     '0',       'sys_job_status',      '',   'primary', 'Y', '0', 'admin', sysdate, '', null, '正常状态');
+insert into sys_dict_data values(9,  2,  '暂停',     '1',       'sys_job_status',      '',   'danger',  'N', '0', 'admin', sysdate, '', null, '停用状态');
+insert into sys_dict_data values(10, 1,  '默认',     'DEFAULT', 'sys_job_group',       '',   '',        'Y', '0', 'admin', sysdate, '', null, '默认分组');
+insert into sys_dict_data values(11, 2,  '系统',     'SYSTEM',  'sys_job_group',       '',   '',        'N', '0', 'admin', sysdate, '', null, '系统分组');
+insert into sys_dict_data values(12, 1,  '是',       'Y',       'sys_yes_no',          '',   'primary', 'Y', '0', 'admin', sysdate, '', null, '系统默认是');
+insert into sys_dict_data values(13, 2,  '否',       'N',       'sys_yes_no',          '',   'danger',  'N', '0', 'admin', sysdate, '', null, '系统默认否');
+insert into sys_dict_data values(14, 1,  '通知',     '1',       'sys_notice_type',     '',   'warning', 'Y', '0', 'admin', sysdate, '', null, '通知');
+insert into sys_dict_data values(15, 2,  '公告',     '2',       'sys_notice_type',     '',   'success', 'N', '0', 'admin', sysdate, '', null, '公告');
+insert into sys_dict_data values(16, 1,  '正常',     '0',       'sys_notice_status',   '',   'primary', 'Y', '0', 'admin', sysdate, '', null, '正常状态');
+insert into sys_dict_data values(17, 2,  '关闭',     '1',       'sys_notice_status',   '',   'danger',  'N', '0', 'admin', sysdate, '', null, '关闭状态');
+insert into sys_dict_data values(18, 99, '其他',     '0',       'sys_oper_type',       '',   'info',    'N', '0', 'admin', sysdate, '', null, '其他操作');
+insert into sys_dict_data values(19, 1,  '新增',     '1',       'sys_oper_type',       '',   'info',    'N', '0', 'admin', sysdate, '', null, '新增操作');
+insert into sys_dict_data values(20, 2,  '修改',     '2',       'sys_oper_type',       '',   'info',    'N', '0', 'admin', sysdate, '', null, '修改操作');
+insert into sys_dict_data values(21, 3,  '删除',     '3',       'sys_oper_type',       '',   'danger',  'N', '0', 'admin', sysdate, '', null, '删除操作');
+insert into sys_dict_data values(22, 4,  '授权',     '4',       'sys_oper_type',       '',   'primary', 'N', '0', 'admin', sysdate, '', null, '授权操作');
+insert into sys_dict_data values(23, 5,  '导出',     '5',       'sys_oper_type',       '',   'warning', 'N', '0', 'admin', sysdate, '', null, '导出操作');
+insert into sys_dict_data values(24, 6,  '导入',     '6',       'sys_oper_type',       '',   'warning', 'N', '0', 'admin', sysdate, '', null, '导入操作');
+insert into sys_dict_data values(25, 7,  '强退',     '7',       'sys_oper_type',       '',   'danger',  'N', '0', 'admin', sysdate, '', null, '强退操作');
+insert into sys_dict_data values(26, 8,  '生成代码', '8',       'sys_oper_type',       '',   'warning', 'N', '0', 'admin', sysdate, '', null, '生成操作');
+insert into sys_dict_data values(27, 9,  '清空数据', '9',       'sys_oper_type',       '',   'danger',  'N', '0', 'admin', sysdate, '', null, '清空操作');
+insert into sys_dict_data values(28, 1,  '成功',     '0',       'sys_common_status',   '',   'primary', 'N', '0', 'admin', sysdate, '', null, '正常状态');
+insert into sys_dict_data values(29, 2,  '失败',     '1',       'sys_common_status',   '',   'danger',  'N', '0', 'admin', sysdate, '', null, '停用状态');
+
+
+-- ----------------------------
+-- 13、参数配置表
+-- ----------------------------
+create sequence seq_sys_config
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_config (
+  config_id         number(20)     not null,
+  config_name       varchar2(100)  default '',
+  config_key        varchar2(100)  default '',
+  config_value      varchar2(100)  default '',
+  config_type       char(1)        default 'N',
+  create_by         varchar2(64)   default '',
+  create_time       date,
+  update_by         varchar2(64)   default '',
+  update_time       date,
+  remark            varchar2(500)  default null
+);
+alter table sys_config add constraint pk_sys_config primary key (config_id);
+
+comment on table  sys_config               is '参数配置表';
+comment on column sys_config.config_id     is '参数主键seq_sys_config.nextval';
+comment on column sys_config.config_name   is '参数名称';
+comment on column sys_config.config_key    is '参数键名';
+comment on column sys_config.config_value  is '参数键值';
+comment on column sys_config.config_type   is '系统内置(Y是 N否)';
+comment on column sys_config.create_by     is '创建者';
+comment on column sys_config.create_time   is '创建时间';
+comment on column sys_config.update_by     is '更新者';
+comment on column sys_config.update_time   is '更新时间';
+comment on column sys_config.remark        is '备注';
+
+insert into sys_config values(1, '主框架页-默认皮肤样式名称',     'sys.index.skinName',       'skin-blue',     'Y', 'admin', sysdate, '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow');
+insert into sys_config values(2, '用户管理-账号初始密码',         'sys.user.initPassword',    '123456',        'Y', 'admin', sysdate, '', null, '初始化密码 123456');
+insert into sys_config values(3, '主框架页-侧边栏主题',           'sys.index.sideTheme',      'theme-dark',    'Y', 'admin', sysdate, '', null, '深黑主题theme-dark,浅色主题theme-light,深蓝主题theme-blue');
+insert into sys_config values(4, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false',         'Y', 'admin', sysdate, '', null, '是否开启注册用户功能');
+
+
+-- ----------------------------
+-- 14、系统访问记录
+-- ----------------------------
+create sequence seq_sys_logininfor
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_logininfor (
+  info_id         number(20)     not null,
+  user_name       varchar2(50)   default '',
+  ipaddr          varchar2(50)   default '',
+  login_location  varchar2(255)  default '',
+  browser         varchar2(50)   default '',
+  os              varchar2(50)   default '',
+  status          char(1)        default '0',
+  msg             varchar2(255)  default '',
+  login_time      date
+);
+
+alter table sys_logininfor add constraint pk_sys_logininfor primary key (info_id);
+
+comment on table  sys_logininfor                is '系统访问记录';
+comment on column sys_logininfor.info_id        is '访问主键seq_seq_sys_logininfor.nextval';
+comment on column sys_logininfor.user_name      is '登录账号';
+comment on column sys_logininfor.ipaddr         is '登录IP地址';
+comment on column sys_logininfor.login_location is '登录地点';
+comment on column sys_logininfor.browser        is '浏览器类型';
+comment on column sys_logininfor.os             is '操作系统';
+comment on column sys_logininfor.status         is '登录状态(0成功 1失败)';
+comment on column sys_logininfor.msg            is '提示消息';
+comment on column sys_logininfor.login_time     is '访问时间';
+
+
+-- ----------------------------
+-- 15、定时任务调度表
+-- ----------------------------
+create sequence seq_sys_job
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_job (
+  job_id              number(20)     not null,
+  job_name            varchar2(64)   default '',
+  job_group           varchar2(64)   default '',
+  invoke_target       varchar2(500)  not null ,
+  cron_expression     varchar2(255)  default '',
+  misfire_policy      varchar2(20)   default '3',
+  concurrent          char(1)        default '1',
+  status              char(1)        default '0',
+  create_by           varchar2(64)   default '',
+  create_time         date,
+  update_by           varchar2(64)   default '',
+  update_time         date,
+  remark              varchar2(500)  default ''
+);
+
+alter table sys_job add constraint pk_sys_job primary key (job_id, job_name, job_group);
+
+comment on table  sys_job                   is '定时任务调度表';
+comment on column sys_job.job_id            is '任务主键seq_sys_job.nextval';
+comment on column sys_job.job_name          is '任务名称';
+comment on column sys_job.job_group         is '任务组名';
+comment on column sys_job.invoke_target     is '调用目标字符串';
+comment on column sys_job.cron_expression   is 'cron执行表达式';
+comment on column sys_job.misfire_policy    is '计划执行错误策略(1立即执行 2执行一次 3放弃执行)';
+comment on column sys_job.concurrent        is '是否并发执行(0允许 1禁止)';
+comment on column sys_job.status            is '状态(0正常 1暂停)';
+comment on column sys_job.create_by         is '创建者';
+comment on column sys_job.create_time       is '创建时间';
+comment on column sys_job.update_by         is '更新者';
+comment on column sys_job.update_time       is '更新时间';
+comment on column sys_job.remark            is '备注信息';
+
+insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams',        '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate, '', null, '');
+insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(''ry'')',  '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate, '', null, '');
+insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(''ry'', true, 2000L, 316.50D, 100)',  '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate, '', null, '');
+
+
+-- ----------------------------
+-- 16、定时任务调度日志表
+-- ----------------------------
+create sequence seq_sys_job_log
+ increment by 1
+ start with 1
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_job_log (
+  job_log_id          number(20)       not null,
+  job_name            varchar2(64)     not null,
+  job_group           varchar2(64)     not null,
+  invoke_target       varchar2(500)    not null ,
+  job_message         varchar2(500),
+  status              char(1)          default '0',
+  exception_info      varchar2(2000)   default '',
+  create_time         date
+);
+
+alter table sys_job_log add constraint pk_sys_job_log primary key (job_log_id);
+
+comment on table  sys_job_log                   is '定时任务调度日志表';
+comment on column sys_job_log.job_log_id        is '日志主键seq_sys_job_log.nextval';
+comment on column sys_job_log.job_name          is '任务名称';
+comment on column sys_job_log.job_group         is '任务组名';
+comment on column sys_job_log.invoke_target     is '调用目标字符串';
+comment on column sys_job_log.job_message       is '日志信息';
+comment on column sys_job_log.status            is '执行状态(0正常 1失败)';
+comment on column sys_job_log.exception_info    is '异常信息';
+comment on column sys_job_log.create_time       is '创建时间';
+
+
+-- ----------------------------
+-- 17、通知公告表
+-- ----------------------------
+create sequence seq_sys_notice
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table sys_notice (
+  notice_id         number(20)      not null,
+  notice_title      varchar2(50)    not null,
+  notice_type       char(1)         not null,
+  notice_content    clob            default null,
+  status            char(1)         default '0',
+  create_by         varchar2(64)    default '',
+  create_time       date,
+  update_by         varchar2(64)    default '',
+  update_time       date,
+  remark            varchar2(255)   default null
+);
+
+alter table sys_notice add constraint pk_sys_notice primary key (notice_id);
+
+comment on table  sys_notice                   is '通知公告表';
+comment on column sys_notice.notice_id         is '公告主键seq_sys_notice.nextval';
+comment on column sys_notice.notice_title      is '公告标题';
+comment on column sys_notice.notice_type       is '公告类型(1通知 2公告)';
+comment on column sys_notice.notice_content    is '公告内容';
+comment on column sys_notice.status            is '公告状态(0正常 1关闭)';
+comment on column sys_notice.create_by         is '创建者';
+comment on column sys_notice.create_time       is '创建时间';
+comment on column sys_notice.update_by         is '更新者';
+comment on column sys_notice.update_time       is '更新时间';
+comment on column sys_notice.remark            is '备注';
+
+-- ----------------------------
+-- 初始化-公告信息表数据
+-- ----------------------------
+insert into sys_notice values('1', '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate, '', null, '管理员');
+insert into sys_notice values('2', '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容',   '0', 'admin', sysdate, '', null, '管理员');
+commit;
+
+
+-- ----------------------------
+-- 18、代码生成业务表
+-- ----------------------------
+create sequence seq_gen_table
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+
+create table gen_table (
+  table_id          number(20)       not null,
+  table_name        varchar2(200)    default '',
+  table_comment     varchar2(500)    default '',
+  class_name        varchar2(100)    default '',
+  tpl_category      varchar2(200)    default 'crud',
+  package_name      varchar2(100),
+  module_name       varchar2(30),
+  business_name     varchar2(30),
+  function_name     varchar2(50),
+  function_author   varchar2(50),
+  gen_type          char(1)          default '0',
+  gen_path          varchar2(200)    default '/',
+  options           varchar2(1000),
+  create_by         varchar2(64)     default '',
+  create_time       date,
+  update_by         varchar2(64)     default '',
+  update_time       date,
+  remark            varchar2(500)    default null
+);
+
+alter table gen_table add constraint pk_gen_table primary key (table_id);
+
+comment on table  gen_table                  is '代码生成业务表';
+comment on column gen_table.table_id         is '编号';
+comment on column gen_table.table_name       is '表名称';
+comment on column gen_table.table_comment    is '表描述';
+comment on column gen_table.class_name       is '实体类名称';
+comment on column gen_table.tpl_category     is '使用的模板(crud单表操作 tree树表操作)';
+comment on column gen_table.package_name     is '生成包路径';
+comment on column gen_table.module_name      is '生成模块名';
+comment on column gen_table.business_name    is '生成业务名';
+comment on column gen_table.function_name    is '生成功能名';
+comment on column gen_table.function_author  is '生成功能作者';
+comment on column gen_table.gen_type         is '生成代码方式(0zip压缩包 1自定义路径)';
+comment on column gen_table.gen_path         is '生成路径(不填默认项目路径)';
+comment on column gen_table.options          is '其它生成选项';
+comment on column gen_table.create_by        is '创建者';
+comment on column gen_table.create_time      is '创建时间';
+comment on column gen_table.update_by        is '更新者';
+comment on column gen_table.update_time      is '更新时间';
+comment on column gen_table.remark           is '备注';
+
+
+-- ----------------------------
+-- 19、代码生成业务表字段
+-- ----------------------------
+create sequence seq_gen_table_column
+ increment by 1
+ start with 100
+ nomaxvalue
+ nominvalue
+ cache 20;
+create table gen_table_column (
+  column_id         number(20)      not null,
+  table_id          varchar2(64),
+  column_name       varchar2(200),
+  column_comment    varchar2(500),
+  column_type       varchar2(100),
+  java_type         varchar2(500),
+  java_field        varchar2(200),
+  is_pk             char(1),
+  is_increment      char(1),
+  is_required       char(1),
+  is_insert         char(1),
+  is_edit           char(1),
+  is_list           char(1),
+  is_query          char(1),
+  query_type        varchar(200)    default 'EQ',
+  html_type         varchar(200),
+  dict_type         varchar(200)    default '',
+  sort              number(4),
+  create_by         varchar(64)     default '',
+  create_time       date ,
+  update_by         varchar(64)     default '',
+  update_time       date
+);
+
+alter table gen_table_column add constraint pk_gen_table_column primary key (column_id);
+
+comment on table  gen_table_column                is '代码生成业务表字段';
+comment on column gen_table_column.column_id      is '编号';
+comment on column gen_table_column.table_id       is '归属表编号';
+comment on column gen_table_column.column_name    is '列名称';
+comment on column gen_table_column.column_comment is '列描述';
+comment on column gen_table_column.column_type    is '列类型';
+comment on column gen_table_column.java_type      is 'JAVA类型';
+comment on column gen_table_column.java_field     is 'JAVA字段名';
+comment on column gen_table_column.is_pk          is '是否主键(1是)';
+comment on column gen_table_column.is_increment   is '是否自增(1是)';
+comment on column gen_table_column.is_required    is '是否必填(1是)';
+comment on column gen_table_column.is_insert      is '是否为插入字段(1是)';
+comment on column gen_table_column.is_edit        is '是否编辑字段(1是)';
+comment on column gen_table_column.is_list        is '是否列表字段(1是)';
+comment on column gen_table_column.is_query       is '是否查询字段(1是)';
+comment on column gen_table_column.query_type     is '查询方式(等于、不等于、大于、小于、范围)';
+comment on column gen_table_column.html_type      is '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)';
+comment on column gen_table_column.dict_type      is '字典类型';
+comment on column gen_table_column.sort           is '排序';
+comment on column gen_table_column.create_by      is '创建者';
+comment on column gen_table_column.create_time    is '创建时间';
+comment on column gen_table_column.update_by      is '更新者';
+comment on column gen_table_column.update_time    is '更新时间';
+
+
+-- ----------------------------
+-- 函数 ,代替mysql的find_in_set
+-- 例如: select * from sys_dept where FIND_IN_SET (101,ancestors) <> 0
+-- mysql可接受0或其它number做为where 条件,oracle只接受表达式做为where 条件
+-- ----------------------------
+create or replace function find_in_set(arg1 in varchar2,arg2 in varchar)
+return number is Result number;
+begin
+select instr(','||arg2||',' , ','||arg1||',') into Result from dual;
+return(Result);
+end find_in_set;

+ 33 - 0
master/src/main/java/com/ruoyi/RuoYiApplication.java

@@ -0,0 +1,33 @@
+package com.ruoyi;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.web.servlet.MultipartConfigFactory;
+import org.springframework.context.annotation.Bean;
+
+import javax.servlet.MultipartConfigElement;
+
+/**
+ * 启动程序
+ *
+ * @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("(♥◠‿◠)ノ゙  CPMS启动成功   ლ(´ڡ`ლ)゙");
+    }
+
+    @Bean
+    MultipartConfigElement multipartConfigElement() {
+        MultipartConfigFactory factory = new MultipartConfigFactory();
+        factory.setLocation("/data/tmp");
+        return factory.createMultipartConfig();
+    }
+}

+ 18 - 0
master/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);
+    }
+}

+ 131 - 0
master/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -0,0 +1,131 @@
+package com.ruoyi.common.constant;
+
+import io.jsonwebtoken.Claims;
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public class Constants
+{
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * 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 LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码 redis key
+     */
+    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 登录用户 redis key
+     */
+    public static final String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    /**
+     * 防重提交 redis key
+     */
+    public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    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_USERNAME = Claims.SUBJECT;
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 参数管理 cache key
+     */
+    public static final String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+}

+ 103 - 0
master/src/main/java/com/ruoyi/common/constant/GenConstants.java

@@ -0,0 +1,103 @@
+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 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", "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", "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_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 REQUIRE = "1";
+}

+ 89 - 0
master/src/main/java/com/ruoyi/common/constant/HttpStatus.java

@@ -0,0 +1,89 @@
+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;
+}

+ 50 - 0
master/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;
+        }
+    }
+}

+ 60 - 0
master/src/main/java/com/ruoyi/common/constant/UserConstants.java

@@ -0,0 +1,60 @@
+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";
+
+    /** 校验返回结果码 */
+    public final static String UNIQUE = "0";
+    public final static String NOT_UNIQUE = "1";
+}

+ 484 - 0
master/src/main/java/com/ruoyi/common/core/lang/UUID.java

@@ -0,0 +1,484 @@
+package com.ruoyi.common.core.lang;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import com.ruoyi.common.exception.UtilException;
+
+/**
+ * 提供通用唯一识别码(universally unique identifier)(UUID)实现
+ *
+ * @author ruoyi
+ */
+public final class UUID implements java.io.Serializable, Comparable<UUID>
+{
+    private static final long serialVersionUID = -1185015143654744140L;
+
+    /**
+     * SecureRandom 的单例
+     *
+     */
+    private static class Holder
+    {
+        static final SecureRandom numberGenerator = getSecureRandom();
+    }
+
+    /** 此UUID的最高64有效位 */
+    private final long mostSigBits;
+
+    /** 此UUID的最低64有效位 */
+    private final long leastSigBits;
+
+    /**
+     * 私有构造
+     *
+     * @param data 数据
+     */
+    private UUID(byte[] data)
+    {
+        long msb = 0;
+        long lsb = 0;
+        assert data.length == 16 : "data must be 16 bytes in length";
+        for (int i = 0; i < 8; i++)
+        {
+            msb = (msb << 8) | (data[i] & 0xff);
+        }
+        for (int i = 8; i < 16; i++)
+        {
+            lsb = (lsb << 8) | (data[i] & 0xff);
+        }
+        this.mostSigBits = msb;
+        this.leastSigBits = lsb;
+    }
+
+    /**
+     * 使用指定的数据构造新的 UUID。
+     *
+     * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位
+     * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位
+     */
+    public UUID(long mostSigBits, long leastSigBits)
+    {
+        this.mostSigBits = mostSigBits;
+        this.leastSigBits = leastSigBits;
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。
+     *
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID fastUUID()
+    {
+        return randomUUID(false);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     *
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID()
+    {
+        return randomUUID(true);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     *
+     * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID(boolean isSecure)
+    {
+        final Random ng = isSecure ? Holder.numberGenerator : getRandom();
+
+        byte[] randomBytes = new byte[16];
+        ng.nextBytes(randomBytes);
+        randomBytes[6] &= 0x0f; /* clear version */
+        randomBytes[6] |= 0x40; /* set to version 4 */
+        randomBytes[8] &= 0x3f; /* clear variant */
+        randomBytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(randomBytes);
+    }
+
+    /**
+     * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。
+     *
+     * @param name 用于构造 UUID 的字节数组。
+     *
+     * @return 根据指定数组生成的 {@code UUID}
+     */
+    public static UUID nameUUIDFromBytes(byte[] name)
+    {
+        MessageDigest md;
+        try
+        {
+            md = MessageDigest.getInstance("MD5");
+        }
+        catch (NoSuchAlgorithmException nsae)
+        {
+            throw new InternalError("MD5 not supported");
+        }
+        byte[] md5Bytes = md.digest(name);
+        md5Bytes[6] &= 0x0f; /* clear version */
+        md5Bytes[6] |= 0x30; /* set to version 3 */
+        md5Bytes[8] &= 0x3f; /* clear variant */
+        md5Bytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(md5Bytes);
+    }
+
+    /**
+     * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。
+     *
+     * @param name 指定 {@code UUID} 字符串
+     * @return 具有指定值的 {@code UUID}
+     * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常
+     *
+     */
+    public static UUID fromString(String name)
+    {
+        String[] components = name.split("-");
+        if (components.length != 5)
+        {
+            throw new IllegalArgumentException("Invalid UUID string: " + name);
+        }
+        for (int i = 0; i < 5; i++)
+        {
+            components[i] = "0x" + components[i];
+        }
+
+        long mostSigBits = Long.decode(components[0]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[1]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[2]).longValue();
+
+        long leastSigBits = Long.decode(components[3]).longValue();
+        leastSigBits <<= 48;
+        leastSigBits |= Long.decode(components[4]).longValue();
+
+        return new UUID(mostSigBits, leastSigBits);
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最低有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中的最低有效 64 位。
+     */
+    public long getLeastSignificantBits()
+    {
+        return leastSigBits;
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最高有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中最高有效 64 位。
+     */
+    public long getMostSignificantBits()
+    {
+        return mostSigBits;
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。
+     * <p>
+     * 版本号具有以下含意:
+     * <ul>
+     * <li>1 基于时间的 UUID
+     * <li>2 DCE 安全 UUID
+     * <li>3 基于名称的 UUID
+     * <li>4 随机生成的 UUID
+     * </ul>
+     *
+     * @return 此 {@code UUID} 的版本号
+     */
+    public int version()
+    {
+        // Version is bits masked by 0x000000000000F000 in MS long
+        return (int) ((mostSigBits >> 12) & 0x0f);
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。
+     * <p>
+     * 变体号具有以下含意:
+     * <ul>
+     * <li>0 为 NCS 向后兼容保留
+     * <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF&nbsp;RFC&nbsp;4122</a>(Leach-Salz), 用于此类
+     * <li>6 保留,微软向后兼容
+     * <li>7 保留供以后定义使用
+     * </ul>
+     *
+     * @return 此 {@code UUID} 相关联的变体号
+     */
+    public int variant()
+    {
+        // This field is composed of a varying number of bits.
+        // 0 - - Reserved for NCS backward compatibility
+        // 1 0 - The IETF aka Leach-Salz variant (used by this class)
+        // 1 1 0 Reserved, Microsoft backward compatibility
+        // 1 1 1 Reserved for future definition.
+        return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63));
+    }
+
+    /**
+     * 与此 UUID 相关联的时间戳值。
+     *
+     * <p>
+     * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br>
+     * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。
+     *
+     * <p>
+     * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。
+     */
+    public long timestamp() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return (mostSigBits & 0x0FFFL) << 48//
+                | ((mostSigBits >> 16) & 0x0FFFFL) << 32//
+                | mostSigBits >>> 32;
+    }
+
+    /**
+     * 与此 UUID 相关联的时钟序列值。
+     *
+     * <p>
+     * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。
+     * <p>
+     * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出
+     * UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的时钟序列
+     *
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public int clockSequence() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48);
+    }
+
+    /**
+     * 与此 UUID 相关的节点值。
+     *
+     * <p>
+     * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。
+     * <p>
+     * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的节点值
+     *
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public long node() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return leastSigBits & 0x0000FFFFFFFFFFFFL;
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     *
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     *
+     * </blockquote>
+     *
+     * @return 此{@code UUID} 的字符串表现形式
+     * @see #toString(boolean)
+     */
+    @Override
+    public String toString()
+    {
+        return toString(false);
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     *
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     *
+     * </blockquote>
+     *
+     * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串
+     * @return 此{@code UUID} 的字符串表现形式
+     */
+    public String toString(boolean isSimple)
+    {
+        final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36);
+        // time_low
+        builder.append(digits(mostSigBits >> 32, 8));
+        if (false == isSimple)
+        {
+            builder.append('-');
+        }
+        // time_mid
+        builder.append(digits(mostSigBits >> 16, 4));
+        if (false == isSimple)
+        {
+            builder.append('-');
+        }
+        // time_high_and_version
+        builder.append(digits(mostSigBits, 4));
+        if (false == isSimple)
+        {
+            builder.append('-');
+        }
+        // variant_and_sequence
+        builder.append(digits(leastSigBits >> 48, 4));
+        if (false == isSimple)
+        {
+            builder.append('-');
+        }
+        // node
+        builder.append(digits(leastSigBits, 12));
+
+        return builder.toString();
+    }
+
+    /**
+     * 返回此 UUID 的哈希码。
+     *
+     * @return UUID 的哈希码值。
+     */
+    @Override
+    public int hashCode()
+    {
+        long hilo = mostSigBits ^ leastSigBits;
+        return ((int) (hilo >> 32)) ^ (int) hilo;
+    }
+
+    /**
+     * 将此对象与指定对象比较。
+     * <p>
+     * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。
+     *
+     * @param obj 要与之比较的对象
+     *
+     * @return 如果对象相同,则返回 {@code true};否则返回 {@code false}
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if ((null == obj) || (obj.getClass() != UUID.class))
+        {
+            return false;
+        }
+        UUID id = (UUID) obj;
+        return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits);
+    }
+
+    // Comparison Operations
+
+    /**
+     * 将此 UUID 与指定的 UUID 比较。
+     *
+     * <p>
+     * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。
+     *
+     * @param val 与此 UUID 比较的 UUID
+     *
+     * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。
+     *
+     */
+    @Override
+    public int compareTo(UUID val)
+    {
+        // The ordering is intentionally set up so that the UUIDs
+        // can simply be numerically compared as two numbers
+        return (this.mostSigBits < val.mostSigBits ? -1 : //
+                (this.mostSigBits > val.mostSigBits ? 1 : //
+                        (this.leastSigBits < val.leastSigBits ? -1 : //
+                                (this.leastSigBits > val.leastSigBits ? 1 : //
+                                        0))));
+    }
+
+    // -------------------------------------------------------------------------------------------------------------------
+    // Private method start
+    /**
+     * 返回指定数字对应的hex值
+     *
+     * @param val 值
+     * @param digits 位
+     * @return 值
+     */
+    private static String digits(long val, int digits)
+    {
+        long hi = 1L << (digits * 4);
+        return Long.toHexString(hi | (val & (hi - 1))).substring(1);
+    }
+
+    /**
+     * 检查是否为time-based版本UUID
+     */
+    private void checkTimeBase()
+    {
+        if (version() != 1)
+        {
+            throw new UnsupportedOperationException("Not a time-based UUID");
+        }
+    }
+
+    /**
+     * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
+     *
+     * @return {@link SecureRandom}
+     */
+    public static SecureRandom getSecureRandom()
+    {
+        try
+        {
+            return SecureRandom.getInstance("SHA1PRNG");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new UtilException(e);
+        }
+    }
+
+    /**
+     * 获取随机数生成器对象<br>
+     * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
+     *
+     * @return {@link ThreadLocalRandom}
+     */
+    public static ThreadLocalRandom getRandom()
+    {
+        return ThreadLocalRandom.current();
+    }
+}

+ 86 - 0
master/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)
+        {
+            srcCharset = 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();
+    }
+}

+ 999 - 0
master/src/main/java/com/ruoyi/common/core/text/Convert.java

@@ -0,0 +1,999 @@
+package com.ruoyi.common.core.text;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.text.NumberFormat;
+import java.util.Set;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 类型转换器
+ *
+ * @author ruoyi
+ */
+public class Convert
+{
+    /**
+     * 转换为字符串<br>
+     * 如果给定的值为null,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static String toStr(Object value, String defaultValue)
+    {
+        if (null == value)
+        {
+            return defaultValue;
+        }
+        if (value instanceof String)
+        {
+            return (String) value;
+        }
+        return value.toString();
+    }
+
+    /**
+     * 转换为字符串<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static String toStr(Object value)
+    {
+        return toStr(value, null);
+    }
+
+    /**
+     * 转换为字符<br>
+     * 如果给定的值为null,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Character toChar(Object value, Character defaultValue)
+    {
+        if (null == value)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Character)
+        {
+            return (Character) value;
+        }
+
+        final String valueStr = toStr(value, null);
+        return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0);
+    }
+
+    /**
+     * 转换为字符<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Character toChar(Object value)
+    {
+        return toChar(value, null);
+    }
+
+    /**
+     * 转换为byte<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Byte toByte(Object value, Byte defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Byte)
+        {
+            return (Byte) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).byteValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Byte.parseByte(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为byte<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Byte toByte(Object value)
+    {
+        return toByte(value, null);
+    }
+
+    /**
+     * 转换为Short<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Short toShort(Object value, Short defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Short)
+        {
+            return (Short) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).shortValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Short.parseShort(valueStr.trim());
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Short<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Short toShort(Object value)
+    {
+        return toShort(value, null);
+    }
+
+    /**
+     * 转换为Number<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Number toNumber(Object value, Number defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Number)
+        {
+            return (Number) value;
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return NumberFormat.getInstance().parse(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Number<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Number toNumber(Object value)
+    {
+        return toNumber(value, null);
+    }
+
+    /**
+     * 转换为int<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Integer toInt(Object value, Integer defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Integer)
+        {
+            return (Integer) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).intValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Integer.parseInt(valueStr.trim());
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为int<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Integer toInt(Object value)
+    {
+        return toInt(value, null);
+    }
+
+    /**
+     * 转换为Integer数组<br>
+     *
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static Integer[] toIntArray(String str)
+    {
+        return toIntArray(",", str);
+    }
+
+    /**
+     * 转换为Long数组<br>
+     *
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static Long[] toLongArray(String str)
+    {
+        return toLongArray(",", str);
+    }
+
+    /**
+     * 转换为Integer数组<br>
+     *
+     * @param split 分隔符
+     * @param split 被转换的值
+     * @return 结果
+     */
+    public static Integer[] toIntArray(String split, String str)
+    {
+        if (StringUtils.isEmpty(str))
+        {
+            return new Integer[] {};
+        }
+        String[] arr = str.split(split);
+        final Integer[] ints = new Integer[arr.length];
+        for (int i = 0; i < arr.length; i++)
+        {
+            final Integer v = toInt(arr[i], 0);
+            ints[i] = v;
+        }
+        return ints;
+    }
+
+    /**
+     * 转换为Long数组<br>
+     *
+     * @param split 分隔符
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static Long[] toLongArray(String split, String str)
+    {
+        if (StringUtils.isEmpty(str))
+        {
+            return new Long[] {};
+        }
+        String[] arr = str.split(split);
+        final Long[] longs = new Long[arr.length];
+        for (int i = 0; i < arr.length; i++)
+        {
+            final Long v = toLong(arr[i], null);
+            longs[i] = v;
+        }
+        return longs;
+    }
+
+    /**
+     * 转换为String数组<br>
+     *
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static String[] toStrArray(String str)
+    {
+        return toStrArray(",", str);
+    }
+
+    /**
+     * 转换为String数组<br>
+     *
+     * @param split 分隔符
+     * @param split 被转换的值
+     * @return 结果
+     */
+    public static String[] toStrArray(String split, String str)
+    {
+        return str.split(split);
+    }
+
+    /**
+     * 转换为long<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Long toLong(Object value, Long defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Long)
+        {
+            return (Long) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).longValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            // 支持科学计数法
+            return new BigDecimal(valueStr.trim()).longValue();
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为long<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Long toLong(Object value)
+    {
+        return toLong(value, null);
+    }
+
+    /**
+     * 转换为double<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Double toDouble(Object value, Double defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Double)
+        {
+            return (Double) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).doubleValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            // 支持科学计数法
+            return new BigDecimal(valueStr.trim()).doubleValue();
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为double<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Double toDouble(Object value)
+    {
+        return toDouble(value, null);
+    }
+
+    /**
+     * 转换为Float<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Float toFloat(Object value, Float defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Float)
+        {
+            return (Float) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).floatValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Float.parseFloat(valueStr.trim());
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Float<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Float toFloat(Object value)
+    {
+        return toFloat(value, null);
+    }
+
+    /**
+     * 转换为boolean<br>
+     * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Boolean toBool(Object value, Boolean defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Boolean)
+        {
+            return (Boolean) value;
+        }
+        String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        valueStr = valueStr.trim().toLowerCase();
+        switch (valueStr)
+        {
+            case "true":
+                return true;
+            case "false":
+                return false;
+            case "yes":
+                return true;
+            case "ok":
+                return true;
+            case "no":
+                return false;
+            case "1":
+                return true;
+            case "0":
+                return false;
+            default:
+                return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为boolean<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Boolean toBool(Object value)
+    {
+        return toBool(value, null);
+    }
+
+    /**
+     * 转换为Enum对象<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     *
+     * @param clazz Enum的Class
+     * @param value 值
+     * @param defaultValue 默认值
+     * @return Enum
+     */
+    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value, E defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (clazz.isAssignableFrom(value.getClass()))
+        {
+            @SuppressWarnings("unchecked")
+            E myE = (E) value;
+            return myE;
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Enum.valueOf(clazz, valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Enum对象<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     *
+     * @param clazz Enum的Class
+     * @param value 值
+     * @return Enum
+     */
+    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value)
+    {
+        return toEnum(clazz, value, null);
+    }
+
+    /**
+     * 转换为BigInteger<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static BigInteger toBigInteger(Object value, BigInteger defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof BigInteger)
+        {
+            return (BigInteger) value;
+        }
+        if (value instanceof Long)
+        {
+            return BigInteger.valueOf((Long) value);
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return new BigInteger(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为BigInteger<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static BigInteger toBigInteger(Object value)
+    {
+        return toBigInteger(value, null);
+    }
+
+    /**
+     * 转换为BigDecimal<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof BigDecimal)
+        {
+            return (BigDecimal) value;
+        }
+        if (value instanceof Long)
+        {
+            return new BigDecimal((Long) value);
+        }
+        if (value instanceof Double)
+        {
+            return new BigDecimal((Double) value);
+        }
+        if (value instanceof Integer)
+        {
+            return new BigDecimal((Integer) value);
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return new BigDecimal(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为BigDecimal<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static BigDecimal toBigDecimal(Object value)
+    {
+        return toBigDecimal(value, null);
+    }
+
+    /**
+     * 将对象转为字符串<br>
+     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+     *
+     * @param obj 对象
+     * @return 字符串
+     */
+    public static String utf8Str(Object obj)
+    {
+        return str(obj, CharsetKit.CHARSET_UTF_8);
+    }
+
+    /**
+     * 将对象转为字符串<br>
+     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+     *
+     * @param obj 对象
+     * @param charsetName 字符集
+     * @return 字符串
+     */
+    public static String str(Object obj, String charsetName)
+    {
+        return str(obj, Charset.forName(charsetName));
+    }
+
+    /**
+     * 将对象转为字符串<br>
+     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+     *
+     * @param obj 对象
+     * @param charset 字符集
+     * @return 字符串
+     */
+    public static String str(Object obj, Charset charset)
+    {
+        if (null == obj)
+        {
+            return null;
+        }
+
+        if (obj instanceof String)
+        {
+            return (String) obj;
+        }
+        else if (obj instanceof byte[] || obj instanceof Byte[])
+        {
+            return str((Byte[]) obj, charset);
+        }
+        else if (obj instanceof ByteBuffer)
+        {
+            return str((ByteBuffer) obj, charset);
+        }
+        return obj.toString();
+    }
+
+    /**
+     * 将byte数组转为字符串
+     *
+     * @param bytes byte数组
+     * @param charset 字符集
+     * @return 字符串
+     */
+    public static String str(byte[] bytes, String charset)
+    {
+        return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset));
+    }
+
+    /**
+     * 解码字节码
+     *
+     * @param data 字符串
+     * @param charset 字符集,如果此字段为空,则解码的结果取决于平台
+     * @return 解码后的字符串
+     */
+    public static String str(byte[] data, Charset charset)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        if (null == charset)
+        {
+            return new String(data);
+        }
+        return new String(data, charset);
+    }
+
+    /**
+     * 将编码的byteBuffer数据转换为字符串
+     *
+     * @param data 数据
+     * @param charset 字符集,如果为空使用当前系统字符集
+     * @return 字符串
+     */
+    public static String str(ByteBuffer data, String charset)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        return str(data, Charset.forName(charset));
+    }
+
+    /**
+     * 将编码的byteBuffer数据转换为字符串
+     *
+     * @param data 数据
+     * @param charset 字符集,如果为空使用当前系统字符集
+     * @return 字符串
+     */
+    public static String str(ByteBuffer data, Charset charset)
+    {
+        if (null == charset)
+        {
+            charset = Charset.defaultCharset();
+        }
+        return charset.decode(data).toString();
+    }
+
+    // ----------------------------------------------------------------------- 全角半角转换
+    /**
+     * 半角转全角
+     *
+     * @param input String.
+     * @return 全角字符串.
+     */
+    public static String toSBC(String input)
+    {
+        return toSBC(input, null);
+    }
+
+    /**
+     * 半角转全角
+     *
+     * @param input String
+     * @param notConvertSet 不替换的字符集合
+     * @return 全角字符串.
+     */
+    public static String toSBC(String input, Set<Character> notConvertSet)
+    {
+        char c[] = input.toCharArray();
+        for (int i = 0; i < c.length; i++)
+        {
+            if (null != notConvertSet && notConvertSet.contains(c[i]))
+            {
+                // 跳过不替换的字符
+                continue;
+            }
+
+            if (c[i] == ' ')
+            {
+                c[i] = '\u3000';
+            }
+            else if (c[i] < '\177')
+            {
+                c[i] = (char) (c[i] + 65248);
+
+            }
+        }
+        return new String(c);
+    }
+
+    /**
+     * 全角转半角
+     *
+     * @param input String.
+     * @return 半角字符串
+     */
+    public static String toDBC(String input)
+    {
+        return toDBC(input, null);
+    }
+
+    /**
+     * 替换全角为半角
+     *
+     * @param text 文本
+     * @param notConvertSet 不替换的字符集合
+     * @return 替换后的字符
+     */
+    public static String toDBC(String text, Set<Character> notConvertSet)
+    {
+        char c[] = text.toCharArray();
+        for (int i = 0; i < c.length; i++)
+        {
+            if (null != notConvertSet && notConvertSet.contains(c[i]))
+            {
+                // 跳过不替换的字符
+                continue;
+            }
+
+            if (c[i] == '\u3000')
+            {
+                c[i] = ' ';
+            }
+            else if (c[i] > '\uFF00' && c[i] < '\uFF5F')
+            {
+                c[i] = (char) (c[i] - 65248);
+            }
+        }
+        String returnString = new String(c);
+
+        return returnString;
+    }
+
+    /**
+     * 数字金额大写转换 先写个完整的然后将如零拾替换成零
+     *
+     * @param n 数字
+     * @return 中文大写数字
+     */
+    public static String digitUppercase(double n)
+    {
+        String[] fraction = { "角", "分" };
+        String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };
+        String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } };
+
+        String head = n < 0 ? "负" : "";
+        n = Math.abs(n);
+
+        String s = "";
+        for (int i = 0; i < fraction.length; i++)
+        {
+            s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
+        }
+        if (s.length() < 1)
+        {
+            s = "整";
+        }
+        int integerPart = (int) Math.floor(n);
+
+        for (int i = 0; i < unit[0].length && integerPart > 0; i++)
+        {
+            String p = "";
+            for (int j = 0; j < unit[1].length && n > 0; j++)
+            {
+                p = digit[integerPart % 10] + unit[1][j] + p;
+                integerPart = integerPart / 10;
+            }
+            s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s;
+        }
+        return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整");
+    }
+}

+ 92 - 0
master/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();
+    }
+}

+ 36 - 0
master/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));
+    }
+}

+ 30 - 0
master/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;
+    }
+}

+ 97 - 0
master/src/main/java/com/ruoyi/common/exception/BaseException.java

@@ -0,0 +1,97 @@
+package com.ruoyi.common.exception;
+
+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;
+    }
+}

+ 43 - 0
master/src/main/java/com/ruoyi/common/exception/CustomException.java

@@ -0,0 +1,43 @@
+package com.ruoyi.common.exception;
+
+/**
+ * 自定义异常
+ *
+ * @author ruoyi
+ */
+public class CustomException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    private Integer code;
+
+    private String message;
+
+    public CustomException(String message)
+    {
+        this.message = message;
+    }
+
+    public CustomException(String message, Integer code)
+    {
+        this.message = message;
+        this.code = code;
+    }
+
+    public CustomException(String message, Throwable e)
+    {
+        super(message, e);
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public Integer getCode()
+    {
+        return code;
+    }
+}

+ 15 - 0
master/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()
+    {
+    }
+}

+ 26 - 0
master/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);
+    }
+}

+ 19 - 0
master/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.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
master/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
master/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 });
+    }
+}

+ 71 - 0
master/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java

@@ -0,0 +1,71 @@
+package com.ruoyi.common.exception.file;
+
+import java.util.Arrays;
+import org.apache.commons.fileupload.FileUploadException;
+
+/**
+ * 文件上传 误异常类
+ *
+ * @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 : [" + filename + "], extension : [" + extension + "], allowed 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);
+        }
+    }
+}

+ 34 - 0
master/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
master/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
master/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
master/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.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
master/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);
+    }
+}

+ 52 - 0
master/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.equalsAnyIgnoreCase(request.getContentType(),
+                MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE))
+        {
+            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
+        }
+        if (null == requestWrapper)
+        {
+            chain.doFilter(request, response);
+        }
+        else
+        {
+            chain.doFilter(requestWrapper, response);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}

+ 72 - 0
master/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java

@@ -0,0 +1,72 @@
+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;
+
+/**
+ * 构建可重复读取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("UTF-8");
+        response.setCharacterEncoding("UTF-8");
+
+        body = HttpHelper.getBodyString(request).getBytes("UTF-8");
+    }
+
+    @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 boolean isFinished()
+            {
+                return false;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return false;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener)
+            {
+
+            }
+        };
+    }
+}

+ 97 - 0
master/src/main/java/com/ruoyi/common/filter/XssFilter.java

@@ -0,0 +1,97 @@
+package com.ruoyi.common.filter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+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;
+
+/**
+ * 防止XSS攻击的过滤器
+ *
+ * @author ruoyi
+ */
+public class XssFilter implements Filter
+{
+    /**
+     * 排除链接
+     */
+    public List<String> excludes = new ArrayList<>();
+
+    /**
+     * xss过滤开关
+     */
+    public boolean enabled = false;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        String tempExcludes = filterConfig.getInitParameter("excludes");
+        String tempEnabled = filterConfig.getInitParameter("enabled");
+        if (StringUtils.isNotEmpty(tempExcludes))
+        {
+            String[] url = tempExcludes.split(",");
+            for (int i = 0; url != null && i < url.length; i++)
+            {
+                excludes.add(url[i]);
+            }
+        }
+        if (StringUtils.isNotEmpty(tempEnabled))
+        {
+            enabled = Boolean.valueOf(tempEnabled);
+        }
+    }
+
+    @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)
+    {
+        if (!enabled)
+        {
+            return true;
+        }
+        if (excludes == null || excludes.isEmpty())
+        {
+            return false;
+        }
+        String url = request.getServletPath();
+        for (String pattern : excludes)
+        {
+            Pattern p = Pattern.compile("^" + pattern);
+            Matcher m = p.matcher(url);
+            if (m.find())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}

+ 105 - 0
master/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java

@@ -0,0 +1,105 @@
+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[] escapseValues = new String[length];
+            for (int i = 0; i < length; i++)
+            {
+                // 防xss攻击和过滤前后空格
+                escapseValues[i] = EscapeUtil.clean(values[i]).trim();
+            }
+            return escapseValues;
+        }
+        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();
+        final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("utf-8"));
+        return new ServletInputStream()
+        {
+            @Override
+            public boolean isFinished()
+            {
+                return true;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return true;
+            }
+
+            @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 MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header)
+                || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(header);
+    }
+}

+ 32 - 0
master/src/main/java/com/ruoyi/common/sendEmail/IMailService.java

@@ -0,0 +1,32 @@
+package com.ruoyi.common.sendEmail;
+
+public interface IMailService {
+    /**
+     * 发送文本邮件
+     *
+     * @param to      收件人
+     * @param subject 主题
+     * @param content 内容
+     */
+    void sendSimpleMail(String to, String subject, String content);
+
+    /**
+     * 发送HTML邮件
+     *
+     * @param to      收件人
+     * @param subject 主题
+     * @param content 内容
+     */
+    public void sendHtmlMail(String to, String subject, String content);
+
+
+    /**
+     * 发送带附件的邮件
+     *
+     * @param to       收件人
+     * @param subject  主题
+     * @param content  内容
+     * @param filePath 附件
+     */
+    public void sendAttachmentsMail(String to, String subject, String content, String filePath);
+}

+ 130 - 0
master/src/main/java/com/ruoyi/common/sendEmail/IMailServiceImpl.java

@@ -0,0 +1,130 @@
+package com.ruoyi.common.sendEmail;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.mail.MailException;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import java.io.File;
+
+/**
+ * @Description:
+ * @param: $params$
+ * @return: $returns$
+ */
+@Service("mailService")
+public class IMailServiceImpl implements IMailService {
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * Spring Boot 提供了一个发送邮件的简单抽象,使用的是下面这个接口,这里直接注入即可使用
+     */
+    @Resource
+    private JavaMailSender mailSender;
+
+    /**
+     * 配置文件中我的qq邮箱
+     */
+    @Value("${spring.mail.from}")
+    private String from;
+
+    /**
+     * 简单文本邮件
+     *
+     * @param to      收件人
+     * @param subject 主题
+     * @param content 内容
+     */
+    @Override
+    public void sendSimpleMail(String to, String subject, String content) {
+        try {
+            //创建SimpleMailMessage对象
+            SimpleMailMessage message = new SimpleMailMessage();
+            //邮件发送人
+            message.setFrom(from);
+            //邮件接收人
+            message.setTo(to);
+            //邮件主题
+            message.setSubject(subject);
+            //邮件内容
+            message.setText(content);
+            //发送邮件
+            mailSender.send(message);
+            logger.info("邮件已经发送。");
+        } catch (MailException e) {
+            logger.error("发送邮件时发生异常!", e);
+        }
+    }
+
+    /**
+     * html邮件
+     *
+     * @param to      收件人
+     * @param subject 主题
+     * @param content 内容
+     */
+    @Override
+    public void sendHtmlMail(String to, String subject, String content) {
+        //获取MimeMessage对象
+        MimeMessage message = mailSender.createMimeMessage();
+        MimeMessageHelper messageHelper;
+        try {
+            messageHelper = new MimeMessageHelper(message, true);
+            //邮件发送人
+            messageHelper.setFrom(from);
+            //邮件接收人
+            messageHelper.setTo(to);
+            //邮件主题
+            message.setSubject(subject);
+            //邮件内容,html格式
+            messageHelper.setText(content, true);
+            //发送
+            mailSender.send(message);
+            //日志信息
+            logger.info("邮件已经发送。");
+        } catch (MessagingException e) {
+            logger.error("发送邮件时发生异常!", e);
+        }
+    }
+
+    /**
+     * 带附件的邮件
+     *
+     * @param to       收件人
+     * @param subject  主题
+     * @param content  内容
+     * @param filePath 附件
+     */
+    @Override
+    public void sendAttachmentsMail(String to, String subject, String content, String filePath) {
+        MimeMessage message = mailSender.createMimeMessage();
+        try {
+            MimeMessageHelper helper = new MimeMessageHelper(message, true);
+            helper.setFrom(from);
+            helper.setTo(to);
+            helper.setSubject(subject);
+            helper.setText(content, true);
+
+            FileSystemResource file = new FileSystemResource(new File(filePath));
+            String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
+            helper.addAttachment(fileName, file);
+            mailSender.send(message);
+            //日志信息
+            logger.info("邮件已经发送。");
+        } catch (MessagingException e) {
+            logger.error("发送邮件时发生异常!", e);
+        }
+
+
+    }
+}

+ 150 - 0
master/src/main/java/com/ruoyi/common/thread/SendEmailThead.java

@@ -0,0 +1,150 @@
+package com.ruoyi.common.thread;
+
+
+import com.ruoyi.common.sendEmail.IMailService;
+import com.ruoyi.project.sems.domain.TApprove;
+
+import java.util.Date;
+
+public class SendEmailThead implements Runnable {
+
+    private IMailService mailService;
+
+    private String email;
+    private String apNo;
+
+    private String username;
+    private String usernameEN;
+    private TApprove tApprove;
+    private String sendType;
+
+    public SendEmailThead(String email, String apNo, String username, String usernameEN, IMailService mailService, TApprove tApprove, String sendType) {
+        this.email = email;
+        this.apNo = apNo;
+        this.username = username;
+        this.usernameEN = usernameEN;
+        this.mailService = mailService;
+        this.tApprove = tApprove;
+        this.sendType = sendType;
+    }
+
+    /*
+    * 发送邮件
+    */
+    @Override
+    public void run() {
+        if (sendType.equals("year")) {
+            year();
+        }else if (sendType.equals("month")) {
+            month();
+        }
+    }
+
+    /*
+     * 发送年度邮件
+     */
+    public void year() {
+        String devType = null;
+        String devTypeEN = null;
+        switch (tApprove.getDevType().toString()) {
+            case "1":
+                devType = "压力容器";
+                devTypeEN = "Pressure Vessel";
+                break;
+            case "2":
+                devType = "压力管道";
+                devTypeEN = "Pressure Pipeline";
+                break;
+        }
+        String approveType = "生成" + devType + "年度检查报告";
+        String approveTypeEN = "Generate annual inspection report of " + devTypeEN;
+        //写html开始内容
+        String start = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title></title></head><body><div style=\"background-color:#ECECEC; padding: 35px;\">" +
+                "<table cellpadding=\"0\" align=\"center\"" +
+                "style=\"width: 600px; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;\">" +
+                "<tbody><tr><th valign=\"middle\" style=\"height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #42a3d3; background-color: #49bcff; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;\">" +
+                "<font face=\"微软雅黑\" size=\"5\" style=\"color: rgb(255, 255, 255); \">特种设备信息管理系统 </font><font face=\"微软雅黑\" size=\"3\" style=\"color: rgb(255, 255, 255); \">Special Equipment Management System (SEMS)</font></th></tr>";
+        //表html中间内容
+        String prime = "";
+        String center = "<tr><td><div style=\"padding:25px 35px 40px; background-color:#fff;\"><h2 style=\"margin: 5px 0px; \">" +
+                "<font color=\"#333333\" style=\"line-height: 20px; \"><font style=\"line-height: 22px; \" size=\"4\">" +
+                "亲爱的 username</font><br><font style=\"line-height: 22px; \" size=\"4\">" +
+                "Dear usernameEN</font></font></h2>" +
+                "<p>您有一个新的待办任务:<br>" +
+                "You have a new to-do task:<br>" +
+                "任务编号:<b>apNo</b><br>" +
+                "Task Number: <b>apNoEN</b><br>" +
+                "申请设备类型:<b>devType</b><br>" +
+                "Equipment Type: <b>devTypeEN</b><br>" +
+                "审批类型:<b>approveType</b><br>" +
+                "Application Category: <b>approveTypeEN</b><br>" +
+                "请登录<a href=\"https://cpms.basf-ypc.net.cn/cpms/index.html#/approve/pending\">特种设备信息管理系统</a>查看。<br>" +
+                "Please log in the <a href=\"https://cpms.basf-ypc.net.cn/cpms/index.html#/approve/pending\">SEMS</a> to handle it.</p>" +
+                "<p align=\"right\">date</p>" +
+                "<div style=\"width:700px;margin:0 auto;\">" +
+                "<div style=\"padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;\">" +
+                "<p>此为系统邮件,请勿回复<br>This e-Mail is an automatic reminder sent by SEMS, please do not reply</p>" +
+                "</div></div></div></td></tr>";
+        String one = center.replaceFirst("username", username);
+        String two = one.replaceFirst("usernameEN", usernameEN);
+        String three = two.replaceFirst("devType", devType);
+        String four = three.replaceFirst("apNo", apNo);
+        String five = four.replaceFirst("apNoEN", apNo);
+        String six = five.replaceFirst("devTypeEN", devTypeEN);
+        String seven = six.replaceFirst("approveType", approveType);
+        String eight = seven.replaceFirst("approveTypeEN", approveTypeEN);
+        String result = eight.replaceFirst("date", String.valueOf(new Date()));
+        prime = prime + result;
+        //写html结尾内容
+        String end = "</tbody></table></div></body></html>";
+        //拼接html
+        String html = start + prime + end;
+        mailService.sendHtmlMail(email, "特种设备:您有一个新的待办任务 SEMS:You have a new to-do task (" + apNo + ")", html);
+    }
+
+    /*
+     * 发送月度邮件
+     */
+    public void month() {
+        String approveType = "生成月度检查报告";
+        String approveTypeEN = "Generate monthly inspection report";
+        //写html开始内容
+        String start = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title></title></head><body><div style=\"background-color:#ECECEC; padding: 35px;\">" +
+                "<table cellpadding=\"0\" align=\"center\"" +
+                "style=\"width: 600px; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;\">" +
+                "<tbody><tr><th valign=\"middle\" style=\"height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #42a3d3; background-color: #49bcff; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;\">" +
+                "<font face=\"微软雅黑\" size=\"5\" style=\"color: rgb(255, 255, 255); \">特种设备信息管理系统 </font><font face=\"微软雅黑\" size=\"3\" style=\"color: rgb(255, 255, 255); \">Special Equipment Management System (SEMS)</font></th></tr>";
+        //表html中间内容
+        String prime = "";
+        String center = "<tr><td><div style=\"padding:25px 35px 40px; background-color:#fff;\"><h2 style=\"margin: 5px 0px; \">" +
+                "<font color=\"#333333\" style=\"line-height: 20px; \"><font style=\"line-height: 22px; \" size=\"4\">" +
+                "亲爱的 username</font><br><font style=\"line-height: 22px; \" size=\"4\">" +
+                "Dear usernameEN</font></font></h2>" +
+                "<p>您有一个新的待办任务:<br>" +
+                "You have a new to-do task:<br>" +
+                "任务编号:<b>apNo</b><br>" +
+                "Task Number: <b>apNoEN</b><br>" +
+                "审批类型:<b>approveType</b><br>" +
+                "Application Category: <b>approveTypeEN</b><br>" +
+                "请登录<a href=\"https://cpms.basf-ypc.net.cn/cpms/index.html#/approve/pending\">特种设备信息管理系统</a>查看。<br>" +
+                "Please log in the <a href=\"https://cpms.basf-ypc.net.cn/cpms/index.html#/approve/pending\">SEMS</a> to handle it.</p>" +
+                "<p align=\"right\">date</p>" +
+                "<div style=\"width:700px;margin:0 auto;\">" +
+                "<div style=\"padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;\">" +
+                "<p>此为系统邮件,请勿回复<br>This e-Mail is an automatic reminder sent by SEMS, please do not reply</p>" +
+                "</div></div></div></td></tr>";
+        String one = center.replaceFirst("username", username);
+        String two = one.replaceFirst("usernameEN", usernameEN);
+        String three = two.replaceFirst("apNo", apNo);
+        String four = three.replaceFirst("apNoEN", apNo);
+        String five = four.replaceFirst("approveType", approveType);
+        String six = five.replaceFirst("approveTypeEN", approveTypeEN);
+        String result = six.replaceFirst("date", String.valueOf(new Date()));
+        prime = prime + result;
+        //写html结尾内容
+        String end = "</tbody></table></div></body></html>";
+        //拼接html
+        String html = start + prime + end;
+        mailService.sendHtmlMail(email, "特种设备:您有一个新的待办任务 SEMS:You have a new to-do task (" + apNo + ")", html);
+    }
+}

+ 37 - 0
master/src/main/java/com/ruoyi/common/thread/SpecDevYlgdThread.java

@@ -0,0 +1,37 @@
+package com.ruoyi.common.thread;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.project.sems.domain.TSpecdevYlgd;
+import com.ruoyi.project.sems.domain.TSpecdevYlrq;
+import com.ruoyi.project.sems.service.ITSpecdevYlgdService;
+import com.ruoyi.project.sems.service.ITSpecdevYlrqService;
+
+import java.util.List;
+
+public class SpecDevYlgdThread implements Runnable {
+
+    private ITSpecdevYlgdService specdevYlgdService;
+
+    public SpecDevYlgdThread(ITSpecdevYlgdService specdevYlgdService) {
+        this.specdevYlgdService = specdevYlgdService;
+    }
+
+    @Override
+    public void run() {
+        List<TSpecdevYlgd> list = this.specdevYlgdService.list(new QueryWrapper<TSpecdevYlgd>()
+                .eq("del_flag", 0).eq("status", "1"));
+        for (TSpecdevYlgd tSpecdevYlgd : list) {
+            for (TSpecdevYlgd specdevYlgd : list) {
+                if (tSpecdevYlgd.getId() == specdevYlgd.getId() || StringUtils.isEmpty(tSpecdevYlgd.getDevno())
+                        || StringUtils.isEmpty(specdevYlgd.getDevno())) {
+                    continue;
+                }
+                if (tSpecdevYlgd.getDevno().equals(specdevYlgd.getDevno())) {
+                    specdevYlgd.setIsRepeat(1L);
+                    this.specdevYlgdService.updateTSpecdevYlgd(specdevYlgd);
+                }
+            }
+        }
+    }
+}

+ 47 - 0
master/src/main/java/com/ruoyi/common/thread/SpecDevYlrqThread.java

@@ -0,0 +1,47 @@
+package com.ruoyi.common.thread;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.project.sems.domain.TSpecdevYlrq;
+import com.ruoyi.project.sems.service.ITSpecdevYlrqService;
+
+import java.util.List;
+
+public class SpecDevYlrqThread implements Runnable {
+    private ITSpecdevYlrqService specdevYlrqService;
+
+    public SpecDevYlrqThread(ITSpecdevYlrqService specdevYlrqService) {
+        this.specdevYlrqService = specdevYlrqService;
+    }
+
+    @Override
+    public void run() {
+        List<TSpecdevYlrq> list = this.specdevYlrqService.list(new QueryWrapper<TSpecdevYlrq>()
+                .eq("del_flag", 0).eq("status", "1"));
+        /*boolean flag = false;
+        List<String> collect = list.stream().map(e -> {
+            return flag ? e.getPlantCode() : e.getDevno();
+        }).collect(Collectors.toMap(e -> e, e -> 1, (a, b) -> a + b)).entrySet().stream() // 所有 entry 对应的 Stream
+                .filter(entry -> entry.getValue() > 1) // 过滤出元素出现次数大于 1 的 entry
+                .map(entry -> entry.getKey()) // 获得 entry 的键(重复元素)对应的 Stream
+                .collect(Collectors.toList());// 转化为 List
+        for (String s : collect) {
+            TSpecdevYlrq tSpecdevYlrq = new TSpecdevYlrq();
+            tSpecdevYlrq.setDevno(s);
+            tSpecdevYlrq.setIsRepeat(1L);
+            this.specdevYlrqService.updateTSpecdevYlrq(tSpecdevYlrq);
+        }*/
+        for (TSpecdevYlrq tSpecdevYlrq : list) {
+            for (TSpecdevYlrq specdevYlrq : list) {
+                if (tSpecdevYlrq.getId() == specdevYlrq.getId() || StringUtils.isEmpty(tSpecdevYlrq.getDevno())
+                        || StringUtils.isEmpty(specdevYlrq.getDevno())) {
+                    continue;
+                }
+                if (tSpecdevYlrq.getDevno().equals(specdevYlrq.getDevno())) {
+                    specdevYlrq.setIsRepeat(1L);
+                    this.specdevYlrqService.updateTSpecdevYlrq(specdevYlrq);
+                }
+            }
+        }
+    }
+}

+ 59 - 0
master/src/main/java/com/ruoyi/common/thread/SpecOverLifeThead.java

@@ -0,0 +1,59 @@
+package com.ruoyi.common.thread;
+
+
+import com.ruoyi.project.sems.domain.TSpecdevYlrq;
+import com.ruoyi.project.sems.service.ITSpecdevYlrqService;
+import org.xhtmlrenderer.swing.DelegatingUserAgent;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+public class SpecOverLifeThead implements Runnable {
+    private ITSpecdevYlrqService specdevYlrqService;
+
+
+    public SpecOverLifeThead( ITSpecdevYlrqService specdevYlrqService) {
+       this.specdevYlrqService = specdevYlrqService;
+    }
+
+    /*
+    * 发送邮件
+    */
+    @Override
+    public void run() {
+        List<TSpecdevYlrq> list = specdevYlrqService.list();
+        for (TSpecdevYlrq t: list
+             ) {
+            String bigStr;
+            try {
+                bigStr = new BigDecimal(t.getDesYear()).toString();
+            } catch (Exception e) {
+                bigStr = "20";
+            }
+            try {
+                Date sub = t.getSubmitdate();
+                Date now = new Date();
+                SimpleDateFormat f = new SimpleDateFormat("yyyy");
+                String subStr = f.format(sub);
+                String nowStr = f.format(now);
+                System.out.println("nowStr:"+nowStr + "subStr:"+ subStr + "bigStr:"+bigStr);
+                if (new BigDecimal(bigStr).compareTo( new BigDecimal(nowStr).subtract(new BigDecimal(subStr))) == 1){
+                    t.setIsOverlife("否");
+                }else {
+                    t.setIsOverlife("是");
+                }
+                specdevYlrqService.updateTSpecdevYlrq(t);
+            }catch (Exception e) {
+                continue;
+            }
+        }
+
+    }
+
+
+
+
+
+}

+ 26 - 0
master/src/main/java/com/ruoyi/common/thread/TaskThread.java

@@ -0,0 +1,26 @@
+package com.ruoyi.common.thread;
+
+import com.ruoyi.project.monitor.domain.SysJob;
+import com.ruoyi.project.monitor.service.ISysJobService;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class TaskThread implements Runnable {
+
+    private ISysJobService jobService;
+
+    private SysJob job;
+    public TaskThread(SysJob job ,ISysJobService jobService ){
+        this.job = job;
+        this.jobService = jobService;
+    }
+
+    @Override
+    public void run() {
+        try {
+            this.jobService.run(job);
+        } catch (SchedulerException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 114 - 0
master/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 = new BigDecimal("1");
+        return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
+    }
+}

+ 230 - 0
master/src/main/java/com/ruoyi/common/utils/DateUtils.java

@@ -0,0 +1,230 @@
+package com.ruoyi.common.utils;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+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 String getDatePoor(Date endDate, Date nowDate)
+    {
+        long nd = 1000 * 24 * 60 * 60;
+        long nh = 1000 * 60 * 60;
+        long nm = 1000 * 60;
+        // long ns = 1000;
+        // 获得两个时间的毫秒时间差异
+        long diff = endDate.getTime() - nowDate.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 + "分钟";
+    }
+
+    /**
+     * 常规自动日期格式识别
+     * @param str 时间字符串
+     * @return Date
+     * @author dc
+     */
+    public static String getDateFormat(String str) throws ParseException {
+//        if (str.matches("[0-9]+")){
+//            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 日期格式
+//            Date date = dateFormat.parse("1900-01-01 00:00:00"); // 指定日期
+//            Date newDate = addDate(date, Long.parseLong(str)); // 指定日期加上天
+//            str =  new SimpleDateFormat("yyyy-MM-dd").format(newDate);
+//        }
+        boolean year = false;
+        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
+        if (pattern.matcher(str.substring(0, 4)).matches()) {
+            year = true;
+        }
+        StringBuilder sb = new StringBuilder();
+        int index = 0;
+        if (!year) {
+            if (str.contains("月") || str.contains("-") || str.contains("/") || str.contains(".")) {
+                if (Character.isDigit(str.charAt(0))) {
+                    index = 1;
+                }
+            } else {
+                index = 3;
+            }
+        }
+        for (int i = 0; i < str.length(); i++) {
+            char chr = str.charAt(i);
+            if (Character.isDigit(chr)) {
+                if (index == 0) {
+                    sb.append("y");
+                }
+                if (index == 1) {
+                    sb.append("M");
+                }
+                if (index == 2) {
+                    sb.append("d");
+                }
+                if (index == 3) {
+                    sb.append("H");
+                }
+                if (index == 4) {
+                    sb.append("m");
+                }
+                if (index == 5) {
+                    sb.append("s");
+                }
+                if (index == 6) {
+                    sb.append("S");
+                }
+            } else {
+                if (i > 0) {
+                    char lastChar = str.charAt(i - 1);
+                    if (Character.isDigit(lastChar)) {
+                        index++;
+                    }
+                }
+                sb.append(chr);
+            }
+        }
+        return sb.toString();
+    }
+    public static Date addDate(Date date, long day) throws ParseException {
+        long time = date.getTime(); // 得到指定日期的毫秒数
+        day = day * 24 * 60 * 60 * 1000; // 要加上的天数转换成毫秒数
+        time += day; // 相加得到新的毫秒数
+        return new Date(time); // 将毫秒数转换成日期
+    }
+
+}

+ 183 - 0
master/src/main/java/com/ruoyi/common/utils/DictUtils.java

@@ -0,0 +1,183 @@
+package com.ruoyi.common.utils;
+
+import java.util.Collection;
+import java.util.List;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.framework.redis.RedisCache;
+import com.ruoyi.project.system.domain.SysDictData;
+
+/**
+ * 字典工具类
+ *
+ * @author ruoyi
+ */
+public class DictUtils
+{
+    /**
+     * 分隔符
+     */
+    public static final String SEPARATOR = ",";
+
+    /**
+     * 设置字典缓存
+     *
+     * @param key 参数键
+     * @param dictDatas 字典数据列表
+     */
+    public static void setDictCache(String key, List<SysDictData> dictDatas)
+    {
+        SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
+    }
+
+    /**
+     * 获取字典缓存
+     *
+     * @param key 参数键
+     * @return dictDatas 字典数据列表
+     */
+    public static List<SysDictData> getDictCache(String key)
+    {
+        Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
+        if (StringUtils.isNotNull(cacheObj))
+        {
+            List<SysDictData> dictDatas = StringUtils.cast(cacheObj);
+            return dictDatas;
+        }
+        return null;
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     *
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue)
+    {
+        return getDictLabel(dictType, dictValue, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     *
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel)
+    {
+        return getDictValue(dictType, dictLabel, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     *
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+
+        if (StringUtils.containsAny(separator, dictValue) && StringUtils.isNotEmpty(datas))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String value : dictValue.split(separator))
+                {
+                    if (value.equals(dict.getDictValue()))
+                    {
+                        propertyString.append(dict.getDictLabel() + separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictValue.equals(dict.getDictValue()))
+                {
+                    return dict.getDictLabel();
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     *
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+
+        if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String label : dictLabel.split(separator))
+                {
+                    if (label.equals(dict.getDictLabel()))
+                    {
+                        propertyString.append(dict.getDictValue() + separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictLabel.equals(dict.getDictLabel()))
+                {
+                    return dict.getDictValue();
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 清空字典缓存
+     */
+    public static void clearDictCache()
+    {
+        Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(Constants.SYS_DICT_KEY + "*");
+        SpringUtils.getBean(RedisCache.class).deleteObject(keys);
+    }
+
+    /**
+     * 设置cache key
+     *
+     * @param configKey 参数键
+     * @return 缓存键key
+     */
+    public static String getCacheKey(String configKey)
+    {
+        return Constants.SYS_DICT_KEY + configKey;
+    }
+
+    public static String getDictVale(List<SysDictData> list , String value){
+        for (SysDictData p : list) {
+            if (p.getDictValue().equals(value)) {
+                return  p.getDictLabel();
+            }
+        }
+        return value;
+    }
+
+}

+ 40 - 0
master/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java

@@ -0,0 +1,40 @@
+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));
+        String str = sw.toString();
+        return str;
+    }
+
+    public static String getRootErrorMseeage(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);
+    }
+}

+ 51 - 0
master/src/main/java/com/ruoyi/common/utils/IdUtils.java

@@ -0,0 +1,51 @@
+package com.ruoyi.common.utils;
+
+import com.ruoyi.common.core.lang.UUID;
+
+/**
+ * ID生成器工具类
+ *
+ * @author ruoyi
+ */
+public class IdUtils
+{
+    /**
+     * 获取随机UUID
+     *
+     * @return 随机UUID
+     */
+    public static String randomUUID()
+    {
+        return UUID.randomUUID().toString();
+    }
+
+    /**
+     * 简化的UUID,去掉了横线
+     *
+     * @return 简化的UUID,去掉了横线
+     */
+    public static String simpleUUID()
+    {
+        return UUID.randomUUID().toString(true);
+    }
+
+    /**
+     * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID
+     *
+     * @return 随机UUID
+     */
+    public static String fastUUID()
+    {
+        return UUID.fastUUID().toString();
+    }
+
+    /**
+     * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID
+     *
+     * @return 简化的UUID,去掉了横线
+     */
+    public static String fastSimpleUUID()
+    {
+        return UUID.fastUUID().toString(true);
+    }
+}

+ 18 - 0
master/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
master/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());
+    }
+}

+ 90 - 0
master/src/main/java/com/ruoyi/common/utils/SecurityUtils.java

@@ -0,0 +1,90 @@
+package com.ruoyi.common.utils;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.exception.CustomException;
+import com.ruoyi.framework.security.LoginUser;
+
+/**
+ * 安全服务工具类
+ *
+ * @author ruoyi
+ */
+public class SecurityUtils
+{
+    /**
+     * 获取用户账户
+     **/
+    public static String getUsername()
+    {
+        try
+        {
+            return getLoginUser().getUsername();
+        }
+        catch (Exception e)
+        {
+            throw new CustomException("获取用户账户异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取用户
+     **/
+    public static LoginUser getLoginUser()
+    {
+        try
+        {
+            return (LoginUser) getAuthentication().getPrincipal();
+        }
+        catch (Exception e)
+        {
+            throw new CustomException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取Authentication
+     */
+    public static Authentication getAuthentication()
+    {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    /**
+     * 生成BCryptPasswordEncoder密码
+     *
+     * @param password 密码
+     * @return 加密字符串
+     */
+    public static String encryptPassword(String password)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.encode(password);
+    }
+
+    /**
+     * 判断密码是否相同
+     *
+     * @param rawPassword 真实密码
+     * @param encodedPassword 加密后字符
+     * @return 结果
+     */
+    public static boolean matchesPassword(String rawPassword, String encodedPassword)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 是否为管理员
+     *
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public static boolean isAdmin(Long userId)
+    {
+        return userId != null && 1L == userId;
+    }
+}

+ 159 - 0
master/src/main/java/com/ruoyi/common/utils/ServletUtils.java

@@ -0,0 +1,159 @@
+package com.ruoyi.common.utils;
+
+import java.io.IOException;
+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.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);
+    }
+
+    /**
+     * 获取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 待渲染的字符串
+     * @return null
+     */
+    public static String 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();
+        }
+        return null;
+    }
+
+    /**
+     * 将字符串渲染到客户端
+     *
+     * @param response 渲染对象
+     * @param string 待渲染的字符串
+     * @return null
+     */
+    public static String renderString(int code,HttpServletResponse response, String string)
+    {
+        try
+        {
+            response.setStatus(code);
+            response.setContentType("application/json");
+            response.setCharacterEncoding("utf-8");
+            response.getWriter().print(string);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 是否是Ajax异步请求
+     *
+     * @param request
+     */
+    public static boolean isAjaxRequest(HttpServletRequest request)
+    {
+        String accept = request.getHeader("accept");
+        if (accept != null && accept.indexOf("application/json") != -1)
+        {
+            return true;
+        }
+
+        String xRequestedWith = request.getHeader("X-Requested-With");
+        if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1)
+        {
+            return true;
+        }
+
+        String uri = request.getRequestURI();
+        if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml"))
+        {
+            return true;
+        }
+
+        String ajax = request.getParameter("__ajax");
+        if (StringUtils.inStringIgnoreCase(ajax, "json", "xml"))
+        {
+            return true;
+        }
+        return false;
+    }
+}

+ 43 - 0
master/src/main/java/com/ruoyi/common/utils/SpringContextUtils.java

@@ -0,0 +1,43 @@
+package com.ruoyi.common.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Spring Context 工具类
+ *
+ * @author Mark 735032128@qq.com
+ */
+@Component
+public class SpringContextUtils implements ApplicationContextAware {
+    public static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext)
+            throws BeansException {
+        com.ruoyi.common.utils.SpringContextUtils.applicationContext = applicationContext;
+    }
+
+    public static Object getBean(String name) {
+        return applicationContext.getBean(name);
+    }
+
+    public static <T> T getBean(String name, Class<T> requiredType) {
+        return applicationContext.getBean(name, requiredType);
+    }
+
+    public static boolean containsBean(String name) {
+        return applicationContext.containsBean(name);
+    }
+
+    public static boolean isSingleton(String name) {
+        return applicationContext.isSingleton(name);
+    }
+
+    public static Class<? extends Object> getType(String name) {
+        return applicationContext.getType(name);
+    }
+
+}

+ 459 - 0
master/src/main/java/com/ruoyi/common/utils/StringUtils.java

@@ -0,0 +1,459 @@
+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 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);
+    }
+
+    /**
+     * 格式化文本, {} 表示占位符<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);
+    }
+
+    /**
+     * 字符串转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;
+    }
+
+    /**
+     * 下划线转驼峰命名
+     */
+    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;
+        }
+        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();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T cast(Object obj)
+    {
+        return (T) obj;
+    }
+}

+ 99 - 0
master/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);
+        }
+    }
+}

+ 228 - 0
master/src/main/java/com/ruoyi/common/utils/VerifyCodeUtils.java

@@ -0,0 +1,228 @@
+package com.ruoyi.common.utils;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Random;
+import javax.imageio.ImageIO;
+
+/**
+ * 验证码工具类
+ *
+ * @author ruoyi
+ */
+public class VerifyCodeUtils
+{
+    // 使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
+    public static final String VERIFY_CODES = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ";
+
+    private static Random random = new SecureRandom();
+
+    /**
+     * 使用系统默认字符源生成验证码
+     *
+     * @param verifySize 验证码长度
+     * @return
+     */
+    public static String generateVerifyCode(int verifySize)
+    {
+        return generateVerifyCode(verifySize, VERIFY_CODES);
+    }
+
+    /**
+     * 使用指定源生成验证码
+     *
+     * @param verifySize 验证码长度
+     * @param sources 验证码字符源
+     * @return
+     */
+    public static String generateVerifyCode(int verifySize, String sources)
+    {
+        if (sources == null || sources.length() == 0)
+        {
+            sources = VERIFY_CODES;
+        }
+        int codesLen = sources.length();
+        Random rand = new Random(System.currentTimeMillis());
+        StringBuilder verifyCode = new StringBuilder(verifySize);
+        for (int i = 0; i < verifySize; i++)
+        {
+            verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
+        }
+        return verifyCode.toString();
+    }
+
+    /**
+     * 输出指定验证码图片流
+     *
+     * @param w
+     * @param h
+     * @param os
+     * @param code
+     * @throws IOException
+     */
+    public static void outputImage(int w, int h, OutputStream os, String code) throws IOException
+    {
+        int verifySize = code.length();
+        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+        Random rand = new Random();
+        Graphics2D g2 = image.createGraphics();
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        Color[] colors = new Color[5];
+        Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA,
+                Color.ORANGE, Color.PINK, Color.YELLOW };
+        float[] fractions = new float[colors.length];
+        for (int i = 0; i < colors.length; i++)
+        {
+            colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
+            fractions[i] = rand.nextFloat();
+        }
+        Arrays.sort(fractions);
+
+        g2.setColor(Color.GRAY);// 设置边框色
+        g2.fillRect(0, 0, w, h);
+
+        Color c = getRandColor(200, 250);
+        g2.setColor(c);// 设置背景色
+        g2.fillRect(0, 2, w, h - 4);
+
+        // 绘制干扰线
+        Random random = new Random();
+        g2.setColor(getRandColor(160, 200));// 设置线条的颜色
+        for (int i = 0; i < 20; i++)
+        {
+            int x = random.nextInt(w - 1);
+            int y = random.nextInt(h - 1);
+            int xl = random.nextInt(6) + 1;
+            int yl = random.nextInt(12) + 1;
+            g2.drawLine(x, y, x + xl + 40, y + yl + 20);
+        }
+
+        // 添加噪点
+        float yawpRate = 0.05f;// 噪声率
+        int area = (int) (yawpRate * w * h);
+        for (int i = 0; i < area; i++)
+        {
+            int x = random.nextInt(w);
+            int y = random.nextInt(h);
+            int rgb = getRandomIntColor();
+            image.setRGB(x, y, rgb);
+        }
+
+        shear(g2, w, h, c);// 使图片扭曲
+
+        g2.setColor(getRandColor(100, 160));
+        int fontSize = h - 4;
+        Font font = new Font("Algerian", Font.ITALIC, fontSize);
+        g2.setFont(font);
+        char[] chars = code.toCharArray();
+        for (int i = 0; i < verifySize; i++)
+        {
+            AffineTransform affine = new AffineTransform();
+            affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1),
+                    (w / verifySize) * i + fontSize / 2, h / 2);
+            g2.setTransform(affine);
+            g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
+        }
+
+        g2.dispose();
+        ImageIO.write(image, "jpg", os);
+    }
+
+    private static Color getRandColor(int fc, int bc)
+    {
+        if (fc > 255) {
+            fc = 255;
+        }
+        if (bc > 255) {
+            bc = 255;
+        }
+        int r = fc + random.nextInt(bc - fc);
+        int g = fc + random.nextInt(bc - fc);
+        int b = fc + random.nextInt(bc - fc);
+        return new Color(r, g, b);
+    }
+
+    private static int getRandomIntColor()
+    {
+        int[] rgb = getRandomRgb();
+        int color = 0;
+        for (int c : rgb)
+        {
+            color = color << 8;
+            color = color | c;
+        }
+        return color;
+    }
+
+    private static int[] getRandomRgb()
+    {
+        int[] rgb = new int[3];
+        for (int i = 0; i < 3; i++)
+        {
+            rgb[i] = random.nextInt(255);
+        }
+        return rgb;
+    }
+
+    private static void shear(Graphics g, int w1, int h1, Color color)
+    {
+        shearX(g, w1, h1, color);
+        shearY(g, w1, h1, color);
+    }
+
+    private static void shearX(Graphics g, int w1, int h1, Color color)
+    {
+
+        int period = random.nextInt(2);
+
+        boolean borderGap = true;
+        int frames = 1;
+        int phase = random.nextInt(2);
+
+        for (int i = 0; i < h1; i++)
+        {
+            double d = (double) (period >> 1)
+                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+            g.copyArea(0, i, w1, 1, (int) d, 0);
+            if (borderGap)
+            {
+                g.setColor(color);
+                g.drawLine((int) d, i, 0, i);
+                g.drawLine((int) d + w1, i, w1, i);
+            }
+        }
+
+    }
+
+    private static void shearY(Graphics g, int w1, int h1, Color color)
+    {
+
+        int period = random.nextInt(40) + 10; // 50;
+
+        boolean borderGap = true;
+        int frames = 20;
+        int phase = 7;
+        for (int i = 0; i < w1; i++)
+        {
+            double d = (double) (period >> 1)
+                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+            g.copyArea(i, 0, 1, h1, 0, (int) d);
+            if (borderGap)
+            {
+                g.setColor(color);
+                g.drawLine(i, (int) d, i, 0);
+                g.drawLine(i, (int) d + h1, i, h1);
+            }
+
+        }
+    }
+}

+ 110 - 0
master/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));
+    }
+}

+ 364 - 0
master/src/main/java/com/ruoyi/common/utils/document/ChartUtil.java

@@ -0,0 +1,364 @@
+package com.ruoyi.common.utils.document;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.StandardChartTheme;
+import org.jfree.chart.axis.CategoryLabelPositions;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.block.BlockBorder;
+import org.jfree.chart.labels.ItemLabelAnchor;
+import org.jfree.chart.labels.ItemLabelPosition;
+import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
+import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
+import org.jfree.chart.plot.*;
+import org.jfree.chart.renderer.category.LineAndShapeRenderer;
+import org.jfree.chart.renderer.category.StandardBarPainter;
+import org.jfree.chart.renderer.xy.StandardXYBarPainter;
+import org.jfree.chart.ui.RectangleInsets;
+import org.jfree.chart.ui.TextAnchor;
+import org.jfree.data.category.DefaultCategoryDataset;
+import org.jfree.data.general.DefaultPieDataset;
+
+import java.awt.*;
+import java.io.File;
+import java.text.NumberFormat;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class ChartUtil {
+    private static String NO_DATA_MSG = "暂无数据";
+    private static Font FONT = new Font("宋体", Font.PLAIN, 20);
+    public static Color[] CHART_COLORS = {
+            new Color(31,129,188), new Color(92,92,97), new Color(144,237,125), new Color(255,188,117),
+            new Color(153,158,255), new Color(255,117,153), new Color(253,236,109), new Color(128,133,232),
+            new Color(158,90,102),new Color(255, 204, 102) };// 颜色
+
+    static{
+        setChartTheme();
+    }
+    /**
+     * 中文主题样式 解决乱码
+     */
+    public static void setChartTheme() {
+        // 设置中文主题样式 解决乱码
+        StandardChartTheme chartTheme = new StandardChartTheme("CN");
+        // 设置标题字体
+        chartTheme.setExtraLargeFont(FONT);
+        // 设置图例的字体
+        chartTheme.setRegularFont(FONT);
+        // 设置轴向的字体
+        chartTheme.setLargeFont(FONT);
+        chartTheme.setSmallFont(FONT);
+        chartTheme.setTitlePaint(new Color(51, 51, 51));
+        chartTheme.setSubtitlePaint(new Color(85, 85, 85));
+
+        chartTheme.setLegendBackgroundPaint(Color.WHITE);// 设置标注
+        chartTheme.setLegendItemPaint(Color.BLACK);//
+        chartTheme.setChartBackgroundPaint(Color.WHITE);
+        // 绘制颜色绘制颜色.轮廓供应商
+        // paintSequence,outlinePaintSequence,strokeSequence,outlineStrokeSequence,shapeSequence
+
+        Paint[] OUTLINE_PAINT_SEQUENCE = new Paint[] { Color.WHITE };
+        // 绘制器颜色源
+        DefaultDrawingSupplier drawingSupplier = new DefaultDrawingSupplier(CHART_COLORS, CHART_COLORS, OUTLINE_PAINT_SEQUENCE,
+                DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE, DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
+                DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE);
+        chartTheme.setDrawingSupplier(drawingSupplier);
+
+        chartTheme.setPlotBackgroundPaint(Color.WHITE);// 绘制区域
+        chartTheme.setPlotOutlinePaint(Color.WHITE);// 绘制区域外边框
+        chartTheme.setLabelLinkPaint(new Color(8, 55, 114));// 链接标签颜色
+        chartTheme.setLabelLinkStyle(PieLabelLinkStyle.CUBIC_CURVE);
+
+        chartTheme.setAxisOffset(new RectangleInsets(5, 12, 5, 12));
+        chartTheme.setDomainGridlinePaint(new Color(192, 208, 224));// X坐标轴垂直网格颜色
+        chartTheme.setRangeGridlinePaint(new Color(192, 192, 192));// Y坐标轴水平网格颜色
+
+        chartTheme.setBaselinePaint(Color.WHITE);
+        chartTheme.setCrosshairPaint(Color.BLUE);// 不确定含义
+        chartTheme.setAxisLabelPaint(new Color(51, 51, 51));// 坐标轴标题文字颜色
+        chartTheme.setTickLabelPaint(new Color(67, 67, 72));// 刻度数字
+        chartTheme.setBarPainter(new StandardBarPainter());// 设置柱状图渲染
+        chartTheme.setXYBarPainter(new StandardXYBarPainter());// XYBar 渲染
+
+        chartTheme.setItemLabelPaint(Color.black);
+        chartTheme.setThermometerPaint(Color.white);// 温度计
+
+        ChartFactory.setChartTheme(chartTheme);
+    }
+    /**
+     * 必须设置文本抗锯齿
+     */
+    public static void setAntiAlias(JFreeChart chart) {
+        chart.setTextAntiAlias(false);
+
+    }
+
+    /**
+     * 设置图例无边框,默认黑色边框
+     */
+    public static void setLegendEmptyBorder(JFreeChart chart) {
+        chart.getLegend().setFrame(new BlockBorder(Color.WHITE));
+
+    }
+
+    /**
+     * 提供静态方法:获取报表图形1:饼状图
+     * @param title    标题
+     * @param datas    数据
+     * @param url    字体
+     */
+    public static void createPiePort(String title, Map<String,Double> datas, String url){
+        try {
+            // 如果不使用Font,中文将显示不出来
+            DefaultPieDataset pds = new DefaultPieDataset();
+
+            // 获取迭代器:
+            Set<Entry<String, Double>> set =  datas.entrySet();
+            Iterator iterator=(Iterator) set.iterator();
+            Entry entry=null;
+            while(iterator.hasNext()){
+                entry=(Entry) iterator.next();
+                pds.setValue(entry.getKey().toString(),Double.parseDouble(entry.getValue().toString()));
+            }
+            /**
+             * 生成一个饼图的图表
+             * 分别是:显示图表的标题、需要提供对应图表的DateSet对象、是否显示图例、是否生成贴士以及是否生成URL链接
+             */
+            JFreeChart chart = ChartFactory.createPieChart(title, pds, true, true, true);
+            setPieRender((PiePlot) chart.getPlot());
+
+            //将内存中的图片写到本地硬盘
+            org.jfree.chart.ChartUtils.saveChartAsPNG(new File(url), chart,800,500);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 提供静态方法:获取报表图形1:饼状图
+     * @param title    标题
+     * @param datas    数据
+     * @param url    字体
+     */
+    public static void createBarPort(String title, Map<String,Double> datas, String url){
+        try {
+            // 如果不使用Font,中文将显示不出来
+            DefaultCategoryDataset  pds = new DefaultCategoryDataset();
+
+            // 获取迭代器:
+            Set<Entry<String, Double>> set =  datas.entrySet();
+            Iterator iterator=(Iterator) set.iterator();
+            Entry entry=null;
+            while(iterator.hasNext()){
+                entry=(Entry) iterator.next();
+                pds.setValue(Double.parseDouble(entry.getValue().toString()),entry.getKey().toString(),entry.getKey().toString());
+            }
+            /**
+             * 生成一个饼图的图表
+             * 分别是:显示图表的标题、需要提供对应图表的DateSet对象、是否显示图例、是否生成贴士以及是否生成URL链接
+             */
+            JFreeChart chart = ChartFactory.createBarChart(title,"分类","数量", pds, PlotOrientation.VERTICAL, true, true, false);
+
+            //将内存中的图片写到本地硬盘
+            org.jfree.chart.ChartUtils.saveChartAsPNG(new File(url), chart,800,500);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+
+
+    /**
+     * 提供静态方法:获取报表图形3:折线图
+     * @param title        标题
+     * @param datas        数据
+     * @param xName        分类(第一季,第二季.....)
+     * @param yName    柱状图的数量单位
+     * @param url        字体
+     */
+    public static void createLinePort(String title,Map<String,Double> datas,String xName,String yName,String url){
+        try {
+            //种类数据集
+            DefaultCategoryDataset dataset = new DefaultCategoryDataset();
+            //获取迭代器:
+            Set<Entry<String, Double>> set =  datas.entrySet();
+            Iterator iterator=(Iterator) set.iterator();
+            Entry entry=null;
+            while(iterator.hasNext()){
+                entry=(Entry) iterator.next();
+                dataset.setValue(Double.parseDouble(entry.getValue().toString()),//y
+                        title,                         //名称
+                        entry.getKey().toString());      //x
+            }
+            //创建折线图,折线图分水平显示和垂直显示两种
+            JFreeChart chart = ChartFactory.createLineChart(title, xName, yName, dataset,//2D折线图
+                    PlotOrientation.VERTICAL,
+                    false, // 是否显示图例(对于简单的柱状图必须是false)
+                    true, // 是否生成工具
+                    true);// 是否生成URL链接
+            //得到绘图区
+            setLineRender((CategoryPlot)chart.getPlot(),true,true);
+            org.jfree.chart.ChartUtils.saveChartAsPNG(new File(url), chart, 1000,600);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 提供静态方法:获取报表图形3:折线图
+     * @param title        标题
+     * @param datas        数据
+     * @param xName        分类(第一季,第二季.....)
+     * @param yName    柱状图的数量单位
+     * @param url        字体
+     */
+    public static void createLinePort(String title,DefaultCategoryDataset datas,String xName,String yName,String url){
+        try {
+            //创建折线图,折线图分水平显示和垂直显示两种
+            JFreeChart chart = ChartFactory.createLineChart(title, xName, yName, datas,//2D折线图
+                    PlotOrientation.VERTICAL,
+                    true, // 是否显示图例(对于简单的柱状图必须是false)
+                    true, // 是否生成工具
+                    true);// 是否生成URL链接
+            //得到绘图区
+            setLineRender((CategoryPlot)chart.getPlot(),true,true);
+            org.jfree.chart.ChartUtils.saveChartAsPNG(new File(url), chart, 1000,600);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 设置折线图样式
+     *
+     * @param plot
+     * @param isShowDataLabels
+     *            是否显示数据标签
+     */
+    public static void setLineRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible) {
+        plot.setNoDataMessage(NO_DATA_MSG);
+        plot.setInsets(new RectangleInsets(10, 10, 0, 10), false);
+        LineAndShapeRenderer lasp = (LineAndShapeRenderer) plot.getRenderer();
+
+        LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
+         renderer.setSeriesPaint(1, Color.RED);//红色
+        renderer.setSeriesPaint(0, Color.BLUE);//蓝色
+        renderer.setDefaultStroke(new BasicStroke(1.5F));
+        if (isShowDataLabels) {
+            renderer.setDefaultItemLabelsVisible(true);
+            renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
+                    NumberFormat.getInstance()));
+            renderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE1, TextAnchor.BOTTOM_CENTER));// weizhi
+        }
+        renderer.setDefaultShapesVisible(isShapesVisible);// 数据点绘制形状
+        setXAixs(plot);
+        setYAixs(plot);
+
+    }
+    /**
+     * 设置饼状图渲染
+     */
+    public static void setBarRender(Plot plot) {
+
+//        CategoryAxis categoryAxis=plot.getDomainAxis();//获得横坐标
+//        categoryAxis.setLabelFont(new Font("微软雅黑",Font.BOLD,12));//设置横坐标字体
+    }
+    /**
+     * 设置饼状图渲染
+     */
+    public static void setPieRender(Plot plot) {
+
+        plot.setNoDataMessage(NO_DATA_MSG);
+        plot.setInsets(new RectangleInsets(10, 10, 5, 10));
+        PiePlot piePlot = (PiePlot) plot;
+        piePlot.setInsets(new RectangleInsets(0, 0, 0, 0));
+        piePlot.setCircular(true);// 圆形
+
+        // piePlot.setSimpleLabels(true);// 简单标签
+        piePlot.setLabelGap(0.01);
+        piePlot.setInteriorGap(0.05D);
+        piePlot.setLegendItemShape(new Rectangle(10, 10));// 图例形状
+        piePlot.setIgnoreNullValues(true);
+        piePlot.setLabelBackgroundPaint(null);// 去掉背景色
+        piePlot.setLabelShadowPaint(null);// 去掉阴影
+        piePlot.setLabelOutlinePaint(null);// 去掉边框
+        piePlot.setShadowPaint(null);
+        // 0:category 1:value:2 :percentage
+        piePlot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}:{2}"));// 显示标签数据
+    }
+
+    /**
+     * 设置类别图表(CategoryPlot) X坐标轴线条颜色和样式
+     *
+     * @param plot
+     */
+    public static void setXAixs(CategoryPlot plot) {
+        Color lineColor = new Color(31, 121, 170);
+        plot.getDomainAxis().setCategoryLabelPositions(CategoryLabelPositions.UP_45);
+        plot.getDomainAxis().setAxisLinePaint(lineColor);// X坐标轴颜色
+        plot.getDomainAxis().setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
+
+    }
+
+    /**
+     * 设置类别图表(CategoryPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
+     *
+     * @param plot
+     */
+    public static void setYAixs(CategoryPlot plot) {
+//      Color lineColor = new Color(192, 208, 224);
+        Color lineColor = new Color(31, 121, 170);
+        ValueAxis axis = plot.getRangeAxis();
+        axis.setAxisLinePaint(lineColor);// Y坐标轴颜色
+        axis.setTickMarkPaint(lineColor);// Y坐标轴标记|竖线颜色
+        // false隐藏Y刻度
+        axis.setAxisLineVisible(true);
+        axis.setTickMarksVisible(true);
+        // Y轴网格线条
+        plot.setRangeGridlinePaint(new Color(192, 192, 192));
+        plot.setRangeGridlineStroke(new BasicStroke(1));
+
+        plot.getRangeAxis().setUpperMargin(0.1);// 设置顶部Y坐标轴间距,防止数据无法显示
+        plot.getRangeAxis().setLowerMargin(0.1);// 设置底部Y坐标轴间距
+
+    }
+
+    /**
+     * 设置XY图表(XYPlot) X坐标轴线条颜色和样式
+     *
+     * @param plot
+     */
+    public static void setXY_XAixs(XYPlot plot) {
+        Color lineColor = new Color(31, 121, 170);
+        plot.getDomainAxis().setAxisLinePaint(lineColor);// X坐标轴颜色
+        plot.getDomainAxis().setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
+
+    }
+
+    /**
+     * 设置XY图表(XYPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
+     *
+     * @param plot
+     */
+    public static void setXY_YAixs(XYPlot plot) {
+        Color lineColor = new Color(192, 208, 224);
+        ValueAxis axis = plot.getRangeAxis();
+        axis.setAxisLinePaint(lineColor);// X坐标轴颜色
+        axis.setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
+        // 隐藏Y刻度
+        axis.setAxisLineVisible(false);
+        axis.setTickMarksVisible(false);
+        // Y轴网格线条
+        plot.setRangeGridlinePaint(new Color(192, 192, 192));
+        plot.setRangeGridlineStroke(new BasicStroke(1));
+        plot.setDomainGridlinesVisible(false);
+
+        plot.getRangeAxis().setUpperMargin(0.12);// 设置顶部Y坐标轴间距,防止数据无法显示
+        plot.getRangeAxis().setLowerMargin(0.12);// 设置底部Y坐标轴间距
+
+    }
+}

+ 59 - 0
master/src/main/java/com/ruoyi/common/utils/document/DocumentHandler.java

@@ -0,0 +1,59 @@
+package com.ruoyi.common.utils.document;
+
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+import java.io.*;
+import java.util.Map;
+
+public class DocumentHandler {
+
+    private Configuration configuration = null;
+
+    public DocumentHandler() {
+        this.configuration = new Configuration();
+        this.configuration.setDefaultEncoding("utf-8");
+    }
+
+    //将xml文件转成模板
+    public Template getTemplate() {
+        configuration.setClassForTemplateLoading(this.getClass(),
+                "/freeMaker");
+        Template t = null;
+        try {
+            t = configuration.getTemplate("fmaker.xml");//模板xml路径
+            t.setEncoding("utf-8");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return t;
+    }
+
+    //获得输出流对象
+    public Writer getWriter(String devType) {
+        //填充后,新文件路径
+        File outFile = new File("renren-fast/src/main/resources/freeMaker/wordTemplate.doc");
+        Writer out = null;
+        try {
+            out = new BufferedWriter(new OutputStreamWriter(
+                    new FileOutputStream(outFile), "utf-8"
+            ));
+        } catch (UnsupportedEncodingException | FileNotFoundException e) {
+            e.printStackTrace();
+        }
+        return out;
+    }
+
+    //转化生成word文件
+    public void createDoc(Template t, Map dataMap, Writer out) {
+        try {
+            t.process(dataMap, out);
+            out.close();
+        } catch (TemplateException | IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+}

+ 113 - 0
master/src/main/java/com/ruoyi/common/utils/document/PDFTemplateUtil.java

@@ -0,0 +1,113 @@
+package com.ruoyi.common.utils.document;
+
+import com.itextpdf.text.pdf.BaseFont;
+import com.ruoyi.framework.config.RuoYiConfig;
+import com.ruoyi.framework.web.domain.server.Sys;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import org.xhtmlrenderer.pdf.ITextRenderer;
+
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.net.URLEncoder;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * @Description: 通过模板导出pdf文件
+ * @Author: cq
+ * @CreateDate: 2020/9/21 9:37
+ */
+public class PDFTemplateUtil {
+
+    /**
+     * 通过模板导出pdf文件
+     *
+     * @param map             数据
+     * @param templateFileName 模板文件名
+     * @throws Exception
+     */
+    private static ByteArrayOutputStream createPDF(Map<String, Object> map, String templateFileName , String filePath) throws Exception {
+        // 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
+        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
+        // 指定FreeMarker模板文件的位置
+        cfg.setClassForTemplateLoading(PDFTemplateUtil.class, "/freeMaker");
+        ITextRenderer renderer = new ITextRenderer();
+        OutputStream out = new ByteArrayOutputStream();
+        try {
+            // 设置 css中 的字体样式(暂时仅支持宋体和黑体) 必须,不然中文不显示
+            renderer.getFontResolver().addFont("/freeMaker/font/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
+            // 设置模板的编码格式
+            cfg.setEncoding(Locale.CHINA, "UTF-8");
+            // 获取模板文件
+            Template template = cfg.getTemplate(templateFileName, "UTF-8");
+            StringWriter writer = new StringWriter();
+
+            // 将数据输出到html中
+            template.process(map, writer);
+            writer.flush();
+
+            String html = writer.toString();
+            // 把html代码传入渲染器中
+            renderer.setDocumentFromString(html);
+
+            // 设置模板中的图片路径 (这里的images在resources/freeMaker目录下) 模板中img标签src路径需要相对路径加图片名 如<img src="images/xh.jpg"/>
+            //String url = PDFTemplateUtil.class.getClassLoader().getResource("freeMaker").toURI().toString();
+            //renderer.getSharedContext().setBaseURL(url);
+            //本地测试路径为(file:/D:/ssyFile/),服务器路径为(file:/ssyFile/)
+
+            System.out.println(filePath);
+            renderer.getSharedContext().setBaseURL(filePath + "/");
+            renderer.layout();
+
+            renderer.createPDF(out, false);
+            renderer.finishPDF();
+            out.flush();
+            return (ByteArrayOutputStream) out;
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * 通过模板导出pdf文件
+     *
+     * @param templateName 模板文件名(模板文件名+后缀)
+     * @param pdfName      pdf文件名(pdf文件名+后缀)
+     * @param map         数据
+     * @param response     HttpServletResponse
+     * @throws Exception
+     */
+    public static void exportPdf(String templateName, String pdfName,String filePath, Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ByteArrayOutputStream baos = null;
+        OutputStream out = null;
+        try {
+            baos = createPDF(map, templateName ,filePath);
+            ;
+            // 设置响应消息头,告诉浏览器当前响应是一个下载文件
+            response.setContentType("application/x-msdownload");
+            // 告诉浏览器,当前响应数据要求用户干预保存到文件中,以及文件名是什么 如果文件名有中文,必须URL编码
+            String fileName = URLEncoder.encode(pdfName, "UTF-8");
+            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+            out = response.getOutputStream();
+            baos.writeTo(out);
+            baos.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new Exception("导出失败:" + e.getMessage());
+        } finally {
+            if (baos != null) {
+                baos.close();
+            }
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+}

+ 35 - 0
master/src/main/java/com/ruoyi/common/utils/export/EncodeTools.java

@@ -0,0 +1,35 @@
+package com.ruoyi.common.utils.export;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.URLEncoder;
+
+/**
+ * @author kai
+ */
+public class EncodeTools {
+    /**
+     * 根据浏览器编码文件名 <br>
+     *
+     * @param fileName 文件名
+     * @param request  HttpServletRequest 对象,用来获取浏览器信息
+     * @return 编码后的文件名
+     * @author Hunter
+     * @version [1.0.0, 2015年10月24日]
+     * @since [1.0.0]
+     */
+    public static String encodeFileNameByBrowser(String fileName, HttpServletRequest request) {
+        String res = fileName;
+        try {
+            String userAgent = request.getHeader("USER-AGENT");
+            if (StringUtils.contains(userAgent, "Edge") || StringUtils.contains(userAgent, "MSIE")) {
+                res = URLEncoder.encode(fileName, "UTF8");
+            } else {
+                res = new String(fileName.getBytes(), "ISO8859-1");
+            }
+        } catch (Exception e) {
+        }
+        return res;
+    }
+}

+ 20 - 0
master/src/main/java/com/ruoyi/common/utils/export/JxlsTemplate.java

@@ -0,0 +1,20 @@
+package com.ruoyi.common.utils.export;
+
+/**
+ * Created by xk on 2017/12/4 0004.
+ */
+public interface JxlsTemplate {
+    /**
+     * 模板路径
+     *
+     * @return
+     */
+    String getPath();
+
+    /**
+     * 生成名称
+     *
+     * @return
+     */
+    String getName();
+}

+ 38 - 0
master/src/main/java/com/ruoyi/common/utils/export/JxlsTemplateEnum.java

@@ -0,0 +1,38 @@
+package com.ruoyi.common.utils.export;
+
+
+/**
+ * Created by xk on 2017/12/4 0004.
+ */
+public enum JxlsTemplateEnum implements JxlsTemplate {
+
+    DONE_TASK_LIST("/template/document/export/plant.xls", "程序请单.xls"),
+    DEV_YLRQ_UPDATE("/static/template/sems/info/ylrqInspectionInfo.xlsx", "压力容器检验信息更新清单.xlsx"),
+    DEV_YLGD_UPDATE("/static/template/sems/info/ylgdInspectionInfo.xlsx", "压力管道检验信息更新清单.xlsx"),
+    DEV_DZSB_UPDATE("/static/template/sems/info/dzsbInspectionInfo.xlsx", "起重机械检验信息更新清单.xlsx"),
+    DEV_GL_UPDATE("/static/template/sems/info/glInspectionInfo.xlsx", "锅炉设备检验信息更新清单.xlsx"),
+    DEV_DT_UPDATE("/static/template/sems/info/dtInspectionInfo.xlsx", "电梯设备检验信息更新清单.xlsx"),
+    DEV_CC_UPDATE("/static/template/sems/info/ccInspectionInfo.xlsx", "叉车设备检验信息更新清单.xlsx");
+
+    private String path;
+    private String name;
+
+    JxlsTemplateEnum(String path, String name) {
+        this.path = path;
+        this.name = name;
+    }
+
+    @Override
+    public String getPath() {
+        return this.path;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 74 - 0
master/src/main/java/com/ruoyi/common/utils/export/JxlsView.java

@@ -0,0 +1,74 @@
+package com.ruoyi.common.utils.export;
+
+import org.jxls.common.Context;
+import org.jxls.util.JxlsHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.servlet.view.AbstractView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * Created by xk on 2017/12/4 0004.
+ */
+public class JxlsView extends AbstractView {
+    private static Logger log = LoggerFactory.getLogger(JxlsView.class);
+
+    private JxlsTemplate template;
+
+    private Object data;
+    private Object sheetdata;
+
+    public JxlsView() {
+        this.setContentType("application/vnd.ms-excel");
+    }
+
+    public JxlsView(JxlsTemplate template, Object data) {
+        this();
+        this.template = template;
+        this.data = data;
+    }
+
+    public JxlsView(JxlsTemplate template, Object data, Object sheetdata) {
+        this();
+        this.template = template;
+        this.data = data;
+        this.sheetdata = sheetdata;
+    }
+
+    protected boolean generatesDownloadContent() {
+        return true;
+    }
+
+    protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+        response.setContentType(this.getContentType());
+        response.setHeader("Content-Disposition", "attachment;filename="
+                + EncodeTools.encodeFileNameByBrowser(template.getName(), request));
+        InputStream is = null;
+        try {
+            is = JxlsView.class.getResourceAsStream(template.getPath());
+            Context context = new Context();
+            context.putVar("data", data);
+            context.putVar("sheetNames", sheetdata);
+            logger.info("data" + data);
+            logger.info("is" + is);
+            logger.info("getOutputStream" + response.getOutputStream());
+            JxlsHelper.getInstance().processTemplate(is, response.getOutputStream(), context);
+        } catch (Exception e) {
+            log.error("导出失败", e);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    log.error("is关闭失败", e);
+                }
+            }
+        }
+
+    }
+}

+ 307 - 0
master/src/main/java/com/ruoyi/common/utils/file/ExcelUtils.java

@@ -0,0 +1,307 @@
+package com.ruoyi.common.utils.file;
+
+import org.apache.commons.lang.time.DateFormatUtils;
+import org.apache.poi.hssf.usermodel.HSSFDataFormat;
+import org.apache.poi.hssf.usermodel.HSSFDateUtil;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by Administrator on 2018/7/26 0026.
+ */
+public class ExcelUtils {
+
+    public static Workbook getWorkBook(MultipartFile file) {
+        //获得文件名
+        String fileName = file.getOriginalFilename();
+        //创建Workbook工作薄对象,表示整个excel
+        Workbook workbook = null;
+        try {
+            //获取excel文件的io流
+            InputStream is = file.getInputStream();
+            //根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象
+            if (fileName.endsWith("xls")) {
+                //2003
+                workbook = new HSSFWorkbook(is);
+            } else if (fileName.endsWith("xlsx")) {
+                //2007 及2007以上
+                workbook = new XSSFWorkbook(is);
+            }
+        } catch (IOException e) {
+//            log.error(e.getMessage());
+        }
+        return workbook;
+    }
+
+    public static Workbook getWorkBook(File file) {
+        //获得文件名
+        String fileName = file.getName();
+        //创建Workbook工作薄对象,表示整个excel
+        Workbook workbook = null;
+        try {
+            //获取excel文件的io流
+            InputStream is =new FileInputStream(file);
+            //根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象
+            if (fileName.endsWith("xls")) {
+                //2003
+                workbook = new HSSFWorkbook(is);
+            } else if (fileName.endsWith("xlsx")) {
+                //2007 及2007以上
+                workbook = new XSSFWorkbook(is);
+            }
+        } catch (IOException e) {
+//            log.error(e.getMessage());
+        }
+        return workbook;
+    }
+
+    public static String getCellValue(Cell cell) {
+        String cellValue = "";
+        if (cell == null) {
+            return cellValue;
+        }
+        System.out.println("Excel格式:"+ cell.getCellType().name());
+
+        switch (cell.getCellType().name()) {
+            case "STRING":
+                cellValue = cell.getRichStringCellValue().getString();
+                break;
+            case "NUMERIC":
+                System.out.println("Excel数据样式:"+ cell.getCellStyle().getDataFormatString());
+                if("General".equals(cell.getCellStyle().getDataFormatString())){
+                    cellValue = new BigDecimal(Double.toString(cell.getNumericCellValue())).toPlainString();
+                }else if("m/d/yyyy;@".equals(cell.getCellStyle().getDataFormatString())){
+                    cellValue =  new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
+                }else if("@".equals(cell.getCellStyle().getDataFormatString())){
+                    cellValue = cell.getNumericCellValue() + "";
+                }else{
+                    try {
+                        cellValue =  new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
+                    }catch (Exception e){
+                        cellValue = cell.getNumericCellValue() + "";
+                    }
+                }
+                break;
+            case "BLANK":
+                cellValue = "";
+                break;
+            default:
+                cellValue = cell.toString();
+                break;
+        }
+        //判断数据的类型
+//        switch (cell.getCellType()) {
+//            case Cell.CELL_TYPE_NUMERIC: //数字
+//                cellValue = stringDateProcess(cell);
+//                break;
+//            case Cell.CELL_TYPE_STRING: //字符串
+//                cellValue = String.valueOf(cell.getStringCellValue());
+//                break;
+//            case Cell.CELL_TYPE_BOOLEAN: //Boolean
+//                cellValue = String.valueOf(cell.getBooleanCellValue());
+//                break;
+//            case Cell.CELL_TYPE_FORMULA: //公式
+//                cellValue = String.valueOf(cell.getCellFormula());
+//                break;
+//            case Cell.CELL_TYPE_BLANK: //空值
+//                cellValue = "";
+//                break;
+//            case Cell.CELL_TYPE_ERROR: //故障
+//                cellValue = "非法字符";
+//                break;
+//            default:
+//                cellValue = "未知类型";
+//                break;
+//        }
+//        cellValue = String.valueOf(cell.getStringCellValue());
+        return cellValue;
+    }
+
+    /**
+     * 时间格式处理
+     *
+     * @return
+     * @author Liu Xin Nan
+     * @data 2017年11月27日
+     */
+    public static String stringDateProcess(Cell cell) {
+        String result = new String();
+        if (HSSFDateUtil.isCellDateFormatted(cell)) {// 处理日期格式、时间格式
+            SimpleDateFormat sdf = null;
+            if (cell.getCellStyle().getDataFormat() == HSSFDataFormat.getBuiltinFormat("h:mm")) {
+                sdf = new SimpleDateFormat("HH:mm");
+            } else {// 日期
+                sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+            }
+            Date date = cell.getDateCellValue();
+            result = sdf.format(date);
+        } else if (cell.getCellStyle().getDataFormat() == 58) {
+            // 处理自定义日期格式:m月d日(通过判断单元格的格式id解决,id的值是58)
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+            double value = cell.getNumericCellValue();
+            Date date = DateUtil
+                    .getJavaDate(value);
+            result = sdf.format(date);
+        } else {
+            double value = cell.getNumericCellValue();
+            CellStyle style = cell.getCellStyle();
+            DecimalFormat format = new DecimalFormat();
+            String temp = style.getDataFormatString();
+            // 单元格设置成常规
+            if (temp.equals("General")) {
+                format.applyPattern("#");
+            }
+            result = format.format(value);
+        }
+
+        return result;
+    }
+
+    /**
+     * 合并单元格处理,获取合并行
+     *
+     * @param sheet
+     * @return List<CellRangeAddress>
+     */
+    public static List<CellRangeAddress> getCombineCell(Sheet sheet) {
+        List<CellRangeAddress> list = new ArrayList<CellRangeAddress>();
+        //获得一个 sheet 中合并单元格的数量
+        int sheetmergerCount = sheet.getNumMergedRegions();
+        //遍历所有的合并单元格
+        for (int i = 0; i < sheetmergerCount; i++) {
+            //获得合并单元格保存进list中
+            CellRangeAddress ca = sheet.getMergedRegion(i);
+            list.add(ca);
+        }
+        return list;
+    }
+
+    public static int getRowNum(List<CellRangeAddress> listCombineCell, Cell cell, Sheet sheet) {
+        int xr = 0;
+        int firstC = 0;
+        int lastC = 0;
+        int firstR = 0;
+        int lastR = 0;
+        for (CellRangeAddress ca : listCombineCell) {
+            //获得合并单元格的起始行, 结束行, 起始列, 结束列
+            firstC = ca.getFirstColumn();
+            lastC = ca.getLastColumn();
+            firstR = ca.getFirstRow();
+            lastR = ca.getLastRow();
+            if (cell.getRowIndex() >= firstR && cell.getRowIndex() <= lastR) {
+                if (cell.getColumnIndex() >= firstC && cell.getColumnIndex() <= lastC) {
+                    xr = lastR;
+                }
+            }
+
+        }
+        return xr;
+
+    }
+
+    /**
+     * 判断单元格是否为合并单元格,是的话则将单元格的值返回
+     *
+     * @param listCombineCell 存放合并单元格的list
+     * @param cell            需要判断的单元格
+     * @param sheet           sheet
+     * @return
+     */
+    public static String isCombineCell(List<CellRangeAddress> listCombineCell, Cell cell, Sheet sheet)
+            throws Exception {
+        int firstC = 0;
+        int lastC = 0;
+        int firstR = 0;
+        int lastR = 0;
+        String cellValue = null;
+        for (CellRangeAddress ca : listCombineCell) {
+            //获得合并单元格的起始行, 结束行, 起始列, 结束列
+            firstC = ca.getFirstColumn();
+            lastC = ca.getLastColumn();
+            firstR = ca.getFirstRow();
+            lastR = ca.getLastRow();
+            if (cell.getRowIndex() >= firstR && cell.getRowIndex() <= lastR) {
+                if (cell.getColumnIndex() >= firstC && cell.getColumnIndex() <= lastC) {
+                    Row fRow = sheet.getRow(firstR);
+                    Cell fCell = fRow.getCell(firstC);
+                    cellValue = getCellValue(fCell);
+                    break;
+                }
+            } else {
+                cellValue = "";
+            }
+        }
+        return cellValue;
+    }
+
+    /**
+     * 获取合并单元格的值
+     *
+     * @param sheet
+     * @param row
+     * @param column
+     * @return
+     */
+    public static String getMergedRegionValue(Sheet sheet, int row, int column) {
+        int sheetMergeCount = sheet.getNumMergedRegions();
+
+        for (int i = 0; i < sheetMergeCount; i++) {
+            CellRangeAddress ca = sheet.getMergedRegion(i);
+            int firstColumn = ca.getFirstColumn();
+            int lastColumn = ca.getLastColumn();
+            int firstRow = ca.getFirstRow();
+            int lastRow = ca.getLastRow();
+
+            if (row >= firstRow && row <= lastRow) {
+                if (column >= firstColumn && column <= lastColumn) {
+                    Row fRow = sheet.getRow(firstRow);
+                    Cell fCell = fRow.getCell(firstColumn);
+                    return getCellValue(fCell);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 判断指定的单元格是否是合并单元格
+     *
+     * @param sheet
+     * @param row    行下标
+     * @param column 列下标
+     * @return
+     */
+    public static boolean isMergedRegion(Sheet sheet, int row, int column) {
+        int sheetMergeCount = sheet.getNumMergedRegions();
+        for (int i = 0; i < sheetMergeCount; i++) {
+            CellRangeAddress range = sheet.getMergedRegion(i);
+            int firstColumn = range.getFirstColumn();
+            int lastColumn = range.getLastColumn();
+            int firstRow = range.getFirstRow();
+            int lastRow = range.getLastRow();
+            if (row >= firstRow && row <= lastRow) {
+                if (column >= firstColumn && column <= lastColumn) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+}

+ 229 - 0
master/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java

@@ -0,0 +1,229 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+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.IdUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.config.RuoYiConfig;
+
+/**
+ * 文件上传工具类
+ *
+ * @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 上传的文件
+     * @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 = 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);
+        File desc = getAbsoluteFile(baseDir, fileName);
+        file.transferTo(desc);
+        String pathFileName = getPathFileName(baseDir, fileName);
+        return pathFileName;
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file)
+    {
+//        String fileName = file.getOriginalFilename();
+//        String extension = getExtension(file);
+        String fileName = DateUtils.dateTime() + "/" + IdUtils.fastUUID().substring(5) + "/" + file.getOriginalFilename();
+        return fileName;
+    }
+
+    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
+    {
+        File desc = new File(uploadDir + File.separator + fileName);
+
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        if (!desc.exists())
+        {
+            desc.createNewFile();
+        }
+        return desc;
+    }
+
+    private static final String getPathFileName(String uploadDir, String fileName) throws IOException
+    {
+        int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
+        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
+        String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
+        return pathFileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @return
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException
+     */
+    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, InvalidExtensionException
+    {
+        long size = file.getSize();
+        if (DEFAULT_MAX_SIZE != -1 && 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
+            {
+                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(file.getContentType());
+        }
+        return extension;
+    }
+}

+ 142 - 0
master/src/main/java/com/ruoyi/common/utils/file/FileUtils.java

@@ -0,0 +1,142 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 文件处理工具类
+ *
+ * @author ruoyi
+ */
+public class FileUtils extends org.apache.commons.io.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
+        {
+            if (os != null)
+            {
+                try
+                {
+                    os.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (fis != null)
+            {
+                try
+                {
+                    fis.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param filePath 文件
+     * @return
+     */
+    public static boolean deleteFile(String filePath)
+    {
+        boolean flag = false;
+        File file = new File(filePath);
+        // 路径为文件且不为空则进行删除
+        if (file.isFile() && file.exists())
+        {
+            file.delete();
+            flag = true;
+        }
+        return flag;
+    }
+
+    /**
+     * 文件名称验证
+     *
+     * @param filename 文件名称
+     * @return true 正常 false 非法
+     */
+    public static boolean isValidFilename(String filename)
+    {
+        return filename.matches(FILENAME_PATTERN);
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @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;
+    }
+}

+ 55 - 0
master/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java

@@ -0,0 +1,55 @@
+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[] DEFAULT_ALLOWED_EXTENSION = {
+            // 图片
+            "bmp", "gif", "jpg", "jpeg", "png",
+            // word excel powerpoint
+            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+            // 压缩文件
+            "rar", "zip", "gz", "bz2",
+            // 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 "";
+        }
+    }
+}

+ 155 - 0
master/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java

@@ -0,0 +1,155 @@
+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)
+    {
+        int len;
+        if ((text == null) || ((len = text.length()) == 0))
+        {
+            return StringUtils.EMPTY;
+        }
+        StringBuilder buffer = new StringBuilder(len + (len >> 2));
+        char c;
+        for (int i = 0; i < len; i++)
+        {
+            c = text.charAt(i);
+            if (c < 64)
+            {
+                buffer.append(TEXT[c]);
+            }
+            else
+            {
+                buffer.append(c);
+            }
+        }
+        return buffer.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 html = "<scr<script>ipt>alert(\"XSS\")</scr<script>ipt>";
+        // String html = "<123";
+        // String html = "123>";
+        System.out.println(EscapeUtil.clean(html));
+        System.out.println(EscapeUtil.escape(html));
+        System.out.println(EscapeUtil.unescape(html));
+    }
+}

+ 570 - 0
master/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 (false == 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));
+    }
+}

+ 51 - 0
master/src/main/java/com/ruoyi/common/utils/http/HttpContextUtils.java

@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2016-2019 人人开源 All rights reserved.
+ * <p>
+ * https://www.renren.io
+ * <p>
+ * 版权所有,侵权必究!
+ */
+
+package com.ruoyi.common.utils.http;
+
+import org.apache.commons.io.IOUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class HttpContextUtils {
+
+    public static void copyImageStream(InputStream inputStream, OutputStream outputStream) {
+        try {
+            IOUtils.copy(inputStream, outputStream);
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                inputStream.close();
+                outputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public static HttpServletRequest getHttpServletRequest() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+    }
+
+    public static String getDomain() {
+        HttpServletRequest request = getHttpServletRequest();
+        StringBuffer url = request.getRequestURL();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
+    }
+
+    public static String getOrigin() {
+        HttpServletRequest request = getHttpServletRequest();
+        return request.getHeader("Origin");
+    }
+}

+ 55 - 0
master/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java

@@ -0,0 +1,55 @@
+package com.ruoyi.common.utils.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import javax.servlet.ServletRequest;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 通用http工具封装
+ *
+ * @author ruoyi
+ */
+public class HttpHelper
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
+
+    public static String getBodyString(ServletRequest request)
+    {
+        StringBuilder sb = new StringBuilder();
+        BufferedReader reader = null;
+        try (InputStream inputStream = request.getInputStream())
+        {
+            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
+            String line = "";
+            while ((line = reader.readLine()) != null)
+            {
+                sb.append(line);
+            }
+        }
+        catch (IOException e)
+        {
+            LOGGER.warn("getBodyString出现问题!");
+        }
+        finally
+        {
+            if (reader != null)
+            {
+                try
+                {
+                    reader.close();
+                }
+                catch (IOException e)
+                {
+                    LOGGER.error(ExceptionUtils.getFullStackTrace(e));
+                }
+            }
+        }
+        return sb.toString();
+    }
+}

+ 262 - 0
master/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java

@@ -0,0 +1,262 @@
+package com.ruoyi.common.utils.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.constant.Constants;
+
+/**
+ * 通用http发送方法
+ *
+ * @author ruoyi
+ */
+public class HttpUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param)
+    {
+        return sendGet(url, param, Constants.UTF8);
+    }
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @param contentType 编码类型
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param, String contentType)
+    {
+        StringBuilder result = new StringBuilder();
+        BufferedReader in = null;
+        try
+        {
+            String urlNameString = url + "?" + param;
+            log.info("sendGet - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            URLConnection connection = realUrl.openConnection();
+            connection.setRequestProperty("accept", "*/*");
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            connection.connect();
+            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+            log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * 向指定 URL 发送POST方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendPost(String url, String param)
+    {
+        PrintWriter out = null;
+        BufferedReader in = null;
+        StringBuilder result = new StringBuilder();
+        try
+        {
+            String urlNameString = url;
+            log.info("sendPost - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            URLConnection conn = realUrl.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "utf-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            out = new PrintWriter(conn.getOutputStream());
+            out.print(param);
+            out.flush();
+            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+            log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (out != null)
+                {
+                    out.close();
+                }
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (IOException ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+
+    public static String sendSSLPost(String url, String param)
+    {
+        StringBuilder result = new StringBuilder();
+        String urlNameString = url + "?" + param;
+        try
+        {
+            log.info("sendSSLPost - {}", urlNameString);
+            SSLContext sc = SSLContext.getInstance("SSL");
+            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
+            URL console = new URL(urlNameString);
+            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "utf-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+
+            conn.setSSLSocketFactory(sc.getSocketFactory());
+            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
+            conn.connect();
+            InputStream is = conn.getInputStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String ret = "";
+            while ((ret = br.readLine()) != null)
+            {
+                if (ret != null && !"".equals(ret.trim()))
+                {
+                    result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
+                }
+            }
+            log.info("recv - {}", result);
+            conn.disconnect();
+            br.close();
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
+        }
+        return result.toString();
+    }
+
+    private static class TrustAnyTrustManager implements X509TrustManager
+    {
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers()
+        {
+            return new X509Certificate[] {};
+        }
+    }
+
+    private static class TrustAnyHostnameVerifier implements HostnameVerifier
+    {
+        @Override
+        public boolean verify(String hostname, SSLSession session)
+        {
+            return true;
+        }
+    }
+}

+ 56 - 0
master/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java

@@ -0,0 +1,56 @@
+package com.ruoyi.common.utils.ip;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.http.HttpUtils;
+import com.ruoyi.framework.config.RuoYiConfig;
+
+/**
+ * 获取地址类
+ *
+ * @author ruoyi
+ */
+public class AddressUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
+
+    // IP地址查询
+    public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
+
+    // 未知地址
+    public static final String UNKNOWN = "XX XX";
+
+    public static String getRealAddressByIP(String ip)
+    {
+        String address = UNKNOWN;
+        // 内网不查询
+        if (IpUtils.internalIp(ip))
+        {
+            return "内网IP";
+        }
+        if (RuoYiConfig.isAddressEnabled())
+        {
+            try
+            {
+                String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
+                if (StringUtils.isEmpty(rspStr))
+                {
+                    log.error("获取地理位置异常 {}", ip);
+                    return UNKNOWN;
+                }
+                JSONObject obj = JSONObject.parseObject(rspStr);
+                String region = obj.getString("pro");
+                String city = obj.getString("city");
+                return String.format("%s %s", region, city);
+            }
+            catch (Exception e)
+            {
+                log.error("获取地理位置异常 {}", ip);
+            }
+        }
+        return address;
+    }
+}

+ 195 - 0
master/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java

@@ -0,0 +1,195 @@
+package com.ruoyi.common.utils.ip;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import javax.servlet.http.HttpServletRequest;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.html.EscapeUtil;
+
+/**
+ * 获取IP方法
+ *
+ * @author ruoyi
+ */
+public class IpUtils
+{
+    public static String getIpAddr(HttpServletRequest request)
+    {
+        if (request == null)
+        {
+            return "unknown";
+        }
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getRemoteAddr();
+        }
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : EscapeUtil.clean(ip);
+    }
+
+    public static boolean internalIp(String ip)
+    {
+        byte[] addr = textToNumericFormatV4(ip);
+        return internalIp(addr) || "127.0.0.1".equals(ip);
+    }
+
+    private static boolean internalIp(byte[] addr)
+    {
+        if (StringUtils.isNull(addr) || addr.length < 2)
+        {
+            return true;
+        }
+        final byte b0 = addr[0];
+        final byte b1 = addr[1];
+        // 10.x.x.x/8
+        final byte SECTION_1 = 0x0A;
+        // 172.16.x.x/12
+        final byte SECTION_2 = (byte) 0xAC;
+        final byte SECTION_3 = (byte) 0x10;
+        final byte SECTION_4 = (byte) 0x1F;
+        // 192.168.x.x/16
+        final byte SECTION_5 = (byte) 0xC0;
+        final byte SECTION_6 = (byte) 0xA8;
+        switch (b0)
+        {
+            case SECTION_1:
+                return true;
+            case SECTION_2:
+                if (b1 >= SECTION_3 && b1 <= SECTION_4)
+                {
+                    return true;
+                }
+            case SECTION_5:
+                switch (b1)
+                {
+                    case SECTION_6:
+                        return true;
+                }
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 将IPv4地址转换成字节
+     *
+     * @param text IPv4地址
+     * @return byte 字节
+     */
+    public static byte[] textToNumericFormatV4(String text)
+    {
+        if (text.length() == 0)
+        {
+            return null;
+        }
+
+        byte[] bytes = new byte[4];
+        String[] elements = text.split("\\.", -1);
+        try
+        {
+            long l;
+            int i;
+            switch (elements.length)
+            {
+                case 1:
+                    l = Long.parseLong(elements[0]);
+                    if ((l < 0L) || (l > 4294967295L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);
+                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 2:
+                    l = Integer.parseInt(elements[0]);
+                    if ((l < 0L) || (l > 255L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l & 0xFF);
+                    l = Integer.parseInt(elements[1]);
+                    if ((l < 0L) || (l > 16777215L)) {
+                        return null;
+                    }
+                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 3:
+                    for (i = 0; i < 2; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    l = Integer.parseInt(elements[2]);
+                    if ((l < 0L) || (l > 65535L)) {
+                        return null;
+                    }
+                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 4:
+                    for (i = 0; i < 4; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    break;
+                default:
+                    return null;
+            }
+        }
+        catch (NumberFormatException e)
+        {
+            return null;
+        }
+        return bytes;
+    }
+
+    public static String getHostIp()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "127.0.0.1";
+    }
+
+    public static String getHostName()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "未知";
+    }
+}

+ 107 - 0
master/src/main/java/com/ruoyi/common/utils/job/AbstractQuartzJob.java

@@ -0,0 +1,107 @@
+package com.ruoyi.common.utils.job;
+
+import java.util.Date;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.utils.ExceptionUtil;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.project.monitor.domain.SysJob;
+import com.ruoyi.project.monitor.domain.SysJobLog;
+import com.ruoyi.project.monitor.service.ISysJobLogService;
+
+/**
+ * 抽象quartz调用
+ *
+ * @author ruoyi
+ */
+public abstract class AbstractQuartzJob implements Job
+{
+    private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
+
+    /**
+     * 线程本地变量
+     */
+    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException
+    {
+        SysJob sysJob = new SysJob();
+        BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
+        try
+        {
+            before(context, sysJob);
+            if (sysJob != null)
+            {
+                doExecute(context, sysJob);
+            }
+            after(context, sysJob, null);
+        }
+        catch (Exception e)
+        {
+            log.error("任务执行异常  - :", e);
+            after(context, sysJob, e);
+        }
+    }
+
+    /**
+     * 执行前
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     */
+    protected void before(JobExecutionContext context, SysJob sysJob)
+    {
+        threadLocal.set(new Date());
+    }
+
+    /**
+     * 执行后
+     *
+     * @param context 工作执行上下文对象
+     * @param sysScheduleJob 系统计划任务
+     */
+    protected void after(JobExecutionContext context, SysJob sysJob, Exception e)
+    {
+        Date startTime = threadLocal.get();
+        threadLocal.remove();
+
+        final SysJobLog sysJobLog = new SysJobLog();
+        sysJobLog.setJobName(sysJob.getJobName());
+        sysJobLog.setJobGroup(sysJob.getJobGroup());
+        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
+        sysJobLog.setStartTime(startTime);
+        sysJobLog.setStopTime(new Date());
+        long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
+        sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
+        if (e != null)
+        {
+            sysJobLog.setStatus(Constants.FAIL);
+            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
+            sysJobLog.setExceptionInfo(errorMsg);
+        }
+        else
+        {
+            sysJobLog.setStatus(Constants.SUCCESS);
+        }
+
+        // 写入数据库当中
+        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
+    }
+
+    /**
+     * 执行方法,由子类重载
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     * @throws Exception 执行过程中的异常
+     */
+    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
+}

+ 63 - 0
master/src/main/java/com/ruoyi/common/utils/job/CronUtils.java

@@ -0,0 +1,63 @@
+package com.ruoyi.common.utils.job;
+
+import java.text.ParseException;
+import java.util.Date;
+import org.quartz.CronExpression;
+
+/**
+ * cron表达式工具类
+ *
+ * @author ruoyi
+ *
+ */
+public class CronUtils
+{
+    /**
+     * 返回一个布尔值代表一个给定的Cron表达式的有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return boolean 表达式是否有效
+     */
+    public static boolean isValid(String cronExpression)
+    {
+        return CronExpression.isValidExpression(cronExpression);
+    }
+
+    /**
+     * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return String 无效时返回表达式错误描述,如果有效返回null
+     */
+    public static String getInvalidMessage(String cronExpression)
+    {
+        try
+        {
+            new CronExpression(cronExpression);
+            return null;
+        }
+        catch (ParseException pe)
+        {
+            return pe.getMessage();
+        }
+    }
+
+    /**
+     * 返回下一个执行时间根据给定的Cron表达式
+     *
+     * @param cronExpression Cron表达式
+     * @return Date 下次Cron表达式执行时间
+     */
+    public static Date getNextExecution(String cronExpression)
+    {
+        try
+        {
+            CronExpression cron = new CronExpression(cronExpression);
+            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
+        }
+        catch (ParseException e)
+        {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+}

+ 182 - 0
master/src/main/java/com/ruoyi/common/utils/job/JobInvokeUtil.java

@@ -0,0 +1,182 @@
+package com.ruoyi.common.utils.job;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.project.monitor.domain.SysJob;
+
+/**
+ * 任务执行工具
+ *
+ * @author ruoyi
+ */
+public class JobInvokeUtil
+{
+    /**
+     * 执行方法
+     *
+     * @param sysJob 系统任务
+     */
+    public static void invokeMethod(SysJob sysJob) throws Exception
+    {
+        String invokeTarget = sysJob.getInvokeTarget();
+        String beanName = getBeanName(invokeTarget);
+        String methodName = getMethodName(invokeTarget);
+        List<Object[]> methodParams = getMethodParams(invokeTarget);
+
+        if (!isValidClassName(beanName))
+        {
+            Object bean = SpringUtils.getBean(beanName);
+            invokeMethod(bean, methodName, methodParams);
+        }
+        else
+        {
+            Object bean = Class.forName(beanName).newInstance();
+            invokeMethod(bean, methodName, methodParams);
+        }
+    }
+
+    /**
+     * 调用任务方法
+     *
+     * @param bean 目标对象
+     * @param methodName 方法名称
+     * @param methodParams 方法参数
+     */
+    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
+            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException
+    {
+        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
+        {
+            Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
+            method.invoke(bean, getMethodParamsValue(methodParams));
+        }
+        else
+        {
+            Method method = bean.getClass().getDeclaredMethod(methodName);
+            method.invoke(bean);
+        }
+    }
+
+    /**
+     * 校验是否为为class包名
+     *
+     * @param str 名称
+     * @return true是 false否
+     */
+    public static boolean isValidClassName(String invokeTarget)
+    {
+        return StringUtils.countMatches(invokeTarget, ".") > 1;
+    }
+
+    /**
+     * 获取bean名称
+     *
+     * @param invokeTarget 目标字符串
+     * @return bean名称
+     */
+    public static String getBeanName(String invokeTarget)
+    {
+        String beanName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringBeforeLast(beanName, ".");
+    }
+
+    /**
+     * 获取bean方法
+     *
+     * @param invokeTarget 目标字符串
+     * @return method方法
+     */
+    public static String getMethodName(String invokeTarget)
+    {
+        String methodName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringAfterLast(methodName, ".");
+    }
+
+    /**
+     * 获取method方法参数相关列表
+     *
+     * @param invokeTarget 目标字符串
+     * @return method方法相关参数列表
+     */
+    public static List<Object[]> getMethodParams(String invokeTarget)
+    {
+        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
+        if (StringUtils.isEmpty(methodStr))
+        {
+            return null;
+        }
+        String[] methodParams = methodStr.split(",");
+        List<Object[]> classs = new LinkedList<>();
+        for (int i = 0; i < methodParams.length; i++)
+        {
+            String str = StringUtils.trimToEmpty(methodParams[i]);
+            // String字符串类型,包含'
+            if (StringUtils.contains(str, "'"))
+            {
+                classs.add(new Object[] { StringUtils.replace(str, "'", ""), String.class });
+            }
+            // boolean布尔类型,等于true或者false
+            else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false"))
+            {
+                classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
+            }
+            // long长整形,包含L
+            else if (StringUtils.containsIgnoreCase(str, "L"))
+            {
+                classs.add(new Object[] { Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class });
+            }
+            // double浮点类型,包含D
+            else if (StringUtils.containsIgnoreCase(str, "D"))
+            {
+                classs.add(new Object[] { Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class });
+            }
+            // 其他类型归类为整形
+            else
+            {
+                classs.add(new Object[] { Integer.valueOf(str), Integer.class });
+            }
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数类型
+     *
+     * @param methodParams 参数相关列表
+     * @return 参数类型列表
+     */
+    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
+    {
+        Class<?>[] classs = new Class<?>[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams)
+        {
+            classs[index] = (Class<?>) os[1];
+            index++;
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数值
+     *
+     * @param methodParams 参数相关列表
+     * @return 参数值列表
+     */
+    public static Object[] getMethodParamsValue(List<Object[]> methodParams)
+    {
+        Object[] classs = new Object[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams)
+        {
+            classs[index] = (Object) os[0];
+            index++;
+        }
+        return classs;
+    }
+}

+ 21 - 0
master/src/main/java/com/ruoyi/common/utils/job/QuartzDisallowConcurrentExecution.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.utils.job;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+import com.ruoyi.project.monitor.domain.SysJob;
+
+/**
+ * 定时任务处理(禁止并发执行)
+ *
+ * @author ruoyi
+ *
+ */
+@DisallowConcurrentExecution
+public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
+{
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
+    {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 19 - 0
master/src/main/java/com/ruoyi/common/utils/job/QuartzJobExecution.java

@@ -0,0 +1,19 @@
+package com.ruoyi.common.utils.job;
+
+import org.quartz.JobExecutionContext;
+import com.ruoyi.project.monitor.domain.SysJob;
+
+/**
+ * 定时任务处理(允许并发执行)
+ *
+ * @author ruoyi
+ *
+ */
+public class QuartzJobExecution extends AbstractQuartzJob
+{
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
+    {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 113 - 0
master/src/main/java/com/ruoyi/common/utils/job/ScheduleUtils.java

@@ -0,0 +1,113 @@
+package com.ruoyi.common.utils.job;
+
+import org.quartz.CronScheduleBuilder;
+import org.quartz.CronTrigger;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.exception.job.TaskException;
+import com.ruoyi.common.exception.job.TaskException.Code;
+import com.ruoyi.project.monitor.domain.SysJob;
+
+/**
+ * 定时任务工具类
+ *
+ * @author ruoyi
+ *
+ */
+public class ScheduleUtils
+{
+    /**
+     * 得到quartz任务类
+     *
+     * @param sysJob 执行计划
+     * @return 具体执行任务类
+     */
+    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
+    {
+        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
+        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
+    }
+
+    /**
+     * 构建任务触发对象
+     */
+    public static TriggerKey getTriggerKey(Long jobId, String jobGroup)
+    {
+        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 构建任务键对象
+     */
+    public static JobKey getJobKey(Long jobId, String jobGroup)
+    {
+        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 创建定时任务
+     */
+    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
+    {
+        Class<? extends Job> jobClass = getQuartzJobClass(job);
+        // 构建job信息
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
+
+        // 表达式调度构建器
+        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
+        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
+
+        // 按新的cronExpression表达式构建一个新的trigger
+        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
+                .withSchedule(cronScheduleBuilder).build();
+
+        // 放入参数,运行时的方法可以获取
+        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
+
+        // 判断是否存在
+        if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
+        {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(getJobKey(jobId, jobGroup));
+        }
+
+        scheduler.scheduleJob(jobDetail, trigger);
+
+        // 暂停任务
+        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
+        {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+    }
+
+    /**
+     * 设置定时任务策略
+     */
+    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
+            throws TaskException
+    {
+        switch (job.getMisfirePolicy())
+        {
+            case ScheduleConstants.MISFIRE_DEFAULT:
+                return cb;
+            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
+                return cb.withMisfireHandlingInstructionIgnoreMisfires();
+            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
+                return cb.withMisfireHandlingInstructionFireAndProceed();
+            case ScheduleConstants.MISFIRE_DO_NOTHING:
+                return cb.withMisfireHandlingInstructionDoNothing();
+            default:
+                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+                        + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
+        }
+    }
+}

+ 89 - 0
master/src/main/java/com/ruoyi/common/utils/poi/CustomXWPFDocument.java

@@ -0,0 +1,89 @@
+package com.ruoyi.common.utils.poi;
+
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlToken;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
+import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class CustomXWPFDocument  extends XWPFDocument {
+    public CustomXWPFDocument(InputStream in) throws IOException {
+        super(in);
+    }
+
+    public CustomXWPFDocument(OPCPackage pkg) throws IOException {
+        super(pkg);
+    }
+
+    public void createPicture(String blipId,int id, int width, int height, XWPFParagraph paragraph)
+    {
+        final int EMU = 9525;
+        width *= EMU;
+        height *= EMU;
+        //String blipId = getAllPictures().get(id).getPackageRelationship().getId();
+
+//        CTInline inline = createParagraph().createRun().getCTR().addNewDrawing().addNewInline();
+        //给段落插入图片
+        CTInline inline = paragraph.createRun().getCTR().addNewDrawing().addNewInline();
+
+        String picXml = "" +
+                "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">" +
+                "   <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +
+                "      <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +
+                "         <pic:nvPicPr>" +
+                "            <pic:cNvPr id=\"" + id + "\" name=\"Generated\"/>" +
+                "            <pic:cNvPicPr/>" +
+                "         </pic:nvPicPr>" +
+                "         <pic:blipFill>" +
+                "            <a:blip r:embed=\"" + blipId + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>" +
+                "            <a:stretch>" +
+                "               <a:fillRect/>" +
+                "            </a:stretch>" +
+                "         </pic:blipFill>" +
+                "         <pic:spPr>" +
+                "            <a:xfrm>" +
+                "               <a:off x=\"0\" y=\"0\"/>" +
+                "               <a:ext cx=\"" + width + "\" cy=\"" + height + "\"/>" +
+                "            </a:xfrm>" +
+                "            <a:prstGeom prst=\"rect\">" +
+                "               <a:avLst/>" +
+                "            </a:prstGeom>" +
+                "         </pic:spPr>" +
+                "      </pic:pic>" +
+                "   </a:graphicData>" +
+                "</a:graphic>";
+
+        //CTGraphicalObjectData graphicData = inline.addNewGraphic().addNewGraphicData();
+        XmlToken xmlToken = null;
+        try
+        {
+            xmlToken = XmlToken.Factory.parse(picXml);
+        }
+        catch(XmlException xe)
+        {
+            xe.printStackTrace();
+        }
+        inline.set(xmlToken);
+        //graphicData.set(xmlToken);
+
+        inline.setDistT(0);
+        inline.setDistB(0);
+        inline.setDistL(0);
+        inline.setDistR(0);
+
+        CTPositiveSize2D extent = inline.addNewExtent();
+        extent.setCx(width);
+        extent.setCy(height);
+
+        CTNonVisualDrawingProps docPr = inline.addNewDocPr();
+        docPr.setId(id);
+        docPr.setName("Picture " + id);
+        docPr.setDescr("Generated");
+    }
+}

+ 1052 - 0
master/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java

@@ -0,0 +1,1052 @@
+package com.ruoyi.common.utils.poi;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import org.apache.poi.hssf.usermodel.HSSFDateUtil;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DataValidation;
+import org.apache.poi.ss.usermodel.DataValidationConstraint;
+import org.apache.poi.ss.usermodel.DataValidationHelper;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.exception.CustomException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.DictUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel.ColumnType;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel.Type;
+import com.ruoyi.framework.aspectj.lang.annotation.Excels;
+import com.ruoyi.framework.config.RuoYiConfig;
+import com.ruoyi.framework.web.domain.AjaxResult;
+
+/**
+ * Excel相关处理
+ *
+ * @author ruoyi
+ */
+public class ExcelUtil<T>
+{
+    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+
+    /**
+     * Excel sheet最大行数,默认65536
+     */
+    public static final int sheetSize = 65536;
+
+    /**
+     * 工作表名称
+     */
+    private String sheetName;
+    /**
+     * 工作表标题
+     */
+    private String title;
+
+    /**
+     * 导出类型(EXPORT:导出数据;IMPORT:导入模板)
+     */
+    private Type type;
+
+    /**
+     * 工作薄对象
+     */
+    private Workbook wb;
+
+    /**
+     * 工作表对象
+     */
+    private Sheet sheet;
+
+    /**
+     * 样式列表
+     */
+    private Map<String, CellStyle> styles;
+
+    /**
+     * 导入导出数据列表
+     */
+    private List<T> list;
+
+    /**
+     * 注解列表
+     */
+    private List<Object[]> fields;
+
+    /**
+     * 统计列表
+     */
+    private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
+
+    /**
+     * 数字格式
+     */
+    private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
+
+    /**
+     * 实体对象
+     */
+    public Class<T> clazz;
+
+
+    public ExcelUtil(Class<T> clazz)
+    {
+        this.clazz = clazz;
+    }
+
+    public void init(List<T> list, String sheetName, Type type)
+    {
+        if (list == null)
+        {
+            list = new ArrayList<T>();
+        }
+        this.list = list;
+        this.sheetName = sheetName;
+        this.type = type;
+        createExcelField();
+        createWorkbook();
+    }
+
+    public void init(List<T> list, String sheetName, Type type ,String title)
+    {
+        if (list == null)
+        {
+            list = new ArrayList<T>();
+        }
+        this.list = list;
+        this.sheetName = sheetName;
+        this.type = type;
+        this.title = title;
+        createExcelField();
+        createWorkbook();
+    }
+
+    /**
+     * 对excel表单默认第一个索引名转换成list
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(InputStream is) throws Exception
+    {
+        return importExcel(StringUtils.EMPTY, is);
+    }
+
+    /**
+     * 对excel表单指定表格索引名转换成list
+     *
+     * @param sheetName 表格索引名
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(String sheetName, InputStream is) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        List<T> list = new ArrayList<T>();
+        Sheet sheet = null;
+        if (StringUtils.isNotEmpty(sheetName))
+        {
+            // 如果指定sheet名,则取指定sheet中的内容.
+            sheet = wb.getSheet(sheetName);
+        }
+        else
+        {
+            // 如果传入的sheet名不存在则默认指向第1个sheet.
+            sheet = wb.getSheetAt(0);
+        }
+
+        if (sheet == null)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+
+        int rows = sheet.getPhysicalNumberOfRows();
+
+        if (rows > 0)
+        {
+            // 定义一个map用于存放excel列的序号和field.
+            Map<String, Integer> cellMap = new HashMap<String, Integer>();
+            // 获取表头
+            Row heard = sheet.getRow(0);
+            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
+            {
+                Cell cell = heard.getCell(i);
+                if (StringUtils.isNotNull(cell))
+                {
+                    String value = this.getCellValue(heard, i).toString();
+                    cellMap.put(value, i);
+                }
+                else
+                {
+                    cellMap.put(null, i);
+                }
+            }
+            // 有数据时才处理 得到类的所有field.
+            Field[] allFields = clazz.getDeclaredFields();
+            // 定义一个map用于存放列的序号和field.
+            Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
+            for (int col = 0; col < allFields.length; col++)
+            {
+                Field field = allFields[col];
+                Excel attr = field.getAnnotation(Excel.class);
+                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                {
+                    // 设置类的私有字段属性可访问.
+                    field.setAccessible(true);
+                    Integer column = cellMap.get(attr.name());
+                    if (column != null)
+                    {
+                        fieldsMap.put(column, field);
+                    }
+                }
+            }
+            for (int i = 1; i < rows; i++)
+            {
+                // 从第2行开始取数据,默认第一行是表头.
+                Row row = sheet.getRow(i);
+                T entity = null;
+                for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
+                {
+                    Object val = this.getCellValue(row, entry.getKey());
+
+                    // 如果不存在实例则新建.
+                    entity = (entity == null ? clazz.newInstance() : entity);
+                    // 从map中得到对应列的field.
+                    Field field = fieldsMap.get(entry.getKey());
+                    // 取得类型,并根据对象类型设置值.
+                    Class<?> fieldType = field.getType();
+                    if (String.class == fieldType)
+                    {
+                        String s = Convert.toStr(val);
+                        if (StringUtils.endsWith(s, ".0"))
+                        {
+                            val = StringUtils.substringBefore(s, ".0");
+                        }
+                        else
+                        {
+                            val = Convert.toStr(val);
+                        }
+                    }
+                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
+                    {
+                        val = Convert.toInt(val);
+                    }
+                    else if (Long.TYPE == fieldType || Long.class == fieldType)
+                    {
+                        val = Convert.toLong(val);
+                    }
+                    else if (Double.TYPE == fieldType || Double.class == fieldType)
+                    {
+                        val = Convert.toDouble(val);
+                    }
+                    else if (Float.TYPE == fieldType || Float.class == fieldType)
+                    {
+                        val = Convert.toFloat(val);
+                    }
+                    else if (BigDecimal.class == fieldType)
+                    {
+                        val = Convert.toBigDecimal(val);
+                    }
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    if (StringUtils.isNotNull(fieldType))
+                    {
+                        Excel attr = field.getAnnotation(Excel.class);
+                        String propertyName = field.getName();
+                        if (StringUtils.isNotEmpty(attr.targetAttr()))
+                        {
+                            propertyName = field.getName() + "." + attr.targetAttr();
+                        }
+                        else if (StringUtils.isNotEmpty(attr.readConverterExp()))
+                        {
+                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
+                        }
+                        else if (StringUtils.isNotEmpty(attr.dictType()))
+                        {
+                            val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
+                        }
+                        ReflectUtils.invokeSetter(entity, propertyName, val);
+                    }
+                }
+                list.add(entity);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @param list 导出数据集合
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult exportExcel(List<T> list, String sheetName)
+    {
+        this.init(list, sheetName, Type.EXPORT);
+        return exportExcel();
+    }
+
+    public AjaxResult exportExcel(List<T> list, String sheetName , String title)
+    {
+        this.init(list, sheetName, Type.EXPORT ,title);
+        return exportExcel();
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult importTemplateExcel(String sheetName)
+    {
+        this.init(null, sheetName, Type.IMPORT);
+        return exportExcel();
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @return 结果
+     */
+    public AjaxResult exportExcel()
+    {
+        OutputStream out = null;
+        try
+        {
+            // 取出一共有多少个sheet.
+            double sheetNo = Math.ceil(list.size() / sheetSize);
+            for (int index = 0; index <= sheetNo; index++)
+            {
+                createSheet(sheetNo, index);
+
+                if (StringUtils.isNotEmpty(this.title)){
+                    Row row1 = sheet.createRow(0);
+                    row1.setHeight((short) 800);
+                    // 创建列
+                    Cell cell = row1.createCell(0);
+                    // 写入列信息
+                    cell.setCellValue(this.title);
+                    CellStyle style = wb.createCellStyle();
+                    style.cloneStyleFrom(styles.get("data"));
+                    style.setAlignment(HorizontalAlignment.LEFT);
+                    style.setVerticalAlignment(VerticalAlignment.CENTER);
+                    style.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
+                    style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+                    Font headerFont = wb.createFont();
+                    headerFont.setFontName("Arial");
+                    headerFont.setFontHeightInPoints((short) 20);
+                    headerFont.setBold(false);
+                    headerFont.setColor(IndexedColors.BLACK.getIndex());
+                    style.setFont(headerFont);
+                    cell.setCellStyle(style);
+                    CellRangeAddress region = new CellRangeAddress(0, 0, 0, fields.size()-1);
+                    sheet.addMergedRegion(region);
+                }
+                // 产生一行
+                Row row = sheet.createRow(1);
+                int column = 0;
+                // 写入各个字段的列头名称
+                for (Object[] os : fields)
+                {
+                    Excel excel = (Excel) os[1];
+                    this.createCell(excel, row, column++);
+                }
+                if (Type.EXPORT.equals(type))
+                {
+                    fillExcelData(index, row);
+                    addStatisticsRow();
+                }
+            }
+            String filename = encodingFilename(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            wb.write(out);
+            return AjaxResult.success(filename);
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new CustomException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 填充excel数据
+     *
+     * @param index 序号
+     * @param row 单元格行
+     */
+    public void fillExcelData(int index, Row row)
+    {
+        int startNo = index * sheetSize;
+        int endNo = Math.min(startNo + sheetSize, list.size());
+        for (int i = startNo; i < endNo; i++)
+        {
+            row = sheet.createRow(i + 2 - startNo);
+            // 得到导出对象.
+            T vo = (T) list.get(i);
+            int column = 0;
+            for (Object[] os : fields)
+            {
+                Field field = (Field) os[0];
+                Excel excel = (Excel) os[1];
+                // 设置实体类私有属性可访问
+                field.setAccessible(true);
+                this.addCell(excel, row, vo, field, column++);
+            }
+        }
+    }
+
+    /**
+     * 创建表格样式
+     *
+     * @param wb 工作薄对象
+     * @return 样式列表
+     */
+    private Map<String, CellStyle> createStyles(Workbook wb)
+    {
+        // 写入各条记录,每条记录对应excel表中的一行
+        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
+        CellStyle style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderTop(BorderStyle.THIN);
+        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        Font dataFont = wb.createFont();
+        dataFont.setFontName("Arial");
+        dataFont.setFontHeightInPoints((short) 10);
+        style.setFont(dataFont);
+        styles.put("data", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        Font headerFont = wb.createFont();
+        headerFont.setFontName("Arial");
+        headerFont.setFontHeightInPoints((short) 10);
+        headerFont.setBold(true);
+        headerFont.setColor(IndexedColors.WHITE.getIndex());
+        style.setFont(headerFont);
+        styles.put("header", style);
+
+        style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        Font totalFont = wb.createFont();
+        totalFont.setFontName("Arial");
+        totalFont.setFontHeightInPoints((short) 10);
+        style.setFont(totalFont);
+        styles.put("total", style);
+
+        return styles;
+    }
+
+    /**
+     * 创建单元格
+     */
+    public Cell createCell(Excel attr, Row row, int column)
+    {
+        // 创建列
+        Cell cell = row.createCell(column);
+        // 写入列信息
+        cell.setCellValue(attr.name());
+        setDataValidation(attr, row, column);
+        cell.setCellStyle(styles.get("header"));
+        return cell;
+    }
+
+    /**
+     * 设置单元格信息
+     *
+     * @param value 单元格值
+     * @param attr 注解相关
+     * @param cell 单元格信息
+     */
+    public void setCellVo(Object value, Excel attr, Cell cell)
+    {
+        if (ColumnType.STRING == attr.cellType())
+        {
+            cell.setCellType(CellType.STRING);
+            cell.setCellValue(StringUtils.isNull(value) ? attr.defaultValue() : value + attr.suffix());
+        }
+        else if (ColumnType.NUMERIC == attr.cellType())
+        {
+            cell.setCellType(CellType.NUMERIC);
+            cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
+        }
+    }
+
+    /**
+     * 创建表格样式
+     */
+    public void setDataValidation(Excel attr, Row row, int column)
+    {
+        if (attr.name().indexOf("注:") >= 0)
+        {
+            sheet.setColumnWidth(column, 6000);
+        }
+        else
+        {
+            // 设置列宽
+            sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
+            row.setHeight((short) (attr.height() * 20));
+        }
+        // 如果设置了提示信息则鼠标放上去提示.
+        if (StringUtils.isNotEmpty(attr.prompt()))
+        {
+            // 这里默认设了2-101列提示.
+            setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column);
+        }
+        // 如果设置了combo属性则本列只能选择不能输入
+        if (attr.combo().length > 0)
+        {
+            // 这里默认设了2-101列只能选择不能输入.
+            setXSSFValidation(sheet, attr.combo(), 1, 100, column, column);
+        }
+    }
+
+    /**
+     * 添加单元格
+     */
+    public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
+    {
+        Cell cell = null;
+        try
+        {
+            // 设置行高
+            row.setHeight((short) (attr.height() * 20));
+            // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
+            if (attr.isExport())
+            {
+                // 创建cell
+                cell = row.createCell(column);
+                cell.setCellStyle(styles.get("data"));
+
+                // 用于读取对象中的属性
+                Object value = getTargetValue(vo, field, attr);
+                String dateFormat = attr.dateFormat();
+                String readConverterExp = attr.readConverterExp();
+                String separator = attr.separator();
+                String dictType = attr.dictType();
+                if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
+                {
+                    cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
+                }
+                else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
+                {
+                    cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
+                }
+                else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value))
+                {
+                    cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator));
+                }
+                else if (value instanceof BigDecimal && -1 != attr.scale())
+                {
+                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
+                }
+                else
+                {
+                    // 设置列类型
+                    setCellVo(value, attr, cell);
+                }
+                addStatisticsData(column, Convert.toStr(value), attr);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel失败{}", e);
+        }
+        return cell;
+    }
+
+    /**
+     * 设置 POI XSSFSheet 单元格提示
+     *
+     * @param sheet 表单
+     * @param promptTitle 提示标题
+     * @param promptContent 提示内容
+     * @param firstRow 开始行
+     * @param endRow 结束行
+     * @param firstCol 开始列
+     * @param endCol 结束列
+     */
+    public void setXSSFPrompt(Sheet sheet, String promptTitle, String promptContent, int firstRow, int endRow,
+            int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        DataValidationConstraint constraint = helper.createCustomConstraint("DD1");
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        dataValidation.createPromptBox(promptTitle, promptContent);
+        dataValidation.setShowPromptBox(true);
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 设置某些列的值只能输入预制的数据,显示下拉框.
+     *
+     * @param sheet 要设置的sheet.
+     * @param textlist 下拉框显示的内容
+     * @param firstRow 开始行
+     * @param endRow 结束行
+     * @param firstCol 开始列
+     * @param endCol 结束列
+     * @return 设置好的sheet.
+     */
+    public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        // 加载下拉列表内容
+        DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist);
+        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        // 数据有效性对象
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        // 处理Excel兼容性问题
+        if (dataValidation instanceof XSSFDataValidation)
+        {
+            dataValidation.setSuppressDropDownArrow(true);
+            dataValidation.setShowErrorBox(true);
+        }
+        else
+        {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 解析导出值 0=男,1=女,2=未知
+     *
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String convertByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StringUtils.containsAny(separator, propertyValue))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[0].equals(value))
+                    {
+                        propertyString.append(itemArray[1] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[0].equals(propertyValue))
+                {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 反向解析值 男=0,女=1,未知=2
+     *
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String reverseByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StringUtils.containsAny(separator, propertyValue))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[1].equals(value))
+                    {
+                        propertyString.append(itemArray[0] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[1].equals(propertyValue))
+                {
+                    return itemArray[0];
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 解析字典值
+     *
+     * @param dictValue 字典值
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String convertDictByExp(String dictValue, String dictType, String separator)
+    {
+        return DictUtils.getDictLabel(dictType, dictValue, separator);
+    }
+
+    /**
+     * 反向解析值字典值
+     *
+     * @param dictLabel 字典标签
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
+    {
+        return DictUtils.getDictValue(dictType, dictLabel, separator);
+    }
+
+    /**
+     * 合计统计信息
+     */
+    private void addStatisticsData(Integer index, String text, Excel entity)
+    {
+        if (entity != null && entity.isStatistics())
+        {
+            Double temp = 0D;
+            if (!statistics.containsKey(index))
+            {
+                statistics.put(index, temp);
+            }
+            try
+            {
+                temp = Double.valueOf(text);
+            }
+            catch (NumberFormatException e)
+            {
+            }
+            statistics.put(index, statistics.get(index) + temp);
+        }
+    }
+
+    /**
+     * 创建统计行
+     */
+    public void addStatisticsRow()
+    {
+        if (statistics.size() > 0)
+        {
+            Cell cell = null;
+            Row row = sheet.createRow(sheet.getLastRowNum() + 1);
+            Set<Integer> keys = statistics.keySet();
+            cell = row.createCell(0);
+            cell.setCellStyle(styles.get("total"));
+            cell.setCellValue("合计");
+
+            for (Integer key : keys)
+            {
+                cell = row.createCell(key);
+                cell.setCellStyle(styles.get("total"));
+                cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
+            }
+            statistics.clear();
+        }
+    }
+
+    /**
+     * 编码文件名
+     */
+    public String encodingFilename(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx";
+        return filename;
+    }
+
+    /**
+     * 获取下载路径
+     *
+     * @param filename 文件名称
+     */
+    public String getAbsoluteFile(String filename)
+    {
+        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
+        File desc = new File(downloadPath);
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        return downloadPath;
+    }
+
+    /**
+     * 获取bean中的属性值
+     *
+     * @param vo 实体对象
+     * @param field 字段
+     * @param excel 注解
+     * @return 最终的属性值
+     * @throws Exception
+     */
+    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
+    {
+        Object o = field.get(vo);
+        if (StringUtils.isNotEmpty(excel.targetAttr()))
+        {
+            String target = excel.targetAttr();
+            if (target.indexOf(".") > -1)
+            {
+                String[] targets = target.split("[.]");
+                for (String name : targets)
+                {
+                    o = getValue(o, name);
+                }
+            }
+            else
+            {
+                o = getValue(o, target);
+            }
+        }
+        return o;
+    }
+
+    /**
+     * 以类的属性的get方法方法形式获取值
+     *
+     * @param o
+     * @param name
+     * @return value
+     * @throws Exception
+     */
+    private Object getValue(Object o, String name) throws Exception
+    {
+        if (StringUtils.isNotEmpty(name))
+        {
+            Class<?> clazz = o.getClass();
+            Field field = clazz.getDeclaredField(name);
+            field.setAccessible(true);
+            o = field.get(o);
+        }
+        return o;
+    }
+
+    /**
+     * 得到所有定义字段
+     */
+    private void createExcelField()
+    {
+        this.fields = new ArrayList<Object[]>();
+        List<Field> tempFields = new ArrayList<>();
+        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
+        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+        for (Field field : tempFields)
+        {
+            // 单注解
+            if (field.isAnnotationPresent(Excel.class))
+            {
+                putToField(field, field.getAnnotation(Excel.class));
+            }
+
+            // 多注解
+            if (field.isAnnotationPresent(Excels.class))
+            {
+                Excels attrs = field.getAnnotation(Excels.class);
+                Excel[] excels = attrs.value();
+                for (Excel excel : excels)
+                {
+                    putToField(field, excel);
+                }
+            }
+        }
+        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
+    }
+
+    /**
+     * 放到字段集合中
+     */
+    private void putToField(Field field, Excel attr)
+    {
+        if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+        {
+            this.fields.add(new Object[] { field, attr });
+        }
+    }
+
+    /**
+     * 创建一个工作簿
+     */
+    public void createWorkbook()
+    {
+        this.wb = new SXSSFWorkbook(500);
+    }
+
+    /**
+     * 创建工作表
+     *
+     * @param sheetNo sheet数量
+     * @param index 序号
+     */
+    public void createSheet(double sheetNo, int index)
+    {
+        this.sheet = wb.createSheet();
+        this.styles = createStyles(wb);
+        // 设置工作表的名称.
+        if (sheetNo == 0)
+        {
+            wb.setSheetName(index, sheetName);
+        }
+        else
+        {
+            wb.setSheetName(index, sheetName + index);
+        }
+    }
+
+    /**
+     * 获取单元格值
+     *
+     * @param row 获取的行
+     * @param column 获取单元格列号
+     * @return 单元格值
+     */
+    public Object getCellValue(Row row, int column)
+    {
+        if (row == null)
+        {
+            return row;
+        }
+        Object val = "";
+        try
+        {
+            Cell cell = row.getCell(column);
+            if (StringUtils.isNotNull(cell))
+            {
+                if (cell.getCellTypeEnum() == CellType.NUMERIC || cell.getCellTypeEnum() == CellType.FORMULA)
+                {
+                    val = cell.getNumericCellValue();
+                    if (HSSFDateUtil.isCellDateFormatted(cell))
+                    {
+                        val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
+                    }
+                    else
+                    {
+                        if ((Double) val % 1 > 0)
+                        {
+                            val = new BigDecimal(val.toString());
+                        }
+                        else
+                        {
+                            val = new DecimalFormat("0").format(val);
+                        }
+                    }
+                }
+                else if (cell.getCellTypeEnum() == CellType.STRING)
+                {
+                    val = cell.getStringCellValue();
+                }
+                else if (cell.getCellTypeEnum() == CellType.BOOLEAN)
+                {
+                    val = cell.getBooleanCellValue();
+                }
+                else if (cell.getCellTypeEnum() == CellType.ERROR)
+                {
+                    val = cell.getErrorCellValue();
+                }
+
+            }
+        }
+        catch (Exception e)
+        {
+            return val;
+        }
+        return val;
+    }
+}

+ 39 - 0
master/src/main/java/com/ruoyi/common/utils/poi/POIConstants.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.utils.poi;
+/**
+ * poi常量
+ * @author bzm
+ * 2020-8-25
+ */
+public class POIConstants {
+
+	//字体
+	public static final String FONT_NAME ="微软雅黑";
+
+	//字体大小
+	public static final String FONT_SIAZE ="7";
+	//字体大小
+	public static final String MT_FONT_SIZE ="10";
+	//字体大小 0:不加粗,1:加粗
+	public static final Integer FONT_BOLD = 0;
+
+	//字体大小 0:不加粗,1:加粗
+	public static final Integer FONT_BOLD_C = 1;
+	//单元格对齐方式-横向
+	public static final String ALIGNMENT ="center";
+
+	//单元格对齐方式-垂直方向
+	public static final String VERTICAL ="center";
+
+	//字体颜色
+	public static final String FONT_COLOR ="#000000";
+
+	//表格标题背景颜色
+	public static final String BG_TITLE_COLOR ="#80bdf9";
+	public static final String MT_BG_TITLE_COLOR ="#FFFFFF";
+	//表格内容背景颜色
+	public static final String BG_COLOR ="#FFFFFF";
+
+	//单元格宽度:Set the width to the value "auto", an integer value (20ths of a point), or a percentage ("nn.nn%")
+	public static final String CELL_WIDTH ="auto";
+
+}

+ 30 - 0
master/src/main/java/com/ruoyi/common/utils/poi/PoiWordTitle.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.utils.poi;
+
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTTitle;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTTx;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
+
+import java.util.List;
+
+/**
+ * 更新图表的标题
+ */
+public class PoiWordTitle {
+
+    public static void setBarTitle(CTChart ctChart, String title) {
+        CTTitle ctTitle = ctChart.getTitle();
+        CTTx ctTx = ctTitle.getTx();
+        if (null != ctTx) {
+            CTTextBody ctTextBody = ctTx.getRich();
+            List<CTTextParagraph> ctTextParagraphslist = ctTextBody.getPList();
+            CTTextParagraph ctTextParagraph1 = ctTextParagraphslist.get(0);
+            List<CTRegularTextRun> ctRegularTextRunslist = ctTextParagraph1.getRList();
+            ctRegularTextRunslist.get(0).setT(title);  // 设置标题
+        }
+
+
+    }
+}

+ 777 - 0
master/src/main/java/com/ruoyi/common/utils/poi/PoiWordTools.java

@@ -0,0 +1,777 @@
+package com.ruoyi.common.utils.poi;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xwpf.usermodel.*;
+import org.openxmlformats.schemas.drawingml.x2006.chart.*;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * poi生成word的工具类
+ * 针对于模板中的图表是静态的,也就是模板中的图表长什么样子不会根据数据而改变
+ * https://blog.csdn.net/u014427811/article/details/100771314
+ */
+public class PoiWordTools {
+
+    private static final BigDecimal bd2 = new BigDecimal("2");
+
+
+    /**
+     * 调用替换雷达图数据
+     */
+    public static void replaceRadarCharts(POIXMLDocumentPart poixmlDocumentPart,
+                                          List<String> titleArr, List<String> fldNameArr, List<Map<String, String>> listItemsByType) {
+        XWPFChart chart = (XWPFChart) poixmlDocumentPart;
+        chart.getCTChart();
+
+        //根据属性第一列名称切换数据类型
+        CTChart ctChart = chart.getCTChart();
+        CTPlotArea plotArea = ctChart.getPlotArea();
+
+        // 设置标题
+        // PoiWordTitle.setBarTitle(ctChart, "我是雷达图标题");
+
+        CTRadarChart radarChart = plotArea.getRadarChartArray(0);
+        List<CTRadarSer> radarList = radarChart.getSerList();  // 获取雷达图单位
+
+        //刷新内置excel数据
+        PoiWordTools.refreshExcel(chart, listItemsByType, fldNameArr, titleArr);
+        //刷新页面显示数据
+        refreshRadarStrGraphContent(radarChart, radarList, listItemsByType, fldNameArr, 1);
+
+
+    }
+
+
+    /**
+     * 调用替换柱状图数据
+     */
+    public static void replaceBarCharts(POIXMLDocumentPart poixmlDocumentPart,
+                                        List<String> titleArr, List<String> fldNameArr, List<Map<String, String>> listItemsByType) {
+        XWPFChart chart = (XWPFChart) poixmlDocumentPart;
+        chart.getCTChart();
+
+        //根据属性第一列名称切换数据类型
+        CTChart ctChart = chart.getCTChart();
+        CTPlotArea plotArea = ctChart.getPlotArea();
+
+        // 设置标题
+       // PoiWordTitle.setBarTitle(ctChart, "我是修改后的标题");
+
+        CTBarChart barChart = plotArea.getBarChartArray(0);
+        List<CTBarSer> BarSerList = barChart.getSerList();  // 获取柱状图单位
+
+        //刷新内置excel数据
+        PoiWordTools.refreshExcel(chart, listItemsByType, fldNameArr, titleArr);
+        //刷新页面显示数据
+        refreshBarStrGraphContent(barChart, BarSerList, listItemsByType, fldNameArr, 1);
+
+
+    }
+
+
+    /**
+     * 调用替换折线图数据
+     */
+    public static void replaceLineCharts(POIXMLDocumentPart poixmlDocumentPart,
+                                         List<String> titleArr, List<String> fldNameArr, List<Map<String, String>> listItemsByType) {
+        XWPFChart chart = (XWPFChart) poixmlDocumentPart;
+        chart.getCTChart();
+
+        //根据属性第一列名称切换数据类型
+        CTChart ctChart = chart.getCTChart();
+        CTPlotArea plotArea = ctChart.getPlotArea();
+
+        CTLineChart lineChart = plotArea.getLineChartArray(0);
+        List<CTLineSer> lineSerList = lineChart.getSerList();   // 获取折线图单位
+
+        //刷新内置excel数据
+         PoiWordTools.refreshExcel(chart, listItemsByType, fldNameArr, titleArr);
+        //刷新页面显示数据
+        PoiWordTools.refreshLineStrGraphContent(lineChart, lineSerList, listItemsByType, fldNameArr, 1);
+
+    }
+
+
+    /**
+     * 调用替换饼图数据
+     */
+    public static void replacePieCharts(POIXMLDocumentPart poixmlDocumentPart,
+                                        List<String> titleArr, List<String> fldNameArr, List<Map<String, String>> listItemsByType) {
+        XWPFChart chart = (XWPFChart) poixmlDocumentPart;
+        chart.getCTChart();
+
+        //根据属性第一列名称切换数据类型
+        CTChart ctChart = chart.getCTChart();
+        CTPlotArea plotArea = ctChart.getPlotArea();
+
+        CTPieChart pieChart = plotArea.getPieChartArray(0);
+        List<CTPieSer> pieSerList = pieChart.getSerList();  // 获取饼图单位
+
+        //刷新内置excel数据
+        PoiWordTools.refreshExcel(chart, listItemsByType, fldNameArr, titleArr);
+        //刷新页面显示数据
+        PoiWordTools.refreshPieStrGraphContent(pieChart, pieSerList, listItemsByType, fldNameArr, 1);
+
+    }
+
+
+    /**
+     *  调用替换柱状图、折线图组合数据
+     *  eg:3项(2柱状,1折线)
+     * @param poixmlDocumentPart
+     * @param titleArr
+     * @param fldNameArr
+     * @param listItemsByType
+     * @param barPos 柱状图数据起始位置
+     * @param linePos 折线图数据起始位置
+     */
+    public static void replaceCombinationCharts(POIXMLDocumentPart poixmlDocumentPart,
+                                                List<String> titleArr, List<String> fldNameArr, List<Map<String, String>> listItemsByType,
+                                                int barPos,int linePos) {
+        XWPFChart chart = (XWPFChart) poixmlDocumentPart;
+        chart.getCTChart();
+        //根据属性第一列名称切换数据类型
+        CTChart ctChart = chart.getCTChart();
+        CTPlotArea plotArea = ctChart.getPlotArea();
+        CTBarChart barChart = plotArea.getBarChartArray(0);
+        List<CTBarSer> barSerList = barChart.getSerList();  // 获取柱状图单位
+        //刷新内置excel数据
+        PoiWordTools.refreshExcel(chart, listItemsByType, fldNameArr, titleArr);
+        //刷新页面显示数据   数据中下标1开始的是柱状图数据,所以这个是1
+        refreshBarStrGraphContent(barChart, barSerList, listItemsByType, fldNameArr, barPos);
+        CTLineChart lineChart = plotArea.getLineChartArray(0);
+        List<CTLineSer> lineSerList = lineChart.getSerList();   // 获取折线图单位
+        //刷新内置excel数据   有一个就可以了    有一个就可以了    有一个就可以了
+        //PoiWordTools.refreshExcel(chart, listItemsByType, fldNameArr, titleArr);
+        //刷新页面显示数据   数据中下标3开始的是折线图的数据,所以这个是3
+        PoiWordTools.refreshLineStrGraphContent(lineChart, lineSerList, listItemsByType, fldNameArr, linePos);
+
+    }
+
+
+    /**
+     * 刷新折线图数据方法
+     *
+     * @param typeChart
+     * @param serList
+     * @param dataList
+     * @param fldNameArr
+     * @param position
+     * @return
+     */
+    @SuppressWarnings("unused")
+	public static boolean refreshLineStrGraphContent(Object typeChart,
+                                                     List<?> serList, List<Map<String, String>> dataList, List<String> fldNameArr, int position) {
+
+        boolean result = true;
+        //更新数据区域
+        for (int i = 0; i < serList.size(); i++) {
+            CTAxDataSource cat = null;
+            CTNumDataSource val = null;
+            CTLineSer ser = ((CTLineChart) typeChart).getSerArray(i);
+            // 设置标题
+            CTSerTx tx = ser.getTx();
+         //   tx.getStrRef().getStrCache().getPtList().get(0).setV("阿里嘎痛"); // wps和office都能打开
+
+            // Category Axis Data
+            cat = ser.getCat();
+            // 获取图表的值
+            val = ser.getVal();
+            // strData.set
+            CTStrData strData = cat.getStrRef().getStrCache();
+            CTNumData numData = val.getNumRef().getNumCache();
+            strData.setPtArray((CTStrVal[]) null); // unset old axis text
+            numData.setPtArray((CTNumVal[]) null); // unset old values
+
+            // set model
+            long idx = 0;
+            for (int j = 0; j < dataList.size(); j++) {
+                //判断获取的值是否为空
+                String value = "0";
+                String cc =dataList.get(j).get(fldNameArr.get(i + position)) == null ? "0" :dataList.get(j).get(fldNameArr.get(i + position));
+                value = new BigDecimal(cc).toString();
+              //  if (!"0".equals(value)) {
+                    CTNumVal numVal = numData.addNewPt();//序列值
+                    numVal.setIdx(idx);
+                    numVal.setV(value);
+               // }
+                CTStrVal sVal = strData.addNewPt();//序列名称
+                sVal.setIdx(idx);
+                sVal.setV(dataList.get(j).get(fldNameArr.get(0)));
+                idx++;
+            }
+            numData.getPtCount().setVal(idx);
+            strData.getPtCount().setVal(idx);
+
+
+            //赋值横坐标数据区域
+            String axisDataRange = new CellRangeAddress(1, dataList.size(), 0, 0)
+                    .formatAsString("Sheet1", false);
+            cat.getStrRef().setF(axisDataRange);
+
+            //数据区域
+            String numDataRange = new CellRangeAddress(1, dataList.size(), i + position, i + position)
+                    .formatAsString("Sheet1", false);
+            val.getNumRef().setF(numDataRange);
+
+            // 设置系列生成方向
+
+
+        }
+        return result;
+    }
+
+
+    /**
+     * 刷新柱状图数据方法
+     *
+     * @param typeChart
+     * @param serList
+     * @param dataList
+     * @param fldNameArr
+     * @param position
+     * @return
+     */
+    @SuppressWarnings("unused")
+	public static boolean refreshBarStrGraphContent(Object typeChart,
+                                                    List<?> serList, List<Map<String, String>> dataList, List<String> fldNameArr, int position) {
+        boolean result = true;
+        //更新数据区域
+        for (int i = 0; i < serList.size(); i++) {
+            CTAxDataSource cat = null;
+            CTNumDataSource val = null;
+            CTBarSer ser = ((CTBarChart) typeChart).getSerArray(i);
+
+            // 设置标题 用以下这个方式,可以兼容office和wps
+            CTSerTx tx = ser.getTx();
+          //  tx.getStrRef().getStrCache().getPtList().get(0).setV("柱状图1");//设置图例
+            // Category Axis Data
+            cat = ser.getCat();
+            // 获取图表的值
+            val = ser.getVal();
+            // strData.set
+            CTStrData strData = cat.getStrRef().getStrCache();
+            CTNumData numData = val.getNumRef().getNumCache();
+            strData.setPtArray((CTStrVal[]) null); // unset old axis text
+            numData.setPtArray((CTNumVal[]) null); // unset old values
+
+            // set model
+            long idx = 0;
+            for (int j = 0; j < dataList.size(); j++) {
+                //判断获取的值是否为空
+                String value = "0";
+                if (new BigDecimal(dataList.get(j).get(fldNameArr.get(i + position))) != null) {
+                    value = new BigDecimal(dataList.get(j).get(fldNameArr.get(i + position))).toString();
+                }
+                if (!"0".equals(value)) {
+                    CTNumVal numVal = numData.addNewPt();//序列值
+                    numVal.setIdx(idx);
+                    numVal.setV(value);
+                }
+                CTStrVal sVal = strData.addNewPt();//序列名称
+                sVal.setIdx(idx);
+                sVal.setV(dataList.get(j).get(fldNameArr.get(0)));
+                idx++;
+            }
+            numData.getPtCount().setVal(idx);
+            strData.getPtCount().setVal(idx);
+
+
+            //赋值横坐标数据区域
+            String axisDataRange = new CellRangeAddress(1, dataList.size(), 0, 0)
+                    .formatAsString("Sheet1", true);
+            cat.getStrRef().setF(axisDataRange);
+
+            //数据区域
+            String numDataRange = new CellRangeAddress(1, dataList.size(), i + position, i + position)
+                    .formatAsString("Sheet1", true);
+            val.getNumRef().setF(numDataRange);
+
+
+        }
+        return result;
+    }
+
+
+    /**
+     * 刷新饼图数据方法
+     *
+     * @param typeChart
+     * @param serList
+     * @param dataList
+     * @param fldNameArr
+     * @param position
+     * @return
+     */
+    public static boolean refreshPieStrGraphContent(Object typeChart,
+                                                    List<?> serList, List<Map<String, String>> dataList, List<String> fldNameArr, int position) {
+
+        boolean result = true;
+        //更新数据区域
+        for (int i = 0; i < serList.size(); i++) {
+            //CTSerTx tx=null;
+            CTAxDataSource cat = null;
+            CTNumDataSource val = null;
+            CTPieSer ser = ((CTPieChart) typeChart).getSerArray(i);
+
+            //tx.getStrRef().getStrCache().getPtList().get(0).setV("阿里嘎痛");
+            // Category Axis Data
+            cat = ser.getCat();
+            // 获取图表的值
+            val = ser.getVal();
+            // strData.set
+            CTStrData strData = cat.getStrRef().getStrCache();
+            CTNumData numData = val.getNumRef().getNumCache();
+            strData.setPtArray((CTStrVal[]) null); // unset old axis text
+            numData.setPtArray((CTNumVal[]) null); // unset old values
+
+            // set model
+            long idx = 0;
+            for (int j = 0; j < dataList.size(); j++) {
+                //判断获取的值是否为空
+                String value = "0";
+                if (new BigDecimal(dataList.get(j).get(fldNameArr.get(i + position))) != null) {
+                    value = new BigDecimal(dataList.get(j).get(fldNameArr.get(i + position))).toString();
+                }
+                if (!"0".equals(value)) {
+                    CTNumVal numVal = numData.addNewPt();//序列值
+                    numVal.setIdx(idx);
+                    numVal.setV(value);
+                }
+                CTStrVal sVal = strData.addNewPt();//序列名称
+                sVal.setIdx(idx);
+                sVal.setV(dataList.get(j).get(fldNameArr.get(0)));
+                idx++;
+            }
+            numData.getPtCount().setVal(idx);
+            strData.getPtCount().setVal(idx);
+
+
+            //赋值横坐标数据区域
+            String axisDataRange = new CellRangeAddress(1, dataList.size(), 0, 0)
+                    .formatAsString("Sheet1", true);
+            cat.getStrRef().setF(axisDataRange);
+
+            //数据区域
+            String numDataRange = new CellRangeAddress(1, dataList.size(), i + position, i + position)
+                    .formatAsString("Sheet1", true);
+            val.getNumRef().setF(numDataRange);
+        }
+        return result;
+    }
+
+
+    /**
+     * 刷新内置excel数据
+     *
+     * @param chart
+     * @param dataList
+     * @param fldNameArr
+     * @param titleArr
+     * @return
+     */
+    public static boolean refreshExcel(XWPFChart chart,
+                                       List<Map<String, String>> dataList, List<String> fldNameArr, List<String> titleArr) {
+        boolean result = true;
+        Workbook wb = new SXSSFWorkbook(1000);
+        Sheet sheet = wb.createSheet("Sheet1");
+        //根据数据创建excel第一行标题行
+        for (int i = 0; i < titleArr.size(); i++) {
+            if (sheet.getRow(0) == null) {
+                sheet.createRow(0).createCell(i).setCellValue(titleArr.get(i) == null ? "" : titleArr.get(i));
+            } else {
+                sheet.getRow(0).createCell(i).setCellValue(titleArr.get(i) == null ? "" : titleArr.get(i));
+            }
+        }
+
+        //遍历数据行
+        for (int i = 0; i < dataList.size(); i++) {
+            Map<String, String> baseFormMap = dataList.get(i);//数据行
+            //fldNameArr字段属性
+            for (int j = 0; j < fldNameArr.size(); j++) {
+                if (sheet.getRow(i + 1) == null) {
+                    if (j == 0) {
+                        try {
+                            sheet.createRow(i + 1).createCell(j).setCellValue(baseFormMap.get(fldNameArr.get(j)) == null ? "" : baseFormMap.get(fldNameArr.get(j)));
+                        } catch (Exception e) {
+                            if (baseFormMap.get(fldNameArr.get(j)) == null) {
+                                sheet.createRow(i + 1).createCell(j).setCellValue("");
+                            } else {
+                                sheet.createRow(i + 1).createCell(j).setCellValue(baseFormMap.get(fldNameArr.get(j)));
+                            }
+                        }
+                    }
+                } else {
+                    BigDecimal b = new BigDecimal(baseFormMap.get(fldNameArr.get(j)) == null ? "0" :baseFormMap.get(fldNameArr.get(j)));
+                    double value = 0d;
+                    if (b != null) {
+                        value = b.doubleValue();
+                    }
+                    if (value == 0) {
+                        sheet.getRow(i + 1).createCell(j);
+                    } else {
+                        sheet.getRow(i + 1).createCell(j).setCellValue(b.doubleValue());
+                    }
+                }
+            }
+
+        }
+        // 更新嵌入的workbook
+        POIXMLDocumentPart xlsPart = chart.getRelations().get(0);
+        OutputStream xlsOut = xlsPart.getPackagePart().getOutputStream();
+
+        try {
+            wb.write(xlsOut);
+            xlsOut.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+            result = false;
+        } finally {
+            if (wb != null) {
+                try {
+                    wb.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    result = false;
+                }
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * 设置表格样式
+     *
+     * @param cell
+     * @param fontName
+     * @param fontSize
+     * @param fontBlod
+     * @param alignment
+     * @param vertical
+     * @param fontColor
+     * @param bgColor
+     * @param cellWidth
+     * @param content
+     */
+    public static void setWordCellSelfStyle(XWPFTableCell cell, String fontName, String fontSize, int fontBlod,
+                                            String alignment, String vertical, String fontColor,
+                                            String bgColor, String cellWidth, String content) {
+
+        //poi对字体大小设置特殊,不支持小数,但对原word字体大小做了乘2处理
+        BigInteger bFontSize = new BigInteger("24");
+        if (fontSize != null && !fontSize.equals("")) {
+            //poi对字体大小设置特殊,不支持小数,但对原word字体大小做了乘2处理
+            BigDecimal fontSizeBD = new BigDecimal(fontSize);
+            fontSizeBD = bd2.multiply(fontSizeBD);
+            fontSizeBD = fontSizeBD.setScale(0, BigDecimal.ROUND_HALF_UP);//这里取整
+            bFontSize = new BigInteger(fontSizeBD.toString());// 字体大小
+        }
+
+        // 设置单元格宽度
+        cell.setWidth(cellWidth);
+
+        //=====获取单元格
+        CTTc tc = cell.getCTTc();
+        //====tcPr开始====》》》》
+        CTTcPr tcPr = tc.getTcPr();//获取单元格里的<w:tcPr>
+        if (tcPr == null) {//没有<w:tcPr>,创建
+            tcPr = tc.addNewTcPr();
+        }
+
+         //  --vjc开始-->>
+        CTVerticalJc vjc = tcPr.getVAlign();//获取<w:tcPr>  的<w:vAlign w:val="center"/>
+        if (vjc == null) {//没有<w:w:vAlign/>,创建
+            vjc = tcPr.addNewVAlign();
+        }
+        //设置单元格对齐方式
+        vjc.setVal(vertical.equals("top") ? STVerticalJc.TOP : vertical.equals("bottom") ? STVerticalJc.BOTTOM : STVerticalJc.CENTER); //垂直对齐
+
+        CTShd shd = tcPr.getShd();//获取<w:tcPr>里的<w:shd w:val="clear" w:color="auto" w:fill="C00000"/>
+        if (shd == null) {//没有<w:shd>,创建
+            shd = tcPr.addNewShd();
+        }
+        // 设置背景颜色
+        shd.setFill(bgColor.substring(1));
+        //《《《《====tcPr结束====
+
+        //====p开始====》》》》
+        CTP p = tc.getPList().get(0);//获取单元格里的<w:p w:rsidR="00C36068" w:rsidRPr="00B705A0" w:rsidRDefault="00C36068" w:rsidP="00C36068">
+
+        //---ppr开始--->>>
+        CTPPr ppr = p.getPPr();//获取<w:p>里的<w:pPr>
+        if (ppr == null) {//没有<w:pPr>,创建
+            ppr = p.addNewPPr();
+        }
+        //  --jc开始-->>
+        CTJc jc = ppr.getJc();//获取<w:pPr>里的<w:jc w:val="left"/>
+        if (jc == null) {//没有<w:jc/>,创建
+            jc = ppr.addNewJc();
+        }
+        //设置单元格对齐方式
+        jc.setVal(alignment.equals("left") ? STJc.LEFT : alignment.equals("right") ? STJc.RIGHT : STJc.CENTER); //水平对齐
+        //  <<--jc结束--
+        //  --pRpr开始-->>
+        CTParaRPr pRpr = ppr.getRPr(); //获取<w:pPr>里的<w:rPr>
+        if (pRpr == null) {//没有<w:rPr>,创建
+            pRpr = ppr.addNewRPr();
+        }
+        CTFonts pfont = pRpr.getRFonts();//获取<w:rPr>里的<w:rFonts w:ascii="宋体" w:eastAsia="宋体" w:hAnsi="宋体"/>
+        if (pfont == null) {//没有<w:rPr>,创建
+            pfont = pRpr.addNewRFonts();
+        }
+        //设置字体
+        pfont.setAscii(fontName);
+        pfont.setEastAsia(fontName);
+        pfont.setHAnsi(fontName);
+
+        CTOnOff pb = pRpr.getB();//获取<w:rPr>里的<w:b/>
+        if (pb == null) {//没有<w:b/>,创建
+            pb = pRpr.addNewB();
+        }
+        //设置字体是否加粗
+        pb.setVal(fontBlod == 1 ? STOnOff.ON : STOnOff.OFF);
+
+        CTHpsMeasure psz = pRpr.getSz();//获取<w:rPr>里的<w:sz w:val="32"/>
+        if (psz == null) {//没有<w:sz w:val="32"/>,创建
+            psz = pRpr.addNewSz();
+        }
+        // 设置单元格字体大小
+        psz.setVal(bFontSize);
+        CTHpsMeasure pszCs = pRpr.getSzCs();//获取<w:rPr>里的<w:szCs w:val="32"/>
+        if (pszCs == null) {//没有<w:szCs w:val="32"/>,创建
+            pszCs = pRpr.addNewSzCs();
+        }
+        // 设置单元格字体大小
+        pszCs.setVal(bFontSize);
+        //  <<--pRpr结束--
+        //<<<---ppr结束---
+
+        //---r开始--->>>
+        List<CTR> rlist = p.getRList(); //获取<w:p>里的<w:r w:rsidRPr="00B705A0">
+        CTR r = null;
+        if (rlist != null && rlist.size() > 0) {//获取第一个<w:r>
+            r = rlist.get(0);
+        } else {//没有<w:r>,创建
+            r = p.addNewR();
+        }
+        //--rpr开始-->>
+        CTRPr rpr = r.getRPr();//获取<w:r w:rsidRPr="00B705A0">里的<w:rPr>
+        if (rpr == null) {//没有<w:rPr>,创建
+            rpr = r.addNewRPr();
+        }
+        //->-
+        CTFonts font = rpr.getRFonts();//获取<w:rPr>里的<w:rFonts w:ascii="宋体" w:eastAsia="宋体" w:hAnsi="宋体" w:hint="eastAsia"/>
+        if (font == null) {//没有<w:rFonts>,创建
+            font = rpr.addNewRFonts();
+        }
+        //设置字体
+        font.setAscii(fontName);
+        font.setEastAsia(fontName);
+        font.setHAnsi(fontName);
+
+        CTOnOff b = rpr.getB();//获取<w:rPr>里的<w:b/>
+        if (b == null) {//没有<w:b/>,创建
+            b = rpr.addNewB();
+        }
+        //设置字体是否加粗
+        b.setVal(fontBlod == 1 ? STOnOff.ON : STOnOff.OFF);
+        CTColor color = rpr.getColor();//获取<w:rPr>里的<w:color w:val="FFFFFF" w:themeColor="background1"/>
+        if (color == null) {//没有<w:color>,创建
+            color = rpr.addNewColor();
+        }
+        if(StringUtils.isNotEmpty(content)){
+    	// 设置字体颜色
+           if (content.contains("↓")) {
+               color.setVal("43CD80");
+           } else if (content.contains("↑")) {
+               color.setVal("943634");
+           } else {
+               color.setVal(fontColor.substring(1));
+           }
+        }else {
+            color.setVal(fontColor.substring(1));
+        }
+        CTHpsMeasure sz = rpr.getSz();
+        if (sz == null) {
+            sz = rpr.addNewSz();
+        }
+        sz.setVal(bFontSize);
+        CTHpsMeasure szCs = rpr.getSzCs();
+        if (szCs == null) {
+            szCs = rpr.addNewSz();
+        }
+        szCs.setVal(bFontSize);
+        //-<-
+        //<<--rpr结束--
+        List<CTText> tlist = r.getTList();
+        CTText t = null;
+        if (tlist != null && tlist.size() > 0) {//获取第一个<w:r>
+            t = tlist.get(0);
+        } else {//没有<w:r>,创建
+            t = r.addNewT();
+        }
+        content = (content != null && !content.equals("null"))? content :"";
+        t.setStringValue(content);
+        //<<<---r结束---
+    }
+
+
+    /**
+     * 获取内置表格数据,拿到第一行第一列格子数据
+     * 有时候模板设计太复杂,对于图表不能精准定位,可以通过设置图表表格数据的第一行第一列格子数据来区分,这个数据不影响图表显示,所以用来区分每个图表
+     */
+    @SuppressWarnings({ "deprecation", "resource" })
+	public static String getZeroData(POIXMLDocumentPart poixmlDocumentPart) {
+        String text = "";
+        try {
+            XWPFChart chart = (XWPFChart) poixmlDocumentPart;
+            chart.getCTChart();
+
+            POIXMLDocumentPart xlsPart = chart.getRelations().get(0);
+            InputStream xlsin = xlsPart.getPackagePart().getInputStream();
+
+            Workbook workbook = new XSSFWorkbook(xlsin);
+            // 获取第一个sheet
+            Sheet sheet = workbook.getSheetAt(0);
+            // 第一行
+            Row row = sheet.getRow(0);
+            // 第一列
+            Cell cell = row.getCell(0);
+
+            cell.setCellType(CellType.STRING);  // 设置一下格子类型为字符串,不然如果是数字或者时间的话,获取很麻烦
+            text = cell.getStringCellValue();   // 获取格子内容
+
+            System.out.println("(0,0)格子值:" + text);
+
+            // 关闭流
+            xlsin.close();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+
+        return text;
+    }
+
+
+    /**
+     * 刷新雷达图数据方法
+     *
+     * @param typeChart
+     * @param serList
+     * @param dataList
+     * @param fldNameArr
+     * @param position
+     * @return
+     */
+    public static boolean refreshRadarStrGraphContent(Object typeChart,
+                                                      List<?> serList, List<Map<String, String>> dataList, List<String> fldNameArr, int position) {
+        boolean result = true;
+        //更新数据区域
+        for (int i = 0; i < serList.size(); i++) {
+            CTAxDataSource cat = null;
+            CTNumDataSource val = null;
+            CTRadarSer ser = ((CTRadarChart) typeChart).getSerArray(i);
+
+
+            // 设置标题 用以下这个方式,可以兼容office和wps
+            CTSerTx tx = ser.getTx();
+            tx.getStrRef().getStrCache().getPtList().get(0).setV("嘿嘿嘿");
+
+            // Category Axis Data
+            cat = ser.getCat();
+            // 获取图表的值
+            val = ser.getVal();
+
+            // strData.set
+            if (null != cat.getNumRef()) {
+                cat.unsetNumRef();
+            }
+            if (null != cat.getStrRef()) {
+                cat.unsetStrRef();
+            }
+            cat.addNewStrRef().addNewStrCache();
+
+            CTStrData strData = cat.getStrRef().getStrCache();
+            CTNumData numData = val.getNumRef().getNumCache();
+            strData.setPtArray((CTStrVal[]) null); // unset old axis text
+            numData.setPtArray((CTNumVal[]) null); // unset old values
+
+            // set model
+            long idx = 0;
+            for (int j = 0; j < dataList.size(); j++) {
+                //判断获取的值是否为空
+                String value = "0";
+                if (new BigDecimal(dataList.get(j).get(fldNameArr.get(i + position))) != null) {
+                    value = new BigDecimal(dataList.get(j).get(fldNameArr.get(i + position))).toString();
+                }
+                if (!"0".equals(value)) {
+                    CTNumVal numVal = numData.addNewPt();//序列值
+                    numVal.setIdx(idx);
+                    numVal.setV(value);
+                }
+                CTStrVal sVal = strData.addNewPt();//序列名称
+                sVal.setIdx(idx);
+                sVal.setV(dataList.get(j).get(fldNameArr.get(0)));
+                idx++;
+            }
+            strData.addNewPtCount().setVal(idx);
+            numData.getPtCount().setVal(idx);
+
+
+            //赋值横坐标数据区域
+            String axisDataRange = new CellRangeAddress(1, dataList.size(), 0, 0)
+                    .formatAsString("Sheet1", true);
+            cat.getStrRef().setF(axisDataRange);
+
+            //数据区域
+            String numDataRange = new CellRangeAddress(1, dataList.size(), i + position, i + position)
+                    .formatAsString("Sheet1", true);
+            val.getNumRef().setF(numDataRange);
+
+
+        }
+        return result;
+    }
+    /**
+     * 将输入流中的数据写入字节数组
+     * @param in
+     * @return
+     */
+    public static byte[] inputStream2ByteArray(InputStream in, boolean isClose){
+        byte[] byteArray = null;
+        try {
+            int total = in.available();
+            byteArray = new byte[total];
+            in.read(byteArray);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }finally{
+            if(isClose){
+                try {
+                    in.close();
+                } catch (Exception e2) {
+                    System.out.println("关闭流失败");
+                }
+            }
+        }
+        return byteArray;
+    }
+
+}

+ 406 - 0
master/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java

@@ -0,0 +1,406 @@
+package com.ruoyi.common.utils.reflect;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Date;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.utils.DateUtils;
+
+/**
+ * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
+ *
+ * @author ruoyi
+ */
+@SuppressWarnings("rawtypes")
+public class ReflectUtils
+{
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    private static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+    private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class);
+
+    /**
+     * 调用Getter方法.
+     * 支持多级,如:对象名.对象名.方法
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeGetter(Object obj, String propertyName)
+    {
+        Object object = obj;
+        for (String name : StringUtils.split(propertyName, "."))
+        {
+            String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+            object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+        }
+        return (E) object;
+    }
+
+    /**
+     * 调用Setter方法, 仅匹配方法名。
+     * 支持多级,如:对象名.对象名.方法
+     */
+    public static <E> void invokeSetter(Object obj, String propertyName, E value)
+    {
+        Object object = obj;
+        String[] names = StringUtils.split(propertyName, ".");
+        for (int i = 0; i < names.length; i++)
+        {
+            if (i < names.length - 1)
+            {
+                String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+                object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+            }
+            else
+            {
+                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+                invokeMethodByName(object, setterMethodName, new Object[] { value });
+            }
+        }
+    }
+
+    /**
+     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E getFieldValue(final Object obj, final String fieldName)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return null;
+        }
+        E result = null;
+        try
+        {
+            result = (E) field.get(obj);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常{}", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
+     */
+    public static <E> void setFieldValue(final Object obj, final String fieldName, final E value)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return;
+        }
+        try
+        {
+            field.set(obj, value);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符.
+     * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
+     * 同时匹配方法名+参数类型,
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
+            final Object[] args)
+    {
+        if (obj == null || methodName == null)
+        {
+            return null;
+        }
+        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
+        if (method == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符,
+     * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
+     * 只匹配函数名,如果有多个同名函数调用第一个。
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args)
+    {
+        Method method = getAccessibleMethodByName(obj, methodName, args.length);
+        if (method == null)
+        {
+            // 如果为空不报错,直接返回空。
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            // 类型转换(将参数数据类型转换为目标方法参数类型)
+            Class<?>[] cs = method.getParameterTypes();
+            for (int i = 0; i < cs.length; i++)
+            {
+                if (args[i] != null && !args[i].getClass().equals(cs[i]))
+                {
+                    if (cs[i] == String.class)
+                    {
+                        args[i] = Convert.toStr(args[i]);
+                        if (StringUtils.endsWith((String) args[i], ".0"))
+                        {
+                            args[i] = StringUtils.substringBefore((String) args[i], ".0");
+                        }
+                    }
+                    else if (cs[i] == Integer.class)
+                    {
+                        args[i] = Convert.toInt(args[i]);
+                    }
+                    else if (cs[i] == Long.class)
+                    {
+                        args[i] = Convert.toLong(args[i]);
+                    }
+                    else if (cs[i] == Double.class)
+                    {
+                        args[i] = Convert.toDouble(args[i]);
+                    }
+                    else if (cs[i] == Float.class)
+                    {
+                        args[i] = Convert.toFloat(args[i]);
+                    }
+                    else if (cs[i] == Date.class)
+                    {
+                        if (args[i] instanceof String)
+                        {
+                            args[i] = DateUtils.parseDate(args[i]);
+                        }
+                        else
+                        {
+                            args[i] = DateUtil.getJavaDate((Double) args[i]);
+                        }
+                    }
+                }
+            }
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     */
+    public static Field getAccessibleField(final Object obj, final String fieldName)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(fieldName, "fieldName can't be blank");
+        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass())
+        {
+            try
+            {
+                Field field = superClass.getDeclaredField(fieldName);
+                makeAccessible(field);
+                return field;
+            }
+            catch (NoSuchFieldException e)
+            {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 匹配函数名+参数类型。
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethod(final Object obj, final String methodName,
+            final Class<?>... parameterTypes)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(methodName, "methodName can't be blank");
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
+        {
+            try
+            {
+                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
+                makeAccessible(method);
+                return method;
+            }
+            catch (NoSuchMethodException e)
+            {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 只匹配函数名。
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(methodName, "methodName can't be blank");
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
+        {
+            Method[] methods = searchType.getDeclaredMethods();
+            for (Method method : methods)
+            {
+                if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum)
+                {
+                    makeAccessible(method);
+                    return method;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Method method)
+    {
+        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
+                && !method.isAccessible())
+        {
+            method.setAccessible(true);
+        }
+    }
+
+    /**
+     * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Field field)
+    {
+        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
+                || Modifier.isFinal(field.getModifiers())) && !field.isAccessible())
+        {
+            field.setAccessible(true);
+        }
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
+     * 如无法找到, 返回Object.class.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Class<T> getClassGenricType(final Class clazz)
+    {
+        return getClassGenricType(clazz, 0);
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
+     * 如无法找到, 返回Object.class.
+     */
+    public static Class getClassGenricType(final Class clazz, final int index)
+    {
+        Type genType = clazz.getGenericSuperclass();
+
+        if (!(genType instanceof ParameterizedType))
+        {
+            logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType");
+            return Object.class;
+        }
+
+        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
+
+        if (index >= params.length || index < 0)
+        {
+            logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+                    + params.length);
+            return Object.class;
+        }
+        if (!(params[index] instanceof Class))
+        {
+            logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
+            return Object.class;
+        }
+
+        return (Class) params[index];
+    }
+
+    public static Class<?> getUserClass(Object instance)
+    {
+        if (instance == null)
+        {
+            throw new RuntimeException("Instance must not be null");
+        }
+        Class clazz = instance.getClass();
+        if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR))
+        {
+            Class<?> superClass = clazz.getSuperclass();
+            if (superClass != null && !Object.class.equals(superClass))
+            {
+                return superClass;
+            }
+        }
+        return clazz;
+
+    }
+
+    /**
+     * 将反射时的checked exception转换为unchecked exception.
+     */
+    public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e)
+    {
+        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
+                || e instanceof NoSuchMethodException)
+        {
+            return new IllegalArgumentException(msg, e);
+        }
+        else if (e instanceof InvocationTargetException)
+        {
+            return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
+        }
+        return new RuntimeException(msg, e);
+    }
+}

+ 291 - 0
master/src/main/java/com/ruoyi/common/utils/sign/Base64.java

@@ -0,0 +1,291 @@
+package com.ruoyi.common.utils.sign;
+
+/**
+ * Base64工具类
+ *
+ * @author ruoyi
+ */
+public final class Base64
+{
+    static private final int     BASELENGTH           = 128;
+    static private final int     LOOKUPLENGTH         = 64;
+    static private final int     TWENTYFOURBITGROUP   = 24;
+    static private final int     EIGHTBIT             = 8;
+    static private final int     SIXTEENBIT           = 16;
+    static private final int     FOURBYTE             = 4;
+    static private final int     SIGN                 = -128;
+    static private final char    PAD                  = '=';
+    static final private byte[]  base64Alphabet       = new byte[BASELENGTH];
+    static final private char[]  lookUpBase64Alphabet = new char[LOOKUPLENGTH];
+
+    static
+    {
+        for (int i = 0; i < BASELENGTH; ++i)
+        {
+            base64Alphabet[i] = -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+
+        for (int i = '9'; i >= '0'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+
+        for (int i = 0; i <= 25; i++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('A' + i);
+        }
+
+        for (int i = 26, j = 0; i <= 51; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('a' + j);
+        }
+
+        for (int i = 52, j = 0; i <= 61; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('0' + j);
+        }
+        lookUpBase64Alphabet[62] = (char) '+';
+        lookUpBase64Alphabet[63] = (char) '/';
+    }
+
+    private static boolean isWhiteSpace(char octect)
+    {
+        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
+    }
+
+    private static boolean isPad(char octect)
+    {
+        return (octect == PAD);
+    }
+
+    private static boolean isData(char octect)
+    {
+        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
+    }
+
+    /**
+     * Encodes hex octects into Base64
+     *
+     * @param binaryData Array containing binaryData
+     * @return Encoded Base64 array
+     */
+    public static String encode(byte[] binaryData)
+    {
+        if (binaryData == null)
+        {
+            return null;
+        }
+
+        int lengthDataBits = binaryData.length * EIGHTBIT;
+        if (lengthDataBits == 0)
+        {
+            return "";
+        }
+
+        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
+        char encodedData[] = null;
+
+        encodedData = new char[numberQuartet * 4];
+
+        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+
+        for (int i = 0; i < numberTriplets; i++)
+        {
+            b1 = binaryData[dataIndex++];
+            b2 = binaryData[dataIndex++];
+            b3 = binaryData[dataIndex++];
+
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
+        }
+
+        // form integral number of 6-bit groups
+        if (fewerThan24bits == EIGHTBIT)
+        {
+            b1 = binaryData[dataIndex];
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
+            encodedData[encodedIndex++] = PAD;
+            encodedData[encodedIndex++] = PAD;
+        }
+        else if (fewerThan24bits == SIXTEENBIT)
+        {
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
+            encodedData[encodedIndex++] = PAD;
+        }
+        return new String(encodedData);
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param encoded string containing Base64 data
+     * @return Array containind decoded data.
+     */
+    public static byte[] decode(String encoded)
+    {
+        if (encoded == null)
+        {
+            return null;
+        }
+
+        char[] base64Data = encoded.toCharArray();
+        // remove white spaces
+        int len = removeWhiteSpace(base64Data);
+
+        if (len % FOURBYTE != 0)
+        {
+            return null;// should be divisible by four
+        }
+
+        int numberQuadruple = (len / FOURBYTE);
+
+        if (numberQuadruple == 0)
+        {
+            return new byte[0];
+        }
+
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
+        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
+
+        int i = 0;
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        decodedData = new byte[(numberQuadruple) * 3];
+
+        for (; i < numberQuadruple - 1; i++)
+        {
+
+            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
+                    || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++])))
+            {
+                return null;
+            } // if found "no data" just return null
+
+            b1 = base64Alphabet[d1];
+            b2 = base64Alphabet[d2];
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+        }
+
+        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])))
+        {
+            return null;// if found "no data" just return null
+        }
+
+        b1 = base64Alphabet[d1];
+        b2 = base64Alphabet[d2];
+
+        d3 = base64Data[dataIndex++];
+        d4 = base64Data[dataIndex++];
+        if (!isData((d3)) || !isData((d4)))
+        {// Check if they are PAD characters
+            if (isPad(d3) && isPad(d4))
+            {
+                if ((b2 & 0xf) != 0)// last 4 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 1];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                return tmp;
+            }
+            else if (!isPad(d3) && isPad(d4))
+            {
+                b3 = base64Alphabet[d3];
+                if ((b3 & 0x3) != 0)// last 2 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 2];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                return tmp;
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        { // No PAD e.g 3cQl
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+
+        }
+        return decodedData;
+    }
+
+    /**
+     * remove WhiteSpace from MIME containing encoded Base64 data.
+     *
+     * @param data the byte array of base64 data (with WS)
+     * @return the new length
+     */
+    private static int removeWhiteSpace(char[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        // count characters that's not whitespace
+        int newSize = 0;
+        int len = data.length;
+        for (int i = 0; i < len; i++)
+        {
+            if (!isWhiteSpace(data[i]))
+            {
+                data[newSize++] = data[i];
+            }
+        }
+        return newSize;
+    }
+}

+ 66 - 0
master/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java

@@ -0,0 +1,66 @@
+package com.ruoyi.common.utils.sign;
+
+import java.security.MessageDigest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Md5加密方法
+ *
+ * @author ruoyi
+ */
+public class Md5Utils
+{
+    private static final Logger log = LoggerFactory.getLogger(Md5Utils.class);
+
+    private static byte[] md5(String s)
+    {
+        MessageDigest algorithm;
+        try
+        {
+            algorithm = MessageDigest.getInstance("MD5");
+            algorithm.reset();
+            algorithm.update(s.getBytes("UTF-8"));
+            byte[] messageDigest = algorithm.digest();
+            return messageDigest;
+        }
+        catch (Exception e)
+        {
+            log.error("MD5 Error...", e);
+        }
+        return null;
+    }
+
+    private static final String toHex(byte hash[])
+    {
+        if (hash == null)
+        {
+            return null;
+        }
+        StringBuffer buf = new StringBuffer(hash.length * 2);
+        int i;
+
+        for (i = 0; i < hash.length; i++)
+        {
+            if ((hash[i] & 0xff) < 0x10)
+            {
+                buf.append("0");
+            }
+            buf.append(Long.toString(hash[i] & 0xff, 16));
+        }
+        return buf.toString();
+    }
+
+    public static String hash(String s)
+    {
+        try
+        {
+            return new String(toHex(md5(s)).getBytes("UTF-8"), "UTF-8");
+        }
+        catch (Exception e)
+        {
+            log.error("not supported charset...{}", e);
+            return s;
+        }
+    }
+}

+ 146 - 0
master/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java

@@ -0,0 +1,146 @@
+package com.ruoyi.common.utils.spring;
+
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * spring工具类 方便在非spring管理环境中获取bean
+ *
+ * @author ruoyi
+ */
+@Component
+public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
+{
+    /** Spring应用上下文环境 */
+    private static ConfigurableListableBeanFactory beanFactory;
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
+    {
+        SpringUtils.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
+    {
+        SpringUtils.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取对象
+     *
+     * @param name
+     * @return Object 一个以所给名字注册的bean的实例
+     * @throws org.springframework.beans.BeansException
+     *
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) throws BeansException
+    {
+        return (T) beanFactory.getBean(name);
+    }
+
+    /**
+     * 获取类型为requiredType的对象
+     *
+     * @param clz
+     * @return
+     * @throws org.springframework.beans.BeansException
+     *
+     */
+    public static <T> T getBean(Class<T> clz) throws BeansException
+    {
+        T result = (T) beanFactory.getBean(clz);
+        return result;
+    }
+
+    /**
+     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+     *
+     * @param name
+     * @return boolean
+     */
+    public static boolean containsBean(String name)
+    {
+        return beanFactory.containsBean(name);
+    }
+
+    /**
+     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+     *
+     * @param name
+     * @return boolean
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
+     *
+     */
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.isSingleton(name);
+    }
+
+    /**
+     * @param name
+     * @return Class 注册对象的类型
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
+     *
+     */
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getType(name);
+    }
+
+    /**
+     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+     *
+     * @param name
+     * @return
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
+     *
+     */
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getAliases(name);
+    }
+
+    /**
+     * 获取aop代理对象
+     *
+     * @param invoker
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getAopProxy(T invoker)
+    {
+        return (T) AopContext.currentProxy();
+    }
+
+    /**
+     * 获取当前的环境配置,无配置返回null
+     *
+     * @return 当前的环境配置
+     */
+    public static String[] getActiveProfiles()
+    {
+        return applicationContext.getEnvironment().getActiveProfiles();
+    }
+
+    /**
+     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
+     *
+     * @return 当前的环境配置
+     */
+    public static String getActiveProfile()
+    {
+        final String[] activeProfiles = getActiveProfiles();
+        return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
+    }
+}

+ 37 - 0
master/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java

@@ -0,0 +1,37 @@
+package com.ruoyi.common.utils.sql;
+
+import com.ruoyi.common.exception.BaseException;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * sql操作工具类
+ *
+ * @author ruoyi
+ */
+public class SqlUtil
+{
+    /**
+     * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
+     */
+    public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
+
+    /**
+     * 检查字符,防止注入绕过
+     */
+    public static String escapeOrderBySql(String value)
+    {
+        if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
+        {
+            throw new BaseException("参数不符合规范,不能进行查询");
+        }
+        return value;
+    }
+
+    /**
+     * 验证 order by 语法是否符合规范
+     */
+    public static boolean isValidOrderBySql(String value)
+    {
+        return value.matches(SQL_PATTERN);
+    }
+}

+ 122 - 0
master/src/main/java/com/ruoyi/config/ActivitiConfig.java

@@ -0,0 +1,122 @@
+package com.ruoyi.config;
+
+
+import org.activiti.engine.*;
+import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
+import org.activiti.engine.repository.DeploymentBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.io.IOException;
+
+/**
+ * 使用Java类完成配置文件
+ */
+@Configuration
+public class ActivitiConfig {
+
+    @Autowired
+    private DataSource dataSource;
+
+    @Autowired
+    private ResourcePatternResolver resourceLoader;
+
+    /**
+     * 初始化配置,将创建28张表
+     *
+     * @return
+     */
+    @Bean
+    public StandaloneProcessEngineConfiguration processEngineConfiguration() {
+
+        /*ProcessEngineConfiguration pec = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration();
+        pec.setJdbcDriver("oracle.jdbc.driver.OracleDriver");
+        pec.setJdbcUrl("jdbc:oracle:thin:@47.114.101.16:1521/xe");
+        pec.setJdbcUsername("newcpms");
+        pec.setJdbcPassword("ssy123456");
+
+        pec.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
+
+        //pec.setDatabaseSchema("FLOWABLE");
+        pec.setDatabaseTablePrefix("flow");
+        ProcessEngine processEngine = pec.buildProcessEngine();*/
+
+        StandaloneProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();
+        configuration.setDataSource(dataSource);
+        configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
+        configuration.setDbIdentityUsed(false);
+        configuration.setAsyncExecutorActivate(false);
+        configuration.setActivityFontName("宋体");
+        configuration.setAnnotationFontName("宋体");
+        configuration.setLabelFontName("宋体");
+        return configuration;
+    }
+
+    @Bean
+    public ProcessEngine processEngine() {
+        return processEngineConfiguration().buildProcessEngine();
+    }
+
+    @Bean
+    public RepositoryService repositoryService() {
+        return processEngine().getRepositoryService();
+    }
+
+    @Bean
+    public RuntimeService runtimeService() {
+        return processEngine().getRuntimeService();
+    }
+
+    @Bean
+    public TaskService taskService() {
+        return processEngine().getTaskService();
+    }
+
+    @Bean
+    public HistoryService historyService() {
+        return processEngine().getHistoryService();
+    }
+
+    /**
+     * 部署流程
+     *
+     * @throws IOException
+     */
+    @PostConstruct
+    public void initProcess() throws IOException {
+        DeploymentBuilder deploymentBuilder = repositoryService().createDeployment();
+        Resource common = resourceLoader.getResource("classpath:/processes/common.bpmn"); //加载流程图资源文件
+        deploymentBuilder.enableDuplicateFiltering().addInputStream(common.getFilename(), common.getInputStream()).name("普通隐患审批流程").deploy(); //按流程id部署
+
+        DeploymentBuilder deploymentBuilder1 = repositoryService().createDeployment();
+        Resource major= resourceLoader.getResource("classpath:/processes/major.bpmn");
+        deploymentBuilder1.enableDuplicateFiltering().addInputStream(major.getFilename(), major.getInputStream()).name("重大隐患审批流程").deploy();
+
+        DeploymentBuilder deploymentBuilder2 = repositoryService().createDeployment();
+        Resource accident = resourceLoader.getResource("classpath:/processes/accident.bpmn"); //加载流程图资源文件
+        deploymentBuilder2.enableDuplicateFiltering().addInputStream(accident.getFilename(), accident.getInputStream()).name("事件审批流程").deploy();//按流程id部署
+
+        DeploymentBuilder deploymentBuilder3 = repositoryService().createDeployment();
+        Resource spec = resourceLoader.getResource("classpath:/processes/MyProcess.bpmn"); //加载流程图资源文件
+        deploymentBuilder3.enableDuplicateFiltering().addInputStream(spec.getFilename(), spec.getInputStream()).name("特种设备审批流程").deploy();//按流程id部署
+
+        DeploymentBuilder deploymentBuilder4 = repositoryService().createDeployment();
+        Resource spec2 = resourceLoader.getResource("classpath:/processes/his/modifySpecProcess.bpmn"); //加载流程图资源文件
+        deploymentBuilder4.enableDuplicateFiltering().addInputStream(spec2.getFilename(), spec2.getInputStream()).name("特种设备新增修改流程").deploy();//按流程id部署
+
+        DeploymentBuilder deploymentBuilder5 = repositoryService().createDeployment();
+        Resource spec3 = resourceLoader.getResource("classpath:/processes/sems/yearProcess.bpmn"); //加载流程图资源文件
+        deploymentBuilder5.enableDuplicateFiltering().addInputStream(spec3.getFilename(), spec3.getInputStream()).name("特种设备年检报告流程").deploy();//按流程id部署
+
+        DeploymentBuilder deploymentBuilder6 = repositoryService().createDeployment();
+        Resource spec4 = resourceLoader.getResource("classpath:/processes/sems/monthProcess.bpmn"); //加载流程图资源文件
+        deploymentBuilder6.enableDuplicateFiltering().addInputStream(spec4.getFilename(), spec4.getInputStream()).name("特种设备月度检验流程").deploy();//按流程id部署
+    }
+}
+
+

+ 34 - 0
master/src/main/java/com/ruoyi/config/MyWebAppConfiguration.java

@@ -0,0 +1,34 @@
+/*
+package com.ruoyi.config;
+
+import com.ruoyi.framework.config.RuoYiConfig;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@Configuration
+public class MyWebAppConfiguration extends WebMvcConfigurationSupport {
+    */
+/**
+     * 添加一些虚拟路径的映射
+     * 静态资源路径和上传文件的路径
+     *
+     * @param registry
+     *//*
+
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+
+         */
+/*Description: 对文件的路径进行配置, 创建一个虚拟路径/Path/** ,即只要在< img src="/Path/picName.jpg" />便可以直接引用图片
+         这是图片的物理路径  "file:/+本地图片的地址"*//*
+
+
+
+        registry.addResourceHandler("/staffmgrPhoto/**").addResourceLocations("file:/" + RuoYiConfig.getProfile() + "/");
+        super.addResourceHandlers(registry);
+    }
+
+}*/

+ 223 - 0
master/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java

@@ -0,0 +1,223 @@
+package com.ruoyi.framework.aspectj;
+
+import java.lang.reflect.Method;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.framework.aspectj.lang.annotation.DataScope;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.framework.security.service.TokenService;
+import com.ruoyi.framework.web.domain.BaseEntity;
+import com.ruoyi.project.system.domain.SysRole;
+import com.ruoyi.project.system.domain.SysUser;
+
+/**
+ * 数据过滤处理
+ *
+ * @author ruoyi
+ */
+@Aspect
+@Component
+public class DataScopeAspect
+{
+    /**
+     * 全部数据权限
+     */
+    public static final String DATA_SCOPE_ALL = "1";
+
+    /**
+     * 自定数据权限
+     */
+    public static final String DATA_SCOPE_CUSTOM = "2";
+
+    /**
+     * 部门数据权限
+     */
+    public static final String DATA_SCOPE_DEPT = "3";
+
+    /**
+     * 部门及以下数据权限
+     */
+    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
+
+    /**
+     * 仅本人数据权限
+     */
+    public static final String DATA_SCOPE_SELF = "5";
+
+    /**
+     * 数据权限过滤关键字
+     */
+    public static final String DATA_SCOPE = "dataScope";
+
+    // 配置织入点
+    @Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.DataScope)")
+    public void dataScopePointCut()
+    {
+    }
+
+    @Before("dataScopePointCut()")
+    public void doBefore(JoinPoint point) throws Throwable
+    {
+        handleDataScope(point);
+    }
+
+    protected void handleDataScope(final JoinPoint joinPoint)
+    {
+        // 获得注解
+        DataScope controllerDataScope = getAnnotationLog(joinPoint);
+        if (controllerDataScope == null)
+        {
+            return;
+        }
+        // 获取当前的用户
+        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
+        if (StringUtils.isNotNull(loginUser))
+        {
+            SysUser currentUser = loginUser.getUser();
+            // 如果是超级管理员,则不过滤数据
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
+            {
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
+                        controllerDataScope.userAlias());
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user 用户
+     */
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
+    {
+        StringBuilder sqlString = new StringBuilder();
+
+            String dataScope = user.getDataScope();
+            if (DATA_SCOPE_ALL.equals(dataScope))
+            {
+                sqlString = new StringBuilder();
+            }
+            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_user_dept WHERE user_id = {} ) ", deptAlias,
+                        user.getUserId()));
+            }
+            else if (DATA_SCOPE_DEPT.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+            }
+            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set({}, ancestors) > 0 )",
+                        deptAlias, user.getDeptId(), user.getDeptId()));
+            }
+            else if (DATA_SCOPE_SELF.equals(dataScope))
+            {
+                if (StringUtils.isNotBlank(userAlias))
+                {
+                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+                }
+                else
+                {
+                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+                    sqlString.append(" OR 1=0 ");
+                }
+            }
+
+        if (StringUtils.isNotBlank(sqlString.toString()))
+        {
+            Object params = joinPoint.getArgs()[0];
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+            {
+                BaseEntity baseEntity = (BaseEntity) params;
+                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user 用户
+     * @param alias 别名
+     */
+//    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
+//    {
+//        StringBuilder sqlString = new StringBuilder();
+//
+//        for (SysRole role : user.getRoles())
+//        {
+//            String dataScope = role.getDataScope();
+//            if (DATA_SCOPE_ALL.equals(dataScope))
+//            {
+//                sqlString = new StringBuilder();
+//                break;
+//            }
+//            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
+//            {
+//                sqlString.append(StringUtils.format(
+//                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
+//                        role.getRoleId()));
+//            }
+//            else if (DATA_SCOPE_DEPT.equals(dataScope))
+//            {
+//                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+//            }
+//            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+//            {
+//                sqlString.append(StringUtils.format(
+//                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set({}, ancestors) > 0 )",
+//                        deptAlias, user.getDeptId(), user.getDeptId()));
+//            }
+//            else if (DATA_SCOPE_SELF.equals(dataScope))
+//            {
+//                if (StringUtils.isNotBlank(userAlias))
+//                {
+//                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+//                }
+//                else
+//                {
+//                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+//                    sqlString.append(" OR 1=0 ");
+//                }
+//            }
+//        }
+//
+//        if (StringUtils.isNotBlank(sqlString.toString()))
+//        {
+//            Object params = joinPoint.getArgs()[0];
+//            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+//            {
+//                BaseEntity baseEntity = (BaseEntity) params;
+//                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+//            }
+//        }
+//    }
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private DataScope getAnnotationLog(JoinPoint joinPoint)
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(DataScope.class);
+        }
+        return null;
+    }
+}

+ 225 - 0
master/src/main/java/com/ruoyi/framework/aspectj/DataScopePlantAspect.java

@@ -0,0 +1,225 @@
+package com.ruoyi.framework.aspectj;
+
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.framework.aspectj.lang.annotation.DataScope;
+import com.ruoyi.framework.aspectj.lang.annotation.DataScopePlant;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.framework.security.service.TokenService;
+import com.ruoyi.framework.web.domain.BaseEntity;
+import com.ruoyi.project.system.domain.SysRole;
+import com.ruoyi.project.system.domain.SysUser;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * 数据过滤处理
+ *
+ * @author ruoyi
+ */
+@Aspect
+@Component
+public class DataScopePlantAspect
+{
+    /**
+     * 全部数据权限
+     */
+    public static final String DATA_SCOPE_ALL = "1";
+
+    /**
+     * 自定数据权限
+     */
+    public static final String DATA_SCOPE_CUSTOM = "2";
+
+    /**
+     * 部门数据权限
+     */
+    public static final String DATA_SCOPE_DEPT = "3";
+
+    /**
+     * 部门及以下数据权限
+     */
+    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
+
+    /**
+     * 仅本人数据权限
+     */
+    public static final String DATA_SCOPE_SELF = "5";
+
+    /**
+     * 数据权限过滤关键字
+     */
+    public static final String DATA_SCOPE = "dataScopePlant";
+
+    // 配置织入点
+    @Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.DataScopePlant)")
+    public void dataScopePointCut()
+    {
+    }
+
+    @Before("dataScopePointCut()")
+    public void doBefore(JoinPoint point) throws Throwable
+    {
+        handleDataScope(point);
+    }
+
+    protected void handleDataScope(final JoinPoint joinPoint)
+    {
+        // 获得注解
+        DataScopePlant controllerDataScope = getAnnotationLog(joinPoint);
+        if (controllerDataScope == null)
+        {
+            return;
+        }
+        // 获取当前的用户
+        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
+        if (StringUtils.isNotNull(loginUser))
+        {
+            SysUser currentUser = loginUser.getUser();
+            // 如果是超级管理员,则不过滤数据
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
+            {
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
+                        controllerDataScope.userAlias());
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user 用户
+     */
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
+    {
+        StringBuilder sqlString = new StringBuilder();
+
+            String dataScope = user.getDataScope();
+            if (DATA_SCOPE_ALL.equals(dataScope))
+            {
+                sqlString = new StringBuilder();
+            }
+            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.PLANT_CODE IN ( SELECT sp.NAME FROM SYS_PLANT sp,SYS_DEPT sd LEFT JOIN SYS_USER_DEPT rd on rd.DEPT_ID = sd.DEPT_ID  WHERE user_id = {} and sp.DEPT_CODE = sd.DEPT_NAME )",
+                        deptAlias, user.getUserId()));
+            }
+            else if (DATA_SCOPE_DEPT.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(" OR {}.PLANT_CODE IN (SELECT sp.NAME FROM SYS_PLANT sp, SYS_DEPT sd WHERE sd.DEPT_ID = {} AND sp.DEPT_CODE = sd.DEPT_NAME ) ", deptAlias, user.getDeptId()));
+            }
+            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set({}, ancestors) > 0 )",
+                        deptAlias, user.getDeptId(), user.getDeptId()));
+            }
+            else if (DATA_SCOPE_SELF.equals(dataScope))
+            {
+                if (StringUtils.isNotBlank(userAlias))
+                {
+                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+                }
+                else
+                {
+                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+                    sqlString.append(" OR 1=0 ");
+                }
+            }
+
+        if (StringUtils.isNotBlank(sqlString.toString()))
+        {
+            Object params = joinPoint.getArgs()[0];
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+            {
+                BaseEntity baseEntity = (BaseEntity) params;
+                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user 用户
+     */
+   /* public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
+    {
+        StringBuilder sqlString = new StringBuilder();
+
+        for (SysRole role : user.getRoles())
+        {
+            String dataScope = role.getDataScope();
+            if (DATA_SCOPE_ALL.equals(dataScope))
+            {
+                sqlString = new StringBuilder();
+                break;
+            }
+            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.PLANT_CODE IN ( SELECT sp.NAME FROM SYS_PLANT sp,SYS_DEPT sd LEFT JOIN SYS_ROLE_DEPT rd on rd.DEPT_ID = sd.DEPT_ID  WHERE role_id = {} and sp.DEPT_CODE = sd.DEPT_NAME )",
+                        deptAlias, role.getRoleId()));
+            }
+            else if (DATA_SCOPE_DEPT.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+            }
+            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set({}, ancestors) > 0 )",
+                        deptAlias, user.getDeptId(), user.getDeptId()));
+            }
+            else if (DATA_SCOPE_SELF.equals(dataScope))
+            {
+                if (StringUtils.isNotBlank(userAlias))
+                {
+                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+                }
+                else
+                {
+                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+                    sqlString.append(" OR 1=0 ");
+                }
+            }
+        }
+
+        if (StringUtils.isNotBlank(sqlString.toString()))
+        {
+            Object params = joinPoint.getArgs()[0];
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+            {
+                BaseEntity baseEntity = (BaseEntity) params;
+                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+            }
+        }
+    }*/
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private DataScopePlant getAnnotationLog(JoinPoint joinPoint)
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(DataScopePlant.class);
+        }
+        return null;
+    }
+}

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