237 Commits
v2.0 ... v2.5.0

Author SHA1 Message Date
RuoYi
31aefd15f7 若依 2.5.0 2021-02-02 13:31:07 +08:00
RuoYi
716405d22b update README.md 2021-02-02 11:40:58 +08:00
RuoYi
3c33fe21fa 升级spring-boot-alibaba到最新版2.2.5 2021-02-02 11:38:53 +08:00
DokiYoloo
e1e05b761a !43 角色管理-编辑角色-功能权限显示异常
Merge pull request !43 from wihi/master
2021-01-30 17:19:23 +08:00
wanghuan
1804d4fce6 修复角色管理-编辑角色-功能权限显示异常 2021-01-30 16:35:11 +08:00
RuoYi
0dcc955ced 更换过期的共享配置属性 2021-01-27 18:00:50 +08:00
RuoYi
53076612f8 增加分布式事务seata支持 2021-01-27 10:47:01 +08:00
RuoYi
f0f2cde0f9 update swagger 2021-01-26 10:52:31 +08:00
若依
0546504cae !39 修复sentinel流量告警无提示
Merge pull request !39 from 9527/master
2021-01-26 10:50:09 +08:00
RuoYi
17652c3a2f 升级element-ui到最新版本2.15.0 2021-01-24 12:07:17 +08:00
ysj
e96d12975f 修复sentinel流量告警前端不响应 2021-01-21 10:06:29 +08:00
RuoYi
a9b51070ed 添加启动执行脚本 2021-01-17 12:06:33 +08:00
若依
a8c7b9102b !38 返回绝对路径
Merge pull request !38 from xlongwei/N/A
2021-01-17 12:02:21 +08:00
xlongwei
ca04b583a9 返回绝对路径 2021-01-16 13:25:39 +08:00
RuoYi
99390fda05 update README.md 2021-01-14 13:22:53 +08:00
RuoYi
be12108139 修复生成树表代码异常 2021-01-13 17:53:28 +08:00
RuoYi
1dec234174 升级spring-boot到最新版本2.3.7 2021-01-13 17:46:26 +08:00
RuoYi
3fc684b346 升级spring-cloud到Hoxton.SR9 2021-01-13 17:45:30 +08:00
RuoYi
12d8add3e4 升级spring-boot-admin到最新版2.3.1 2021-01-13 17:43:49 +08:00
RuoYi
372b77c662 升级fastjson到最新版1.2.75 2021-01-13 17:24:26 +08:00
RuoYi
5891960756 代码生成模板支持主子表 2021-01-08 11:08:59 +08:00
RuoYi
c5899bfbf0 修复导入数据为负浮点数时丢失精度问题 2021-01-08 11:08:43 +08:00
RuoYi
4125f44179 用户显隐列添加不同key防止被复用 2021-01-07 13:37:33 +08:00
RuoYi
09a0058379 表格右侧工具栏组件支持显隐列 2021-01-06 17:50:01 +08:00
RuoYi
3fdcac939a 升级druid到最新版本v1.2.4 2021-01-06 12:01:05 +08:00
RuoYi
4cc4e8a8fa 编码解码用户名,防止中文出现乱码 2021-01-06 12:00:41 +08:00
RuoYi
dee4653b9b 代码生成支持文件上传组件 2021-01-06 11:59:13 +08:00
若依
2c610dc465 !37 update ruoyi-ui/src/components/FileUpload/index.vue.
Merge pull request !37 from ouwei2020/N/A
2021-01-06 11:57:57 +08:00
ouwei2020
2c323ca3ff update ruoyi-ui/src/components/FileUpload/index.vue. 2021-01-06 11:09:23 +08:00
DokiYolo
a95be9d418 解决header获取username中文情况下乱码 2021-01-06 10:06:57 +08:00
RuoYi
a445462153 代码生成支持文件上传组件 2021-01-05 21:34:27 +08:00
RuoYi
843f08984b 图片组件添加预览&移除功能 2021-01-05 21:08:01 +08:00
RuoYi
2b3820223c 修复IE11浏览器报错问题 2021-01-05 10:30:05 +08:00
RuoYi
a5d0028b39 操作按钮组调整为朴素按钮样式 2021-01-05 10:20:26 +08:00
RuoYi
449704180b Update copyright 2021-01-04 17:54:40 +08:00
JuJu
8a18873b81 修正操作日志删除接口路径 2021-01-01 12:47:46 +08:00
JuJu
199228f6cb spring.factories增加RemoteFileFallbackFactory 2021-01-01 12:41:28 +08:00
RuoYi
6d0b4f5d16 用户手机邮箱&菜单组件修改允许空字符串 2020-12-29 17:29:22 +08:00
RuoYi
4cc0e2650c 代码生成数据库文本类型生成表单文本域 2020-12-29 11:27:58 +08:00
RuoYi
06074571f5 Excel注解支持Image图片导出 2020-12-27 10:05:28 +08:00
RuoYi
2698ea58d4 代码生成日期控件区分范围 2020-12-25 09:36:22 +08:00
RuoYi
8c9eb7d6b6 修正侧边栏静态路由丢失问题 2020-12-24 19:33:09 +08:00
RuoYi
79472708d9 防止get请求参数值为false或0等特殊值会导致无法正确的传参 2020-12-22 16:25:16 +08:00
RuoYi
586908b4d7 若依 2.4.0 2020-12-22 10:40:42 +08:00
RuoYi
a7bac940a6 若依 2.4.0 2020-12-22 10:21:29 +08:00
RuoYi
e100b0b940 增加分布式文件Minio支持 2020-12-22 09:30:07 +08:00
DokiYoloo
161a6249e9 权限工具类增加admin判断 2020-12-21 19:11:25 +08:00
RuoYi
e4a6e5ef19 支持多数据源切换 2020-12-21 16:32:00 +08:00
RuoYi
7ceb5c3c8f 优化多级菜单之间切换无法缓存的问题 2020-12-21 11:13:35 +08:00
RuoYi
bfd33f2412 移除path-to-regexp正则匹配插件 2020-12-17 12:07:26 +08:00
RuoYi
2ddf834552 优化多级菜单之间切换无法缓存的问题 2020-12-16 22:19:30 +08:00
RuoYi
743e59db7a README 2020-12-15 10:15:27 +08:00
RuoYi
88aff5d8a7 登录后push添加catch防止出现检查错误 2020-12-15 10:15:06 +08:00
JuJu
dbeded1c09 修复导出时携带params空对象导致转换错误 2020-12-14 16:09:15 +08:00
DokiYoloo
e4b8898d0d !30 修正代码生成下载zip路径
Merge pull request !30 from 我啊/master
2020-12-14 14:41:00 +08:00
isme
62d7ce5aa9 修复代码生成路径错误 2020-12-14 14:38:31 +08:00
RuoYi
7fd4860ff2 调整代码生成页列宽 2020-12-13 17:08:51 +08:00
RuoYi
ff979772fe 修改Set可能导致嵌套的问题 2020-12-13 17:08:30 +08:00
RuoYi
dddbaeb5c0 代码生成预览支持高亮显示 2020-12-11 18:01:44 +08:00
RuoYi
3c5893074b 去除用户手机邮箱部门必填验证 2020-12-11 17:40:28 +08:00
RuoYi
2cde98a01b 日志记录增加过滤多文件场景 2020-12-11 17:24:31 +08:00
RuoYi
22a825718f 项目添加robots.txt 防止系统被搜索引擎收录 2020-12-10 18:42:44 +08:00
RuoYi
a0bcaab926 前端更新插件版本 2020-12-10 18:37:44 +08:00
RuoYi
1c023fbe7c 升级vue-router到最新版本3.4.9 2020-12-10 11:28:27 +08:00
RuoYi
81bf4d28ef 代码生成预览提供滚动机制 2020-12-10 11:27:52 +08:00
RuoYi
963d2a0443 代码生成预览溢出提供滚动机制 2020-12-09 10:34:53 +08:00
RuoYi
10de0df04e 删除用户和角色解绑关联 2020-12-08 19:37:07 +08:00
RuoYi
1066713524 关闭页签清理缓存数据 2020-12-08 19:35:29 +08:00
RuoYi
bcdf270bb0 回显数据字典防止空值报错 2020-12-08 19:34:52 +08:00
RuoYi
0673eab57e 支持主题风格配置 2020-12-07 14:25:11 +08:00
RuoYi
5094bba352 修改用户头像预览宽高 2020-12-06 17:47:25 +08:00
RuoYi
7523e0475f get请求params添加null值判断 2020-12-06 11:06:32 +08:00
RuoYi
6beb5cadb2 支持get请求映射params参数 2020-12-04 10:53:25 +08:00
RuoYi
5174536744 升级poi到最新版本4.1.2 2020-12-03 13:36:30 +08:00
RuoYi
e526e33030 Excel支持注解align对齐方式 2020-12-03 13:34:04 +08:00
RuoYi
e8b19f863a 防止安全扫描YUI出现的风险提示 2020-12-03 10:27:45 +08:00
RuoYi
91a2f7b16b 获取请求token方法移至权限工具类 2020-11-30 15:09:59 +08:00
RuoYi
3a9d45a7dd 服务之间feign调用传递用户请求头 2020-11-30 12:49:18 +08:00
RuoYi
073d90ed17 设置用户头像悬停高度 2020-11-30 11:08:23 +08:00
RuoYi
689d5b1612 升级element-ui到最新版本v2.14.1 2020-11-30 10:51:10 +08:00
RuoYi
c615248ad6 修正转换字符串的目标字符集属性 2020-11-30 10:50:24 +08:00
RuoYi
20f85a37cf 三级菜单自动配置组件 2020-11-28 21:19:14 +08:00
RuoYi
b199db3822 修复三级菜单之间切换页面无法缓存的问题 2020-11-28 21:08:02 +08:00
RuoYi
f5bf01159a 代码生成删除多余的数字float类型 2020-11-28 13:21:24 +08:00
RuoYi
2c05324108 Excel支持导入Boolean型数据 2020-11-28 13:21:02 +08:00
RuoYi
6b36b32e39 删除多余的依赖 2020-11-25 18:22:57 +08:00
RuoYi
1abf802a09 若依 2.3.0 2020-11-20 08:39:32 +08:00
RuoYi
2bb0eb1edb 增加分布式文件FastDFS支持 2020-11-19 18:51:22 +08:00
RuoYi
1a12d56cc3 优化头像样式,鼠标移入悬停遮罩 2020-11-19 09:33:32 +08:00
RuoYi
45e5395550 README 2020-11-18 20:29:13 +08:00
RuoYi
692cc84693 README 2020-11-18 20:15:17 +08:00
RuoYi
b812e01100 新增文件服务应用 2020-11-18 19:57:55 +08:00
RuoYi
594329127f AjaxResult重写put方法,以方便链式调用 2020-11-18 16:49:01 +08:00
RuoYi
cdc813c453 代码生成支持上传控件 2020-11-17 20:37:41 +08:00
RuoYi
3d7fe2a288 Copyright 2018-2020 2020-11-17 20:36:59 +08:00
RuoYi
93920707b5 新增图片上传组件 2020-11-17 18:57:20 +08:00
RuoYi
84ffdaefe2 调整默认首页 2020-11-07 11:06:22 +08:00
RuoYi
0422f858f7 2020年双十一云服务器优惠,错过又要等一年 2020-11-04 16:59:04 +08:00
RuoYi
01c3019cea 添加新群号 2020-11-04 12:09:11 +08:00
JuJu
24fb0b712a 删除无用代码 2020-10-26 15:39:36 +08:00
JuJu
8fdcd576c9 修正hasRole匹配为equals 2020-10-23 16:00:39 +08:00
JuJu
07df90f99b 修正数组权限为空判断 2020-10-23 15:33:06 +08:00
RuoYi
7bd762267b 删除不必要的代码 2020-10-13 17:58:36 +08:00
RuoYi
512c6acebc 修正注释选中节点和半选节点获取 2020-10-13 17:44:56 +08:00
RuoYi
ad928ee719 升级pagehelper到最新版1.3.0 2020-10-13 17:43:03 +08:00
RuoYi
04d8c97c62 升级fastjson到最新版1.2.74 2020-10-13 17:41:28 +08:00
RuoYi
9fc5d74f6e 使用参数dateRange而非bind在model上的dateRange 2020-10-13 17:40:36 +08:00
RuoYi
f245105b58 修正定时任务执行一次权限标识 2020-10-13 17:39:25 +08:00
RuoYi
c0cc0bdf84 修复页签关闭所有固定标签路由不刷新问题 2020-10-13 17:38:38 +08:00
RuoYi
c04ea0221a 表单构建布局型组件新增按钮 2020-10-13 17:37:12 +08:00
RuoYi
d52f6673bc 调整日志路径到模块目录 2020-10-13 17:36:00 +08:00
RuoYi
dce8ee4236 修正菜单提示信息错误 2020-10-03 10:54:47 +08:00
RuoYi
f64f806a42 若依 2.2 2020-09-30 09:54:59 +08:00
RuoYi
fbbc91ea0a 左侧菜单文字过长显示省略号 2020-09-24 19:22:29 +08:00
RuoYi
c2bd0ace6e 修改用户个人资料/密码同步缓存信息 2020-09-24 16:29:21 +08:00
RuoYi
6b74ea676c 修正文字错误 2020-09-24 16:28:43 +08:00
RuoYi
86c7a763bc 新增在线用户管理 2020-09-24 15:44:43 +08:00
JuJu
3eac04311f Job与Gen增加默认Redis配置 2020-09-24 14:52:42 +08:00
RuoYi
679756c633 菜单新增是否缓存keep-alive 2020-09-24 14:06:19 +08:00
RuoYi
2115edcfb4 菜单新增是否缓存keep-alive 2020-09-21 18:29:44 +08:00
RuoYi
8acb322d49 菜单&数据权限新增(展开/折叠 全选/全不选 父子联动) 2020-09-21 18:10:26 +08:00
RuoYi
f4f89f9de6 代码生成支持同步数据库 2020-09-21 13:25:51 +08:00
RuoYi
5d1701fd69 表格操作列间距调整 2020-09-21 12:35:48 +08:00
RuoYi
fec24d6785 导入excel整形值校验优化 2020-09-21 12:35:32 +08:00
RuoYi
00e2c7f04d 升级Spring Cloud组件到最新版 2020-09-21 12:27:11 +08:00
RuoYi
0e21fab978 token续期调整到后端&默认有效期延长 2020-09-17 12:44:05 +08:00
RuoYi
4ec6d51aca 全局异常处理 2020-09-17 12:08:30 +08:00
RuoYi
15a4a5350d 修复前端通用导出方法参数传值请求方式问题 2020-09-17 11:41:35 +08:00
RuoYi
9277d7173d 代码生成支持富文本控件 2020-09-15 17:21:40 +08:00
RuoYi
35f8665dac 限制系统内置参数不允许删除 2020-09-15 17:12:51 +08:00
RuoYi
8601b26cff 退出登录接口加入白名单&修改过期提示 2020-09-15 17:10:37 +08:00
RuoYi
eda2ec10f4 修复通知公告longblob类型乱码问题 2020-09-13 12:06:59 +08:00
RuoYi
fc48704057 富文本工具栏样式对齐 2020-09-13 12:06:46 +08:00
RuoYi
00345099cf 修正注释图标路径 2020-09-13 12:06:10 +08:00
RuoYi
155b585ae5 Excel导出类型NUMERIC支持精度浮点类型 2020-09-13 12:05:49 +08:00
RuoYi
0ffefeb420 修正调用目标字符串最大长度 2020-09-13 12:05:26 +08:00
RuoYi
dbadce31c6 网关白名单放入nacos配置&支持模糊匹配 2020-09-13 12:00:46 +08:00
RuoYi
857a5b26e7 数据权限中的空值处理 2020-09-04 18:36:58 +08:00
RuoYi
ca97fc0b5d 数据字典缓存空值处理 2020-09-04 18:34:50 +08:00
RuoYi
293b855866 修复Editor组件无法对响应式更新null值问题 2020-09-04 18:32:25 +08:00
RuoYi
f9d537b567 修改自定义权限实现 2020-09-04 18:31:08 +08:00
RuoYi
d42a6751e3 修改公告内容字段类型 2020-09-01 18:08:38 +08:00
RuoYi
f124236a64 remove \ 2020-09-01 16:03:51 +08:00
RuoYi
6704db8108 移除 OAuth2 改为 Redis 2020-09-01 13:31:00 +08:00
RuoYi
179062e6e5 数据权限判断对象类型 2020-08-31 23:25:44 +08:00
RuoYi
397e821824 修正数据库字符串类型nvarchar 2020-08-31 23:24:38 +08:00
RuoYi
6b25828113 优化递归子节点 2020-08-31 23:23:08 +08:00
RuoYi
bd7ae4e96b 代码生成树模板去掉多余双引号 2020-08-31 23:19:29 +08:00
RuoYi
5f5c4e8415 remove pig 2020-08-31 17:55:18 +08:00
RuoYi
66e8b59e26 导出Excel调整targetAttr获取值方法,防止get方法不规范 2020-08-26 15:40:00 +08:00
RuoYi
a7d93d38ce 生成页面时不忽略remark属性 2020-08-26 14:25:41 +08:00
RuoYi
ba3549e824 Excel注解支持自动统计数据总和 2020-08-26 11:44:58 +08:00
RuoYi
e2c4ca4922 终端授权类型改为复选框 2020-08-23 10:51:46 +08:00
RuoYi
1305507bf4 终端设置新增明文安全码 2020-08-22 22:00:50 +08:00
RuoYi
c789ef147d Editor组件优化,支持自定义高度&图片冲突问题 2020-08-22 14:59:44 +08:00
RuoYi
1ffee7ac0b 修改公告内容字段类型 2020-08-22 14:59:04 +08:00
RuoYi
7401297236 Excel注解支持设置BigDecimal精度&舍入规则 2020-08-22 14:56:28 +08:00
RuoYi
6f1dd1125b 升级fastjson到最新版1.2.73 2020-08-22 14:53:27 +08:00
RuoYi
82eeb86d60 代码生成添加select必填选项 2020-08-22 14:52:37 +08:00
RuoYi
f5cee45345 修改sass为node-sass,避免el-icon图标乱码 2020-08-22 14:49:47 +08:00
RuoYi
1524005060 修复根节点为子部门时,树状结构显示问题 2020-08-22 14:47:44 +08:00
RuoYi
b70e8ad81e 表格右侧工具栏组件 2020-08-13 14:11:00 +08:00
RuoYi
a2c265848d 优化上级菜单不能选择自己 2020-08-13 13:52:40 +08:00
RuoYi
19e5f11fd3 修复代码生成下载路径错误 2020-08-13 11:04:55 +08:00
RuoYi
8d99adceb2 唯一限制条件只返回单条数据 2020-08-07 17:32:33 +08:00
RuoYi
f1bc33e80d 唯一限制条件只返回单条数据 2020-08-07 16:42:36 +08:00
RuoYi
6bd7e183f6 修复富文本空格和缩进保存后不生效问题&删除重复的placeholder 2020-08-07 16:39:14 +08:00
RuoYi
7d94113d24 修改Excel设置STRING单元格类型 2020-08-05 13:02:57 +08:00
RuoYi
97ebab0c67 全局异常状态汉化拦截处理 2020-08-03 09:29:26 +08:00
RuoYi
cca4eeae72 若依 2.1 2020-08-02 09:59:55 +08:00
RuoYi
bf544deeaa 表格工具栏右侧添加刷新&显隐查询栏 2020-08-01 19:25:18 +08:00
RuoYi
a9a1200e77 升级vue-cli版本到4.4.4 2020-08-01 19:09:08 +08:00
RuoYi
43bc0ca39b OAuth自动刷新续签Token 2020-08-01 18:28:30 +08:00
RuoYi
c0251e5cda 修复验证码异常时network面板的中文会出现乱码问题 2020-07-31 15:32:54 +08:00
RuoYi
56ea7c9caf 代码生成支持自定义路径 2020-07-31 11:45:27 +08:00
RuoYi
17d5751452 表单类型为Integer/Long设置整形默认值 2020-07-30 21:50:01 +08:00
RuoYi
8b5a16c692 权限修正(角色导出权限) 2020-07-30 21:46:24 +08:00
RuoYi
5524d5a0bc 修改 node-sass 为 dart-sass 2020-07-30 21:44:53 +08:00
RuoYi
40696a10e3 excel 导入数字不需要格式化 ,导入允许列和属性个数不一致 2020-07-30 21:42:34 +08:00
RuoYi
9bfe3e5328 Excel支持分割字符串组内容 2020-07-30 21:23:42 +08:00
RuoYi
165a957d67 代码生成支持复选框 2020-07-30 21:17:54 +08:00
RuoYi
4a48702ccb 检查字符支持小数点&降级改成异常提醒 2020-07-30 18:30:30 +08:00
RuoYi
41cf67da6d 验证码类型支持(数组计算、字符验证) 2020-07-30 18:26:52 +08:00
RuoYi
4126f634ba 优化selectDictLabel方法,数组迭代器换为some 2020-07-30 18:14:51 +08:00
RuoYi
337f6fd3db 修复 utils/index.js 中不包含 parseTime 函数的 bug 2020-07-30 18:12:39 +08:00
RuoYi
8ae4f5e90b 代码生成支持选择上级菜单 2020-07-30 18:09:44 +08:00
RuoYi
7e2b00b98b 代码生成查询条件修正 2020-07-30 17:33:54 +08:00
RuoYi
57200e5f5d 禁止加密密文返回前端 2020-07-30 17:30:50 +08:00
RuoYi
bb5b7466db 升级element-ui版本到2.13.2 2020-07-30 17:29:17 +08:00
RuoYi
c7908e1b8e 删除babel,提高编译速度。 2020-07-30 17:26:47 +08:00
RuoYi
76aa1cda49 新增菜单默认主类目 2020-07-30 17:24:41 +08:00
RuoYi
59b8df2e90 定时任务cron表达式验证 2020-07-30 17:16:05 +08:00
RuoYi
5967f7f11b 验证码高度调整 2020-07-30 16:48:00 +08:00
若依
3ad7742838 !12 修改import引入语句缺少分号问题
Merge pull request !12 from jingfeng/master
2020-07-22 16:02:26 +08:00
jingfeng
3a400df707 update ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/serviceImpl.java.vm.
修改import引入语句缺少分号问题
2020-07-21 14:26:08 +08:00
RuoYi
4a608d4d21 添加新群号 2020-07-13 09:21:48 +08:00
RuoYi
d9ebddc669 防止切换权限用户后登录出现404 2020-07-11 09:42:25 +08:00
RuoYi
7ecba12d3e 角色权限修改时已有权限未自动勾选异常修复 2020-07-11 09:41:30 +08:00
RuoYi
30aa0c4fca 修复客户端模式认证会出现错误 2020-07-11 09:20:13 +08:00
若依
99e1dfccf5 !9 RedisCache中所有方法参数添加final,并优化list取出效率,添加其它常用redis方法
Merge pull request !9 from 心悦李国楠/dev-心悦
2020-07-11 09:00:46 +08:00
RuoYi
d0fe6f3462 权限配置自动注册 2020-07-10 15:53:34 +08:00
RuoYi
c404c280f7 Excel支持sort导出排序 2020-07-10 15:12:20 +08:00
RuoYi
7bdd20ec24 修改文件编码格式utf-8 2020-07-04 15:59:53 +08:00
心悦李国楠
e0ba2ba3c5 RedisCache中所有方法参数添加final,并优化list取出效率,添加其它常用redis方法 2020-07-03 14:24:33 +08:00
RuoYi
e76d087b21 终端设置更新缓存 2020-07-02 20:26:04 +08:00
RuoYi
394bde5e8e 终端设置安全码加密 2020-07-02 20:25:11 +08:00
RuoYi
ad690bac17 修复头像上传成功二次打开无法改变裁剪框大小和位置问题 2020-07-02 16:18:07 +08:00
RuoYi
e056ab0cc2 修复布局为small者mini用户表单显示错位问题 2020-07-02 16:14:16 +08:00
RuoYi
7549c904f2 修复代码生成点击多次表修改数据不变化的问题 2020-07-02 16:12:03 +08:00
RuoYi
0788a57bec 修复代码生成导入表结构出现异常页面不提醒问题 2020-07-02 16:09:07 +08:00
RuoYi
eb48d6a80f 创建用户不允许选择系统管理员角色 2020-07-02 16:02:00 +08:00
RuoYi
1b70ef990b 全局异常处理(网关异常&业务异常) 2020-07-02 15:51:45 +08:00
RuoYi
e48d75afcd 修复Enter键搜索时是刷新页面而不是查询列表 2020-07-02 09:26:06 +08:00
RuoYi
c91194f848 Feign配置自动注册 2020-06-27 22:01:27 +08:00
RuoYi
a90b576116 删除job重复表单参数 2020-06-19 22:19:51 +08:00
Sxile
9aee5142fd 修复注释与参数名称不对应 2020-06-19 17:30:39 +08:00
Sxile
230d4170e1 添加缺少的@Override 2020-06-19 17:28:36 +08:00
Sxile
2c250c1d1b 添加{}使代码更容易理解 2020-06-19 17:26:37 +08:00
Sxile
7302189208 常量接口修改为常量类 2020-06-19 17:24:33 +08:00
Sxile
a40d691e31 添加@Component注解 2020-06-19 17:18:49 +08:00
Sxile
719e1fdfa9 添加@Component注解 2020-06-19 17:18:05 +08:00
Sxile
5734607e03 删除多余的代码 2020-06-19 16:40:01 +08:00
Sxile
e6e673e9b4 删除已经注释掉的代码 2020-06-19 16:09:27 +08:00
Sxile
6eda1dee45 常量接口修改为常量类 2020-06-19 16:05:51 +08:00
RuoYi
ff3bc4b25e 代码生成浮点型改用BigDecimal 2020-06-18 18:54:20 +08:00
RuoYi
aa13a1d88e 修改用户管理复选框宽度,防止部分浏览器出现省略号 2020-06-18 18:48:52 +08:00
RuoYi
beaf17ddd2 修正定时任务日志权限字符 2020-06-18 18:47:34 +08:00
RuoYi
029fe5c63f 添加Jackson时区配置 2020-06-18 18:28:00 +08:00
RuoYi
bea2f32781 修正代码生成时间工具类包路径 2020-06-18 17:45:12 +08:00
RuoYi
3b88fe2b6f 更新nacos配置脚本 2020-06-18 17:38:16 +08:00
RuoYi
97a0326abd 修改树表代码生成模板 2020-06-14 15:39:43 +08:00
RuoYi
83460dfcb6 修改单表代码生成模板 2020-06-14 15:30:58 +08:00
RuoYi
6ee4efa284 自定义oauth2返回异常信息 2020-06-14 14:47:47 +08:00
RuoYi
1c06db8999 升级nacos到最新版1.3.0 全新内核构建 2020-06-13 12:32:47 +08:00
若依
5fa7b3f579 !6 修正【代码生成】功能无法下载的问题
Merge pull request !6 from 覃盛春/master
2020-06-13 12:03:15 +08:00
RuoYi
bb305dfb6f 网关支持黑名单配置 2020-06-13 11:54:53 +08:00
qinsc
5585a4e807 * 修正【代码生成】无法下载的Bug,主要是URL并接少了斜杠导致URL路径不对; 2020-06-11 18:42:56 +08:00
299 changed files with 10555 additions and 5003 deletions

View File

@@ -1,13 +1,16 @@
## 平台简介 ## 平台简介
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
* 采用前后端分离的模式,微服务版本前端(基于 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue))。 * 采用前后端分离的模式,微服务版本前端(基于 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue))。
* 后端采用Spring Boot、Spring Cloud & Alibaba。 * 后端采用Spring Boot、Spring Cloud & Alibaba。
* 注册中心、配置中心选型Nacos权限认证使用OAuth2 * 注册中心、配置中心选型Nacos权限认证使用Redis
* 流量控制框架选型Sentinel。 * 流量控制框架选型Sentinel分布式事务选型Seata
* 感谢[ruoyi-cloud-design](https://gitee.com/zhangmrit/ruoyi-cloud)[pig](https://gitee.com/log4j/pig)。
* 如需不分离应用,请移步 [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)
* 阿里云优惠券[点我进入](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)   * 阿里云折扣场[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场[点我进入](http://txy.ruoyi.vip)  
* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)  
#### 友情链接 [若依/RuoYi-Cloud](https://gitee.com/zhangmrit/ruoyi-cloud) Ant Design版本。
## 系统模块 ## 系统模块
@@ -21,6 +24,7 @@ com.ruoyi
├── ruoyi-common // 通用模块 ├── ruoyi-common // 通用模块
│ └── ruoyi-common-core // 核心模块 │ └── ruoyi-common-core // 核心模块
│ └── ruoyi-common-datascope // 权限范围 │ └── ruoyi-common-datascope // 权限范围
│ └── ruoyi-common-datasource // 多数据源
│ └── ruoyi-common-log // 日志记录 │ └── ruoyi-common-log // 日志记录
│ └── ruoyi-common-redis // 缓存服务 │ └── ruoyi-common-redis // 缓存服务
│ └── ruoyi-common-security // 安全模块 │ └── ruoyi-common-security // 安全模块
@@ -29,6 +33,7 @@ com.ruoyi
│ └── ruoyi-system // 系统模块 [9201] │ └── ruoyi-system // 系统模块 [9201]
│ └── ruoyi-gen // 代码生成 [9202] │ └── ruoyi-gen // 代码生成 [9202]
│ └── ruoyi-job // 定时任务 [9203] │ └── ruoyi-job // 定时任务 [9203]
│ └── ruoyi-file // 文件服务 [9300]
├── ruoyi-visual // 图形化管理模块 ├── ruoyi-visual // 图形化管理模块
│ └── ruoyi-visual-monitor // 监控中心 [9100] │ └── ruoyi-visual-monitor // 监控中心 [9100]
├──pom.xml // 公共依赖 ├──pom.xml // 公共依赖
@@ -36,7 +41,7 @@ com.ruoyi
## 架构图 ## 架构图
<img src="https://oscimg.oschina.net/oscnet/up-aaa2d885b0fba37e52b56f0948edde1c4fe.png"/> <img src="https://oscimg.oschina.net/oscnet/up-82e9722ecb846786405a904bafcf19f73f3.png"/>
## 内置功能 ## 内置功能
@@ -74,27 +79,27 @@ com.ruoyi
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/707825ad3f29de74a8d6d02fbd73ad631ea.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/46be40cc6f01aa300eed53a19b5012bf484.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/4284796d4cea240d181b8f2201813dda710.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/3ecfac87a049f7fe36abbcaafb2c40d36cf.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-4148b24f58660a9dc347761e4cf6162f28f.png"/></td>
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/71c2d48905221a09a728df4aff4160b8607.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/c14c1ee9a64a6a9c2c22f67d43198767dbe.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/fdea1d8bb8625c27bf964176a2c8ebc6945.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/509d2708cfd762b6e6339364cac1cc1970c.jpg"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-f1fd681cc9d295db74e85ad6d2fe4389454.png"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
</tr> </tr>
<tr> <tr>
@@ -102,12 +107,12 @@ com.ruoyi
<td><img src="https://oscimg.oschina.net/oscnet/up-92ffb7f3835855cff100fa0f754a6be0d99.png"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-92ffb7f3835855cff100fa0f754a6be0d99.png"/></td>
</tr> </tr>
<tr> <tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-d69a19493e5aff7ecdd5d02410862bcea40.png"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-ff9e3066561574aca73005c5730c6a41f15.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-6d73c2140ce694e3de4c05035fdc1868d4c.png"/></td> <td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
</tr> </tr>
</table> </table>
## 若依微服务交流群 ## 若依微服务交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) 点击按钮入群。 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) 点击按钮入群。

14
bin/run-auth.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
echo.
echo [信息] 运行auth工程。
echo.
cd %~dp0
cd ../ruoyi-auth/target
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -Dfile.encoding=utf-8 -jar %JAVA_OPTS% ruoyi-auth.jar
cd bin
pause

14
bin/run-gateway.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
echo.
echo [信息] 运行gateway工程。
echo.
cd %~dp0
cd ../ruoyi-gateway/target
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -Dfile.encoding=utf-8 -jar %JAVA_OPTS% ruoyi-gateway.jar
cd bin
pause

14
bin/run-modules-file.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
echo.
echo [信息] 运行modules-file工程。
echo.
cd %~dp0
cd ../ruoyi-modules/ruoyi-file/target
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -Dfile.encoding=utf-8 -jar %JAVA_OPTS% ruoyi-modules-file.jar
cd bin
pause

14
bin/run-modules-gen.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
echo.
echo [信息] 运行modules-gen工程。
echo.
cd %~dp0
cd ../ruoyi-modules/ruoyi-gen/target
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -Dfile.encoding=utf-8 -jar %JAVA_OPTS% ruoyi-modules-gen.jar
cd bin
pause

14
bin/run-modules-job.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
echo.
echo [信息] 运行modules-job工程。
echo.
cd %~dp0
cd ../ruoyi-modules/ruoyi-job/target
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -Dfile.encoding=utf-8 -jar %JAVA_OPTS% ruoyi-modules-job.jar
cd bin
pause

View File

@@ -0,0 +1,14 @@
@echo off
echo.
echo [信息] 运行modules-system工程。
echo.
cd %~dp0
cd ../ruoyi-modules/ruoyi-system/target
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -Dfile.encoding=utf-8 -jar %JAVA_OPTS% ruoyi-modules-system.jar
cd bin
pause

14
bin/run-monitor.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
echo.
echo [信息] 运行monitor工程。
echo.
cd %~dp0
cd ../ruoyi-visual/ruoyi-monitor/target
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -Dfile.encoding=utf-8 -jar %JAVA_OPTS% ruoyi-visual-monitor.jar
cd bin
pause

39
pom.xml
View File

@@ -6,30 +6,35 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>2.0.0</version> <version>2.5.0</version>
<name>ruoyi</name> <name>ruoyi</name>
<url>http://www.ruoyi.vip</url> <url>http://www.ruoyi.vip</url>
<description>若依微服务系统</description> <description>若依微服务系统</description>
<properties> <properties>
<ruoyi.version>2.0.0</ruoyi.version> <ruoyi.version>2.5.0</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<spring-boot.version>2.2.6.RELEASE</spring-boot.version> <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR4</spring-cloud.version> <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-boot-admin.version>2.2.3</spring-boot-admin.version> <spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
<spring-boot.mybatis>2.1.2</spring-boot.mybatis> <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
<spring-boot.mybatis>2.1.3</spring-boot.mybatis>
<swagger.fox.version>2.9.2</swagger.fox.version> <swagger.fox.version>2.9.2</swagger.fox.version>
<swagger.core.version>1.5.24</swagger.core.version> <swagger.core.version>1.5.24</swagger.core.version>
<tobato.version>1.26.5</tobato.version>
<kaptcha.version>2.3.2</kaptcha.version> <kaptcha.version>2.3.2</kaptcha.version>
<pagehelper.boot.version>1.2.12</pagehelper.boot.version> <pagehelper.boot.version>1.3.0</pagehelper.boot.version>
<druid.version>1.2.4</druid.version>
<dynamic-ds.version>3.2.1</dynamic-ds.version>
<commons.io.version>2.5</commons.io.version> <commons.io.version>2.5</commons.io.version>
<commons.fileupload.version>1.3.3</commons.fileupload.version> <commons.fileupload.version>1.3.3</commons.fileupload.version>
<velocity.version>1.7</velocity.version> <velocity.version>1.7</velocity.version>
<fastjson.version>1.2.70</fastjson.version> <fastjson.version>1.2.75</fastjson.version>
<poi.version>3.17</poi.version> <minio.version>8.0.3</minio.version>
<poi.version>4.1.2</poi.version>
<common-pool.version>2.6.2</common-pool.version> <common-pool.version>2.6.2</common-pool.version>
</properties> </properties>
@@ -50,7 +55,7 @@
<dependency> <dependency>
<groupId>com.alibaba.cloud</groupId> <groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId> <artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version> <version>${spring-cloud-alibaba.version}</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@@ -71,6 +76,13 @@
<version>${spring-boot-admin.version}</version> <version>${spring-boot-admin.version}</version>
</dependency> </dependency>
<!-- FastDFS 分布式文件系统 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${tobato.version}</version>
</dependency>
<!-- Mybatis 依赖配置 --> <!-- Mybatis 依赖配置 -->
<dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId> <groupId>org.mybatis.spring.boot</groupId>
@@ -174,6 +186,13 @@
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<!-- 多数据源 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datasource</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>2.0.0</version> <version>2.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -0,0 +1,29 @@
package com.ruoyi.system.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.domain.SysFile;
import com.ruoyi.system.api.factory.RemoteFileFallbackFactory;
/**
* 文件服务
*
* @author ruoyi
*/
@FeignClient(contextId = "remoteFileService", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class)
public interface RemoteFileService
{
/**
* 上传文件
*
* @param file 文件信息
* @return 结果
*/
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file);
}

View File

@@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.factory.RemoteUserFallbackFactory; import com.ruoyi.system.api.factory.RemoteUserFallbackFactory;
import com.ruoyi.system.api.model.UserInfo; import com.ruoyi.system.api.model.LoginUser;
/** /**
* 用户服务 * 用户服务
@@ -23,5 +23,5 @@ public interface RemoteUserService
* @return 结果 * @return 结果
*/ */
@GetMapping(value = "/user/info/{username}") @GetMapping(value = "/user/info/{username}")
public R<UserInfo> getUserInfo(@PathVariable("username") String username); public R<LoginUser> getUserInfo(@PathVariable("username") String username);
} }

View File

@@ -0,0 +1,50 @@
package com.ruoyi.system.api.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* 文件信息
*
* @author ruoyi
*/
public class SysFile
{
/**
* 文件名称
*/
private String name;
/**
* 文件地址
*/
private String url;
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;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("name", getName())
.append("url", getUrl())
.toString();
}
}

View File

@@ -37,6 +37,12 @@ public class SysRole extends BaseEntity
@Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限") @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限")
private String dataScope; private String dataScope;
/** 菜单树选择项是否关联显示( 0父子不互相关联显示 1父子互相关联显示 */
private boolean menuCheckStrictly;
/** 部门树选择项是否关联显示0父子不互相关联显示 1父子互相关联显示 */
private boolean deptCheckStrictly;
/** 角色状态0正常 1停用 */ /** 角色状态0正常 1停用 */
@Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用")
private String status; private String status;
@@ -128,6 +134,26 @@ public class SysRole extends BaseEntity
this.dataScope = dataScope; this.dataScope = dataScope;
} }
public boolean isMenuCheckStrictly()
{
return menuCheckStrictly;
}
public void setMenuCheckStrictly(boolean menuCheckStrictly)
{
this.menuCheckStrictly = menuCheckStrictly;
}
public boolean isDeptCheckStrictly()
{
return deptCheckStrictly;
}
public void setDeptCheckStrictly(boolean deptCheckStrictly)
{
this.deptCheckStrictly = deptCheckStrictly;
}
public String getStatus() public String getStatus()
{ {
return status; return status;
@@ -185,6 +211,8 @@ public class SysRole extends BaseEntity
.append("roleKey", getRoleKey()) .append("roleKey", getRoleKey())
.append("roleSort", getRoleSort()) .append("roleSort", getRoleSort())
.append("dataScope", getDataScope()) .append("dataScope", getDataScope())
.append("menuCheckStrictly", isMenuCheckStrictly())
.append("deptCheckStrictly", isDeptCheckStrictly())
.append("status", getStatus()) .append("status", getStatus())
.append("delFlag", getDelFlag()) .append("delFlag", getDelFlag())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())

View File

@@ -67,12 +67,12 @@ public class SysUser extends BaseEntity
/** 删除标志0代表存在 2代表删除 */ /** 删除标志0代表存在 2代表删除 */
private String delFlag; private String delFlag;
/** 最后登IP */ /** 最后登IP */
@Excel(name = "最后登IP", type = Type.EXPORT) @Excel(name = "最后登IP", type = Type.EXPORT)
private String loginIp; private String loginIp;
/** 最后登时间 */ /** 最后登时间 */
@Excel(name = "最后登时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) @Excel(name = "最后登时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate; private Date loginDate;
/** 部门对象 */ /** 部门对象 */

View File

@@ -0,0 +1,35 @@
package com.ruoyi.system.api.factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteFileService;
import com.ruoyi.system.api.domain.SysFile;
import feign.hystrix.FallbackFactory;
/**
* 文件服务降级处理
*
* @author ruoyi
*/
@Component
public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileService>
{
private static final Logger log = LoggerFactory.getLogger(RemoteFileFallbackFactory.class);
@Override
public RemoteFileService create(Throwable throwable)
{
log.error("文件服务调用失败:{}", throwable.getMessage());
return new RemoteFileService()
{
@Override
public R<SysFile> upload(MultipartFile file)
{
return R.fail("上传文件失败:" + throwable.getMessage());
}
};
}
}

View File

@@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteUserService; import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.model.UserInfo; import com.ruoyi.system.api.model.LoginUser;
import feign.hystrix.FallbackFactory; import feign.hystrix.FallbackFactory;
/** /**
@@ -25,9 +25,9 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
return new RemoteUserService() return new RemoteUserService()
{ {
@Override @Override
public R<UserInfo> getUserInfo(String username) public R<LoginUser> getUserInfo(String username)
{ {
return null; return R.fail("获取用户失败:" + throwable.getMessage());
} }
}; };
} }

View File

@@ -0,0 +1,150 @@
package com.ruoyi.system.api.model;
import java.io.Serializable;
import java.util.Set;
import com.ruoyi.system.api.domain.SysUser;
/**
* 用户信息
*
* @author ruoyi
*/
public class LoginUser implements Serializable
{
private static final long serialVersionUID = 1L;
/**
* 用户唯一标识
*/
private String token;
/**
* 用户名id
*/
private Long userid;
/**
* 用户名
*/
private String username;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 权限列表
*/
private Set<String> permissions;
/**
* 角色列表
*/
private Set<String> roles;
/**
* 用户信息
*/
private SysUser sysUser;
public String getToken()
{
return token;
}
public void setToken(String token)
{
this.token = token;
}
public Long getUserid()
{
return userid;
}
public void setUserid(Long userid)
{
this.userid = userid;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public Long getLoginTime()
{
return loginTime;
}
public void setLoginTime(Long loginTime)
{
this.loginTime = loginTime;
}
public Long getExpireTime()
{
return expireTime;
}
public void setExpireTime(Long expireTime)
{
this.expireTime = expireTime;
}
public String getIpaddr()
{
return ipaddr;
}
public void setIpaddr(String ipaddr)
{
this.ipaddr = ipaddr;
}
public Set<String> getPermissions()
{
return permissions;
}
public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}
public Set<String> getRoles()
{
return roles;
}
public void setRoles(Set<String> roles)
{
this.roles = roles;
}
public SysUser getSysUser()
{
return sysUser;
}
public void setSysUser(SysUser sysUser)
{
this.sysUser = sysUser;
}
}

View File

@@ -1,60 +0,0 @@
package com.ruoyi.system.api.model;
import java.io.Serializable;
import java.util.Set;
import com.ruoyi.system.api.domain.SysUser;
/**
* 用户信息
*
* @author ruoyi
*/
public class UserInfo implements Serializable
{
private static final long serialVersionUID = 1L;
/**
* 用户基本信息
*/
private SysUser sysUser;
/**
* 权限标识集合
*/
private Set<String> permissions;
/**
* 角色集合
*/
private Set<String> roles;
public SysUser getSysUser()
{
return sysUser;
}
public void setSysUser(SysUser sysUser)
{
this.sysUser = sysUser;
}
public Set<String> getPermissions()
{
return permissions;
}
public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}
public Set<String> getRoles()
{
return roles;
}
public void setRoles(Set<String> roles)
{
this.roles = roles;
}
}

View File

@@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.system.api.factory.RemoteUserFallbackFactory,\ com.ruoyi.system.api.factory.RemoteUserFallbackFactory,\
com.ruoyi.system.api.factory.RemoteLogFallbackFactory com.ruoyi.system.api.factory.RemoteLogFallbackFactory, \
com.ruoyi.system.api.factory.RemoteFileFallbackFactory

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>2.0.0</version> <version>2.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -28,10 +28,10 @@
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency> </dependency>
<!-- SpringCloud Netflix Hystrix --> <!-- SpringCloud Ailibaba Sentinel -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency> </dependency>
<!-- SpringBoot Web --> <!-- SpringBoot Web -->
@@ -40,6 +40,12 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Mysql Connector --> <!-- Mysql Connector -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
@@ -52,15 +58,10 @@
<artifactId>ruoyi-common-security</artifactId> <artifactId>ruoyi-common-security</artifactId>
</dependency> </dependency>
<!-- RuoYi Common Redis-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@@ -1,139 +0,0 @@
package com.ruoyi.auth.config;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import com.ruoyi.auth.exception.CustomWebResponseExceptionTranslator;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.common.security.service.RedisClientDetailsService;
/**
* OAuth2 认证服务配置
*
* @author ruoyi
*/
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter
{
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenEnhancer tokenEnhancer;
/**
* 定义授权和令牌端点以及令牌服务
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
{
endpoints
// 请求方式
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
// 指定token存储位置
.tokenStore(tokenStore())
// 自定义生成令牌
.tokenEnhancer(tokenEnhancer)
// 用户账号密码认证
.userDetailsService(userDetailsService)
// 指定认证管理器
.authenticationManager(authenticationManager)
// 是否重复使用 refresh_token
.reuseRefreshTokens(false)
// 自定义异常处理
.exceptionTranslator(new CustomWebResponseExceptionTranslator());
}
/**
* 配置令牌端点(Token Endpoint)的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
{
oauthServer.allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
}
/**
* 声明 ClientDetails实现
*/
public RedisClientDetailsService clientDetailsService()
{
RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);
return clientDetailsService;
}
/**
* 配置客户端详情
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
{
clients.withClientDetails(clientDetailsService());
}
/**
* 基于 Redis 实现,令牌保存到缓存
*/
@Bean
public TokenStore tokenStore()
{
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(CacheConstants.OAUTH_ACCESS);
return tokenStore;
}
/**
* 自定义生成令牌
*/
@Bean
public TokenEnhancer tokenEnhancer()
{
return new TokenEnhancer()
{
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication)
{
if (accessToken instanceof DefaultOAuth2AccessToken)
{
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
LoginUser user = (LoginUser) authentication.getUserAuthentication().getPrincipal();
Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
additionalInformation.put(SecurityConstants.DETAILS_USERNAME, authentication.getName());
additionalInformation.put(SecurityConstants.DETAILS_USER_ID, user.getUserId());
token.setAdditionalInformation(additionalInformation);
}
return accessToken;
};
};
}
}

View File

@@ -1,59 +0,0 @@
package com.ruoyi.auth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Security 安全认证相关配置
* Oauth2依赖于Security 默认情况下WebSecurityConfig执行比ResourceServerConfig优先
*
* @author ruoyi
*/
@Order(99)
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers(
"/actuator/**",
"/oauth/*",
"/token/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
}

View File

@@ -1,20 +1,17 @@
package com.ruoyi.auth.controller; package com.ruoyi.auth.controller;
import java.util.Map; import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.constant.Constants; import com.ruoyi.auth.form.LoginBody;
import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.auth.service.SysLoginService;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.api.RemoteLogService; import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.system.api.model.LoginUser;
/** /**
* token 控制 * token 控制
@@ -22,42 +19,47 @@ import com.ruoyi.system.api.RemoteLogService;
* @author ruoyi * @author ruoyi
*/ */
@RestController @RestController
@RequestMapping("/token")
public class TokenController public class TokenController
{ {
@Autowired @Autowired
private TokenStore tokenStore; private TokenService tokenService;
@Autowired @Autowired
private RemoteLogService remoteLogService; private SysLoginService sysLoginService;
@DeleteMapping("/logout") @PostMapping("login")
public R<?> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) public R<?> login(@RequestBody LoginBody form)
{ {
if (StringUtils.isEmpty(authHeader)) // 用户登录
{ LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());
return R.ok(); // 获取登录token
} return R.ok(tokenService.createToken(userInfo));
}
String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StringUtils.EMPTY).trim(); @DeleteMapping("logout")
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue); public R<?> logout(HttpServletRequest request)
if (accessToken == null || StringUtils.isEmpty(accessToken.getValue())) {
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser))
{ {
return R.ok(); String username = loginUser.getUsername();
} // 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken());
// 清空 access token
tokenStore.removeAccessToken(accessToken);
// 清空 refresh token
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
Map<String, ?> map = accessToken.getAdditionalInformation();
if (map.containsKey(SecurityConstants.DETAILS_USERNAME))
{
String username = (String) map.get(SecurityConstants.DETAILS_USERNAME);
// 记录用户退出日志 // 记录用户退出日志
remoteLogService.saveLogininfor(username, Constants.LOGOUT, "退出成功"); sysLoginService.logout(username);
}
return R.ok();
}
@PostMapping("refresh")
public R<?> refresh(HttpServletRequest request)
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser))
{
// 刷新令牌有效期
tokenService.refreshToken(loginUser);
return R.ok();
} }
return R.ok(); return R.ok();
} }

View File

@@ -1,21 +0,0 @@
package com.ruoyi.auth.controller;
import java.security.Principal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 身份信息获取
*
* @author ruoyi
*/
@RestController
@RequestMapping("/oauth")
public class UserController
{
@RequestMapping("/user")
public Principal user(Principal user)
{
return user;
}
}

View File

@@ -1,21 +0,0 @@
package com.ruoyi.auth.exception;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
/**
* OAuth2 自定义异常处理
*
* @author ruoyi
*/
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception>
{
@Override
public ResponseEntity<OAuth2Exception> translate(Exception e)
{
OAuth2Exception oAuth2Exception = (OAuth2Exception) e;
return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED).body(oAuth2Exception);
}
}

View File

@@ -0,0 +1,39 @@
package com.ruoyi.auth.form;
/**
* 用户登录对象
*
* @author ruoyi
*/
public class LoginBody
{
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}

View File

@@ -1,39 +0,0 @@
package com.ruoyi.auth.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.system.api.RemoteLogService;
/**
* 认证成功处理
*
* @author ruoyi
*/
@Component
public class AuthenticationSuccessEventHandler implements ApplicationListener<AuthenticationSuccessEvent>
{
@Autowired
private RemoteLogService remoteLogService;
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event)
{
Authentication authentication = (Authentication) event.getSource();
if (StringUtils.isNotEmpty(authentication.getAuthorities())
&& authentication.getPrincipal() instanceof LoginUser)
{
LoginUser user = (LoginUser) authentication.getPrincipal();
String username = user.getUsername();
// 记录用户登录日志
remoteLogService.saveLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
}
}
}

View File

@@ -0,0 +1,95 @@
package com.ruoyi.auth.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser;
/**
* 登录校验方法
*
* @author ruoyi
*/
@Component
public class SysLoginService
{
@Autowired
private RemoteLogService remoteLogService;
@Autowired
private RemoteUserService remoteUserService;
/**
* 登录
*/
public LoginUser login(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
throw new BaseException("用户/密码必须填写");
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
throw new BaseException("用户密码不在指定范围");
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new BaseException("用户名不在指定范围");
}
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username);
if (R.FAIL == userResult.getCode())
{
throw new BaseException(userResult.getMsg());
}
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new BaseException("登录用户:" + username + " 不存在");
}
LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
throw new BaseException("用户不存在/密码错误");
}
remoteLogService.saveLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
return userInfo;
}
public void logout(String loginName)
{
remoteLogService.saveLogininfor(loginName, Constants.LOGOUT, "退出成功");
}
}

View File

@@ -21,4 +21,5 @@ spring:
# 配置文件格式 # 配置文件格式
file-extension: yml file-extension: yml
# 共享配置 # 共享配置
shared-dataids: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="logs/ruoyi-auth" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.ruoyi" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>

View File

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

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>2.0.0</version> <version>2.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -47,10 +47,10 @@
<artifactId>pagehelper-spring-boot-starter</artifactId> <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency> </dependency>
<!-- Java Validation --> <!-- Hibernate Validator -->
<dependency> <dependency>
<groupId>javax.validation</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>validation-api</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<!-- Jackson --> <!-- Jackson -->

View File

@@ -4,6 +4,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.math.BigDecimal;
/** /**
* 自定义导出Excel数据注解 * 自定义导出Excel数据注解
@@ -14,6 +15,11 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
public @interface Excel public @interface Excel
{ {
/**
* 导出时在excel中排序
*/
public int sort() default Integer.MAX_VALUE;
/** /**
* 导出到Excel中的名字. * 导出到Excel中的名字.
*/ */
@@ -29,6 +35,21 @@ public @interface Excel
*/ */
public String readConverterExp() default ""; public String readConverterExp() default "";
/**
* 分隔符,读取字符串组内容
*/
public String separator() default ",";
/**
* BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
*/
public int scale() default -1;
/**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/** /**
* 导出类型0数字 1字符串 * 导出类型0数字 1字符串
*/ */
@@ -74,6 +95,32 @@ public @interface Excel
*/ */
public String targetAttr() default ""; public String targetAttr() default "";
/**
* 是否自动统计数据,在最后追加一行统计数据总和
*/
public boolean isStatistics() default false;
/**
* 导出字段对齐方式0默认1靠左2居中3靠右
*/
Align align() default Align.AUTO;
public enum Align
{
AUTO(0), LEFT(1), CENTER(2), RIGHT(3);
private final int value;
Align(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
/** /**
* 字段类型0导出导入1仅导出2仅导入 * 字段类型0导出导入1仅导出2仅导入
*/ */
@@ -97,7 +144,7 @@ public @interface Excel
public enum ColumnType public enum ColumnType
{ {
NUMERIC(0), STRING(1); NUMERIC(0), STRING(1), IMAGE(2);
private final int value; private final int value;
ColumnType(int value) ColumnType(int value)

View File

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

View File

@@ -31,10 +31,11 @@ public class Constants
* 成功标记 * 成功标记
*/ */
public static final Integer SUCCESS = 200; public static final Integer SUCCESS = 200;
/** /**
* 失败标记 * 失败标记
*/ */
public static final Integer FAIL = 501; public static final Integer FAIL = 500;
/** /**
* 登录成功 * 登录成功
@@ -84,7 +85,12 @@ public class Constants
/** /**
* 验证码有效期(分钟) * 验证码有效期(分钟)
*/ */
public static final Integer CAPTCHA_EXPIRATION = 2; public static final long CAPTCHA_EXPIRATION = 2;
/**
* 令牌有效期(分钟)
*/
public final static long TOKEN_EXPIRE = 720;
/** /**
* 参数管理 cache key * 参数管理 cache key

View File

@@ -13,6 +13,9 @@ public class GenConstants
/** 树表(增删改查) */ /** 树表(增删改查) */
public static final String TPL_TREE = "tree"; public static final String TPL_TREE = "tree";
/** 主子表(增删改查) */
public static final String TPL_SUB = "sub";
/** 树编码字段 */ /** 树编码字段 */
public static final String TREE_CODE = "treeCode"; public static final String TREE_CODE = "treeCode";
@@ -22,16 +25,24 @@ public class GenConstants
/** 树名称字段 */ /** 树名称字段 */
public static final String TREE_NAME = "treeName"; public static final String TREE_NAME = "treeName";
/** 上级菜单ID字段 */
public static final String PARENT_MENU_ID = "parentMenuId";
/** 上级菜单名称字段 */
public static final String PARENT_MENU_NAME = "parentMenuName";
/** 数据库字符串类型 */ /** 数据库字符串类型 */
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "narchar", "varchar2", "tinytext", "text", public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
"mediumtext", "longtext" };
/** 数据库文本类型 */
public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" };
/** 数据库时间类型 */ /** 数据库时间类型 */
public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" };
/** 数据库数字类型 */ /** 数据库数字类型 */
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", "float", "double", "decimal" }; "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" };
@@ -68,6 +79,15 @@ public class GenConstants
/** 日期控件 */ /** 日期控件 */
public static final String HTML_DATETIME = "datetime"; public static final String HTML_DATETIME = "datetime";
/** 图片上传控件 */
public static final String HTML_IMAGE_UPLOAD = "imageUpload";
/** 文件上传控件 */
public static final String HTML_FILE_UPLOAD = "fileUpload";
/** 富文本控件 */
public static final String HTML_EDITOR = "editor";
/** 字符串类型 */ /** 字符串类型 */
public static final String TYPE_STRING = "String"; public static final String TYPE_STRING = "String";

View File

@@ -5,7 +5,7 @@ package com.ruoyi.common.core.constant;
* *
* @author ruoyi * @author ruoyi
*/ */
public interface HttpStatus public class HttpStatus
{ {
/** /**
* 操作成功 * 操作成功

View File

@@ -5,7 +5,7 @@ package com.ruoyi.common.core.constant;
* *
* @author ruoyi * @author ruoyi
*/ */
public interface ScheduleConstants public class ScheduleConstants
{ {
public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";

View File

@@ -1,56 +0,0 @@
package com.ruoyi.common.core.constant;
/**
* 权限相关通用常量
*
* @author ruoyi
*/
public class SecurityConstants
{
/**
* 令牌类型
*/
public static final String BEARER_TOKEN_TYPE = "Bearer";
/**
* 授权token url
*/
public static final String AUTH_TOKEN = "/oauth/token";
/**
* 注销token url
*/
public static final String TOKEN_LOGOUT = "/token/logout";
/**
* 用户ID字段
*/
public static final String DETAILS_USER_ID = "user_id";
/**
* 用户名字段
*/
public static final String DETAILS_USERNAME = "username";
/**
* sys_oauth_client_details 表的字段不包括client_id、client_secret
*/
public static final String CLIENT_FIELDS = "client_id, client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
/**
* JdbcClientDetailsService 查询语句
*/
public static final String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";
/**
* 按条件client_id 查询
*/
public static final String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
/**
* 默认的查询语句
*/
public static final String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";
}

View File

@@ -5,7 +5,7 @@ package com.ruoyi.common.core.constant;
* *
* @author ruoyi * @author ruoyi
*/ */
public interface ServiceNameConstants public class ServiceNameConstants
{ {
/** /**
* 认证服务的serviceid * 认证服务的serviceid
@@ -16,4 +16,9 @@ public interface ServiceNameConstants
* 系统模块的serviceid * 系统模块的serviceid
*/ */
public static final String SYSTEM_SERVICE = "ruoyi-system"; public static final String SYSTEM_SERVICE = "ruoyi-system";
/**
* 文件服务的serviceid
*/
public static final String FILE_SERVICE = "ruoyi-file";
} }

View File

@@ -54,7 +54,25 @@ public class UserConstants
/** Layout组件标识 */ /** Layout组件标识 */
public final static String LAYOUT = "Layout"; public final static String LAYOUT = "Layout";
/** ParentView组件标识 */
public final static String PARENT_VIEW = "ParentView";
/** 校验返回结果码 */ /** 校验返回结果码 */
public final static String UNIQUE = "0"; public final static String UNIQUE = "0";
public final static String NOT_UNIQUE = "1"; public final static String NOT_UNIQUE = "1";
/**
* 用户名长度限制
*/
public static final int USERNAME_MIN_LENGTH = 2;
public static final int USERNAME_MAX_LENGTH = 20;
/**
* 密码长度限制
*/
public static final int PASSWORD_MIN_LENGTH = 5;
public static final int PASSWORD_MAX_LENGTH = 20;
} }

View File

@@ -12,6 +12,12 @@ public class R<T> implements Serializable
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 成功 */
public static final int SUCCESS = Constants.SUCCESS;
/** 失败 */
public static final int FAIL = Constants.FAIL;
private int code; private int code;
private String msg; private String msg;
@@ -20,40 +26,40 @@ public class R<T> implements Serializable
public static <T> R<T> ok() public static <T> R<T> ok()
{ {
return restResult(null, Constants.SUCCESS, null); return restResult(null, SUCCESS, null);
} }
public static <T> R<T> ok(T data) public static <T> R<T> ok(T data)
{ {
return restResult(data, Constants.SUCCESS, null); return restResult(data, SUCCESS, null);
} }
public static <T> R<T> ok(T data, String msg) public static <T> R<T> ok(T data, String msg)
{ {
return restResult(data, Constants.SUCCESS, msg); return restResult(data, SUCCESS, msg);
} }
public static <T> R<T> failed() public static <T> R<T> fail()
{ {
return restResult(null, Constants.FAIL, null); return restResult(null, FAIL, null);
} }
public static <T> R<T> failed(String msg) public static <T> R<T> fail(String msg)
{ {
return restResult(null, Constants.FAIL, msg); return restResult(null, FAIL, msg);
} }
public static <T> R<T> failed(T data) public static <T> R<T> fail(T data)
{ {
return restResult(data, Constants.FAIL, null); return restResult(data, FAIL, null);
} }
public static <T> R<T> failed(T data, String msg) public static <T> R<T> fail(T data, String msg)
{ {
return restResult(data, Constants.FAIL, msg); return restResult(data, FAIL, msg);
} }
public static <T> R<T> failed(int code, String msg) public static <T> R<T> fail(int code, String msg)
{ {
return restResult(null, code, msg); return restResult(null, code, msg);
} }

View File

@@ -0,0 +1,15 @@
package com.ruoyi.common.core.exception;
/**
* 权限异常
*
* @author ruoyi
*/
public class PreAuthorizeException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public PreAuthorizeException()
{
}
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.common.core.exception.file;
import com.ruoyi.common.core.exception.BaseException;
/**
* 文件信息异常类
*
* @author ruoyi
*/
public class FileException extends BaseException
{
private static final long serialVersionUID = 1L;
public FileException(String code, Object[] args)
{
super("file", code, args, null);
}
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.common.core.exception.file;
/**
* 文件名称超长限制异常类
*
* @author ruoyi
*/
public class FileNameLengthLimitExceededException extends FileException
{
private static final long serialVersionUID = 1L;
public FileNameLengthLimitExceededException(int defaultFileNameLength)
{
super("upload.filename.exceed.length", new Object[] { defaultFileNameLength });
}
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.common.core.exception.file;
/**
* 文件名大小限制异常类
*
* @author ruoyi
*/
public class FileSizeLimitExceededException extends FileException
{
private static final long serialVersionUID = 1L;
public FileSizeLimitExceededException(long defaultMaxSize)
{
super("upload.exceed.maxSize", new Object[] { defaultMaxSize });
}
}

View File

@@ -0,0 +1,81 @@
package com.ruoyi.common.core.exception.file;
import java.util.Arrays;
import org.apache.commons.fileupload.FileUploadException;
/**
* 文件上传 误异常类
*
* @author ruoyi
*/
public class InvalidExtensionException extends FileUploadException
{
private static final long serialVersionUID = 1L;
private String[] allowedExtension;
private String extension;
private String filename;
public InvalidExtensionException(String[] allowedExtension, String extension, String filename)
{
super("filename : [" + filename + "], extension : [" + extension + "], allowed extension : [" + Arrays.toString(allowedExtension) + "]");
this.allowedExtension = allowedExtension;
this.extension = extension;
this.filename = filename;
}
public String[] getAllowedExtension()
{
return allowedExtension;
}
public String getExtension()
{
return extension;
}
public String getFilename()
{
return filename;
}
public static class InvalidImageExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;
public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}
public static class InvalidFlashExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;
public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}
public static class InvalidMediaExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;
public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}
public static class InvalidVideoExtensionException extends InvalidExtensionException
{
private static final long serialVersionUID = 1L;
public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename)
{
super(allowedExtension, extension, filename);
}
}
}

View File

@@ -66,7 +66,7 @@ public class CharsetKit
if (null == destCharset) if (null == destCharset)
{ {
srcCharset = StandardCharsets.UTF_8; destCharset = StandardCharsets.UTF_8;
} }
if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset))

View File

@@ -376,6 +376,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
* *
* @return UUID 的哈希码值。 * @return UUID 的哈希码值。
*/ */
@Override
public int hashCode() public int hashCode()
{ {
long hilo = mostSigBits ^ leastSigBits; long hilo = mostSigBits ^ leastSigBits;
@@ -391,6 +392,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
* *
* @return 如果对象相同,则返回 {@code true};否则返回 {@code false} * @return 如果对象相同,则返回 {@code true};否则返回 {@code false}
*/ */
@Override
public boolean equals(Object obj) public boolean equals(Object obj)
{ {
if ((null == obj) || (obj.getClass() != UUID.class)) if ((null == obj) || (obj.getClass() != UUID.class))
@@ -414,6 +416,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
* @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。
* *
*/ */
@Override
public int compareTo(UUID val) public int compareTo(UUID val)
{ {
// The ordering is intentionally set up so that the UUIDs // The ordering is intentionally set up so that the UUIDs

View File

@@ -1,9 +1,9 @@
package com.ruoyi.common.security.utils; package com.ruoyi.common.core.utils;
import org.springframework.security.core.Authentication; import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.ruoyi.common.security.domain.LoginUser; import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.text.Convert;
/** /**
* 权限获取工具类 * 权限获取工具类
@@ -12,46 +12,53 @@ import com.ruoyi.common.security.domain.LoginUser;
*/ */
public class SecurityUtils public class SecurityUtils
{ {
/**
* 获取Authentication
*/
public static Authentication getAuthentication()
{
return SecurityContextHolder.getContext().getAuthentication();
}
/** /**
* 获取用户 * 获取用户
*/ */
public static String getUsername() public static String getUsername()
{ {
return getLoginUser().getUsername(); String username = ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USERNAME);
return ServletUtils.urlDecode(username);
} }
/** /**
* 获取用户 * 获取用户ID
*/ */
public static LoginUser getLoginUser(Authentication authentication) public static Long getUserId()
{ {
Object principal = authentication.getPrincipal(); return Convert.toLong(ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USER_ID));
if (principal instanceof LoginUser)
{
return (LoginUser) principal;
}
return null;
} }
/** /**
* 获取用户 * 获取请求token
*/ */
public static LoginUser getLoginUser() public static String getToken()
{ {
Authentication authentication = getAuthentication(); return getToken(ServletUtils.getRequest());
if (authentication == null) }
/**
* 根据request获取请求token
*/
public static String getToken(HttpServletRequest request)
{
String token = ServletUtils.getRequest().getHeader(CacheConstants.HEADER);
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX))
{ {
return null; token = token.replace(CacheConstants.TOKEN_PREFIX, "");
} }
return getLoginUser(authentication); return token;
}
/**
* 是否为管理员
*
* @param userId 用户ID
* @return 结果
*/
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
} }
/** /**
@@ -78,15 +85,4 @@ public class SecurityUtils
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword); return passwordEncoder.matches(rawPassword, encodedPassword);
} }
/**
* 是否为管理员
*
* @param userId 用户ID
* @return 结果
*/
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
}
} }

View File

@@ -1,12 +1,19 @@
package com.ruoyi.common.core.utils; package com.ruoyi.common.core.utils;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
/** /**
@@ -53,7 +60,14 @@ public class ServletUtils
*/ */
public static HttpServletRequest getRequest() public static HttpServletRequest getRequest()
{ {
return getRequestAttributes().getRequest(); try
{
return getRequestAttributes().getRequest();
}
catch (Exception e)
{
return null;
}
} }
/** /**
@@ -61,7 +75,14 @@ public class ServletUtils
*/ */
public static HttpServletResponse getResponse() public static HttpServletResponse getResponse()
{ {
return getRequestAttributes().getResponse(); try
{
return getRequestAttributes().getResponse();
}
catch (Exception e)
{
return null;
}
} }
/** /**
@@ -74,8 +95,31 @@ public class ServletUtils
public static ServletRequestAttributes getRequestAttributes() public static ServletRequestAttributes getRequestAttributes()
{ {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); try
return (ServletRequestAttributes) attributes; {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
catch (Exception e)
{
return null;
}
}
public static Map<String, String> getHeaders(HttpServletRequest request)
{
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null)
{
while (enumeration.hasMoreElements())
{
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
}
return map;
} }
/** /**
@@ -133,4 +177,40 @@ public class ServletUtils
} }
return false; return false;
} }
/**
* 内容编码
*
* @param str 内容
* @return 编码后的内容
*/
public static String urlEncode(String str)
{
try
{
return URLEncoder.encode(str, Constants.UTF8);
}
catch (UnsupportedEncodingException e)
{
return "";
}
}
/**
* 内容解码
*
* @param str 内容
* @return 解码后的内容
*/
public static String urlDecode(String str)
{
try
{
return URLDecoder.decode(str, Constants.UTF8);
}
catch (UnsupportedEncodingException e)
{
return "";
}
}
} }

View File

@@ -1,6 +1,7 @@
package com.ruoyi.common.core.utils; package com.ruoyi.common.core.utils;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.ruoyi.common.core.text.StrFormatter; import com.ruoyi.common.core.text.StrFormatter;
@@ -17,6 +18,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/** 下划线 */ /** 下划线 */
private static final char SEPARATOR = '_'; private static final char SEPARATOR = '_';
/** 星号 */
private static final String START = "*";
/** /**
* 获取参数不为空值 * 获取参数不为空值
* *
@@ -396,6 +400,121 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return sb.toString(); return sb.toString();
} }
/**
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matches(String str, List<String> strs)
{
if (isEmpty(str) || isEmpty(strs))
{
return false;
}
for (String testStr : strs)
{
if (matches(str, testStr))
{
return true;
}
}
return false;
}
/**
* 查找指定字符串是否匹配指定字符串数组中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matches(String str, String... strs)
{
if (isEmpty(str) || isEmpty(strs))
{
return false;
}
for (String testStr : strs)
{
if (matches(str, testStr))
{
return true;
}
}
return false;
}
/**
* 查找指定字符串是否匹配
*
* @param str 指定字符串
* @param pattern 需要检查的字符串
* @return 是否匹配
*/
public static boolean matches(String str, String pattern)
{
if (isEmpty(pattern) || isEmpty(str))
{
return false;
}
pattern = pattern.replaceAll("\\s*", ""); // 替换空格
int beginOffset = 0; // pattern截取开始位置
int formerStarOffset = -1; // 前星号的偏移位置
int latterStarOffset = -1; // 后星号的偏移位置
String remainingURI = str;
String prefixPattern = "";
String suffixPattern = "";
boolean result = false;
do
{
formerStarOffset = indexOf(pattern, START, beginOffset);
prefixPattern = substring(pattern, beginOffset, formerStarOffset > -1 ? formerStarOffset : pattern.length());
// 匹配前缀Pattern
result = remainingURI.contains(prefixPattern);
// 已经没有星号,直接返回
if (formerStarOffset == -1)
{
return result;
}
// 匹配失败,直接返回
if (!result)
return false;
if (!isEmpty(prefixPattern))
{
remainingURI = substringAfter(str, prefixPattern);
}
// 匹配后缀Pattern
latterStarOffset = indexOf(pattern, START, formerStarOffset + 1);
suffixPattern = substring(pattern, formerStarOffset + 1, latterStarOffset > -1 ? latterStarOffset : pattern.length());
result = remainingURI.contains(suffixPattern);
// 匹配失败,直接返回
if (!result)
return false;
if (!isEmpty(suffixPattern))
{
remainingURI = substringAfter(str, suffixPattern);
}
// 移动指针
beginOffset = latterStarOffset + 1;
}
while (!isEmpty(suffixPattern) && !isEmpty(remainingURI));
return true;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T cast(Object obj) public static <T> T cast(Object obj)
{ {

View File

@@ -0,0 +1,76 @@
package com.ruoyi.common.core.utils.file;
import java.io.File;
import org.apache.commons.lang3.StringUtils;
/**
* 文件类型工具类
*
* @author ruoyi
*/
public class FileTypeUtils
{
/**
* 获取文件类型
* <p>
* 例如: ruoyi.txt, 返回: txt
*
* @param file 文件名
* @return 后缀(不含".")
*/
public static String getFileType(File file)
{
if (null == file)
{
return StringUtils.EMPTY;
}
return getFileType(file.getName());
}
/**
* 获取文件类型
* <p>
* 例如: ruoyi.txt, 返回: txt
*
* @param fileName 文件名
* @return 后缀(不含".")
*/
public static String getFileType(String fileName)
{
int separatorIndex = fileName.lastIndexOf(".");
if (separatorIndex < 0)
{
return "";
}
return fileName.substring(separatorIndex + 1).toLowerCase();
}
/**
* 获取文件类型
*
* @param photoByte 文件字节码
* @return 后缀(不含".")
*/
public static String getFileExtendName(byte[] photoByte)
{
String strFileExtendName = "JPG";
if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
{
strFileExtendName = "GIF";
}
else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
{
strFileExtendName = "JPG";
}
else if ((photoByte[0] == 66) && (photoByte[1] == 77))
{
strFileExtendName = "BMP";
}
else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
{
strFileExtendName = "PNG";
}
return strFileExtendName;
}
}

View File

@@ -0,0 +1,260 @@
package com.ruoyi.common.core.utils.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.core.utils.StringUtils;
/**
* 文件处理工具类
*
* @author ruoyi
*/
public class FileUtils extends org.apache.commons.io.FileUtils
{
/** 字符常量:斜杠 {@code '/'} */
public static final char SLASH = '/';
/** 字符常量:反斜杠 {@code '\\'} */
public static final char BACKSLASH = '\\';
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
/**
* 输出指定文件的byte数组
*
* @param filePath 文件路径
* @param os 输出流
* @return
*/
public static void writeBytes(String filePath, OutputStream os) throws IOException
{
FileInputStream fis = null;
try
{
File file = new File(filePath);
if (!file.exists())
{
throw new FileNotFoundException(filePath);
}
fis = new FileInputStream(file);
byte[] b = new byte[1024];
int length;
while ((length = fis.read(b)) > 0)
{
os.write(b, 0, length);
}
}
catch (IOException e)
{
throw e;
}
finally
{
if (os != null)
{
try
{
os.close();
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
if (fis != null)
{
try
{
fis.close();
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
}
}
/**
* 删除文件
*
* @param filePath 文件
* @return
*/
public static boolean deleteFile(String filePath)
{
boolean flag = false;
File file = new File(filePath);
// 路径为文件且不为空则进行删除
if (file.isFile() && file.exists())
{
file.delete();
flag = true;
}
return flag;
}
/**
* 文件名称验证
*
* @param filename 文件名称
* @return true 正常 false 非法
*/
public static boolean isValidFilename(String filename)
{
return filename.matches(FILENAME_PATTERN);
}
/**
* 检查文件是否可下载
*
* @param resource 需要下载的文件
* @return true 正常 false 非法
*/
public static boolean checkAllowDownload(String resource)
{
// 禁止目录上跳级别
if (StringUtils.contains(resource, ".."))
{
return false;
}
// 检查允许下载的文件规则
if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
{
return true;
}
// 不在允许下载的文件规则
return false;
}
/**
* 下载文件名重新编码
*
* @param request 请求对象
* @param fileName 文件名
* @return 编码后的文件名
*/
public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
{
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE"))
{
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
else if (agent.contains("Firefox"))
{
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
}
else if (agent.contains("Chrome"))
{
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
else
{
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
/**
* 返回文件名
*
* @param filePath 文件
* @return 文件名
*/
public static String getName(String filePath)
{
if (null == filePath)
{
return null;
}
int len = filePath.length();
if (0 == len)
{
return filePath;
}
if (isFileSeparator(filePath.charAt(len - 1)))
{
// 以分隔符结尾的去掉结尾分隔符
len--;
}
int begin = 0;
char c;
for (int i = len - 1; i > -1; i--)
{
c = filePath.charAt(i);
if (isFileSeparator(c))
{
// 查找最后一个路径分隔符(/或者\
begin = i + 1;
break;
}
}
return filePath.substring(begin, len);
}
/**
* 是否为Windows或者LinuxUnix文件分隔符<br>
* Windows平台下分隔符为\LinuxUnix为/
*
* @param c 字符
* @return 是否为Windows或者LinuxUnix文件分隔符
*/
public static boolean isFileSeparator(char c)
{
return SLASH == c || BACKSLASH == c;
}
/**
* 下载文件名重新编码
*
* @param response 响应对象
* @param realFileName 真实文件名
* @return
*/
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
{
String percentEncodedFileName = percentEncode(realFileName);
StringBuilder contentDispositionValue = new StringBuilder();
contentDispositionValue.append("attachment; filename=")
.append(percentEncodedFileName)
.append(";")
.append("filename*=")
.append("utf-8''")
.append(percentEncodedFileName);
response.setHeader("Content-disposition", contentDispositionValue.toString());
}
/**
* 百分号编码工具方法
*
* @param s 需要百分号编码的字符串
* @return 百分号编码后的字符串
*/
public static String percentEncode(String s) throws UnsupportedEncodingException
{
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
return encode.replaceAll("\\+", "%20");
}
}

View File

@@ -0,0 +1,86 @@
package com.ruoyi.common.core.utils.file;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import org.apache.poi.util.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 图片处理工具类
*
* @author ruoyi
*/
public class ImageUtils
{
private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
public static byte[] getImage(String imagePath)
{
InputStream is = getFile(imagePath);
try
{
return IOUtils.toByteArray(is);
}
catch (Exception e)
{
log.error("图片加载异常 {}", e);
return null;
}
finally
{
IOUtils.closeQuietly(is);
}
}
public static InputStream getFile(String imagePath)
{
try
{
byte[] result = readFile(imagePath);
result = Arrays.copyOf(result, result.length);
return new ByteArrayInputStream(result);
}
catch (Exception e)
{
log.error("获取图片异常 {}", e);
}
return null;
}
/**
* 读取文件为字节数据
*
* @param key 地址
* @return 字节数据
*/
public static byte[] readFile(String url)
{
InputStream in = null;
ByteArrayOutputStream baos = null;
try
{
// 网络地址
URL urlObj = new URL(url);
URLConnection urlConnection = urlObj.openConnection();
urlConnection.setConnectTimeout(30 * 1000);
urlConnection.setReadTimeout(60 * 1000);
urlConnection.setDoInput(true);
in = urlConnection.getInputStream();
return IOUtils.toByteArray(in);
}
catch (Exception e)
{
log.error("访问文件异常 {}", e);
return null;
}
finally
{
IOUtils.closeQuietly(baos);
}
}
}

View File

@@ -0,0 +1,59 @@
package com.ruoyi.common.core.utils.file;
/**
* 媒体类型工具类
*
* @author ruoyi
*/
public class MimeTypeUtils
{
public static final String IMAGE_PNG = "image/png";
public static final String IMAGE_JPG = "image/jpg";
public static final String IMAGE_JPEG = "image/jpeg";
public static final String IMAGE_BMP = "image/bmp";
public static final String IMAGE_GIF = "image/gif";
public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
public static final String[] FLASH_EXTENSION = { "swf", "flv" };
public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb" };
public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf
"pdf" };
public static String getExtension(String prefix)
{
switch (prefix)
{
case IMAGE_PNG:
return "png";
case IMAGE_JPG:
return "jpg";
case IMAGE_JPEG:
return "jpeg";
case IMAGE_BMP:
return "bmp";
case IMAGE_GIF:
return "gif";
default:
return "";
}
}
}

View File

@@ -14,34 +14,43 @@ public class IpUtils
{ {
public static String getIpAddr(HttpServletRequest request) public static String getIpAddr(HttpServletRequest request)
{ {
if (request == null) String ip = null;
// X-Forwarded-ForSquid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
{ {
return "unknown"; // Proxy-Client-IPapache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
} }
String ip = request.getHeader("x-forwarded-for"); if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{ {
ip = request.getHeader("Proxy-Client-IP"); // WL-Proxy-Client-IPweblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
} }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
{ {
ip = request.getHeader("X-Forwarded-For"); // HTTP_CLIENT_IP有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
} }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
{ {
ip = request.getHeader("WL-Proxy-Client-IP"); // X-Real-IPnginx服务代理
} ipAddresses = request.getHeader("X-Real-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Real-IP");
} }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) // 有些网络通过多层代理那么获取到的ip就会有多个一般都是通过逗号,分割开来并且第一个ip为客户端的真实IP
if (ipAddresses != null && ipAddresses.length() != 0)
{
ip = ipAddresses.split(",")[0];
}
// 还是不能获取到最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses))
{ {
ip = request.getRemoteAddr(); ip = request.getRemoteAddr();
} }
return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
} }
public static boolean internalIp(String ip) public static boolean internalIp(String ip)
@@ -110,8 +119,9 @@ public class IpUtils
{ {
case 1: case 1:
l = Long.parseLong(elements[0]); l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L)) if ((l < 0L) || (l > 4294967295L)){
return null; return null;
}
bytes[0] = (byte) (int) (l >> 24 & 0xFF); bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
@@ -119,12 +129,14 @@ public class IpUtils
break; break;
case 2: case 2:
l = Integer.parseInt(elements[0]); l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L)) if ((l < 0L) || (l > 255L)) {
return null; return null;
}
bytes[0] = (byte) (int) (l & 0xFF); bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]); l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L)) if ((l < 0L) || (l > 16777215L)) {
return null; return null;
}
bytes[1] = (byte) (int) (l >> 16 & 0xFF); bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF); bytes[3] = (byte) (int) (l & 0xFF);
@@ -133,13 +145,15 @@ public class IpUtils
for (i = 0; i < 2; ++i) for (i = 0; i < 2; ++i)
{ {
l = Integer.parseInt(elements[i]); l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) if ((l < 0L) || (l > 255L)) {
return null; return null;
}
bytes[i] = (byte) (int) (l & 0xFF); bytes[i] = (byte) (int) (l & 0xFF);
} }
l = Integer.parseInt(elements[2]); l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L)) if ((l < 0L) || (l > 65535L)) {
return null; return null;
}
bytes[2] = (byte) (int) (l >> 8 & 0xFF); bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF); bytes[3] = (byte) (int) (l & 0xFF);
break; break;
@@ -147,8 +161,9 @@ public class IpUtils
for (i = 0; i < 4; ++i) for (i = 0; i < 4; ++i)
{ {
l = Integer.parseInt(elements[i]); l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) if ((l < 0L) || (l > 255L)) {
return null; return null;
}
bytes[i] = (byte) (int) (l & 0xFF); bytes[i] = (byte) (int) (l & 0xFF);
} }
break; break;

View File

@@ -4,25 +4,28 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell; 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.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;
import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.HorizontalAlignment;
@@ -34,6 +37,7 @@ import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDataValidation; import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -44,6 +48,8 @@ import com.ruoyi.common.core.annotation.Excels;
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;
import com.ruoyi.common.core.utils.file.FileTypeUtils;
import com.ruoyi.common.core.utils.file.ImageUtils;
import com.ruoyi.common.core.utils.reflect.ReflectUtils; import com.ruoyi.common.core.utils.reflect.ReflectUtils;
/** /**
@@ -95,6 +101,21 @@ public class ExcelUtil<T>
*/ */
private List<Object[]> fields; private List<Object[]> fields;
/**
* 最大高度
*/
private short maxHeight;
/**
* 统计列表
*/
private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
/**
* 数字格式
*/
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
/** /**
* 实体对象 * 实体对象
*/ */
@@ -192,7 +213,10 @@ public class ExcelUtil<T>
// 设置类的私有字段属性可访问. // 设置类的私有字段属性可访问.
field.setAccessible(true); field.setAccessible(true);
Integer column = cellMap.get(attr.name()); Integer column = cellMap.get(attr.name());
fieldsMap.put(column, field); if (column != null)
{
fieldsMap.put(column, field);
}
} }
} }
for (int i = 1; i < rows; i++) for (int i = 1; i < rows; i++)
@@ -219,22 +243,30 @@ public class ExcelUtil<T>
} }
else else
{ {
val = Convert.toStr(val); String dateFormat = field.getAnnotation(Excel.class).dateFormat();
if (StringUtils.isNotEmpty(dateFormat))
{
val = DateUtils.parseDateToStr(dateFormat, (Date) val);
}
else
{
val = Convert.toStr(val);
}
} }
} }
else if ((Integer.TYPE == fieldType) || (Integer.class == fieldType)) else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
{ {
val = Convert.toInt(val); val = Convert.toInt(val);
} }
else if ((Long.TYPE == fieldType) || (Long.class == fieldType)) else if (Long.TYPE == fieldType || Long.class == fieldType)
{ {
val = Convert.toLong(val); val = Convert.toLong(val);
} }
else if ((Double.TYPE == fieldType) || (Double.class == fieldType)) else if (Double.TYPE == fieldType || Double.class == fieldType)
{ {
val = Convert.toDouble(val); val = Convert.toDouble(val);
} }
else if ((Float.TYPE == fieldType) || (Float.class == fieldType)) else if (Float.TYPE == fieldType || Float.class == fieldType)
{ {
val = Convert.toFloat(val); val = Convert.toFloat(val);
} }
@@ -253,6 +285,10 @@ public class ExcelUtil<T>
val = DateUtil.getJavaDate((Double) val); val = DateUtil.getJavaDate((Double) val);
} }
} }
else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
{
val = Convert.toBool(val, false);
}
if (StringUtils.isNotNull(fieldType)) if (StringUtils.isNotNull(fieldType))
{ {
Excel attr = field.getAnnotation(Excel.class); Excel attr = field.getAnnotation(Excel.class);
@@ -263,7 +299,7 @@ public class ExcelUtil<T>
} }
else if (StringUtils.isNotEmpty(attr.readConverterExp())) else if (StringUtils.isNotEmpty(attr.readConverterExp()))
{ {
val = reverseByExp(String.valueOf(val), attr.readConverterExp()); val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
} }
ReflectUtils.invokeSetter(entity, propertyName, val); ReflectUtils.invokeSetter(entity, propertyName, val);
} }
@@ -332,6 +368,7 @@ public class ExcelUtil<T>
if (Type.EXPORT.equals(type)) if (Type.EXPORT.equals(type))
{ {
fillExcelData(index, row); fillExcelData(index, row);
addStatisticsRow();
} }
} }
wb.write(outputStream); wb.write(outputStream);
@@ -435,6 +472,30 @@ public class ExcelUtil<T>
style.setFont(headerFont); style.setFont(headerFont);
styles.put("header", style); styles.put("header", style);
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font totalFont = wb.createFont();
totalFont.setFontName("Arial");
totalFont.setFontHeightInPoints((short) 10);
style.setFont(totalFont);
styles.put("total", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.LEFT);
styles.put("data1", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.CENTER);
styles.put("data2", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.RIGHT);
styles.put("data3", style);
return styles; return styles;
} }
@@ -463,14 +524,53 @@ public class ExcelUtil<T>
{ {
if (ColumnType.STRING == attr.cellType()) if (ColumnType.STRING == attr.cellType())
{ {
cell.setCellType(CellType.NUMERIC);
cell.setCellValue(StringUtils.isNull(value) ? attr.defaultValue() : value + attr.suffix()); cell.setCellValue(StringUtils.isNull(value) ? attr.defaultValue() : value + attr.suffix());
} }
else if (ColumnType.NUMERIC == attr.cellType()) else if (ColumnType.NUMERIC == attr.cellType())
{ {
cell.setCellType(CellType.NUMERIC); cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
cell.setCellValue(Integer.parseInt(value + ""));
} }
else if (ColumnType.IMAGE == attr.cellType())
{
ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1),
cell.getRow().getRowNum() + 1);
String imagePath = Convert.toStr(value);
if (StringUtils.isNotEmpty(imagePath))
{
byte[] data = ImageUtils.getImage(imagePath);
getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
}
}
}
/**
* 获取画布
*/
public static Drawing<?> getDrawingPatriarch(Sheet sheet)
{
if (sheet.getDrawingPatriarch() == null)
{
sheet.createDrawingPatriarch();
}
return sheet.getDrawingPatriarch();
}
/**
* 获取图片类型,设置图片插入类型
*/
public int getImageType(byte[] value)
{
String type = FileTypeUtils.getFileExtendName(value);
if ("JPG".equalsIgnoreCase(type))
{
return Workbook.PICTURE_TYPE_JPEG;
}
else if ("PNG".equalsIgnoreCase(type))
{
return Workbook.PICTURE_TYPE_PNG;
}
return Workbook.PICTURE_TYPE_JPEG;
} }
/** /**
@@ -486,7 +586,6 @@ public class ExcelUtil<T>
{ {
// 设置列宽 // 设置列宽
sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
row.setHeight((short) (attr.height() * 20));
} }
// 如果设置了提示信息则鼠标放上去提示. // 如果设置了提示信息则鼠标放上去提示.
if (StringUtils.isNotEmpty(attr.prompt())) if (StringUtils.isNotEmpty(attr.prompt()))
@@ -511,31 +610,38 @@ public class ExcelUtil<T>
try try
{ {
// 设置行高 // 设置行高
row.setHeight((short) (attr.height() * 20)); row.setHeight(maxHeight);
// 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
if (attr.isExport()) if (attr.isExport())
{ {
// 创建cell // 创建cell
cell = row.createCell(column); cell = row.createCell(column);
cell.setCellStyle(styles.get("data")); int align = attr.align().value();
cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
// 用于读取对象中的属性 // 用于读取对象中的属性
Object value = getTargetValue(vo, field, attr); Object value = getTargetValue(vo, field, attr);
String dateFormat = attr.dateFormat(); String dateFormat = attr.dateFormat();
String readConverterExp = attr.readConverterExp(); String readConverterExp = attr.readConverterExp();
String separator = attr.separator();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{ {
cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value)); cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
} }
else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
{ {
cell.setCellValue(convertByExp(String.valueOf(value), readConverterExp)); cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
}
else if (value instanceof BigDecimal && -1 != attr.scale())
{
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
} }
else else
{ {
// 设置列类型 // 设置列类型
setCellVo(value, attr, cell); setCellVo(value, attr, cell);
} }
addStatisticsData(column, Convert.toStr(value), attr);
} }
} }
catch (Exception e) catch (Exception e)
@@ -607,28 +713,36 @@ public class ExcelUtil<T>
* *
* @param propertyValue 参数值 * @param propertyValue 参数值
* @param converterExp 翻译注解 * @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值 * @return 解析后值
* @throws Exception
*/ */
public static String convertByExp(String propertyValue, String converterExp) throws Exception public static String convertByExp(String propertyValue, String converterExp, String separator)
{ {
try StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(",");
for (String item : convertSource)
{ {
String[] convertSource = converterExp.split(","); String[] itemArray = item.split("=");
for (String item : convertSource) if (StringUtils.containsAny(separator, propertyValue))
{
for (String value : propertyValue.split(separator))
{
if (itemArray[0].equals(value))
{
propertyString.append(itemArray[1] + separator);
break;
}
}
}
else
{ {
String[] itemArray = item.split("=");
if (itemArray[0].equals(propertyValue)) if (itemArray[0].equals(propertyValue))
{ {
return itemArray[1]; return itemArray[1];
} }
} }
} }
catch (Exception e) return StringUtils.stripEnd(propertyString.toString(), separator);
{
throw e;
}
return propertyValue;
} }
/** /**
@@ -636,28 +750,83 @@ public class ExcelUtil<T>
* *
* @param propertyValue 参数值 * @param propertyValue 参数值
* @param converterExp 翻译注解 * @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值 * @return 解析后值
* @throws Exception
*/ */
public static String reverseByExp(String propertyValue, String converterExp) throws Exception public static String reverseByExp(String propertyValue, String converterExp, String separator)
{ {
try StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(",");
for (String item : convertSource)
{ {
String[] convertSource = converterExp.split(","); String[] itemArray = item.split("=");
for (String item : convertSource) if (StringUtils.containsAny(separator, propertyValue))
{
for (String value : propertyValue.split(separator))
{
if (itemArray[1].equals(value))
{
propertyString.append(itemArray[0] + separator);
break;
}
}
}
else
{ {
String[] itemArray = item.split("=");
if (itemArray[1].equals(propertyValue)) if (itemArray[1].equals(propertyValue))
{ {
return itemArray[0]; return itemArray[0];
} }
} }
} }
catch (Exception e) return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 合计统计信息
*/
private void addStatisticsData(Integer index, String text, Excel entity)
{
if (entity != null && entity.isStatistics())
{ {
throw e; Double temp = 0D;
if (!statistics.containsKey(index))
{
statistics.put(index, temp);
}
try
{
temp = Double.valueOf(text);
}
catch (NumberFormatException e)
{
}
statistics.put(index, statistics.get(index) + temp);
}
}
/**
* 创建统计行
*/
public void addStatisticsRow()
{
if (statistics.size() > 0)
{
Cell cell = null;
Row row = sheet.createRow(sheet.getLastRowNum() + 1);
Set<Integer> keys = statistics.keySet();
cell = row.createCell(0);
cell.setCellStyle(styles.get("total"));
cell.setCellValue("合计");
for (Integer key : keys)
{
cell = row.createCell(key);
cell.setCellStyle(styles.get("total"));
cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
}
statistics.clear();
} }
return propertyValue;
} }
/** /**
@@ -701,12 +870,12 @@ public class ExcelUtil<T>
*/ */
private Object getValue(Object o, String name) throws Exception private Object getValue(Object o, String name) throws Exception
{ {
if (StringUtils.isNotEmpty(name)) if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
{ {
Class<?> clazz = o.getClass(); Class<?> clazz = o.getClass();
String methodName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1); Field field = clazz.getDeclaredField(name);
Method method = clazz.getMethod(methodName); field.setAccessible(true);
o = method.invoke(o); o = field.get(o);
} }
return o; return o;
} }
@@ -739,6 +908,22 @@ public class ExcelUtil<T>
} }
} }
} }
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
this.maxHeight = getRowHeight();
}
/**
* 根据注解获取最大行高
*/
public short getRowHeight()
{
double maxHeight = 0;
for (Object[] os : this.fields)
{
Excel excel = (Excel) os[1];
maxHeight = maxHeight > excel.height() ? maxHeight : excel.height();
}
return (short) (maxHeight * 20);
} }
/** /**
@@ -800,18 +985,18 @@ public class ExcelUtil<T>
Cell cell = row.getCell(column); Cell cell = row.getCell(column);
if (StringUtils.isNotNull(cell)) if (StringUtils.isNotNull(cell))
{ {
if (cell.getCellTypeEnum() == CellType.NUMERIC || cell.getCellTypeEnum() == CellType.FORMULA) if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
{ {
val = cell.getNumericCellValue(); val = cell.getNumericCellValue();
if (HSSFDateUtil.isCellDateFormatted(cell)) if (DateUtil.isCellDateFormatted(cell))
{ {
val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
} }
else else
{ {
if ((Double) val % 1 > 0) if ((Double) val % 1 != 0)
{ {
val = new DecimalFormat("0.00").format(val); val = new BigDecimal(val.toString());
} }
else else
{ {
@@ -819,15 +1004,15 @@ public class ExcelUtil<T>
} }
} }
} }
else if (cell.getCellTypeEnum() == CellType.STRING) else if (cell.getCellType() == CellType.STRING)
{ {
val = cell.getStringCellValue(); val = cell.getStringCellValue();
} }
else if (cell.getCellTypeEnum() == CellType.BOOLEAN) else if (cell.getCellType() == CellType.BOOLEAN)
{ {
val = cell.getBooleanCellValue(); val = cell.getBooleanCellValue();
} }
else if (cell.getCellTypeEnum() == CellType.ERROR) else if (cell.getCellType() == CellType.ERROR)
{ {
val = cell.getErrorCellValue(); val = cell.getErrorCellValue();
} }

View File

@@ -204,6 +204,10 @@ public class ReflectUtils
args[i] = DateUtil.getJavaDate((Double) args[i]); args[i] = DateUtil.getJavaDate((Double) args[i]);
} }
} }
else if (cs[i] == boolean.class || cs[i] == Boolean.class)
{
args[i] = Convert.toBool(args[i]);
}
} }
} }
return (E) method.invoke(obj, args); return (E) method.invoke(obj, args);

View File

@@ -1,5 +1,6 @@
package com.ruoyi.common.core.utils.sql; package com.ruoyi.common.core.utils.sql;
import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
/** /**
@@ -10,9 +11,9 @@ import com.ruoyi.common.core.utils.StringUtils;
public class SqlUtil public class SqlUtil
{ {
/** /**
* 仅支持字母、数字、下划线、空格、逗号(支持多个字段排序) * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
*/ */
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,]+"; public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/** /**
* 检查字符,防止注入绕过 * 检查字符,防止注入绕过
@@ -21,7 +22,7 @@ public class SqlUtil
{ {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
{ {
return StringUtils.EMPTY; throw new BaseException("参数不符合规范,不能进行查询");
} }
return value; return value;
} }

View File

@@ -58,6 +58,20 @@ public class AjaxResult extends HashMap<String, Object>
} }
} }
/**
* 方便链式调用
*
* @param key
* @param value
* @return
*/
@Override
public AjaxResult put(String key, Object value)
{
super.put(key, value);
return this;
}
/** /**
* 返回成功消息 * 返回成功消息
* *

View File

@@ -5,7 +5,6 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
/** /**
* Entity基类 * Entity基类
@@ -36,14 +35,6 @@ public class BaseEntity implements Serializable
/** 备注 */ /** 备注 */
private String remark; private String remark;
/** 开始时间 */
@JsonIgnore
private String beginTime;
/** 结束时间 */
@JsonIgnore
private String endTime;
/** 请求参数 */ /** 请求参数 */
private Map<String, Object> params; private Map<String, Object> params;
@@ -107,26 +98,6 @@ public class BaseEntity implements Serializable
this.remark = remark; this.remark = remark;
} }
public String getBeginTime()
{
return beginTime;
}
public void setBeginTime(String beginTime)
{
this.beginTime = beginTime;
}
public String getEndTime()
{
return endTime;
}
public void setEndTime(String endTime)
{
this.endTime = endTime;
}
public Map<String, Object> getParams() public Map<String, Object> getParams()
{ {
if (params == null) if (params == null)

View File

@@ -0,0 +1,79 @@
package com.ruoyi.common.core.web.domain;
import java.util.ArrayList;
import java.util.List;
/**
* Tree基类
*
* @author ruoyi
*/
public class TreeEntity extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 父菜单名称 */
private String parentName;
/** 父菜单ID */
private Long parentId;
/** 显示顺序 */
private Integer orderNum;
/** 祖级列表 */
private String ancestors;
/** 子部门 */
private List<?> children = new ArrayList<>();
public String getParentName()
{
return parentName;
}
public void setParentName(String parentName)
{
this.parentName = parentName;
}
public Long getParentId()
{
return parentId;
}
public void setParentId(Long parentId)
{
this.parentId = parentId;
}
public Integer getOrderNum()
{
return orderNum;
}
public void setOrderNum(Integer orderNum)
{
this.orderNum = orderNum;
}
public String getAncestors()
{
return ancestors;
}
public void setAncestors(String ancestors)
{
this.ancestors = ancestors;
}
public List<?> getChildren()
{
return children;
}
public void setChildren(List<?> children)
{
this.children = children;
}
}

View File

@@ -17,9 +17,9 @@ public class PageDomain
/** 排序列 */ /** 排序列 */
private String orderByColumn; private String orderByColumn;
/** 排序的方向 "desc" 或者 "asc". */
private String isAsc; /** 排序的方向desc或者asc */
private String isAsc = "asc";
public String getOrderBy() public String getOrderBy()
{ {

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>2.0.0</version> <version>2.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -12,10 +12,10 @@ import org.springframework.stereotype.Component;
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;
import com.ruoyi.common.datascope.annotation.DataScope; import com.ruoyi.common.datascope.annotation.DataScope;
import com.ruoyi.common.datascope.service.AwaitUserService; import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.system.api.domain.SysRole; import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.UserInfo; import com.ruoyi.system.api.model.LoginUser;
/** /**
* 数据过滤处理 * 数据过滤处理
@@ -57,7 +57,7 @@ public class DataScopeAspect
public static final String DATA_SCOPE = "dataScope"; public static final String DATA_SCOPE = "dataScope";
@Autowired @Autowired
private AwaitUserService awaitUserService; private TokenService tokenService;
// 配置织入点 // 配置织入点
@Pointcut("@annotation(com.ruoyi.common.datascope.annotation.DataScope)") @Pointcut("@annotation(com.ruoyi.common.datascope.annotation.DataScope)")
@@ -80,12 +80,12 @@ public class DataScopeAspect
return; return;
} }
// 获取当前的用户 // 获取当前的用户
UserInfo loginUser = awaitUserService.info(); LoginUser loginUser = tokenService.getLoginUser();
SysUser currentUser = loginUser.getSysUser(); if (StringUtils.isNotNull(loginUser))
if (currentUser != null)
{ {
SysUser currentUser = loginUser.getSysUser();
// 如果是超级管理员,则不过滤数据 // 如果是超级管理员,则不过滤数据
if (!currentUser.isAdmin()) if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{ {
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias()); controllerDataScope.userAlias());
@@ -98,7 +98,8 @@ public class DataScopeAspect
* *
* @param joinPoint 切点 * @param joinPoint 切点
* @param user 用户 * @param user 用户
* @param alias 别名 * @param deptAlias 部门别名
* @param userAlias 用户别名
*/ */
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
{ {
@@ -144,8 +145,12 @@ public class DataScopeAspect
if (StringUtils.isNotBlank(sqlString.toString())) if (StringUtils.isNotBlank(sqlString.toString()))
{ {
BaseEntity baseEntity = (BaseEntity) joinPoint.getArgs()[0]; Object params = joinPoint.getArgs()[0];
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
} }
} }

View File

@@ -1,42 +0,0 @@
package com.ruoyi.common.datascope.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.model.UserInfo;
/**
* 同步调用用户服务
*
* @author ruoyi
*/
@Service
public class AwaitUserService
{
private static final Logger log = LoggerFactory.getLogger(AwaitUserService.class);
@Autowired
private RemoteUserService remoteUserService;
/**
* 查询当前用户信息
*
* @return 用户基本信息
*/
public UserInfo info()
{
String username = SecurityUtils.getUsername();
R<UserInfo> userResult = remoteUserService.getUserInfo(username);
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
log.info("数据权限范围查询用户:{} 不存在.", username);
return null;
}
return userResult.getData();
}
}

View File

@@ -1,5 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.common.datascope.service.AwaitUserService,\
com.ruoyi.common.datascope.aspect.DataScopeAspect com.ruoyi.common.datascope.aspect.DataScopeAspect

View File

@@ -0,0 +1,41 @@
<?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>2.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-datasource</artifactId>
<description>
ruoyi-common-datasource多数据源
</description>
<dependencies>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- Dynamic DataSource -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<!-- SpringBoot Seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
package com.ruoyi.common.datasource.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.baomidou.dynamic.datasource.annotation.DS;
/**
* 主库数据源
*
* @author ruoyi
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS("master")
public @interface Master
{
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.common.datasource.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.baomidou.dynamic.datasource.annotation.DS;
/**
* 从库数据源
*
* @author ruoyi
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS("slave")
public @interface Slave
{
}

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>2.0.0</version> <version>2.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -1,6 +1,8 @@
package com.ruoyi.common.log.aspect; package com.ruoyi.common.log.aspect;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -17,16 +19,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils; import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.log.annotation.Log; import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessStatus; import com.ruoyi.common.log.enums.BusinessStatus;
import com.ruoyi.common.log.service.AsyncLogService; import com.ruoyi.common.log.service.AsyncLogService;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.domain.SysOperLog; import com.ruoyi.system.api.domain.SysOperLog;
/** /**
@@ -83,9 +83,6 @@ public class LogAspect
return; return;
} }
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
// *========数据库日志=========*// // *========数据库日志=========*//
SysOperLog operLog = new SysOperLog(); SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
@@ -96,9 +93,10 @@ public class LogAspect
operLog.setJsonResult(JSON.toJSONString(jsonResult)); operLog.setJsonResult(JSON.toJSONString(jsonResult));
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI()); operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null) String username = SecurityUtils.getUsername();
if (StringUtils.isNotBlank(username))
{ {
operLog.setOperName(loginUser.getUsername()); operLog.setOperName(username);
} }
if (e != null) if (e != null)
@@ -163,11 +161,6 @@ public class LogAspect
String params = argsArrayToString(joinPoint.getArgs()); String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000)); operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} }
else
{
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
} }
/** /**
@@ -198,8 +191,14 @@ public class LogAspect
{ {
if (!isFilterObject(paramsArray[i])) if (!isFilterObject(paramsArray[i]))
{ {
Object jsonObj = JSON.toJSON(paramsArray[i]); try
params += jsonObj.toString() + " "; {
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
} }
} }
} }
@@ -212,8 +211,31 @@ public class LogAspect
* @param o 对象信息。 * @param o 对象信息。
* @return 如果是需要过滤的对象则返回true否则返回false。 * @return 如果是需要过滤的对象则返回true否则返回false。
*/ */
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) public boolean isFilterObject(final Object o)
{ {
Class<?> clazz = o.getClass();
if (clazz.isArray())
{
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
}
else if (Collection.class.isAssignableFrom(clazz))
{
Collection collection = (Collection) o;
for (Iterator iter = collection.iterator(); iter.hasNext();)
{
return iter.next() instanceof MultipartFile;
}
}
else if (Map.class.isAssignableFrom(clazz))
{
Map map = (Map) o;
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();)
{
Map.Entry entry = (Map.Entry) iter.next();
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse; return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
} }
} }

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>2.0.0</version> <version>2.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -36,6 +36,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
this.clazz = clazz; this.clazz = clazz;
} }
@Override
public byte[] serialize(T t) throws SerializationException public byte[] serialize(T t) throws SerializationException
{ {
if (t == null) if (t == null)
@@ -45,6 +46,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
} }
@Override
public T deserialize(byte[] bytes) throws SerializationException public T deserialize(byte[] bytes) throws SerializationException
{ {
if (bytes == null || bytes.length <= 0) if (bytes == null || bytes.length <= 0)

View File

@@ -1,8 +1,6 @@
package com.ruoyi.common.redis.service; package com.ruoyi.common.redis.service;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -11,7 +9,6 @@ import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -33,13 +30,10 @@ public class RedisService
* *
* @param key 缓存的键值 * @param key 缓存的键值
* @param value 缓存的值 * @param value 缓存的值
* @return 缓存的对象
*/ */
public <T> ValueOperations<String, T> setCacheObject(String key, T value) public <T> void setCacheObject(final String key, final T value)
{ {
ValueOperations<String, T> operation = redisTemplate.opsForValue(); redisTemplate.opsForValue().set(key, value);
operation.set(key, value);
return operation;
} }
/** /**
@@ -49,13 +43,35 @@ public class RedisService
* @param value 缓存的值 * @param value 缓存的值
* @param timeout 时间 * @param timeout 时间
* @param timeUnit 时间颗粒度 * @param timeUnit 时间颗粒度
* @return 缓存的对象
*/ */
public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
{ {
ValueOperations<String, T> operation = redisTemplate.opsForValue(); redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
operation.set(key, value, timeout, timeUnit); }
return operation;
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
} }
/** /**
@@ -64,7 +80,7 @@ public class RedisService
* @param key 缓存键值 * @param key 缓存键值
* @return 缓存键值对应的数据 * @return 缓存键值对应的数据
*/ */
public <T> T getCacheObject(String key) public <T> T getCacheObject(final String key)
{ {
ValueOperations<String, T> operation = redisTemplate.opsForValue(); ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key); return operation.get(key);
@@ -75,19 +91,20 @@ public class RedisService
* *
* @param key * @param key
*/ */
public void deleteObject(String key) public boolean deleteObject(final String key)
{ {
redisTemplate.delete(key); return redisTemplate.delete(key);
} }
/** /**
* 删除集合对象 * 删除集合对象
* *
* @param collection * @param collection 多个对象
* @return
*/ */
public void deleteObject(Collection collection) public long deleteObject(final Collection collection)
{ {
redisTemplate.delete(collection); return redisTemplate.delete(collection);
} }
/** /**
@@ -97,18 +114,10 @@ public class RedisService
* @param dataList 待缓存的List数据 * @param dataList 待缓存的List数据
* @return 缓存的对象 * @return 缓存的对象
*/ */
public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList) public <T> long setCacheList(final String key, final List<T> dataList)
{ {
ListOperations listOperation = redisTemplate.opsForList(); Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
if (null != dataList) return count == null ? 0 : count;
{
int size = dataList.size();
for (int i = 0; i < size; i++)
{
listOperation.leftPush(key, dataList.get(i));
}
}
return listOperation;
} }
/** /**
@@ -117,17 +126,9 @@ public class RedisService
* @param key 缓存的键值 * @param key 缓存的键值
* @return 缓存键值对应的数据 * @return 缓存键值对应的数据
*/ */
public <T> List<T> getCacheList(String key) public <T> List<T> getCacheList(final String key)
{ {
List<T> dataList = new ArrayList<T>(); return redisTemplate.opsForList().range(key, 0, -1);
ListOperations<String, T> listOperation = redisTemplate.opsForList();
Long size = listOperation.size(key);
for (int i = 0; i < size; i++)
{
dataList.add(listOperation.index(key, i));
}
return dataList;
} }
/** /**
@@ -137,7 +138,7 @@ public class RedisService
* @param dataSet 缓存的数据 * @param dataSet 缓存的数据
* @return 缓存数据的对象 * @return 缓存数据的对象
*/ */
public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet) public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{ {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator(); Iterator<T> it = dataSet.iterator();
@@ -154,12 +155,9 @@ public class RedisService
* @param key * @param key
* @return * @return
*/ */
public <T> Set<T> getCacheSet(String key) public <T> Set<T> getCacheSet(final String key)
{ {
Set<T> dataSet = new HashSet<T>(); return redisTemplate.opsForSet().members(key);
BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key);
dataSet = operation.members();
return dataSet;
} }
/** /**
@@ -167,19 +165,12 @@ public class RedisService
* *
* @param key * @param key
* @param dataMap * @param dataMap
* @return
*/ */
public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap) public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{ {
HashOperations hashOperations = redisTemplate.opsForHash(); if (dataMap != null) {
if (null != dataMap) redisTemplate.opsForHash().putAll(key, dataMap);
{
for (Map.Entry<String, T> entry : dataMap.entrySet())
{
hashOperations.put(key, entry.getKey(), entry.getValue());
}
} }
return hashOperations;
} }
/** /**
@@ -188,10 +179,46 @@ public class RedisService
* @param key * @param key
* @return * @return
*/ */
public <T> Map<String, T> getCacheMap(String key) public <T> Map<String, T> getCacheMap(final String key)
{ {
Map<String, T> map = redisTemplate.opsForHash().entries(key); return redisTemplate.opsForHash().entries(key);
return map; }
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
} }
/** /**
@@ -200,7 +227,7 @@ public class RedisService
* @param pattern 字符串前缀 * @param pattern 字符串前缀
* @return 对象列表 * @return 对象列表
*/ */
public Collection<String> keys(String pattern) public Collection<String> keys(final String pattern)
{ {
return redisTemplate.keys(pattern); return redisTemplate.keys(pattern);
} }

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>2.0.0</version> <version>2.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -16,18 +16,18 @@
<dependencies> <dependencies>
<!-- Spring Security Oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- RuoYi Api System --> <!-- RuoYi Api System -->
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api-system</artifactId> <artifactId>ruoyi-api-system</artifactId>
</dependency> </dependency>
<!-- RuoYi Common Redis-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -10,7 +10,8 @@ import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
import com.ruoyi.common.security.config.SecurityImportBeanDefinitionRegistrar; import com.ruoyi.common.security.config.ApplicationConfig;
import com.ruoyi.common.security.feign.FeignAutoConfiguration;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@@ -23,7 +24,7 @@ import com.ruoyi.common.security.config.SecurityImportBeanDefinitionRegistrar;
// 开启线程异步执行 // 开启线程异步执行
@EnableAsync @EnableAsync
// 自动加载类 // 自动加载类
@Import(SecurityImportBeanDefinitionRegistrar.class) @Import({ ApplicationConfig.class, FeignAutoConfiguration.class })
public @interface EnableCustomConfig public @interface EnableCustomConfig
{ {

View File

@@ -0,0 +1,46 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限注解
*
* @author ruoyi
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuthorize
{
/**
* 验证用户是否具备某权限
*/
public String hasPermi() default "";
/**
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
*/
public String lacksPermi() default "";
/**
* 验证用户是否具有以下任意一个权限
*/
public String[] hasAnyPermi() default {};
/**
* 判断用户是否拥有某个角色
*/
public String hasRole() default "";
/**
* 验证用户是否不具备某角色,与 isRole逻辑相反
*/
public String lacksRole() default "";
/**
* 验证用户是否具有以下任意一个角色
*/
public String[] hasAnyRoles() default {};
}

View File

@@ -0,0 +1,225 @@
package com.ruoyi.common.security.aspect;
import java.lang.reflect.Method;
import java.util.Collection;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
import com.ruoyi.common.core.exception.PreAuthorizeException;
import com.ruoyi.common.security.annotation.PreAuthorize;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.system.api.model.LoginUser;
/**
* 自定义权限实现
*
* @author ruoyi
*/
@Aspect
@Component
public class PreAuthorizeAspect
{
@Autowired
private TokenService tokenService;
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
/** 数组为0时 */
private static final Integer ARRAY_EMPTY = 0;
@Around("@annotation(com.ruoyi.common.security.annotation.PreAuthorize)")
public Object around(ProceedingJoinPoint point) throws Throwable
{
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
PreAuthorize annotation = method.getAnnotation(PreAuthorize.class);
if (annotation == null)
{
return point.proceed();
}
if (!StringUtils.isEmpty(annotation.hasPermi()))
{
if (hasPermi(annotation.hasPermi()))
{
return point.proceed();
}
throw new PreAuthorizeException();
}
else if (!StringUtils.isEmpty(annotation.lacksPermi()))
{
if (lacksPermi(annotation.lacksPermi()))
{
return point.proceed();
}
throw new PreAuthorizeException();
}
else if (ARRAY_EMPTY < annotation.hasAnyPermi().length)
{
if (hasAnyPermi(annotation.hasAnyPermi()))
{
return point.proceed();
}
throw new PreAuthorizeException();
}
else if (!StringUtils.isEmpty(annotation.hasRole()))
{
if (hasRole(annotation.hasRole()))
{
return point.proceed();
}
throw new PreAuthorizeException();
}
else if (!StringUtils.isEmpty(annotation.lacksRole()))
{
if (lacksRole(annotation.lacksRole()))
{
return point.proceed();
}
throw new PreAuthorizeException();
}
else if (ARRAY_EMPTY < annotation.hasAnyRoles().length)
{
if (hasAnyRoles(annotation.hasAnyRoles()))
{
return point.proceed();
}
throw new PreAuthorizeException();
}
return point.proceed();
}
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
LoginUser userInfo = tokenService.getLoginUser();
if (StringUtils.isEmpty(userInfo) || CollectionUtils.isEmpty(userInfo.getPermissions()))
{
return false;
}
return hasPermissions(userInfo.getPermissions(), permission);
}
/**
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
*
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
return hasPermi(permission) != true;
}
/**
* 验证用户是否具有以下任意一个权限
*
* @param permissions 权限列表
* @return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String[] permissions)
{
LoginUser userInfo = tokenService.getLoginUser();
if (StringUtils.isEmpty(userInfo) || CollectionUtils.isEmpty(userInfo.getPermissions()))
{
return false;
}
Collection<String> authorities = userInfo.getPermissions();
for (String permission : permissions)
{
if (permission != null && hasPermissions(authorities, permission))
{
return true;
}
}
return false;
}
/**
* 判断用户是否拥有某个角色
*
* @param role 角色字符串
* @return 用户是否具备某角色
*/
public boolean hasRole(String role)
{
LoginUser userInfo = tokenService.getLoginUser();
if (StringUtils.isEmpty(userInfo) || CollectionUtils.isEmpty(userInfo.getRoles()))
{
return false;
}
for (String roleKey : userInfo.getRoles())
{
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(role))
{
return true;
}
}
return false;
}
/**
* 验证用户是否不具备某角色,与 isRole逻辑相反。
*
* @param role 角色名称
* @return 用户是否不具备某角色
*/
public boolean lacksRole(String role)
{
return hasRole(role) != true;
}
/**
* 验证用户是否具有以下任意一个角色
*
* @param roles 角色列表
* @return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String[] roles)
{
LoginUser userInfo = tokenService.getLoginUser();
if (StringUtils.isEmpty(userInfo) || CollectionUtils.isEmpty(userInfo.getRoles()))
{
return false;
}
for (String role : roles)
{
if (hasRole(role))
{
return true;
}
}
return false;
}
/**
* 判断是否包含权限
*
* @param authorities 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Collection<String> authorities, String permission)
{
return authorities.stream().filter(StringUtils::hasText)
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(permission, x));
}
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.common.security.config;
import java.util.TimeZone;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
/**
* 系统配置
*
* @author ruoyi
*/
public class ApplicationConfig
{
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}

View File

@@ -1,28 +0,0 @@
package com.ruoyi.common.security.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 忽略服务间的认证
*
* @author ruoyi
**/
@Configurable
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class AuthIgnoreConfig
{
private List<String> urls = new ArrayList<>();
public List<String> getUrls()
{
return urls;
}
public void setUrls(List<String> urls)
{
this.urls = urls;
}
}

View File

@@ -1,75 +0,0 @@
package com.ruoyi.common.security.config;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.util.StringUtils;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.security.domain.LoginUser;
/**
* https://my.oschina.net/giegie/blog/3023768 根据checktoken 的结果转化用户信息
*
* @author lengleng
*/
public class CommonUserConverter implements UserAuthenticationConverter
{
private static final String N_A = "N/A";
/**
* 将授权信息返回到资源服务
*/
@Override
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication)
{
Map<String, Object> authMap = new LinkedHashMap<>();
authMap.put(USERNAME, userAuthentication.getName());
if (userAuthentication.getAuthorities() != null && !userAuthentication.getAuthorities().isEmpty())
{
authMap.put(AUTHORITIES, AuthorityUtils.authorityListToSet(userAuthentication.getAuthorities()));
}
return authMap;
}
/**
* 获取用户认证信息
*/
@Override
public Authentication extractAuthentication(Map<String, ?> map)
{
if (map.containsKey(USERNAME))
{
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
Long userId = Convert.toLong(map.get(SecurityConstants.DETAILS_USER_ID));
String username = (String) map.get(SecurityConstants.DETAILS_USERNAME);
LoginUser user = new LoginUser(userId, username, N_A, true, true, true, true, authorities);
return new UsernamePasswordAuthenticationToken(user, N_A, authorities);
}
return null;
}
/**
* 获取权限资源信息
*/
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map)
{
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String)
{
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection)
{
return AuthorityUtils.commaSeparatedStringToAuthorityList(
StringUtils.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}

View File

@@ -1,82 +0,0 @@
package com.ruoyi.common.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* oauth2 服务配置
*
* @author ruoyi
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
{
@Autowired
private ResourceServerProperties resourceServerProperties;
@Autowired
private OAuth2ClientProperties oAuth2ClientProperties;
@Bean
public AuthIgnoreConfig authIgnoreConfig()
{
return new AuthIgnoreConfig();
}
@Bean
@LoadBalanced
public RestTemplate restTemplate()
{
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
@Bean
public ResourceServerTokenServices tokenServices()
{
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
UserAuthenticationConverter userTokenConverter = new CommonUserConverter();
accessTokenConverter.setUserTokenConverter(userTokenConverter);
remoteTokenServices.setCheckTokenEndpointUrl(resourceServerProperties.getTokenInfoUri());
remoteTokenServices.setClientId(oAuth2ClientProperties.getClientId());
remoteTokenServices.setClientSecret(oAuth2ClientProperties.getClientSecret());
remoteTokenServices.setRestTemplate(restTemplate());
remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
return remoteTokenServices;
}
@Override
public void configure(HttpSecurity http) throws Exception
{
http.csrf().disable();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
// 不登录可以访问
authIgnoreConfig().getUrls().forEach(url -> registry.antMatchers(url).permitAll());
registry.anyRequest().authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources)
{
resources.tokenServices(tokenServices());
}
}

View File

@@ -1,24 +0,0 @@
package com.ruoyi.common.security.config;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import com.ruoyi.common.core.utils.StringUtils;
/**
* 导入 SecurityImportBeanDefinitionRegistrar 自动加载类
*
* @author ruoyi
*/
public class SecurityImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar
{
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
{
Class<ResourceServerConfig> aClass = ResourceServerConfig.class;
String beanName = StringUtils.uncapitalize(aClass.getSimpleName());
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ResourceServerConfig.class);
registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
}
}

View File

@@ -1,37 +0,0 @@
package com.ruoyi.common.security.domain;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
/**
* 登录用户身份权限
*
* @author ruoyi
*/
public class LoginUser extends User
{
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
public LoginUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities)
{
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.userId = userId;
}
public Long getUserId()
{
return userId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
}

View File

@@ -5,16 +5,16 @@ import org.springframework.context.annotation.Configuration;
import feign.RequestInterceptor; import feign.RequestInterceptor;
/** /**
* Feign配置注册 * Feign 配置注册
* *
* @author ruoyi * @author ruoyi
**/ **/
@Configuration @Configuration
public class OAuth2FeignConfig public class FeignAutoConfiguration
{ {
@Bean @Bean
public RequestInterceptor requestInterceptor() public RequestInterceptor requestInterceptor()
{ {
return new OAuth2FeignRequestInterceptor(); return new FeignRequestInterceptor();
} }
} }

View File

@@ -0,0 +1,45 @@
package com.ruoyi.common.security.feign;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
* feign 请求拦截器
*
* @author ruoyi
*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor
{
@Override
public void apply(RequestTemplate requestTemplate)
{
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
if (StringUtils.isNotNull(httpServletRequest))
{
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
// 传递用户信息请求头,防止丢失
String userId = headers.get(CacheConstants.DETAILS_USER_ID);
if (StringUtils.isNotEmpty(userId))
{
requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId);
}
String userName = headers.get(CacheConstants.DETAILS_USERNAME);
if (StringUtils.isNotEmpty(userName))
{
requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName);
}
String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication))
{
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
}
}
}
}

View File

@@ -1,33 +0,0 @@
package com.ruoyi.common.security.feign;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.SecurityConstants;
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
* feign 请求拦截器
*
* @author ruoyi
*/
@Component
public class OAuth2FeignRequestInterceptor implements RequestInterceptor
{
@Override
public void apply(RequestTemplate requestTemplate)
{
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails)
{
OAuth2AuthenticationDetails dateils = (OAuth2AuthenticationDetails) authentication.getDetails();
requestTemplate.header(HttpHeaders.AUTHORIZATION,
String.format("%s %s", SecurityConstants.BEARER_TOKEN_TYPE, dateils.getTokenValue()));
}
}
}

View File

@@ -1,33 +0,0 @@
package com.ruoyi.common.security.handler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.ServletUtils;
/**
* 自定义访问无权限资源时的异常
*
* @author ruoyi
*/
@Component
public class CustomAccessDeniedHandler extends OAuth2AccessDeniedHandler
{
private final Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException)
{
logger.info("权限不足,请联系管理员 {}", request.getRequestURI());
String msg = authException.getMessage();
ServletUtils.renderString(response, JSON.toJSONString(R.failed(HttpStatus.FORBIDDEN, msg)));
}
}

View File

@@ -0,0 +1,94 @@
package com.ruoyi.common.security.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.exception.CustomException;
import com.ruoyi.common.core.exception.DemoModeException;
import com.ruoyi.common.core.exception.PreAuthorizeException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
/**
* 全局异常处理器
*
* @author ruoyi
*/
@RestControllerAdvice
public class GlobalExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 基础异常
*/
@ExceptionHandler(BaseException.class)
public AjaxResult baseException(BaseException e)
{
return AjaxResult.error(e.getDefaultMessage());
}
/**
* 业务异常
*/
@ExceptionHandler(CustomException.class)
public AjaxResult businessException(CustomException e)
{
if (StringUtils.isNull(e.getCode()))
{
return AjaxResult.error(e.getMessage());
}
return AjaxResult.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e)
{
log.error(e.getMessage(), e);
return AjaxResult.error(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e)
{
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 权限异常
*/
@ExceptionHandler(PreAuthorizeException.class)
public AjaxResult preAuthorizeException(PreAuthorizeException e)
{
return AjaxResult.error("没有权限,请联系管理员授权");
}
/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult demoModeException(DemoModeException e)
{
return AjaxResult.error("演示模式,不允许操作");
}
}

View File

@@ -1,167 +0,0 @@
package com.ruoyi.common.security.service;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.common.security.utils.SecurityUtils;
/**
* 自定义权限实现
*
* @author ruoyi
*/
@Service("ss")
public class PermissionService
{
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
private static final String PERMISSION_DELIMETER = ",";
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
return hasPermissions(loginUser.getAuthorities(), permission);
}
/**
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
*
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
return hasPermi(permission) != true;
}
/**
* 验证用户是否具有以下任意一个权限
*
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String permissions)
{
if (StringUtils.isEmpty(permissions))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();
for (String permission : permissions.split(PERMISSION_DELIMETER))
{
if (permission != null && hasPermissions(authorities, permission))
{
return true;
}
}
return false;
}
/**
* 判断用户是否拥有某个角色
*
* @param role 角色字符串
* @return 用户是否具备某角色
*/
public boolean hasRole(String role)
{
if (StringUtils.isEmpty(role))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
for (GrantedAuthority authorities : loginUser.getAuthorities())
{
String roleKey = authorities.getAuthority();
if (SUPER_ADMIN.contains(roleKey) || roleKey.contains(role))
{
return true;
}
}
return false;
}
/**
* 验证用户是否不具备某角色,与 isRole逻辑相反。
*
* @param role 角色名称
* @return 用户是否不具备某角色
*/
public boolean lacksRole(String role)
{
return hasRole(role) != true;
}
/**
* 验证用户是否具有以下任意一个角色
*
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String roles)
{
if (StringUtils.isEmpty(roles))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
for (String role : roles.split(ROLE_DELIMETER))
{
if (hasRole(role))
{
return true;
}
}
return false;
}
/**
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Collection<? extends GrantedAuthority> authorities, String permission)
{
return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(permission, x));
}
}

View File

@@ -1,30 +0,0 @@
package com.ruoyi.common.security.service;
import javax.sql.DataSource;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.SecurityConstants;
/**
* 重写原生方法支持redis缓存
*
* @author ruoyi
*/
public class RedisClientDetailsService extends JdbcClientDetailsService
{
public RedisClientDetailsService(DataSource dataSource)
{
super(dataSource);
super.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
super.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
}
@Override
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
public ClientDetails loadClientByClientId(String clientId)
{
return super.loadClientByClientId(clientId);
}
}

View File

@@ -0,0 +1,123 @@
package com.ruoyi.common.security.service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.utils.IdUtils;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.system.api.model.LoginUser;
/**
* token验证处理
*
* @author ruoyi
*/
@Component
public class TokenService
{
@Autowired
private RedisService redisService;
private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60;
private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
protected static final long MILLIS_SECOND = 1000;
/**
* 创建令牌
*/
public Map<String, Object> createToken(LoginUser loginUser)
{
// 生成token
String token = IdUtils.fastUUID();
loginUser.setToken(token);
loginUser.setUserid(loginUser.getSysUser().getUserId());
loginUser.setUsername(loginUser.getSysUser().getUserName());
loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
refreshToken(loginUser);
// 保存或更新用户token
Map<String, Object> map = new HashMap<String, Object>();
map.put("access_token", token);
map.put("expires_in", EXPIRE_TIME);
redisService.setCacheObject(ACCESS_TOKEN + token, loginUser, EXPIRE_TIME, TimeUnit.SECONDS);
return map;
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser()
{
return getLoginUser(ServletUtils.getRequest());
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
// 获取请求携带的令牌
String token = SecurityUtils.getToken(request);
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
LoginUser user = redisService.getCacheObject(userKey);
return user;
}
return null;
}
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser)
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
refreshToken(loginUser);
}
}
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
redisService.deleteObject(userKey);
}
}
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + EXPIRE_TIME * MILLIS_SECOND);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisService.setCacheObject(userKey, loginUser, EXPIRE_TIME, TimeUnit.SECONDS);
}
private String getTokenKey(String token)
{
return ACCESS_TOKEN + token;
}
}

View File

@@ -1,83 +0,0 @@
package com.ruoyi.common.security.service;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.UserInfo;
/**
* 用户信息处理
*
* @author ruoyi
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private RemoteUserService remoteUserService;
@Override
public UserDetails loadUserByUsername(String username)
{
R<UserInfo> userResult = remoteUserService.getUserInfo(username);
checkUser(userResult, username);
return getUserDetails(userResult);
}
public void checkUser(R<UserInfo> userResult, String username)
{
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
log.info("登录用户:{} 不存在.", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(userResult.getData().getSysUser().getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(userResult.getData().getSysUser().getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
}
private UserDetails getUserDetails(R<UserInfo> result)
{
UserInfo info = result.getData();
Set<String> dbAuthsSet = new HashSet<String>();
if (StringUtils.isNotEmpty(info.getRoles()))
{
// 获取角色
dbAuthsSet.addAll(info.getRoles());
// 获取权限
dbAuthsSet.addAll(info.getPermissions());
}
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
return new LoginUser(user.getUserId(), user.getUserName(), user.getPassword(), true, true, true, true,
authorities);
}
}

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ package com.ruoyi.common.swagger.config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -15,11 +14,9 @@ import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope; import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact; import springfox.documentation.service.Contact;
import springfox.documentation.service.GrantType;
import springfox.documentation.service.OAuth;
import springfox.documentation.service.ResourceOwnerPasswordCredentialsGrant;
import springfox.documentation.service.SecurityReference; import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spi.service.contexts.SecurityContext;
@@ -66,54 +63,55 @@ public class SwaggerAutoConfiguration
List<Predicate<String>> excludePath = new ArrayList<>(); List<Predicate<String>> excludePath = new ArrayList<>();
swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path))); swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));
//noinspection Guava //noinspection Guava
return new Docket(DocumentationType.SWAGGER_2) return new Docket(DocumentationType.SWAGGER_2)
.host(swaggerProperties.getHost()) .host(swaggerProperties.getHost())
.apiInfo(apiInfo(swaggerProperties)).select() .apiInfo(apiInfo(swaggerProperties)).select()
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage())) .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
.paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath))) .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
.build() .build()
.securitySchemes(Collections.singletonList(securitySchema())) .securitySchemes(securitySchemes())
.securityContexts(Collections.singletonList(securityContext())) .securityContexts(securityContexts())
.pathMapping("/"); .pathMapping("/");
} }
/** /**
* 配置默认的全局鉴权策略的开关通过正则表达式进行匹配默认匹配所有URL * 安全模式这里指定token通过Authorization头请求头传递
* */
* @return private List<ApiKey> securitySchemes()
*/
private SecurityContext securityContext()
{ {
return SecurityContext.builder() List<ApiKey> apiKeyList = new ArrayList<ApiKey>();
.securityReferences(defaultAuth()) apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
.forPaths(PathSelectors.regex(swaggerProperties().getAuthorization().getAuthRegex())) return apiKeyList;
.build();
} }
/** /**
* 默认的全局鉴权策略 * 安全上下文
* */
* @return private List<SecurityContext> securityContexts()
*/ {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$"))
.build());
return securityContexts;
}
/**
* 默认的全局鉴权策略
*
* @return
*/
private List<SecurityReference> defaultAuth() private List<SecurityReference> defaultAuth()
{ {
ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>(); AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
swaggerProperties().getAuthorization().getAuthorizationScopeList().forEach(authorizationScope -> authorizationScopeList.add(new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription()))); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
AuthorizationScope[] authorizationScopes = new AuthorizationScope[authorizationScopeList.size()]; authorizationScopes[0] = authorizationScope;
return Collections.singletonList(SecurityReference.builder() List<SecurityReference> securityReferences = new ArrayList<>();
.reference(swaggerProperties().getAuthorization().getName()) securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
.scopes(authorizationScopeList.toArray(authorizationScopes)) return securityReferences;
.build());
}
private OAuth securitySchema()
{
ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
swaggerProperties().getAuthorization().getAuthorizationScopeList().forEach(authorizationScope -> authorizationScopeList.add(new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));
ArrayList<GrantType> grantTypes = new ArrayList<>();
swaggerProperties().getAuthorization().getTokenUrlList().forEach(tokenUrl -> grantTypes.add(new ResourceOwnerPasswordCredentialsGrant(tokenUrl)));
return new OAuth(swaggerProperties().getAuthorization().getName(), authorizationScopeList, grantTypes);
} }
private ApiInfo apiInfo(SwaggerProperties swaggerProperties) private ApiInfo apiInfo(SwaggerProperties swaggerProperties)
@@ -128,5 +126,4 @@ public class SwaggerAutoConfiguration
.version(swaggerProperties.getVersion()) .version(swaggerProperties.getVersion())
.build(); .build();
} }
} }

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