204 Commits

Author SHA1 Message Date
刘继东
ffa4957b75 Pre Merge pull request !276 from 刘继东/N/A 2025-03-05 10:15:13 +00:00
RuoYi
85ff6a9910 优化顶部菜单搜索栏为多层级显示 2025-03-05 18:14:13 +08:00
RuoYi
cab5beaca7 文件上传组件新增disabled属性&类型 2025-03-05 18:13:40 +08:00
RuoYi
fc8069a250 优化导出Excel日期格式双击离开后与设定的格式不一致问题 2025-03-05 18:13:12 +08:00
RuoYi
924e705dca 代码生成列表支持按时间排序 2025-03-05 18:12:52 +08:00
RuoYi
049ba453d1 优化空指针异常时无法获取错误信息问题 2025-03-05 18:10:56 +08:00
RuoYi
4aa261e8f7 优化定时任务字符包含多个括号导致数据错误 2025-03-05 18:10:12 +08:00
RuoYi
914a6620f5 升级tomcat到最新版本9.0.98 2025-03-05 18:08:29 +08:00
RuoYi
3dcee7057d 优化代码 2025-03-05 18:01:33 +08:00
RuoYi
0467631319 update ry_config.sql 2025-02-24 16:24:36 +08:00
若依
a11df90255 !393 config(nacos):增加nacos2.5.0配置文件sql
Merge pull request !393 from 牟雷/master
2025-02-18 00:33:39 +00:00
牟雷
596e4fe756 config(nacos):增加nacos2.5.0配置文件sql 2025-02-17 20:19:22 +08:00
RuoYi
37219e4ae6 copyright 2025 2025-01-07 10:55:58 +08:00
RuoYi
adaa3e1db8 代码生成新增配置是否允许文件覆盖到本地 2024-12-25 16:30:44 +08:00
RuoYi
88ad5a2c19 优化导入带标题文件关闭清理 2024-12-25 16:24:38 +08:00
RuoYi
b45dc2ec25 update sqlkeyword 2024-12-25 16:24:02 +08:00
RuoYi
67b17da06f 优化特殊字符密码修改失败问题 2024-12-17 14:28:17 +08:00
RuoYi
b25a280ebb 优化TopNav内链菜单点击没有高亮(IB8WHJ) 2024-12-17 11:57:05 +08:00
RuoYi
cc026e75a3 优化菜单管理切换Mini布局错乱问题 2024-12-17 11:25:03 +08:00
RuoYi
7216b56a56 用户管理过滤掉已禁用部门 2024-12-11 11:43:00 +08:00
RuoYi
a326e880a1 修改主题样式本地读取 2024-12-07 17:08:53 +08:00
RuoYi
92c6d21855 优化文件异常输入流未关闭的问题 2024-12-05 14:27:54 +08:00
RuoYi
2335157f6e 白名单支持对通配符路径匹配 2024-12-04 08:52:13 +08:00
RuoYi
6c3b01c3c5 Excel注解支持wrapText是否允许内容换行 2024-12-03 09:07:04 +08:00
RuoYi
8faea60191 修复导出子列表对象只能在最后的问题 2024-12-02 20:37:12 +08:00
RuoYi
28a16d9878 修复默认关闭Tags-Views时,内链页面打不开 2024-11-27 19:49:12 +08:00
RuoYi
dd3cf18e27 修复TopNav无法正确获取active的问题 2024-11-27 09:02:26 +08:00
RuoYi
65d03dc014 优化代码 2024-11-25 22:30:03 +08:00
RuoYi
7912fd81bd 面板兼容移动端显示 2024-11-25 15:41:00 +08:00
RuoYi
e2175e5b9d 参数键值更换为多行文本 2024-11-25 12:15:49 +08:00
RuoYi
c0e119f8e0 菜单面包屑导航支持多层级显示 2024-11-22 20:45:08 +08:00
RuoYi
fa77b2a08c 分栏参数微调 2024-11-22 14:46:59 +08:00
RuoYi
a222c24796 用户管理支持分栏拖动 2024-11-22 13:58:37 +08:00
RuoYi
08f4b877ce 优化代码 2024-11-20 11:13:24 +08:00
RuoYi
aa607d135c update .env.staging 2024-11-20 10:43:07 +08:00
RuoYi
6d34cdb8a3 若依 3.6.5 2024-11-13 08:38:24 +08:00
RuoYi
d47352253e 升级spring-cloud相关组件到最新版 2024-11-12 15:12:01 +08:00
RuoYi
61cbd470e1 update datascope 2024-11-12 15:10:47 +08:00
RuoYi
b3ef4adfed 支持自定义显示Excel属性列 2024-11-07 22:25:48 +08:00
RuoYi
856c471472 优化代码 2024-11-06 10:20:18 +08:00
RuoYi
bec5600f16 优化无用户编号不校验数据权限 2024-11-05 16:30:15 +08:00
RuoYi
5b485e7934 校检文件名是否包含特殊字符 2024-11-05 12:50:12 +08:00
RuoYi
73a752d3ab 优化身份证脱敏正则 2024-10-21 17:16:01 +08:00
RuoYi
1899a832b9 优化权限更新后同步缓存 2024-10-21 17:15:30 +08:00
RuoYi
e6796c0954 操作日志记录DELETE请求参数 2024-10-17 13:17:25 +08:00
RuoYi
4987289a98 升级quill到最新版本2.0.2 2024-10-15 16:21:20 +08:00
RuoYi
18409922a5 修改代码生成上级菜单字段类型 2024-09-27 16:23:36 +08:00
RuoYi
3dca02b306 修复角色禁用权限不失效问题 2024-09-21 12:08:41 +08:00
RuoYi
75f3275e15 修复角色禁用权限不失效问题 2024-09-21 12:01:48 +08:00
RuoYi
b2e4a7046b update ry_config 2024-09-02 20:21:30 +08:00
RuoYi
60618c1da9 优化提示 2024-09-02 20:21:15 +08:00
RuoYi
3b499b1344 使用SpringDoc代替Swagger 2024-08-30 11:26:57 +08:00
RuoYi
8984ecba86 记录用户登录IP地址和登录时间 2024-07-09 12:12:01 +08:00
RuoYi
0953a9c0b2 记录用户登录IP地址和登录时间 2024-07-09 12:06:40 +08:00
RuoYi
fcff9dfdea 修改时间范围日期格式 2024-07-08 16:57:25 +08:00
RuoYi
d1cb4e1f71 remove sub resultType 2024-07-08 16:57:04 +08:00
RuoYi
68ef1297cb avatar add headers 2024-07-02 16:10:58 +08:00
RuoYi
b79f01e051 升级axios到最新版本0.28.1 2024-07-02 12:58:10 +08:00
RuoYi
095f3a126f 菜单管理新增路由名称 2024-06-29 22:46:54 +08:00
RuoYi
ce94a9d620 升级core-js到最新版本3.37.1 2024-06-29 22:46:18 +08:00
RuoYi
2a73de34f0 优化代码 2024-06-29 22:45:51 +08:00
RuoYi
8694501a7e 升级druid到最新版本1.2.23 2024-06-25 13:55:12 +08:00
RuoYi
4dfc3d766e 优化代码 2024-06-25 13:53:58 +08:00
RuoYi
0680d1ed1f 优化数据权限代码 2024-06-05 12:48:37 +08:00
RuoYi
493dee03c8 优化代码生成主子表关联查询方式 2024-06-05 12:48:18 +08:00
RuoYi
3992b1e666 添加新群号:158753145 2024-05-29 15:18:57 +08:00
RuoYi
c855884ebd update sql 2024-05-29 15:18:36 +08:00
RuoYi
19c457ae5f 限制用户操作数据权限范围 2024-05-29 15:18:20 +08:00
若依
1a3751ab71 !368 未合理判断验证码失效
Merge pull request !368 from PowderSnow/N/A
2024-05-29 07:00:02 +00:00
PowderSnow
af8d62e5e2 update ruoyi-gateway/src/main/java/com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java.
目前报”验证码已失效“的场景是uuid为空,应通过判断captcha是否为null得出验证码是否已过期的结果。

Signed-off-by: PowderSnow <1109835296@qq.com>
2024-05-12 19:21:59 +00:00
RuoYi
cf03781bd7 升级spring-framework到安全版本,防止漏洞风险 2024-04-11 17:01:16 +08:00
RuoYi
fa2a28e877 新增数据脱敏过滤注解 2024-04-08 13:16:52 +08:00
RuoYi
e2f1b31735 若依 3.6.4 2024-04-01 09:28:45 +08:00
RuoYi
488aafb683 升级spring-boot-admin到最新版2.7.15 2024-03-29 11:08:23 +08:00
RuoYi
735273d69f Excel注解ColumnType类型新增文本 2024-03-22 16:44:18 +08:00
RuoYi
f2a587c2f9 更新compressionPlugin到6.1.2以兼容node18+ 2024-03-18 14:30:11 +08:00
RuoYi
4f7bcef44c 定时任务白名单配置范围缩小 2024-03-11 11:23:38 +08:00
RuoYi
393a9326c8 update copyright 2024 2024-03-11 10:48:30 +08:00
若依
33a52c8166 !360 update ruoyi-modules/ruoyi-job/src/main/resources/mapper/job/SysJobLogMapper.xml.
Merge pull request !360 from 刘继东/N/A
2024-03-04 01:48:41 +00:00
刘继东
b44e0e4de6 update ruoyi-modules/ruoyi-job/src/main/resources/mapper/job/SysJobLogMapper.xml.
任务运行日志列表增加按创建日期倒叙排序

Signed-off-by: 刘继东 <wwwliujidong@163.com>
2024-03-02 03:52:29 +00:00
RuoYi
3fa4901d57 用户密码新增非法字符验证 2024-03-01 21:55:38 +08:00
RuoYi
2dca6f0a12 优化匹配方式 2024-03-01 21:55:28 +08:00
RuoYi
78e61d89ba 添加新群号:179219821 2024-01-11 15:47:14 +08:00
RuoYi
4908b6230b update vue.config.js 2024-01-11 15:47:05 +08:00
RuoYi
f6d4f5c6e3 升级element-ui到最新版本2.15.14 2023-12-07 11:19:06 +08:00
RuoYi
1580c1e672 删除无用的代码 2023-12-07 11:18:32 +08:00
RuoYi
6ff45b2644 升级spring-cloud相关组件到最新版 2023-12-04 16:46:42 +08:00
RuoYi
ae77c82304 update ry_config.sql 2023-12-04 16:45:53 +08:00
RuoYi
41c2d6fc2a 显隐列组件支持复选框弹出类型 2023-12-01 11:20:51 +08:00
RuoYi
d0e9e07e9c 代码生成支持选择前端模板类型 2023-11-30 09:46:27 +08:00
RuoYi
210626b21f 修复头像上传成功图片不显示问题 2023-11-30 09:46:06 +08:00
RuoYi
85ab6236e7 优化代码 2023-11-30 09:45:07 +08:00
RuoYi
0037e65ca0 优化头像上传参数新增文件名称 2023-11-29 13:00:45 +08:00
RuoYi
adec3f0608 优化字典标签支持自定义分隔符 2023-11-29 12:59:55 +08:00
RuoYi
e7adf59b88 优化下载zip方法新增遮罩层 2023-11-29 12:59:42 +08:00
RuoYi
21a17945fa 优化代码 2023-11-29 12:59:22 +08:00
RuoYi
b7abcd8374 优化个人中心/基本资料修改时数据显示问题 2023-11-28 13:07:42 +08:00
RuoYi
2eeb642f07 优化白名单页面放行逻辑 2023-11-28 13:06:20 +08:00
RuoYi
4512af436d 防止高频率定时任务不执行问题 2023-11-28 13:06:06 +08:00
若依
6a8964f086 !347 update ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java.
Merge pull request !347 from 刚刚好/N/A
2023-11-28 05:04:55 +00:00
刚刚好
75b2eb067d update ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java.
提交错别字

Signed-off-by: 刚刚好 <380862139@qq.com>
2023-11-12 02:40:27 +00:00
RuoYi
2eaabefeac 修改权限字符匹配方式 2023-11-10 16:18:24 +08:00
RuoYi
c3ab80967c 修复五级路由缓存无效问题 2023-11-10 15:31:43 +08:00
RuoYi
892e3ac822 修复内链iframe没有传递参数问题 2023-11-10 11:19:31 +08:00
RuoYi
e2b4bb0420 修复外链带端口出现的异常 2023-11-07 11:41:50 +08:00
RuoYi
d6b4806e43 修复数据字典列表页重置后标签值无法输入问题 2023-11-01 10:01:47 +08:00
RuoYi
7df1b02476 优化数字金额大写转换精度丢失问题 2023-11-01 10:00:21 +08:00
RuoYi
6d9cbe218c 升级fastjson到最新版2.0.41 2023-10-21 15:16:38 +08:00
RuoYi
302299bbb0 登录不做数据重复提交验证 2023-10-21 14:32:50 +08:00
RuoYi
437301d5d9 去掉多余的参数 2023-10-21 14:32:43 +08:00
RuoYi
dc121ff4aa 富文本Editor组件检验图片格式 2023-10-02 12:46:35 +08:00
RuoYi
c4cda3f4ad 修复HeaderSearch组件跳转query参数丢失问题 2023-09-28 22:21:50 +08:00
RuoYi
e067c04464 操作日志列表新增IP地址查询 2023-09-27 15:27:29 +08:00
RuoYi
af03fefc83 全局数据存储用户编号 2023-09-27 15:27:07 +08:00
RuoYi
2d49f2a56e 优化菜单管理类型为按钮状态可选 2023-09-18 15:05:23 +08:00
RuoYi
2af351f898 修复自定义字典样式不生效的问题 2023-09-14 17:27:13 +08:00
wind
a709c8f06e update docker/copy.sh.
Signed-off-by: wind <zhangmrit@126.com>
2023-09-05 08:50:40 +00:00
RuoYi
b00775f988 删除无用的传参 2023-09-01 09:40:02 +08:00
RuoYi
48711178ec 优化TopNav菜单没有图标svg不显示 2023-08-31 10:21:59 +08:00
RuoYi
07bb636109 修改未登录访问需要登录的资源,在登录后重定向丢失请求参数问题 2023-08-31 10:21:08 +08:00
RuoYi
24e0fa863e 优化代码 2023-08-25 09:45:44 +08:00
RuoYi
fb6d93fbab 修复字典缓存删除方法参数错误问题 2023-08-23 14:56:07 +08:00
RuoYi
db18477786 修复Excels导入时无法获取到readConverterExp内容转表达式问题 2023-08-21 16:01:55 +08:00
RuoYi
91575613a8 防重复提交数据大小限制 2023-08-21 11:57:58 +08:00
RuoYi
a5354083b9 Excel导入数据临时文件无法删除问题 2023-08-19 16:24:04 +08:00
RuoYi
1665fa2793 升级fastjson到最新版2.0.39 2023-08-15 12:24:16 +08:00
RuoYi
df8d6bcd33 升级commons.io到最新版本2.13.0 2023-08-15 12:20:37 +08:00
若依
2fecc6273b !340 修改个人信息:防止用户有目的性的修改其他属性
Merge pull request !340 from 码上秃Hello/N/A
2023-08-15 04:17:49 +00:00
码上秃Hello
8b252709a4 修改个人信息:防止用户有目的性的修改其他属性
Signed-off-by: 码上秃Hello <19973252123@163.com>
2023-08-15 03:25:00 +00:00
RuoYi
b2cb085f21 Excel自定义数据处理器增加单元格/工作簿对象 2023-08-14 19:20:04 +08:00
RuoYi
1b1bc96bd5 优化定时任务状态页面显示 2023-08-14 19:19:55 +08:00
RuoYi
a416d55780 优化代码 2023-08-14 19:19:35 +08:00
RuoYi
1994aff544 添加新群号:128355254 2023-07-28 11:22:41 +08:00
RuoYi
b18eced03d 若依 3.6.3 2023-07-07 08:30:21 +08:00
RuoYi
ee2fef02f8 排序属性orderBy参数限制长度 2023-07-06 22:29:30 +08:00
RuoYi
ad954dacb7 屏蔽定时任务bean违规的字符 2023-07-06 22:29:09 +08:00
RuoYi
10de4eb96d 升级spring-cloud相关组件到最新版 2023-07-06 20:01:30 +08:00
RuoYi
a5b612c8e4 update sql 2023-07-06 20:00:45 +08:00
RuoYi
ebc848b5b5 优化页签在Firefox浏览器被遮挡的问题(I7BVAJ) 2023-07-06 09:27:48 +08:00
若依
f31c207551 !333 注释名称与参数名不一致
Merge pull request !333 from mxy/master
2023-07-06 01:26:09 +00:00
若依
dd338b56da !332 在全局异常拦截器中增加两类异常处理
Merge pull request !332 from OTTO/master
2023-07-06 01:26:05 +00:00
BearXuan
7c3d7b384c 注释名称与参数名不一致 2023-07-03 15:18:33 +08:00
otto
0647ca8b66 style(在全局异常拦截器中增加两类异常处理): 格式化代码 2023-07-03 11:52:23 +08:00
otto
f3477f75e6 Merge branch 'master' of gitee.com:OTTTTO/RuoYi-Cloud 2023-07-03 11:04:55 +08:00
otto
22fa7ac6f6 feat(在全局异常拦截器中增加两类异常处理): 1、请求路径中缺少必需的路径变量;2、请求参数类型不匹配; 2023-07-03 11:04:22 +08:00
otto
924aafae86 feat(在全局异常拦截器中增加两类异常处理): 1、请求路径中缺少必需的路径变量;2、请求参数类型不匹配 2023-07-03 11:02:12 +08:00
RuoYi
5280fb5a0d 升级element-ui到最新版本2.15.13 2023-06-30 14:05:22 +08:00
RuoYi
d3c6ba2598 升级fastjson到最新版2.0.34 2023-06-30 14:05:13 +08:00
RuoYi
08308d61c7 优化侧边栏的平台标题与VUE_APP_TITLE保持同步 2023-06-30 14:04:52 +08:00
RuoYi
356b451b2a optimized code 2023-06-30 14:03:52 +08:00
RuoYi
a606973cbe 恢复翻页/切换路由滚动功能 2023-04-23 16:59:22 +08:00
RuoYi
19fb27502b 修复代码生成表字段注释不全问题 2023-04-23 16:58:43 +08:00
RuoYi
d7b309afd9 修复路由跳转被阻止时vue-router内部产生报错信息问题 2023-04-23 16:58:19 +08:00
RuoYi
d9bfc3e322 DictTag组件,当value没有匹配的值时,展示value 2023-04-23 16:57:54 +08:00
RuoYi
74045776d6 优化文件输入流可能为空的问题 2023-04-18 16:14:08 +08:00
若依
5fcec99e6c !321 解决文件输入流为可能为空的问题
Merge pull request !321 from maochd/master
2023-04-18 08:07:22 +00:00
maochd
102a5ac575 简化允许下载的文件规则判断 2023-04-18 13:56:59 +08:00
maochd
3786f3671d 解决文件输入流为可能为空的问题 2023-04-18 13:55:07 +08:00
RuoYi
49e6fbf2ff 修复开启TopNav后一级菜单路由参数设置无效问题(I6T1DK) 2023-04-11 16:52:35 +08:00
RuoYi
81a8192de7 修复导入用户时无法更新存在用户数据的问题 2023-04-10 18:17:12 +08:00
RuoYi
a366d09659 优化已选择下拉图标高亮回显 2023-04-10 18:17:00 +08:00
RuoYi
2fe7341fa7 优化避免鼠标移出时无法隐藏滚动条的问题 2023-04-10 18:15:36 +08:00
RuoYi
a3615e5c6c 优化代码 2023-04-10 18:14:54 +08:00
RuoYi
68eac98acf 优化代码 2023-04-05 19:16:25 +08:00
RuoYi
d4bae9745c 修复tab栏”关闭其他“异常的问题 2023-04-05 19:03:18 +08:00
RuoYi
23ae0eb43b 修复tab栏”关闭其他“异常的问题 2023-04-05 19:02:44 +08:00
RuoYi
308d497d74 优化生成表字段comment过长问题 2023-04-05 19:00:27 +08:00
RuoYi
7af254ee51 delete vue-multiselect style 2023-04-05 18:59:31 +08:00
RuoYi
2ad13c9f72 优化固定头部页签滚动条被隐藏的问题(I6ORT1) 2023-04-05 16:19:47 +08:00
RuoYi
56d50d10e9 update docker 2023-04-05 16:04:07 +08:00
RuoYi
0b21d15f26 优化代码 2023-03-29 10:45:46 +08:00
若依
7421d7ccce !313 异步保存日志的时候,报错被吞 ,导致没跑进RemoteLogFallbackFactory中
Merge pull request !313 from Llorando/master
2023-03-29 02:44:23 +00:00
若依
84ba2dbd93 !310 完善ruoyi-file模块的upload接口在文件过大和文件名过长的情况下的返回值中的msg提示信息
Merge pull request !310 from pigwantacat/master
2023-03-29 02:39:08 +00:00
若依
55447c72e9 !306 注释 @EnableCustomSwagger2 注解后,项目启动失败
Merge pull request !306 from Gelis/master
2023-03-29 02:30:18 +00:00
若依
26c63520f2 !302 【轻量级 PR】 文件上传服务获取InputStream未关闭
Merge pull request !302 from 程序凡/master
2023-03-29 02:02:48 +00:00
Llorando
8b52b90670 异步保存日志的时候,报错被吞,导致没跑进RemoteLogFallbackFactory中 2023-03-21 17:24:33 +08:00
RuoYi
05ca78e82f 添加新群号:101038945 2023-03-18 10:51:46 +08:00
RuoYi
b155059d66 升级fastjson到最新版2.0.25 2023-03-18 10:45:57 +08:00
RuoYi
f8ad7ea3ce 支持自定义隐藏属性列过滤子对象 2023-03-18 10:45:29 +08:00
RuoYi
feff419641 关闭页签后存在其他页签时不应该跳转首页 2023-03-18 10:44:37 +08:00
RuoYi
33a0806cbe 优化弹窗后导航栏偏移的问题 2023-03-18 10:43:52 +08:00
RuoYi
230e2bb3f4 delete build style 2023-03-18 10:42:44 +08:00
RuoYi
24d9ccba40 修复页面切换时布局错乱的问题 2023-03-18 10:42:31 +08:00
RuoYi
dc24ed81b8 修复用户多角色数据权限可能出现权限抬升的情况 2023-03-18 10:41:33 +08:00
pigwantacat
504a4e8e66 完善ruoyi-file模块的upload接口在文件过大和文件名过长的情况下的返回值中的msg提示信息 2023-03-16 23:48:40 +08:00
RuoYi
775234dabe 优化修改密码日志存储明文问题(I6ESO9) 2023-03-05 13:07:22 +08:00
RuoYi
7fdd20c054 优化文件下载出现的异常(I6DLNU) 2023-02-28 15:02:20 +08:00
Gelis
1eeed86b65 修复注释 @EnableCustomSwagger2 后,项目启动失败
Signed-off-by: Gelis <806938079@qq.com>
2023-02-24 08:56:26 +00:00
RuoYi
7c9903e057 日志管理使用索引提升查询性能 2023-02-23 12:32:02 +08:00
若依
20dd6d37ea !305 修复新增参数时判断错误的问题
Merge pull request !305 from Rain/N/A
2023-02-22 02:54:42 +00:00
Rain
89a04423cd 修复新增参数管理时判断错误的问题
Signed-off-by: Rain <938448486@qq.com>
2023-02-22 02:53:40 +00:00
若依
c42b4cad02 !304 修复isMatchedIp的参数判断产生NullPointerException的问题
Merge pull request !304 from wangfeiyu/dev20230222
2023-02-22 02:23:46 +00:00
wangfeiyu
4b21a3bc48 在方法isMatchedIp的参数中,当filter为null而ip非null/空时,报filter为null异常。把判断条件由&&改为||解决此bug。 2023-02-22 08:54:10 +08:00
RuoYi
1ef82d75b1 移除apache/commons-fileupload依赖 2023-02-21 22:55:51 +08:00
RuoYi
c10a3b6dbb 升级druid到最新版本1.2.16 2023-02-21 22:50:54 +08:00
RuoYi
b93c9bfc5d 优化代码 2023-02-21 22:50:27 +08:00
RuoYi
cd5556d188 支持登录IP黑名单限制 2023-02-21 10:31:54 +08:00
RuoYi
1126e2234f 日志注解支持排除指定的请求参数 2023-02-21 09:14:37 +08:00
CQG
dce91a03ef 文件上传服务获取InputStream未关闭,导致删除临时文件删除失败 2023-02-17 14:19:16 +08:00
RuoYi
8dff14a6cc 操作日志新增消耗时间属性 2023-02-16 11:10:00 +08:00
RuoYi
3462c58ce4 升级fastjson到最新版2.0.23 2023-02-16 11:07:41 +08:00
RuoYi
6c3e88ee6d 首页页签右键选择时不显示关闭左侧 2023-02-04 22:45:01 +08:00
RuoYi
da2e5a2d93 update copyright 2023 2023-02-04 22:44:29 +08:00
刘继东
91b661c0d5 refactor:字典组件忽略大小写
Signed-off-by: 刘继东 <wwwliujidong@163.com>
2022-10-28 05:01:11 +00:00
223 changed files with 3889 additions and 3199 deletions

View File

@@ -1,11 +1,11 @@
<p align="center"> <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-b99b286755aef70355a7084753f89cdb7c9.png"> <img alt="logo" src="https://oscimg.oschina.net/oscnet/up-b99b286755aef70355a7084753f89cdb7c9.png">
</p> </p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.6.2</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.6.5</h1>
<h4 align="center">基于 Vue/Element UI 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构</h4> <h4 align="center">基于 Vue/Element UI 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构</h4>
<p align="center"> <p align="center">
<a href="https://gitee.com/y_project/RuoYi-Cloud/stargazers"><img src="https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=dark"></a> <a href="https://gitee.com/y_project/RuoYi-Cloud/stargazers"><img src="https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud"><img src="https://img.shields.io/badge/RuoYi-v3.6.2-brightgreen.svg"></a> <a href="https://gitee.com/y_project/RuoYi-Cloud"><img src="https://img.shields.io/badge/RuoYi-v3.6.5-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a> <a href="https://gitee.com/y_project/RuoYi-Cloud/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p> </p>
@@ -17,12 +17,9 @@
* 后端采用Spring Boot、Spring Cloud & Alibaba。 * 后端采用Spring Boot、Spring Cloud & Alibaba。
* 注册中心、配置中心选型Nacos权限认证使用Redis。 * 注册中心、配置中心选型Nacos权限认证使用Redis。
* 流量控制框架选型Sentinel分布式事务选型Seata。 * 流量控制框架选型Sentinel分布式事务选型Seata。
* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Cloud-Vue3](https://github.com/yangzongzhuan/RuoYi-Cloud-Vue3),保持同步更新。 * 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Cloud-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Cloud-Vue3),保持同步更新。
* 如需不分离应用,请移步 [RuoYi](https://gitee.com/y_project/RuoYi),如需分离应用,请移步 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) * 如需不分离应用,请移步 [RuoYi](https://gitee.com/y_project/RuoYi),如需分离应用,请移步 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)
* 阿里云折扣场[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp; * 阿里云优惠券[点我进入](http://aly.ruoyi.vip),腾讯云优惠券[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)&nbsp;&nbsp;
#### 友情链接 [若依/RuoYi-Cloud](https://gitee.com/zhangmrit/ruoyi-cloud) Ant Design版本。
## 系统模块 ## 系统模块
@@ -41,6 +38,7 @@ com.ruoyi
│ └── ruoyi-common-redis // 缓存服务 │ └── ruoyi-common-redis // 缓存服务
│ └── ruoyi-common-seata // 分布式事务 │ └── ruoyi-common-seata // 分布式事务
│ └── ruoyi-common-security // 安全模块 │ └── ruoyi-common-security // 安全模块
│ └── ruoyi-common-sensitive // 数据脱敏
│ └── ruoyi-common-swagger // 系统接口 │ └── ruoyi-common-swagger // 系统接口
├── ruoyi-modules // 业务模块 ├── ruoyi-modules // 业务模块
│ └── ruoyi-system // 系统模块 [9201] │ └── ruoyi-system // 系统模块 [9201]
@@ -128,4 +126,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群](https://img.shields.io/badge/已满-201705537-blue.svg)](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) [![加入QQ群](https://img.shields.io/badge/已满-236543183-blue.svg)](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [![加入QQ群](https://img.shields.io/badge/已满-213618602-blue.svg)](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [![加入QQ群](https://img.shields.io/badge/已满-148794840-blue.svg)](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [![加入QQ群](https://img.shields.io/badge/118752664-blue.svg)](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) 点击按钮入群。 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) [![加入QQ群](https://img.shields.io/badge/已满-236543183-blue.svg)](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [![加入QQ群](https://img.shields.io/badge/已满-213618602-blue.svg)](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [![加入QQ群](https://img.shields.io/badge/已满-148794840-blue.svg)](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [![加入QQ群](https://img.shields.io/badge/已满-118752664-blue.svg)](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) [![加入QQ群](https://img.shields.io/badge/已满-101038945-blue.svg)](https://jq.qq.com/?_wv=1027&k=FqImHgH2) [![加入QQ群](https://img.shields.io/badge/已满-128355254-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G4jZ4EtdT50PhnMBudTnEwgonxkXOscJ&authKey=FkGHYfoTKlGE6wHdKdjH9bVoOgQjtLP9WM%2Fj7pqGY1msoqw9uxDiBo39E2mLgzYg&noverify=0&group_code=128355254) [![加入QQ群](https://img.shields.io/badge/已满-179219821-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=irnwcXhbLOQEv1g-TwGifjNTA_f4wZiA&authKey=4bpzEwhcUY%2FvsPDHvzYn6xfoS%2FtOArvZ%2BGXzfr7O0%2FEqLfkKA%2BuCDXlzHIFg8t93&noverify=0&group_code=179219821) [![加入QQ群](https://img.shields.io/badge/158753145-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=lx1uEdEDuxeM7rUvF3qmlFdqKqdJ5Z-R&authKey=rgyPW9yhhh4IIURKVFa6NgP3qiqH04WAzrJ0trsgkr3pjzm6sKIOGyA58oOjoj%2FJ&noverify=0&group_code=158753145) 点击按钮入群。

View File

@@ -9,8 +9,8 @@ usage() {
# copy sql # copy sql
echo "begin copy sql " echo "begin copy sql "
cp ../sql/ry_20220814.sql ./mysql/db cp ../sql/ry_20240629.sql ./mysql/db
cp ../sql/ry_config_20220510.sql ./mysql/db cp ../sql/ry_config_20250224.sql ./mysql/db
# copy html # copy html
echo "begin copy html " echo "begin copy html "

View File

@@ -14,23 +14,28 @@ http {
listen 80; listen 80;
server_name localhost; server_name localhost;
location / { location / {
root /home/ruoyi/projects/ruoyi-ui; root /home/ruoyi/projects/ruoyi-ui;
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
index index.html index.htm; index index.html index.htm;
} }
location /prod-api/{ location /prod-api/{
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ruoyi-gateway:8080/; proxy_pass http://ruoyi-gateway:8080/;
} }
# 避免actuator暴露
if ($request_uri ~ "/actuator") {
return 403;
}
error_page 500 502 503 504 /50x.html; error_page 500 502 503 504 /50x.html;
location = /50x.html { location = /50x.html {
root html; root html;
} }
} }
}# requirepass 123456 }

101
pom.xml
View File

@@ -6,42 +6,54 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.2</version> <version>3.6.5</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.6.2</ruoyi.version> <ruoyi.version>3.6.5</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.7.7</spring-boot.version> <spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>2021.0.5</spring-cloud.version> <spring-cloud.version>2021.0.9</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version> <spring-cloud-alibaba.version>2021.0.6.1</spring-cloud-alibaba.version>
<spring-boot-admin.version>2.7.10</spring-boot-admin.version> <spring-boot-admin.version>2.7.16</spring-boot-admin.version>
<swagger.fox.version>3.0.0</swagger.fox.version>
<swagger.core.version>1.6.2</swagger.core.version>
<tobato.version>1.27.2</tobato.version> <tobato.version>1.27.2</tobato.version>
<kaptcha.version>2.3.3</kaptcha.version> <kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>1.4.6</pagehelper.boot.version> <pagehelper.boot.version>2.0.0</pagehelper.boot.version>
<druid.version>1.2.15</druid.version> <druid.version>1.2.23</druid.version>
<dynamic-ds.version>3.5.2</dynamic-ds.version> <dynamic-ds.version>4.3.1</dynamic-ds.version>
<commons.io.version>2.11.0</commons.io.version> <commons.io.version>2.13.0</commons.io.version>
<commons.fileupload.version>1.4</commons.fileupload.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<fastjson.version>2.0.22</fastjson.version> <fastjson.version>2.0.53</fastjson.version>
<jjwt.version>0.9.1</jjwt.version> <jjwt.version>0.9.1</jjwt.version>
<minio.version>8.2.2</minio.version> <minio.version>8.2.2</minio.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<transmittable-thread-local.version>2.14.2</transmittable-thread-local.version> <springdoc.version>1.6.9</springdoc.version>
<transmittable-thread-local.version>2.14.4</transmittable-thread-local.version>
<!-- override dependency version -->
<tomcat.version>9.0.98</tomcat.version>
<logback.version>1.2.13</logback.version>
<spring-framework.version>5.3.39</spring-framework.version>
</properties> </properties>
<!-- 依赖声明 --> <!-- 依赖声明 -->
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<!-- 覆盖SpringFramework的依赖配置-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring-framework.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringCloud 微服务 --> <!-- SpringCloud 微服务 -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
@@ -69,6 +81,38 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- 覆盖logback的依赖配置-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- 覆盖tomcat的依赖配置-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- FastDFS 分布式文件系统 --> <!-- FastDFS 分布式文件系统 -->
<dependency> <dependency>
<groupId>com.github.tobato</groupId> <groupId>com.github.tobato</groupId>
@@ -76,16 +120,11 @@
<version>${tobato.version}</version> <version>${tobato.version}</version>
</dependency> </dependency>
<!-- Swagger 依赖配置 --> <!-- Springdoc webmvc 依赖配置 -->
<dependency> <dependency>
<groupId>io.swagger</groupId> <groupId>org.springdoc</groupId>
<artifactId>swagger-models</artifactId> <artifactId>springdoc-openapi-ui</artifactId>
<version>${swagger.core.version}</version> <version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.core.version}</version>
</dependency> </dependency>
<!-- 验证码 --> <!-- 验证码 -->
@@ -116,13 +155,6 @@
<version>${poi.version}</version> <version>${poi.version}</version>
</dependency> </dependency>
<!-- 文件上传工具类 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons.fileupload.version}</version>
</dependency>
<!-- 代码生成使用模板 --> <!-- 代码生成使用模板 -->
<dependency> <dependency>
<groupId>org.apache.velocity</groupId> <groupId>org.apache.velocity</groupId>
@@ -172,6 +204,13 @@
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 数据脱敏 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-sensitive</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 权限范围 --> <!-- 权限范围 -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.2</version> <version>3.6.5</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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -27,7 +27,7 @@ public interface RemoteLogService
* @return 结果 * @return 结果
*/ */
@PostMapping("/operlog") @PostMapping("/operlog")
public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source) throws Exception;
/** /**
* 保存访问记录 * 保存访问记录

View File

@@ -4,6 +4,7 @@ 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.PostMapping;
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.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.constant.SecurityConstants;
@@ -40,4 +41,14 @@ public interface RemoteUserService
*/ */
@PostMapping("/user/register") @PostMapping("/user/register")
public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 记录用户登录IP地址和登录时间
*
* @param sysUser 用户信息
* @param source 请求来源
* @return 结果
*/
@PutMapping("/user/recordlogin")
public R<Boolean> recordUserLogin(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
} }

View File

@@ -79,6 +79,10 @@ public class SysOperLog extends BaseEntity
@Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date operTime; private Date operTime;
/** 消耗时间 */
@Excel(name = "消耗时间", suffix = "毫秒")
private Long costTime;
public Long getOperId() public Long getOperId()
{ {
return operId; return operId;
@@ -238,4 +242,14 @@ public class SysOperLog extends BaseEntity
{ {
this.operTime = operTime; this.operTime = operTime;
} }
public Long getCostTime()
{
return costTime;
}
public void setCostTime(Long costTime)
{
this.costTime = costTime;
}
} }

View File

@@ -22,7 +22,7 @@ public class SysUser extends BaseEntity
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 用户ID */ /** 用户ID */
@Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") @Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号")
private Long userId; private Long userId;
/** 部门ID */ /** 部门ID */
@@ -42,7 +42,7 @@ public class SysUser extends BaseEntity
private String email; private String email;
/** 手机号码 */ /** 手机号码 */
@Excel(name = "手机号码") @Excel(name = "手机号码", cellType = ColumnType.TEXT)
private String phonenumber; private String phonenumber;
/** 用户性别 */ /** 用户性别 */

View File

@@ -28,13 +28,13 @@ public class RemoteLogFallbackFactory implements FallbackFactory<RemoteLogServic
@Override @Override
public R<Boolean> saveLog(SysOperLog sysOperLog, String source) public R<Boolean> saveLog(SysOperLog sysOperLog, String source)
{ {
return null; return R.fail("保存操作日志失败:" + throwable.getMessage());
} }
@Override @Override
public R<Boolean> saveLogininfor(SysLogininfor sysLogininfor, String source) public R<Boolean> saveLogininfor(SysLogininfor sysLogininfor, String source)
{ {
return null; return R.fail("保存登录日志失败:" + throwable.getMessage());
} }
}; };

View File

@@ -36,6 +36,12 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
{ {
return R.fail("注册用户失败:" + throwable.getMessage()); return R.fail("注册用户失败:" + throwable.getMessage());
} }
@Override
public R<Boolean> recordUserLogin(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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -2,13 +2,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.CacheConstants;
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.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.ServiceException; import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.DateUtils;
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.common.redis.service.RedisService;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.RemoteUserService; import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.domain.SysUser;
@@ -31,6 +36,9 @@ public class SysLoginService
@Autowired @Autowired
private SysRecordLogService recordLogService; private SysRecordLogService recordLogService;
@Autowired
private RedisService redisService;
/** /**
* 登录 * 登录
*/ */
@@ -56,20 +64,21 @@ public class SysLoginService
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围"); recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new ServiceException("用户名不在指定范围"); throw new ServiceException("用户名不在指定范围");
} }
// IP黑名单校验
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾访问IP已被列入系统黑名单");
throw new ServiceException("很遗憾访问IP已被列入系统黑名单");
}
// 查询用户信息 // 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new ServiceException("登录用户:" + username + " 不存在");
}
if (R.FAIL == userResult.getCode()) if (R.FAIL == userResult.getCode())
{ {
throw new ServiceException(userResult.getMsg()); throw new ServiceException(userResult.getMsg());
} }
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()))
@@ -84,9 +93,26 @@ public class SysLoginService
} }
passwordService.validate(user, password); passwordService.validate(user, password);
recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功"); recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
recordLoginInfo(user.getUserId());
return userInfo; return userInfo;
} }
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId)
{
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
// 更新用户登录IP
sysUser.setLoginIp(IpUtils.getIpAddr());
// 更新用户登录时间
sysUser.setLoginDate(DateUtils.getNowDate());
remoteUserService.recordUserLogin(sysUser, SecurityConstants.INNER);
}
public void logout(String loginName) public void logout(String loginName)
{ {
recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功"); recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功");

View File

@@ -4,7 +4,6 @@ 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.SecurityConstants;
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.common.core.utils.ip.IpUtils;
import com.ruoyi.system.api.RemoteLogService; import com.ruoyi.system.api.RemoteLogService;
@@ -33,7 +32,7 @@ public class SysRecordLogService
{ {
SysLogininfor logininfor = new SysLogininfor(); SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username); logininfor.setUserName(username);
logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); logininfor.setIpaddr(IpUtils.getIpAddr());
logininfor.setMsg(message); logininfor.setMsg(message);
// 日志状态 // 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,7 @@
<module>ruoyi-common-seata</module> <module>ruoyi-common-seata</module>
<module>ruoyi-common-swagger</module> <module>ruoyi-common-swagger</module>
<module>ruoyi-common-security</module> <module>ruoyi-common-security</module>
<module>ruoyi-common-sensitive</module>
<module>ruoyi-common-datascope</module> <module>ruoyi-common-datascope</module>
<module>ruoyi-common-datasource</module> <module>ruoyi-common-datasource</module>
</modules> </modules>

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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -95,12 +95,6 @@
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
</dependency> </dependency>
<!-- Commons Fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!-- excel工具 --> <!-- excel工具 -->
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
@@ -113,12 +107,6 @@
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
</dependency> </dependency>
<!-- Swagger -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -54,12 +54,12 @@ public @interface Excel
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/** /**
* 导出时在excel中每个列的高度 单位为字符 * 导出时在excel中每个列的高度
*/ */
public double height() default 14; public double height() default 14;
/** /**
* 导出时在excel中每个列的宽 单位为字符 * 导出时在excel中每个列的宽
*/ */
public double width() default 16; public double width() default 16;
@@ -78,6 +78,11 @@ public @interface Excel
*/ */
public String prompt() default ""; public String prompt() default "";
/**
* 是否允许内容换行
*/
public boolean wrapText() default false;
/** /**
* 设置只能选择不能输入的列内容. * 设置只能选择不能输入的列内容.
*/ */
@@ -109,7 +114,7 @@ public @interface Excel
public ColumnType cellType() default ColumnType.STRING; public ColumnType cellType() default ColumnType.STRING;
/** /**
* 导出列头背景色 * 导出列头背景
*/ */
public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
@@ -119,7 +124,7 @@ public @interface Excel
public IndexedColors headerColor() default IndexedColors.WHITE; public IndexedColors headerColor() default IndexedColors.WHITE;
/** /**
* 导出单元格背景色 * 导出单元格背景
*/ */
public IndexedColors backgroundColor() default IndexedColors.WHITE; public IndexedColors backgroundColor() default IndexedColors.WHITE;
@@ -166,7 +171,7 @@ public @interface Excel
public enum ColumnType public enum ColumnType
{ {
NUMERIC(0), STRING(1), IMAGE(2); NUMERIC(0), STRING(1), IMAGE(2), TEXT(3);
private final int value; private final int value;
ColumnType(int value) ColumnType(int value)

View File

@@ -51,4 +51,9 @@ public class CacheConstants
* 登录账户密码错误次数 redis key * 登录账户密码错误次数 redis key
*/ */
public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
/**
* 登录IP黑名单 cache key
*/
public static final String SYS_LOGIN_BLACKIPLIST = SYS_CONFIG_KEY + "sys.login.blackIPList";
} }

View File

@@ -117,10 +117,15 @@ public class Constants
*/ */
public static final String RESOURCE_PREFIX = "/profile"; public static final String RESOURCE_PREFIX = "/profile";
/**
* 自动识别json对象白名单配置仅允许解析的包名范围越小越安全
*/
public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
/** /**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
*/ */
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" }; public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.job.task" };
/** /**
* 定时任务违规的字符 * 定时任务违规的字符

View File

@@ -42,7 +42,7 @@ public class GenConstants
/** 数据库数字类型 */ /** 数据库数字类型 */
public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
"bigint", "float", "double", "decimal" }; "bit", "bigint", "float", "double", "decimal" };
/** 页面不需要编辑字段 */ /** 页面不需要编辑字段 */
public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };

View File

@@ -20,7 +20,7 @@ public class SecurityConstants
/** /**
* 授权信息字段 * 授权信息字段
*/ */
public static final String AUTHORIZATION_HEADER = "authorization"; public static final String AUTHORIZATION_HEADER = "Authorization";
/** /**
* 请求来源 * 请求来源

View File

@@ -7,11 +7,6 @@ package com.ruoyi.common.core.constant;
*/ */
public class TokenConstants public class TokenConstants
{ {
/**
* 令牌自定义标识
*/
public static final String AUTHENTICATION = "Authorization";
/** /**
* 令牌前缀 * 令牌前缀
*/ */

View File

@@ -21,6 +21,9 @@ public class UserConstants
/** 用户封禁状态 */ /** 用户封禁状态 */
public static final String USER_DISABLE = "1"; public static final String USER_DISABLE = "1";
/** 角色正常状态 */
public static final String ROLE_NORMAL = "0";
/** 角色封禁状态 */ /** 角色封禁状态 */
public static final String ROLE_DISABLE = "1"; public static final String ROLE_DISABLE = "1";
@@ -60,10 +63,9 @@ public class UserConstants
/** InnerLink组件标识 */ /** InnerLink组件标识 */
public final static String INNER_LINK = "InnerLink"; public final static String INNER_LINK = "InnerLink";
/** 校验返回结果码 */ /** 校验是否唯一的返回标识 */
public final static String UNIQUE = "0"; public final static boolean UNIQUE = true;
public final static boolean NOT_UNIQUE = false;
public final static String NOT_UNIQUE = "1";
/** /**
* 用户名长度限制 * 用户名长度限制

View File

@@ -11,9 +11,9 @@ public class FileException extends BaseException
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public FileException(String code, Object[] args) public FileException(String code, Object[] args, String msg)
{ {
super("file", code, args, null); super("file", code, args, msg);
} }
} }

View File

@@ -11,6 +11,6 @@ public class FileNameLengthLimitExceededException extends FileException
public FileNameLengthLimitExceededException(int defaultFileNameLength) public FileNameLengthLimitExceededException(int defaultFileNameLength)
{ {
super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }, "the filename is too long");
} }
} }

View File

@@ -11,6 +11,6 @@ public class FileSizeLimitExceededException extends FileException
public FileSizeLimitExceededException(long defaultMaxSize) public FileSizeLimitExceededException(long defaultMaxSize)
{ {
super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); super("upload.exceed.maxSize", new Object[] { defaultMaxSize }, "the filesize is too large");
} }
} }

View File

@@ -0,0 +1,61 @@
package com.ruoyi.common.core.exception.file;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* 文件上传异常类
*
* @author ruoyi
*/
public class FileUploadException extends Exception
{
private static final long serialVersionUID = 1L;
private final Throwable cause;
public FileUploadException()
{
this(null, null);
}
public FileUploadException(final String msg)
{
this(msg, null);
}
public FileUploadException(String msg, Throwable cause)
{
super(msg);
this.cause = cause;
}
@Override
public void printStackTrace(PrintStream stream)
{
super.printStackTrace(stream);
if (cause != null)
{
stream.println("Caused by:");
cause.printStackTrace(stream);
}
}
@Override
public void printStackTrace(PrintWriter writer)
{
super.printStackTrace(writer);
if (cause != null)
{
writer.println("Caused by:");
cause.printStackTrace(writer);
}
}
@Override
public Throwable getCause()
{
return cause;
}
}

View File

@@ -1,7 +1,6 @@
package com.ruoyi.common.core.exception.file; package com.ruoyi.common.core.exception.file;
import java.util.Arrays; import java.util.Arrays;
import org.apache.commons.fileupload.FileUploadException;
/** /**
* 文件上传 误异常类 * 文件上传 误异常类

View File

@@ -2,6 +2,7 @@ package com.ruoyi.common.core.text;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.NumberFormat; import java.text.NumberFormat;
@@ -363,6 +364,10 @@ public class Convert
*/ */
public static String[] toStrArray(String str) public static String[] toStrArray(String str)
{ {
if (StringUtils.isEmpty(str))
{
return new String[] {};
}
return toStrArray(",", str); return toStrArray(",", str);
} }
@@ -535,9 +540,9 @@ public class Convert
/** /**
* 转换为boolean<br> * 转换为boolean<br>
* String支持的值为true、false、yes、ok、no1,0 如果给定的值为空,或者转换失败,返回默认值<br> * String支持的值为true、false、yes、ok、no、1、0、是、否, 如果给定的值为空,或者转换失败,返回默认值<br>
* 转换失败不会报错 * 转换失败不会报错
* *
* @param value 被转换的值 * @param value 被转换的值
* @param defaultValue 转换错误时的默认值 * @param defaultValue 转换错误时的默认值
* @return 结果 * @return 结果
@@ -564,10 +569,12 @@ public class Convert
case "yes": case "yes":
case "ok": case "ok":
case "1": case "1":
case "":
return true; return true;
case "false": case "false":
case "no": case "no":
case "0": case "0":
case "":
return false; return false;
default: default:
return defaultValue; return defaultValue;
@@ -983,7 +990,12 @@ public class Convert
String s = ""; String s = "";
for (int i = 0; i < fraction.length; i++) for (int i = 0; i < fraction.length; i++)
{ {
s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); // 优化double计算精度丢失问题
BigDecimal nNum = new BigDecimal(n);
BigDecimal decimal = new BigDecimal(10);
BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN);
double d = scale.doubleValue();
s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
} }
if (s.length() < 1) if (s.length() < 1)
{ {

View File

@@ -137,16 +137,20 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
} }
/** /**
* 计算两个时间差 * 计算时间差
*
* @param endDate 最后时间
* @param startTime 开始时间
* @return 时间差(天/小时/分钟)
*/ */
public static String getDatePoor(Date endDate, Date nowDate) public static String timeDistance(Date endDate, Date startTime)
{ {
long nd = 1000 * 24 * 60 * 60; long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60; long nh = 1000 * 60 * 60;
long nm = 1000 * 60; long nm = 1000 * 60;
// long ns = 1000; // long ns = 1000;
// 获得两个时间的毫秒时间差异 // 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime(); long diff = endDate.getTime() - startTime.getTime();
// 计算差多少天 // 计算差多少天
long day = diff / nd; long day = diff / nd;
// 计算差多少小时 // 计算差多少小时

View File

@@ -20,6 +20,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/** 下划线 */ /** 下划线 */
private static final char SEPARATOR = '_'; private static final char SEPARATOR = '_';
/** 星号 */
private static final char ASTERISK = '*';
/** /**
* 获取参数不为空值 * 获取参数不为空值
* *
@@ -160,6 +163,49 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return (str == null ? "" : str.trim()); return (str == null ? "" : str.trim());
} }
/**
* 替换指定字符串的指定区间内字符为"*"
*
* @param str 字符串
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @return 替换后的字符串
*/
public static String hide(CharSequence str, int startInclude, int endExclude)
{
if (isEmpty(str))
{
return NULLSTR;
}
final int strLength = str.length();
if (startInclude > strLength)
{
return NULLSTR;
}
if (endExclude > strLength)
{
endExclude = strLength;
}
if (startInclude > endExclude)
{
// 如果起始位置大于结束位置,不替换
return NULLSTR;
}
final char[] chars = new char[strLength];
for (int i = 0; i < strLength; i++)
{
if (i >= startInclude && i < endExclude)
{
chars[i] = ASTERISK;
}
else
{
chars[i] = str.charAt(i);
}
}
return new String(chars);
}
/** /**
* 截取字符串 * 截取字符串
* *
@@ -237,6 +283,32 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return str.substring(start, end); return str.substring(start, end);
} }
/**
* 在字符串中查找第一个出现的 `open` 和最后一个出现的 `close` 之间的子字符串
*
* @param str 要截取的字符串
* @param open 起始字符串
* @param close 结束字符串
* @return 截取结果
*/
public static String substringBetweenLast(final String str, final String open, final String close)
{
if (isEmpty(str) || isEmpty(open) || isEmpty(close))
{
return NULLSTR;
}
final int start = str.indexOf(open);
if (start != INDEX_NOT_FOUND)
{
final int end = str.lastIndexOf(close);
if (end != INDEX_NOT_FOUND)
{
return str.substring(start + open.length(), end);
}
}
return NULLSTR;
}
/** /**
* 判断是否为空,并且不是空白字符 * 判断是否为空,并且不是空白字符
* *
@@ -295,9 +367,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
} }
/** /**
* 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
* *
* @param set 给定的集合 * @param collection 给定的集合
* @param array 给定的数组 * @param array 给定的数组
* @return boolean 结果 * @return boolean 结果
*/ */

View File

@@ -126,15 +126,8 @@ public class FileUtils
{ {
return false; return false;
} }
// 判断是否在允许下载的文件规则内
// 检查允许下载的文件规则 return ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource));
if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
{
return true;
}
// 不在允许下载的文件规则
return false;
} }
/** /**

View File

@@ -3,6 +3,7 @@ package com.ruoyi.common.core.utils.ip;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
/** /**
@@ -12,6 +13,23 @@ import com.ruoyi.common.core.utils.StringUtils;
*/ */
public class IpUtils public class IpUtils
{ {
public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
// 匹配 ip
public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
// 匹配网段
public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
/**
* 获取客户端IP
*
* @return IP地址
*/
public static String getIpAddr()
{
return getIpAddr(ServletUtils.getRequest());
}
/** /**
* 获取客户端IP * 获取客户端IP
* *
@@ -248,7 +266,7 @@ public class IpUtils
} }
} }
} }
return ip; return StringUtils.substring(ip, 0, 255);
} }
/** /**
@@ -261,4 +279,104 @@ public class IpUtils
{ {
return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
} }
/**
* 是否为IP
*/
public static boolean isIP(String ip)
{
return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP);
}
/**
* 是否为IP或 *为间隔的通配符地址
*/
public static boolean isIpWildCard(String ip)
{
return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
}
/**
* 检测参数是否在ip通配符里
*/
public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip)
{
String[] s1 = ipWildCard.split("\\.");
String[] s2 = ip.split("\\.");
boolean isMatchedSeg = true;
for (int i = 0; i < s1.length && !s1[i].equals("*"); i++)
{
if (!s1[i].equals(s2[i]))
{
isMatchedSeg = false;
break;
}
}
return isMatchedSeg;
}
/**
* 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
*/
public static boolean isIPSegment(String ipSeg)
{
return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
}
/**
* 判断ip是否在指定网段中
*/
public static boolean ipIsInNetNoCheck(String iparea, String ip)
{
int idx = iparea.indexOf('-');
String[] sips = iparea.substring(0, idx).split("\\.");
String[] sipe = iparea.substring(idx + 1).split("\\.");
String[] sipt = ip.split("\\.");
long ips = 0L, ipe = 0L, ipt = 0L;
for (int i = 0; i < 4; ++i)
{
ips = ips << 8 | Integer.parseInt(sips[i]);
ipe = ipe << 8 | Integer.parseInt(sipe[i]);
ipt = ipt << 8 | Integer.parseInt(sipt[i]);
}
if (ips > ipe)
{
long t = ips;
ips = ipe;
ipe = t;
}
return ips <= ipt && ipt <= ipe;
}
/**
* 校验ip是否符合过滤串规则
*
* @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
* @param ip 校验IP地址
* @return boolean 结果
*/
public static boolean isMatchedIp(String filter, String ip)
{
if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip))
{
return false;
}
String[] ips = filter.split(";");
for (String iStr : ips)
{
if (isIP(iStr) && iStr.equals(ip))
{
return true;
}
else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip))
{
return true;
}
else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip))
{
return true;
}
}
return false;
}
} }

View File

@@ -1,5 +1,8 @@
package com.ruoyi.common.core.utils.poi; package com.ruoyi.common.core.utils.poi;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
/** /**
* Excel数据格式处理适配器 * Excel数据格式处理适配器
* *
@@ -12,8 +15,10 @@ public interface ExcelHandlerAdapter
* *
* @param value 单元格数据值 * @param value 单元格数据值
* @param args excel注解args参数组 * @param args excel注解args参数组
* @param cell 单元格对象
* @param wb 工作簿对象
* *
* @return 处理后的值 * @return 处理后的值
*/ */
Object format(Object value, String[] args); Object format(Object value, String[] args, Cell cell, Workbook wb);
} }

View File

@@ -28,6 +28,7 @@ import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint; import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper; import org.apache.poi.ss.usermodel.DataValidationHelper;
@@ -55,6 +56,7 @@ import com.ruoyi.common.core.annotation.Excel;
import com.ruoyi.common.core.annotation.Excel.ColumnType; import com.ruoyi.common.core.annotation.Excel.ColumnType;
import com.ruoyi.common.core.annotation.Excel.Type; import com.ruoyi.common.core.annotation.Excel.Type;
import com.ruoyi.common.core.annotation.Excels; import com.ruoyi.common.core.annotation.Excels;
import com.ruoyi.common.core.exception.UtilException;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.DateUtils; import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
@@ -165,6 +167,11 @@ public class ExcelUtil<T>
*/ */
public Class<T> clazz; public Class<T> clazz;
/**
* 需要显示列属性
*/
public String[] includeFields;
/** /**
* 需要排除列属性 * 需要排除列属性
*/ */
@@ -175,11 +182,20 @@ public class ExcelUtil<T>
this.clazz = clazz; this.clazz = clazz;
} }
/**
* 仅在Excel中显示列属性
*
* @param fields 列属性名 示例[单个"name"/多个"id","name"]
*/
public void showColumn(String... fields)
{
this.includeFields = fields;
}
/** /**
* 隐藏Excel中列属性 * 隐藏Excel中列属性
* *
* @param fields 列属性名 示例[单个"name"/多个"id","name"] * @param fields 列属性名 示例[单个"name"/多个"id","name"]
* @throws Exception
*/ */
public void hideColumn(String... fields) public void hideColumn(String... fields)
{ {
@@ -209,8 +225,6 @@ public class ExcelUtil<T>
{ {
if (StringUtils.isNotEmpty(title)) if (StringUtils.isNotEmpty(title))
{ {
subMergedFirstRowNum++;
subMergedLastRowNum++;
int titleLastCol = this.fields.size() - 1; int titleLastCol = this.fields.size() - 1;
if (isSubList()) if (isSubList())
{ {
@@ -221,7 +235,7 @@ public class ExcelUtil<T>
Cell titleCell = titleRow.createCell(0); Cell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(styles.get("title")); titleCell.setCellStyle(styles.get("title"));
titleCell.setCellValue(title); titleCell.setCellValue(title);
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol)); sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol));
} }
} }
@@ -232,23 +246,31 @@ public class ExcelUtil<T>
{ {
if (isSubList()) if (isSubList())
{ {
subMergedFirstRowNum++;
subMergedLastRowNum++;
Row subRow = sheet.createRow(rownum); Row subRow = sheet.createRow(rownum);
int excelNum = 0; int column = 0;
int subFieldSize = subFields != null ? subFields.size() : 0;
for (Object[] objects : fields) for (Object[] objects : fields)
{ {
Field field = (Field) objects[0];
Excel attr = (Excel) objects[1]; Excel attr = (Excel) objects[1];
Cell headCell1 = subRow.createCell(excelNum); if (Collection.class.isAssignableFrom(field.getType()))
headCell1.setCellValue(attr.name()); {
headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); Cell cell = subRow.createCell(column);
excelNum++; cell.setCellValue(attr.name());
} cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
int headFirstRow = excelNum - 1; if (subFieldSize > 1)
int headLastRow = headFirstRow + subFields.size() - 1; {
if (headLastRow > headFirstRow) CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1);
{ sheet.addMergedRegion(cellAddress);
sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow)); }
column += subFieldSize;
}
else
{
Cell cell = subRow.createCell(column++);
cell.setCellValue(attr.name());
cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
}
} }
rownum++; rownum++;
} }
@@ -260,7 +282,7 @@ public class ExcelUtil<T>
* @param is 输入流 * @param is 输入流
* @return 转换后集合 * @return 转换后集合
*/ */
public List<T> importExcel(InputStream is) throws Exception public List<T> importExcel(InputStream is)
{ {
return importExcel(is, 0); return importExcel(is, 0);
} }
@@ -272,9 +294,23 @@ public class ExcelUtil<T>
* @param titleNum 标题占用行数 * @param titleNum 标题占用行数
* @return 转换后集合 * @return 转换后集合
*/ */
public List<T> importExcel(InputStream is, int titleNum) throws Exception public List<T> importExcel(InputStream is, int titleNum)
{ {
return importExcel(StringUtils.EMPTY, is, titleNum); List<T> list = null;
try
{
list = importExcel(StringUtils.EMPTY, is, titleNum);
}
catch (Exception e)
{
log.error("导入Excel异常{}", e.getMessage());
throw new UtilException(e.getMessage());
}
finally
{
IOUtils.closeQuietly(is);
}
return list;
} }
/** /**
@@ -299,7 +335,6 @@ public class ExcelUtil<T>
// 获取最后一个非空行的行下标比如总行数为n则返回的为n-1 // 获取最后一个非空行的行下标比如总行数为n则返回的为n-1
int rows = sheet.getLastRowNum(); int rows = sheet.getLastRowNum();
if (rows > 0) if (rows > 0)
{ {
// 定义一个map用于存放excel列的序号和field. // 定义一个map用于存放excel列的序号和field.
@@ -414,13 +449,13 @@ public class ExcelUtil<T>
{ {
propertyName = field.getName() + "." + attr.targetAttr(); propertyName = field.getName() + "." + attr.targetAttr();
} }
else if (StringUtils.isNotEmpty(attr.readConverterExp())) if (StringUtils.isNotEmpty(attr.readConverterExp()))
{ {
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)) else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{ {
val = dataFormatHandlerAdapter(val, attr); val = dataFormatHandlerAdapter(val, attr, null);
} }
ReflectUtils.invokeSetter(entity, propertyName, val); ReflectUtils.invokeSetter(entity, propertyName, val);
} }
@@ -560,64 +595,91 @@ public class ExcelUtil<T>
{ {
int startNo = index * sheetSize; int startNo = index * sheetSize;
int endNo = Math.min(startNo + sheetSize, list.size()); int endNo = Math.min(startNo + sheetSize, list.size());
int rowNo = (1 + rownum) - startNo; int currentRowNum = rownum + 1; // 从标题行后开始
for (int i = startNo; i < endNo; i++) for (int i = startNo; i < endNo; i++)
{ {
rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo; row = sheet.createRow(currentRowNum);
row = sheet.createRow(rowNo);
// 得到导出对象.
T vo = (T) list.get(i); T vo = (T) list.get(i);
Collection<?> subList = null;
if (isSubList())
{
if (isSubListValue(vo))
{
subList = getListCellValue(vo);
subMergedLastRowNum = subMergedLastRowNum + subList.size();
}
else
{
subMergedFirstRowNum++;
subMergedLastRowNum++;
}
}
int column = 0; int column = 0;
int maxSubListSize = getCurrentMaxSubListSize(vo);
for (Object[] os : fields) for (Object[] os : fields)
{ {
Field field = (Field) os[0]; Field field = (Field) os[0];
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) if (Collection.class.isAssignableFrom(field.getType()))
{ {
boolean subFirst = false; try
for (Object obj : subList)
{ {
if (subFirst) Collection<?> subList = (Collection<?>) getTargetValue(vo, field, excel);
if (subList != null && !subList.isEmpty())
{ {
rowNo++; int subIndex = 0;
row = sheet.createRow(rowNo); for (Object subVo : subList)
}
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
int subIndex = 0;
for (Field subField : subFields)
{
if (subField.isAnnotationPresent(Excel.class))
{ {
subField.setAccessible(true); Row subRow = sheet.getRow(currentRowNum + subIndex);
Excel attr = subField.getAnnotation(Excel.class); if (subRow == null)
this.addCell(attr, row, (T) obj, subField, column + subIndex); {
subRow = sheet.createRow(currentRowNum + subIndex);
}
int subColumn = column;
for (Field subField : subFields)
{
Excel subExcel = subField.getAnnotation(Excel.class);
addCell(subExcel, subRow, (T) subVo, subField, subColumn++);
}
subIndex++;
} }
subIndex++; column += subFields.size();
} }
subFirst = true;
} }
this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size(); catch (Exception e)
{
log.error("填充集合数据失败", e);
}
} }
else else
{ {
this.addCell(excel, row, vo, field, column++); // 创建单元格并设置值
addCell(excel, row, vo, field, column);
if (maxSubListSize > 1 && excel.needMerge())
{
sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column));
}
column++;
}
}
currentRowNum += maxSubListSize;
}
}
/**
* 获取子列表最大数
*/
private int getCurrentMaxSubListSize(T vo)
{
int maxSubListSize = 1;
for (Object[] os : fields)
{
Field field = (Field) os[0];
if (Collection.class.isAssignableFrom(field.getType()))
{
try
{
Collection<?> subList = (Collection<?>) getTargetValue(vo, field, (Excel) os[1]);
if (subList != null && !subList.isEmpty())
{
maxSubListSize = Math.max(maxSubListSize, subList.size());
}
}
catch (Exception e)
{
log.error("获取集合大小失败", e);
} }
} }
} }
return maxSubListSize;
} }
/** /**
@@ -638,6 +700,8 @@ public class ExcelUtil<T>
titleFont.setFontHeightInPoints((short) 16); titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true); titleFont.setBold(true);
style.setFont(titleFont); style.setFont(titleFont);
DataFormat dataFormat = wb.createDataFormat();
style.setDataFormat(dataFormat.getFormat("@"));
styles.put("title", style); styles.put("title", style);
style = wb.createCellStyle(); style = wb.createCellStyle();
@@ -700,6 +764,9 @@ public class ExcelUtil<T>
headerFont.setBold(true); headerFont.setBold(true);
headerFont.setColor(excel.headerColor().index); headerFont.setColor(excel.headerColor().index);
style.setFont(headerFont); style.setFont(headerFont);
// 设置表格头单元格文本形式
DataFormat dataFormat = wb.createDataFormat();
style.setDataFormat(dataFormat.getFormat("@"));
headerStyles.put(key, style); headerStyles.put(key, style);
} }
} }
@@ -717,34 +784,67 @@ public class ExcelUtil<T>
Map<String, CellStyle> styles = new HashMap<String, CellStyle>(); Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
for (Object[] os : fields) for (Object[] os : fields)
{ {
Field field = (Field) os[0];
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor()); if (Collection.class.isAssignableFrom(field.getType()))
if (!styles.containsKey(key))
{ {
CellStyle style = wb.createCellStyle(); ParameterizedType pt = (ParameterizedType) field.getGenericType();
style.setAlignment(excel.align()); Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
style.setVerticalAlignment(VerticalAlignment.CENTER); List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
style.setBorderRight(BorderStyle.THIN); for (Field subField : subFields)
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); {
style.setBorderLeft(BorderStyle.THIN); Excel subExcel = subField.getAnnotation(Excel.class);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); annotationDataStyles(styles, subField, subExcel);
style.setBorderTop(BorderStyle.THIN); }
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); }
style.setBorderBottom(BorderStyle.THIN); else
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); {
style.setFillPattern(FillPatternType.SOLID_FOREGROUND); annotationDataStyles(styles, field, excel);
style.setFillForegroundColor(excel.backgroundColor().getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(excel.color().index);
style.setFont(dataFont);
styles.put(key, style);
} }
} }
return styles; return styles;
} }
/**
* 根据Excel注解创建表格列样式
*
* @param styles 自定义样式列表
* @param field 属性列信息
* @param excel 注解信息
*/
public void annotationDataStyles(Map<String, CellStyle> styles, Field field, Excel excel)
{
String key = StringUtils.format("data_{}_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType(), excel.wrapText());
if (!styles.containsKey(key))
{
CellStyle style = wb.createCellStyle();
style.setAlignment(excel.align());
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(BorderStyle.THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(BorderStyle.THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setFillForegroundColor(excel.backgroundColor().getIndex());
style.setWrapText(excel.wrapText());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(excel.color().index);
style.setFont(dataFont);
if (ColumnType.TEXT == excel.cellType())
{
DataFormat dataFormat = wb.createDataFormat();
style.setDataFormat(dataFormat.getFormat("@"));
}
styles.put(key, style);
}
}
/** /**
* 创建单元格 * 创建单元格
*/ */
@@ -759,7 +859,7 @@ public class ExcelUtil<T>
if (isSubList()) if (isSubList())
{ {
// 填充默认样式,防止合并单元格样式失效 // 填充默认样式,防止合并单元格样式失效
sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor()))); sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText())));
if (attr.needMerge()) if (attr.needMerge())
{ {
sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));
@@ -777,7 +877,7 @@ public class ExcelUtil<T>
*/ */
public void setCellVo(Object value, Excel attr, Cell cell) public void setCellVo(Object value, Excel attr, Cell cell)
{ {
if (ColumnType.STRING == attr.cellType()) if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType())
{ {
String cellValue = Convert.toStr(value); String cellValue = Convert.toStr(value);
// 对于任何以表达式触发字符 =-+@开头的单元格直接使用tab字符作为前缀防止CSV注入。 // 对于任何以表达式触发字符 =-+@开头的单元格直接使用tab字符作为前缀防止CSV注入。
@@ -886,10 +986,12 @@ public class ExcelUtil<T>
cell = row.createCell(column); cell = row.createCell(column);
if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge())
{ {
CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column); if (subMergedLastRowNum >= subMergedFirstRowNum)
sheet.addMergedRegion(cellAddress); {
sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column));
}
} }
cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor()))); cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText())));
// 用于读取对象中的属性 // 用于读取对象中的属性
Object value = getTargetValue(vo, field, attr); Object value = getTargetValue(vo, field, attr);
@@ -898,6 +1000,7 @@ public class ExcelUtil<T>
String separator = attr.separator(); String separator = attr.separator();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{ {
cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat));
cell.setCellValue(parseDateToStr(dateFormat, value)); cell.setCellValue(parseDateToStr(dateFormat, value));
} }
else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
@@ -910,7 +1013,7 @@ public class ExcelUtil<T>
} }
else if (!attr.handler().equals(ExcelHandlerAdapter.class)) else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{ {
cell.setCellValue(dataFormatHandlerAdapter(value, attr)); cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell));
} }
else else
{ {
@@ -1018,7 +1121,7 @@ public class ExcelUtil<T>
/** /**
* 解析导出值 0=男,1=女,2=未知 * 解析导出值 0=男,1=女,2=未知
* *
* @param propertyValue 参数值 * @param propertyValue 参数值
* @param converterExp 翻译注解 * @param converterExp 翻译注解
* @param separator 分隔符 * @param separator 分隔符
@@ -1097,13 +1200,13 @@ public class ExcelUtil<T>
* @param excel 数据注解 * @param excel 数据注解
* @return * @return
*/ */
public String dataFormatHandlerAdapter(Object value, Excel excel) public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell)
{ {
try try
{ {
Object instance = excel.handler().newInstance(); Object instance = excel.handler().newInstance();
Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class }); Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class });
value = formatMethod.invoke(instance, value, excel.args()); value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -1169,6 +1272,7 @@ public class ExcelUtil<T>
*/ */
private Object getTargetValue(T vo, Field field, Excel excel) throws Exception private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
{ {
field.setAccessible(true);
Object o = field.get(vo); Object o = field.get(vo);
if (StringUtils.isNotEmpty(excel.targetAttr())) if (StringUtils.isNotEmpty(excel.targetAttr()))
{ {
@@ -1228,45 +1332,83 @@ public class ExcelUtil<T>
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()));
for (Field field : tempFields) if (StringUtils.isNotEmpty(includeFields))
{ {
if (!ArrayUtils.contains(this.excludeFields, field.getName())) for (Field field : tempFields)
{ {
// 单注解 if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class))
if (field.isAnnotationPresent(Excel.class))
{ {
Excel attr = field.getAnnotation(Excel.class); addField(fields, field);
if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) }
}
}
else if (StringUtils.isNotEmpty(excludeFields))
{
for (Field field : tempFields)
{
if (!ArrayUtils.contains(this.excludeFields, field.getName()))
{
addField(fields, field);
}
}
}
else
{
for (Field field : tempFields)
{
addField(fields, field);
}
}
return fields;
}
/**
* 添加字段信息
*/
public void addField(List<Object[]> fields, Field field)
{
// 单注解
if (field.isAnnotationPresent(Excel.class))
{
Excel attr = field.getAnnotation(Excel.class);
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
{
fields.add(new Object[] { field, attr });
}
if (Collection.class.isAssignableFrom(field.getType()))
{
subMethod = getSubMethod(field.getName(), clazz);
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
}
}
// 多注解
if (field.isAnnotationPresent(Excels.class))
{
Excels attrs = field.getAnnotation(Excels.class);
Excel[] excels = attrs.value();
for (Excel attr : excels)
{
if (StringUtils.isNotEmpty(includeFields))
{
if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr())
&& (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
{ {
field.setAccessible(true);
fields.add(new Object[] { field, attr }); fields.add(new Object[] { field, attr });
} }
if (Collection.class.isAssignableFrom(field.getType()))
{
subMethod = getSubMethod(field.getName(), clazz);
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
}
} }
else
// 多注解
if (field.isAnnotationPresent(Excels.class))
{ {
Excels attrs = field.getAnnotation(Excels.class); if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())
Excel[] excels = attrs.value(); && (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
for (Excel attr : excels)
{ {
if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) fields.add(new Object[] { field, attr });
{
field.setAccessible(true);
fields.add(new Object[] { field, attr });
}
} }
} }
} }
} }
return fields;
} }
/** /**

View File

@@ -13,13 +13,18 @@ public class SqlUtil
/** /**
* 定义常用的 sql关键字 * 定义常用的 sql关键字
*/ */
public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()"; public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/** /**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
*/ */
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/**
* 限制orderBy最大长度
*/
private static final int ORDER_BY_MAX_LENGTH = 500;
/** /**
* 检查字符,防止注入绕过 * 检查字符,防止注入绕过
*/ */
@@ -29,6 +34,10 @@ public class SqlUtil
{ {
throw new UtilException("参数不符合规范,不能进行查询"); throw new UtilException("参数不符合规范,不能进行查询");
} }
if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH)
{
throw new UtilException("参数已超过最大限制,不能进行查询");
}
return value; return value;
} }

View File

@@ -22,7 +22,7 @@ public class Seq
private static AtomicInteger uploadSeq = new AtomicInteger(1); private static AtomicInteger uploadSeq = new AtomicInteger(1);
// 机器标识 // 机器标识
private static String machineCode = "A"; private static final String machineCode = "A";
/** /**
* 获取通用序列号 * 获取通用序列号

View File

@@ -66,7 +66,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
} }
/** /**
* 获取类型 4伪随机生成的UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 * 获取类型 4伪随机生成的UUID 的静态工厂。
* *
* @return 随机生成的 {@code UUID} * @return 随机生成的 {@code UUID}
*/ */

View File

@@ -180,6 +180,16 @@ public class AjaxResult extends HashMap<String, Object>
return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG)); return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
} }
/**
* 是否为警告消息
*
* @return 结果
*/
public boolean isWarn()
{
return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
}
/** /**
* 是否为错误消息 * 是否为错误消息
* *
@@ -187,7 +197,7 @@ public class AjaxResult extends HashMap<String, Object>
*/ */
public boolean isError() public boolean isError()
{ {
return !isSuccess(); return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
} }
/** /**

View File

@@ -37,7 +37,7 @@ public class TableDataInfo implements Serializable
* @param list 列表数据 * @param list 列表数据
* @param total 总记录数 * @param total 总记录数
*/ */
public TableDataInfo(List<?> list, int total) public TableDataInfo(List<?> list, long total)
{ {
this.rows = list; this.rows = list;
this.total = total; this.total = total;

View File

@@ -27,8 +27,13 @@ public class XssValidator implements ConstraintValidator<Xss, String>
public static boolean containsHtml(String value) public static boolean containsHtml(String value)
{ {
StringBuilder sHtml = new StringBuilder();
Pattern pattern = Pattern.compile(HTML_PATTERN); Pattern pattern = Pattern.compile(HTML_PATTERN);
Matcher matcher = pattern.matcher(value); Matcher matcher = pattern.matcher(value);
return matcher.matches(); while (matcher.find())
{
sHtml.append(matcher.group());
}
return pattern.matcher(sHtml).matches();
} }
} }

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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -6,6 +6,7 @@ import org.aspectj.lang.JoinPoint;
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.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.context.SecurityContextHolder; import com.ruoyi.common.core.context.SecurityContextHolder;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
@@ -73,8 +74,7 @@ public class DataScopeAspect
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{ {
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission()); String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission);
controllerDataScope.userAlias(), permission);
} }
} }
} }
@@ -92,29 +92,42 @@ public class DataScopeAspect
{ {
StringBuilder sqlString = new StringBuilder(); StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>(); List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> {
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
scopeCustomIds.add(Convert.toStr(role.getRoleId()));
}
});
for (SysRole role : user.getRoles()) for (SysRole role : user.getRoles())
{ {
String dataScope = role.getDataScope(); String dataScope = role.getDataScope();
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE))
{ {
continue; continue;
} }
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions()) if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{ {
continue; continue;
} }
if (DATA_SCOPE_ALL.equals(dataScope)) if (DATA_SCOPE_ALL.equals(dataScope))
{ {
sqlString = new StringBuilder(); sqlString = new StringBuilder();
conditions.add(dataScope);
break; break;
} }
else if (DATA_SCOPE_CUSTOM.equals(dataScope)) else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{ {
sqlString.append(StringUtils.format( if (scopeCustomIds.size() > 1)
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, {
role.getRoleId())); // 多个自定数据权限使用in查询避免多次拼接。
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
}
else
{
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId()));
}
} }
else if (DATA_SCOPE_DEPT.equals(dataScope)) else if (DATA_SCOPE_DEPT.equals(dataScope))
{ {
@@ -122,9 +135,7 @@ public class DataScopeAspect
} }
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{ {
sqlString.append(StringUtils.format( sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId()));
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
} }
else if (DATA_SCOPE_SELF.equals(dataScope)) else if (DATA_SCOPE_SELF.equals(dataScope))
{ {
@@ -141,6 +152,12 @@ public class DataScopeAspect
conditions.add(dataScope); conditions.add(dataScope);
} }
// 角色都不包含传递过来的权限字符这个时候sqlString也会为空所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
if (StringUtils.isNotBlank(sqlString.toString())) if (StringUtils.isNotBlank(sqlString.toString()))
{ {
Object params = joinPoint.getArgs()[0]; Object params = joinPoint.getArgs()[0];

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.6.2</version> <version>3.6.5</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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -43,4 +43,9 @@ public @interface Log
* 是否保存响应的参数 * 是否保存响应的参数
*/ */
public boolean isSaveResponseData() default true; public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
} }

View File

@@ -4,18 +4,23 @@ import java.util.Collection;
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.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
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.Before;
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;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.ExceptionUtil;
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 com.ruoyi.common.core.utils.ip.IpUtils;
@@ -40,9 +45,21 @@ public class LogAspect
/** 排除敏感属性字段 */ /** 排除敏感属性字段 */
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
@Autowired @Autowired
private AsyncLogService asyncLogService; private AsyncLogService asyncLogService;
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog)
{
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
/** /**
* 处理完请求后执行 * 处理完请求后执行
* *
@@ -74,7 +91,7 @@ public class LogAspect
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();
operLog.setOperIp(ip); operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
String username = SecurityUtils.getUsername(); String username = SecurityUtils.getUsername();
@@ -86,7 +103,7 @@ public class LogAspect
if (e != null) if (e != null)
{ {
operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); operLog.setErrorMsg(StringUtils.substring(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000));
} }
// 设置方法名称 // 设置方法名称
String className = joinPoint.getTarget().getClass().getName(); String className = joinPoint.getTarget().getClass().getName();
@@ -96,6 +113,8 @@ public class LogAspect
operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数 // 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
// 保存数据库 // 保存数据库
asyncLogService.saveSysLog(operLog); asyncLogService.saveSysLog(operLog);
} }
@@ -105,6 +124,10 @@ public class LogAspect
log.error("异常信息:{}", exp.getMessage()); log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace(); exp.printStackTrace();
} }
finally
{
TIME_THREADLOCAL.remove();
}
} }
/** /**
@@ -126,7 +149,7 @@ public class LogAspect
if (log.isSaveRequestData()) if (log.isSaveRequestData())
{ {
// 获取参数的信息,传入到数据库中。 // 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog); setRequestValue(joinPoint, operLog, log.excludeParamNames());
} }
// 是否需要保存response参数和值 // 是否需要保存response参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
@@ -141,25 +164,25 @@ public class LogAspect
* @param operLog 操作日志 * @param operLog 操作日志
* @throws Exception 异常 * @throws Exception 异常
*/ */
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
{ {
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
String requestMethod = operLog.getRequestMethod(); String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name()))
{ {
String params = argsArrayToString(joinPoint.getArgs()); String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000)); operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} }
else else
{ {
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter()), 0, 2000));
} }
} }
/** /**
* 参数拼装 * 参数拼装
*/ */
private String argsArrayToString(Object[] paramsArray) private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{ {
String params = ""; String params = "";
if (paramsArray != null && paramsArray.length > 0) if (paramsArray != null && paramsArray.length > 0)
@@ -170,7 +193,7 @@ public class LogAspect
{ {
try try
{ {
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter()); String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj.toString() + " "; params += jsonObj.toString() + " ";
} }
catch (Exception e) catch (Exception e)
@@ -185,9 +208,9 @@ public class LogAspect
/** /**
* 忽略敏感属性 * 忽略敏感属性
*/ */
public PropertyPreExcludeFilter excludePropertyPreFilter() public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
{ {
return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES); return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
} }
/** /**

View File

@@ -22,7 +22,7 @@ public class AsyncLogService
* 保存系统日志记录 * 保存系统日志记录
*/ */
@Async @Async
public void saveSysLog(SysOperLog sysOperLog) public void saveSysLog(SysOperLog sysOperLog) throws Exception
{ {
remoteLogService.saveLog(sysOperLog, SecurityConstants.INNER); 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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -6,6 +6,8 @@ import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.filter.Filter;
import com.ruoyi.common.core.constant.Constants;
/** /**
* Redis使用FastJson序列化 * Redis使用FastJson序列化
@@ -16,8 +18,9 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{ {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz; static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz) public FastJson2JsonRedisSerializer(Class<T> clazz)
{ {
@@ -44,6 +47,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
} }
String str = new String(bytes, DEFAULT_CHARSET); String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
} }
} }

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.6.2</version> <version>3.6.5</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-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -355,7 +355,7 @@ public class AuthLogic
public boolean hasPermi(Collection<String> authorities, String permission) public boolean hasPermi(Collection<String> authorities, String permission)
{ {
return authorities.stream().filter(StringUtils::hasText) return authorities.stream().filter(StringUtils::hasText)
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission)); .anyMatch(x -> ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission));
} }
/** /**
@@ -368,6 +368,6 @@ public class AuthLogic
public boolean hasRole(Collection<String> roles, String role) public boolean hasRole(Collection<String> roles, String role)
{ {
return roles.stream().filter(StringUtils::hasText) return roles.stream().filter(StringUtils::hasText)
.anyMatch(x -> SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role)); .anyMatch(x -> SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role));
} }
} }

View File

@@ -27,7 +27,7 @@ public class AuthUtil
/** /**
* 会话注销根据指定Token * 会话注销根据指定Token
* *
* @param tokenValue 指定token * @param token 指定token
*/ */
public static void logoutByToken(String token) public static void logoutByToken(String token)
{ {
@@ -44,6 +44,9 @@ public class AuthUtil
/** /**
* 获取当前登录用户信息 * 获取当前登录用户信息
*
* @param token 指定token
* @return 用户信息
*/ */
public static LoginUser getLoginUser(String token) public static LoginUser getLoginUser(String token)
{ {
@@ -52,6 +55,8 @@ public class AuthUtil
/** /**
* 验证当前用户有效期 * 验证当前用户有效期
*
* @param loginUser 用户信息
*/ */
public static void verifyLoginUserExpire(LoginUser loginUser) public static void verifyLoginUserExpire(LoginUser loginUser)
{ {

View File

@@ -48,7 +48,7 @@ public class FeignRequestInterceptor implements RequestInterceptor
} }
// 配置客户端IP // 配置客户端IP
requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr(ServletUtils.getRequest())); requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr());
} }
} }
} }

View File

@@ -6,20 +6,24 @@ import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
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 org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import com.ruoyi.common.core.constant.HttpStatus; import com.ruoyi.common.core.constant.HttpStatus;
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.InnerAuthException;
import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.exception.auth.NotPermissionException; import com.ruoyi.common.core.exception.auth.NotPermissionException;
import com.ruoyi.common.core.exception.auth.NotRoleException; import com.ruoyi.common.core.exception.auth.NotRoleException;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.html.EscapeUtil;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.AjaxResult;
/** /**
* 全局异常处理器 * 全局异常处理器
* *
* @author ruoyi * @author ruoyi
*/ */
@RestControllerAdvice @RestControllerAdvice
@@ -53,8 +57,7 @@ public class GlobalExceptionHandler
* 请求方式不支持 * 请求方式不支持
*/ */
@ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request)
HttpServletRequest request)
{ {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
@@ -72,6 +75,33 @@ public class GlobalExceptionHandler
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
} }
/**
* 请求路径中缺少必需的路径变量
*/
@ExceptionHandler(MissingPathVariableException.class)
public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
* 请求参数类型不匹配
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
String value = Convert.toStr(e.getValue());
if (StringUtils.isNotEmpty(value))
{
value = EscapeUtil.clean(value);
}
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value));
}
/** /**
* 拦截未知的运行时异常 * 拦截未知的运行时异常
*/ */

View File

@@ -4,6 +4,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.CacheConstants; import com.ruoyi.common.core.constant.CacheConstants;
@@ -25,6 +27,8 @@ import com.ruoyi.system.api.model.LoginUser;
@Component @Component
public class TokenService public class TokenService
{ {
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
@Autowired @Autowired
private RedisService redisService; private RedisService redisService;
@@ -32,11 +36,11 @@ public class TokenService
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private final static long expireTime = CacheConstants.EXPIRATION; private final static long TOKEN_EXPIRE_TIME = CacheConstants.EXPIRATION;
private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; private final static Long TOKEN_REFRESH_THRESHOLD_MINUTES = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
/** /**
* 创建令牌 * 创建令牌
@@ -49,7 +53,7 @@ public class TokenService
loginUser.setToken(token); loginUser.setToken(token);
loginUser.setUserid(userId); loginUser.setUserid(userId);
loginUser.setUsername(userName); loginUser.setUsername(userName);
loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); loginUser.setIpaddr(IpUtils.getIpAddr());
refreshToken(loginUser); refreshToken(loginUser);
// Jwt存储信息 // Jwt存储信息
@@ -61,7 +65,7 @@ public class TokenService
// 接口返回信息 // 接口返回信息
Map<String, Object> rspMap = new HashMap<String, Object>(); Map<String, Object> rspMap = new HashMap<String, Object>();
rspMap.put("access_token", JwtUtils.createToken(claimsMap)); rspMap.put("access_token", JwtUtils.createToken(claimsMap));
rspMap.put("expires_in", expireTime); rspMap.put("expires_in", TOKEN_EXPIRE_TIME);
return rspMap; return rspMap;
} }
@@ -106,6 +110,7 @@ public class TokenService
} }
catch (Exception e) catch (Exception e)
{ {
log.error("获取用户信息异常'{}'", e.getMessage());
} }
return user; return user;
} }
@@ -142,7 +147,7 @@ public class TokenService
{ {
long expireTime = loginUser.getExpireTime(); long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) if (expireTime - currentTime <= TOKEN_REFRESH_THRESHOLD_MINUTES)
{ {
refreshToken(loginUser); refreshToken(loginUser);
} }
@@ -156,10 +161,10 @@ public class TokenService
public void refreshToken(LoginUser loginUser) public void refreshToken(LoginUser loginUser)
{ {
loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); loginUser.setExpireTime(loginUser.getLoginTime() + TOKEN_EXPIRE_TIME * MILLIS_MINUTE);
// 根据uuid将loginUser缓存 // 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken()); String userKey = getTokenKey(loginUser.getToken());
redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); redisService.setCacheObject(userKey, loginUser, TOKEN_EXPIRE_TIME, TimeUnit.MINUTES);
} }
private String getTokenKey(String token) private String getTokenKey(String token)

View File

@@ -62,7 +62,7 @@ public class SecurityUtils
public static String getToken(HttpServletRequest request) public static String getToken(HttpServletRequest request)
{ {
// 从header获取token标识 // 从header获取token标识
String token = request.getHeader(TokenConstants.AUTHENTICATION); String token = request.getHeader(SecurityConstants.AUTHORIZATION_HEADER);
return replaceTokenPrefix(token); return replaceTokenPrefix(token);
} }

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.6.5</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-sensitive</artifactId>
<description>
ruoyi-common-sensitive数据脱敏
</description>
<dependencies>
<!-- RuoYi Common Security -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,24 @@
package com.ruoyi.common.sensitive.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.ruoyi.common.sensitive.config.SensitiveJsonSerializer;
import com.ruoyi.common.sensitive.enums.DesensitizedType;
/**
* 数据脱敏注解
*
* @author ruoyi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive
{
DesensitizedType desensitizedType();
}

View File

@@ -0,0 +1,67 @@
package com.ruoyi.common.sensitive.config;
import java.io.IOException;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.common.sensitive.annotation.Sensitive;
import com.ruoyi.common.sensitive.enums.DesensitizedType;
import com.ruoyi.system.api.model.LoginUser;
/**
* 数据脱敏序列化过滤
*
* @author ruoyi
*/
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer
{
private DesensitizedType desensitizedType;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException
{
if (desensitization())
{
gen.writeString(desensitizedType.desensitizer().apply(value));
}
else
{
gen.writeString(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException
{
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
{
this.desensitizedType = annotation.desensitizedType();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
/**
* 是否需要脱敏处理
*/
private boolean desensitization()
{
try
{
LoginUser securityUser = SecurityUtils.getLoginUser();
// 管理员不脱敏
return !securityUser.getSysUser().isAdmin();
}
catch (Exception e)
{
return true;
}
}
}

View File

@@ -0,0 +1,59 @@
package com.ruoyi.common.sensitive.enums;
import java.util.function.Function;
import com.ruoyi.common.sensitive.utils.DesensitizedUtil;
/**
* 脱敏类型
*
* @author ruoyi
*/
public enum DesensitizedType
{
/**
* 姓名第2位星号替换
*/
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
/**
* 密码,全部字符都用*代替
*/
PASSWORD(DesensitizedUtil::password),
/**
* 身份证中间10位星号替换
*/
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{3}[Xx]|\\d{4})", "$1** **** ****$2")),
/**
* 手机号中间4位星号替换
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
/**
* 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换
*/
EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")),
/**
* 银行卡号保留最后4位其他星号替换
*/
BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")),
/**
* 车牌号码,包含普通车辆、新能源车辆
*/
CAR_LICENSE(DesensitizedUtil::carLicense);
private final Function<String, String> desensitizer;
DesensitizedType(Function<String, String> desensitizer)
{
this.desensitizer = desensitizer;
}
public Function<String, String> desensitizer()
{
return desensitizer;
}
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.common.sensitive.utils;
import com.ruoyi.common.core.utils.StringUtils;
/**
* 脱敏工具类
*
* @author ruoyi
*/
public class DesensitizedUtil
{
/**
* 密码的全部字符都用*代替,比如:******
*
* @param password 密码
* @return 脱敏后的密码
*/
public static String password(String password)
{
if (StringUtils.isBlank(password))
{
return StringUtils.EMPTY;
}
return StringUtils.repeat('*', password.length());
}
/**
* 车牌中间用*代替,如果是错误的车牌,不处理
*
* @param carLicense 完整的车牌号
* @return 脱敏后的车牌
*/
public static String carLicense(String carLicense)
{
if (StringUtils.isBlank(carLicense))
{
return StringUtils.EMPTY;
}
// 普通车牌
if (carLicense.length() == 7)
{
carLicense = StringUtils.hide(carLicense, 3, 6);
}
else if (carLicense.length() == 8)
{
// 新能源车牌
carLicense = StringUtils.hide(carLicense, 3, 7);
}
return carLicense;
}
}

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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -23,11 +23,10 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<!-- Swagger --> <!-- SpringDoc webmvc -->
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>org.springdoc</groupId>
<artifactId>springfox-swagger2</artifactId> <artifactId>springdoc-openapi-ui</artifactId>
<version>${swagger.fox.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -1,20 +0,0 @@
package com.ruoyi.common.swagger.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import com.ruoyi.common.swagger.config.SwaggerAutoConfiguration;
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ SwaggerAutoConfiguration.class })
public @interface EnableCustomSwagger2
{
}

View File

@@ -0,0 +1,63 @@
package com.ruoyi.common.swagger.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import com.ruoyi.common.swagger.config.properties.SpringDocProperties;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
/**
* Swagger 文档配置
*
* @author ruoyi
*/
@EnableConfigurationProperties(SpringDocProperties.class)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
public class SpringDocAutoConfiguration
{
@Bean
@ConditionalOnMissingBean(OpenAPI.class)
public OpenAPI openApi(SpringDocProperties properties)
{
return new OpenAPI().components(new Components()
// 设置认证的请求头
.addSecuritySchemes("apikey", securityScheme()))
.addSecurityItem(new SecurityRequirement().addList("apikey"))
.info(convertInfo(properties.getInfo()))
.servers(servers(properties.getGatewayUrl()));
}
public SecurityScheme securityScheme()
{
return new SecurityScheme().type(SecurityScheme.Type.APIKEY)
.name("Authorization")
.in(SecurityScheme.In.HEADER)
.scheme("Bearer");
}
private Info convertInfo(SpringDocProperties.InfoProperties infoProperties)
{
Info info = new Info();
info.setTitle(infoProperties.getTitle());
info.setDescription(infoProperties.getDescription());
info.setContact(infoProperties.getContact());
info.setLicense(infoProperties.getLicense());
info.setVersion(infoProperties.getVersion());
return info;
}
public List<Server> servers(String gatewayUrl)
{
List<Server> serverList = new ArrayList<>();
serverList.add(new Server().url(gatewayUrl));
return serverList;
}
}

View File

@@ -1,129 +0,0 @@
package com.ruoyi.common.swagger.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
@EnableAutoConfiguration
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerAutoConfiguration
{
/**
* 默认的排除路径排除Spring Boot默认的错误处理路径和端点
*/
private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");
private static final String BASE_PATH = "/**";
@Bean
@ConditionalOnMissingBean
public SwaggerProperties swaggerProperties()
{
return new SwaggerProperties();
}
@Bean
public Docket api(SwaggerProperties swaggerProperties)
{
// base-path处理
if (swaggerProperties.getBasePath().isEmpty())
{
swaggerProperties.getBasePath().add(BASE_PATH);
}
// noinspection unchecked
List<Predicate<String>> basePath = new ArrayList<Predicate<String>>();
swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));
// exclude-path处理
if (swaggerProperties.getExcludePath().isEmpty())
{
swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
}
List<Predicate<String>> excludePath = new ArrayList<>();
swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));
ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2).host(swaggerProperties.getHost())
.apiInfo(apiInfo(swaggerProperties)).select()
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()));
swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p)));
swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate()));
return builder.build().securitySchemes(securitySchemes()).securityContexts(securityContexts()).pathMapping("/");
}
/**
* 安全模式这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes()
{
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
return apiKeyList;
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts()
{
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}
/**
* 默认的全局鉴权策略
*
* @return
*/
private List<SecurityReference> defaultAuth()
{
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
private ApiInfo apiInfo(SwaggerProperties swaggerProperties)
{
return new ApiInfoBuilder()
.title(swaggerProperties.getTitle())
.description(swaggerProperties.getDescription())
.license(swaggerProperties.getLicense())
.licenseUrl(swaggerProperties.getLicenseUrl())
.termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
.contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
.version(swaggerProperties.getVersion())
.build();
}
}

View File

@@ -1,54 +0,0 @@
package com.ruoyi.common.swagger.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
/**
* swagger 在 springboot 2.6.x 不兼容问题的处理
*
* @author ruoyi
*/
@Component
public class SwaggerBeanPostProcessor implements BeanPostProcessor
{
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider)
{
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings)
{
List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean)
{
try
{
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
}
catch (IllegalArgumentException | IllegalAccessException e)
{
throw new IllegalStateException(e);
}
}
}

View File

@@ -1,345 +0,0 @@
package com.ruoyi.common.swagger.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("swagger")
public class SwaggerProperties
{
/**
* 是否开启swagger
*/
private Boolean enabled;
/**
* swagger会解析的包路径
**/
private String basePackage = "";
/**
* swagger会解析的url规则
**/
private List<String> basePath = new ArrayList<>();
/**
* 在basePath基础上需要排除的url规则
**/
private List<String> excludePath = new ArrayList<>();
/**
* 标题
**/
private String title = "";
/**
* 描述
**/
private String description = "";
/**
* 版本
**/
private String version = "";
/**
* 许可证
**/
private String license = "";
/**
* 许可证URL
**/
private String licenseUrl = "";
/**
* 服务条款URL
**/
private String termsOfServiceUrl = "";
/**
* host信息
**/
private String host = "";
/**
* 联系人信息
*/
private Contact contact = new Contact();
/**
* 全局统一鉴权配置
**/
private Authorization authorization = new Authorization();
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public String getBasePackage()
{
return basePackage;
}
public void setBasePackage(String basePackage)
{
this.basePackage = basePackage;
}
public List<String> getBasePath()
{
return basePath;
}
public void setBasePath(List<String> basePath)
{
this.basePath = basePath;
}
public List<String> getExcludePath()
{
return excludePath;
}
public void setExcludePath(List<String> excludePath)
{
this.excludePath = excludePath;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public String getVersion()
{
return version;
}
public void setVersion(String version)
{
this.version = version;
}
public String getLicense()
{
return license;
}
public void setLicense(String license)
{
this.license = license;
}
public String getLicenseUrl()
{
return licenseUrl;
}
public void setLicenseUrl(String licenseUrl)
{
this.licenseUrl = licenseUrl;
}
public String getTermsOfServiceUrl()
{
return termsOfServiceUrl;
}
public void setTermsOfServiceUrl(String termsOfServiceUrl)
{
this.termsOfServiceUrl = termsOfServiceUrl;
}
public String getHost()
{
return host;
}
public void setHost(String host)
{
this.host = host;
}
public Contact getContact()
{
return contact;
}
public void setContact(Contact contact)
{
this.contact = contact;
}
public Authorization getAuthorization()
{
return authorization;
}
public void setAuthorization(Authorization authorization)
{
this.authorization = authorization;
}
public static class Contact
{
/**
* 联系人
**/
private String name = "";
/**
* 联系人url
**/
private String url = "";
/**
* 联系人email
**/
private String email = "";
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getUrl()
{
return url;
}
public void setUrl(String url)
{
this.url = url;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
}
public static class Authorization
{
/**
* 鉴权策略ID需要和SecurityReferences ID保持一致
*/
private String name = "";
/**
* 需要开启鉴权URL的正则
*/
private String authRegex = "^.*$";
/**
* 鉴权作用域列表
*/
private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
private List<String> tokenUrlList = new ArrayList<>();
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getAuthRegex()
{
return authRegex;
}
public void setAuthRegex(String authRegex)
{
this.authRegex = authRegex;
}
public List<AuthorizationScope> getAuthorizationScopeList()
{
return authorizationScopeList;
}
public void setAuthorizationScopeList(List<AuthorizationScope> authorizationScopeList)
{
this.authorizationScopeList = authorizationScopeList;
}
public List<String> getTokenUrlList()
{
return tokenUrlList;
}
public void setTokenUrlList(List<String> tokenUrlList)
{
this.tokenUrlList = tokenUrlList;
}
}
public static class AuthorizationScope
{
/**
* 作用域名称
*/
private String scope = "";
/**
* 作用域描述
*/
private String description = "";
public String getScope()
{
return scope;
}
public void setScope(String scope)
{
this.scope = scope;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
}
}

View File

@@ -1,22 +0,0 @@
package com.ruoyi.common.swagger.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* swagger 资源映射路径
*
* @author ruoyi
*/
@Configuration
public class SwaggerWebConfiguration implements WebMvcConfigurer
{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
/** swagger-ui 地址 */
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
}
}

View File

@@ -0,0 +1,135 @@
package com.ruoyi.common.swagger.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.License;
/**
* Swagger 配置属性
*
* @author ruoyi
*/
@ConfigurationProperties(prefix = "springdoc")
public class SpringDocProperties
{
/**
* 网关
*/
private String gatewayUrl;
/**
* 文档基本信息
*/
@NestedConfigurationProperty
private InfoProperties info = new InfoProperties();
/**
* <p>
* 文档的基础属性信息
* </p>
*
* @see io.swagger.v3.oas.models.info.Info
*
* 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
*/
public static class InfoProperties
{
/**
* 标题
*/
private String title = null;
/**
* 描述
*/
private String description = null;
/**
* 联系人信息
*/
@NestedConfigurationProperty
private Contact contact = null;
/**
* 许可证
*/
@NestedConfigurationProperty
private License license = null;
/**
* 版本
*/
private String version = null;
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public Contact getContact()
{
return contact;
}
public void setContact(Contact contact)
{
this.contact = contact;
}
public License getLicense()
{
return license;
}
public void setLicense(License license)
{
this.license = license;
}
public String getVersion()
{
return version;
}
public void setVersion(String version)
{
this.version = version;
}
}
public String getGatewayUrl()
{
return gatewayUrl;
}
public void setGatewayUrl(String gatewayUrl)
{
this.gatewayUrl = gatewayUrl;
}
public InfoProperties getInfo()
{
return info;
}
public void setInfo(InfoProperties info)
{
this.info = info;
}
}

View File

@@ -1,3 +1 @@
com.ruoyi.common.swagger.config.SwaggerAutoConfiguration com.ruoyi.common.swagger.config.SpringDocAutoConfiguration
com.ruoyi.common.swagger.config.SwaggerWebConfiguration
com.ruoyi.common.swagger.config.SwaggerBeanPostProcessor

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -76,16 +76,11 @@
<artifactId>ruoyi-common-redis</artifactId> <artifactId>ruoyi-common-redis</artifactId>
</dependency> </dependency>
<!-- Swagger --> <!-- Springdoc -->
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>org.springdoc</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>${swagger.fox.version}</version> <version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.fox.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1,93 @@
package com.ruoyi.gateway.config;
import java.util.Set;
import java.util.stream.Collectors;
import org.springdoc.core.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.SwaggerUiConfigProperties;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Configuration;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.ruoyi.common.core.utils.StringUtils;
/**
* SpringDoc配置类
*
* @author ruoyi
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "springdoc.api-docs.enabled", matchIfMissing = true)
public class SpringDocConfig implements InitializingBean
{
@Autowired
private SwaggerUiConfigProperties swaggerUiConfigProperties;
@Autowired
private DiscoveryClient discoveryClient;
/**
* 在初始化后调用的方法
*/
@Override
public void afterPropertiesSet()
{
NotifyCenter.registerSubscriber(new SwaggerDocRegister(swaggerUiConfigProperties, discoveryClient));
}
}
/**
* Swagger文档注册器
*/
class SwaggerDocRegister extends Subscriber<InstancesChangeEvent>
{
@Autowired
private SwaggerUiConfigProperties swaggerUiConfigProperties;
@Autowired
private DiscoveryClient discoveryClient;
private final static String[] EXCLUDE_ROUTES = new String[] { "ruoyi-gateway", "ruoyi-auth", "ruoyi-file", "ruoyi-monitor" };
public SwaggerDocRegister(SwaggerUiConfigProperties swaggerUiConfigProperties, DiscoveryClient discoveryClient)
{
this.swaggerUiConfigProperties = swaggerUiConfigProperties;
this.discoveryClient = discoveryClient;
}
/**
* 事件回调方法处理InstancesChangeEvent事件
* @param event 事件对象
*/
@Override
public void onEvent(InstancesChangeEvent event)
{
Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> swaggerUrlSet = discoveryClient.getServices()
.stream()
.flatMap(serviceId -> discoveryClient.getInstances(serviceId).stream())
.filter(instance -> !StringUtils.equalsAnyIgnoreCase(instance.getServiceId(), EXCLUDE_ROUTES))
.map(instance -> {
AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl();
swaggerUrl.setName(instance.getServiceId());
swaggerUrl.setUrl(String.format("/%s/v3/api-docs", instance.getServiceId()));
return swaggerUrl;
})
.collect(Collectors.toSet());
swaggerUiConfigProperties.setUrls(swaggerUrlSet);
}
/**
* 订阅类型方法,返回订阅的事件类型
* @return 订阅的事件类型
*/
@Override
public Class<? extends Event> subscribeType()
{
return InstancesChangeEvent.class;
}
}

View File

@@ -1,79 +0,0 @@
package com.ruoyi.gateway.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
/**
* 聚合系统接口
*
* @author ruoyi
*/
@Component
public class SwaggerProvider implements SwaggerResourcesProvider, WebFluxConfigurer
{
/**
* Swagger2默认的url后缀
*/
public static final String SWAGGER2URL = "/v2/api-docs";
/**
* 网关路由
*/
@Lazy
@Autowired
private RouteLocator routeLocator;
@Autowired
private GatewayProperties gatewayProperties;
/**
* 聚合其他服务接口
*
* @return
*/
@Override
public List<SwaggerResource> get()
{
List<SwaggerResource> resourceList = new ArrayList<>();
List<String> routes = new ArrayList<>();
// 获取网关中配置的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
gatewayProperties.getRoutes().stream()
.filter(routeDefinition -> routes
.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))
.filter(predicateDefinition -> !"ruoyi-auth".equalsIgnoreCase(routeDefinition.getId()))
.forEach(predicateDefinition -> resourceList
.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs()
.get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL)))));
return resourceList;
}
private SwaggerResource swaggerResource(String name, String location)
{
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
/** swagger-ui 地址 */
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
}
}

View File

@@ -101,7 +101,7 @@ public class AuthFilter implements GlobalFilter, Ordered
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg)
{ {
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); log.error("[鉴权异常处理]请求路径:{},错误信息:{}", exchange.getRequest().getPath(), msg);
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
} }
@@ -118,7 +118,7 @@ public class AuthFilter implements GlobalFilter, Ordered
*/ */
private String getToken(ServerHttpRequest request) private String getToken(ServerHttpRequest request)
{ {
String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION); String token = request.getHeaders().getFirst(SecurityConstants.AUTHORIZATION_HEADER);
// 如果前端设置了令牌前缀,则裁剪掉前缀 // 如果前端设置了令牌前缀,则裁剪掉前缀
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
{ {

View File

@@ -45,7 +45,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭,不处理 // 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
{ {
return chain.filter(exchange); return chain.filter(exchange);
} }

View File

@@ -87,7 +87,7 @@ public class XssFilter implements GlobalFilter, Ordered
// 防xss攻击过滤 // 防xss攻击过滤
bodyStr = EscapeUtil.clean(bodyStr); bodyStr = EscapeUtil.clean(bodyStr);
// 转成字节 // 转成字节
byte[] bytes = bodyStr.getBytes(); byte[] bytes = bodyStr.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes); buffer.write(bytes);

View File

@@ -1,56 +0,0 @@
package com.ruoyi.gateway.handler;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler
{
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources)
{
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration()
{
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()),
HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration()
{
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@SuppressWarnings("rawtypes")
@GetMapping("")
public Mono<ResponseEntity> swaggerResources()
{
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}

View File

@@ -103,14 +103,13 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
{ {
throw new CaptchaException("验证码不能为空"); throw new CaptchaException("验证码不能为空");
} }
if (StringUtils.isEmpty(uuid)) String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
String captcha = redisService.getCacheObject(verifyKey);
if (captcha == null)
{ {
throw new CaptchaException("验证码已失效"); throw new CaptchaException("验证码已失效");
} }
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisService.getCacheObject(verifyKey);
redisService.deleteObject(verifyKey); redisService.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha)) if (!code.equalsIgnoreCase(captcha))
{ {
throw new CaptchaException("验证码错误"); throw new CaptchaException("验证码错误");

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.2</version> <version>3.6.5</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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -40,6 +40,12 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- FastDFS --> <!-- FastDFS -->
<dependency> <dependency>
@@ -60,12 +66,6 @@
<artifactId>ruoyi-api-system</artifactId> <artifactId>ruoyi-api-system</artifactId>
</dependency> </dependency>
<!-- RuoYi Common Swagger -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -3,14 +3,12 @@ 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;
/** /**
* 文件服务 * 文件服务
* *
* @author ruoyi * @author ruoyi
*/ */
@EnableCustomSwagger2
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class RuoYiFileApplication public class RuoYiFileApplication
{ {

View File

@@ -1,5 +1,7 @@
package com.ruoyi.file.service; package com.ruoyi.file.service;
import java.io.InputStream;
import com.alibaba.nacos.common.utils.IoUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -10,7 +12,7 @@ import com.ruoyi.common.core.utils.file.FileTypeUtils;
/** /**
* FastDFS 文件存储 * FastDFS 文件存储
* *
* @author ruoyi * @author ruoyi
*/ */
@Service @Service
@@ -27,7 +29,7 @@ public class FastDfsSysFileServiceImpl implements ISysFileService
/** /**
* FastDfs文件上传接口 * FastDfs文件上传接口
* *
* @param file 上传的文件 * @param file 上传的文件
* @return 访问地址 * @return 访问地址
* @throws Exception * @throws Exception
@@ -35,8 +37,20 @@ public class FastDfsSysFileServiceImpl implements ISysFileService
@Override @Override
public String uploadFile(MultipartFile file) throws Exception public String uploadFile(MultipartFile file) throws Exception
{ {
StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), InputStream inputStream = null;
FileTypeUtils.getExtension(file), null); try
return domain + "/" + storePath.getFullPath(); {
inputStream = file.getInputStream();
StorePath storePath = storageClient.uploadFile(inputStream, file.getSize(), FileTypeUtils.getExtension(file), null);
return domain + "/" + storePath.getFullPath();
}
catch (Exception e)
{
throw new RuntimeException("FastDfs Failed to upload file", e);
}
finally
{
IoUtils.closeQuietly(inputStream);
}
} }
} }

View File

@@ -1,8 +1,10 @@
package com.ruoyi.file.service; package com.ruoyi.file.service;
import java.io.InputStream;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.alibaba.nacos.common.utils.IoUtils;
import com.ruoyi.file.config.MinioConfig; import com.ruoyi.file.config.MinioConfig;
import com.ruoyi.file.utils.FileUploadUtils; import com.ruoyi.file.utils.FileUploadUtils;
import io.minio.MinioClient; import io.minio.MinioClient;
@@ -10,7 +12,7 @@ import io.minio.PutObjectArgs;
/** /**
* Minio 文件存储 * Minio 文件存储
* *
* @author ruoyi * @author ruoyi
*/ */
@Service @Service
@@ -23,8 +25,8 @@ public class MinioSysFileServiceImpl implements ISysFileService
private MinioClient client; private MinioClient client;
/** /**
* 本地文件上传接口 * Minio文件上传接口
* *
* @param file 上传的文件 * @param file 上传的文件
* @return 访问地址 * @return 访问地址
* @throws Exception * @throws Exception
@@ -32,14 +34,27 @@ public class MinioSysFileServiceImpl implements ISysFileService
@Override @Override
public String uploadFile(MultipartFile file) throws Exception public String uploadFile(MultipartFile file) throws Exception
{ {
String fileName = FileUploadUtils.extractFilename(file); InputStream inputStream = null;
PutObjectArgs args = PutObjectArgs.builder() try
.bucket(minioConfig.getBucketName()) {
.object(fileName) String fileName = FileUploadUtils.extractFilename(file);
.stream(file.getInputStream(), file.getSize(), -1) inputStream = file.getInputStream();
.contentType(file.getContentType()) PutObjectArgs args = PutObjectArgs.builder()
.build(); .bucket(minioConfig.getBucketName())
client.putObject(args); .object(fileName)
return minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + fileName; .stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
.build();
client.putObject(args);
return minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + fileName;
}
catch (Exception e)
{
throw new RuntimeException("Minio Failed to upload file", e);
}
finally
{
IoUtils.closeQuietly(inputStream);
}
} }
} }

View File

@@ -6,6 +6,7 @@ import java.nio.file.Paths;
import java.util.Objects; import java.util.Objects;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.exception.file.FileException;
import com.ruoyi.common.core.exception.file.FileNameLengthLimitExceededException; import com.ruoyi.common.core.exception.file.FileNameLengthLimitExceededException;
import com.ruoyi.common.core.exception.file.FileSizeLimitExceededException; import com.ruoyi.common.core.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.core.exception.file.InvalidExtensionException; import com.ruoyi.common.core.exception.file.InvalidExtensionException;
@@ -25,7 +26,7 @@ public class FileUploadUtils
/** /**
* 默认大小 50M * 默认大小 50M
*/ */
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
/** /**
* 默认的文件名最大长度 100 * 默认的文件名最大长度 100
@@ -46,6 +47,10 @@ public class FileUploadUtils
{ {
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
} }
catch (FileException fe)
{
throw new IOException(fe.getDefaultMessage(), fe);
}
catch (Exception e) catch (Exception e)
{ {
throw new IOException(e.getMessage(), e); throw new IOException(e.getMessage(), e);

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.6.2</version> <version>3.6.5</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -40,13 +40,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- Swagger UI -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.fox.version}</version>
</dependency>
<!-- Apache Velocity --> <!-- Apache Velocity -->
<dependency> <dependency>
@@ -56,8 +49,8 @@
<!-- Mysql Connector --> <!-- Mysql Connector -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-j</artifactId>
</dependency> </dependency>
<!-- RuoYi Common Log --> <!-- RuoYi Common Log -->

View File

@@ -4,7 +4,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.ruoyi.common.security.annotation.EnableCustomConfig; import com.ruoyi.common.security.annotation.EnableCustomConfig;
import com.ruoyi.common.security.annotation.EnableRyFeignClients; import com.ruoyi.common.security.annotation.EnableRyFeignClients;
import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
/** /**
* 代码生成 * 代码生成
@@ -12,7 +11,6 @@ import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
* @author ruoyi * @author ruoyi
*/ */
@EnableCustomConfig @EnableCustomConfig
@EnableCustomSwagger2
@EnableRyFeignClients @EnableRyFeignClients
@SpringBootApplication @SpringBootApplication
public class RuoYiGenApplication public class RuoYiGenApplication

View File

@@ -18,12 +18,15 @@ public class GenConfig
/** 生成包路径 */ /** 生成包路径 */
public static String packageName; public static String packageName;
/** 自动去除表前缀默认是false */ /** 自动去除表前缀 */
public static boolean autoRemovePre; public static boolean autoRemovePre;
/** 表前缀(类名不会包含表前缀) */ /** 表前缀 */
public static String tablePrefix; public static String tablePrefix;
/** 是否允许生成文件覆盖到本地(自定义路径) */
public static boolean allowOverwrite;
public static String getAuthor() public static String getAuthor()
{ {
return author; return author;
@@ -63,4 +66,14 @@ public class GenConfig
{ {
GenConfig.tablePrefix = tablePrefix; GenConfig.tablePrefix = tablePrefix;
} }
public static boolean isAllowOverwrite()
{
return allowOverwrite;
}
public void setAllowOverwrite(boolean allowOverwrite)
{
GenConfig.allowOverwrite = allowOverwrite;
}
} }

View File

@@ -23,6 +23,7 @@ import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.log.annotation.Log; import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType; import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.RequiresPermissions; import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.gen.config.GenConfig;
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.service.IGenTableColumnService; import com.ruoyi.gen.service.IGenTableColumnService;
@@ -168,6 +169,10 @@ public class GenController extends BaseController
@GetMapping("/genCode/{tableName}") @GetMapping("/genCode/{tableName}")
public AjaxResult genCode(@PathVariable("tableName") String tableName) public AjaxResult genCode(@PathVariable("tableName") String tableName)
{ {
if (!GenConfig.isAllowOverwrite())
{
return AjaxResult.error("【系统预设】不允许生成文件覆盖到本地");
}
genTableService.generatorCode(tableName); genTableService.generatorCode(tableName);
return success(); return success();
} }

View File

@@ -41,6 +41,9 @@ public class GenTable extends BaseEntity
/** 使用的模板crud单表操作 tree树表操作 sub主子表操作 */ /** 使用的模板crud单表操作 tree树表操作 sub主子表操作 */
private String tplCategory; private String tplCategory;
/** 前端类型element-ui模版 element-plus模版 */
private String tplWebType;
/** 生成包路径 */ /** 生成包路径 */
@NotBlank(message = "生成包路径不能为空") @NotBlank(message = "生成包路径不能为空")
private String packageName; private String packageName;
@@ -90,7 +93,7 @@ public class GenTable extends BaseEntity
private String treeName; private String treeName;
/** 上级菜单ID字段 */ /** 上级菜单ID字段 */
private String parentMenuId; private Long parentMenuId;
/** 上级菜单名称字段 */ /** 上级菜单名称字段 */
private String parentMenuName; private String parentMenuName;
@@ -165,6 +168,16 @@ public class GenTable extends BaseEntity
this.tplCategory = tplCategory; this.tplCategory = tplCategory;
} }
public String getTplWebType()
{
return tplWebType;
}
public void setTplWebType(String tplWebType)
{
this.tplWebType = tplWebType;
}
public String getPackageName() public String getPackageName()
{ {
return packageName; return packageName;
@@ -304,12 +317,12 @@ public class GenTable extends BaseEntity
this.treeName = treeName; this.treeName = treeName;
} }
public String getParentMenuId() public Long getParentMenuId()
{ {
return parentMenuId; return parentMenuId;
} }
public void setParentMenuId(String parentMenuId) public void setParentMenuId(Long parentMenuId)
{ {
this.parentMenuId = parentMenuId; this.parentMenuId = parentMenuId;
} }

View File

@@ -1,7 +1,6 @@
package com.ruoyi.gen.domain; package com.ruoyi.gen.domain;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.BaseEntity; import com.ruoyi.common.core.web.domain.BaseEntity;

View File

@@ -129,9 +129,9 @@ public class GenTableServiceImpl implements IGenTableService
int row = genTableMapper.updateGenTable(genTable); int row = genTableMapper.updateGenTable(genTable);
if (row > 0) if (row > 0)
{ {
for (GenTableColumn cenTableColumn : genTable.getColumns()) for (GenTableColumn genTableColumn : genTable.getColumns())
{ {
genTableColumnMapper.updateGenTableColumn(cenTableColumn); genTableColumnMapper.updateGenTableColumn(genTableColumn);
} }
} }
} }
@@ -206,7 +206,7 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory()); List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
for (String template : templates) for (String template : templates)
{ {
// 渲染模板 // 渲染模板
@@ -254,7 +254,7 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory()); List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
for (String template : templates) for (String template : templates)
{ {
if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm"))
@@ -367,7 +367,7 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory()); List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
for (String template : templates) for (String template : templates)
{ {
// 渲染模板 // 渲染模板
@@ -414,16 +414,16 @@ public class GenTableServiceImpl implements IGenTableService
{ {
throw new ServiceException("树名称字段不能为空"); 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 ServiceException("关联子表的表名不能为空");
{ }
throw new ServiceException("关联子表的表名不能为空"); else if (StringUtils.isEmpty(genTable.getSubTableFkName()))
} {
else if (StringUtils.isEmpty(genTable.getSubTableFkName())) throw new ServiceException("子表关联的外键名不能为空");
{
throw new ServiceException("子表关联的外键名不能为空");
}
} }
} }
} }
@@ -491,7 +491,7 @@ public class GenTableServiceImpl implements IGenTableService
String treeCode = paramsObj.getString(GenConstants.TREE_CODE); String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
String treeName = paramsObj.getString(GenConstants.TREE_NAME); String treeName = paramsObj.getString(GenConstants.TREE_NAME);
String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID);
String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);
genTable.setTreeCode(treeCode); genTable.setTreeCode(treeCode);

View File

@@ -123,11 +123,17 @@ public class VelocityUtils
/** /**
* 获取模板信息 * 获取模板信息
* * @param tplCategory 生成的模板
* @param tplWebType 前端类型
* @return 模板列表 * @return 模板列表
*/ */
public static List<String> getTemplateList(String tplCategory) public static List<String> getTemplateList(String tplCategory, String tplWebType)
{ {
String useWebType = "vm/vue";
if ("element-plus".equals(tplWebType))
{
useWebType = "vm/vue/v3";
}
List<String> templates = new ArrayList<String>(); List<String> templates = new ArrayList<String>();
templates.add("vm/java/domain.java.vm"); templates.add("vm/java/domain.java.vm");
templates.add("vm/java/mapper.java.vm"); templates.add("vm/java/mapper.java.vm");
@@ -139,15 +145,15 @@ public class VelocityUtils
templates.add("vm/js/api.js.vm"); templates.add("vm/js/api.js.vm");
if (GenConstants.TPL_CRUD.equals(tplCategory)) if (GenConstants.TPL_CRUD.equals(tplCategory))
{ {
templates.add("vm/vue/index.vue.vm"); templates.add(useWebType + "/index.vue.vm");
} }
else if (GenConstants.TPL_TREE.equals(tplCategory)) else if (GenConstants.TPL_TREE.equals(tplCategory))
{ {
templates.add("vm/vue/index-tree.vue.vm"); templates.add(useWebType + "/index-tree.vue.vm");
} }
else if (GenConstants.TPL_SUB.equals(tplCategory)) else if (GenConstants.TPL_SUB.equals(tplCategory))
{ {
templates.add("vm/vue/index.vue.vm"); templates.add(useWebType + "/index.vue.vm");
templates.add("vm/java/sub-domain.java.vm"); templates.add("vm/java/sub-domain.java.vm");
} }
return templates; return templates;

View File

@@ -3,7 +3,7 @@
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.gen.mapper.GenTableColumnMapper"> <mapper namespace="com.ruoyi.gen.mapper.GenTableColumnMapper">
<resultMap type="GenTableColumn" id="GenTableColumnResult"> <resultMap type="GenTableColumn" id="GenTableColumnResult">
<id property="columnId" column="column_id" /> <id property="columnId" column="column_id" />
<result property="tableId" column="table_id" /> <result property="tableId" column="table_id" />
@@ -28,23 +28,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateBy" column="update_by" /> <result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" /> <result property="updateTime" column="update_time" />
</resultMap> </resultMap>
<sql id="selectGenTableColumnVo"> <sql id="selectGenTableColumnVo">
select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column
</sql> </sql>
<select id="selectGenTableColumnListByTableId" parameterType="GenTableColumn" resultMap="GenTableColumnResult"> <select id="selectGenTableColumnListByTableId" parameterType="GenTableColumn" resultMap="GenTableColumnResult">
<include refid="selectGenTableColumnVo"/> <include refid="selectGenTableColumnVo"/>
where table_id = #{tableId} where table_id = #{tableId}
order by sort order by sort
</select> </select>
<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult"> <select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type
from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName}) from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
order by ordinal_position order by ordinal_position
</select> </select>
<insert id="insertGenTableColumn" parameterType="GenTableColumn" useGeneratedKeys="true" keyProperty="columnId"> <insert id="insertGenTableColumn" parameterType="GenTableColumn" useGeneratedKeys="true" keyProperty="columnId">
insert into gen_table_column ( insert into gen_table_column (
<if test="tableId != null and tableId != ''">table_id,</if> <if test="tableId != null and tableId != ''">table_id,</if>
@@ -88,7 +88,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
sysdate() sysdate()
) )
</insert> </insert>
<update id="updateGenTableColumn" parameterType="GenTableColumn"> <update id="updateGenTableColumn" parameterType="GenTableColumn">
update gen_table_column update gen_table_column
<set> <set>
@@ -111,14 +111,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update> </update>
<delete id="deleteGenTableColumnByIds" parameterType="Long"> <delete id="deleteGenTableColumnByIds" parameterType="Long">
delete from gen_table_column where table_id in delete from gen_table_column where table_id in
<foreach collection="array" item="tableId" open="(" separator="," close=")"> <foreach collection="array" item="tableId" open="(" separator="," close=")">
#{tableId} #{tableId}
</foreach> </foreach>
</delete> </delete>
<delete id="deleteGenTableColumns"> <delete id="deleteGenTableColumns">
delete from gen_table_column where column_id in delete from gen_table_column where column_id in
<foreach collection="list" item="item" open="(" separator="," close=")"> <foreach collection="list" item="item" open="(" separator="," close=")">
#{item.columnId} #{item.columnId}
</foreach> </foreach>

View File

@@ -12,6 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="subTableFkName" column="sub_table_fk_name" /> <result property="subTableFkName" column="sub_table_fk_name" />
<result property="className" column="class_name" /> <result property="className" column="class_name" />
<result property="tplCategory" column="tpl_category" /> <result property="tplCategory" column="tpl_category" />
<result property="tplWebType" column="tpl_web_type" />
<result property="packageName" column="package_name" /> <result property="packageName" column="package_name" />
<result property="moduleName" column="module_name" /> <result property="moduleName" column="module_name" />
<result property="businessName" column="business_name" /> <result property="businessName" column="business_name" />
@@ -25,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateBy" column="update_by" /> <result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" /> <result property="updateTime" column="update_time" />
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult" /> <collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult" />
</resultMap> </resultMap>
<resultMap type="GenTableColumn" id="GenTableColumnResult"> <resultMap type="GenTableColumn" id="GenTableColumnResult">
@@ -54,7 +55,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectGenTableVo"> <sql id="selectGenTableVo">
select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table
</sql> </sql>
<select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult"> <select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult">
@@ -67,10 +68,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND lower(table_comment) like lower(concat('%', #{tableComment}, '%')) AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
</if> </if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d') AND date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
</if> </if>
<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>
</where> </where>
</select> </select>
@@ -78,7 +79,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult"> <select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time from information_schema.tables select table_name, table_comment, create_time, update_time from information_schema.tables
where table_schema = (select database()) where table_schema = (select database())
AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%' AND table_name NOT LIKE 'qrtz\_%' AND table_name NOT LIKE 'gen\_%'
AND table_name NOT IN (select table_name from gen_table) AND table_name NOT IN (select table_name from gen_table)
<if test="tableName != null and tableName != ''"> <if test="tableName != null and tableName != ''">
AND lower(table_name) like lower(concat('%', #{tableName}, '%')) AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
@@ -87,17 +88,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND lower(table_comment) like lower(concat('%', #{tableComment}, '%')) AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
</if> </if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d') AND date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
</if> </if>
<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 order by create_time desc
</select> </select>
<select id="selectDbTableListByNames" resultMap="GenTableResult"> <select id="selectDbTableListByNames" resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time from information_schema.tables select table_name, table_comment, create_time, update_time from information_schema.tables
where table_name NOT LIKE 'qrtz_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database()) where table_name NOT LIKE 'qrtz\_%' and table_name NOT LIKE 'gen\_%' and table_schema = (select database())
and table_name in and table_name in
<foreach collection="array" item="name" open="(" separator="," close=")"> <foreach collection="array" item="name" open="(" separator="," close=")">
#{name} #{name}
@@ -111,7 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult"> <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -119,7 +120,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult"> <select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -127,7 +128,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult"> <select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -140,6 +141,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="tableComment != null and tableComment != ''">table_comment,</if> <if test="tableComment != null and tableComment != ''">table_comment,</if>
<if test="className != null and className != ''">class_name,</if> <if test="className != null and className != ''">class_name,</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category,</if> <if test="tplCategory != null and tplCategory != ''">tpl_category,</if>
<if test="tplWebType != null and tplWebType != ''">tpl_web_type,</if>
<if test="packageName != null and packageName != ''">package_name,</if> <if test="packageName != null and packageName != ''">package_name,</if>
<if test="moduleName != null and moduleName != ''">module_name,</if> <if test="moduleName != null and moduleName != ''">module_name,</if>
<if test="businessName != null and businessName != ''">business_name,</if> <if test="businessName != null and businessName != ''">business_name,</if>
@@ -155,6 +157,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="tableComment != null and tableComment != ''">#{tableComment},</if> <if test="tableComment != null and tableComment != ''">#{tableComment},</if>
<if test="className != null and className != ''">#{className},</if> <if test="className != null and className != ''">#{className},</if>
<if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if> <if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if>
<if test="tplWebType != null and tplWebType != ''">#{tplWebType},</if>
<if test="packageName != null and packageName != ''">#{packageName},</if> <if test="packageName != null and packageName != ''">#{packageName},</if>
<if test="moduleName != null and moduleName != ''">#{moduleName},</if> <if test="moduleName != null and moduleName != ''">#{moduleName},</if>
<if test="businessName != null and businessName != ''">#{businessName},</if> <if test="businessName != null and businessName != ''">#{businessName},</if>
@@ -180,6 +183,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="genType != null and genType != ''">gen_type = #{genType},</if> <if test="genType != null and genType != ''">gen_type = #{genType},</if>
<if test="genPath != null and genPath != ''">gen_path = #{genPath},</if> <if test="genPath != null and genPath != ''">gen_path = #{genPath},</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if> <if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if>
<if test="tplWebType != null and tplWebType != ''">tpl_web_type = #{tplWebType},</if>
<if test="packageName != null and packageName != ''">package_name = #{packageName},</if> <if test="packageName != null and packageName != ''">package_name = #{packageName},</if>
<if test="moduleName != null and moduleName != ''">module_name = #{moduleName},</if> <if test="moduleName != null and moduleName != ''">module_name = #{moduleName},</if>
<if test="businessName != null and businessName != ''">business_name = #{businessName},</if> <if test="businessName != null and businessName != ''">business_name = #{businessName},</if>

View File

@@ -71,9 +71,9 @@ public class ${ClassName} extends ${Entity}
{ {
return $column.javaField; return $column.javaField;
} }
#end
#end
#end
#end
#if($table.sub) #if($table.sub)
public List<${subClassName}> get${subClassName}List() public List<${subClassName}> get${subClassName}List()
{ {

View File

@@ -75,7 +75,7 @@
icon="el-icon-plus" icon="el-icon-plus"
size="mini" size="mini"
@click="handleAdd" @click="handleAdd"
v-hasPermi="['${moduleName}:${businessName}:add']" v-hasPermi="['${permissionPrefix}:add']"
>新增</el-button> >新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
@@ -144,21 +144,21 @@
type="text" type="text"
icon="el-icon-edit" icon="el-icon-edit"
@click="handleUpdate(scope.row)" @click="handleUpdate(scope.row)"
v-hasPermi="['${moduleName}:${businessName}:edit']" v-hasPermi="['${permissionPrefix}:edit']"
>修改</el-button> >修改</el-button>
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
icon="el-icon-plus" icon="el-icon-plus"
@click="handleAdd(scope.row)" @click="handleAdd(scope.row)"
v-hasPermi="['${moduleName}:${businessName}:add']" v-hasPermi="['${permissionPrefix}:add']"
>新增</el-button> >新增</el-button>
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
icon="el-icon-delete" icon="el-icon-delete"
@click="handleDelete(scope.row)" @click="handleDelete(scope.row)"
v-hasPermi="['${moduleName}:${businessName}:remove']" v-hasPermi="['${permissionPrefix}:remove']"
>删除</el-button> >删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
@@ -453,7 +453,7 @@ export default {
this.reset(); this.reset();
this.getTreeselect(); this.getTreeselect();
if (row != null) { if (row != null) {
this.form.${treeParentCode} = row.${treeCode}; this.form.${treeParentCode} = row.${treeParentCode};
} }
get${BusinessName}(row.${pkColumn.javaField}).then(response => { get${BusinessName}(row.${pkColumn.javaField}).then(response => {
this.form = response.data; this.form = response.data;

View File

@@ -75,7 +75,7 @@
icon="el-icon-plus" icon="el-icon-plus"
size="mini" size="mini"
@click="handleAdd" @click="handleAdd"
v-hasPermi="['${moduleName}:${businessName}:add']" v-hasPermi="['${permissionPrefix}:add']"
>新增</el-button> >新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
@@ -86,7 +86,7 @@
size="mini" size="mini"
:disabled="single" :disabled="single"
@click="handleUpdate" @click="handleUpdate"
v-hasPermi="['${moduleName}:${businessName}:edit']" v-hasPermi="['${permissionPrefix}:edit']"
>修改</el-button> >修改</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
@@ -97,7 +97,7 @@
size="mini" size="mini"
:disabled="multiple" :disabled="multiple"
@click="handleDelete" @click="handleDelete"
v-hasPermi="['${moduleName}:${businessName}:remove']" v-hasPermi="['${permissionPrefix}:remove']"
>删除</el-button> >删除</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
@@ -107,7 +107,7 @@
icon="el-icon-download" icon="el-icon-download"
size="mini" size="mini"
@click="handleExport" @click="handleExport"
v-hasPermi="['${moduleName}:${businessName}:export']" v-hasPermi="['${permissionPrefix}:export']"
>导出</el-button> >导出</el-button>
</el-col> </el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -158,14 +158,14 @@
type="text" type="text"
icon="el-icon-edit" icon="el-icon-edit"
@click="handleUpdate(scope.row)" @click="handleUpdate(scope.row)"
v-hasPermi="['${moduleName}:${businessName}:edit']" v-hasPermi="['${permissionPrefix}:edit']"
>修改</el-button> >修改</el-button>
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
icon="el-icon-delete" icon="el-icon-delete"
@click="handleDelete(scope.row)" @click="handleDelete(scope.row)"
v-hasPermi="['${moduleName}:${businessName}:remove']" v-hasPermi="['${permissionPrefix}:remove']"
>删除</el-button> >删除</el-button>
</template> </template>
</el-table-column> </el-table-column>

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