149 Commits

Author SHA1 Message Date
RuoYi
af1b557bc8 若依 3.2.0 2021-10-12 09:01:05 +08:00
RuoYi
e6c3bd1ce5 升级spring-boot-admin到最新版2.5.2 2021-10-11 18:57:21 +08:00
RuoYi
046d25f35d 升级spring-boot到最新版本2.5.5 2021-10-10 16:07:43 +08:00
RuoYi
d20d4ffa16 菜单子项添加路由参数 2021-10-10 16:07:06 +08:00
RuoYi
76f2273d0b Excel导入支持@Excels注解 2021-10-10 16:02:54 +08:00
RuoYi
dda78c95b7 升级pagehelper到最新版1.4.0 2021-10-10 16:01:07 +08:00
RuoYi
ebbeb047be 升级druid到最新版1.2.8 2021-10-10 09:46:21 +08:00
RuoYi
ebd7fd59d0 升级element-ui到最新版本2.15.6 2021-10-09 11:54:18 +08:00
RuoYi
c20c2c221f 导入模板标题默认空参数 2021-10-09 11:53:36 +08:00
RuoYi
ee33dbdc0e 升级sass-loader到最新版本10.1.1 2021-09-27 19:06:57 +08:00
RuoYi
5d8d4fa530 升级dart-sass到版本1.32.13 2021-09-27 19:06:43 +08:00
RuoYi
2021028cad 升级file-saver到最新版本2.0.5 2021-09-27 19:06:23 +08:00
RuoYi
7fcb1b2e0a 新增通用方法简化下载使用 2021-09-27 12:10:39 +08:00
RuoYi
f4a2f909a7 Excel注解支持导入导出标题信息 2021-09-26 09:03:28 +08:00
RuoYi
328630fe5d 修复xss过滤后格式出现的异常 2021-09-25 17:16:42 +08:00
RuoYi
f7ae7e29d1 修正代码生成编辑页面单词拼写错误 2021-09-24 14:56:08 +08:00
RuoYi
e46ea3ebac 自动生成代码漏掉 this.#[[$modal]]# 2021-09-24 14:55:11 +08:00
RuoYi
d565fa4883 升级springcloud到最新版2020.0.4 2021-09-24 09:54:05 +08:00
RuoYi
a627108dfe 升级fastjson到最新版1.2.78 2021-09-24 09:53:31 +08:00
RuoYi
829451f05e 新增通用方法简化模态/缓存使用 2021-09-23 09:59:17 +08:00
RuoYi
cae41a8da2 新增通用方法简化模态/缓存使用 2021-09-23 09:35:59 +08:00
RuoYi
a4099cf645 Excel注解支持自定义数据处理器 2021-09-22 09:06:51 +08:00
RuoYi
0a104689ed Excel注解支持自定义数据处理器 2021-09-22 09:03:21 +08:00
RuoYi
97d0226c78 升级spring-boot-admin到最新版2.5.1 2021-09-18 21:34:55 +08:00
RuoYi
3a04dda55d 升级spring-boot到最新版本2.5.4 2021-09-18 21:34:31 +08:00
RuoYi
94310c4f3d 代码生成点击预览重置激活tab 2021-09-18 21:20:45 +08:00
RuoYi
39e7d8a84b 优化aop语法 使用spring自动注入注解 基于注解拦截的aop注解不可能为空 2021-09-18 21:20:18 +08:00
RuoYi
e1037ac125 Cron表达式生成器关闭时销毁,避免再次打开时存在上一次修改的数据 2021-09-18 21:19:52 +08:00
RuoYi
a10384c009 使用vue-data-dict简化数据字典使用 2021-09-17 15:43:02 +08:00
RuoYi
ab8215d1ce 日志注解新增是否保存响应参数 2021-09-16 16:19:23 +08:00
RuoYi
524982c7bd 禁用el-tag组件的渐变动画 2021-09-16 16:19:11 +08:00
RuoYi
d6188aa009 修复后端主子表代码模板方法名生成错误问题 2021-09-16 16:18:48 +08:00
RuoYi
7e8b08a58c 修复多图组件验证失败被删除问题 2021-09-10 11:08:23 +08:00
RuoYi
14771f0ab4 修复代码生成页面数据编辑保存之后总是跳转第一页的问题 2021-09-08 11:29:20 +08:00
RuoYi
97c0603269 优化提示 2021-09-08 11:28:54 +08:00
RuoYi
a58c430858 菜单管理支持配置路由参数 2021-09-08 10:04:23 +08:00
RuoYi
cd4119b26a 修正单词拼写错误 2021-09-08 09:31:37 +08:00
RuoYi
ea20fa3ce2 页签新增关闭左侧 2021-09-05 13:28:32 +08:00
RuoYi
3b54208ac9 页签右键按钮添加图标 2021-09-05 13:27:20 +08:00
RuoYi
d2de744c3f 菜单&部门新增展开/折叠功能 2021-09-04 12:21:33 +08:00
RuoYi
655249d67a 新增暗色菜单风格主题 2021-09-04 12:21:18 +08:00
RuoYi
80a890549d 修复保存配置主题颜色失效问题 2021-09-03 16:52:14 +08:00
RuoYi
5da1979ce6 自定义弹层溢出滚动样式 2021-09-03 13:28:37 +08:00
RuoYi
642512aa91 定时任务支持在线生成cron表达式 2021-09-03 09:53:26 +08:00
RuoYi
4a32a8e725 代码生成导入表按创建时间排序 2021-09-03 09:52:49 +08:00
RuoYi
bb30ae7086 防止表格最后页最后项删除变成暂无数据 2021-09-02 10:46:16 +08:00
RuoYi
34a6f2e1f1 查询列表设置数据字典样式回显 2021-09-01 14:03:59 +08:00
RuoYi
0e51d1e7b1 根据用户ID查询菜单条件加别名 2021-08-30 17:08:55 +08:00
RuoYi
40bdf7b100 修复字典组件值为整形不显示问题 2021-08-29 17:39:43 +08:00
RuoYi
57f51249b6 验证码默认20s超时 2021-08-29 17:39:25 +08:00
RuoYi
dcc5dc0d16 修复带utc日期格式 yyyy-MM-dd'T'HH:mm:ss.SSS 在safari浏览器中无法正确格式化的问题 2021-08-29 17:39:10 +08:00
RuoYi
e87a818706 添加日期范围支持重复添加多组日期范围 2021-08-29 17:38:36 +08:00
RuoYi
67df97d5a7 修改时检查用户数据权限范围 2021-08-24 16:24:25 +08:00
RuoYi
3f8fd0a0f8 定时任务对检查异常进行事务回滚 2021-08-24 11:19:09 +08:00
RuoYi
961fe9a5f1 自定义可拖动弹窗宽高指令 2021-08-20 21:25:53 +08:00
RuoYi
22f156bbb7 补充定时任务表字段注释 2021-08-20 11:07:38 +08:00
RuoYi
22c22c4246 定时任务屏蔽ldap远程调用 2021-08-19 15:34:13 +08:00
RuoYi
994cd62c7a 删掉此处代码,使右边栏动画生效。 2021-08-19 15:32:01 +08:00
RuoYi
1e4ed04a65 优化异常信息 2021-08-16 22:26:29 +08:00
RuoYi
139b25639e 日期范围支持添加多组 2021-08-16 22:26:20 +08:00
RuoYi
13800738cb 修改Dashboard名称 2021-08-16 22:25:09 +08:00
RuoYi
98c538c9e3 默认首页使用keep-alive缓存 2021-08-13 16:41:56 +08:00
RuoYi
0e7c45173c 代码生成主子表多选行数据 2021-08-13 16:41:41 +08:00
RuoYi
132b23dc33 补充遗漏的@Override注解 2021-08-13 16:37:47 +08:00
RuoYi
c3f1dd846c update bin 2021-08-13 16:32:40 +08:00
RuoYi
7b7abde756 添加新群号:201705537 2021-08-08 19:55:51 +08:00
RuoYi
aeeb0eb26b 字典工具类更换路径 2021-08-06 17:52:44 +08:00
RuoYi
265a181891 http请求默认外链打开 2021-08-06 17:02:37 +08:00
RuoYi
22663e1b43 升级element-ui到最新版本2.15.5 2021-08-06 14:33:23 +08:00
RuoYi
24be1a436e 若依 3.1.0 2021-08-01 09:01:15 +08:00
RuoYi
370c53edfa 文件服务本地资源允许跨域访问 2021-07-31 21:51:40 +08:00
RuoYi
f112133ddf 是否开启用户注册功能sql脚本 2021-07-31 21:49:44 +08:00
RuoYi
93ee021b6e XSS过滤排除非json类型 2021-07-31 12:18:24 +08:00
RuoYi
e57d2ea17c 升级nacos到最新版2.0.3 2021-07-31 09:54:28 +08:00
RuoYi
ea038a5437 优化代码生成模板 2021-07-30 22:33:09 +08:00
RuoYi
329aa68644 新增是否开启用户注册功能 2021-07-30 20:03:59 +08:00
Ricky
887430874d 优化代码生成 2021-07-30 18:41:46 +08:00
Ricky
52f8693e1d 优化代码生成 2021-07-30 18:35:52 +08:00
RuoYi
8057dcc4fc 定时任务屏蔽http(s)远程调用 2021-07-30 11:06:11 +08:00
RuoYi
66e0f9a53d 启用部门状态排除顶级节点 2021-07-30 11:05:54 +08:00
RuoYi
7a35c474d6 统一网关错误码响应 2021-07-29 14:51:27 +08:00
RuoYi
892065003d 修复导出含params属性对象参数问题 2021-07-29 10:38:34 +08:00
RuoYi
ede8353503 升级common-pool到最新版本2.10.0 2021-07-28 20:34:41 +08:00
RuoYi
60796ca8fb rollback pr 2021-07-28 20:33:46 +08:00
RuoYi
25e9112ccc 升级minio到最新版本8.2.2 2021-07-28 20:12:50 +08:00
RuoYi
f6c5c91eb1 升级commons.io到最新版本v2.11.0 2021-07-28 14:00:32 +08:00
若依
740da06972 !91 【轻量级PR】 [SysUser] 返回给前端数据的时候,隐藏密码和加盐,防止别人猜测加密方式 或者 暴力破击
Merge pull request !91 from dazer007/master
2021-07-28 05:51:09 +00:00
RuoYi
2a363f9c17 升级tobato到最新版本1.27.2 2021-07-28 13:33:49 +08:00
RuoYi
3c649a8814 升级minio到最新版本8.3.0 2021-07-28 13:30:46 +08:00
RuoYi
a144bf2bff 升级dynamic-ds到最新版本3.4.1 2021-07-28 13:28:50 +08:00
RuoYi
83ee4223be 升级spring-boot-mybatis到最新版2.2.0 2021-07-28 13:24:29 +08:00
RuoYi
8f6c864e96 升级spring-boot-admin到最新版2.4.3 2021-07-28 13:19:50 +08:00
RuoYi
12209ae5a4 升级spring-boot到最新版本2.5.3 2021-07-28 13:17:20 +08:00
duandazhi
4e0301a7b2 [SysUser] 返回给前端数据的时候,隐藏密码和加盐,防止别人猜测加密方式 或者 暴力破击 2021-07-28 13:07:06 +08:00
RuoYi
00fa1c3158 支持配置XSS跨站脚本过滤 2021-07-28 13:05:18 +08:00
若依
04edd66199 !90 【轻量级PR】SysUserController remove 解决把自己删除的bug
Merge pull request !90 from dazer007/secerity-fix-remove-self-ok
2021-07-28 05:01:49 +00:00
若依
d8896c9054 !89 update ruoyi-ui/src/utils/zipdownload.js.
Merge pull request !89 from ytzjf/N/A
2021-07-28 05:01:46 +00:00
duandazhi
dd70c1950e sysuercontroller remove self 问题修复 2021-07-28 10:24:14 +08:00
RuoYi
954d208ac6 支持配置XSS跨站脚本过滤 2021-07-28 09:58:59 +08:00
ytzjf
98d25fa16e update ruoyi-ui/src/utils/zipdownload.js.
BLOB下载时清除URL对象引用
2021-07-28 01:57:11 +00:00
RuoYi
3af7af265b 验证码配置 2021-07-27 20:47:57 +08:00
RuoYi
436c2154ad 支持配置验证码开关&类型 2021-07-27 20:39:46 +08:00
RuoYi
42e8baa85c 添加新群号 2021-07-27 20:38:30 +08:00
RuoYi
0dff71bd4d 跳转路由高亮相对应的菜单栏 2021-07-27 13:09:30 +08:00
RuoYi
20ce9da509 修复任意账户越权问题 2021-07-27 13:08:37 +08:00
RuoYi
a044b0d205 升级element-ui到最新版本2.15.3 2021-07-26 10:11:08 +08:00
RuoYi
698200ecc2 角色&菜单新增字段属性提示信息 2021-07-26 10:10:39 +08:00
RuoYi
883d89ee0b 内链设置meta信息 2021-07-26 10:08:42 +08:00
RuoYi
fb9a480f3c 密码框新增显示切换密码图标 2021-07-26 10:08:28 +08:00
RuoYi
e288710251 导入用户样式调整 2021-07-26 10:06:08 +08:00
RuoYi
1019aa58ce 顶部菜单样式调整 2021-07-26 10:03:52 +08:00
RuoYi
e9621469b2 更多操作按钮添加权限控制 2021-07-25 11:47:55 +08:00
RuoYi
57c4910605 富文本新增上传文件大小限制 2021-07-25 11:39:12 +08:00
RuoYi
9920957796 状态码401返回Promise.reject 2021-07-25 11:36:31 +08:00
RuoYi
e798f46876 顶部菜单排除隐藏的默认路由 2021-07-24 19:15:16 +08:00
RuoYi
caa7537607 修复定时任务日志执行状态显示 2021-07-17 16:51:37 +08:00
若依
8b23c97fdb !83 【轻量级PR】RuoYiFileApplication 重命名为RuoYiFileApplication
Merge pull request !83 from dazer007/RuoYFileApplication-rename-
2021-07-17 08:48:40 +00:00
duandazhi
af49ad54f6 RuoYFileApplication-rename 2021-07-16 12:26:07 +08:00
RuoYi
816479e092 定时任务新增更多操作 2021-07-15 17:36:14 +08:00
RuoYi
7e72849d05 修复多图时无法删除相应图片问题 2021-07-13 10:32:48 +08:00
RuoYi
99206a5d65 删除富文本video事件 2021-07-13 10:32:23 +08:00
RuoYi
114c9f3af6 菜单路由配置支持内链访问 2021-07-11 19:30:24 +08:00
RuoYi
e14fb70c26 自定义弹窗拖拽指令 2021-07-09 21:14:16 +08:00
RuoYi
201149a131 富文本默认上传返回url类型 2021-07-09 21:13:54 +08:00
RuoYi
2bc80c4c07 授权用户添加事务 2021-07-09 21:13:09 +08:00
RuoYi
ee7607bcca 全局注册通用组件 2021-07-09 21:12:37 +08:00
RuoYi
7e2dbc5608 ImageUpload组件支持多图片上传 2021-07-09 21:11:07 +08:00
RuoYi
8eac239e48 FileUpload组件支持多文件上传 2021-07-09 21:10:08 +08:00
若依
6650397e38 !79 【轻量级 PR】:修改登录失效返回值code401
Merge pull request !79 from bug制造者/master
2021-07-09 12:57:28 +00:00
bug制造者
1ca4c26d81 修改登录失效返回值code401
之前返回的code200,前端就会$message一下错误信息,不会弹出401重新登录跳转的弹框。
2021-07-07 09:16:05 +00:00
RuoYi
0b0da91139 角色管理新增分配用户功能 2021-07-06 14:02:37 +08:00
RuoYi
3d47ec2e73 用户管理新增分配角色功能 2021-07-02 10:59:22 +08:00
RuoYi
1f21102204 升级pagehelper到最新版1.3.1 2021-06-25 17:42:41 +08:00
RuoYi
58b4600144 用户信息长度校验限制 2021-06-25 17:41:40 +08:00
RuoYi
6146d63f2c 修复日志列表取消字段排序时的报错问题 2021-06-25 17:40:05 +08:00
RuoYi
dbf42739ca 全局挂载字典标签组件 2021-06-25 17:39:43 +08:00
Ricky
57f56c2769 增加字典标签样式回显 2021-06-22 14:47:28 +08:00
RuoYi
5c130cfda6 封装iframe组件 2021-06-17 20:19:31 +08:00
RuoYi
df284ff6f5 日志列表支持排序操作 2021-06-17 20:18:59 +08:00
RuoYi
1b7550f9a2 升级commons.fileupload到最新版本v1.4 2021-06-16 10:06:09 +08:00
RuoYi
195df24b4b 升级commons.io到最新版本v2.10.0 2021-06-16 10:05:53 +08:00
RuoYi
68de85aa5f 升级nacos到最新版2.0.2 2021-06-15 10:42:03 +08:00
RuoYi
8b6784d8e2 升级element-ui到最新版本2.15.2 2021-06-15 10:41:31 +08:00
RuoYi
b73c14400c 升级spring-boot到最新版本2.5.1 2021-06-12 14:05:27 +08:00
RuoYi
54922af7db 升级dynamic-ds到最新版本3.4.0 2021-06-12 14:05:07 +08:00
RuoYi
23944b2f9c 系统布局配置支持动态标题开关 2021-06-11 11:04:06 +08:00
RuoYi
9d0240b831 分页组件新增pagerCount属性 2021-06-11 11:03:54 +08:00
RuoYi
1aef90d506 修复用户搜索分页变量错误 2021-06-11 11:03:33 +08:00
RuoYi
41b7e62b02 优化部门父级启用状态 2021-06-11 10:49:26 +08:00
236 changed files with 8985 additions and 3051 deletions

View File

@@ -115,4 +115,4 @@ com.ruoyi
## 若依微服务交流群 ## 若依微服务交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [![加入QQ群](https://img.shields.io/badge/已满-170157040-blue.svg)](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [![加入QQ群](https://img.shields.io/badge/已满-130643120-blue.svg)](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [![加入QQ群](https://img.shields.io/badge/225920371-blue.svg)](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) 点击按钮入群。 QQ群 [![加入QQ群](https://img.shields.io/badge/已满-42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [![加入QQ群](https://img.shields.io/badge/已满-170157040-blue.svg)](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [![加入QQ群](https://img.shields.io/badge/已满-130643120-blue.svg)](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [![加入QQ群](https://img.shields.io/badge/已满-225920371-blue.svg)](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [![加入QQ群](https://img.shields.io/badge/201705537-blue.svg)](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) 点击按钮入群。

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 清理生成路径。 echo [信息] 清理工程target生成路径。
echo. echo.
%~d0 %~d0

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 运行auth工程。 echo [信息] 使用Jar命令运行Auth工程。
echo. echo.
cd %~dp0 cd %~dp0

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 运行gateway工程。 echo [信息] 使用Jar命令运行Gateway工程。
echo. echo.
cd %~dp0 cd %~dp0

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 运行modules-file工程。 echo [信息] 使用Jar命令运行Modules-File工程。
echo. echo.
cd %~dp0 cd %~dp0

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 运行modules-gen工程。 echo [信息] 使用Jar命令运行Modules-Gen工程。
echo. echo.
cd %~dp0 cd %~dp0

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 运行modules-job工程。 echo [信息] 使用Jar命令运行Modules-Job工程。
echo. echo.
cd %~dp0 cd %~dp0

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 运行modules-system工程。 echo [信息] 使用Jar命令运行Modules-System工程。
echo. echo.
cd %~dp0 cd %~dp0

View File

@@ -1,6 +1,6 @@
@echo off @echo off
echo. echo.
echo [信息] 运行monitor工程。 echo [信息] 使用Jar命令运行Monitor工程。
echo. echo.
cd %~dp0 cd %~dp0

76
pom.xml
View File

@@ -6,37 +6,37 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
<name>ruoyi</name> <name>ruoyi</name>
<url>http://www.ruoyi.vip</url> <url>http://www.ruoyi.vip</url>
<description>若依微服务系统</description> <description>若依微服务系统</description>
<properties> <properties>
<ruoyi.version>3.0.0</ruoyi.version> <ruoyi.version>3.2.0</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<spring-boot.version>2.5.0</spring-boot.version> <spring-boot.version>2.5.5</spring-boot.version>
<spring-cloud.version>2020.0.3</spring-cloud.version> <spring-cloud.version>2020.0.4</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version> <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<alibaba.nacos.version>2.0.1</alibaba.nacos.version> <alibaba.nacos.version>2.0.3</alibaba.nacos.version>
<spring-boot-admin.version>2.4.1</spring-boot-admin.version> <spring-boot-admin.version>2.5.2</spring-boot-admin.version>
<spring-boot.mybatis>2.1.4</spring-boot.mybatis> <spring-boot.mybatis>2.2.0</spring-boot.mybatis>
<swagger.fox.version>3.0.0</swagger.fox.version> <swagger.fox.version>3.0.0</swagger.fox.version>
<swagger.core.version>1.6.2</swagger.core.version> <swagger.core.version>1.6.2</swagger.core.version>
<tobato.version>1.26.5</tobato.version> <tobato.version>1.27.2</tobato.version>
<kaptcha.version>2.3.2</kaptcha.version> <kaptcha.version>2.3.2</kaptcha.version>
<pagehelper.boot.version>1.3.0</pagehelper.boot.version> <pagehelper.boot.version>1.4.0</pagehelper.boot.version>
<druid.version>1.2.6</druid.version> <druid.version>1.2.8</druid.version>
<dynamic-ds.version>3.3.2</dynamic-ds.version> <dynamic-ds.version>3.4.1</dynamic-ds.version>
<commons.io.version>2.5</commons.io.version> <commons.io.version>2.11.0</commons.io.version>
<commons.fileupload.version>1.3.3</commons.fileupload.version> <commons.fileupload.version>1.4</commons.fileupload.version>
<velocity.version>1.7</velocity.version> <velocity.version>1.7</velocity.version>
<fastjson.version>1.2.76</fastjson.version> <fastjson.version>1.2.78</fastjson.version>
<minio.version>8.2.1</minio.version> <minio.version>8.2.2</minio.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<common-pool.version>2.6.2</common-pool.version> <common-pool.version>2.10.0</common-pool.version>
<commons-collections.version>3.2.2</commons-collections.version> <commons-collections.version>3.2.2</commons-collections.version>
</properties> </properties>
@@ -53,7 +53,7 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- SpringCloud Alibaba 微服务 --> <!-- SpringCloud Alibaba 微服务 -->
<dependency> <dependency>
<groupId>com.alibaba.cloud</groupId> <groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId> <artifactId>spring-cloud-alibaba-dependencies</artifactId>
@@ -77,28 +77,28 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- SpringBoot 监控客户端 --> <!-- SpringBoot 监控客户端 -->
<dependency> <dependency>
<groupId>de.codecentric</groupId> <groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId> <artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-boot-admin.version}</version> <version>${spring-boot-admin.version}</version>
</dependency> </dependency>
<!-- FastDFS 分布式文件系统 --> <!-- FastDFS 分布式文件系统 -->
<dependency> <dependency>
<groupId>com.github.tobato</groupId> <groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId> <artifactId>fastdfs-client</artifactId>
<version>${tobato.version}</version> <version>${tobato.version}</version>
</dependency> </dependency>
<!-- Mybatis 依赖配置 --> <!-- Mybatis 依赖配置 -->
<dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId> <groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId> <artifactId>mybatis-spring-boot-starter</artifactId>
<version>${spring-boot.mybatis}</version> <version>${spring-boot.mybatis}</version>
</dependency> </dependency>
<!-- Swagger 依赖配置 --> <!-- Swagger 依赖配置 -->
<dependency> <dependency>
<groupId>io.swagger</groupId> <groupId>io.swagger</groupId>
@@ -172,7 +172,7 @@
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>
<version>${fastjson.version}</version> <version>${fastjson.version}</version>
</dependency> </dependency>
<!-- 公共资源池 --> <!-- 公共资源池 -->
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
@@ -181,71 +181,71 @@
</dependency> </dependency>
<!-- 核心模块 --> <!-- 核心模块 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-core</artifactId> <artifactId>ruoyi-common-core</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 接口模块 --> <!-- 接口模块 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId> <artifactId>ruoyi-common-swagger</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 安全模块 --> <!-- 安全模块 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-security</artifactId> <artifactId>ruoyi-common-security</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 权限范围 --> <!-- 权限范围 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datascope</artifactId> <artifactId>ruoyi-common-datascope</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 多数据源 --> <!-- 多数据源 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datasource</artifactId> <artifactId>ruoyi-common-datasource</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 日志记录 --> <!-- 日志记录 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId> <artifactId>ruoyi-common-log</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 缓存服务 --> <!-- 缓存服务 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId> <artifactId>ruoyi-common-redis</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 系统接口 --> <!-- 系统接口 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api-system</artifactId> <artifactId>ruoyi-api-system</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<modules> <modules>
<module>ruoyi-auth</module> <module>ruoyi-auth</module>
<module>ruoyi-gateway</module> <module>ruoyi-gateway</module>
<module>ruoyi-visual</module> <module>ruoyi-visual</module>
<module>ruoyi-modules</module> <module>ruoyi-modules</module>
<module>ruoyi-api</module> <module>ruoyi-api</module>
<module>ruoyi-common</module> <module>ruoyi-common</module>
</modules> </modules>
<packaging>pom</packaging> <packaging>pom</packaging>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api</artifactId> <artifactId>ruoyi-api</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -3,9 +3,11 @@ package com.ruoyi.system.api;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestHeader;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysOperLog; import com.ruoyi.system.api.domain.SysOperLog;
import com.ruoyi.system.api.factory.RemoteLogFallbackFactory; import com.ruoyi.system.api.factory.RemoteLogFallbackFactory;
@@ -21,20 +23,19 @@ public interface RemoteLogService
* 保存系统日志 * 保存系统日志
* *
* @param sysOperLog 日志实体 * @param sysOperLog 日志实体
* @param source 请求来源
* @return 结果 * @return 结果
*/ */
@PostMapping("/operlog") @PostMapping("/operlog")
R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog); public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/** /**
* 保存访问记录 * 保存访问记录
* *
* @param username 用户名称 * @param sysLogininfor 访问实体
* @param status 状态 * @param source 请求来源
* @param message 消息
* @return 结果 * @return 结果
*/ */
@PostMapping("/logininfor") @PostMapping("/logininfor")
R<Boolean> saveLogininfor(@RequestParam("username") String username, @RequestParam("status") String status, public R<Boolean> saveLogininfor(@RequestBody SysLogininfor sysLogininfor, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
@RequestParam("message") String message);
} }

View File

@@ -3,8 +3,13 @@ package com.ruoyi.system.api;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.factory.RemoteUserFallbackFactory; import com.ruoyi.system.api.factory.RemoteUserFallbackFactory;
import com.ruoyi.system.api.model.LoginUser; import com.ruoyi.system.api.model.LoginUser;
@@ -20,8 +25,19 @@ public interface RemoteUserService
* 通过用户名查询用户信息 * 通过用户名查询用户信息
* *
* @param username 用户名 * @param username 用户名
* @param source 请求来源
* @return 结果 * @return 结果
*/ */
@GetMapping(value = "/user/info/{username}") @GetMapping("/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username); public R<LoginUser> getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 注册用户信息
*
* @param sysUser 用户信息
* @param source 请求来源
* @return 结果
*/
@PostMapping("/user/register")
public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
} }

View File

@@ -1,4 +1,4 @@
package com.ruoyi.system.domain; package com.ruoyi.system.api.domain;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;

View File

@@ -1,4 +1,4 @@
package com.ruoyi.system.domain; package com.ruoyi.system.api.domain;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;

View File

@@ -1,4 +1,4 @@
package com.ruoyi.system.domain; package com.ruoyi.system.api.domain;
import java.util.Date; import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;

View File

@@ -203,7 +203,8 @@ public class SysRole extends BaseEntity
{ {
this.deptIds = deptIds; this.deptIds = deptIds;
} }
@Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("roleId", getRoleId()) .append("roleId", getRoleId())

View File

@@ -57,9 +57,6 @@ public class SysUser extends BaseEntity
/** 密码 */ /** 密码 */
private String password; private String password;
/** 盐加密 */
private String salt;
/** 帐号状态0正常 1停用 */ /** 帐号状态0正常 1停用 */
@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
private String status; private String status;
@@ -91,6 +88,9 @@ public class SysUser extends BaseEntity
/** 岗位组 */ /** 岗位组 */
private Long[] postIds; private Long[] postIds;
/** 角色ID */
private Long roleId;
public SysUser() public SysUser()
{ {
@@ -208,16 +208,6 @@ public class SysUser extends BaseEntity
this.password = password; this.password = password;
} }
public String getSalt()
{
return salt;
}
public void setSalt(String salt)
{
this.salt = salt;
}
public String getStatus() public String getStatus()
{ {
return status; return status;
@@ -297,7 +287,16 @@ public class SysUser extends BaseEntity
{ {
this.postIds = postIds; this.postIds = postIds;
} }
public Long getRoleId()
{
return roleId;
}
public void setRoleId(Long roleId)
{
this.roleId = roleId;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -310,7 +309,6 @@ public class SysUser extends BaseEntity
.append("sex", getSex()) .append("sex", getSex())
.append("avatar", getAvatar()) .append("avatar", getAvatar())
.append("password", getPassword()) .append("password", getPassword())
.append("salt", getSalt())
.append("status", getStatus()) .append("status", getStatus())
.append("delFlag", getDelFlag()) .append("delFlag", getDelFlag())
.append("loginIp", getLoginIp()) .append("loginIp", getLoginIp())

View File

@@ -6,6 +6,7 @@ import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteLogService; import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysOperLog; import com.ruoyi.system.api.domain.SysOperLog;
/** /**
@@ -25,13 +26,13 @@ public class RemoteLogFallbackFactory implements FallbackFactory<RemoteLogServic
return new RemoteLogService() return new RemoteLogService()
{ {
@Override @Override
public R<Boolean> saveLog(SysOperLog sysOperLog) public R<Boolean> saveLog(SysOperLog sysOperLog, String source)
{ {
return null; return null;
} }
@Override @Override
public R<Boolean> saveLogininfor(String username, String status, String message) public R<Boolean> saveLogininfor(SysLogininfor sysLogininfor, String source)
{ {
return null; return null;
} }

View File

@@ -6,6 +6,7 @@ import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteUserService; import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser; import com.ruoyi.system.api.model.LoginUser;
/** /**
@@ -25,10 +26,16 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
return new RemoteUserService() return new RemoteUserService()
{ {
@Override @Override
public R<LoginUser> getUserInfo(String username) public R<LoginUser> getUserInfo(String username, String source)
{ {
return R.fail("获取用户失败:" + throwable.getMessage()); return R.fail("获取用户失败:" + throwable.getMessage());
} }
@Override
public R<Boolean> registerUserInfo(SysUser sysUser, String source)
{
return R.fail("注册用户失败:" + throwable.getMessage());
}
}; };
} }
} }

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.auth.form.LoginBody; import com.ruoyi.auth.form.LoginBody;
import com.ruoyi.auth.form.RegisterBody;
import com.ruoyi.auth.service.SysLoginService; import com.ruoyi.auth.service.SysLoginService;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
@@ -63,4 +64,12 @@ public class TokenController
} }
return R.ok(); return R.ok();
} }
@PostMapping("register")
public R<?> register(@RequestBody RegisterBody registerBody)
{
// 用户注册
sysLoginService.register(registerBody.getUsername(), registerBody.getPassword());
return R.ok();
}
} }

View File

@@ -0,0 +1,11 @@
package com.ruoyi.auth.form;
/**
* 用户注册对象
*
* @author ruoyi
*/
public class RegisterBody extends LoginBody
{
}

View File

@@ -3,14 +3,18 @@ package com.ruoyi.auth.service;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.Constants; import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.enums.UserStatus; import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.BaseException; import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.SecurityUtils; import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.system.api.RemoteLogService; import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.RemoteUserService; import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser; import com.ruoyi.system.api.model.LoginUser;
@@ -36,60 +40,120 @@ public class SysLoginService
// 用户名或密码为空 错误 // 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password)) if (StringUtils.isAnyBlank(username, password))
{ {
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写"); recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
throw new BaseException("用户/密码必须填写"); throw new ServiceException("用户/密码必须填写");
} }
// 密码如果不在指定范围内 错误 // 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{ {
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围"); recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
throw new BaseException("用户密码不在指定范围"); throw new ServiceException("用户密码不在指定范围");
} }
// 用户名不在指定范围内 错误 // 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) || username.length() > UserConstants.USERNAME_MAX_LENGTH)
{ {
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围"); recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new BaseException("用户名不在指定范围"); throw new ServiceException("用户名不在指定范围");
} }
// 查询用户信息 // 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username); R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (R.FAIL == userResult.getCode()) if (R.FAIL == userResult.getCode())
{ {
throw new BaseException(userResult.getMsg()); throw new ServiceException(userResult.getMsg());
} }
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{ {
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在"); recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new BaseException("登录用户:" + username + " 不存在"); throw new ServiceException("登录用户:" + username + " 不存在");
} }
LoginUser userInfo = userResult.getData(); LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser(); SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{ {
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除"); recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
} }
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{ {
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员"); recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new BaseException("对不起,您的账号:" + username + " 已停用"); throw new ServiceException("对不起,您的账号:" + username + " 已停用");
} }
if (!SecurityUtils.matchesPassword(password, user.getPassword())) if (!SecurityUtils.matchesPassword(password, user.getPassword()))
{ {
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误"); recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
throw new BaseException("用户不存在/密码错误"); throw new ServiceException("用户不存在/密码错误");
} }
remoteLogService.saveLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功"); recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
return userInfo; return userInfo;
} }
public void logout(String loginName) public void logout(String loginName)
{ {
remoteLogService.saveLogininfor(loginName, Constants.LOGOUT, "退出成功"); recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
}
/**
* 注册
*/
public void register(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
throw new ServiceException("用户/密码必须填写");
}
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
throw new ServiceException("账户长度必须在2到20个字符之间");
}
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
throw new ServiceException("密码长度必须在5到20个字符之间");
}
// 注册用户信息
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(SecurityUtils.encryptPassword(password));
R<?> registerResult = remoteUserService.registerUserInfo(sysUser, SecurityConstants.INNER);
if (R.FAIL == registerResult.getCode())
{
throw new ServiceException(registerResult.getMsg());
}
recordLogininfor(username, Constants.REGISTER, "注册成功");
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
public void recordLogininfor(String username, String status, String message)
{
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
{
logininfor.setStatus("0");
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus("1");
}
remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER);
} }
} }

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,6 +5,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.math.BigDecimal; import java.math.BigDecimal;
import com.ruoyi.common.core.utils.poi.ExcelHandlerAdapter;
/** /**
* 自定义导出Excel数据注解 * 自定义导出Excel数据注解
@@ -103,7 +104,17 @@ public @interface Excel
/** /**
* 导出字段对齐方式0默认1靠左2居中3靠右 * 导出字段对齐方式0默认1靠左2居中3靠右
*/ */
Align align() default Align.AUTO; public Align align() default Align.AUTO;
/**
* 自定义数据处理器
*/
public Class<?> handler() default ExcelHandlerAdapter.class;
/**
* 自定义数据处理器参数
*/
public String[] args() default {};
public enum Align public enum Align
{ {

View File

@@ -7,33 +7,8 @@ package com.ruoyi.common.core.constant;
*/ */
public class CacheConstants public class CacheConstants
{ {
/**
* 令牌自定义标识
*/
public static final String HEADER = "Authorization";
/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/** /**
* 权限缓存前缀 * 权限缓存前缀
*/ */
public final static String LOGIN_TOKEN_KEY = "login_tokens:"; public final static String LOGIN_TOKEN_KEY = "login_tokens:";
/**
* 用户ID字段
*/
public static final String DETAILS_USER_ID = "user_id";
/**
* 用户名字段
*/
public static final String DETAILS_USERNAME = "username";
/**
* 授权信息字段
*/
public static final String AUTHORIZATION_HEADER = "authorization";
} }

View File

@@ -17,6 +17,16 @@ public class Constants
*/ */
public static final String GBK = "GBK"; public static final String GBK = "GBK";
/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi://";
/**
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap://";
/** /**
* http请求 * http请求
*/ */

View File

@@ -0,0 +1,44 @@
package com.ruoyi.common.core.constant;
/**
* 权限相关通用常量
*
* @author ruoyi
*/
public class SecurityConstants
{
/**
* 令牌自定义标识
*/
public static final String TOKEN_AUTHENTICATION = "Authorization";
/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 用户ID字段
*/
public static final String DETAILS_USER_ID = "user_id";
/**
* 用户名字段
*/
public static final String DETAILS_USERNAME = "username";
/**
* 授权信息字段
*/
public static final String AUTHORIZATION_HEADER = "authorization";
/**
* 请求来源
*/
public static final String FROM_SOURCE = "from-source";
/**
* 内部请求
*/
public static final String INNER = "inner";
}

View File

@@ -57,6 +57,9 @@ public class UserConstants
/** ParentView组件标识 */ /** ParentView组件标识 */
public final static String PARENT_VIEW = "ParentView"; public final static String PARENT_VIEW = "ParentView";
/** InnerLink组件标识 */
public final static String INNER_LINK = "InnerLink";
/** 校验返回结果码 */ /** 校验返回结果码 */
public final static String UNIQUE = "0"; public final static String UNIQUE = "0";

View File

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

View File

@@ -0,0 +1,58 @@
package com.ruoyi.common.core.exception;
/**
* 全局异常
*
* @author ruoyi
*/
public class GlobalException extends RuntimeException
{
private static final long serialVersionUID = 1L;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
private String detailMessage;
/**
* 空构造方法,避免反序列化问题
*/
public GlobalException()
{
}
public GlobalException(String message)
{
this.message = message;
}
public String getDetailMessage()
{
return detailMessage;
}
public GlobalException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}
public String getMessage()
{
return message;
}
public GlobalException setMessage(String message)
{
this.message = message;
return this;
}
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.common.core.exception;
/**
* 内部认证异常
*
* @author ruoyi
*/
public class InnerAuthException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public InnerAuthException(String message)
{
super(message);
}
}

View File

@@ -0,0 +1,73 @@
package com.ruoyi.common.core.exception;
/**
* 业务异常
*
* @author ruoyi
*/
public final class ServiceException extends RuntimeException
{
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
private String detailMessage;
/**
* 空构造方法,避免反序列化问题
*/
public ServiceException()
{
}
public ServiceException(String message)
{
this.message = message;
}
public ServiceException(String message, Integer code)
{
this.message = message;
this.code = code;
}
public String getDetailMessage()
{
return detailMessage;
}
public String getMessage()
{
return message;
}
public Integer getCode()
{
return code;
}
public ServiceException setMessage(String message)
{
this.message = message;
return this;
}
public ServiceException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}
}

View File

@@ -1,79 +1,79 @@
package com.ruoyi.common.core.exception; package com.ruoyi.common.core.exception.base;
/** /**
* 基础异常 * 基础异常
* *
* @author ruoyi * @author ruoyi
*/ */
public class BaseException extends RuntimeException public class BaseException extends RuntimeException
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
* 所属模块 * 所属模块
*/ */
private String module; private String module;
/** /**
* 错误码 * 错误码
*/ */
private String code; private String code;
/** /**
* 错误码对应的参数 * 错误码对应的参数
*/ */
private Object[] args; private Object[] args;
/** /**
* 错误消息 * 错误消息
*/ */
private String defaultMessage; private String defaultMessage;
public BaseException(String module, String code, Object[] args, String defaultMessage) public BaseException(String module, String code, Object[] args, String defaultMessage)
{ {
this.module = module; this.module = module;
this.code = code; this.code = code;
this.args = args; this.args = args;
this.defaultMessage = defaultMessage; this.defaultMessage = defaultMessage;
} }
public BaseException(String module, String code, Object[] args) public BaseException(String module, String code, Object[] args)
{ {
this(module, code, args, null); this(module, code, args, null);
} }
public BaseException(String module, String defaultMessage) public BaseException(String module, String defaultMessage)
{ {
this(module, null, null, defaultMessage); this(module, null, null, defaultMessage);
} }
public BaseException(String code, Object[] args) public BaseException(String code, Object[] args)
{ {
this(null, code, args, null); this(null, code, args, null);
} }
public BaseException(String defaultMessage) public BaseException(String defaultMessage)
{ {
this(null, null, null, defaultMessage); this(null, null, null, defaultMessage);
} }
public String getModule() public String getModule()
{ {
return module; return module;
} }
public String getCode() public String getCode()
{ {
return code; return code;
} }
public Object[] getArgs() public Object[] getArgs()
{ {
return args; return args;
} }
public String getDefaultMessage() public String getDefaultMessage()
{ {
return defaultMessage; return defaultMessage;
} }
} }

View File

@@ -1,6 +1,6 @@
package com.ruoyi.common.core.exception.file; package com.ruoyi.common.core.exception.file;
import com.ruoyi.common.core.exception.BaseException; import com.ruoyi.common.core.exception.base.BaseException;
/** /**
* 文件信息异常类 * 文件信息异常类

View File

@@ -1,6 +1,6 @@
package com.ruoyi.common.core.exception.user; package com.ruoyi.common.core.exception.user;
import com.ruoyi.common.core.exception.BaseException; import com.ruoyi.common.core.exception.base.BaseException;
/** /**
* 用户信息异常类 * 用户信息异常类

View File

@@ -2,7 +2,7 @@ package com.ruoyi.common.core.utils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.ruoyi.common.core.constant.CacheConstants; import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
/** /**
@@ -17,7 +17,7 @@ public class SecurityUtils
*/ */
public static String getUsername() public static String getUsername()
{ {
String username = ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USERNAME); String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
return ServletUtils.urlDecode(username); return ServletUtils.urlDecode(username);
} }
@@ -26,7 +26,7 @@ public class SecurityUtils
*/ */
public static Long getUserId() public static Long getUserId()
{ {
return Convert.toLong(ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USER_ID)); return Convert.toLong(ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID));
} }
/** /**
@@ -42,10 +42,18 @@ public class SecurityUtils
*/ */
public static String getToken(HttpServletRequest request) public static String getToken(HttpServletRequest request)
{ {
String token = ServletUtils.getRequest().getHeader(CacheConstants.HEADER); String token = request.getHeader(SecurityConstants.TOKEN_AUTHENTICATION);
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX)) return replaceTokenPrefix(token);
}
/**
* 替换token前缀
*/
public static String replaceTokenPrefix(String token)
{
if (StringUtils.isNotEmpty(token) && token.startsWith(SecurityConstants.TOKEN_PREFIX))
{ {
token = token.replace(CacheConstants.TOKEN_PREFIX, ""); token = token.replace(SecurityConstants.TOKEN_PREFIX, "");
} }
return token; return token;
} }

View File

@@ -10,11 +10,19 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.constant.Constants; import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import reactor.core.publisher.Mono;
/** /**
* 客户端工具类 * 客户端工具类
@@ -55,6 +63,22 @@ public class ServletUtils
return Convert.toInt(getRequest().getParameter(name), defaultValue); return Convert.toInt(getRequest().getParameter(name), defaultValue);
} }
/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name)
{
return Convert.toBool(getRequest().getParameter(name));
}
/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name, Boolean defaultValue)
{
return Convert.toBool(getRequest().getParameter(name), defaultValue);
}
/** /**
* 获取request * 获取request
*/ */
@@ -213,4 +237,62 @@ public class ServletUtils
return ""; return "";
} }
} }
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value)
{
return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code)
{
return webFluxResponseWriter(response, HttpStatus.OK, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code)
{
return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param contentType content-type
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code)
{
response.setStatusCode(status);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
R<?> result = R.fail(code, value.toString());
DataBuffer dataBuffer = response.bufferFactory().wrap(JSONObject.toJSONString(result).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
} }

View File

@@ -4,6 +4,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.text.StrFormatter; import com.ruoyi.common.core.text.StrFormatter;
/** /**
@@ -282,6 +283,17 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return StrFormatter.format(template, params); return StrFormatter.format(template, params);
} }
/**
* 是否为http(s)://开头
*
* @param link 链接
* @return 结果
*/
public static boolean ishttp(String link)
{
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
}
/** /**
* 驼峰转下划线命名 * 驼峰转下划线命名
*/ */

View File

@@ -18,7 +18,7 @@ import com.ruoyi.common.core.utils.StringUtils;
* *
* @author ruoyi * @author ruoyi
*/ */
public class FileUtils extends org.apache.commons.io.FileUtils public class FileUtils
{ {
/** 字符常量:斜杠 {@code '/'} */ /** 字符常量:斜杠 {@code '/'} */
public static final char SLASH = '/'; public static final char SLASH = '/';
@@ -244,6 +244,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils
.append(percentEncodedFileName); .append(percentEncodedFileName);
response.setHeader("Content-disposition", contentDispositionValue.toString()); response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);
} }
/** /**

View File

@@ -0,0 +1,155 @@
package com.ruoyi.common.core.utils.html;
import com.ruoyi.common.core.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));
}
}

View File

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

View File

@@ -0,0 +1,19 @@
package com.ruoyi.common.core.utils.poi;
/**
* Excel数据格式处理适配器
*
* @author ruoyi
*/
public interface ExcelHandlerAdapter
{
/**
* 格式化
*
* @param value 单元格数据值
* @param args excel注解args参数组
*
* @return 处理后的值
*/
Object format(Object value, String[] args);
}

View File

@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -35,6 +36,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory; 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.ss.util.CellRangeAddressList;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook;
@@ -102,6 +104,16 @@ public class ExcelUtil<T>
*/ */
private List<Object[]> fields; private List<Object[]> fields;
/**
* 当前行号
*/
private int rownum;
/**
* 标题
*/
private String title;
/** /**
* 最大高度 * 最大高度
*/ */
@@ -127,7 +139,7 @@ public class ExcelUtil<T>
this.clazz = clazz; this.clazz = clazz;
} }
public void init(List<T> list, String sheetName, Type type) public void init(List<T> list, String sheetName, String title, Type type)
{ {
if (list == null) if (list == null)
{ {
@@ -136,8 +148,27 @@ public class ExcelUtil<T>
this.list = list; this.list = list;
this.sheetName = sheetName; this.sheetName = sheetName;
this.type = type; this.type = type;
this.title = title;
createExcelField(); createExcelField();
createWorkbook(); createWorkbook();
createTitle();
}
/**
* 创建excel第一行标题
*/
public void createTitle()
{
if (StringUtils.isNotEmpty(title))
{
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
titleRow.setHeightInPoints(30);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(styles.get("title"));
titleCell.setCellValue(title);
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(),
this.fields.size() - 1));
}
} }
/** /**
@@ -148,33 +179,36 @@ public class ExcelUtil<T>
*/ */
public List<T> importExcel(InputStream is) throws Exception public List<T> importExcel(InputStream is) throws Exception
{ {
return importExcel(StringUtils.EMPTY, is); return importExcel(is, 0);
}
/**
* 对excel表单默认第一个索引名转换成list
*
* @param is 输入流
* @param titleNum 标题占用行数
* @return 转换后集合
*/
public List<T> importExcel(InputStream is, int titleNum) throws Exception
{
return importExcel(StringUtils.EMPTY, is, titleNum);
} }
/** /**
* 对excel表单指定表格索引名转换成list * 对excel表单指定表格索引名转换成list
* *
* @param sheetName 表格索引名 * @param sheetName 表格索引名
* @param titleNum 标题占用行数
* @param is 输入流 * @param is 输入流
* @return 转换后集合 * @return 转换后集合
*/ */
public List<T> importExcel(String sheetName, InputStream is) throws Exception public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
{ {
this.type = Type.IMPORT; this.type = Type.IMPORT;
this.wb = WorkbookFactory.create(is); this.wb = WorkbookFactory.create(is);
List<T> list = new ArrayList<T>(); List<T> list = new ArrayList<T>();
Sheet sheet = null; // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
if (StringUtils.isNotEmpty(sheetName)) Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
{
// 如果指定sheet名,则取指定sheet中的内容.
sheet = wb.getSheet(sheetName);
}
else
{
// 如果传入的sheet名不存在则默认指向第1个sheet.
sheet = wb.getSheetAt(0);
}
if (sheet == null) if (sheet == null)
{ {
throw new IOException("文件sheet不存在"); throw new IOException("文件sheet不存在");
@@ -188,7 +222,7 @@ public class ExcelUtil<T>
// 定义一个map用于存放excel列的序号和field. // 定义一个map用于存放excel列的序号和field.
Map<String, Integer> cellMap = new HashMap<String, Integer>(); Map<String, Integer> cellMap = new HashMap<String, Integer>();
// 获取表头 // 获取表头
Row heard = sheet.getRow(0); Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
{ {
Cell cell = heard.getCell(i); Cell cell = heard.getCell(i);
@@ -203,25 +237,18 @@ public class ExcelUtil<T>
} }
} }
// 有数据时才处理 得到类的所有field. // 有数据时才处理 得到类的所有field.
Field[] allFields = clazz.getDeclaredFields(); List<Object[]> fields = this.getFields();
// 定义一个map用于存放列的序号和field. Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>(); for (Object[] objects : fields)
for (int col = 0; col < allFields.length; col++)
{ {
Field field = allFields[col]; Excel attr = (Excel) objects[1];
Excel attr = field.getAnnotation(Excel.class); Integer column = cellMap.get(attr.name());
if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) if (column != null)
{ {
// 设置类的私有字段属性可访问. fieldsMap.put(column, objects);
field.setAccessible(true);
Integer column = cellMap.get(attr.name());
if (column != null)
{
fieldsMap.put(column, field);
}
} }
} }
for (int i = 1; i <= rows; i++) for (int i = titleNum + 1; i <= rows; i++)
{ {
// 从第2行开始取数据,默认第一行是表头. // 从第2行开始取数据,默认第一行是表头.
Row row = sheet.getRow(i); Row row = sheet.getRow(i);
@@ -231,14 +258,15 @@ public class ExcelUtil<T>
continue; continue;
} }
T entity = null; T entity = null;
for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet()) for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
{ {
Object val = this.getCellValue(row, entry.getKey()); Object val = this.getCellValue(row, entry.getKey());
// 如果不存在实例则新建. // 如果不存在实例则新建.
entity = (entity == null ? clazz.newInstance() : entity); entity = (entity == null ? clazz.newInstance() : entity);
// 从map中得到对应列的field. // 从map中得到对应列的field.
Field field = fieldsMap.get(entry.getKey()); Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1];
// 取得类型,并根据对象类型设置值. // 取得类型,并根据对象类型设置值.
Class<?> fieldType = field.getType(); Class<?> fieldType = field.getType();
if (String.class == fieldType) if (String.class == fieldType)
@@ -298,7 +326,6 @@ public class ExcelUtil<T>
} }
if (StringUtils.isNotNull(fieldType)) if (StringUtils.isNotNull(fieldType))
{ {
Excel attr = field.getAnnotation(Excel.class);
String propertyName = field.getName(); String propertyName = field.getName();
if (StringUtils.isNotEmpty(attr.targetAttr())) if (StringUtils.isNotEmpty(attr.targetAttr()))
{ {
@@ -308,6 +335,10 @@ public class ExcelUtil<T>
{ {
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
} }
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{
val = dataFormatHandlerAdapter(val, attr);
}
ReflectUtils.invokeSetter(entity, propertyName, val); ReflectUtils.invokeSetter(entity, propertyName, val);
} }
} }
@@ -326,14 +357,35 @@ public class ExcelUtil<T>
* @return 结果 * @return 结果
* @throws IOException * @throws IOException
*/ */
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) throws IOException public void exportExcel(HttpServletResponse response, List<T> list, String sheetName)throws IOException
{
exportExcel(response, list, sheetName, StringUtils.EMPTY);
}
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param response 返回数据
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param title 标题
* @return 结果
* @throws IOException
*/
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title) throws IOException
{ {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8");
this.init(list, sheetName, Type.EXPORT); this.init(list, sheetName, title, Type.EXPORT);
exportExcel(response.getOutputStream()); exportExcel(response.getOutputStream());
} }
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param sheetName 工作表的名称
* @return 结果
*/
/** /**
* 对list数据源将其里面的数据导入到excel表单 * 对list数据源将其里面的数据导入到excel表单
* *
@@ -341,10 +393,22 @@ public class ExcelUtil<T>
* @return 结果 * @return 结果
*/ */
public void importTemplateExcel(HttpServletResponse response, String sheetName) throws IOException public void importTemplateExcel(HttpServletResponse response, String sheetName) throws IOException
{
importTemplateExcel(response, sheetName, StringUtils.EMPTY);
}
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param sheetName 工作表的名称
* @param title 标题
* @return 结果
*/
public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) throws IOException
{ {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8");
this.init(null, sheetName, Type.IMPORT); this.init(null, sheetName, title, Type.IMPORT);
exportExcel(response.getOutputStream()); exportExcel(response.getOutputStream());
} }
@@ -377,13 +441,13 @@ public class ExcelUtil<T>
public void writeSheet() public void writeSheet()
{ {
// 取出一共有多少个sheet. // 取出一共有多少个sheet.
double sheetNo = Math.ceil(list.size() / sheetSize); int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
for (int index = 0; index <= sheetNo; index++) for (int index = 0; index < sheetNo; index++)
{ {
createSheet(sheetNo, index); createSheet(sheetNo, index);
// 产生一行 // 产生一行
Row row = sheet.createRow(0); Row row = sheet.createRow(rownum);
int column = 0; int column = 0;
// 写入各个字段的列头名称 // 写入各个字段的列头名称
for (Object[] os : fields) for (Object[] os : fields)
@@ -411,7 +475,7 @@ public class ExcelUtil<T>
int endNo = Math.min(startNo + sheetSize, list.size()); int endNo = Math.min(startNo + sheetSize, list.size());
for (int i = startNo; i < endNo; i++) for (int i = startNo; i < endNo; i++)
{ {
row = sheet.createRow(i + 1 - startNo); row = sheet.createRow(i + 1 + rownum - startNo);
// 得到导出对象. // 得到导出对象.
T vo = (T) list.get(i); T vo = (T) list.get(i);
int column = 0; int column = 0;
@@ -419,8 +483,6 @@ public class ExcelUtil<T>
{ {
Field field = (Field) os[0]; Field field = (Field) os[0];
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
// 设置实体类私有属性可访问
field.setAccessible(true);
this.addCell(excel, row, vo, field, column++); this.addCell(excel, row, vo, field, column++);
} }
} }
@@ -439,6 +501,16 @@ public class ExcelUtil<T>
CellStyle style = wb.createCellStyle(); CellStyle style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER); style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER);
Font titleFont = wb.createFont();
titleFont.setFontName("Arial");
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true);
style.setFont(titleFont);
styles.put("title", style);
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderRight(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN);
@@ -466,7 +538,7 @@ public class ExcelUtil<T>
headerFont.setColor(IndexedColors.WHITE.getIndex()); headerFont.setColor(IndexedColors.WHITE.getIndex());
style.setFont(headerFont); style.setFont(headerFont);
styles.put("header", style); styles.put("header", style);
style = wb.createCellStyle(); style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER); style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER);
@@ -633,6 +705,10 @@ public class ExcelUtil<T>
{ {
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString()); cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
} }
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{
cell.setCellValue(dataFormatHandlerAdapter(value, attr));
}
else else
{ {
// 设置列类型 // 设置列类型
@@ -779,6 +855,28 @@ public class ExcelUtil<T>
return StringUtils.stripEnd(propertyString.toString(), separator); return StringUtils.stripEnd(propertyString.toString(), separator);
} }
/**
* 数据处理器
*
* @param value 数据值
* @param excel 数据注解
* @return
*/
public String dataFormatHandlerAdapter(Object value, Excel excel)
{
try
{
Object instance = excel.handler().newInstance();
Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class });
value = formatMethod.invoke(instance, value, excel.args());
}
catch (Exception e)
{
log.error("不能格式化数据 " + excel.handler(), e.getMessage());
}
return Convert.toStr(value);
}
/** /**
* 合计统计信息 * 合计统计信息
*/ */
@@ -809,10 +907,9 @@ public class ExcelUtil<T>
{ {
if (statistics.size() > 0) if (statistics.size() > 0)
{ {
Cell cell = null;
Row row = sheet.createRow(sheet.getLastRowNum() + 1); Row row = sheet.createRow(sheet.getLastRowNum() + 1);
Set<Integer> keys = statistics.keySet(); Set<Integer> keys = statistics.keySet();
cell = row.createCell(0); Cell cell = row.createCell(0);
cell.setCellStyle(styles.get("total")); cell.setCellStyle(styles.get("total"));
cell.setCellValue("合计"); cell.setCellValue("合计");
@@ -882,7 +979,17 @@ public class ExcelUtil<T>
*/ */
private void createExcelField() private void createExcelField()
{ {
this.fields = new ArrayList<Object[]>(); this.fields = getFields();
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
this.maxHeight = getRowHeight();
}
/**
* 获取字段注解信息
*/
public List<Object[]> getFields()
{
List<Object[]> fields = new ArrayList<Object[]>();
List<Field> tempFields = new ArrayList<>(); List<Field> tempFields = new ArrayList<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
@@ -891,7 +998,12 @@ public class ExcelUtil<T>
// 单注解 // 单注解
if (field.isAnnotationPresent(Excel.class)) if (field.isAnnotationPresent(Excel.class))
{ {
putToField(field, field.getAnnotation(Excel.class)); Excel attr = field.getAnnotation(Excel.class);
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
{
field.setAccessible(true);
fields.add(new Object[] { field, attr });
}
} }
// 多注解 // 多注解
@@ -899,16 +1011,19 @@ public class ExcelUtil<T>
{ {
Excels attrs = field.getAnnotation(Excels.class); Excels attrs = field.getAnnotation(Excels.class);
Excel[] excels = attrs.value(); Excel[] excels = attrs.value();
for (Excel excel : excels) for (Excel attr : excels)
{ {
putToField(field, excel); if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
{
field.setAccessible(true);
fields.add(new Object[] { field, attr });
}
} }
} }
} }
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); return fields;
this.maxHeight = getRowHeight();
} }
/** /**
* 根据注解获取最大行高 * 根据注解获取最大行高
*/ */
@@ -923,23 +1038,15 @@ public class ExcelUtil<T>
return (short) (maxHeight * 20); return (short) (maxHeight * 20);
} }
/**
* 放到字段集合中
*/
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() public void createWorkbook()
{ {
this.wb = new SXSSFWorkbook(500); this.wb = new SXSSFWorkbook(500);
this.sheet = wb.createSheet();
wb.setSheetName(0, sheetName);
this.styles = createStyles(wb);
} }
/** /**
@@ -948,17 +1055,13 @@ public class ExcelUtil<T>
* @param sheetNo sheet数量 * @param sheetNo sheet数量
* @param index 序号 * @param index 序号
*/ */
public void createSheet(double sheetNo, int index) public void createSheet(int sheetNo, int index)
{ {
this.sheet = wb.createSheet();
this.styles = createStyles(wb);
// 设置工作表的名称. // 设置工作表的名称.
if (sheetNo == 0) if (sheetNo > 1 && index > 0)
{
wb.setSheetName(index, sheetName);
}
else
{ {
this.sheet = wb.createSheet();
this.createTitle();
wb.setSheetName(index, sheetName + index); wb.setSheetName(index, sheetName + index);
} }
} }

View File

@@ -1,6 +1,6 @@
package com.ruoyi.common.core.utils.sql; package com.ruoyi.common.core.utils.sql;
import com.ruoyi.common.core.exception.BaseException; import com.ruoyi.common.core.exception.UtilException;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
/** /**
@@ -22,7 +22,7 @@ public class SqlUtil
{ {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
{ {
throw new BaseException("参数不符合规范,不能进行查询"); throw new UtilException("参数不符合规范,不能进行查询");
} }
return value; return value;
} }

View File

@@ -55,7 +55,8 @@ public class BaseController
if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
{ {
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
PageHelper.startPage(pageNum, pageSize, orderBy); Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
} }
} }

View File

@@ -21,6 +21,9 @@ public class PageDomain
/** 排序的方向desc或者asc */ /** 排序的方向desc或者asc */
private String isAsc = "asc"; private String isAsc = "asc";
/** 分页参数合理化 */
private Boolean reasonable = true;
public String getOrderBy() public String getOrderBy()
{ {
if (StringUtils.isEmpty(orderByColumn)) if (StringUtils.isEmpty(orderByColumn))
@@ -67,6 +70,32 @@ public class PageDomain
public void setIsAsc(String isAsc) public void setIsAsc(String isAsc)
{ {
this.isAsc = isAsc; if (StringUtils.isNotEmpty(isAsc))
{
// 兼容前端排序类型
if ("ascending".equals(isAsc))
{
isAsc = "asc";
}
else if ("descending".equals(isAsc))
{
isAsc = "desc";
}
this.isAsc = isAsc;
}
}
public Boolean getReasonable()
{
if (StringUtils.isNull(reasonable))
{
return Boolean.TRUE;
}
return reasonable;
}
public void setReasonable(Boolean reasonable)
{
this.reasonable = reasonable;
} }
} }

View File

@@ -29,6 +29,11 @@ public class TableSupport
*/ */
public static final String IS_ASC = "isAsc"; public static final String IS_ASC = "isAsc";
/**
* 分页参数合理化
*/
public static final String REASONABLE = "reasonable";
/** /**
* 封装分页对象 * 封装分页对象
*/ */
@@ -39,6 +44,7 @@ public class TableSupport
pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE)); pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE));
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
return pageDomain; return pageDomain;
} }

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -1,12 +1,8 @@
package com.ruoyi.common.datascope.aspect; package com.ruoyi.common.datascope.aspect;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
@@ -59,27 +55,15 @@ public class DataScopeAspect
@Autowired @Autowired
private TokenService tokenService; private TokenService tokenService;
// 配置织入点 @Before("@annotation(controllerDataScope)")
@Pointcut("@annotation(com.ruoyi.common.datascope.annotation.DataScope)") public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
public void dataScopePointCut()
{
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable
{ {
clearDataScope(point); clearDataScope(point);
handleDataScope(point); handleDataScope(point, controllerDataScope);
} }
protected void handleDataScope(final JoinPoint joinPoint) protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{ {
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null)
{
return;
}
// 获取当前的用户 // 获取当前的用户
LoginUser loginUser = tokenService.getLoginUser(); LoginUser loginUser = tokenService.getLoginUser();
if (StringUtils.isNotNull(loginUser)) if (StringUtils.isNotNull(loginUser))
@@ -155,22 +139,6 @@ public class DataScopeAspect
} }
} }
/**
* 是否存在注解,如果存在就获取
*/
private DataScope getAnnotationLog(JoinPoint joinPoint)
{
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
return method.getAnnotation(DataScope.class);
}
return null;
}
/** /**
* 拼接权限sql前先清空params.dataScope参数防止注入 * 拼接权限sql前先清空params.dataScope参数防止注入
*/ */

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -1,18 +1,13 @@
package com.ruoyi.common.log.aspect; package com.ruoyi.common.log.aspect;
import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -44,21 +39,15 @@ public class LogAspect
@Autowired @Autowired
private AsyncLogService asyncLogService; private AsyncLogService asyncLogService;
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.log.annotation.Log)")
public void logPointCut()
{
}
/** /**
* 处理完请求后执行 * 处理完请求后执行
* *
* @param joinPoint 切点 * @param joinPoint 切点
*/ */
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult") @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{ {
handleLog(joinPoint, null, jsonResult); handleLog(joinPoint, controllerLog, null, jsonResult);
} }
/** /**
@@ -67,32 +56,22 @@ public class LogAspect
* @param joinPoint 切点 * @param joinPoint 切点
* @param e 异常 * @param e 异常
*/ */
@AfterThrowing(value = "logPointCut()", throwing = "e") @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{ {
handleLog(joinPoint, e, null); handleLog(joinPoint, controllerLog, e, null);
} }
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{ {
try try
{ {
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null)
{
return;
}
// *========数据库日志=========*// // *========数据库日志=========*//
SysOperLog operLog = new SysOperLog(); SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址 // 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip); operLog.setOperIp(ip);
// 返回参数
operLog.setJsonResult(JSON.toJSONString(jsonResult));
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI()); operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
String username = SecurityUtils.getUsername(); String username = SecurityUtils.getUsername();
if (StringUtils.isNotBlank(username)) if (StringUtils.isNotBlank(username))
@@ -112,7 +91,7 @@ public class LogAspect
// 设置请求方式 // 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数 // 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog); getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 保存数据库 // 保存数据库
asyncLogService.saveSysLog(operLog); asyncLogService.saveSysLog(operLog);
} }
@@ -132,7 +111,7 @@ public class LogAspect
* @param operLog 操作日志 * @param operLog 操作日志
* @throws Exception * @throws Exception
*/ */
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
{ {
// 设置action动作 // 设置action动作
operLog.setBusinessType(log.businessType().ordinal()); operLog.setBusinessType(log.businessType().ordinal());
@@ -146,6 +125,11 @@ public class LogAspect
// 获取参数的信息,传入到数据库中。 // 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog); setRequestValue(joinPoint, operLog);
} }
// 是否需要保存response参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
{
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
} }
/** /**
@@ -164,22 +148,6 @@ public class LogAspect
} }
} }
/**
* 是否存在注解,如果存在就获取
*/
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
{
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
return method.getAnnotation(Log.class);
}
return null;
}
/** /**
* 参数拼装 * 参数拼装
*/ */
@@ -188,13 +156,13 @@ public class LogAspect
String params = ""; String params = "";
if (paramsArray != null && paramsArray.length > 0) if (paramsArray != null && paramsArray.length > 0)
{ {
for (int i = 0; i < paramsArray.length; i++) for (Object o : paramsArray)
{ {
if (StringUtils.isNotNull(paramsArray[i]) && !isFilterObject(paramsArray[i])) if (StringUtils.isNotNull(o) && !isFilterObject(o))
{ {
try try
{ {
Object jsonObj = JSON.toJSON(paramsArray[i]); Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " "; params += jsonObj.toString() + " ";
} }
catch (Exception e) catch (Exception e)
@@ -223,17 +191,17 @@ public class LogAspect
else if (Collection.class.isAssignableFrom(clazz)) else if (Collection.class.isAssignableFrom(clazz))
{ {
Collection collection = (Collection) o; Collection collection = (Collection) o;
for (Iterator iter = collection.iterator(); iter.hasNext();) for (Object value : collection)
{ {
return iter.next() instanceof MultipartFile; return value instanceof MultipartFile;
} }
} }
else if (Map.class.isAssignableFrom(clazz)) else if (Map.class.isAssignableFrom(clazz))
{ {
Map map = (Map) o; Map map = (Map) o;
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) for (Object value : map.entrySet())
{ {
Map.Entry entry = (Map.Entry) iter.next(); Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile; return entry.getValue() instanceof MultipartFile;
} }
} }

View File

@@ -3,6 +3,7 @@ package com.ruoyi.common.log.service;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.system.api.RemoteLogService; import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.domain.SysOperLog; import com.ruoyi.system.api.domain.SysOperLog;
@@ -23,6 +24,6 @@ public class AsyncLogService
@Async @Async
public void saveSysLog(SysOperLog sysOperLog) public void saveSysLog(SysOperLog sysOperLog)
{ {
remoteLogService.saveLog(sysOperLog); remoteLogService.saveLog(sysOperLog, SecurityConstants.INNER);
} }
} }

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -74,6 +74,17 @@ public class RedisService
return redisTemplate.expire(key, timeout, unit); return redisTemplate.expire(key, timeout, unit);
} }
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
/** /**
* 获得缓存的基本对象。 * 获得缓存的基本对象。
* *

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,19 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.*;
/**
* 内部认证注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth
{
/**
* 是否校验用户信息
*/
boolean isUser() default false;
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.common.security.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.exception.InnerAuthException;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.annotation.InnerAuth;
/**
* 内部服务调用验证处理
*
* @author ruoyi
*/
@Aspect
@Component
public class InnerAuthAspect implements Ordered
{
@Around("@annotation(innerAuth)")
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable
{
String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
// 内部请求验证
if (!StringUtils.equals(SecurityConstants.INNER, source))
{
throw new InnerAuthException("没有内部访问权限,不允许访问");
}
String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);
String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
// 用户信息验证
if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)))
{
throw new InnerAuthException("没有设置用户信息,不允许访问 ");
}
return point.proceed();
}
/**
* 确保在权限认证aop执行前执行
*/
@Override
public int getOrder()
{
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

View File

@@ -2,11 +2,11 @@ package com.ruoyi.common.security.feign;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import com.ruoyi.common.core.utils.ip.IpUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.CacheConstants; import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
@@ -26,20 +26,20 @@ public class FeignRequestInterceptor implements RequestInterceptor
{ {
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest); Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
// 传递用户信息请求头,防止丢失 // 传递用户信息请求头,防止丢失
String userId = headers.get(CacheConstants.DETAILS_USER_ID); String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
if (StringUtils.isNotEmpty(userId)) if (StringUtils.isNotEmpty(userId))
{ {
requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId); requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);
} }
String userName = headers.get(CacheConstants.DETAILS_USERNAME); String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
if (StringUtils.isNotEmpty(userName)) if (StringUtils.isNotEmpty(userName))
{ {
requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName); requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName);
} }
String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER); String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication)) if (StringUtils.isNotEmpty(authentication))
{ {
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication); requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
} }
// 配置客户端IP // 配置客户端IP

View File

@@ -1,15 +1,18 @@
package com.ruoyi.common.security.handler; package com.ruoyi.common.security.handler;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.ruoyi.common.core.exception.BaseException; import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.exception.CustomException;
import com.ruoyi.common.core.exception.DemoModeException; import com.ruoyi.common.core.exception.DemoModeException;
import com.ruoyi.common.core.exception.InnerAuthException;
import com.ruoyi.common.core.exception.PreAuthorizeException; import com.ruoyi.common.core.exception.PreAuthorizeException;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.AjaxResult;
@@ -24,31 +27,58 @@ public class GlobalExceptionHandler
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/** /**
* 基础异常 * 权限异常
*/ */
@ExceptionHandler(BaseException.class) @ExceptionHandler(PreAuthorizeException.class)
public AjaxResult baseException(BaseException e) public AjaxResult handlePreAuthorizeException(PreAuthorizeException e, HttpServletRequest request)
{ {
return AjaxResult.error(e.getDefaultMessage()); String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return AjaxResult.error(e.getMessage());
} }
/** /**
* 业务异常 * 业务异常
*/ */
@ExceptionHandler(CustomException.class) @ExceptionHandler(ServiceException.class)
public AjaxResult businessException(CustomException e) public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
{
if (StringUtils.isNull(e.getCode()))
{
return AjaxResult.error(e.getMessage());
}
return AjaxResult.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e)
{ {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(e.getMessage()); return AjaxResult.error(e.getMessage());
} }
@@ -56,7 +86,7 @@ public class GlobalExceptionHandler
* 自定义验证异常 * 自定义验证异常
*/ */
@ExceptionHandler(BindException.class) @ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e) public AjaxResult handleBindException(BindException e)
{ {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage(); String message = e.getAllErrors().get(0).getDefaultMessage();
@@ -67,27 +97,27 @@ public class GlobalExceptionHandler
* 自定义验证异常 * 自定义验证异常
*/ */
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e) public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{ {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage(); String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message); return AjaxResult.error(message);
} }
/** /**
* 权限异常 * 内部认证异常
*/ */
@ExceptionHandler(PreAuthorizeException.class) @ExceptionHandler(InnerAuthException.class)
public AjaxResult preAuthorizeException(PreAuthorizeException e) public AjaxResult handleInnerAuthException(InnerAuthException e)
{ {
return AjaxResult.error("没有权限,请联系管理员授权"); return AjaxResult.error(e.getMessage());
} }
/** /**
* 演示模式异常 * 演示模式异常
*/ */
@ExceptionHandler(DemoModeException.class) @ExceptionHandler(DemoModeException.class)
public AjaxResult demoModeException(DemoModeException e) public AjaxResult handleDemoModeException(DemoModeException e)
{ {
return AjaxResult.error("演示模式,不允许操作"); return AjaxResult.error("演示模式,不允许操作");
} }

View File

@@ -73,6 +73,16 @@ public class TokenService
{ {
// 获取请求携带的令牌 // 获取请求携带的令牌
String token = SecurityUtils.getToken(request); String token = SecurityUtils.getToken(request);
return getLoginUser(token);
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(String token)
{
if (StringUtils.isNotEmpty(token)) if (StringUtils.isNotEmpty(token))
{ {
String userKey = getTokenKey(token); String userKey = getTokenKey(token);

View File

@@ -1,4 +1,4 @@
package com.ruoyi.system.utils; package com.ruoyi.common.security.utils;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -6,7 +6,7 @@ import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.utils.SpringUtils; import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.system.domain.SysDictData; import com.ruoyi.system.api.domain.SysDictData;
/** /**
* 字典工具类 * 字典工具类

View File

@@ -1,4 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.common.security.service.TokenService,\ com.ruoyi.common.security.service.TokenService,\
com.ruoyi.common.security.aspect.PreAuthorizeAspect,\ com.ruoyi.common.security.aspect.PreAuthorizeAspect,\
com.ruoyi.common.security.aspect.InnerAuthAspect,\
com.ruoyi.common.security.handler.GlobalExceptionHandler com.ruoyi.common.security.handler.GlobalExceptionHandler

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,46 @@
package com.ruoyi.gateway.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 验证码配置
*
* @author ruoyi
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties
{
/**
* 验证码开关
*/
private Boolean enabled;
/**
* 验证码类型math 数组计算 char 字符)
*/
private String type;
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
}

View File

@@ -13,7 +13,7 @@ import org.springframework.context.annotation.Configuration;
*/ */
@Configuration @Configuration
@RefreshScope @RefreshScope
@ConfigurationProperties(prefix = "ignore") @ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties public class IgnoreWhiteProperties
{ {
/** /**

View File

@@ -0,0 +1,48 @@
package com.ruoyi.gateway.config.properties;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* XSS跨站脚本配置
*
* @author ruoyi
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.xss")
public class XssProperties
{
/**
* Xss开关
*/
private Boolean enabled;
/**
* 排除路径
*/
private List<String> excludeUrls = new ArrayList<>();
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public List<String> getExcludeUrls()
{
return excludeUrls;
}
public void setExcludeUrls(List<String> excludeUrls)
{
this.excludeUrls = excludeUrls;
}
}

View File

@@ -7,19 +7,16 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.constant.CacheConstants; import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants; import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.redis.service.RedisService;
@@ -35,7 +32,7 @@ import reactor.core.publisher.Mono;
public class AuthFilter implements GlobalFilter, Ordered public class AuthFilter implements GlobalFilter, Ordered
{ {
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60; private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60;
// 排除过滤的 uri 地址nacos自行添加 // 排除过滤的 uri 地址nacos自行添加
@@ -44,61 +41,75 @@ public class AuthFilter implements GlobalFilter, Ordered
@Resource(name = "stringRedisTemplate") @Resource(name = "stringRedisTemplate")
private ValueOperations<String, String> sops; private ValueOperations<String, String> sops;
@Autowired @Autowired
private RedisService redisService; private RedisService redisService;
@Override @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{ {
String url = exchange.getRequest().getURI().getPath(); ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder mutate = request.mutate();
String url = request.getURI().getPath();
// 跳过不需要验证的路径 // 跳过不需要验证的路径
if (StringUtils.matches(url, ignoreWhite.getWhites())) if (StringUtils.matches(url, ignoreWhite.getWhites()))
{ {
return chain.filter(exchange); return chain.filter(exchange);
} }
String token = getToken(exchange.getRequest()); String token = getToken(request);
if (StringUtils.isBlank(token)) if (StringUtils.isEmpty(token))
{ {
return setUnauthorizedResponse(exchange, "令牌不能为空"); return unauthorizedResponse(exchange, "令牌不能为空");
} }
String userStr = sops.get(getTokenKey(token)); String userStr = sops.get(getTokenKey(token));
if (StringUtils.isNull(userStr)) if (StringUtils.isEmpty(userStr))
{ {
return setUnauthorizedResponse(exchange, "登录状态已过期"); return unauthorizedResponse(exchange, "登录状态已过期");
} }
JSONObject obj = JSONObject.parseObject(userStr); JSONObject cacheObj = JSONObject.parseObject(userStr);
String userid = obj.getString("userid"); String userid = cacheObj.getString("userid");
String username = obj.getString("username"); String username = cacheObj.getString("username");
if (StringUtils.isBlank(userid) || StringUtils.isBlank(username)) if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))
{ {
return setUnauthorizedResponse(exchange, "令牌验证失败"); return unauthorizedResponse(exchange, "令牌验证失败");
} }
// 设置过期时间 // 设置过期时间
redisService.expire(getTokenKey(token), EXPIRE_TIME); redisService.expire(getTokenKey(token), EXPIRE_TIME);
// 设置用户信息到请求 // 设置用户信息到请求
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(CacheConstants.DETAILS_USER_ID, userid) addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
.header(CacheConstants.DETAILS_USERNAME, ServletUtils.urlEncode(username)).build(); addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build(); // 内部请求来源参数清除
removeHeader(mutate, SecurityConstants.FROM_SOURCE);
return chain.filter(mutableExchange); return chain.filter(exchange.mutate().request(mutate.build()).build());
} }
private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg) private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value)
{ {
ServerHttpResponse response = exchange.getResponse(); if (value == null)
response.getHeaders().setContentType(MediaType.APPLICATION_JSON); {
response.setStatusCode(HttpStatus.OK); return;
}
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); String valueStr = value.toString();
String valueEncode = ServletUtils.urlEncode(valueStr);
return response.writeWith(Mono.fromSupplier(() -> { mutate.header(name, valueEncode);
DataBufferFactory bufferFactory = response.bufferFactory();
return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg)));
}));
} }
private void removeHeader(ServerHttpRequest.Builder mutate, String name)
{
mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
}
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg)
{
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
}
/**
* 获取缓存key
*/
private String getTokenKey(String token) private String getTokenKey(String token)
{ {
return CacheConstants.LOGIN_TOKEN_KEY + token; return CacheConstants.LOGIN_TOKEN_KEY + token;
@@ -109,12 +120,8 @@ public class AuthFilter implements GlobalFilter, Ordered
*/ */
private String getToken(ServerHttpRequest request) private String getToken(ServerHttpRequest request)
{ {
String token = request.getHeaders().getFirst(CacheConstants.HEADER); String token = request.getHeaders().getFirst(SecurityConstants.TOKEN_AUTHENTICATION);
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX)) return SecurityUtils.replaceTokenPrefix(token);
{
token = token.replace(CacheConstants.TOKEN_PREFIX, "");
}
return token;
} }
@Override @Override

View File

@@ -5,11 +5,8 @@ import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON; import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import reactor.core.publisher.Mono;
/** /**
* 黑名单过滤器 * 黑名单过滤器
@@ -27,10 +24,7 @@ public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUr
String url = exchange.getRequest().getURI().getPath(); String url = exchange.getRequest().getURI().getPath();
if (config.matchBlacklist(url)) if (config.matchBlacklist(url))
{ {
ServerHttpResponse response = exchange.getResponse(); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问");
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return exchange.getResponse().writeWith(
Mono.just(response.bufferFactory().wrap(JSON.toJSONBytes(AjaxResult.error("请求地址不允许访问")))));
} }
return chain.filter(exchange); return chain.filter(exchange);

View File

@@ -16,6 +16,11 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
/**
* 获取body请求数据解决流不能重复读取问题
*
* @author ruoyi
*/
@Component @Component
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config> public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
{ {

View File

@@ -9,15 +9,13 @@ import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFac
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService; import com.ruoyi.gateway.service.ValidateCodeService;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/** /**
* 验证码过滤器 * 验证码过滤器
@@ -27,11 +25,14 @@ import reactor.core.publisher.Mono;
@Component @Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
{ {
private final static String AUTH_URL = "/auth/login"; private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };
@Autowired @Autowired
private ValidateCodeService validateCodeService; private ValidateCodeService validateCodeService;
@Autowired
private CaptchaProperties captchaProperties;
private static final String CODE = "code"; private static final String CODE = "code";
private static final String UUID = "uuid"; private static final String UUID = "uuid";
@@ -42,8 +43,8 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
return (exchange, chain) -> { return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
// 非登录请求,不处理 // 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.containsIgnoreCase(request.getURI().getPath(), AUTH_URL)) if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
{ {
return chain.filter(exchange); return chain.filter(exchange);
} }
@@ -56,10 +57,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
} }
catch (Exception e) catch (Exception e)
{ {
ServerHttpResponse response = exchange.getResponse(); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return exchange.getResponse().writeWith(
Mono.just(response.bufferFactory().wrap(JSON.toJSONBytes(AjaxResult.error(e.getMessage())))));
} }
return chain.filter(exchange); return chain.filter(exchange);
}; };

View File

@@ -0,0 +1,120 @@
package com.ruoyi.gateway.filter;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.html.EscapeUtil;
import com.ruoyi.gateway.config.properties.XssProperties;
import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 跨站脚本过滤器
*
* @author ruoyi
*/
@Component
@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true")
public class XssFilter implements GlobalFilter, Ordered
{
// 跨站脚本的 xss 配置nacos自行添加
@Autowired
private XssProperties xss;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
ServerHttpRequest request = exchange.getRequest();
// GET DELETE 不过滤
HttpMethod method = request.getMethod();
if (method == null || method.matches("GET") || method.matches("DELETE"))
{
return chain.filter(exchange);
}
// 非json类型不过滤
if (!isJsonRequest(exchange))
{
return chain.filter(exchange);
}
// excludeUrls 不过滤
String url = request.getURI().getPath();
if (StringUtils.matches(url, xss.getExcludeUrls()))
{
return chain.filter(exchange);
}
ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange);
return chain.filter(exchange.mutate().request(httpRequestDecorator).build());
}
private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange)
{
ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest())
{
@Override
public Flux<DataBuffer> getBody()
{
Flux<DataBuffer> body = super.getBody();
return body.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
DataBufferUtils.release(dataBuffer);
String bodyStr = new String(content, StandardCharsets.UTF_8);
// 防xss攻击过滤
bodyStr = EscapeUtil.clean(bodyStr);
// 转成字节
byte[] bytes = bodyStr.getBytes();
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
});
}
@Override
public HttpHeaders getHeaders()
{
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
// 由于修改了请求体的body导致content-length长度不确定因此需要删除原先的content-length
httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
return httpHeaders;
}
};
return serverHttpRequestDecorator;
}
/**
* 是否是Json请求
*
* @param request
*/
public boolean isJsonRequest(ServerWebExchange exchange)
{
String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
}
@Override
public int getOrder()
{
return -100;
}
}

View File

@@ -6,14 +6,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import com.alibaba.fastjson.JSON; import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.domain.R;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
/** /**
@@ -55,12 +51,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
response.getHeaders().setContentType(MediaType.APPLICATION_JSON); return ServletUtils.webFluxResponseWriter(response, msg);
response.setStatusCode(HttpStatus.OK);
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg)));
}));
} }
} }

View File

@@ -1,10 +1,8 @@
package com.ruoyi.gateway.handler; package com.ruoyi.gateway.handler;
import java.nio.charset.StandardCharsets;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.core.io.buffer.DataBuffer; import com.ruoyi.common.core.utils.ServletUtils;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler; import org.springframework.web.server.WebExceptionHandler;
@@ -19,11 +17,7 @@ public class SentinelFallbackHandler implements WebExceptionHandler
{ {
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
{ {
ServerHttpResponse serverHttpResponse = exchange.getResponse(); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍后再试");
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":429,\"msg\":\"请求超过最大数,请稍后再试\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
} }
@Override @Override

View File

@@ -16,6 +16,7 @@ import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.sign.Base64; import com.ruoyi.common.core.utils.sign.Base64;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService; import com.ruoyi.gateway.service.ValidateCodeService;
/** /**
@@ -35,8 +36,8 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
@Autowired @Autowired
private RedisService redisService; private RedisService redisService;
// 验证码类型 @Autowired
private String captchaType = "math"; private CaptchaProperties captchaProperties;
/** /**
* 生成验证码 * 生成验证码
@@ -44,6 +45,14 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
@Override @Override
public AjaxResult createCapcha() throws IOException, CaptchaException public AjaxResult createCapcha() throws IOException, CaptchaException
{ {
AjaxResult ajax = AjaxResult.success();
boolean captchaOnOff = captchaProperties.getEnabled();
ajax.put("captchaOnOff", captchaOnOff);
if (!captchaOnOff)
{
return ajax;
}
// 保存验证码信息 // 保存验证码信息
String uuid = IdUtils.simpleUUID(); String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
@@ -51,6 +60,7 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
String capStr = null, code = null; String capStr = null, code = null;
BufferedImage image = null; BufferedImage image = null;
String captchaType = captchaProperties.getType();
// 生成验证码 // 生成验证码
if ("math".equals(captchaType)) if ("math".equals(captchaType))
{ {
@@ -77,7 +87,6 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
return AjaxResult.error(e.getMessage()); return AjaxResult.error(e.getMessage());
} }
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid); ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray())); ajax.put("img", Base64.encode(os.toByteArray()));
return ajax; return ajax;

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -1,31 +1,31 @@
package com.ruoyi.file; package com.ruoyi.file;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2; import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
/** /**
* 文件服务 * 文件服务
* *
* @author ruoyi * @author ruoyi
*/ */
@EnableCustomSwagger2 @EnableCustomSwagger2
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class RuoYFileApplication public class RuoYiFileApplication
{ {
public static void main(String[] args) public static void main(String[] args)
{ {
SpringApplication.run(RuoYFileApplication.class, args); SpringApplication.run(RuoYiFileApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" + System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" + " .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" + " | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" + " | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" + " |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" + " | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" + " | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" + " | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" + " | | \\ / \\ / \n" +
" ''-' `'-' `-..-' "); " ''-' `'-' `-..-' ");
} }
} }

View File

@@ -3,6 +3,7 @@ package com.ruoyi.file.config;
import java.io.File; import java.io.File;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -33,4 +34,17 @@ public class ResourcesConfig implements WebMvcConfigurer
registry.addResourceHandler(localFilePrefix + "/**") registry.addResourceHandler(localFilePrefix + "/**")
.addResourceLocations("file:" + localFilePath + File.separator); .addResourceLocations("file:" + localFilePath + File.separator);
} }
/**
* 开启跨域
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路由
registry.addMapping(localFilePrefix + "/**")
// 设置允许跨域请求的域名
.allowedOrigins("*")
// 设置允许的方法
.allowedMethods("GET");
}
} }

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -10,6 +10,7 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template; import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext; import org.apache.velocity.VelocityContext;
@@ -23,11 +24,10 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.constant.Constants; import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.GenConstants; import com.ruoyi.common.core.constant.GenConstants;
import com.ruoyi.common.core.exception.CustomException; import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.text.CharsetKit; import com.ruoyi.common.core.text.CharsetKit;
import com.ruoyi.common.core.utils.SecurityUtils; import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.file.FileUtils;
import com.ruoyi.gen.domain.GenTable; import com.ruoyi.gen.domain.GenTable;
import com.ruoyi.gen.domain.GenTableColumn; import com.ruoyi.gen.domain.GenTableColumn;
import com.ruoyi.gen.mapper.GenTableColumnMapper; import com.ruoyi.gen.mapper.GenTableColumnMapper;
@@ -180,7 +180,7 @@ public class GenTableServiceImpl implements IGenTableService
} }
catch (Exception e) catch (Exception e)
{ {
throw new CustomException("导入失败:" + e.getMessage()); throw new ServiceException("导入失败:" + e.getMessage());
} }
} }
@@ -269,7 +269,7 @@ public class GenTableServiceImpl implements IGenTableService
} }
catch (IOException e) catch (IOException e)
{ {
throw new CustomException("渲染模板失败,表名:" + table.getTableName()); throw new ServiceException("渲染模板失败,表名:" + table.getTableName());
} }
} }
} }
@@ -291,7 +291,7 @@ public class GenTableServiceImpl implements IGenTableService
List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
if (StringUtils.isEmpty(dbTableColumns)) if (StringUtils.isEmpty(dbTableColumns))
{ {
throw new CustomException("同步数据失败,原表结构不存在"); throw new ServiceException("同步数据失败,原表结构不存在");
} }
List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList());
@@ -383,25 +383,25 @@ public class GenTableServiceImpl implements IGenTableService
JSONObject paramsObj = JSONObject.parseObject(options); JSONObject paramsObj = JSONObject.parseObject(options);
if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE)))
{ {
throw new CustomException("树编码字段不能为空"); throw new ServiceException("树编码字段不能为空");
} }
else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE)))
{ {
throw new CustomException("树父编码字段不能为空"); throw new ServiceException("树父编码字段不能为空");
} }
else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME)))
{ {
throw new CustomException("树名称字段不能为空"); throw new ServiceException("树名称字段不能为空");
} }
else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory()))
{ {
if (StringUtils.isEmpty(genTable.getSubTableName())) if (StringUtils.isEmpty(genTable.getSubTableName()))
{ {
throw new CustomException("关联子表的表名不能为空"); throw new ServiceException("关联子表的表名不能为空");
} }
else if (StringUtils.isEmpty(genTable.getSubTableFkName())) else if (StringUtils.isEmpty(genTable.getSubTableFkName()))
{ {
throw new CustomException("子表关联的外键名不能为空"); throw new ServiceException("子表关联的外键名不能为空");
} }
} }
} }

View File

@@ -58,6 +58,7 @@ public class VelocityUtils
velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
velocityContext.put("columns", genTable.getColumns()); velocityContext.put("columns", genTable.getColumns());
velocityContext.put("table", genTable); velocityContext.put("table", genTable);
velocityContext.put("dicts", getDicts(genTable));
setMenuVelocityContext(velocityContext, genTable); setMenuVelocityContext(velocityContext, genTable);
if (GenConstants.TPL_TREE.equals(tplCategory)) if (GenConstants.TPL_TREE.equals(tplCategory))
{ {
@@ -260,6 +261,27 @@ public class VelocityUtils
return importList; return importList;
} }
/**
* 根据列类型获取字典组
*
* @param genTable 业务表对象
* @return 返回字典组
*/
public static String getDicts(GenTable genTable)
{
List<GenTableColumn> columns = genTable.getColumns();
List<String> dicts = new ArrayList<String>();
for (GenTableColumn column : columns)
{
if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
column.getHtmlType(), new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO }))
{
dicts.add("'" + column.getDictType() + "'");
}
}
return StringUtils.join(dicts, ", ");
}
/** /**
* 获取权限前缀 * 获取权限前缀
* *
@@ -280,7 +302,8 @@ public class VelocityUtils
*/ */
public static String getParentMenuId(JSONObject paramsObj) public static String getParentMenuId(JSONObject paramsObj)
{ {
if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)) if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
&& StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID)))
{ {
return paramsObj.getString(GenConstants.PARENT_MENU_ID); return paramsObj.getString(GenConstants.PARENT_MENU_ID);
} }

View File

@@ -92,6 +92,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d') AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if> </if>
order by create_time desc
</select> </select>
<select id="selectDbTableListByNames" resultMap="GenTableResult"> <select id="selectDbTableListByNames" resultMap="GenTableResult">

View File

@@ -78,7 +78,7 @@ public class ${ClassName}Controller extends BaseController
@GetMapping(value = "/{${pkColumn.javaField}}") @GetMapping(value = "/{${pkColumn.javaField}}")
public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField})
{ {
return AjaxResult.success(${className}Service.select${ClassName}ById(${pkColumn.javaField})); return AjaxResult.success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}));
} }
/** /**
@@ -111,6 +111,6 @@ public class ${ClassName}Controller extends BaseController
@DeleteMapping("/{${pkColumn.javaField}s}") @DeleteMapping("/{${pkColumn.javaField}s}")
public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s)
{ {
return toAjax(${className}Service.delete${ClassName}ByIds(${pkColumn.javaField}s)); return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s));
} }
} }

View File

@@ -17,10 +17,10 @@ public interface ${ClassName}Mapper
/** /**
* 查询${functionName} * 查询${functionName}
* *
* @param ${pkColumn.javaField} ${functionName}ID * @param ${pkColumn.javaField} ${functionName}主键
* @return ${functionName} * @return ${functionName}
*/ */
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
/** /**
* 查询${functionName}列表 * 查询${functionName}列表
@@ -49,24 +49,24 @@ public interface ${ClassName}Mapper
/** /**
* 删除${functionName} * 删除${functionName}
* *
* @param ${pkColumn.javaField} ${functionName}ID * @param ${pkColumn.javaField} ${functionName}主键
* @return 结果 * @return 结果
*/ */
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
/** /**
* 批量删除${functionName} * 批量删除${functionName}
* *
* @param ${pkColumn.javaField}s 需要删除的数据ID * @param ${pkColumn.javaField}s 需要删除的数据主键集合
* @return 结果 * @return 结果
*/ */
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s); public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
#if($table.sub) #if($table.sub)
/** /**
* 批量删除${subTable.functionName} * 批量删除${subTable.functionName}
* *
* @param customerIds 需要删除的数据ID * @param ${pkColumn.javaField}s 需要删除的数据主键集合
* @return 结果 * @return 结果
*/ */
public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
@@ -81,7 +81,7 @@ public interface ${ClassName}Mapper
/** /**
* 通过${functionName}ID删除${subTable.functionName}信息 * 通过${functionName}主键删除${subTable.functionName}信息
* *
* @param ${pkColumn.javaField} ${functionName}ID * @param ${pkColumn.javaField} ${functionName}ID
* @return 结果 * @return 结果

View File

@@ -14,10 +14,10 @@ public interface I${ClassName}Service
/** /**
* 查询${functionName} * 查询${functionName}
* *
* @param ${pkColumn.javaField} ${functionName}ID * @param ${pkColumn.javaField} ${functionName}主键
* @return ${functionName} * @return ${functionName}
*/ */
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
/** /**
* 查询${functionName}列表 * 查询${functionName}列表
@@ -46,16 +46,16 @@ public interface I${ClassName}Service
/** /**
* 批量删除${functionName} * 批量删除${functionName}
* *
* @param ${pkColumn.javaField}s 需要删除的${functionName}ID * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合
* @return 结果 * @return 结果
*/ */
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s); public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
/** /**
* 删除${functionName}信息 * 删除${functionName}信息
* *
* @param ${pkColumn.javaField} ${functionName}ID * @param ${pkColumn.javaField} ${functionName}主键
* @return 结果 * @return 结果
*/ */
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
} }

View File

@@ -34,13 +34,13 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service
/** /**
* 查询${functionName} * 查询${functionName}
* *
* @param ${pkColumn.javaField} ${functionName}ID * @param ${pkColumn.javaField} ${functionName}主键
* @return ${functionName} * @return ${functionName}
*/ */
@Override @Override
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField})
{ {
return ${className}Mapper.select${ClassName}ById(${pkColumn.javaField}); return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField});
} }
/** /**
@@ -108,34 +108,34 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service
/** /**
* 批量删除${functionName} * 批量删除${functionName}
* *
* @param ${pkColumn.javaField}s 需要删除的${functionName}ID * @param ${pkColumn.javaField}s 需要删除的${functionName}主键
* @return 结果 * @return 结果
*/ */
#if($table.sub) #if($table.sub)
@Transactional @Transactional
#end #end
@Override @Override
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s) public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s)
{ {
#if($table.sub) #if($table.sub)
${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s);
#end #end
return ${className}Mapper.delete${ClassName}ByIds(${pkColumn.javaField}s); return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s);
} }
/** /**
* 删除${functionName}信息 * 删除${functionName}信息
* *
* @param ${pkColumn.javaField} ${functionName}ID * @param ${pkColumn.javaField} ${functionName}主键
* @return 结果 * @return 结果
*/ */
@Override @Override
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField})
{ {
#if($table.sub) #if($table.sub)
${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField});
#end #end
return ${className}Mapper.delete${ClassName}ById(${pkColumn.javaField}); return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField});
} }
#if($table.sub) #if($table.sub)
@@ -147,7 +147,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service
public void insert${subClassName}(${ClassName} ${className}) public void insert${subClassName}(${ClassName} ${className})
{ {
List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List();
Long ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}();
if (StringUtils.isNotNull(${subclassName}List)) if (StringUtils.isNotNull(${subclassName}List))
{ {
List<${subClassName}> list = new ArrayList<${subClassName}>(); List<${subClassName}> list = new ArrayList<${subClassName}>();

View File

@@ -25,10 +25,10 @@
<el-form-item label="${comment}" prop="${column.javaField}"> <el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small"> <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
<el-option <el-option
v-for="dict in ${column.javaField}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
:label="dict.dictLabel" :label="dict.label"
:value="dict.dictValue" :value="dict.value"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -106,7 +106,11 @@
</template> </template>
</el-table-column> </el-table-column>
#elseif($column.list && "" != $column.dictType) #elseif($column.list && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}" :formatter="${javaField}Format" /> <el-table-column label="${comment}" align="center" prop="${javaField}">
<template slot-scope="scope">
<dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
</template>
</el-table-column>
#elseif($column.list && "" != $javaField) #elseif($column.list && "" != $javaField)
#if(${foreach.index} == 1) #if(${foreach.index} == 1)
<el-table-column label="${comment}" prop="${javaField}" /> <el-table-column label="${comment}" prop="${javaField}" />
@@ -180,10 +184,10 @@
<el-form-item label="${comment}" prop="${field}"> <el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option <el-option
v-for="dict in ${field}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
:label="dict.dictLabel" :label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.dictValue)"#else:value="dict.dictValue"#end #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
></el-option> ></el-option>
</el-select> </el-select>
@@ -198,10 +202,10 @@
<el-form-item label="${comment}"> <el-form-item label="${comment}">
<el-checkbox-group v-model="form.${field}"> <el-checkbox-group v-model="form.${field}">
<el-checkbox <el-checkbox
v-for="dict in ${field}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
:label="dict.dictValue"> :label="dict.value">
{{dict.dictLabel}} {{dict.label}}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
@@ -215,11 +219,11 @@
<el-form-item label="${comment}"> <el-form-item label="${comment}">
<el-radio-group v-model="form.${field}"> <el-radio-group v-model="form.${field}">
<el-radio <el-radio
v-for="dict in ${field}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.dictValue)"#else:label="dict.dictValue"#end #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
>{{dict.dictLabel}}</el-radio> >{{dict.label}}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
#elseif($column.htmlType == "radio" && $dictType) #elseif($column.htmlType == "radio" && $dictType)
@@ -258,46 +262,13 @@
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, export${BusinessName} } from "@/api/${moduleName}/${businessName}"; import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, export${BusinessName} } from "@/api/${moduleName}/${businessName}";
import Treeselect from "@riophae/vue-treeselect"; import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import "@riophae/vue-treeselect/dist/vue-treeselect.css";
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
import ImageUpload from '@/components/ImageUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
import FileUpload from '@/components/FileUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
import Editor from '@/components/Editor';
#break
#end
#end
export default { export default {
name: "${BusinessName}", name: "${BusinessName}",
#if(${dicts} != '')
dicts: [${dicts}],
#end
components: { components: {
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
ImageUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
FileUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
Editor,
#break
#end
#end
Treeselect Treeselect
}, },
data() { data() {
@@ -315,16 +286,7 @@ export default {
// 是否显示弹出层 // 是否显示弹出层
open: false, open: false,
#foreach ($column in $columns) #foreach ($column in $columns)
#set($parentheseIndex=$column.columnComment.indexOf("")) #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if(${column.dictType} != '')
// $comment字典
${column.javaField}Options: [],
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
// $comment时间范围 // $comment时间范围
daterange${AttrName}: [], daterange${AttrName}: [],
@@ -362,13 +324,6 @@ export default {
}, },
created() { created() {
this.getList(); this.getList();
#foreach ($column in $columns)
#if(${column.dictType} != '')
this.getDicts("${column.dictType}").then(response => {
this.${column.javaField}Options = response.data;
});
#end
#end
}, },
methods: { methods: {
/** 查询${functionName}列表 */ /** 查询${functionName}列表 */
@@ -414,20 +369,6 @@ export default {
this.${businessName}Options.push(data); this.${businessName}Options.push(data);
}); });
}, },
#foreach ($column in $columns)
#if(${column.dictType} != '')
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
// $comment字典翻译
${column.javaField}Format(row, column) {
return this.selectDictLabel#if($column.htmlType == "checkbox")s#end(this.${column.javaField}Options, row.${column.javaField});
},
#end
#end
// 取消按钮 // 取消按钮
cancel() { cancel() {
this.open = false; this.open = false;
@@ -507,13 +448,13 @@ export default {
#end #end
if (this.form.${pkColumn.javaField} != null) { if (this.form.${pkColumn.javaField} != null) {
update${BusinessName}(this.form).then(response => { update${BusinessName}(this.form).then(response => {
this.msgSuccess("修改成功"); this.#[[$modal]]#.msgSuccess("修改成功");
this.open = false; this.open = false;
this.getList(); this.getList();
}); });
} else { } else {
add${BusinessName}(this.form).then(response => { add${BusinessName}(this.form).then(response => {
this.msgSuccess("新增成功"); this.#[[$modal]]#.msgSuccess("新增成功");
this.open = false; this.open = false;
this.getList(); this.getList();
}); });
@@ -523,16 +464,12 @@ export default {
}, },
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
this.$confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?', "警告", { this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项').then(function() {
confirmButtonText: "确定", return del${BusinessName}(row.${pkColumn.javaField});
cancelButtonText: "取消", }).then(() => {
type: "warning" this.getList();
}).then(function() { this.#[[$modal]]#.msgSuccess("删除成功");
return del${BusinessName}(row.${pkColumn.javaField}); }).catch(() => {});
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
}).catch(() => {});
} }
} }
}; };

View File

@@ -25,10 +25,10 @@
<el-form-item label="${comment}" prop="${column.javaField}"> <el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small"> <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable size="small">
<el-option <el-option
v-for="dict in ${column.javaField}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
:label="dict.dictLabel" :label="dict.label"
:value="dict.dictValue" :value="dict.value"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -134,7 +134,11 @@
</template> </template>
</el-table-column> </el-table-column>
#elseif($column.list && "" != $column.dictType) #elseif($column.list && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}" :formatter="${javaField}Format" /> <el-table-column label="${comment}" align="center" prop="${javaField}">
<template slot-scope="scope">
<dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
</template>
</el-table-column>
#elseif($column.list && "" != $javaField) #elseif($column.list && "" != $javaField)
<el-table-column label="${comment}" align="center" prop="${javaField}" /> <el-table-column label="${comment}" align="center" prop="${javaField}" />
#end #end
@@ -201,10 +205,10 @@
<el-form-item label="${comment}" prop="${field}"> <el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option <el-option
v-for="dict in ${field}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
:label="dict.dictLabel" :label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.dictValue)"#else:value="dict.dictValue"#end #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
></el-option> ></el-option>
</el-select> </el-select>
@@ -219,10 +223,10 @@
<el-form-item label="${comment}"> <el-form-item label="${comment}">
<el-checkbox-group v-model="form.${field}"> <el-checkbox-group v-model="form.${field}">
<el-checkbox <el-checkbox
v-for="dict in ${field}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
:label="dict.dictValue"> :label="dict.value">
{{dict.dictLabel}} {{dict.label}}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
@@ -236,11 +240,11 @@
<el-form-item label="${comment}"> <el-form-item label="${comment}">
<el-radio-group v-model="form.${field}"> <el-radio-group v-model="form.${field}">
<el-radio <el-radio
v-for="dict in ${field}Options" v-for="dict in dict.type.${dictType}"
:key="dict.dictValue" :key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.dictValue)"#else:label="dict.dictValue"#end #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
>{{dict.dictLabel}}</el-radio> >{{dict.label}}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
#elseif($column.htmlType == "radio" && $dictType) #elseif($column.htmlType == "radio" && $dictType)
@@ -309,47 +313,12 @@
<script> <script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"; import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
import ImageUpload from '@/components/ImageUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
import FileUpload from '@/components/FileUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
import Editor from '@/components/Editor';
#break
#end
#end
export default { export default {
name: "${BusinessName}", name: "${BusinessName}",
components: { #if(${dicts} != '')
#foreach($column in $columns) dicts: [${dicts}],
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
ImageUpload,
#break
#end #end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
FileUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
Editor,
#break
#end
#end
},
data() { data() {
return { return {
// 遮罩层 // 遮罩层
@@ -379,16 +348,7 @@ export default {
// 是否显示弹出层 // 是否显示弹出层
open: false, open: false,
#foreach ($column in $columns) #foreach ($column in $columns)
#set($parentheseIndex=$column.columnComment.indexOf("")) #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if(${column.dictType} != '')
// $comment字典
${column.javaField}Options: [],
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
// $comment时间范围 // $comment时间范围
daterange${AttrName}: [], daterange${AttrName}: [],
@@ -428,13 +388,6 @@ export default {
}, },
created() { created() {
this.getList(); this.getList();
#foreach ($column in $columns)
#if(${column.dictType} != '')
this.getDicts("${column.dictType}").then(response => {
this.${column.javaField}Options = response.data;
});
#end
#end
}, },
methods: { methods: {
/** 查询${functionName}列表 */ /** 查询${functionName}列表 */
@@ -461,20 +414,6 @@ export default {
this.loading = false; this.loading = false;
}); });
}, },
#foreach ($column in $columns)
#if(${column.dictType} != '')
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
// $comment字典翻译
${column.javaField}Format(row, column) {
return this.selectDictLabel#if($column.htmlType == "checkbox")s#end(this.${column.javaField}Options, row.${column.javaField});
},
#end
#end
// 取消按钮 // 取消按钮
cancel() { cancel() {
this.open = false; this.open = false;
@@ -561,13 +500,13 @@ export default {
#end #end
if (this.form.${pkColumn.javaField} != null) { if (this.form.${pkColumn.javaField} != null) {
update${BusinessName}(this.form).then(response => { update${BusinessName}(this.form).then(response => {
this.msgSuccess("修改成功"); this.#[[$modal]]#.msgSuccess("修改成功");
this.open = false; this.open = false;
this.getList(); this.getList();
}); });
} else { } else {
add${BusinessName}(this.form).then(response => { add${BusinessName}(this.form).then(response => {
this.msgSuccess("新增成功"); this.#[[$modal]]#.msgSuccess("新增成功");
this.open = false; this.open = false;
this.getList(); this.getList();
}); });
@@ -578,16 +517,12 @@ export default {
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids; const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids;
this.$confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?', "警告", { this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项').then(function() {
confirmButtonText: "确定", return del${BusinessName}(${pkColumn.javaField}s);
cancelButtonText: "取消", }).then(() => {
type: "warning" this.getList();
}).then(function() { this.#[[$modal]]#.msgSuccess("删除成功");
return del${BusinessName}(${pkColumn.javaField}s); }).catch(() => {});
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
}).catch(() => {});
}, },
#if($table.sub) #if($table.sub)
/** ${subTable.functionName}序号 */ /** ${subTable.functionName}序号 */
@@ -608,19 +543,18 @@ export default {
/** ${subTable.functionName}删除按钮操作 */ /** ${subTable.functionName}删除按钮操作 */
handleDelete${subClassName}() { handleDelete${subClassName}() {
if (this.checked${subClassName}.length == 0) { if (this.checked${subClassName}.length == 0) {
this.$alert("请先选择要删除的${subTable.functionName}数据", "提示", { confirmButtonText: "确定", }); this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
} else { } else {
this.${subclassName}List.splice(this.checked${subClassName}[0].index - 1, 1); const ${subclassName}List = this.${subclassName}List;
const checked${subClassName} = this.checked${subClassName};
this.${subclassName}List = ${subclassName}List.filter(function(item) {
return checked${subClassName}.indexOf(item.index) == -1
});
} }
}, },
/** 选框选中数据 */ /** 选框选中数据 */
handle${subClassName}SelectionChange(selection) { handle${subClassName}SelectionChange(selection) {
if (selection.length > 1) { this.checked${subClassName} = selection.map(item => item.index)
this.$refs.${subclassName}.clearSelection();
this.$refs.${subclassName}.toggleRowSelection(selection.pop());
} else {
this.checked${subClassName} = selection;
}
}, },
#end #end
/** 导出按钮操作 */ /** 导出按钮操作 */

View File

@@ -58,7 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where> </where>
</select> </select>
<select id="select${ClassName}ById" parameterType="${pkColumn.javaType}" resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end"> <select id="select${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}" resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end">
#if($table.crud || $table.tree) #if($table.crud || $table.tree)
<include refid="select${ClassName}Vo"/> <include refid="select${ClassName}Vo"/>
where ${pkColumn.columnName} = #{${pkColumn.javaField}} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
@@ -102,11 +102,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where ${pkColumn.columnName} = #{${pkColumn.javaField}} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</update> </update>
<delete id="delete${ClassName}ById" parameterType="${pkColumn.javaType}"> <delete id="delete${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}">
delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</delete> </delete>
<delete id="delete${ClassName}ByIds" parameterType="String"> <delete id="delete${ClassName}By${pkColumn.capJavaField}s" parameterType="String">
delete from ${tableName} where ${pkColumn.columnName} in delete from ${tableName} where ${pkColumn.columnName} in
<foreach item="${pkColumn.javaField}" collection="array" open="(" separator="," close=")"> <foreach item="${pkColumn.javaField}" collection="array" open="(" separator="," close=")">
#{${pkColumn.javaField}} #{${pkColumn.javaField}}
@@ -121,7 +121,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</delete> </delete>
<delete id="delete${subClassName}By${subTableFkClassName}" parameterType="Long"> <delete id="delete${subClassName}By${subTableFkClassName}" parameterType="${pkColumn.javaType}">
delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}}
</delete> </delete>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -13,8 +13,10 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.exception.job.TaskException; import com.ruoyi.common.core.exception.job.TaskException;
import com.ruoyi.common.core.utils.SecurityUtils; import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.poi.ExcelUtil; import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController; import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.AjaxResult;
@@ -79,14 +81,26 @@ public class SysJobController extends BaseController
@PreAuthorize(hasPermi = "monitor:job:add") @PreAuthorize(hasPermi = "monitor:job:add")
@Log(title = "定时任务", businessType = BusinessType.INSERT) @Log(title = "定时任务", businessType = BusinessType.INSERT)
@PostMapping @PostMapping
public AjaxResult add(@RequestBody SysJob sysJob) throws SchedulerException, TaskException public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException
{ {
if (!CronUtils.isValid(sysJob.getCronExpression())) if (!CronUtils.isValid(job.getCronExpression()))
{ {
return AjaxResult.error("cron表达式不正确"); return error("新增任务'" + job.getJobName() + "'失败Cron表达式不正确");
} }
sysJob.setCreateBy(SecurityUtils.getUsername()); else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
return toAjax(jobService.insertJob(sysJob)); {
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用");
}
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_LDAP))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap://'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)//'调用");
}
job.setCreateBy(SecurityUtils.getUsername());
return toAjax(jobService.insertJob(job));
} }
/** /**
@@ -95,14 +109,26 @@ public class SysJobController extends BaseController
@PreAuthorize(hasPermi = "monitor:job:edit") @PreAuthorize(hasPermi = "monitor:job:edit")
@Log(title = "定时任务", businessType = BusinessType.UPDATE) @Log(title = "定时任务", businessType = BusinessType.UPDATE)
@PutMapping @PutMapping
public AjaxResult edit(@RequestBody SysJob sysJob) throws SchedulerException, TaskException public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException
{ {
if (!CronUtils.isValid(sysJob.getCronExpression())) if (!CronUtils.isValid(job.getCronExpression()))
{ {
return AjaxResult.error("cron表达式不正确"); return error("修改任务'" + job.getJobName() + "'失败Cron表达式不正确");
} }
sysJob.setUpdateBy(SecurityUtils.getUsername()); else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
return toAjax(jobService.updateJob(sysJob)); {
return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用");
}
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_LDAP))
{
return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap://'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)//'调用");
}
job.setUpdateBy(SecurityUtils.getUsername());
return toAjax(jobService.updateJob(job));
} }
/** /**

View File

@@ -74,7 +74,7 @@ public class SysJobServiceImpl implements ISysJobService
* @param job 调度信息 * @param job 调度信息
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public int pauseJob(SysJob job) throws SchedulerException public int pauseJob(SysJob job) throws SchedulerException
{ {
Long jobId = job.getJobId(); Long jobId = job.getJobId();
@@ -94,7 +94,7 @@ public class SysJobServiceImpl implements ISysJobService
* @param job 调度信息 * @param job 调度信息
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public int resumeJob(SysJob job) throws SchedulerException public int resumeJob(SysJob job) throws SchedulerException
{ {
Long jobId = job.getJobId(); Long jobId = job.getJobId();
@@ -114,7 +114,7 @@ public class SysJobServiceImpl implements ISysJobService
* @param job 调度信息 * @param job 调度信息
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public int deleteJob(SysJob job) throws SchedulerException public int deleteJob(SysJob job) throws SchedulerException
{ {
Long jobId = job.getJobId(); Long jobId = job.getJobId();
@@ -134,7 +134,7 @@ public class SysJobServiceImpl implements ISysJobService
* @return 结果 * @return 结果
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public void deleteJobByIds(Long[] jobIds) throws SchedulerException public void deleteJobByIds(Long[] jobIds) throws SchedulerException
{ {
for (Long jobId : jobIds) for (Long jobId : jobIds)
@@ -150,7 +150,7 @@ public class SysJobServiceImpl implements ISysJobService
* @param job 调度信息 * @param job 调度信息
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public int changeStatus(SysJob job) throws SchedulerException public int changeStatus(SysJob job) throws SchedulerException
{ {
int rows = 0; int rows = 0;
@@ -172,7 +172,7 @@ public class SysJobServiceImpl implements ISysJobService
* @param job 调度信息 * @param job 调度信息
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public void run(SysJob job) throws SchedulerException public void run(SysJob job) throws SchedulerException
{ {
Long jobId = job.getJobId(); Long jobId = job.getJobId();
@@ -190,7 +190,7 @@ public class SysJobServiceImpl implements ISysJobService
* @param job 调度信息 调度信息 * @param job 调度信息 调度信息
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public int insertJob(SysJob job) throws SchedulerException, TaskException public int insertJob(SysJob job) throws SchedulerException, TaskException
{ {
job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
@@ -208,7 +208,7 @@ public class SysJobServiceImpl implements ISysJobService
* @param job 调度信息 * @param job 调度信息
*/ */
@Override @Override
@Transactional @Transactional(rollbackFor = Exception.class)
public int updateJob(SysJob job) throws SchedulerException, TaskException public int updateJob(SysJob job) throws SchedulerException, TaskException
{ {
SysJob properties = selectJobById(job.getJobId()); SysJob properties = selectJobById(job.getJobId());

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

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