Compare commits

...

227 Commits

Author SHA1 Message Date
857fb53fa1 Merge pull request #6293 from EightMonth/master
优化生产环境屏蔽swagger处理
2024-06-06 10:42:17 +08:00
9db6c1a7ac 优化生产环境屏蔽swagger处理 2024-06-06 10:39:50 +08:00
fd0461644e 免token注解@IgnoreAuth改造后, 不需此代码 2024-06-05 19:05:16 +08:00
10263720d4 Merge pull request #6220 from EightMonth/master
修复#6100、#6169、@IgnoreAuth扫描加速
2024-06-05 18:18:23 +08:00
7548f3aa60 Merge pull request #6114 from EightMonth/master
rocketmq-starter应用举例
2024-05-13 10:59:08 +08:00
ad3d2eb3fc 修复#6169 2024-04-30 13:49:34 +08:00
d0406fcd83 发issue,请明确那个分支 2024-04-28 15:49:35 +08:00
9159b55096 发issue,请明确那个分支 2024-04-26 15:07:50 +08:00
faebdee755 修复#6100、@IgnoreAuth扫描加速 2024-04-25 11:52:41 +08:00
2fc672dfab 写错了 2024-04-22 09:35:01 +08:00
4dc4e87900 解决SQL注入检测逻辑影响业务 #6105 2024-04-20 18:34:52 +08:00
13d00a8bb4 【issues/6113】online表单生成代码, 关联表生成vue3模板里的 *.data.ts 这个文件会有报错 2024-04-17 10:43:19 +08:00
fb95cf7f2f -- 修复设置用户生日格式报错 2024-04-17 09:59:48 +08:00
78f048fda5 增加新QQ群⑨808791225 2024-04-15 13:50:00 +08:00
200adb8490 使用代码生成器生成树的结构,批量删除时报错,在很老的jeecg,vue2版本就在报错了 #6043 2024-04-12 15:42:37 +08:00
f1496b5084 rocketmq-starter应用举例 2024-04-12 10:16:30 +08:00
7b06715bff 排除minidao的子依赖jsqlparser和druid,总与mybatisplus冲突 2024-04-11 11:13:39 +08:00
3091d5b6f0 缩小IgnoreAuth注解扫描范围 2024-04-08 15:03:13 +08:00
c117abb2d4 升级druid到1.2.22 2024-04-08 14:23:49 +08:00
dbc3f13c65 补充注释说明 2024-04-08 14:03:27 +08:00
ea6927a2a7 Merge pull request #6007 from EightMonth/master
修复 #5901
2024-04-08 13:52:58 +08:00
b69a716b04 修改druid配置类名称 2024-04-08 13:45:27 +08:00
7e71fa26d7 升级druid v1.2.22版本兼容处理 2024-04-08 13:11:55 +08:00
4fed40ff7d Update pom.xml 2024-04-03 16:06:04 +08:00
ee4ff35c90 Revert "修复 #6070"
This reverts commit 6edef14f07.
2024-04-03 16:04:29 +08:00
c9b92decaf Revert "处理升级druid 1.2.22版本兼容处理"
This reverts commit eed3bc346d.
2024-04-03 16:04:23 +08:00
eed3bc346d 处理升级druid 1.2.22版本兼容处理 2024-04-03 16:01:39 +08:00
6edef14f07 修复 #6070 2024-04-03 11:18:47 +08:00
ab49983759 回滚druid版本升级,导致问题“角色功能报错,列表查询报错。 #6070” 2024-04-03 09:44:01 +08:00
5a09a6fb4a Merge branch 'master' into master 2024-04-02 15:43:35 +08:00
ac93bf7d6b 升级积木报表到1.7.4,升级druid到1.2.22 2024-04-01 13:47:48 +08:00
c9c6dd5c1d Online表单中 下拉搜索框 搜索时报sql错误,生成的SQL多了一个 “and" #5978
字典下拉异步出错 #1108
2024-03-28 14:44:24 +08:00
e3e1cd6b0d 修复 #5936 2024-03-25 16:27:53 +08:00
f3cf90bd28 在租户不隔离的情况下导出部门报错 2024-03-25 09:42:33 +08:00
70847d17f1 升级积木报表版本 2024-03-23 21:22:29 +08:00
2cfc39b23f Merge branch 'master' of https://github.com/EightMonth/jeecg-boot 2024-03-22 17:12:32 +08:00
c8676b3040 优化注解生效范围配置 2024-03-22 17:11:18 +08:00
73bd04d04a Merge branch 'master' into master 2024-03-22 16:38:39 +08:00
0ca4badb77 修复 #6021 2024-03-22 16:37:00 +08:00
80b92ca132 flowable、activiti都支持 2024-03-22 16:22:48 +08:00
58865bef28 jimureport-drag:2.0.1 报错 java.lang.IllegalStateException: No TaglibFactory available #6021 2024-03-20 22:59:27 +08:00
9fd40d0973 代码生成器jar包中携带配置文件导致 spring.profiles.active 被覆盖 #6010 2024-03-20 10:07:54 +08:00
266ebd9122 修复 #5901 2024-03-19 09:41:29 +08:00
fee729e16c 【回滚】暂时回退mybatisplus、minidao版本,online模块不兼容 2024-03-18 14:06:09 +08:00
10a3e9c6ba 升级knife4j、mybatisplus、justauth、minidao版本 2024-03-18 10:11:05 +08:00
990f79fdfe 调整flyway配置,检查数据库类型只有mysql才启用 2024-03-18 09:27:54 +08:00
6360aee0ff 【QQYUN-8561】企业微信登陆请求接口设置上下文不一致,导致接口404 2024-03-18 09:25:51 +08:00
685b81e5ec Merge pull request #5930 from EightMonth/master
处理 #5601 ,添加@ignoreAuth注解
2024-03-15 15:30:48 +08:00
e38e395436 Merge branch 'master' into master 2024-03-15 15:29:35 +08:00
39af6e25ee ChatGPT AI助手,对接配置文档 2024-03-11 19:08:33 +08:00
b15e6e0422 【优化代码生成功能】uniapp调整目录 2024-03-09 22:30:41 +08:00
8f99a80352 【优化代码生成功能】uniapp调整目录、升级sql的命名改成flyway规则 2024-03-09 22:11:00 +08:00
936a87e738 积木报表 v1.7.2 紧急发布,修复 1.7.1 严重 Bug 2024-03-07 22:32:39 +08:00
2af165b201 【3.6.3版本发布】初始化sql有问题启动报flyway错误 2024-03-07 11:30:51 +08:00
3d5efc07ad 【3.6.3版本发布】升级积木报表的升级SQL 2024-03-06 19:02:39 +08:00
21e8d640d2 【3.6.3版本发布】升级积木报表的升级SQL 2024-03-06 18:55:35 +08:00
b0ce456909 【3.6.3版本发布】升级积木报表的升级SQL 2024-03-06 18:45:44 +08:00
b8e1306955 【3.6.3版本发布】升级积木报表的升级SQL 2024-03-06 18:44:35 +08:00
37a38ad288 【3.6.3版本发布】升级版本号 2024-03-06 17:13:05 +08:00
9ee05c9510 【3.6.3版本发布】新版数据库脚本 2024-03-06 17:00:54 +08:00
275a68bb6a 忽略文件 2024-03-06 16:24:23 +08:00
f3f70e8549 【3.6.3版本发布】代码修复 2024-03-06 16:22:36 +08:00
e15e9d80c4 【3.6.3版本发布】ai聊天模块新增代码 2024-03-06 16:22:03 +08:00
f7538c1ed8 【3.6.3版本发布】首页支持自定义 2024-03-06 16:20:49 +08:00
a9dba08a8d 【3.6.3版本发布】性能优化部分代码 2024-03-06 16:19:51 +08:00
acf0713385 Update ShiroConfig.java 2024-02-29 17:43:56 +08:00
cee872000a 处理 #5601 ,添加@ignoreAuth注解 2024-02-29 17:11:44 +08:00
4a857680d0 原生表单新增携带 createTime 等系统字段 #1033 2024-02-23 09:59:19 +08:00
a47d0984dc [issues/5755]vue代码不加入逻辑删除字段
[issue/#5711]修复用户选择组件在生成代码后变成部门用户选择组件
[issues/1022]我这个控件是哪里设置没对吗,为什么打开已有的记录,会触发提示“请输入”验证? #1022
fix 带条件字典存在单引号导致js编译错误
2024-02-19 16:40:33 +08:00
e333b126b6 微服务下字典拦截器报错 2024-01-10 14:58:01 +08:00
3618842f44 [issue/5787]增加非空判断防止代码生成时空指针异常 2024-01-09 19:12:16 +08:00
fd8c848c9e 修改小bug 2024-01-07 20:30:49 +08:00
09614a0239 修复几个bug 2024-01-07 20:28:41 +08:00
6fe8f1d81a 从3.6.2+版本增加flyway自动升级数据库机制 2024-01-04 14:56:06 +08:00
0bd7f715c4 默认删除人大金仓依赖,需要请自己放开 2024-01-04 13:57:23 +08:00
041d88161e 2024年首版本发布(antd4和仪表盘大升级) 2024-01-03 22:12:07 +08:00
79a62aa056 从3.6.2+版本增加flyway自动升级数据库机制 2024-01-03 22:07:27 +08:00
b86b4d9676 从3.6.2+版本增加flyway自动升级数据库机制 2024-01-03 22:01:07 +08:00
aeaac80012 2024年首版本发布(antd4和仪表盘大升级) 2024-01-03 19:46:35 +08:00
e0ef20cf08 3.6.2版本对应的数据库脚本 2024-01-03 18:58:30 +08:00
169a66f5dd 升级新版本号3.6.2 2024-01-03 13:47:33 +08:00
7e39b31123 日志暂时不做权限控制 2024-01-03 13:44:36 +08:00
18765450a6 2024年首版本发布(仪表盘大升级) 2024-01-03 13:40:44 +08:00
dff8c84d9c 2024年首版本发布(仪表盘大升级) 2024-01-03 13:37:06 +08:00
d962c34846 新版数据库,有新升级sql和权限授权配置 2024-01-03 13:36:01 +08:00
cd9794d818 删除无用代码 2023-12-31 17:11:18 +08:00
5034b7cf18 提供新版仪表盘 2023-12-31 15:14:04 +08:00
fdde84c68a 代码生成器更新 2023-12-29 21:14:49 +08:00
4c54ff6f52 Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot 2023-12-29 19:44:16 +08:00
de3285dc1b 通知公告 2023-12-29 19:43:12 +08:00
7f0c035c4c 企业微信集成改造 2023-12-29 19:42:36 +08:00
43593e8def 加权限注解 2023-12-29 19:42:24 +08:00
48b0b608d8 租户改造 2023-12-29 19:42:13 +08:00
69287a772b 小功能修改 2023-12-29 19:41:44 +08:00
337d5a9489 Merge pull request #5692 from hoperunChen/fix-QQYUN-7527
fix QQYUN-7527 vue3代码生成默认带上高级查询
2023-12-26 14:35:56 +08:00
cfeb81ee1e Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot.git 2023-12-26 14:08:37 +08:00
09f92f01aa 提交online新依赖(解决online接口安全漏洞问题) 2023-12-26 14:08:20 +08:00
6d1094936b Merge pull request #5691 from hoperunChen/Fix-QQYUN-7583
fix QQYUN-7583 Vue3风格表单页面多选控件渲染成了下拉多选
2023-12-26 14:00:59 +08:00
8836a2793a Merge pull request #5690 from hoperunChen/fix-issues-#5658
fix issues #5658 树表复选框与展开按钮重叠问题
2023-12-26 13:59:52 +08:00
c36ece8923 补充信息 2023-12-23 19:24:15 +08:00
a82213b90c 还原错误提交 2023-12-21 15:47:28 +08:00
98facdd2ee Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot.git 2023-12-21 15:45:21 +08:00
d080b0b5ea SpringBoot3+JDK17版本可以下载了 2023-12-21 15:36:09 +08:00
338902ca0c Merge pull request #5706 from hoperunChen/fix-issue-vue3-#939
fix issue vue3 #939 npm run build 失败
2023-12-21 14:59:56 +08:00
7ae6a11cf0 fix issue vue3 #939: modify file suffix 2023-12-21 14:31:51 +08:00
cdbe1cb1a9 fix issue vue3 #939 2023-12-21 13:31:04 +08:00
232037ec58 1 2023-12-20 16:26:51 +08:00
b18c1120ab 流程引擎推荐 2023-12-20 16:25:45 +08:00
f6a7831963 1 2023-12-20 15:16:41 +08:00
08a4473bbc 1 2023-12-20 15:12:14 +08:00
9008ddafa4 1 2023-12-20 15:10:59 +08:00
7012ed4c2c 流程引擎 2023-12-20 15:08:54 +08:00
1d8c8c30d0 升级hutool版本号,解决hutool漏洞问题 CVE-2023-42278、CVE-2023-42277、CVE-2023-42276、CVE-2023-33695、CVE-2023-3276、CVE-2022-4565 2023-12-20 11:09:40 +08:00
090f790df4 fix QQYUN-7527 vue3代码生成,默认带上高级查询 2023-12-19 11:06:05 +08:00
82d051f388 fix QQYUN-7583 Vue3风格表单页面多选控件渲染成了下拉多选 2023-12-19 11:05:16 +08:00
5a3631c332 fix issues #5658 树表复选框与展开按钮重叠问题 2023-12-19 11:03:18 +08:00
c00b5526c3 解决3.6.1版本添加online报表报错Subject does not have permission [online:report:add] #5672 2023-12-16 10:42:24 +08:00
2e35abd3a3 大龄码农的思考 2023-12-15 13:57:51 +08:00
9bc3f6c56e 源码下载说明 2023-12-15 13:53:42 +08:00
5588912b62 分支说明 2023-12-15 13:43:06 +08:00
37cf913d6d springboot3分支 2023-12-14 11:57:48 +08:00
94ba767090 SpringBoot3(JDK17)版本介绍 2023-12-14 11:54:44 +08:00
a406c7cd81 Merge pull request #5659 from EightMonth/master
修改docker-compose文件添加network配置
2023-12-12 17:49:27 +08:00
480878a3db 修改docker-compose文件添加network配置 2023-12-12 17:43:54 +08:00
a85499119d Merge pull request #5605 from EightMonth/master
修复 #5556

sentinel配置文件修改账号密码,logging.level.pattern 等配置无效! #5556
shiro集成 redis 不支持 sentinel 方式部署的redis集群 #5569
2023-12-12 16:23:32 +08:00
4cbe9cad8e 字典code解码失败,可能是使用了非法字符,请检查! #5655 2023-12-12 10:50:42 +08:00
2dfc06c679 3.6.1 新版oracle脚本 2023-12-11 10:10:14 +08:00
a591ad9fed 人大金仓和达梦数据库驱动 2023-12-11 10:06:07 +08:00
5420e69b59 3.6.0到3.6.1升级sql 2023-12-11 09:58:04 +08:00
f157c96f65 JeecgBoot 3.6.1版本发布——ONLINE专题版本 2023-12-10 11:16:50 +08:00
9588ace87f 企业微信域名文件认证万能接口 2023-12-08 17:06:42 +08:00
cc57ac379b JeecgBoot 3.6.1版本发布——ONLINE专题版本 2023-12-07 12:04:58 +08:00
074920552a JeecgBoot 3.6.1版本发布——ONLINE专题版本 2023-12-07 12:01:14 +08:00
2a00a24058 升级online到3.6.0版本(注意有升级sql) 2023-12-07 11:13:17 +08:00
06cc4ec0fe Update docker-compose.yml 2023-12-05 18:24:09 +08:00
decea393a5 移除更新格式化内容 2023-12-05 10:13:29 +08:00
65b0fab80d 移除格式化更新 2023-12-05 10:09:14 +08:00
1d4098ae14 修复 #5569 2023-12-04 20:02:30 +08:00
5c4f303a0d Merge branch 'master' of https://github.com/EightMonth/jeecg-boot 2023-11-23 13:53:54 +08:00
8dcc5bdf8a 修复 #5556 2023-11-23 13:50:07 +08:00
b3e4a73a34 【QQYUN-7028】用户职务保存后未回显 2023-11-20 11:22:32 +08:00
60b4a038f6 查询地域组件更换 2023-11-16 17:56:41 +08:00
4e3738100a vue3版本支持uniapp的代码生成 2023-11-16 17:56:27 +08:00
8216889078 优化代码生成器模板 2023-11-15 10:38:18 +08:00
6ab4ee6a91 维护用户租户关系有问题 2023-11-05 11:36:53 +08:00
a8dde73a8c 生成代码模版的删除按钮弹窗的确认、取消显示错位问题 2023-10-30 21:26:26 +08:00
b2b11611c1 群满了,增加新QQ群⑧825232878 2023-10-26 17:14:00 +08:00
04c55fa3ba Merge pull request #5480 from EightMonth/master
优化seata测试案例,使全局事务可以 回滚
2023-10-20 13:59:54 +08:00
4957330c1c 原生代码生成模板优化升级
1、表单默认值带不出来 issues/5304
2、一对多erp风格支持原生生成 issues/5294
3、查询条件样式错位
2023-10-20 12:07:57 +08:00
ea9dfd6ea7 JeecgBoot 3.6.0大版本发布 2023-10-19 09:28:35 +08:00
60f7191659 JeecgBoot 3.6.0大版本发布 2023-10-18 19:55:36 +08:00
6f560c1cd7 JeecgBoot 3.6.0大版本发布 2023-10-18 17:31:45 +08:00
524cd4a3d1 JeecgBoot 3.6.0大版本发布 2023-10-18 15:04:41 +08:00
f342c93eec 删除无用文件 2023-10-17 10:40:23 +08:00
a364025bd2 升级seata版本至1.5.2 2023-10-16 18:24:17 +08:00
5fa6a5e686 积木报表新版发布1.6.4 2023-10-16 12:07:41 +08:00
6d34164a16 Merge pull request #5277 from Chenzhexian/master
网关路由配置Bug问题!!!
2023-10-16 12:04:45 +08:00
e15335ddab 更新公司名称 2023-10-11 10:58:51 +08:00
b373a80498 升级driud依赖到1.2.19版本号 2023-10-11 10:58:38 +08:00
f0fb3ed5ff 升级积木报表到1.6.3版本 2023-10-11 10:57:00 +08:00
411deee8af 敲敲云零代码体验地址 2023-10-09 11:43:02 +08:00
b2942e0323 公司信息更新 2023-10-09 10:43:08 +08:00
78e371ab9f 优化seata测试案例,使全局事务可以 回滚 2023-09-26 10:17:14 +08:00
ffeb607ad3 jeecgboot 3.5.5版本发布(数据库更新) 2023-09-21 13:59:21 +08:00
7dd68068a1 jeecgboot 3.5.5版本发布(数据库更新) 2023-09-21 13:54:22 +08:00
51d7f3b06b 解决仪表盘设计中,数据集中获取分组报错 #5416 2023-09-21 09:51:59 +08:00
a060db5530 jeecgboot 3.5.5版本发布(数据库更新) 2023-09-20 21:54:41 +08:00
30caf045ed 解决jeecg-cloud-nacos 打包镜像运行问题 #5379 2023-09-20 19:39:36 +08:00
6264b2ec70 jeecgboot 3.5.5版本发布(数据库更新) 2023-09-20 15:27:00 +08:00
9d1f73b3f7 jeecgboot 3.5.5版本发布(跳过3.5.4) 2023-09-20 14:33:20 +08:00
d557aa4cc0 jeecgboot 3.5.5版本发布(跳过3.5.4) 2023-09-20 11:58:10 +08:00
81351aeb7a dynamic-datasource-spring-boot-starter改为3.5.2稳定版本 2023-09-20 11:53:25 +08:00
ee1a568bed 升级mybatis-plus、dynamic-datasource-spring-boot-starter、commons-fileupload依赖版本号 2023-09-20 10:37:19 +08:00
e721668eb0 修改一个bug 2023-09-20 10:31:06 +08:00
5edb30c51f 只是改注释 2023-09-20 10:29:16 +08:00
9c5bd7193f 提示语修改 2023-09-20 10:28:33 +08:00
3e9b7ccfc1 vue3 online专属接口兼容 2023-09-20 10:26:05 +08:00
f1717c7000 升级mybatis-plus、driud依赖;修改配置写法 #4558 2023-09-16 22:30:55 +08:00
946d535f68 jeecg-cloud-xxljob模块中logback.xml中生成日志路径问题 #5344 2023-09-16 21:40:52 +08:00
1a326cb70f 空值问题处理 2023-09-16 21:29:01 +08:00
5a9344b190 【issues/5368】缺少租户选择套餐权限升级sql 2023-09-16 21:15:46 +08:00
4eaebd658b 空值问题处理 2023-09-16 21:15:05 +08:00
581047c569 升级积木报表1.6.2-GA5,解决仪表盘兼容问题 2023-09-12 17:26:23 +08:00
87677df925 升级积木报表1.6.2-GA3,解决严重SQL漏洞 2023-09-12 16:50:33 +08:00
56e81fbf7b 升级积木报表1.6.2-GA,解决严重SQL漏洞 2023-09-11 22:45:22 +08:00
473875a9d2 --重构表字典逻辑,深度解决SQL注入漏洞问题(修复导致的bug修复)-- 2023-09-05 22:59:08 +08:00
44952c79c2 ---重构表字典逻辑,深度解决SQL注入漏洞问题,新旧版本都可以参考此修改合并---
(重点针对表名和字段进行单独check处理,更严格的格式要求,可能会导致一些特殊字典用法出问题,请根据自己业务做灵活调整)
org\jeecg\common\exception\JeecgSqlInjectionException.java(+)
org\jeecg\common\exception\JeecgBootExceptionHandler.java

org\jeecg\common\util\security\AbstractQueryBlackListHandler.java
org\jeecg\common\util\SqlInjectionUtil.java
org\jeecg\modules\system\controller\DuplicateCheckController.java
org\jeecg\modules\system\mapper\xml\SysDictMapper.xml
org\jeecg\modules\system\mapper\SysDictMapper.java
org\jeecg\modules\system\service\impl\SysDictServiceImpl.java
org\jeecg\modules\system\service\ISysDictService.java
2023-09-03 20:07:58 +08:00
58aebdbba4 调整版本号准备发版 2023-08-31 09:48:20 +08:00
2411d85af4 Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot 2023-08-30 19:02:34 +08:00
572ea6dd69 Mono示例 2023-08-30 19:02:00 +08:00
6e417a22ba Merge pull request #5328 from EightMonth/master
issue#5295 issue#5296 issue#5255
2023-08-30 18:50:35 +08:00
3478e8f7bc issue#5295 issue#5296 issue#5255 2023-08-30 11:38:47 +08:00
f9982a9132 【误删接口恢复】用户配置多租户后,登录后点击切换部门按钮,报错 #5298 2023-08-28 17:37:28 +08:00
d3238205e1 jeecg存在权限提升漏洞,可直接获得管理员权限 #5270 2023-08-20 18:51:43 +08:00
ff083361d4 放开系统权限控制,@RequiresPermissions,解决不安全的问题
jeecg存在权限提升漏洞,可直接获得管理员权限 #5270
2023-08-20 18:01:25 +08:00
9c038a979d 时间盲注SQL注入绕过 #5269 2023-08-20 11:32:17 +08:00
5974a2e8a4 Update DynamicRouteLoader.java 2023-08-18 09:54:20 +08:00
30151a2324 Update DynamicRouteLoader.java 2023-08-17 16:06:16 +08:00
28293aba7d Create PredicatesVo.java 2023-08-17 16:01:51 +08:00
d36caf8c69 SQL注入 #5173 2023-08-16 09:40:40 +08:00
e6bd2d5009 升级druid 2023-08-15 18:38:53 +08:00
baf4b96b3f 【漏洞处理】freemarker模板注入漏洞——升级仪表盘 2023-08-15 18:38:41 +08:00
acb48179ab 【漏洞处理】freemarker模板注入问题 禁止解析ObjectConstructor,Execute
(提示:部分依赖尚未上传maven官仓,依赖下载失败请配置jeecg私服)
2023-08-15 17:45:07 +08:00
20889e8724 【issues/4393】解决使用参数tableName=sys_user t&复测,漏洞仍然存在 2023-08-14 15:54:03 +08:00
751b81c7bf 【issues/4393】解决使用参数tableName=sys_user t&复测,漏洞仍然存在 2023-08-14 12:51:31 +08:00
0bc7e0967d 【issues/4393】解决使用参数tableName=sys_user t&复测,漏洞仍然存在 2023-08-14 10:01:49 +08:00
05181754bb 签名失败 2023-08-09 20:45:49 +08:00
5ab62d5d3b 解决Apache Shiro高危漏洞编号:CVE-2023-34478 2023-07-25 11:31:54 +08:00
83d96bf89e JeecgBoot 低代码平台 3.5.3 版本发布,Online 功能专题升级 2023-07-24 11:51:45 +08:00
0b2c966a72 README 更新 2023-07-24 11:11:49 +08:00
ffda530daa README 更新 2023-07-24 11:06:18 +08:00
4c0641f374 README 更新 2023-07-24 10:20:12 +08:00
54c7c034a4 JeecgBoot 低代码平台 3.5.3 版本发布,Online 功能专题升级 2023-07-24 10:11:12 +08:00
7167668c2a JeecgBoot 低代码平台 3.5.3 版本发布,Online 功能专题升级 2023-07-23 23:11:39 +08:00
1a722fcb07 Online功能专题升级,升级到3.5.3版本 2023-07-23 22:30:26 +08:00
648e66d5ef SQL注入 #5173 2023-07-23 16:52:55 +08:00
e6e6902e85 [issues/5134] duplicate/check Sql泄露问题 2023-07-22 11:24:00 +08:00
04dd6b056f Token有效期改为7天 2023-07-18 16:58:10 +08:00
96b3ba6e9a nacos支持开启鉴权的配置 2023-07-18 14:43:46 +08:00
8d22362fff 升级jimureport依赖,修复两个安全漏洞问题 2023-07-18 14:34:47 +08:00
436bfbced9 升级Nacos 至 2.2.3 版本解决 raft 漏洞问题 2023-07-17 19:54:16 +08:00
5273b81f94 【issues/5115】因swagger文档导致gateway内存溢出 2023-07-17 16:11:04 +08:00
6134db4984 开源协议补充 2023-07-05 19:15:35 +08:00
6e7d9e8e5d 开源协议补充 2023-07-05 19:13:59 +08:00
741dc958f1 升级3.5.2的数据库 2023-06-07 18:30:56 +08:00
101ffd91af 升级版本号3.5.2 2023-06-07 15:00:01 +08:00
cecbc3f628 JeecgBoot仪表盘版本发布,重磅新功能!支持在线拖拽设计大屏和门户 2023-06-06 21:16:58 +08:00
411 changed files with 52854 additions and 19462 deletions

View File

@ -1,21 +0,0 @@
##### 版本号:
##### 前端版本vue3版还是 vue2版
##### 问题描述:
##### 截图&代码:
#### 友情提示为了提高issue处理效率
- 未按格式要求发帖,会被直接删掉;
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;
- 请自己初判问题描述是否清楚,是否方便我们调查处理;
- 针对问题请说明是Online在线功能(需说明用的主题模板),还是生成的代码功能;

View File

@ -1,8 +1,9 @@
##### 版本号: ##### 版本号:
##### 分支: master还是springboot3?
##### 前端版本vue3版还是 vue2版
##### 前端版本: vue3版还是 vue2版
##### 问题描述: ##### 问题描述:
@ -13,9 +14,9 @@
#### 友情提示为了提高issue处理效率 #### 友情提示为了提高issue处理效率
- 未按格式要求发帖,会被直接删掉; - 未按格式要求发帖、描述过于简抽象的,会被直接删掉
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;
- 请自己初判问题描述是否清楚,是否方便我们调查处理; - 请自己初判问题描述是否清楚,是否方便我们调查处理;
- 针对问题请说明是Online在线功能(需说明用的主题模板),还是生成的代码功能; - 针对问题请说明是Online在线功能(需说明用的主题模板),还是生成的代码功能;
- springboot3_sas分支采用 `Spring Authorization Server` 替换 `Shiro`目前是beta版不稳定生产项目不要使用;

4
.gitignore vendored
View File

@ -9,3 +9,7 @@ rebel.xml
## front ## front
**/*.lock **/*.lock
os_del.cmd
os_del_doc.cmd
.svn
derby.log

View File

@ -203,11 +203,14 @@
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software. In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
开源协议补充 开源协议补充
JeecgBoot 是由 北京敲敲云科技有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com
本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。 本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
1.允许基于本平台软件开展业务系统开发。 1.允许基于本平台软件开展业务系统开发。
2.不得基于该平台软件的基础修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售或与JeecgBoot参与同类软件产品市场的竞争。 2.JeecgBoot底层依赖的非开源功能online lib依赖、仪表盘lib依赖等统一采用LGPL开源协议不二次改造、不拆分出jeecgboot之外使用,就不产生侵权)
3.不得基于该平台软件的基础修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售或与JeecgBoot参与同类软件产品市场的竞争。
违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。 违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
总结在遵循Apache开源协议和开源协议补充条款下允许商用使用不会造成侵权行为
解释权归http://www.jeecg.com 解释权归http://www.jeecg.com

View File

@ -7,13 +7,13 @@
JEECG BOOT Low Code Development Platform JEECG BOOT Low Code Development Platform
=============== ===============
The Latest Version 3.5.1Release date2023-04-20 当前最新版本: 3.6.3(发布日期2024-03-11
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE) [![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-qiaoqiaoyun-orange.svg)](http://www.jeecg.com) [![](https://img.shields.io/badge/Author-guojusoft-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/Blog-blog-blue.svg)](https://jeecg.blog.csdn.net) [![](https://img.shields.io/badge/Blog-blog-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.5.1-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot) [![](https://img.shields.io/badge/version-3.6.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot) [![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot) [![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
@ -44,10 +44,12 @@ Official Support: http://jeecg.com/doc/help
Download the source code Download the source code
----------------------------------- -----------------------------------
- The background source https://github.com/jeecgboot/jeecg-boot 项目源码
- Front-end source (Vue3 version)https://github.com/jeecgboot/jeecgboot-vue3 -----------------------------------
- Front-end source (Vue2 version)https://github.com/jeecgboot/ant-design-vue-jeecg | Source |Front-end source (Vue3 version) | The background source |
- APP Supporting frameworkhttps://github.com/jeecgboot/jeecg-uniapp |-|-|-|
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
| Gitee | [jeecgboot-vue3](https://gitee.com/jeecg/jeecgboot-vue3) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
##### Project description ##### Project description
@ -55,11 +57,9 @@ Download the source code
|--------------------|------------------------| |--------------------|------------------------|
| `jeecg-boot` | SpringBoot background source code (support microservices) | | `jeecg-boot` | SpringBoot background source code (support microservices) |
| `jeecgboot-vue3` | Vue3+TS new front-end source code| | `jeecgboot-vue3` | Vue3+TS new front-end source code|
| `ant-design-vue-jeecg` |Vue2 version front-end source code | | `jeecg-uniapp` | [APP development framework, a code multi terminal adaptation, and support APP, small program, H5](https://github.com/jeecgboot/jeecg-uniapp) |
| `jeecg-uniapp` | APP development framework, a code multi terminal adaptation, and support APP, small program, H5 | | `SpringBoot3+JDK17` | [BranchSourceCode](https://github.com/jeecgboot/jeecg-boot/tree/springboot3) [UpgradeBlog](https://blog.csdn.net/zhangdaiscott/article/details/134805602) |
| `jeecg-boot-starter` | [Stater relies on the project to be maintained separately. Click Download](https://gitee.com/jeecg/jeecg-boot-starter) | | `More` | [Download more source code](http://jeecg.com/download) |
| `More` | [Download more source code](https://github.com/jeecgboot) |
@ -76,7 +76,7 @@ Docker starts the project
- [Docker starts the monomer background](https://help.jeecg.com/java/setup/docker/up.html) - [Docker starts the monomer background](https://help.jeecg.com/java/setup/docker/up.html)
- [Docker starts the Vue3 front-end](http://help.jeecg.com/publish/docker.html) - [Docker starts the Vue3 front-end](http://help.jeecg.com/publish/docker.html)
- [Docker starts the micro-service background](https://help.jeecg.com/java/springcloud/docker.html) - [Docker starts the micro-service background](https://help.jeecg.com/java/springcloud/docker.html)
- [ChatGPT AI Config](https://help.jeecg.com/java/chatgpt.html)
@ -84,21 +84,23 @@ Technical documentation
----------------------------------- -----------------------------------
- Website [http://www.jeecg.com](http://www.jeecg.com) - Website [http://www.jeecg.com](http://www.jeecg.com)
- Demo [Vue3](http://boot3.jeecg.com) | [Vue2](http://boot.jeecg.com)
- Doc [http://help.jeecg.com](http://help.jeecg.com) - Doc [http://help.jeecg.com](http://help.jeecg.com)
- Newbie guide [Quick start](http://www.jeecg.com/doc/quickstart) | [video](https://space.bilibili.com/454617261/channel/series) | [Q&A ](http://www.jeecg.com/doc/qa) | [help](http://jeecg.com/doc/help) | [1 minute experience](https://my.oschina.net/jeecg/blog/3083313) - Newbie guide [Quick start](http://www.jeecg.com/doc/quickstart) | [video](https://space.bilibili.com/454617261/channel/series) | [Q&A ](http://www.jeecg.com/doc/qa) | [help](http://jeecg.com/doc/help) | [1 minute experience](https://my.oschina.net/jeecg/blog/3083313)
- Microservice Development [Monomer upgrade to microservice](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html) - Microservice Development [Monomer upgrade to microservice](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
- QQ group ⑨808791225、⑧825232878、⑦791696430、⑥730954414(full)、683903138(full)、⑤860162132(full)、④774126647(full)、③816531124(full)、②769925425(full)、①284271917(full)
- QQ group ⑦791696430、⑥730954414、683903138、⑤860162132(full)、④774126647(full)、③816531124(full)、②769925425(full)、①284271917(full) - Demo [Vue3](http://boot3.jeecg.com) | [Vue2](http://boot.jeecg.com) | [APP](http://jeecg.com/appIndex)
> [please click obtain account password to obtain](http://jeecg.com/doc/demo)
Thinking
-----------------------------------
> We are pursuing the goal of implementing complex business systems without writing code! That has been done so far
- https://www.qiaoqiaoyun.com
##### Star charts
Star charts
-----------------------------------
[![Star History Chart](https://api.star-history.com/svg?repos=jeecgboot/jeecg-boot&type=Date)](https://star-history.com/#jeecgboot/jeecg-boot) [![Star History Chart](https://api.star-history.com/svg?repos=jeecgboot/jeecg-boot&type=Date)](https://star-history.com/#jeecgboot/jeecg-boot)
@ -439,6 +441,10 @@ Technical Architecture:
### Effect of system ### Effect of system
##### ChatGPT AI Dialog
> Go to the JeecgBoot background home page and click "AI Assistant" in the middle of the right side of the home page. The AI Assistant dialog screen is displayed.
![](https://oscimg.oschina.net/oscnet/up-7c6405641a40f56638999d52da0cb5b4343.png)
##### PC ##### PC
![](https://oscimg.oschina.net/oscnet/up-000530d95df337b43089ac77e562494f454.png) ![](https://oscimg.oschina.net/oscnet/up-000530d95df337b43089ac77e562494f454.png)

140
README.md
View File

@ -7,13 +7,13 @@
JEECG BOOT 低代码开发平台 JEECG BOOT 低代码开发平台
=============== ===============
当前最新版本: 3.5.1发布日期2023-04-20 当前最新版本: 3.6.3发布日期2024-03-11
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE) [![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京敲敲云科技-orange.svg)](http://www.jeecg.com) [![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://jeecg.com/aboutusIndex)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://jeecg.blog.csdn.net) [![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.5.1-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot) [![](https://img.shields.io/badge/version-3.6.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot) [![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot) [![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
@ -33,39 +33,74 @@ JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。 `JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。
遇到技术问题,[请在这里反馈BUG](https://github.com/jeecgboot/jeecg-boot/issues/new)
适用项目 适用项目
----------------------------------- -----------------------------------
Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中尤其适合SAAS项目、企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRM其半智能手工Merge的开发方式可以显著提高开发效率70%以上,极大降低开发成本。 Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中尤其适合SAAS项目、企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRM其半智能手工Merge的开发方式可以显著提高开发效率70%以上,极大降低开发成本。
源码下载
项目源码
----------------------------------- -----------------------------------
| 仓库 |前端源码 Vue3版 | 后端JAVA源码 |
|-|-|-|
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
| 码云 | [jeecgboot-vue3](https://gitee.com/jeecg/jeecgboot-vue3) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
| 源码 | 源码地址 | > 官方已推出 `SpringBoot3+JDK17版本` [分支源码下载](https://github.com/jeecgboot/jeecg-boot/tree/springboot3) | [升级SpringBoot3博客](https://blog.csdn.net/zhangdaiscott/article/details/134805602)
|--------------------|------------------------|
| 后台源码 JAVA | https://github.com/jeecgboot/jeecg-boot |
| 前端源码 `Vue3版` | https://github.com/jeecgboot/jeecgboot-vue3 |
| APP配套框架 | https://github.com/jeecgboot/jeecg-uniapp |
##### 快速搭建开发环境 #### 项目说明
- [通过IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup.html)
- [Vue3前端项目快速启动](http://help.jeecg.com/setup/startup.html)
- [单体快速切换为微服务版](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
##### 项目说明
| 项目名 | 说明 | | 项目名 | 说明 |
|--------------------|------------------------| |--------------------|------------------------|
| `jeecg-boot` | SpringBoot后台源码支持微服务 | | `jeecgboot-vue3` | 前端源码 (Vue3版本) |
| `jeecgboot-vue3` | Vue3+TS 新版前端源码 | | `jeecg-boot` | 后端JAVA源码支持微服务 |
| `jeecg-uniapp` | APP开发框架一份代码多终端适配同时支持APP、小程序、H5 | | `jeecg-uniapp` | [APP开发框架一份代码多终端适配同时支持APP、小程序、H5](https://github.com/jeecgboot/jeecg-uniapp) |
| `jeecg-boot-starter` | [Stater依赖项目单独维护点击下载](https://gitee.com/jeecg/jeecg-boot-starter) | | `更多开源项目` | [更多底层源码下载](http://jeecg.com/download) |
| `更多开源插件` | [更多源码下载](https://github.com/jeecgboot) |
快速搭建开发环境
-----------------------------------
- [通过IDEA导入项目](https://help.jeecg.com/java/setup/idea.html)
- [通过IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup.html)
- [Vue3前端项目快速启动](http://help.jeecg.com/setup/startup.html)
- [单体快速切换为微服务版](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
- [ChatGPT AI助手配置文档](https://help.jeecg.com/java/chatgpt.html)
Docker快速启动项目
-----------------------------------
- [Docker启动单体后台](https://help.jeecg.com/java/setup/docker/up.html)
- [Docker启动Vue3前端](http://help.jeecg.com/publish/docker.html)
- [Docker启动微服务后台](https://help.jeecg.com/java/springcloud/docker.html)
技术文档
-----------------------------------
- 项目官网: [http://www.jeecg.com](http://www.jeecg.com)
- 开发文档: [https://help.jeecg.com](https://help.jeecg.com)
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [常见问题 ](http://www.jeecg.com/doc/qa) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [1分钟低代码体验](https://my.oschina.net/jeecg/blog/3083313)
- 在线演示 [Vue3演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [敲敲云零代码](https://qiaoqiaoyun.com)
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
>
- QQ交流群 ⑨808791225、⑧825232878、⑦791696430(满)、⑥730954414(满)、683903138(满)、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
> ` 提醒【QQ群是自助服务群建议给帮助您解决问题的同学发送指定红包表示感谢】 `
大龄码农的思考
-----------------------------------
> 作为码农年纪大了写不动代码了怎么办??哎!!
所以我们团队在追求不写代码也可实现复杂业务系统!目前已经做到了,不信你到敲敲云零代码试试(通过流程串联修改业务数据)
- https://www.qiaoqiaoyun.com
技术支持 技术支持
----------------------------------- -----------------------------------
@ -74,44 +109,19 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
官方支持: [http://jeecg.com/doc/help](http://jeecg.com/doc/help) 官方支持: [http://jeecg.com/doc/help](http://jeecg.com/doc/help)
技术文档
-----------------------------------
- 项目官网: [http://www.jeecg.com](http://www.jeecg.com)
- 在线演示 [低代码演示](http://boot3.jeecg.com) | [零代码体验](http://app.qiaoqiaoyun.com)
- 开发文档: [http://help.jeecg.com](http://help.jeecg.com)
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [常见问题 ](http://www.jeecg.com/doc/qa) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [1分钟低代码体验](https://my.oschina.net/jeecg/blog/3083313)
- QQ交流群 ⑦791696430、⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
> ` 提醒【QQ群是自助服务群建议给帮助您解决问题的同学发送指定红包表示感谢】 `
Docker启动项目
-----------------------------------
- [Docker启动单体后台](https://help.jeecg.com/java/setup/docker/up.html)
- [Docker启动Vue3前端](http://help.jeecg.com/publish/docker.html)
- [Docker启动微服务后台](https://help.jeecg.com/java/springcloud/docker.html)
=======【VUE2版本专题介绍】============================================
VUE2版本专题介绍 VUE2版本专题介绍
----------------------------------- -----------------------------------
#### 项目介绍 #### 项目介绍
- 项目名称ant-design-vue-jeecg - 项目名称ant-design-vue-jeecg
- 说明JeecgBoot前端提供两套解决方案一套VUE2和一套VUE3版本目前vue2版本最新代码只支持到jeecgboot 3.4.3版本,一定注意。 - 说明JeecgBoot前端提供两套解决方案一套VUE2和一套VUE3版本目前vue2版本最新代码只支持到jeecgboot 3.4.3版本,一定注意。
- 更多介绍:[Vue2版演示](http://boot.jeecg.com) |[开发文档](http://doc.jeecg.com)
- [快速启动——Vue2前端](http://doc.jeecg.com/2678320)
- [Docker启动——Vue2前端](http://doc.jeecg.com/3043612)
#### 源码下载
| 源码 | 源码地址 |
|--------------------|------------------------|
| 后端JAVA源码 `Vue2版` |https://gitee.com/jeecg/jeecg-boot/tree/v3.4.3last |
| 前端vue2源码 `Vue2版` |https://gitee.com/jeecg/ant-design-vue-jeecg |
#### Vue2与Vue3版本区别 #### Vue2与Vue3版本区别
> - VUE3版本彻底抛弃IE兼容不兼容IE和低版本浏览器只适配高版本谷歌和Edge > - VUE3版本彻底抛弃IE兼容不兼容IE和低版本浏览器只适配高版本谷歌和Edge
@ -119,21 +129,14 @@ VUE2版本专题介绍
> - 所以如果对浏览器有要求的项目请选择VUE2版本。 > - 所以如果对浏览器有要求的项目请选择VUE2版本。
> - VUE3版是全新的技术栈紧跟主流前端重写各个功能都做了优化拥有更好的体验效果 > - VUE3版是全新的技术栈紧跟主流前端重写各个功能都做了优化拥有更好的体验效果
#### 技术文档
#### 源码下载 - 在线演示:[Vue2版演示](http://boot.jeecg.com)
| 源码 | 源码地址 | - 开发文档:| [开发文档](http://doc.jeecg.com) | [Vue2前端快速启动](http://doc.jeecg.com/2678320) | [Vue2前端采用Docker启动](http://doc.jeecg.com/3043612)
|--------------------|------------------------|
| 后端源码 `Vue2版` |https://gitee.com/jeecg/jeecg-boot/tree/v3.4.3last |
| 前端源码 `Vue2版` |https://gitee.com/jeecg/ant-design-vue-jeecg |
=========【VUE2版本专题介绍】=========================================
Star走势图
-----------------------------------
##### Star走势图
[![Star History Chart](https://api.star-history.com/svg?repos=jeecgboot/jeecg-boot&type=Date)](https://star-history.com/#jeecgboot/jeecg-boot) [![Star History Chart](https://api.star-history.com/svg?repos=jeecgboot/jeecg-boot&type=Date)](https://star-history.com/#jeecgboot/jeecg-boot)
@ -193,8 +196,8 @@ VUE2版本专题介绍
* 17.支持SAAS服务模式提供SaaS多租户架构方案。 * 17.支持SAAS服务模式提供SaaS多租户架构方案。
* 18.分布式文件服务集成minio、阿里OSS等优秀的第三方提供便捷的文件上传与管理同时也支持本地存储。 * 18.分布式文件服务集成minio、阿里OSS等优秀的第三方提供便捷的文件上传与管理同时也支持本地存储。
* 19.主流数据库兼容一套代码完全兼容Mysql、Postgresql、Oracle、Sqlserver、MariaDB、达梦等主流数据库。 * 19.主流数据库兼容一套代码完全兼容Mysql、Postgresql、Oracle、Sqlserver、MariaDB、达梦等主流数据库。
* 20.集成工作流activiti、flowable并实现了只需在页面配置流程转向可极大的简化bpm工作流的开发用bpm的流程设计器画出了流程走向一个工作流基本就完成了只需写很少量的java代码 * 20.集成工作流flowable、activiti并实现了只需在页面配置流程转向可极大的简化bpm工作流的开发用bpm的流程设计器画出了流程走向一个工作流基本就完成了只需写很少量的java代码
* 21.低代码能力:在线流程设计,采用开源Activiti流程引擎实现在线画流程,自定义表单,表单挂靠,业务流转 * 21.低代码能力:在线流程设计,采用开源flowable、activiti流程引擎实现在线画流程,自定义表单,表单挂靠,业务流转
* 22.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据; * 22.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
* 23.提供单点登录CAS集成方案项目中已经提供完善的对接代码 * 23.提供单点登录CAS集成方案项目中已经提供完善的对接代码
* 24.低代码能力表单设计器支持用户自定义表单布局支持单表一对多表单、支持select、radio、checkbox、textarea、date、popup、列表、宏等控件 * 24.低代码能力表单设计器支持用户自定义表单布局支持单表一对多表单、支持select、radio、checkbox、textarea、date、popup、列表、宏等控件
@ -464,13 +467,20 @@ VUE2版本专题介绍
``` ```
### 流程引擎推荐
JeecgBoot企业版本默认集成了activiti和flowable两套方案大家在使用本开源项目时如果想进一步集成流程引擎推荐结合贺波老师的书 [《深入Activiti流程引擎核心原理与高阶实战》](https://item.m.jd.com/product/13928958.html?gx=RnAomTM2bmCImZxDqYAkVCoIHuIYVqc)
<img src="https://jeecgos.oss-cn-beijing.aliyuncs.com/files/tuijian20231220161656.png" width="25%" height="auto">
### 系统效果 ### 系统效果
##### ChatGPT AI交互
> 进入JeecgBoot后台首页点击首页右侧中间“AI助手”弹出AI助手对话界面。
![](https://oscimg.oschina.net/oscnet/up-7c6405641a40f56638999d52da0cb5b4343.png)
##### PC端 ##### PC端
![](https://oscimg.oschina.net/oscnet/up-000530d95df337b43089ac77e562494f454.png) ![](https://oscimg.oschina.net/oscnet/up-000530d95df337b43089ac77e562494f454.png)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +0,0 @@
oracle导出编码 export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
导出用户: jeecgboot
导入命令: imp scott/tiger@orcl file=jeecgboot-oracle11g.dmp

View File

@ -1,161 +0,0 @@
-- 升级说明系统敏感接口加了权限注解此部分是补请求注解配置
-- author: wangshuai---date:20221118 -for: [VUEN-2735] jeecgboot敏感接口权限加注解 vue3
-- 用户管理
UPDATE sys_permission SET perms = 'system:user:changepwd' WHERE id = '1588513553652436993';
UPDATE sys_permission SET name = '用户导入', perms = 'system:user:import' WHERE id = 'f15543b0263cf6c5fac85afdd3eba3f2';
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593185714482880514', '3f915b2769fc80648e92d04e84ca059d', '用户导出', NULL, NULL, 0, NULL, NULL, 2, 'system:user:export', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 18:14:09', NULL, NULL, 0, 0, '1', 0);
-- 数据字典子项
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593160905216663554', '1438782641187074050', '字典子项新增', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:35:34', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593160959633563650', '1438782641187074050', '字典子项编辑', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:35:47', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161025790320641', '1438782641187074050', '字典子项删除', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:36:03', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161089787011074', '1438782641187074050', '字典子项批量删除', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:36:18', NULL, NULL, 0, 0, '1', 0);
-- 定时任务
UPDATE sys_permission SET is_leaf = 0 WHERE id = '1439488251473993730';
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161421350936578', '1439488251473993730', '添加定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:37:37', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161483627962370', '1439488251473993730', '更新定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:37:52', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161551202394114', '1439488251473993730', '删除定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:08', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161608362369026', '1439488251473993730', '批量删除定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:22', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161657385394177', '1439488251473993730', '停止定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:pause', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:33', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161697348722689', '1439488251473993730', '启动定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:resume', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:43', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161743607701505', '1439488251473993730', '立即执行定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:execute', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:54', NULL, NULL, 0, 0, '1', 0);
-- oos上传
UPDATE sys_permission SET is_leaf = 0 WHERE id = '1442055284830769154';
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593173018886709250', '1442055284830769154', '文件oos上传', NULL, NULL, 0, NULL, NULL, 2, 'system:ossFile:upload', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 17:23:42', NULL, NULL, 0, 0, '1', 0);
-- author: wangshuai---date:20221118 -for: [VUEN-2735] jeecgboot敏感接口权限加注解 vue3
-- author: wangshuai---date:20221118 -for: [VUEN-2735] jeecgboot敏感接口权限加注解 vue2
-- 用户管理
UPDATE sys_permission_v2 SET perms = 'system:user:changepwd' WHERE id = '1588513553652436993';
UPDATE sys_permission_v2 SET name = '用户导入', perms = 'system:user:import' WHERE id = 'f15543b0263cf6c5fac85afdd3eba3f2';
UPDATE sys_permission_v2 SET perms = 'system:user:export' WHERE id = '1305812030117220354';
-- 数据字典子项
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593160905216663554', 'f1cb187abf927c88b89470d08615f5ac', '字典子项新增', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:35:34', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593160959633563650', 'f1cb187abf927c88b89470d08615f5ac', '字典子项编辑', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:35:47', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161025790320641', 'f1cb187abf927c88b89470d08615f5ac', '字典子项删除', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:36:03', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161089787011074', 'f1cb187abf927c88b89470d08615f5ac', '字典子项批量删除', NULL, NULL, 0, NULL, NULL, 2, 'system:dict:item:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:36:18', NULL, NULL, 0, 0, '1', 0);
-- 定时任务
UPDATE sys_permission_v2 SET is_leaf = 0 WHERE id = 'b1cb0a3fedf7ed0e4653cb5a229837ee';
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161421350936578', 'b1cb0a3fedf7ed0e4653cb5a229837ee', '添加定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:37:37', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161483627962370', 'b1cb0a3fedf7ed0e4653cb5a229837ee', '更新定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:37:52', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161551202394114', 'b1cb0a3fedf7ed0e4653cb5a229837ee', '删除定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:08', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161608362369026', 'b1cb0a3fedf7ed0e4653cb5a229837ee', '批量删除定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:22', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161657385394177', 'b1cb0a3fedf7ed0e4653cb5a229837ee', '停止定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:pause', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:33', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161697348722689', 'b1cb0a3fedf7ed0e4653cb5a229837ee', '启动定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:resume', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:43', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593161743607701505', 'b1cb0a3fedf7ed0e4653cb5a229837ee', '立即执行定时任务', NULL, NULL, 0, NULL, NULL, 2, 'system:quartzJob:execute', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 16:38:54', NULL, NULL, 0, 0, '1', 0);
-- oos上传
UPDATE sys_permission_v2 SET is_leaf = 0 WHERE id = '1166535831146504193';
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1593173018886709250', '1166535831146504193', '文件oos上传', NULL, NULL, 0, NULL, NULL, 2, 'system:ossFile:upload', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-17 17:23:42', NULL, NULL, 0, 0, '1', 0);
-- author: wangshuai---date:20221118 -for: [VUEN-2735] jeecgboot敏感接口权限加注解 vue2
-- author: wangshuai---date:20221126 -for:首页用户设置用户编辑敏感权限
UPDATE sys_permission SET parent_id = 'd7d6e2e4e2934f2c9385a623fd98c6f3', name = '用户设置', url = '/system/usersetting', component = 'system/usersetting/UserSetting', is_route = 1, component_name = '', redirect = NULL, menu_type = 1, perms = NULL, perms_type = '0', sort_no = 12.00, always_show = 0, icon = 'ant-design:setting-twotone', is_leaf = 0, keep_alive = 0, hidden = 0, hide_tab = 0, description = NULL, create_by = 'admin', create_time = '2022-11-25 22:01:08', update_by = 'admin', update_time = '2022-11-26 10:50:07', del_flag = 0, rule_flag = 0, status = NULL, internal_or_external = 0 WHERE id = '1596141938193747970';
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1596335805278990338', '1596141938193747970', '账户设置用户编辑权限', NULL, NULL, 0, NULL, NULL, 2, 'system:user:setting:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-26 10:51:29', NULL, NULL, 0, 0, '1', 0);
-- author: wangshuai---date:20221126 -for:首页用户设置用户编辑敏感权限
-- 租户邀请用户按钮权限菜单
UPDATE sys_permission SET is_leaf = 0 WHERE id = '1280350452934307841';
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1600105607009165314', '1280350452934307841', '邀请用户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:invitation:user', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-12-06 20:31:20', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1600108123037917186', '1280350452934307841', '通过租户id获取用户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:user:list', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-12-06 20:41:20', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1600129606082650113', '1280350452934307841', '租户请离', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:leave', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-12-06 22:06:42', NULL, NULL, 0, 0, '1', 0);
-- author: wangshuai---date:20221209--for:租户改造新增字段菜单权限字典-----
-- -author:scott---date:2022-12-30---for:增加查询全部用户接口原用户接口改成租户下的用户接口支持租户隔离---
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('1592135223910765570', '3f915b2769fc80648e92d04e84ca059d', '查询全部用户', NULL, NULL, 0, NULL, NULL, 2, 'system:user:listAll', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-14 19:20:22', NULL, NULL, 0, 0, '1', 0);
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
VALUES ('119213522910765570', 'd7d6e2e4e2934f2c9385a623fd98c6f3', '租户用户', '/system/user/tenantUserList', 'system/user/TenantUserList', 1, NULL, NULL, 1, NULL, NULL, 3.2, 0, 'ant-design:user', 1, 0, 0, 0, NULL, NULL, '2018-12-25 20:34:38', 'admin', '2022-12-30 10:11:27', 0, 0, NULL, 0);
-- -author:scott---date:2022-12-30---for:增加查询全部用户接口原用户接口改成租户下的用户接口支持租户隔离---
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1609123240547344385', '1280350452934307841', '产品包分页列表查询', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:packList', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-12-31 17:44:11', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1609123437247619074', '1280350452934307841', '创建租户产品包', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:add:pack', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-12-31 17:44:58', 'admin', '2022-12-31 20:27:56', 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1609164542165012482', '1280350452934307841', '编辑租户产品包', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:edit:pack', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-12-31 20:28:18', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1609164635442139138', '1280350452934307841', '批量删除租户产品包', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:delete:pack', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-12-31 20:28:41', NULL, NULL, 0, 0, '1', 0);
-- -author:wangshuai---date:2023-01-04---for: 产品包升级sql
-- 新增接口权限菜单:分页获取租户用户数据/通过用户id获取租户列表/更新用户租户关系状态/注销租户
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1611620416187969538', '1280350452934307841', '分页获取租户用户数据', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:tenantPageList', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-07 15:07:04', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1611620600003342337', '1280350452934307841', '通过用户id获取租户列表', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:getTenantListByUserId', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-07 15:07:48', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1611620654621569026', '1280350452934307841', '更新用户租户关系状态', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:updateUserTenantStatus', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-07 15:08:01', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1611620772498288641', '1280350452934307841', '注销租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:cancelTenant', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-07 15:08:29', NULL, NULL, 0, 0, '1', 0);
-- author:wangshuai---date:2023-01-09---for: 关系表状态修改/租户表增加逻辑删除/新增接口权限菜单
-- author:scott---date:2023-01-09---for: 租户修改注解权限
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
VALUES ('1612438989792034818', '1280350452934307841', '编辑租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-07 15:08:29', NULL, NULL, 0, 0, '1', 0);
-- author:scott---date:2023-01-09---for: 租户修改注解权限
-- -author:scott---date:2023-01-11--for租户安全加强因为id规则暴露了很不安全
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1611620772498218641', '1280350452934307841', '查询租户列表', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:list', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-11 15:08:29', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1621620772498288641', '1280350452934307841', '添加租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-11 15:08:29', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1611650772498288641', '1280350452934307841', '删除租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-11 15:08:29', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1613620712498288641', '1280350452934307841', '批量删除租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-11 15:08:29', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES
('1697220712498288641', '1280350452934307841', '根据ids查询租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:queryList', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-11 15:08:29', NULL, NULL, 0, 0, '1', 0);
-- -author:scott---date:2023-01-11--for租户安全加强因为id规则暴露了很不安全
-- -author:scott---date:2023-01-11--for增加一个查询全部角色不做租户隔离的接口
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1592120224120850434', '190c2b43bec6a5f7a4194a85db67d96a', '查询全部角色不租户隔离', NULL, NULL, 0, NULL, NULL, 2, 'system:role:queryallNoByTenant', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-11 19:41:18', NULL, NULL, 0, 0, '1', 0);
-- -author:scott---date:2023-01-11--for增加一个查询全部角色不做租户隔离的接口
-- -author:scott---date:2023-01-31--for用户敏感接口通过id获取用户信息通过id查询用户角色加权限
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1d592115213910765570', '3f915b2769fc80648e92d04e84ca059d', '通过ID查询用户拥有的角色', NULL, NULL, 0, NULL, NULL, 2, 'system:user:queryUserRole', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-14 19:20:22', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('15c92115213910765570', '3f915b2769fc80648e92d04e84ca059d', '通过ID查询用户信息接口', NULL, NULL, 0, NULL, NULL, 2, 'system:user:queryById', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-14 19:20:22', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('159d2115213910765570', '3f915b2769fc80648e92d04e84ca059d', '通过ID查询用户拥有的角色', NULL, NULL, 0, NULL, NULL, 2, 'system:user:queryUserRole', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-14 19:20:22', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('15s92115213910765570', '3f915b2769fc80648e92d04e84ca059d', '通过ID查询用户信息接口', NULL, NULL, 0, NULL, NULL, 2, 'system:user:queryById', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-14 19:20:22', NULL, NULL, 0, 0, '1', 0);
-- -author:scott---date:2023-01-31--for用户敏感接口通过id获取用户信息通过id查询用户角色加权限
-- -author:wangshuai---date:2023-02-02--for[QQYUN-3959]新增回收站接口权限
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1620261087828418562', '1280350452934307841', '获取租户删除的列表', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:recycleBinPageList', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-31 11:22:01', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1620305415648989186', '1280350452934307841', '彻底删除租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:deleteTenantLogic', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-31 14:18:10', 'admin', '2023-01-31 14:19:51', 0, 0, '1', 0);
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1620327825894981634', '1280350452934307841', '租户还原', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:revertTenantLogic', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-31 15:47:13', NULL, NULL, 0, 0, '1', 0);
-- -author:wangshuai---date:2023-02-02--for[QQYUN-3959]新增回收站接口权限
-- -author:wangshuai---date:2023-02-02--for[QQYUN-3959]vue2菜单新增租户回收站接口权限
UPDATE sys_permission_v2 SET is_leaf = 0 WHERE id = '1280350452934307841';
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1620261087828418562', '1280350452934307841', '获取租户删除的列表', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:recycleBinPageList', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-31 11:22:01', NULL, NULL, 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1620305415648989186', '1280350452934307841', '彻底删除租户', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:deleteTenantLogic', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-31 14:18:10', 'admin', '2023-01-31 14:19:51', 0, 0, '1', 0);
INSERT INTO sys_permission_v2(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1620327825894981634', '1280350452934307841', '租户还原', NULL, NULL, 0, NULL, NULL, 2, 'system:tenant:revertTenantLogic', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-01-31 15:47:13', NULL, NULL, 0, 0, '1', 0);
-- -author:wangshuai---date:2023-02-02--for[QQYUN-3959]vue2菜单新增租户回收站接口权限
-- author:wangshuai ---date:2022-11-23 for新增在线用户菜单----
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1594930803956920321', '1439398677984878593', '在线用户', '/system/onlineuser', 'system/onlineuser/OnlineUserList', 1, '', NULL, 1, NULL, '0', 12.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-22 13:48:31', 'admin', '2022-11-23 19:35:10', 0, 0, NULL, 0);
-- author:wangshuai ---date:2022-11-23 for新增在线用户菜单----
-- author:wangshuai ---date:2022-11-25 for新增用户设置菜单----
INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1596141938193747970', 'd7d6e2e4e2934f2c9385a623fd98c6f3', '用户设置', '/system/usersetting', 'system/usersetting/UserSetting', 1, '', NULL, 1, NULL, '0', 12.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-11-25 22:01:08', 'admin', '2022-11-25 22:01:21', 0, 0, NULL, 0);
-- author:wangshuai ---date:2022-11-25 for新增用户设置菜单----
-- author:scott ---date:2022-11-29 for新增租户角色功能----
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1597419994965786625', 'd7d6e2e4e2934f2c9385a623fd98c6f3', '租户角色', '/system/role/TenantRoleList', 'system/role/TenantRoleList', 1, '', NULL, 1, NULL, '0', 3.10, 0, 'ant-design:line-height-outlined', 1, 0, 0, 0, NULL, 'admin', '2022-11-29 10:39:40', 'admin', '2022-11-29 11:08:05', 0, 0, NULL, 0);
-- author:scott ---date:2022-11-29 for新增租户角色功能----
-- author:wangshuai ---date:2022-11-30 for用户设置菜单路由隐藏----
UPDATE sys_permission SET hidden = 1 WHERE id = '1596141938193747970';
-- author:wangshuai ---date:2022-11-30 for用户设置菜单路由隐藏----

View File

@ -1,257 +0,0 @@
-- author: scott---date:20221009 -for: 乐观锁测试
ALTER TABLE `demo`
ADD COLUMN `update_count` int NULL COMMENT '乐观锁测试' AFTER `tenant_id`;
-- author: scott---date:20221009 -for: 乐观锁测试
-- author: scott---date:20221108 -for:VUEN-2064 改造登录选择部门和租户逻辑
ALTER TABLE `sys_user`
ADD COLUMN `login_tenant_id` int NULL COMMENT '上次登录选择租户ID' AFTER `client_id`;
-- author: scott---date:20221108 -for:VUEN-2064 改造登录选择部门和租户逻辑
-- author: scott---date:20221129-for: 系统管理模块支持多租户机制默认加字段[tenant_id]-----
ALTER TABLE `sys_depart`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
ALTER TABLE `sys_role`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
ALTER TABLE `sys_dict`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
ALTER TABLE `sys_position`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
ALTER TABLE `sys_category`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
ALTER TABLE `sys_data_source`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
ALTER TABLE `sys_announcement`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
ALTER TABLE `sys_user_role`
ADD COLUMN `tenant_id` int(10) NULL DEFAULT 0 COMMENT '租户ID';
-- author: scott---date:20221129--for:系统管理模块支持多租户机制默认加字段[tenant_id]-----
-- author: wangshuai---date:20221209--for:租户改造新增字段菜单权限字典-----
ALTER TABLE sys_tenant
ADD COLUMN trade varchar(10) NULL COMMENT '所属行业' AFTER status,
ADD COLUMN company_size varchar(10) NULL COMMENT '公司规模' AFTER trade,
ADD COLUMN company_address varchar(100) NULL COMMENT '公司地址' AFTER company_size,
ADD COLUMN company_logo varchar(200) NULL COMMENT '公司logo' AFTER company_address;
-- 所属行业字典
INSERT INTO sys_dict(id, dict_name, dict_code, description, del_flag, create_by, create_time, update_by, update_time, type, tenant_id) VALUES ('1600042215909134338', '所属行业', 'trade', '行业', 0, 'admin', '2022-12-06 16:19:26', 'admin', '2022-12-06 16:20:50', 0, 0);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600042651777011713', '1600042215909134338', '信息传输软件和信息技术服务业', '1', NULL, 1, 1, 'admin', '2022-12-06 16:21:10', 'admin', '2022-12-06 16:21:27');
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600042736254488578', '1600042215909134338', '制造业', '2', NULL, 1, 1, 'admin', '2022-12-06 16:21:30', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600042785646612481', '1600042215909134338', '租赁和商务服务业', '3', NULL, 1, 1, 'admin', '2022-12-06 16:21:42', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600042835433000961', '1600042215909134338', '教育', '4', NULL, 1, 1, 'admin', '2022-12-06 16:21:54', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600042892072882177', '1600042215909134338', '金融业', '5', NULL, 1, 1, 'admin', '2022-12-06 16:22:07', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600042975539531778', '1600042215909134338', '建筑业', '6', NULL, 1, 1, 'admin', '2022-12-06 16:22:27', 'admin', '2022-12-06 16:22:32');
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043052177854466', '1600042215909134338', '科学研究和技术服务业', '7', NULL, 1, 1, 'admin', '2022-12-06 16:22:46', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043101976825857', '1600042215909134338', '批发和零售业', '8', NULL, 1, 1, 'admin', '2022-12-06 16:22:58', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043157069008898', '1600042215909134338', '住宿和餐饮业', '9', NULL, 1, 1, 'admin', '2022-12-06 16:23:11', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043203105689601', '1600042215909134338', '电子商务', '10', NULL, 1, 1, 'admin', '2022-12-06 16:23:22', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043277504253953', '1600042215909134338', '线下零售与服务业', '11', NULL, 1, 1, 'admin', '2022-12-06 16:23:39', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043334618091521', '1600042215909134338', '文化体育和娱乐业', '12', NULL, 1, 1, 'admin', '2022-12-06 16:23:53', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043401030701058', '1600042215909134338', '房地产业', '13', NULL, 1, 1, 'admin', '2022-12-06 16:24:09', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043476440092673', '1600042215909134338', '交通运输仓储和邮政业', '14', NULL, 1, 1, 'admin', '2022-12-06 16:24:27', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043553837584386', '1600042215909134338', '卫生和社会工作', '15', NULL, 1, 1, 'admin', '2022-12-06 16:24:45', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043628793991170', '1600042215909134338', '公共管理社会保障和社会组织', '16', NULL, 1, 1, 'admin', '2022-12-06 16:25:03', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043675329794050', '1600042215909134338', '电力热力燃气及水生产和供应业', '18', NULL, 1, 1, 'admin', '2022-12-06 16:25:14', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043734607892482', '1600042215909134338', '水利环境和公共设施管理业', '19', NULL, 1, 1, 'admin', '2022-12-06 16:25:28', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043783068880897', '1600042215909134338', '居民服务修理和其他服务业', '20', NULL, 1, 1, 'admin', '2022-12-06 16:25:40', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043822679887874', '1600042215909134338', '政府机构', '21', NULL, 1, 1, 'admin', '2022-12-06 16:25:49', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043859539431426', '1600042215909134338', '渔业', '22', NULL, 1, 1, 'admin', '2022-12-06 16:25:58', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043907551629313', '1600042215909134338', '采矿业', '23', NULL, 1, 1, 'admin', '2022-12-06 16:26:10', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043955731599362', '1600042215909134338', '国际组织', '24', NULL, 1, 1, 'admin', '2022-12-06 16:26:21', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600043991685173249', '1600042215909134338', '其他', '25', NULL, 1, 1, 'admin', '2022-12-06 16:26:30', NULL, NULL);
-- 公司规模字典
INSERT INTO sys_dict(id, dict_name, dict_code, description, del_flag, create_by, create_time, update_by, update_time, type, tenant_id) VALUES ('1600044537800331266', '公司规模', 'company_size', '公司规模', 0, 'admin', '2022-12-06 16:28:40', 'admin', '2022-12-06 16:30:23', 0, 0);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600044644096577538', '1600044537800331266', '20人以下', '1', NULL, 1, 1, 'admin', '2022-12-06 16:29:05', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600044698618335233', '1600044537800331266', '21-99人', '2', NULL, 1, 1, 'admin', '2022-12-06 16:29:18', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600044744172670978', '1600044537800331266', '100-499人', '3', NULL, 1, 1, 'admin', '2022-12-06 16:29:29', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600044792306503681', '1600044537800331266', '500-999人', '4', NULL, 1, 1, 'admin', '2022-12-06 16:29:41', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600044861302804481', '1600044537800331266', '1000-9999人', '5', NULL, 1, 1, 'admin', '2022-12-06 16:29:57', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1600044924313833473', '1600044537800331266', '10000人以上', '6', NULL, 1, 1, 'admin', '2022-12-06 16:30:12', NULL, NULL);
-- author: scott---date:20221227--for:字典增加应用ID-----
ALTER TABLE `sys_dict`
ADD COLUMN `low_app_id` varchar(32) NULL COMMENT '低代码应用ID' AFTER `tenant_id`;
-- author: scott---date:20221227--for:字典增加应用ID-----
-- author: scott---date:20221227--for:租户ID改成10位整数-----
ALTER TABLE `sys_tenant`
MODIFY COLUMN `id` int(10) NOT NULL COMMENT '租户编码' FIRST;
-- author: scott---date:20221227--for:租户ID改成10位整数-----
-- author: wangshuai---date:20221229--for:[QQYUN-3608]租户逻辑改造-----
-- 新增租户中间表
CREATE TABLE sys_user_tenant(
id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键id',
user_id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户id',
tenant_id int(10) NULL DEFAULT NULL COMMENT '租户id',
status varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态(0 冻结 1 正常)',
create_by varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人登录名称',
create_time datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
update_by varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人登录名称',
update_time datetime(0) NULL DEFAULT NULL COMMENT '更新日期',
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户租户关系表' ROW_FORMAT = Dynamic;
-- 职级字典
INSERT INTO sys_dict(id, dict_name, dict_code, description, del_flag, create_by, create_time, update_by, update_time, type, tenant_id) VALUES ('1606645341269299201', '职级', 'company_rank', '公司职级', 0, 'admin', '2022-12-24 21:37:54', 'admin', '2022-12-24 21:38:25', 0, 0);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606645562573361153', '1606645341269299201', '总裁/总经理/CEO', '1', NULL, 1, 1, 'admin', '2022-12-24 21:38:47', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606645619930468354', '1606645341269299201', '副总裁/副总经理/VP', '2', NULL, 2, 1, 'admin', '2022-12-24 21:39:00', 'admin', '2022-12-24 21:40:00');
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606645660241924097', '1606645341269299201', '总监/主管/经理', '3', NULL, 3, 1, 'admin', '2022-12-24 21:39:10', 'admin', '2022-12-24 21:39:41');
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606645696715591682', '1606645341269299201', '员工/专员/执行', '4', NULL, 4, 1, 'admin', '2022-12-24 21:39:19', 'admin', '2022-12-24 21:39:37');
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606645744023146497', '1606645341269299201', '其他', '5', NULL, 5, 1, 'admin', '2022-12-24 21:39:30', NULL, NULL);
-- 部门字典
INSERT INTO sys_dict(id, dict_name, dict_code, description, del_flag, create_by, create_time, update_by, update_time, type, tenant_id) VALUES ('1606646440684457986', '公司部门', 'company_department', '公司部门', 0, 'admin', '2022-12-24 21:42:16', NULL, NULL, 0, 0);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606647668965412866', '1606646440684457986', '总经办', '1', NULL, 1, 1, 'admin', '2022-12-24 21:47:09', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606647703098658817', '1606646440684457986', '技术/IT/研发', '2', NULL, 2, 1, 'admin', '2022-12-24 21:47:17', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606647737919770625', '1606646440684457986', '产品/设计', '3', NULL, 3, 1, 'admin', '2022-12-24 21:47:25', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606647789614567425', '1606646440684457986', '销售/市场/运营', '4', '', 4, 1, 'admin', '2022-12-24 21:47:38', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606647827921145857', '1606646440684457986', '人事/财务/行政', '5', NULL, 5, 1, 'admin', '2022-12-24 21:47:47', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606647860955484162', '1606646440684457986', '资源/仓储/采购', '6', NULL, 6, 1, 'admin', '2022-12-24 21:47:55', NULL, NULL);
INSERT INTO sys_dict_item(id, dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES ('1606647915473047553', '1606646440684457986', '其他', '7', NULL, 7, 1, 'admin', '2022-12-24 21:48:08', NULL, NULL);
-- 租户新增字段
ALTER TABLE sys_tenant
ADD COLUMN house_number varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '门牌号' AFTER company_logo,
ADD COLUMN work_place varchar(100) CHARACTER SET utf32 COLLATE utf32_general_ci NULL DEFAULT NULL COMMENT '工作地点' AFTER house_number,
ADD COLUMN secondary_domain varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '二级域名' AFTER work_place,
ADD COLUMN login_bkgd_img varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录背景图片' AFTER secondary_domain,
ADD COLUMN position varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '职级' AFTER login_bkgd_img,
ADD COLUMN department varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门' AFTER position;
-- 移除用户多租户id
ALTER TABLE sys_user
DROP COLUMN rel_tenant_ids;
-- author: wangshuai---date:20221229--for:[QQYUN-3608]租户逻辑改造-----
-- author: wangshuai---date:20221229--for:租户表创建查询索引,避免数据量大查询慢-----
ALTER TABLE sys_user_tenant
ADD INDEX idx_sut_user_id(user_id) USING BTREE,
ADD INDEX idx_sut_tenant_id(tenant_id) USING BTREE;
-- author: wangshuai---date:20221229--for:租户表创建查询索引,避免数据量大查询慢-----
-- -author:wangshuai---date:2023-01-04---for: 产品包升级sql
CREATE TABLE sys_pack_permission (
id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键编号',
pack_id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '租户产品包名称',
permission_id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单id',
create_by varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
create_time date NULL DEFAULT NULL COMMENT '创建时间',
update_by varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人',
update_time date NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户产品包和菜单关系表' ROW_FORMAT = Dynamic;
CREATE TABLE sys_tenant_pack (
id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键id',
tenant_id int(10) NULL DEFAULT NULL COMMENT '租户id',
pack_name varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '产品包名',
status varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开启状态(0 未开启 1开启)',
remarks varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
create_by varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
create_time date NULL DEFAULT NULL COMMENT '创建时间',
update_by varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人',
update_time date NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户产品包' ROW_FORMAT = Dynamic;
-- author:wangshuai---date:2023-01-09---for: 关系表状态修改/租户表增加逻辑删除/新增接口权限菜单
-- 关系表状态修改
ALTER TABLE sys_user_tenant
MODIFY COLUMN status varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态(1 正常 2 冻结 3 待审核 4 拒绝)' AFTER tenant_id;
-- 租户表增加逻辑删除
ALTER TABLE sys_tenant
ADD COLUMN del_flag tinyint(1) NULL DEFAULT NULL COMMENT '删除状态(0-正常,1-已删除)' AFTER department;
ALTER TABLE `sys_tenant`
MODIFY COLUMN `del_flag` tinyint(1) NULL DEFAULT 0 COMMENT '删除状态(0-正常,1-已删除)' AFTER `department`;
-- 需要将租户删除状态改成0正常,否则可能导致数据出不来
update sys_tenant set del_flag = 0;
-- -author:wangshuai---date:2023-01-11--forQQYUN-3938租户表加修改人修改时间
ALTER TABLE sys_tenant
ADD COLUMN update_by varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人' AFTER del_flag,
ADD COLUMN update_time datetime(0) NULL DEFAULT NULL COMMENT '更新时间' AFTER update_by;
-- -author:wangshuai---date:2023-01-11--forQQYUN-3938租户表加修改人修改时间
-- -author:wangshuai---date:2023-01-11--for用户租户修改离职状态
ALTER TABLE sys_user_tenant
MODIFY COLUMN status varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态(1 正常 2 离职 3 待审核 4 审核未通过)' AFTER tenant_id;
-- -author:wangshuai---date:2023-01-11--for用户租户修改离职状态
-- -author:wangshuai---date:2023-02-02--for[QQYUN-3988]租户产品包表改名
ALTER TABLE sys_pack_permission RENAME sys_tenant_pack_perms;
-- -author:wangshuai---date:2023-02-02--for[QQYUN-3988]租户产品包表改名
-- -author:zyf---date:2023-02-02--for添加流程离职入职状态
ALTER TABLE sys_user
ADD COLUMN bpm_status varchar(2) NULL DEFAULT NULL COMMENT '流程入职离职状态';
-- -author:zyf---date:2023-02-02--for添加流程离职入职状态
-- -author:scott---date:2023-02-08--for尽量不要用大字段会出问题
ALTER TABLE `sys_user`
MODIFY COLUMN `depart_ids` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '负责部门' AFTER `user_identity`;
-- -author:scott---date:2023-02-08--for尽量不要用大字段会出问题
-- -author:wangshuai---date:2023-02-16--for[QQYUN-4163]部门新增是否叶子结点
ALTER TABLE sys_depart
ADD COLUMN iz_leaf tinyint(1) NULL DEFAULT 0 COMMENT '是否有叶子节点: 1是0否' AFTER tenant_id;
-- 更新父级部门不为叶子结点
update sys_depart set iz_leaf = 0 where id in ( select a.parent_id from (select parent_id from sys_depart where parent_id!='' and parent_id is not null) as a);
-- -author:wangshuai---date:2023-02-16--for[QQYUN-4163]部门新增是否叶子结点
-- -author: taoyan---date:2023-02-17--forQQYUN-3851租户租户管理员功能
ALTER TABLE `sys_tenant_pack`
ADD COLUMN `pack_code` varchar(50) NULL COMMENT '编码,默认添加的三个管理员需要设置编码' AFTER `update_time`;
ALTER TABLE `sys_log`
ADD COLUMN `tenant_id` int(10) NULL COMMENT '租户ID' AFTER `update_time`;
CREATE TABLE `sys_tenant_pack_user` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`pack_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '租户产品包ID',
`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户ID',
`tenant_id` int(10) NULL DEFAULT NULL COMMENT '租户ID',
`create_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户套餐人员表' ROW_FORMAT = Dynamic;
ALTER TABLE `sys_tenant_pack_user`
ADD COLUMN `status` int(3) NULL COMMENT '状态 正常状态1 申请状态0' AFTER `update_time`;
ALTER TABLE `sys_tenant`
ADD COLUMN `apply_status` int(3) NULL COMMENT '允许申请管理员 1允许 0不允许' AFTER `update_time`;
update sys_tenant set apply_status = 1;
ALTER TABLE `sys_log`
MODIFY COLUMN `log_type` int(3) NULL DEFAULT NULL COMMENT '日志类型1登录日志2操作日志, 3.租户操作日志' AFTER `id`;
-- -author: taoyan---date:2023-02-17--forQQYUN-3851租户租户管理员功能

View File

@ -1,10 +0,0 @@
版本升级方法?
JeecgBoot属于平台级产品每次升级改动内容较多目前做不到平滑升级。
这里给用户的升级建议是这样的:
1.代码升级 => 本地版本通过svn或者git做好主干在分支上做业务开发jeecg每次版本发布可以手工覆盖主干的代码对比代码进行提交
2.数据库升级 => 针对数据库我们每次发布会提供增量升级SQL可以通过增量SQL实现数据库的升级。
3.兼容问题 => 每次版本发布会针对不兼容地方标注说明,需要手工修改不兼容的代码。
注意: 升级sql目前只提供mysql版本执行完脚步后新菜单需要手工进行角色授权刷新首页才会出现。

15
db/版本升级说明.md Normal file
View File

@ -0,0 +1,15 @@
# 版本升级方法
> JeecgBoot属于平台级产品每次升级改动较大目前做不到平滑升级。
### 增量升级方案
#### 1.代码合并
本地通过svn或git做好主干在分支上做业务开发jeecg每次版本发布可以手工覆盖主干的代码对比合并代码
#### 2.数据库升级
- 从3.6.2+版本增加flyway自动升级数据库机制支持 mysql5.7、mysql8;
- 其他库请手工执行SQL, 目录: `jeecg-module-system\jeecg-system-start\src\main\resources\flyway\sql\mysql`
> 注意: 升级sql只提供mysql版本如果有权限升级, 还需要手工角色授权,退出重新登录才好使。
#### 3.兼容问题
每次发版,会针对不兼容地方重点说明。

View File

@ -19,6 +19,8 @@ services:
--default-authentication-plugin=caching_sha2_password --default-authentication-plugin=caching_sha2_password
ports: ports:
- 3306:3306 - 3306:3306
networks:
- jeecg-boot
jeecg-boot-redis: jeecg-boot-redis:
image: redis:5.0 image: redis:5.0
@ -27,6 +29,8 @@ services:
restart: always restart: always
hostname: jeecg-boot-redis hostname: jeecg-boot-redis
container_name: jeecg-boot-redis container_name: jeecg-boot-redis
networks:
- jeecg-boot
jeecg-boot-system: jeecg-boot-system:
build: build:
@ -39,4 +43,10 @@ services:
image: jeecg-boot-system image: jeecg-boot-system
hostname: jeecg-boot-system hostname: jeecg-boot-system
ports: ports:
- 8080:8080 - 8080:8080
networks:
- jeecg-boot
networks:
jeecg-boot:
name: jeecg_boot

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.jeecgframework.boot</groupId> <groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-parent</artifactId> <artifactId>jeecg-boot-parent</artifactId>
<version>3.5.1</version> <version>3.6.3</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-boot-base-core</artifactId> <artifactId>jeecg-boot-base-core</artifactId>
@ -82,7 +82,7 @@
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>${commons.version}</version> <version>${commons-io.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-lang</groupId> <groupId>commons-lang</groupId>
@ -145,7 +145,7 @@
<version>${postgresql.version}</version> <version>${postgresql.version}</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- Quartz定时任务 --> <!-- Quartz定时任务 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -175,13 +175,22 @@
<groupId>org.apache.shiro</groupId> <groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId> <artifactId>shiro-core</artifactId>
</exclusion> </exclusion>
<exclusion>
<artifactId>checkstyle</artifactId>
<groupId>com.puppycrawl.tools</groupId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<!-- knife4j --> <!-- knife4j -->
<dependency> <!-- <dependency>
<groupId>com.github.xiaoymin</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId> <artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>${knife4j-spring-boot-starter.version}</version> <version>${knife4j-spring-boot-starter.version}</version>
</dependency> </dependency>
@ -252,6 +261,15 @@
<groupId>commons-fileupload</groupId> <groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId> <artifactId>commons-fileupload</artifactId>
</dependency> </dependency>
<!--加载hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -22,10 +22,10 @@ public interface CommonAPI {
/** /**
* 2查询用户权限信息 * 2查询用户权限信息
* @param username * @param userId
* @return * @return
*/ */
Set<String> queryUserAuths(String username); Set<String> queryUserAuths(String userId);
/** /**
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel * 3根据 id 查询数据库中存储的 DynamicDataSourceModel
@ -102,12 +102,12 @@ public interface CommonAPI {
/** /**
* 13获取表数据字典 * 13获取表数据字典
* @param table * @param tableFilterSql
* @param text * @param text
* @param code * @param code
* @return * @return
*/ */
List<DictModel> queryTableDictItemsByCode(String table, String text, String code); List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code);
/** /**
* 14 普通字典的翻译根据多个dictCode和多条数据多个以逗号分割 * 14 普通字典的翻译根据多个dictCode和多条数据多个以逗号分割
@ -117,14 +117,17 @@ public interface CommonAPI {
*/ */
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys); Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
//update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
/** /**
* 15 字典表的 翻译,可批量 * 15 字典表的 翻译,可批量
* @param table * @param table
* @param text * @param text
* @param code * @param code
* @param keys 多个用逗号分割 * @param keys 多个用逗号分割
* @param dataSource 数据源
* @return * @return
*/ */
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys); List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
} }

View File

@ -17,6 +17,8 @@ public class DataLogDTO {
private String type; private String type;
private String createName;
public DataLogDTO(){ public DataLogDTO(){
} }

View File

@ -30,6 +30,13 @@ public class OnlineAuthDTO implements Serializable {
*/ */
private String onlineFormUrl; private String onlineFormUrl;
//update-begin---author:chenrui ---date:20240123 for[QQYUN-7992]【online】工单申请下的online表单未配置online表单开发菜单操作报错无权限------------
/**
* online工单的地址
*/
private String onlineWorkOrderUrl;
//update-end---author:chenrui ---date:20240123 for[QQYUN-7992]【online】工单申请下的online表单未配置online表单开发菜单操作报错无权限------------
public OnlineAuthDTO(){ public OnlineAuthDTO(){
} }

View File

@ -4,7 +4,7 @@ import lombok.Data;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.*;
/** /**
* 普通消息 * 普通消息
@ -43,14 +43,7 @@ public class MessageDTO implements Serializable {
* 消息类型 1:消息 2:系统消息 * 消息类型 1:消息 2:系统消息
*/ */
protected String category; protected String category;
//-----------------------------------------------------------------------
//update-begin---author:taoyan ---date:20220705 for支持自定义推送类型邮件、钉钉、企业微信、系统消息-----------
/**
* 模板消息对应的模板编码
*/
protected String templateCode;
/** /**
* 消息类型org.jeecg.common.constant.enums.MessageTypeEnum * 消息类型org.jeecg.common.constant.enums.MessageTypeEnum
* XT("system", "系统消息") * XT("system", "系统消息")
@ -60,23 +53,38 @@ public class MessageDTO implements Serializable {
*/ */
protected String type; protected String type;
//---【推送模板相关参数】-------------------------------------------------------------
/** /**
* 是否发送Markdown格式的消息 * 是否发送Markdown格式的消息
*/ */
protected boolean isMarkdown; protected boolean isMarkdown;
/**
* 模板消息对应的模板编码
*/
protected String templateCode;
/** /**
* 解析模板内容 对应的数据 * 解析模板内容 对应的数据
*/ */
protected Map<String, Object> data; protected Map<String, Object> data;
//update-end---author:taoyan ---date::20220705 for支持自定义推送类型邮件、钉钉、企业微信、系统消息----------- //---【推送模板相关参数】-------------------------------------------------------------
//-----------------------------------------------------------------------
//---【邮件相关参数】-------------------------------------------------------------
/** /**
* 抄送人 * 邮件抄送人
*/ */
private String copyToUser; private String copyToUser;
/**
* 邮件推送地址
*/
protected Set<String> toEmailList;
/**
* 邮件抄送地址
*/
protected Set<String> ccEmailList;
//---【邮件相关参数】-------------------------------------------------------------
public MessageDTO(){ public MessageDTO(){
} }

View File

@ -158,6 +158,9 @@ public class AutoLogAspect {
if(value!=null && value.toString().length()>length){ if(value!=null && value.toString().length()>length){
return false; return false;
} }
if(value instanceof MultipartFile){
return false;
}
return true; return true;
} }
}; };

View File

@ -140,11 +140,15 @@ public class DictAspect {
String code = field.getAnnotation(Dict.class).dicCode(); String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText(); String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable(); String table = field.getAnnotation(Dict.class).dictTable();
//update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
String dataSource = field.getAnnotation(Dict.class).ds();
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
List<String> dataList; List<String> dataList;
String dictCode = code; String dictCode = code;
if (!StringUtils.isEmpty(table)) { if (!StringUtils.isEmpty(table)) {
dictCode = String.format("%s,%s,%s", table, text, code); //update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
} }
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>()); dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(","))); this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
@ -169,10 +173,15 @@ public class DictAspect {
String code = field.getAnnotation(Dict.class).dicCode(); String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText(); String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable(); String table = field.getAnnotation(Dict.class).dictTable();
//update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
// 自定义的字典表数据源
String dataSource = field.getAnnotation(Dict.class).ds();
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
String fieldDictCode = code; String fieldDictCode = code;
if (!StringUtils.isEmpty(table)) { if (!StringUtils.isEmpty(table)) {
fieldDictCode = String.format("%s,%s,%s", table, text, code); //update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
} }
String value = record.getString(field.getName()); String value = record.getString(field.getName());
@ -274,9 +283,25 @@ public class DictAspect {
String[] arr = dictCode.split(","); String[] arr = dictCode.split(",");
String table = arr[0], text = arr[1], code = arr[2]; String table = arr[0], text = arr[1], code = arr[2];
String values = String.join(",", needTranslDataTable); String values = String.join(",", needTranslDataTable);
//update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
// 自定义的数据源
String dataSource = null;
if (arr.length > 3) {
dataSource = arr[3];
}
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode); log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
log.debug("translateDictFromTableByKeys.values:" + values); log.debug("translateDictFromTableByKeys.values:" + values);
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values); //update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
if(null == dataSource){
dataSource = "";
}
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
log.debug("translateDictFromTableByKeys.result:" + texts); log.debug("translateDictFromTableByKeys.result:" + texts);
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>()); List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.addAll(texts); list.addAll(texts);

View File

@ -59,8 +59,7 @@ public class PermissionDataAspect {
requestPath = filterUrl(requestPath); requestPath = filterUrl(requestPath);
//update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效 //update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
//先判断是否online报表请求 //先判断是否online报表请求
// TODO 参数顺序调整有隐患 if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0){
// 获取地址栏参数 // 获取地址栏参数
String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR); String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR);
if(oConvertUtils.isNotEmpty(urlParamString)){ if(oConvertUtils.isNotEmpty(urlParamString)){
@ -68,7 +67,7 @@ public class PermissionDataAspect {
} }
} }
//update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效 //update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
log.info("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod); log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
String username = JwtUtil.getUserNameByToken(request); String username = JwtUtil.getUserNameByToken(request);
//查询数据权限信息 //查询数据权限信息
//TODO 微服务情况下也得支持缓存机制 //TODO 微服务情况下也得支持缓存机制

View File

@ -14,6 +14,8 @@ public enum UrlMatchEnum {
CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"), CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"),
/**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */ /**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */
CGREPORT_DATA("/online/cgreport/api/getColumnsAndData/", "/online/cgreport/"), CGREPORT_DATA("/online/cgreport/api/getColumnsAndData/", "/online/cgreport/"),
/** 求URL与菜单路由URL转换规则/online/cgreport/api/getData/ 【vue3报表数据请求地址】 */
CGREPORT_ONLY_DATA("/online/cgreport/api/getData/", "/online/cgreport/"),
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportXls/ */ /**求URL与菜单路由URL转换规则 /online/cgreport/api/exportXls/ */
CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"), CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"),
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */ /**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */

View File

@ -1,33 +0,0 @@
package org.jeecg.common.aspect.annotation;
import java.lang.annotation.*;
import org.jeecg.common.constant.enums.LowAppAopEnum;
/**
* 自动注入low_app_id
*
* @Author scott
* @email jeecgos@163.com
* @Date 2022年01月05日
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoLowApp {
/**
* 切面类型add、delete、db_import等其他操作
*
* @return
*/
LowAppAopEnum action();
/**
* 业务类型cgform等
*
* @return
*/
String bizType();
}

View File

@ -39,4 +39,16 @@ public @interface Dict {
* @return 返回类型: String * @return 返回类型: String
*/ */
String dictTable() default ""; String dictTable() default "";
//update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
/**
* 方法描述: 数据字典表所在数据源名称
* 作 者: chenrui
* 日 期: 2023年12月20日-下午4:58
*
* @return 返回类型: String
*/
String ds() default "";
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
} }

View File

@ -69,6 +69,8 @@ public interface CommonConstant {
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */ /** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
Integer SC_INTERNAL_SERVER_ERROR_500 = 500; Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
/** {@code 404 Not Found} (HTTP/1.0 - RFC 1945) */
Integer SC_INTERNAL_NOT_FOUND_404 = 404;
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */ /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
Integer SC_OK_200 = 200; Integer SC_OK_200 = 200;
@ -112,8 +114,8 @@ public interface CommonConstant {
String HAS_CANCLE = "2"; String HAS_CANCLE = "2";
/**阅读状态0未读1已读*/ /**阅读状态0未读1已读*/
String HAS_READ_FLAG = "1"; Integer HAS_READ_FLAG = 1;
String NO_READ_FLAG = "0"; Integer NO_READ_FLAG = 0;
/**优先级L低M中H高*/ /**优先级L低M中H高*/
String PRIORITY_L = "L"; String PRIORITY_L = "L";
@ -160,6 +162,8 @@ public interface CommonConstant {
/**字典翻译文本后缀*/ /**字典翻译文本后缀*/
String DICT_TEXT_SUFFIX = "_dictText"; String DICT_TEXT_SUFFIX = "_dictText";
/**字典翻译颜色后缀*/
String DICT_COLOR_SUFFIX = "_dictColor";
/** /**
* 表单设计器主表类型 * 表单设计器主表类型
@ -315,6 +319,8 @@ public interface CommonConstant {
String X_TIMESTAMP = "X-TIMESTAMP"; String X_TIMESTAMP = "X-TIMESTAMP";
/** 租户请求头 更名为X-Tenant-Id */ /** 租户请求头 更名为X-Tenant-Id */
String TENANT_ID = "X-Tenant-Id"; String TENANT_ID = "X-Tenant-Id";
/** 简流接口请求头,用于排除不支持的控件字段 */
String X_MiniFlowExclusionFieldMode = "X-Miniflowexclusionfieldmode";
/**===============================================================================================*/ /**===============================================================================================*/
String TOKEN_IS_INVALID_MSG = "Token失效请重新登录!"; String TOKEN_IS_INVALID_MSG = "Token失效请重新登录!";
@ -371,6 +377,8 @@ public interface CommonConstant {
/**前端vue3版本Header参数名*/ /**前端vue3版本Header参数名*/
String VERSION="X-Version"; String VERSION="X-Version";
String VERSION_V3 = "v3";
/**存储在线程变量里的动态表名*/ /**存储在线程变量里的动态表名*/
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME"; String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
/** /**
@ -388,6 +396,7 @@ public interface CommonConstant {
/** 部门表唯一keyorgCode */ /** 部门表唯一keyorgCode */
String DEPART_KEY_ORG_CODE = "orgCode"; String DEPART_KEY_ORG_CODE = "orgCode";
/**======【消息推送相关】==============================================================================*/
/** /**
* 发消息 会传递一些信息到map * 发消息 会传递一些信息到map
*/ */
@ -398,6 +407,11 @@ public interface CommonConstant {
*/ */
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID"; String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
/**
* 发消息 消息业务类型
*/
String NOTICE_MSG_BUS_TYPE = "NOTICE_MSG_BUS_TYPE";
/** /**
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值 * 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
*/ */
@ -420,6 +434,7 @@ public interface CommonConstant {
/** 消息模板markdown */ /** 消息模板markdown */
String MSG_TEMPLATE_TYPE_MD = "5"; String MSG_TEMPLATE_TYPE_MD = "5";
/**========【消息推送相关】==========================================================================*/
/** /**
* 短信验证码redis-key的前缀 * 短信验证码redis-key的前缀
@ -481,6 +496,11 @@ public interface CommonConstant {
*/ */
String USER_TENANT_REFUSE = "4"; String USER_TENANT_REFUSE = "4";
/**
* 用户租户状态(邀请)
*/
String USER_TENANT_INVITE = "5";
/** /**
* 不是叶子节点 * 不是叶子节点
*/ */
@ -490,4 +510,71 @@ public interface CommonConstant {
* 是叶子节点 * 是叶子节点
*/ */
Integer IS_LEAF = 1; Integer IS_LEAF = 1;
/**
* 钉钉
*/
String DINGTALK = "DINGTALK";
/**
* 企业微信
*/
String WECHAT_ENTERPRISE = "WECHAT_ENTERPRISE";
/**
* 系统默认租户id 0
*/
Integer TENANT_ID_DEFAULT_VALUE = 0;
/**
* 【low-app用】 应用级别的复制
*/
String COPY_LEVEL_APP = "app";
/**
* 【low-app用】 菜单级别的复制
*/
String COPY_LEVEL_MENU = "menu";
/**
* 【low-app用】 应用备份
*/
String COPY_LEVEL_BAK = "backup";
/**
* 【low-app用】 从备份还原
*/
String COPY_LEVEL_COVER = "cover";
/** 【QQYUN-6034】关联字段变更历史值缓存半个小时 */
String CACHE_REL_FIELD_OLD_VAL = "sys:cache:desform:relFieldOldVal:";
/**
* 排序类型:升序
*/
String ORDER_TYPE_ASC = "ASC";
/**
* 排序类型:降序
*/
String ORDER_TYPE_DESC = "DESC";
//update-begin---author:scott ---date:2023-09-10 for积木报表常量----
/**
* 报表允许设计开发的角色
*/
public static String[] allowDevRoles = new String[]{"lowdeveloper", "admin"};
/**
* 【对应积木报表的常量】
* 数据隔离模式: 按照创建人隔离
*/
public static final String SAAS_MODE_CREATED = "created";
/**
* 【对应积木报表的常量】
* 数据隔离模式: 按照租户隔离
*/
public static final String SAAS_MODE_TENANT = "tenant";
//update-end---author:scott ---date::2023-09-10 for积木报表常量----
} }

View File

@ -28,12 +28,21 @@ public interface CommonSendStatus {
public static final String APP_SESSION_SUFFIX = "_app"; public static final String APP_SESSION_SUFFIX = "_app";
/**-----【流程相关通知模板code】------------------------------------------------------------*/
/**流程催办——系统通知消息模板*/ /**流程催办——系统通知消息模板*/
public static final String TZMB_BPM_CUIBAN = "bpm_cuiban"; public static final String TZMB_BPM_CUIBAN = "bpm_cuiban";
/**流程抄送——系统通知消息模板*/
public static final String TZMB_BPM_CC = "bpm_cc";
/**流程催办——邮件通知消息模板*/ /**流程催办——邮件通知消息模板*/
public static final String TZMB_BPM_CUIBAN_EMAIL = "bpm_cuiban_email"; public static final String TZMB_BPM_CUIBAN_EMAIL = "bpm_cuiban_email";
/**标准模板—系统消息通知*/ /**标准模板—系统消息通知*/
public static final String TZMB_SYS_TS_NOTE = "sys_ts_note"; public static final String TZMB_SYS_TS_NOTE = "sys_ts_note";
/**流程超时提醒——系统通知消息模板*/ /**流程超时提醒——系统通知消息模板*/
public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip"; public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip";
/**-----【流程相关通知模板code】-----------------------------------------------------------*/
/**
* 系统通知拓展参数(比如:用于流程抄送和催办通知,这里额外传递流程跳转页面所需要的路由参数)
*/
public static final String MSG_ABSTRACT_JSON = "msg_abstract";
} }

View File

@ -17,6 +17,9 @@ public interface DataBaseConstant {
/**postgreSQL达梦数据库*/ /**postgreSQL达梦数据库*/
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL"; public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
/**人大金仓数据库*/
public static final String DB_TYPE_KINGBASEES = "KINGBASEES";
/**sqlserver数据库*/ /**sqlserver数据库*/
public static final String DB_TYPE_SQLSERVER = "SQLSERVER"; public static final String DB_TYPE_SQLSERVER = "SQLSERVER";

View File

@ -116,4 +116,8 @@ public class SymbolConstant {
*/ */
public static final String SQUARE_BRACKETS_RIGHT = "]"; public static final String SQUARE_BRACKETS_RIGHT = "]";
/**
* 拼接字符串符号 分号 ;
*/
public static final String SEMICOLON = ";";
} }

View File

@ -28,7 +28,7 @@ public enum CgformEnum {
/** /**
* 多表 (erp风格) * 多表 (erp风格)
*/ */
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格" ,new String[]{"vue3","vue"}), ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格" ,new String[]{"vue3","vue","vue3Native"}),
/** /**
* 多表(内嵌子表风格) * 多表(内嵌子表风格)
*/ */

View File

@ -1,4 +1,4 @@
package org.jeecg.common.util; package org.jeecg.common.constant.enums;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -17,7 +17,11 @@ public enum DySmsEnum {
/**会议通知*/ /**会议通知*/
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"), MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"),
/**我的计划通知*/ /**我的计划通知*/
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time"); PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time"),
/**支付成功短信通知*/
PAY_SUCCESS_NOTICE_CODE("SMS_461735163","敲敲云","realname,money,endTime"),
/**会员到期通知提醒*/
VIP_EXPIRE_NOTICE_CODE("SMS_461885023","敲敲云","realname,endTime");
/** /**
* 短信模板编码 * 短信模板编码

View File

@ -0,0 +1,66 @@
package org.jeecg.common.constant.enums;
import org.jeecg.common.util.oConvertUtils;
/**
* 邮件html模板配置地址美剧
*
* @author: liusq
* @Date: 2023-10-13
*/
public enum EmailTemplateEnum {
/**
* 流程催办
*/
BPM_CUIBAN_EMAIL("bpm_cuiban_email", "/templates/email/bpm_cuiban_email.ftl"),
/**
* 流程新任务
*/
BPM_NEW_TASK_EMAIL("bpm_new_task_email", "/templates/email/bpm_new_task_email.ftl"),
/**
* 表单新增记录
*/
DESFORM_NEW_DATA_EMAIL("desform_new_data_email", "/templates/email/desform_new_data_email.ftl");
/**
* 模板名称
*/
private String name;
/**
* 模板地址
*/
private String url;
EmailTemplateEnum(String name, String url) {
this.name = name;
this.url = 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;
}
public static EmailTemplateEnum getByName(String name) {
if (oConvertUtils.isEmpty(name)) {
return null;
}
for (EmailTemplateEnum val : values()) {
if (val.getName().equals(name)) {
return val;
}
}
return null;
}
}

View File

@ -6,7 +6,7 @@ import org.jeecg.common.util.oConvertUtils;
* 文件类型 * 文件类型
*/ */
public enum FileTypeEnum { public enum FileTypeEnum {
// 文档类型folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频) // 文档类型folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频 voice:语音
// FOLDER // FOLDER
xls(".xls","excel","excel"), xls(".xls","excel","excel"),
xlsx(".xlsx","excel","excel"), xlsx(".xlsx","excel","excel"),
@ -26,7 +26,8 @@ public enum FileTypeEnum {
flv(".flv","video","视频"), flv(".flv","video","视频"),
mp4(".mp4","video","视频"), mp4(".mp4","video","视频"),
zip(".zip","zip","压缩包"), zip(".zip","zip","压缩包"),
pdf(".pdf","pdf","pdf"); pdf(".pdf","pdf","pdf"),
mp3(".mp3","mp3","语音");
private String type; private String type;
private String value; private String value;

View File

@ -1,30 +0,0 @@
package org.jeecg.common.constant.enums;
/**
* LowApp 切面注解枚举
* @date 2022-1-5
* @author: jeecg-boot
*/
public enum LowAppAopEnum {
/**
* 新增方法
*/
ADD,
/**
* 删除方法(包含单个和批量删除)
*/
DELETE,
/** 复制表单操作 */
COPY,
/**
* Online表单专用数据库表转Online表单
*/
CGFORM_DB_IMPORT,
/**
* 表单设计器专用:子表转工作表
*/
DESFORM_SUB2WORK
}

View File

@ -13,12 +13,16 @@ import java.util.List;
public enum RoleIndexConfigEnum { public enum RoleIndexConfigEnum {
/**首页自定义 admin*/ /**首页自定义 admin*/
ADMIN("admin", "dashboard/Analysis"), // ADMIN("admin", "dashboard/Analysis"),
//TEST("test", "dashboard/IndexChart"), //TEST("test", "dashboard/IndexChart"),
/**首页自定义 hr*/ /**首页自定义 hr*/
HR("hr", "dashboard/IndexBdc"); // HR("hr", "dashboard/IndexBdc");
//DM("dm", "dashboard/IndexTask"), //DM("dm", "dashboard/IndexTask"),
// 注:此值仅为防止报错,无任何实际意义
ROLE_INDEX_CONFIG_ENUM("RoleIndexConfigEnumDefault", "dashboard/Analysis");
/** /**
* 角色编码 * 角色编码
*/ */

View File

@ -1,4 +1,6 @@
package org.jeecg.common.util; package org.jeecg.common.constant.enums;
import org.jeecg.common.util.oConvertUtils;
/** /**
* 系统公告自定义跳转方式 * 系统公告自定义跳转方式
@ -12,7 +14,16 @@ public enum SysAnnmentTypeEnum {
/** /**
* 流程跳转到我的任务 * 流程跳转到我的任务
*/ */
BPM("bpm", "url", "/bpm/task/MyTaskList"); BPM("bpm", "url", "/bpm/task/MyTaskList"),
/**
* 流程抄送任务
*/
BPM_VIEW("bpm_cc", "url", "/bpm/task/MyTaskList"),
/**
* 邀请用户跳转到个人设置
*/
TENANT_INVITE("tenant_invite", "url", "/system/usersetting");
/** /**
* 业务类型(email:邮件 bpm:流程) * 业务类型(email:邮件 bpm:流程)

View File

@ -1,4 +1,4 @@
package org.jeecg.modules.message.enums; package org.jeecg.common.constant.enums;
import org.jeecg.common.system.annotation.EnumDict; import org.jeecg.common.system.annotation.EnumDict;
import org.jeecg.common.system.vo.DictModel; import org.jeecg.common.system.vo.DictModel;
@ -18,6 +18,16 @@ public enum Vue3MessageHrefEnum {
* 流程催办 * 流程催办
*/ */
BPM("bpm", "/task/myHandleTaskInfo"), BPM("bpm", "/task/myHandleTaskInfo"),
/**
* 系统消息通知
*/
BPM_SYSTEM_MSG("bpm_msg_node", ""),
/**
* 流程抄送任务
*/
BPM_VIEW("bpm_cc", "/task/myHandleTaskInfo"),
/** /**
* 节点通知 * 节点通知

View File

@ -1,5 +1,7 @@
package org.jeecg.common.exception; package org.jeecg.common.exception;
import org.jeecg.common.constant.CommonConstant;
/** /**
* @Description: jeecg-boot自定义异常 * @Description: jeecg-boot自定义异常
* @author: jeecg-boot * @author: jeecg-boot
@ -7,10 +9,24 @@ package org.jeecg.common.exception;
public class JeecgBootException extends RuntimeException { public class JeecgBootException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 返回给前端的错误code
*/
private int errCode = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
public JeecgBootException(String message){ public JeecgBootException(String message){
super(message); super(message);
} }
public JeecgBootException(String message, int errCode){
super(message);
this.errCode = errCode;
}
public int getErrCode() {
return errCode;
}
public JeecgBootException(Throwable cause) public JeecgBootException(Throwable cause)
{ {
super(cause); super(cause);

View File

@ -1,6 +1,7 @@
package org.jeecg.common.exception; package org.jeecg.common.exception;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
@ -16,8 +17,6 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.NoHandlerFoundException;
import lombok.extern.slf4j.Slf4j;
/** /**
* 异常处理器 * 异常处理器
* *
@ -34,7 +33,7 @@ public class JeecgBootExceptionHandler {
@ExceptionHandler(JeecgBootException.class) @ExceptionHandler(JeecgBootException.class)
public Result<?> handleJeecgBootException(JeecgBootException e){ public Result<?> handleJeecgBootException(JeecgBootException e){
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
return Result.error(e.getMessage()); return Result.error(e.getErrCode(), e.getMessage());
} }
/** /**
@ -133,4 +132,24 @@ public class JeecgBootExceptionHandler {
return Result.error("Redis 连接异常!"); return Result.error("Redis 连接异常!");
} }
/**
* SQL注入风险全局异常处理
*
* @param exception
* @return
*/
@ExceptionHandler(JeecgSqlInjectionException.class)
public Result<?> handleSQLException(Exception exception) {
String msg = exception.getMessage().toLowerCase();
final String extractvalue = "extractvalue";
final String updatexml = "updatexml";
boolean hasSensitiveInformation = msg.indexOf(extractvalue) >= 0 || msg.indexOf(updatexml) >= 0;
if (msg != null && hasSensitiveInformation) {
log.error("校验失败存在SQL注入风险{}", msg);
return Result.error("校验失败存在SQL注入风险");
}
return Result.error("校验失败存在SQL注入风险" + msg);
}
} }

View File

@ -0,0 +1,23 @@
package org.jeecg.common.exception;
/**
* @Description: jeecg-boot自定义SQL注入异常
* @author: jeecg-boot
*/
public class JeecgSqlInjectionException extends RuntimeException {
private static final long serialVersionUID = 1L;
public JeecgSqlInjectionException(String message){
super(message);
}
public JeecgSqlInjectionException(Throwable cause)
{
super(cause);
}
public JeecgSqlInjectionException(String message, Throwable cause)
{
super(message,cause);
}
}

View File

@ -19,7 +19,6 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.entity.enmus.ExcelType; import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@ -29,7 +28,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
/** /**
* @Description: Controller基类 * @Description: Controller基类
@ -70,7 +68,7 @@ public class JeecgController<T, S extends IService<T>> {
mv.addObject(NormalExcelConstants.FILE_NAME, title); mv.addObject(NormalExcelConstants.FILE_NAME, title);
mv.addObject(NormalExcelConstants.CLASS, clazz); mv.addObject(NormalExcelConstants.CLASS, clazz);
//update-begin--Author:liusq Date:20210126 for图片导出报错ImageBasePath未设置-------------------- //update-begin--Author:liusq Date:20210126 for图片导出报错ImageBasePath未设置--------------------
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title); ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload()); exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
//update-end--Author:liusq Date:20210126 for图片导出报错ImageBasePath未设置---------------------- //update-end--Author:liusq Date:20210126 for图片导出报错ImageBasePath未设置----------------------
mv.addObject(NormalExcelConstants.PARAMS,exportParams); mv.addObject(NormalExcelConstants.PARAMS,exportParams);
@ -110,7 +108,7 @@ public class JeecgController<T, S extends IService<T>> {
IPage<T> pageList = service.page(page, queryWrapper); IPage<T> pageList = service.page(page, queryWrapper);
List<T> exportList = pageList.getRecords(); List<T> exportList = pageList.getRecords();
Map<String, Object> map = new HashMap<>(5); Map<String, Object> map = new HashMap<>(5);
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload()); ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
exportParams.setType(ExcelType.XSSF); exportParams.setType(ExcelType.XSSF);
//map.put("title",exportParams); //map.put("title",exportParams);
//表格Title //表格Title

View File

@ -20,6 +20,14 @@ public class QueryCondition implements Serializable {
private String dbType; private String dbType;
private String rule; private String rule;
private String val; private String val;
public QueryCondition(String field, String type, String dbType, String rule, String val) {
this.field = field;
this.type = type;
this.dbType = dbType;
this.rule = rule;
this.val = val;
}
public String getField() { public String getField() {
return field; return field;

View File

@ -19,11 +19,9 @@ import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.util.JeecgDataAutorUtils; import org.jeecg.common.system.util.JeecgDataAutorUtils;
import org.jeecg.common.system.util.JwtUtil; import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.util.SqlConcatUtil;
import org.jeecg.common.system.vo.SysPermissionDataRuleModel; import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
import org.jeecg.common.util.CommonUtils; import org.jeecg.common.util.*;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.SqlInjectionUtil;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.util.NumberUtils; import org.springframework.util.NumberUtils;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
@ -143,7 +141,7 @@ public class QueryGenerator {
} }
Object value = PropertyUtils.getSimpleProperty(searchObj, name); Object value = PropertyUtils.getSimpleProperty(searchObj, name);
column = getTableFieldName(searchObj.getClass(), name); column = ReflectHelper.getTableFieldName(searchObj.getClass(), name);
if(column==null){ if(column==null){
//column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了 //column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了
continue; continue;
@ -283,15 +281,9 @@ public class QueryGenerator {
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc" // 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc" // 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) { if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) {
//queryWrapper.orderByAsc(oConvertUtils.camelToUnderline(column)); queryWrapper.orderByAsc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
String columnStr = oConvertUtils.camelToUnderline(column);
String[] columnArray = columnStr.split(",");
queryWrapper.orderByAsc(Arrays.asList(columnArray));
} else { } else {
//queryWrapper.orderByDesc(oConvertUtils.camelToUnderline(column)); queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
String columnStr = oConvertUtils.camelToUnderline(column);
String[] columnArray = columnStr.split(",");
queryWrapper.orderByDesc(Arrays.asList(columnArray));
} }
//update-end--Author:scott Date:20210531 for36 多条件排序无效问题修正------- //update-end--Author:scott Date:20210531 for36 多条件排序无效问题修正-------
} }
@ -347,7 +339,7 @@ public class QueryGenerator {
return; return;
} }
// update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and // update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
log.info("---高级查询参数-->" + filterConditions); log.debug("---高级查询参数-->" + filterConditions);
queryWrapper.and(andWrapper -> { queryWrapper.and(andWrapper -> {
for (int i = 0; i < filterConditions.size(); i++) { for (int i = 0; i < filterConditions.size(); i++) {
@ -641,11 +633,11 @@ public class QueryGenerator {
* @param value 查询条件值 * @param value 查询条件值
*/ */
public static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value) { public static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value) {
if (value == null || rule == null || oConvertUtils.isEmpty(value)) { if (name==null || value == null || rule == null || oConvertUtils.isEmpty(value)) {
return; return;
} }
name = oConvertUtils.camelToUnderline(name); name = oConvertUtils.camelToUnderline(name);
log.info("---查询过滤器,Query规则---field:{}, rule:{}, value:{}",name,rule.getValue(),value); log.debug("---高级查询 Query规则---field:{} , rule:{} , value:{}",name,rule.getValue(),value);
switch (rule) { switch (rule) {
case GT: case GT:
queryWrapper.gt(name, value); queryWrapper.gt(name, value);
@ -713,7 +705,14 @@ public class QueryGenerator {
*/ */
public static Map<String, SysPermissionDataRuleModel> getRuleMap() { public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5); Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5);
List<SysPermissionDataRuleModel> list =JeecgDataAutorUtils.loadDataSearchConditon(); List<SysPermissionDataRuleModel> list = null;
//update-begin-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
try {
list = JeecgDataAutorUtils.loadDataSearchConditon();
}catch (Exception e){
log.error("根据request对象获取权限数据失败可能是定时任务中执行的。", e);
}
//update-end-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
if(list != null&&list.size()>0){ if(list != null&&list.size()>0){
if(list.get(0)==null){ if(list.get(0)==null){
return ruleMap; return ruleMap;
@ -821,223 +820,7 @@ public class QueryGenerator {
* @return * @return
*/ */
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) { public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) {
return getSingleQueryConditionSql(field, alias, value, isString,null); return SqlConcatUtil.getSingleQueryConditionSql(field, alias, value, isString,null);
}
/**
* 报表获取查询条件 支持多数据源
* @param field
* @param alias
* @param value
* @param isString
* @param dataBaseType
* @return
*/
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
if (value == null) {
return "";
}
field = alias+oConvertUtils.camelToUnderline(field);
QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
}
/**
* 获取单个查询条件的值
* @param rule
* @param field
* @param value
* @param isString
* @param dataBaseType
* @return
*/
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
String res = "";
switch (rule) {
case GT:
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case GE:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case LT:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case LE:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case EQ:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case EQ_WITH_ADD:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
break;
case NE:
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
break;
case IN:
res = field + " in "+getInConditionValue(value, isString);
break;
case LIKE:
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
break;
case LEFT_LIKE:
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
break;
case RIGHT_LIKE:
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
break;
default:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
break;
}
return res;
}
/**
* 获取单个查询条件的值
* @param rule
* @param field
* @param value
* @param isString
* @return
*/
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
return getSingleSqlByRule(rule, field, value, isString, null);
}
/**
* 获取查询条件的值
* @param value
* @param isString
* @param dataBaseType
* @return
*/
private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
String str = value.toString().trim();
if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
str = str.substring(1);
}else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
str = str.substring(2);
}else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
str = str.substring(2);
}else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
str = str.substring(1);
}else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
str = str.substring(1);
}else if(str.indexOf(QUERY_COMMA_ESCAPE)>0) {
str = str.replaceAll("\\+\\+", COMMA);
}
if(dataBaseType==null){
dataBaseType = getDbType();
}
if(isString) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
return " N'"+str+"' ";
}else{
return " '"+str+"' ";
}
}else {
// 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“admin”这种格式的
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
return " N"+str;
}
return value.toString();
}
}
private static String getInConditionValue(Object value,boolean isString) {
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
String[] temp = value.toString().split(",");
if(temp.length==0){
return "('')";
}
if(isString) {
List<String> res = new ArrayList<>();
for (String string : temp) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
res.add("N'"+string+"'");
}else{
res.add("'"+string+"'");
}
}
return "("+String.join("," ,res)+")";
}else {
return "("+value.toString()+")";
}
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
}
/**
* 先根据值判断 走左模糊还是右模糊
* 最后如果值不带任何标识(*或者%)则再根据ruleEnum判断
* @param value
* @param ruleEnum
* @return
*/
private static String getLikeConditionValue(Object value, QueryRuleEnum ruleEnum) {
String str = value.toString().trim();
if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'%"+str.substring(1,str.length()-1)+"%'";
}else{
return "'%"+str.substring(1,str.length()-1)+"%'";
}
}else if(str.startsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'%"+str.substring(1)+"'";
}else{
return "'%"+str.substring(1)+"'";
}
}else if(str.endsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'"+str.substring(0,str.length()-1)+"%'";
}else{
return "'"+str.substring(0,str.length()-1)+"%'";
}
}else {
if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
return "N"+str;
}else{
return "N"+"'"+str+"'";
}
}else{
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
return str;
}else{
return "'"+str+"'";
}
}
}else {
//update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'%" + str + "'";
} else {
return "'%" + str + "'";
}
} else if (ruleEnum == QueryRuleEnum.RIGHT_LIKE) {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'" + str + "%'";
} else {
return "'" + str + "%'";
}
} else {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'%" + str + "%'";
} else {
return "'%" + str + "%'";
}
}
//update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
}
}
} }
/** /**
@ -1064,7 +847,7 @@ public class QueryGenerator {
continue; continue;
} }
if(ruleMap.containsKey(name)) { if(ruleMap.containsKey(name)) {
column = getTableFieldName(clazz, name); column = ReflectHelper.getTableFieldName(clazz, name);
if(column==null){ if(column==null){
continue; continue;
} }
@ -1078,7 +861,7 @@ public class QueryGenerator {
}else { }else {
value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType); value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
} }
String filedSql = getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString); String filedSql = SqlConcatUtil.getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
sb.append(sqlAnd+filedSql); sb.append(sqlAnd+filedSql);
} }
} }
@ -1107,7 +890,7 @@ public class QueryGenerator {
if (judgedIsUselessField(name)) { if (judgedIsUselessField(name)) {
continue; continue;
} }
column = getTableFieldName(clazz, name); column = ReflectHelper.getTableFieldName(clazz, name);
if(column==null){ if(column==null){
continue; continue;
} }
@ -1126,42 +909,6 @@ public class QueryGenerator {
return getSqlRuleValue(sql); return getSqlRuleValue(sql);
} }
/**
* 获取所有配置的权限 返回sql字符串 不受字段限制 配置什么就拿到什么
* @return
*/
public static String getAllConfigAuth() {
StringBuffer sb = new StringBuffer();
//权限查询
Map<String,SysPermissionDataRuleModel> ruleMap = getRuleMap();
String sqlAnd = " and ";
for (String c : ruleMap.keySet()) {
SysPermissionDataRuleModel dataRule = ruleMap.get(c);
String ruleValue = dataRule.getRuleValue();
if(oConvertUtils.isEmpty(ruleValue)){
continue;
}
if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
sb.append(sqlAnd+getSqlRuleValue(ruleValue));
}else{
boolean isString = false;
ruleValue = ruleValue.trim();
if(ruleValue.startsWith("'") && ruleValue.endsWith("'")){
isString = true;
ruleValue = ruleValue.substring(1,ruleValue.length()-1);
}
QueryRuleEnum rule = QueryRuleEnum.getByValue(dataRule.getRuleConditions());
String value = converRuleValue(ruleValue);
String filedSql = getSingleSqlByRule(rule, c, value,isString);
sb.append(sqlAnd+filedSql);
}
}
log.info("query auth sql is = "+sb.toString());
return sb.toString();
}
/** /**
* 获取系统数据库类型 * 获取系统数据库类型
*/ */
@ -1169,71 +916,6 @@ public class QueryGenerator {
return CommonUtils.getDatabaseType(); return CommonUtils.getDatabaseType();
} }
/**
* 获取class的 包括父类的
* @param clazz
* @return
*/
private static List<Field> getClassFields(Class<?> clazz) {
List<Field> list = new ArrayList<Field>();
Field[] fields;
do{
fields = clazz.getDeclaredFields();
for(int i = 0;i<fields.length;i++){
list.add(fields[i]);
}
clazz = clazz.getSuperclass();
}while(clazz!= Object.class&&clazz!=null);
return list;
}
/**
* 获取表字段名
* @param clazz
* @param name
* @return
*/
private static String getTableFieldName(Class<?> clazz, String name) {
try {
//如果字段加注解了@TableField(exist = false),不走DB查询
Field field = null;
try {
field = clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
//e.printStackTrace();
}
//如果为空,则去父类查找字段
if (field == null) {
List<Field> allFields = getClassFields(clazz);
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
if(searchFields!=null && searchFields.size()>0){
field = searchFields.get(0);
}
}
if (field != null) {
TableField tableField = field.getAnnotation(TableField.class);
if (tableField != null){
if(tableField.exist() == false){
//如果设置了TableField false 这个字段不需要处理
return null;
}else{
String column = tableField.value();
//如果设置了TableField value 这个字段是实体字段
if(!"".equals(column)){
return column;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return name;
}
/** /**
* mysql 模糊查询之特殊字符下划线 _、\ * mysql 模糊查询之特殊字符下划线 _、\
* *

View File

@ -25,12 +25,19 @@ public enum QueryRuleEnum {
IN("IN","in","包含"), IN("IN","in","包含"),
/**查询规则 全模糊*/ /**查询规则 全模糊*/
LIKE("LIKE","like","全模糊"), LIKE("LIKE","like","全模糊"),
/**查询规则 不模糊包含*/
NOT_LIKE("NOT_LIKE","not_like","不模糊包含"),
/**查询规则 左模糊*/ /**查询规则 左模糊*/
LEFT_LIKE("LEFT_LIKE","left_like","左模糊"), LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
/**查询规则 右模糊*/ /**查询规则 右模糊*/
RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"), RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
/**查询规则 带加号等于*/ /**查询规则 带加号等于*/
EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"), EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"),
/**查询规则 多词模糊匹配*/
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
/**查询规则 自定义SQL片段*/
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
// ------- 当前表单设计器内专用 ------- // ------- 当前表单设计器内专用 -------
/** 值为空 */ /** 值为空 */
EMPTY("EMPTY","empty","值为空"), EMPTY("EMPTY","empty","值为空"),
@ -38,15 +45,12 @@ public enum QueryRuleEnum {
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"), NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
/**查询规则 不包含*/ /**查询规则 不包含*/
NOT_IN("NOT_IN","not_in","不包含"), NOT_IN("NOT_IN","not_in","不包含"),
// ------- 当前表单设计器内专用 -------
/**查询规则 多词模糊匹配*/
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
/**查询规则 自定义SQL片段*/
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
/**查询规则 多词匹配*/ /**查询规则 多词匹配*/
ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"), ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"),
/**查询规则 范围查询*/ /**查询规则 范围查询*/
RANGE("RANGE","range","范围查询"); RANGE("RANGE","range","范围查询"),
NOT_RANGE("NOT_RANGE","not_range","不在范围查询");
// ------- 当前表单设计器内专用 -------
private String value; private String value;
@ -89,7 +93,7 @@ public enum QueryRuleEnum {
return null; return null;
} }
for(QueryRuleEnum val :values()){ for(QueryRuleEnum val :values()){
if (val.getValue().equals(value) || val.getCondition().equals(value)){ if (val.getValue().equals(value) || val.getCondition().equalsIgnoreCase(value)){
return val; return val;
} }
} }

View File

@ -16,6 +16,7 @@ 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 lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
@ -34,10 +35,11 @@ import org.jeecg.common.util.oConvertUtils;
* @Date 2018-07-12 14:23 * @Date 2018-07-12 14:23
* @Desc JWT工具类 * @Desc JWT工具类
**/ **/
@Slf4j
public class JwtUtil { public class JwtUtil {
/**Token有效期为24小时Token在reids中缓存时间乘以2*/ /**Token有效期为7天Token在reids中缓存时间为两倍*/
public static final long EXPIRE_TIME = 12 * 60 * 60 * 1000; public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET; static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
/** /**
@ -163,15 +165,24 @@ public class JwtUtil {
* @param user * @param user
* @return * @return
*/ */
public static String getUserSystemData(String key,SysUserCacheInfo user) { public static String getUserSystemData(String key, SysUserCacheInfo user) {
//1.优先获取 SysUserCacheInfo
if(user==null) { if(user==null) {
user = JeecgDataAutorUtils.loadUserInfo(); try {
user = JeecgDataAutorUtils.loadUserInfo();
} catch (Exception e) {
log.warn("获取用户信息异常:" + e.getMessage());
}
} }
//2.通过shiro获取登录用户信息
LoginUser sysUser = null;
try {
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
} catch (Exception e) {
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
}
//#{sys_user_code}% //#{sys_user_code}%
// 获取登录用户信息
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String moshi = ""; String moshi = "";
String wellNumber = WELL_NUMBER; String wellNumber = WELL_NUMBER;
if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){ if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
@ -184,6 +195,24 @@ public class JwtUtil {
} else { } else {
key = key; key = key;
} }
//替换为当前系统时间(年月日)
if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
returnValue = DateUtils.formatDate();
}
//替换为当前系统时间(年月日时分秒)
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
returnValue = DateUtils.now();
}
//流程状态默认值(默认未发起)
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
returnValue = "1";
}
//后台任务获取用户信息异常,导致程序中断
if(sysUser==null && user==null){
return null;
}
//替换为系统登录用户帐号 //替换为系统登录用户帐号
if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) { if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
if(user==null) { if(user==null) {
@ -222,21 +251,13 @@ public class JwtUtil {
} }
} }
} }
//替换为当前系统时间(年月日)
else if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
returnValue = DateUtils.formatDate();
}
//替换为当前系统时间(年月日时分秒)
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
returnValue = DateUtils.now();
}
//流程状态默认值(默认未发起)
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
returnValue = "1";
}
//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量 //update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){ else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID); try {
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
} catch (Exception e) {
log.warn("获取系统租户异常:" + e.getMessage());
}
} }
//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量 //update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
if(returnValue!=null){returnValue = returnValue + moshi;} if(returnValue!=null){returnValue = returnValue + moshi;}

View File

@ -0,0 +1,243 @@
package org.jeecg.common.system.util;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.oConvertUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 查询过滤器SQL拼接写法拆成独立工具类
* @author:qinfeng
* @date 20230904
*/
@Slf4j
public class SqlConcatUtil {
/**
* 获取单个查询条件的值
* @param rule
* @param field
* @param value
* @param isString
* @return
*/
public static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
return getSingleSqlByRule(rule, field, value, isString, null);
}
/**
* 报表获取查询条件 支持多数据源
* @param field
* @param alias
* @param value
* @param isString
* @param dataBaseType
* @return
*/
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
if (value == null) {
return "";
}
field = alias+oConvertUtils.camelToUnderline(field);
QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
}
/**
* 获取单个查询条件的值
* @param rule
* @param field
* @param value
* @param isString
* @param dataBaseType
* @return
*/
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
String res = "";
switch (rule) {
case GT:
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case GE:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case LT:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case LE:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case EQ:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case EQ_WITH_ADD:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
break;
case NE:
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
break;
case IN:
res = field + " in "+getInConditionValue(value, isString);
break;
case LIKE:
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
break;
case LEFT_LIKE:
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
break;
case RIGHT_LIKE:
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
break;
default:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
break;
}
return res;
}
/**
* 获取查询条件的值
* @param value
* @param isString
* @param dataBaseType
* @return
*/
private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
String str = value.toString().trim();
if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
str = str.substring(1);
}else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
str = str.substring(2);
}else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
str = str.substring(2);
}else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
str = str.substring(1);
}else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
str = str.substring(1);
}else if(str.indexOf(QueryGenerator.QUERY_COMMA_ESCAPE)>0) {
str = str.replaceAll("\\+\\+", SymbolConstant.COMMA);
}
if(dataBaseType==null){
dataBaseType = getDbType();
}
if(isString) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
return " N'"+str+"' ";
}else{
return " '"+str+"' ";
}
}else {
// 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“admin”这种格式的
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
return " N"+str;
}
return value.toString();
}
}
private static String getInConditionValue(Object value,boolean isString) {
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
String[] temp = value.toString().split(",");
if(temp.length==0){
return "('')";
}
if(isString) {
List<String> res = new ArrayList<>();
for (String string : temp) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
res.add("N'"+string+"'");
}else{
res.add("'"+string+"'");
}
}
return "("+String.join("," ,res)+")";
}else {
return "("+value.toString()+")";
}
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
}
/**
* 先根据值判断 走左模糊还是右模糊
* 最后如果值不带任何标识(*或者%)则再根据ruleEnum判断
* @param value
* @param ruleEnum
* @return
*/
private static String getLikeConditionValue(Object value, QueryRuleEnum ruleEnum) {
String str = value.toString().trim();
if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'%"+str.substring(1,str.length()-1)+"%'";
}else{
return "'%"+str.substring(1,str.length()-1)+"%'";
}
}else if(str.startsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'%"+str.substring(1)+"'";
}else{
return "'%"+str.substring(1)+"'";
}
}else if(str.endsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'"+str.substring(0,str.length()-1)+"%'";
}else{
return "'"+str.substring(0,str.length()-1)+"%'";
}
}else {
if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
return "N"+str;
}else{
return "N"+"'"+str+"'";
}
}else{
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
return str;
}else{
return "'"+str+"'";
}
}
}else {
//update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'%" + str + "'";
} else {
return "'%" + str + "'";
}
} else if (ruleEnum == QueryRuleEnum.RIGHT_LIKE) {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'" + str + "%'";
} else {
return "'" + str + "%'";
}
} else {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'%" + str + "%'";
} else {
return "'%" + str + "%'";
}
}
//update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
}
}
}
/**
* 获取系统数据库类型
*/
private static String getDbType() {
return CommonUtils.getDatabaseType();
}
}

View File

@ -2,6 +2,7 @@ package org.jeecg.common.system.vo;
import java.io.Serializable; import java.io.Serializable;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data; import lombok.Data;
@ -26,7 +27,13 @@ public class DictModel implements Serializable{
this.value = value; this.value = value;
this.text = text; this.text = text;
} }
public DictModel(String value, String text, String color) {
this.value = value;
this.text = text;
this.color = color;
}
/** /**
* 字典value * 字典value
*/ */
@ -35,6 +42,10 @@ public class DictModel implements Serializable{
* 字典文本 * 字典文本
*/ */
private String text; private String text;
/**
* 字典颜色
*/
private String color;
/** /**
* 特殊用途: JgEditableTable * 特殊用途: JgEditableTable
@ -50,4 +61,11 @@ public class DictModel implements Serializable{
return this.text; return this.text;
} }
/**
* 用于表单设计器 关联记录表数据存储
* QQYUN-5595【表单设计器】他表字段 导入没有翻译
*/
private JSONObject jsonObject;
} }

View File

@ -1,15 +1,13 @@
package org.jeecg.common.system.vo; package org.jeecg.common.system.vo;
import java.util.Date;
import org.jeecg.common.desensitization.annotation.SensitiveField;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.jeecg.common.desensitization.annotation.SensitiveField;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/** /**
* <p> * <p>
@ -51,6 +49,7 @@ public class LoginUser {
/** /**
* 当前登录部门code * 当前登录部门code
*/ */
@SensitiveField
private String orgCode; private String orgCode;
/** /**
* 头像 * 头像
@ -61,7 +60,6 @@ public class LoginUser {
/** /**
* 生日 * 生日
*/ */
@SensitiveField
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday; private Date birthday;
@ -107,6 +105,7 @@ public class LoginUser {
/** /**
* 管理部门ids * 管理部门ids
*/ */
@SensitiveField
private String departIds; private String departIds;
/** /**
@ -122,6 +121,7 @@ public class LoginUser {
private String telephone; private String telephone;
/** 多租户ids临时用不持久化数据库(数据库字段不存在) */ /** 多租户ids临时用不持久化数据库(数据库字段不存在) */
@SensitiveField
private String relTenantIds; private String relTenantIds;
/**设备id uniapp推送用*/ /**设备id uniapp推送用*/

View File

@ -19,6 +19,8 @@ public class SysFilesModel {
private String storeType; private String storeType;
/**文件大小kb*/ /**文件大小kb*/
private Double fileSize; private Double fileSize;
/**租户id*/
private String tenantId;
public String getId() { public String getId() {
return id; return id;
@ -67,4 +69,12 @@ public class SysFilesModel {
public void setFileSize(Double fileSize) { public void setFileSize(Double fileSize) {
this.fileSize = fileSize; this.fileSize = fileSize;
} }
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
} }

View File

@ -0,0 +1,61 @@
package org.jeecg.common.system.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecg.common.desensitization.annotation.SensitiveField;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* <p>
* 在线用户信息
* </p>
*
* @Author scott
* @since 2023-08-16
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserAccountInfo {
/**
* 登录人id
*/
private String id;
/**
* 登录人账号
*/
private String username;
/**
* 登录人名字
*/
private String realname;
/**
* 电子邮件
*/
private String email;
/**
* 头像
*/
@SensitiveField
private String avatar;
/**
* 同步工作流引擎1同步0不同步
*/
private Integer activitiSync;
/**
* 电话
*/
@SensitiveField
private String phone;
}

View File

@ -1,16 +1,18 @@
package org.jeecg.common.util; package org.jeecg.common.util;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty; import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils; import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant; import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.ServiceNameConstants; import org.jeecg.common.constant.ServiceNameConstants;
import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.util.filter.FileTypeFilter; import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.common.util.oss.OssBootUtil; import org.jeecg.common.util.oss.OssBootUtil;
import org.jeecgframework.poi.util.PoiPublicUtil; import org.jeecgframework.poi.util.PoiPublicUtil;
import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.jdbc.datasource.DriverManagerDataSource;
@ -26,7 +28,9 @@ import java.io.InputStream;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -136,6 +140,7 @@ public class CommonUtils {
} }
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
throw new JeecgBootException(e.getMessage());
} }
return url; return url;
} }
@ -148,7 +153,7 @@ public class CommonUtils {
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){ public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
try { try {
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型 //update-begin-author:liusq date:20210809 for: 过滤上传文件类型
FileTypeFilter.fileTypeFilter(mf); SsrfFileTypeFilter.checkUploadFileType(mf);
//update-end-author:liusq date:20210809 for: 过滤上传文件类型 //update-end-author:liusq date:20210809 for: 过滤上传文件类型
String fileName = null; String fileName = null;
File file = new File(uploadpath + File.separator + bizPath + File.separator ); File file = new File(uploadpath + File.separator + bizPath + File.separator );
@ -299,7 +304,7 @@ public class CommonUtils {
DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE; DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) { }else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) {
DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER; DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER;
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0) { }else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0 || dbType.indexOf(DataBaseConstant.DB_TYPE_KINGBASEES)>=0) {
DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL; DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL;
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) { }else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) {
DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB; DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
@ -343,8 +348,11 @@ public class CommonUtils {
//返回 host domain //返回 host domain
String baseDomainPath = null; String baseDomainPath = null;
int length = 80; //update-begin---author:wangshuai---date:2024-03-15---for:【QQYUN-8561】企业微信登陆请求接口设置上下文不一致导致接口404---
if(length == serverPort){ int httpPort = 80;
int httpsPort = 443;
if(httpPort == serverPort || httpsPort == serverPort){
//update-end---author:wangshuai---date:2024-03-15---for:【QQYUN-8561】企业微信登陆请求接口设置上下文不一致导致接口404---~
baseDomainPath = scheme + "://" + serverName + contextPath ; baseDomainPath = scheme + "://" + serverName + contextPath ;
}else{ }else{
baseDomainPath = scheme + "://" + serverName + ":" + serverPort + contextPath ; baseDomainPath = scheme + "://" + serverName + ":" + serverPort + contextPath ;
@ -392,4 +400,91 @@ public class CommonUtils {
return target; return target;
} }
/**
* 将list集合以分割符的方式进行分割
* @param list String类型的集合文本
* @param separator 分隔符
* @return
*/
public static String getSplitText(List<String> list, String separator) {
if (null != list && list.size() > 0) {
return StringUtils.join(list, separator);
}
return "";
}
/**
* 通过table的条件SQL
*
* @param tableSql sys_user where name = '1212'
* @return name = '1212'
*/
public static String getFilterSqlByTableSql(String tableSql) {
if(oConvertUtils.isEmpty(tableSql)){
return null;
}
if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
String[] arr = tableSql.split(" (?i)where ");
if (arr != null && oConvertUtils.isNotEmpty(arr[1])) {
return arr[1];
}
}
return "";
}
/**
* 通过table获取表名
*
* @param tableSql sys_user where name = '1212'
* @return sys_user
*/
public static String getTableNameByTableSql(String tableSql) {
if(oConvertUtils.isEmpty(tableSql)){
return null;
}
if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
String[] arr = tableSql.split(" (?i)where ");
return arr[0].trim();
} else {
return tableSql;
}
}
/**
* 判断两个数组是否存在交集
* @param set1
* @param arr2
* @return
*/
public static boolean hasIntersection(Set<String> set1, String[] arr2) {
if (set1 == null) {
return false;
}
if(set1.size()>0){
for (String str : arr2) {
if (set1.contains(str)) {
return true;
}
}
}
return false;
}
/**
* 输出info日志会捕获异常防止因为日志问题导致程序异常
*
* @param msg
* @param objects
*/
public static void logInfo(String msg, Object... objects) {
try {
log.info(msg, objects);
} catch (Exception e) {
log.warn("{} —— {}", msg, e.getMessage());
}
}
} }

View File

@ -1,9 +1,5 @@
package org.jeecg.common.util; package org.jeecg.common.util;
import org.jeecg.config.StaticConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient; import com.aliyuncs.IAcsClient;
@ -12,6 +8,10 @@ import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile; import com.aliyuncs.profile.IClientProfile;
import org.jeecg.common.constant.enums.DySmsEnum;
import org.jeecg.config.StaticConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Created on 17/6/7. * Created on 17/6/7.
@ -55,15 +55,15 @@ public class DySmsHelper {
} }
public static boolean sendSms(String phone,JSONObject templateParamJson,DySmsEnum dySmsEnum) throws ClientException { public static boolean sendSms(String phone, JSONObject templateParamJson, DySmsEnum dySmsEnum) throws ClientException {
//可自助调整超时时间 //可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000"); System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000"); System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//update-begin-authortaoyan date:20200811 for:配置类数据获取 //update-begin-authortaoyan date:20200811 for:配置类数据获取
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class); StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
logger.info("阿里大鱼短信秘钥 accessKeyId" + staticConfig.getAccessKeyId()); //logger.info("阿里大鱼短信秘钥 accessKeyId" + staticConfig.getAccessKeyId());
logger.info("阿里大鱼短信秘钥 accessKeySecret"+ staticConfig.getAccessKeySecret()); //logger.info("阿里大鱼短信秘钥 accessKeySecret"+ staticConfig.getAccessKeySecret());
setAccessKeyId(staticConfig.getAccessKeyId()); setAccessKeyId(staticConfig.getAccessKeyId());
setAccessKeySecret(staticConfig.getAccessKeySecret()); setAccessKeySecret(staticConfig.getAccessKeySecret());
//update-end-authortaoyan date:20200811 for:配置类数据获取 //update-end-authortaoyan date:20200811 for:配置类数据获取

View File

@ -4,7 +4,7 @@ import io.minio.*;
import io.minio.http.Method; import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.util.filter.FileTypeFilter; import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.common.util.filter.StrAttackFilter; import org.jeecg.common.util.filter.StrAttackFilter;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -60,7 +60,7 @@ public class MinioUtil {
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击 //update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型 //update-begin-author:liusq date:20210809 for: 过滤上传文件类型
FileTypeFilter.fileTypeFilter(file); SsrfFileTypeFilter.checkUploadFileType(file);
//update-end-author:liusq date:20210809 for: 过滤上传文件类型 //update-end-author:liusq date:20210809 for: 过滤上传文件类型
String newBucket = bucketName; String newBucket = bucketName;

View File

@ -1,5 +1,6 @@
package org.jeecg.common.util; package org.jeecg.common.util;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -7,6 +8,7 @@ import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** /**
* @author 张代浩 * @author 张代浩
@ -252,4 +254,86 @@ public class ReflectHelper {
return value; return value;
} }
/**
* 判断给定的字段是不是类中的属性
* @param field 字段名
* @param clazz 类对象
* @return
*/
public static boolean isClassField(String field, Class clazz){
Field[] fields = clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++){
String fieldName = fields[i].getName();
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
return true;
}
}
return false;
}
/**
* 获取class的 包括父类的
* @param clazz
* @return
*/
public static List<Field> getClassFields(Class<?> clazz) {
List<Field> list = new ArrayList<Field>();
Field[] fields;
do{
fields = clazz.getDeclaredFields();
for(int i = 0;i<fields.length;i++){
list.add(fields[i]);
}
clazz = clazz.getSuperclass();
}while(clazz!= Object.class&&clazz!=null);
return list;
}
/**
* 获取表字段名
* @param clazz
* @param name
* @return
*/
public static String getTableFieldName(Class<?> clazz, String name) {
try {
//如果字段加注解了@TableField(exist = false),不走DB查询
Field field = null;
try {
field = clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
//e.printStackTrace();
}
//如果为空,则去父类查找字段
if (field == null) {
List<Field> allFields = getClassFields(clazz);
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
if(searchFields!=null && searchFields.size()>0){
field = searchFields.get(0);
}
}
if (field != null) {
TableField tableField = field.getAnnotation(TableField.class);
if (tableField != null){
if(tableField.exist() == false){
//如果设置了TableField false 这个字段不需要处理
return null;
}else{
String column = tableField.value();
//如果设置了TableField value 这个字段是实体字段
if(!"".equals(column)){
return column;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return name;
}
} }

View File

@ -1,11 +1,12 @@
package org.jeecg.common.util; package org.jeecg.common.util;
import cn.hutool.crypto.SecureUtil; import cn.hutool.core.util.ReUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.constant.CommonConstant;
import javax.servlet.http.HttpServletRequest; import org.jeecg.common.constant.SymbolConstant;
import java.lang.reflect.Field; import org.jeecg.common.exception.JeecgSqlInjectionException;
import java.util.Set; import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -15,61 +16,74 @@ import java.util.regex.Pattern;
* @author zhoujf * @author zhoujf
*/ */
@Slf4j @Slf4j
public class SqlInjectionUtil { public class SqlInjectionUtil {
/** /**
* sign 用于表字典加签的盐值【SQL漏洞】 * 默认—sql注入关键词
* (上线修改值 20200501同步修改前端的盐值
*/ */
private final static String TABLE_DICT_SIGN_SALT = "20200501"; private final static String XSS_STR = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
private final static String XSS_STR = "and |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
/** /**
* 正则 user() 匹配更严谨 * online报表专用—sql注入关键词
*/ */
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)"; private static String specialReportXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |alter |delete |grant |update |drop |master |truncate |declare |--";
/**正则 show tables*/
private final static String SHOW_TABLES = "show\\s+tables";
/** /**
* sleep函数 * 字典专用—sql注入关键词
*/ */
private final static Pattern FUN_SLEEP = Pattern.compile("sleep\\([\\d\\.]*\\)"); private static String specialDictSqlXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|--";
/**
* 完整匹配的key不需要考虑前空格
*/
private static List<String> FULL_MATCHING_KEYWRODS = new ArrayList<>();
static {
FULL_MATCHING_KEYWRODS.add(";");
FULL_MATCHING_KEYWRODS.add("+");
FULL_MATCHING_KEYWRODS.add("--");
}
/**
* sql注入风险的 正则关键字
*
* 函数匹配,需要用正则模式
*/
private final static String[] XSS_REGULAR_STR_ARRAY = new String[]{
"chr\\s*\\(",
"mid\\s*\\(",
" char\\s*\\(",
"sleep\\s*\\(",
"user\\s*\\(",
"show\\s+tables",
"user[\\s]*\\([\\s]*\\)",
"show\\s+databases",
"sleep\\(\\d*\\)",
"sleep\\(.*\\)",
};
/** /**
* sql注释的正则 * sql注释的正则
*/ */
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/"); private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
private final static String SQL_ANNOTATION2 = "--";
/** /**
* 针对表字典进行额外的sign签名校验增加安全机制 * sql注入提示语
* @param dictCode:
* @param sign:
* @param request:
* @Return: void
*/ */
public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) { private final static String SQL_INJECTION_KEYWORD_TIP = "请注意存在SQL注入关键词---> {}";
//表字典SQL注入漏洞,签名校验 private final static String SQL_INJECTION_TIP = "请注意值可能存在SQL注入风险!--->";
String accessToken = request.getHeader("X-Access-Token"); private final static String SQL_INJECTION_TIP_VARIABLE = "请注意值可能存在SQL注入风险!---> {}";
String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
String javaSign = SecureUtil.md5(signStr);
if (!javaSign.equals(sign)) {
log.error("表字典SQL注入漏洞签名校验失败 " + sign + "!=" + javaSign+ ",dictCode=" + dictCode);
throw new JeecgBootException("无权限访问!");
}
log.info(" 表字典SQL注入漏洞签名校验成功sign=" + sign + ",dictCode=" + dictCode);
}
/** /**
* sql注入过滤处理遇到注入关键字抛异常 * sql注入过滤处理遇到注入关键字抛异常
* @param value * @param values
*/ */
public static void filterContent(String value) { public static void filterContent(String... values) {
filterContent(value, null); filterContent(values, null);
} }
/** /**
* sql注入过滤处理遇到注入关键字抛异常 * 校验比较严格
* *
* sql注入过滤处理遇到注入关键字抛异常
*
* @param value * @param value
* @return * @return
*/ */
@ -77,47 +91,83 @@ public class SqlInjectionUtil {
if (value == null || "".equals(value)) { if (value == null || "".equals(value)) {
return; return;
} }
// 校验sql注释 不允许有sql注释 // 一、校验sql注释 不允许有sql注释
checkSqlAnnotation(value); checkSqlAnnotation(value);
// 统一转为小写 // 转为小写进行后续比较
value = value.toLowerCase(); value = value.toLowerCase().trim();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
//value = value.replaceAll("/\\*.*\\*/",""); // 二、SQL注入检测存在绕过风险 (普通文本校验)
//https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
String[] xssArr = XSS_STR.split("\\|"); String[] xssArr = XSS_STR.split("\\|");
for (int i = 0; i < xssArr.length; i++) { for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1) { if (value.indexOf(xssArr[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]); log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
} }
} }
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号 // 三、SQL注入检测存在绕过风险 (自定义传入普通文本校验)
if (customXssString != null) { if (customXssString != null) {
String[] xssArr2 = customXssString.split("\\|"); String[] xssArr2 = customXssString.split("\\|");
for (int i = 0; i < xssArr2.length; i++) { for (int i = 0; i < xssArr2.length; i++) {
if (value.indexOf(xssArr2[i]) > -1) { if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr2[i]); log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr2[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
} }
} }
} }
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ // 四、SQL注入检测存在绕过风险 (正则校验)
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
String regular = ".*" + regularOriginal + ".*";
if (Pattern.matches(regular, value)) {
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
}
} }
return; return;
} }
/** /**
* sql注入过滤处理遇到注入关键字抛异常 * 判断是否存在SQL注入关键词字符串
* @param values *
* @param keyword
* @return
*/ */
public static void filterContent(String[] values) { @SuppressWarnings("AlibabaUndefineMagicConstant")
filterContent(values, null); private static boolean isExistSqlInjectKeyword(String sql, String keyword) {
if (sql.startsWith(keyword.trim())) {
return true;
} else if (sql.contains(keyword)) {
// 需要匹配的sql注入关键词
String matchingText = " " + keyword;
if(FULL_MATCHING_KEYWRODS.contains(keyword)){
matchingText = keyword;
}
if (sql.contains(matchingText)) {
return true;
} else {
String regularStr = "\\s+\\S+" + keyword;
List<String> resultFindAll = ReUtil.findAll(regularStr, sql, 0, new ArrayList<String>());
for (String res : resultFindAll) {
log.info("isExistSqlInjectKeyword —- 匹配到的SQL注入关键词{}", res);
/**
* SQL注入中可以替换空格的字符(%09 %0A %0D +都可以替代空格)
* http://blog.chinaunix.net/uid-12501104-id-2932639.html
* https://www.cnblogs.com/Vinson404/p/7253255.html
* */
if (res.contains("%") || res.contains("+") || res.contains("#") || res.contains("/") || res.contains(")")) {
return true;
}
}
}
}
return false;
} }
/** /**
* sql注入过滤处理遇到注入关键字抛异常 * sql注入过滤处理遇到注入关键字抛异常
* *
@ -125,40 +175,11 @@ public class SqlInjectionUtil {
* @return * @return
*/ */
public static void filterContent(String[] values, String customXssString) { public static void filterContent(String[] values, String customXssString) {
String[] xssArr = XSS_STR.split("\\|"); for (String val : values) {
for (String value : values) { if (oConvertUtils.isEmpty(val)) {
if (value == null || "".equals(value)) {
return; return;
} }
// 校验sql注释 不允许有sql注释 filterContent(val, customXssString);
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
//value = value.replaceAll("/\\*.*\\*/","");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
}
}
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号
if (customXssString != null) {
String[] xssArr2 = customXssString.split("\\|");
for (int i = 0; i < xssArr2.length; i++) {
if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意存在SQL注入关键词---> {}", xssArr2[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
}
}
}
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
}
} }
return; return;
} }
@ -170,130 +191,230 @@ public class SqlInjectionUtil {
* @param value * @param value
* @return * @return
*/ */
//@Deprecated
public static void specialFilterContentForDictSql(String value) { public static void specialFilterContentForDictSql(String value) {
String specialXssStr = " exec |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract| insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()"; String[] xssArr = specialDictSqlXssStr.split("\\|");
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) { if (value == null || "".equals(value)) {
return; return;
} }
// 校验sql注释 不允许有sql注释 // 一、校验sql注释 不允许有sql注释
checkSqlAnnotation(value); checkSqlAnnotation(value);
// 统一转为小写 value = value.toLowerCase().trim();
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE // 二、SQL注入检测存在绕过风险 (普通文本校验)
//value = value.replaceAll("/\\*.*\\*/","");
for (int i = 0; i < xssArr.length; i++) { for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) { if (isExistSqlInjectKeyword(value, xssArr[i])) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]); log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
} }
} }
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); // 三、SQL注入检测存在绕过风险 (正则校验)
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
String regular = ".*" + regularOriginal + ".*";
if (Pattern.matches(regular, value)) {
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
}
} }
return; return;
} }
/** /**
* 【提醒:不通用】 * 【提醒:不通用】
* 仅用于Online报表SQL解析注入过滤 * 仅用于Online报表SQL解析注入过滤
* @param value * @param value
* @return * @return
*/ */
//@Deprecated
public static void specialFilterContentForOnlineReport(String value) { public static void specialFilterContentForOnlineReport(String value) {
String specialXssStr = " exec |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract| insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()"; String[] xssArr = specialReportXssStr.split("\\|");
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) { if (value == null || "".equals(value)) {
return; return;
} }
// 校验sql注释 不允许有sql注释 // 一、校验sql注释 不允许有sql注释
checkSqlAnnotation(value); checkSqlAnnotation(value);
// 统一转为小写 value = value.toLowerCase().trim();
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE // 二、SQL注入检测存在绕过风险 (普通文本校验)
//value = value.replaceAll("/\\*.*\\*/"," ");
for (int i = 0; i < xssArr.length; i++) { for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) { if (isExistSqlInjectKeyword(value, xssArr[i])) {
log.error("请注意存在SQL注入关键词---> {}", xssArr[i]); log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
log.error("请注意值可能存在SQL注入风险!---> {}", value); log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
} }
} }
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ // 三、SQL注入检测存在绕过风险 (正则校验)
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
String regular = ".*" + regularOriginal + ".*";
if (Pattern.matches(regular, value)) {
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
}
} }
return; return;
} }
/**
* 判断给定的字段是不是类中的属性
* @param field 字段名
* @param clazz 类对象
* @return
*/
public static boolean isClassField(String field, Class clazz){
Field[] fields = clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++){
String fieldName = fields[i].getName();
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
return true;
}
}
return false;
}
/**
* 判断给定的多个字段是不是类中的属性
* @param fieldSet 字段名set
* @param clazz 类对象
* @return
*/
public static boolean isClassField(Set<String> fieldSet, Class clazz){
Field[] fields = clazz.getDeclaredFields();
for(String field: fieldSet){
boolean exist = false;
for(int i=0;i<fields.length;i++){
String fieldName = fields[i].getName();
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
exist = true;
break;
}
}
if(!exist){
return false;
}
}
return true;
}
/** /**
* 校验是否有sql注释 * 校验是否有sql注释
* @return * @return
*/ */
public static void checkSqlAnnotation(String str){ public static void checkSqlAnnotation(String str){
if(str.contains(SQL_ANNOTATION2)){
String error = "请注意SQL中不允许含注释有安全风险";
log.error(error);
throw new RuntimeException(error);
}
Matcher matcher = SQL_ANNOTATION.matcher(str); Matcher matcher = SQL_ANNOTATION.matcher(str);
if(matcher.find()){ if(matcher.find()){
String error = "请注意值可能存在SQL注入风险---> \\*.*\\"; String error = "请注意值可能存在SQL注入风险---> \\*.*\\";
log.error(error); log.error(error);
throw new RuntimeException(error); throw new JeecgSqlInjectionException(error);
}
// issues/4737 sys/duplicate/check SQL注入 #4737
Matcher sleepMatcher = FUN_SLEEP.matcher(str);
if(sleepMatcher.find()){
String error = "请注意值可能存在SQL注入风险---> sleep";
log.error(error);
throw new RuntimeException(error);
} }
} }
/**
* 返回查询表名
* <p>
* sql注入过滤处理遇到注入关键字抛异常
*
* @param table
*/
private static Pattern tableNamePattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_\\$]{0,63}$");
public static String getSqlInjectTableName(String table) {
if(oConvertUtils.isEmpty(table)){
return table;
}
table = table.trim();
/**
* 检验表名是否合法
*
* 表名只能由字母、数字和下划线组成。
* 表名必须以字母开头。
* 表名长度通常有限制,例如最多为 64 个字符。
*/
boolean isValidTableName = tableNamePattern.matcher(table).matches();
if (!isValidTableName) {
String errorMsg = "表名不合法存在SQL注入风险!--->" + table;
log.error(errorMsg);
throw new JeecgSqlInjectionException(errorMsg);
}
//进一步验证是否存在SQL注入风险
filterContent(table);
return table;
}
/**
* 返回查询字段
* <p>
* sql注入过滤处理遇到注入关键字抛异常
*
* @param field
*/
static final Pattern fieldPattern = Pattern.compile("^[a-zA-Z0-9_]+$");
public static String getSqlInjectField(String field) {
if(oConvertUtils.isEmpty(field)){
return field;
}
field = field.trim();
if (field.contains(SymbolConstant.COMMA)) {
return getSqlInjectField(field.split(SymbolConstant.COMMA));
}
/**
* 校验表字段是否有效
*
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
*/
boolean isValidField = fieldPattern.matcher(field).matches();
if (!isValidField) {
String errorMsg = "字段不合法存在SQL注入风险!--->" + field;
log.error(errorMsg);
throw new JeecgSqlInjectionException(errorMsg);
}
//进一步验证是否存在SQL注入风险
filterContent(field);
return field;
}
/**
* 获取多个字段
* 返回: 逗号拼接
*
* @param fields
* @return
*/
public static String getSqlInjectField(String... fields) {
for (String s : fields) {
getSqlInjectField(s);
}
return String.join(SymbolConstant.COMMA, fields);
}
/**
* 获取排序字段
* 返回:字符串
*
* 1.将驼峰命名转化成下划线
* 2.限制sql注入
* @param sortField 排序字段
* @return
*/
public static String getSqlInjectSortField(String sortField) {
String field = SqlInjectionUtil.getSqlInjectField(oConvertUtils.camelToUnderline(sortField));
return field;
}
/**
* 获取多个排序字段
* 返回:数组
*
* 1.将驼峰命名转化成下划线
* 2.限制sql注入
* @param sortFields 多个排序字段
* @return
*/
public static List getSqlInjectSortFields(String... sortFields) {
List list = new ArrayList<String>();
for (String sortField : sortFields) {
list.add(getSqlInjectSortField(sortField));
}
return list;
}
/**
* 获取 orderBy type
* 返回:字符串
* <p>
* 1.检测是否为 asc 或 desc 其中的一个
* 2.限制sql注入
*
* @param orderType
* @return
*/
public static String getSqlInjectOrderType(String orderType) {
if (orderType == null) {
return null;
}
orderType = orderType.trim();
if (CommonConstant.ORDER_TYPE_ASC.equalsIgnoreCase(orderType)) {
return CommonConstant.ORDER_TYPE_ASC;
} else {
return CommonConstant.ORDER_TYPE_DESC;
}
}
} }

View File

@ -34,6 +34,21 @@ public class TokenUtils {
} }
return token; return token;
} }
/**
* 获取 request 里传递的 token
* @return
*/
public static String getTokenByRequest() {
String token = null;
try {
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
token = TokenUtils.getTokenByRequest(request);
} catch (Exception e) {
//e.printStackTrace();
}
return token;
}
/** /**
* 获取 request 里传递的 tenantId (租户ID) * 获取 request 里传递的 tenantId (租户ID)

View File

@ -34,11 +34,11 @@ public class FreemarkerParseFactory {
/** /**
* 文件缓存 * 文件缓存
*/ */
private static final Configuration TPL_CONFIG = new Configuration(); private static final Configuration TPL_CONFIG = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
/** /**
* SQL 缓存 * SQL 缓存
*/ */
private static final Configuration SQL_CONFIG = new Configuration(); private static final Configuration SQL_CONFIG = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
private static StringTemplateLoader stringTemplateLoader = new StringTemplateLoader(); private static StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
@ -47,8 +47,7 @@ public class FreemarkerParseFactory {
.compile("(?ms)/\\*.*?\\*/|^\\s*//.*?$"); .compile("(?ms)/\\*.*?\\*/|^\\s*//.*?$");
static { static {
TPL_CONFIG.setClassForTemplateLoading( TPL_CONFIG.setClassForTemplateLoading(new FreemarkerParseFactory().getClass(), "/");
new FreemarkerParseFactory().getClass(), "/");
TPL_CONFIG.setNumberFormat("0.#####################"); TPL_CONFIG.setNumberFormat("0.#####################");
SQL_CONFIG.setTemplateLoader(stringTemplateLoader); SQL_CONFIG.setTemplateLoader(stringTemplateLoader);
SQL_CONFIG.setNumberFormat("0.#####################"); SQL_CONFIG.setNumberFormat("0.#####################");
@ -57,6 +56,7 @@ public class FreemarkerParseFactory {
//update-begin-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructorExecute和freemarker.template.utility.JythonRuntime。 //update-begin-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructorExecute和freemarker.template.utility.JythonRuntime。
//https://ackcent.com/in-depth-freemarker-template-injection/ //https://ackcent.com/in-depth-freemarker-template-injection/
TPL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
//update-end-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructorExecute和freemarker.template.utility.JythonRuntime。 //update-end-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructorExecute和freemarker.template.utility.JythonRuntime。
} }

View File

@ -4,27 +4,72 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
/** /**
* @Description: 校验上传文件敏感后缀 * @Description: 校验文件敏感后缀
* @author: lsq * @author: lsq
* @date: 2021年08月09 15:29 * @date: 2023年09月12 15:29
*/ */
@Slf4j @Slf4j
public class FileTypeFilter { public class SsrfFileTypeFilter {
/**文件后缀*/
private static String[] forbidType = {"jsp","php"};
/**
* 允许操作文件类型白名单
*/
private final static List<String> FILE_TYPE_WHITE_LIST = new ArrayList<>();
/**初始化文件头类型,不够的自行补充*/ /**初始化文件头类型,不够的自行补充*/
final static HashMap<String, String> FILE_TYPE_MAP = new HashMap<>(); final static HashMap<String, String> FILE_TYPE_MAP = new HashMap<>();
static { static {
//图片文件
FILE_TYPE_WHITE_LIST.add("jpg");
FILE_TYPE_WHITE_LIST.add("jpeg");
FILE_TYPE_WHITE_LIST.add("png");
FILE_TYPE_WHITE_LIST.add("gif");
FILE_TYPE_WHITE_LIST.add("bmp");
FILE_TYPE_WHITE_LIST.add("svg");
FILE_TYPE_WHITE_LIST.add("ico");
//文本文件
FILE_TYPE_WHITE_LIST.add("txt");
FILE_TYPE_WHITE_LIST.add("doc");
FILE_TYPE_WHITE_LIST.add("docx");
FILE_TYPE_WHITE_LIST.add("pdf");
FILE_TYPE_WHITE_LIST.add("csv");
// FILE_TYPE_WHITE_LIST.add("xml");
//音视频文件
FILE_TYPE_WHITE_LIST.add("mp4");
FILE_TYPE_WHITE_LIST.add("avi");
FILE_TYPE_WHITE_LIST.add("mov");
FILE_TYPE_WHITE_LIST.add("wmv");
FILE_TYPE_WHITE_LIST.add("mp3");
FILE_TYPE_WHITE_LIST.add("wav");
//表格文件
FILE_TYPE_WHITE_LIST.add("xls");
FILE_TYPE_WHITE_LIST.add("xlsx");
//压缩文件
FILE_TYPE_WHITE_LIST.add("zip");
FILE_TYPE_WHITE_LIST.add("rar");
FILE_TYPE_WHITE_LIST.add("7z");
FILE_TYPE_WHITE_LIST.add("tar");
//app文件后缀
FILE_TYPE_WHITE_LIST.add("apk");
FILE_TYPE_WHITE_LIST.add("wgt");
//设置禁止文件的头部标记
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp"); FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");
FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php"); FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php");
FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");
FILE_TYPE_MAP.put("494e5345525420494e54", "sql");
/* fileTypeMap.put("ffd8ffe000104a464946", "jpg"); /* fileTypeMap.put("ffd8ffe000104a464946", "jpg");
fileTypeMap.put("89504e470d0a1a0a0000", "png"); fileTypeMap.put("89504e470d0a1a0a0000", "png");
fileTypeMap.put("47494638396126026f01", "gif"); fileTypeMap.put("47494638396126026f01", "gif");
@ -89,17 +134,38 @@ public class FileTypeFilter {
return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()); return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
} }
/** /**
* 文件类型过滤 * 下载文件类型过滤
*
* @param filePath
*/
public static void checkDownloadFileType(String filePath) throws IOException {
//文件后缀
String suffix = getFileTypeBySuffix(filePath);
log.info("suffix:{}", suffix);
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
//是否允许下载的文件
if (!isAllowExtension) {
throw new IOException("下载失败,存在非法文件类型:" + suffix);
}
}
/**
* 上传文件类型过滤
* *
* @param file * @param file
*/ */
public static void fileTypeFilter(MultipartFile file) throws Exception { public static void checkUploadFileType(MultipartFile file) throws Exception {
//获取文件真是后缀
String suffix = getFileType(file); String suffix = getFileType(file);
for (String type : forbidType) {
if (type.contains(suffix)) { log.info("suffix:{}", suffix);
throw new Exception("上传失败,非法文件类型:" + suffix); boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
} //是否允许下载的文件
if (!isAllowExtension) {
throw new Exception("上传失败,存在非法文件类型:" + suffix);
} }
} }

View File

@ -168,6 +168,17 @@ public class oConvertUtils {
} }
} }
public static Integer getInteger(Object object, Integer defval) {
if (isEmpty(object)) {
return (defval);
}
try {
return (Integer.parseInt(object.toString()));
} catch (NumberFormatException e) {
return (defval);
}
}
public static Integer getInt(Object object) { public static Integer getInt(Object object) {
if (isEmpty(object)) { if (isEmpty(object)) {
return null; return null;
@ -702,9 +713,20 @@ public class oConvertUtils {
if (isArray(oldVal)) { if (isArray(oldVal)) {
return equalityOfArrays((Object[]) oldVal, (Object[]) newVal); return equalityOfArrays((Object[]) oldVal, (Object[]) newVal);
}else if(oldVal instanceof JSONArray){ }else if(oldVal instanceof JSONArray){
return equalityOfJSONArray((JSONArray) oldVal, (JSONArray) newVal); if(newVal instanceof JSONArray){
return equalityOfJSONArray((JSONArray) oldVal, (JSONArray) newVal);
}else{
if (isEmpty(newVal) && (oldVal == null || ((JSONArray) oldVal).size() == 0)) {
return true;
}
List<Object> arrayStr = Arrays.asList(newVal.toString().split(","));
JSONArray newValArray = new JSONArray(arrayStr);
return equalityOfJSONArray((JSONArray) oldVal, newValArray);
}
}else{
return oldVal.equals(newVal);
} }
return oldVal.equals(newVal);
} else { } else {
if (oldVal == null && newVal == null) { if (oldVal == null && newVal == null) {
return true; return true;
@ -742,7 +764,7 @@ public class oConvertUtils {
Object[] newValArray = newVal.toArray(); Object[] newValArray = newVal.toArray();
return equalityOfArrays(oldValArray,newValArray); return equalityOfArrays(oldValArray,newValArray);
} else { } else {
if (oldVal == null && newVal == null) { if ((oldVal == null || oldVal.size() == 0) && (newVal == null || newVal.size() == 0)) {
return true; return true;
} else { } else {
return false; return false;
@ -750,6 +772,38 @@ public class oConvertUtils {
} }
} }
/**
* 比较带逗号的字符串
* QQYUN-5212【简流】按日期触发 多选 人员组件 选择顺序不一致时 不触发,应该是统一问题 包括多选部门组件
* @param oldVal
* @param newVal
* @return
*/
public static boolean equalityOfStringArrays(String oldVal, String newVal) {
if(oldVal.equals(newVal)){
return true;
}
if(oldVal.indexOf(",")>=0 && newVal.indexOf(",")>=0){
String[] arr1 = oldVal.split(",");
String[] arr2 = newVal.split(",");
if(arr1.length == arr2.length){
boolean flag = true;
Map<String, Integer> map = new HashMap<>();
for(String s1: arr1){
map.put(s1, 1);
}
for(String s2: arr2){
if(map.get(s2) == null){
flag = false;
break;
}
}
return flag;
}
}
return false;
}
/** /**
* 判断两个数组是否相等(数组元素不分顺序) * 判断两个数组是否相等(数组元素不分顺序)
* *
@ -763,7 +817,7 @@ public class oConvertUtils {
Arrays.sort(newVal); Arrays.sort(newVal);
return Arrays.equals(oldVal, newVal); return Arrays.equals(oldVal, newVal);
} else { } else {
if (oldVal == null && newVal == null) { if ((oldVal == null || oldVal.length == 0) && (newVal == null || newVal.length == 0)) {
return true; return true;
} else { } else {
return false; return false;
@ -807,4 +861,85 @@ public class oConvertUtils {
} }
return json; return json;
} }
/**
* 将List 转成 JSONArray
* @return
*/
public static JSONArray list2JSONArray(List<String> list){
if(list==null || list.size()==0){
return null;
}
JSONArray array = new JSONArray();
for(String str: list){
array.add(str);
}
return array;
}
/**
* 判断两个list中的元素是否完全一致
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
* @return
*/
public static boolean isEqList(List<String> list1, List<String> list2){
if(list1.size() != list2.size()){
return false;
}
for(String str1: list1){
boolean flag = false;
for(String str2: list2){
if(str1.equals(str2)){
flag = true;
break;
}
}
if(!flag){
return false;
}
}
return true;
}
/**
* 判断 list1中的元素是否在list2中出现
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
* @param list1
* @param list2
* @return
*/
public static boolean isInList(List<String> list1, List<String> list2){
for(String str1: list1){
boolean flag = false;
for(String str2: list2){
if(str1.equals(str2)){
flag = true;
break;
}
}
if(flag){
return true;
}
}
return false;
}
/**
* 计算文件大小转成MB
* @param uploadCount
* @return
*/
public static Double calculateFileSizeToMb(Long uploadCount){
double count = 0.0;
if(uploadCount>0) {
BigDecimal bigDecimal = new BigDecimal(uploadCount);
//换算成MB
BigDecimal divide = bigDecimal.divide(new BigDecimal(1048576));
count = divide.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
return count;
}
return count;
}
} }

View File

@ -11,7 +11,7 @@ import org.apache.commons.fileupload.FileItemStream;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.util.CommonUtils; import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.filter.FileTypeFilter; import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.common.util.filter.StrAttackFilter; import org.jeecg.common.util.filter.StrAttackFilter;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -98,7 +98,7 @@ public class OssBootUtil {
*/ */
public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception { public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型 //update-begin-author:liusq date:20210809 for: 过滤上传文件类型
FileTypeFilter.fileTypeFilter(file); SsrfFileTypeFilter.checkUploadFileType(file);
//update-end-author:liusq date:20210809 for: 过滤上传文件类型 //update-end-author:liusq date:20210809 for: 过滤上传文件类型
String filePath = null; String filePath = null;

View File

@ -1,6 +1,8 @@
package org.jeecg.common.util.security; package org.jeecg.common.util.security;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.exception.JeecgSqlInjectionException;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -59,27 +61,33 @@ public abstract class AbstractQueryBlackListHandler {
if(list==null){ if(list==null){
return true; return true;
} }
log.info("--获取sql信息--", list.toString()); log.info(" 获取sql信息 {} ", list.toString());
boolean flag = checkTableAndFieldsName(list); boolean flag = checkTableAndFieldsName(list);
if(flag == false){ if(flag == false){
return false; return false;
} }
for (QueryTable table : list) { for (QueryTable table : list) {
String name = table.getName(); String name = table.getName();
String fieldString = ruleMap.get(name); String fieldRule = ruleMap.get(name);
// 有没有配置这张表 // 有没有配置这张表
if (fieldString != null) { if (fieldRule != null) {
if ("*".equals(fieldString) || table.isAll()) { if ("*".equals(fieldRule) || table.isAll()) {
flag = false; flag = false;
log.warn("sql黑名单校验表【"+name+"】禁止查询"); log.warn("sql黑名单校验表【"+name+"】禁止查询");
break; break;
} else if (table.existSameField(fieldString)) { } else if (table.existSameField(fieldRule)) {
flag = false; flag = false;
break; break;
} }
} }
} }
// 返回黑名单校验结果(不合法直接抛出异常)
if(!flag){
log.error(this.getError());
throw new JeecgSqlInjectionException(this.getError());
}
return flag; return flag;
} }
@ -189,21 +197,21 @@ public abstract class AbstractQueryBlackListHandler {
* @return * @return
*/ */
public boolean existSameField(String fieldString) { public boolean existSameField(String fieldString) {
String[] arr = fieldString.split(","); String[] controlFields = fieldString.split(",");
for (String exp : fields) { for (String sqlField : fields) {
for (String config : arr) { for (String controlField : controlFields) {
if (exp.equals(config)) { if (sqlField.equals(controlField)) {
// 非常明确的列直接比较 // 非常明确的列直接比较
log.warn("sql黑名单校验表【"+name+"】中字段【"+config+"】禁止查询"); log.warn("sql黑名单校验表【"+name+"】中字段【"+controlField+"】禁止查询");
return true; return true;
} else { } else {
// 使用表达式的列 只能判读字符串包含了 // 使用表达式的列 只能判读字符串包含了
String aliasColumn = config; String aliasColumn = controlField;
if (alias != null && alias.length() > 0) { if (StringUtils.isNotBlank(alias)) {
aliasColumn = alias + "." + config; aliasColumn = alias + "." + controlField;
} }
if (exp.indexOf(aliasColumn) > 0) { if (sqlField.indexOf(aliasColumn) != -1) {
log.warn("sql黑名单校验表【"+name+"】中字段【"+config+"】禁止查询"); log.warn("sql黑名单校验表【"+name+"】中字段【"+controlField+"】禁止查询");
return true; return true;
} }
} }

View File

@ -0,0 +1,33 @@
package org.jeecg.common.util.sqlInjection;
import net.sf.jsqlparser.parser.CCJSqlParserDefaultVisitor;
import net.sf.jsqlparser.parser.SimpleNode;
import net.sf.jsqlparser.statement.select.UnionOp;
import org.jeecg.common.exception.JeecgSqlInjectionException;
/**
* 基于抽象语法树(AST)的注入攻击分析实现
*
* @author guyadong
*/
public class InjectionAstNodeVisitor extends CCJSqlParserDefaultVisitor {
public InjectionAstNodeVisitor() {
}
/**
* 处理禁止联合查询
*
* @param node
* @param data
* @return
*/
@Override
public Object visit(SimpleNode node, Object data) {
Object value = node.jjtGetValue();
if (value instanceof UnionOp) {
throw new JeecgSqlInjectionException("DISABLE UNION");
}
return super.visit(node, data);
}
}

View File

@ -0,0 +1,172 @@
package org.jeecg.common.util.sqlInjection;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ComparisonOperator;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.WithItem;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.jeecg.common.exception.JeecgSqlInjectionException;
import org.jeecg.common.util.sqlInjection.parse.ConstAnalyzer;
import org.jeecg.common.util.sqlInjection.parse.ParserSupport;
/**
* 基于SQL语法对象的SQL注入攻击分析实现
*
* @author guyadong
*/
public class InjectionSyntaxObjectAnalyzer extends TablesNamesFinder {
/**
* 危险函数名
*/
private static final String DANGROUS_FUNCTIONS = "(sleep|benchmark|extractvalue|updatexml|ST_LatFromGeoHash|ST_LongFromGeoHash|GTID_SUBSET|GTID_SUBTRACT|floor|ST_Pointfromgeohash"
+ "|geometrycollection|multipoint|polygon|multipolygon|linestring|multilinestring)";
private static ThreadLocal<Boolean> disableSubselect = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return true;
}
};
private ConstAnalyzer constAnalyzer = new ConstAnalyzer();
public InjectionSyntaxObjectAnalyzer() {
super();
init(true);
}
@Override
public void visitBinaryExpression(BinaryExpression binaryExpression) {
if (binaryExpression instanceof ComparisonOperator) {
if (isConst(binaryExpression.getLeftExpression()) && isConst(binaryExpression.getRightExpression())) {
/** 禁用恒等式 */
throw new JeecgSqlInjectionException("DISABLE IDENTICAL EQUATION " + binaryExpression);
}
}
super.visitBinaryExpression(binaryExpression);
}
@Override
public void visit(AndExpression andExpression) {
super.visit(andExpression);
checkConstExpress(andExpression.getLeftExpression());
checkConstExpress(andExpression.getRightExpression());
}
@Override
public void visit(OrExpression orExpression) {
super.visit(orExpression);
checkConstExpress(orExpression.getLeftExpression());
checkConstExpress(orExpression.getRightExpression());
}
@Override
public void visit(Function function) {
if (function.getName().matches(DANGROUS_FUNCTIONS)) {
/** 禁用危险函数 */
throw new JeecgSqlInjectionException("DANGROUS FUNCTION: " + function.getName());
}
super.visit(function);
}
@Override
public void visit(WithItem withItem) {
try {
/** 允许 WITH 语句中的子查询 */
disableSubselect.set(false);
super.visit(withItem);
} finally {
disableSubselect.set(true);
}
}
@Override
public void visit(SubSelect subSelect) {
try {
/** 允许语句中的子查询 */
disableSubselect.set(false);
super.visit(subSelect);
} finally {
disableSubselect.set(true);
}
// if (disableSubselect.get()) {
// // 禁用子查询
// throw new JeecgSqlInjectionException("DISABLE subselect " + subSelect);
// }
}
@Override
public void visit(Column tableColumn) {
if (ParserSupport.isBoolean(tableColumn)) {
throw new JeecgSqlInjectionException("DISABLE CONST BOOL " + tableColumn);
}
super.visit(tableColumn);
}
@Override
public void visit(PlainSelect plainSelect) {
if (plainSelect.getSelectItems() != null) {
for (SelectItem item : plainSelect.getSelectItems()) {
item.accept(this);
}
}
if (plainSelect.getFromItem() != null) {
plainSelect.getFromItem().accept(this);
}
if (plainSelect.getJoins() != null) {
for (Join join : plainSelect.getJoins()) {
join.getRightItem().accept(this);
for (Expression e : join.getOnExpressions()) {
e.accept(this);
}
}
}
if (plainSelect.getWhere() != null) {
plainSelect.getWhere().accept(this);
checkConstExpress(plainSelect.getWhere());
}
if (plainSelect.getHaving() != null) {
plainSelect.getHaving().accept(this);
}
if (plainSelect.getOracleHierarchical() != null) {
plainSelect.getOracleHierarchical().accept(this);
}
if (plainSelect.getOrderByElements() != null) {
for (OrderByElement orderByElement : plainSelect.getOrderByElements()) {
orderByElement.getExpression().accept(this);
}
}
if (plainSelect.getGroupBy() != null) {
for (Expression expression : plainSelect.getGroupBy().getGroupByExpressionList().getExpressions()) {
expression.accept(this);
}
}
}
private boolean isConst(Expression expression) {
return constAnalyzer.isConstExpression(expression);
}
private void checkConstExpress(Expression expression) {
if (constAnalyzer.isConstExpression(expression)) {
/** 禁用常量表达式 */
throw new JeecgSqlInjectionException("DISABLE CONST EXPRESSION " + expression);
}
}
}

View File

@ -0,0 +1,65 @@
package org.jeecg.common.util.sqlInjection;
import org.jeecg.common.exception.JeecgSqlInjectionException;
import org.jeecg.common.util.sqlInjection.parse.ParserSupport;
;
/**
* SQL注入攻击分析器
*
* @author guyadong
* 参考:
* https://blog.csdn.net/10km/article/details/127767358
* https://gitee.com/l0km/sql2java/tree/dev/sql2java-manager/src/main/java/gu/sql2java/parser
*/
public class SqlInjectionAnalyzer {
//启用/关闭注入攻击检查
private boolean injectCheckEnable = true;
//防止SQL注入攻击分析实现
private final InjectionSyntaxObjectAnalyzer injectionChecker;
private final InjectionAstNodeVisitor injectionVisitor;
public SqlInjectionAnalyzer() {
this.injectionChecker = new InjectionSyntaxObjectAnalyzer();
this.injectionVisitor = new InjectionAstNodeVisitor();
}
/**
* 启用/关闭注入攻击检查,默认启动
*
* @param enable
* @return
*/
public SqlInjectionAnalyzer injectCheckEnable(boolean enable) {
injectCheckEnable = enable;
return this;
}
/**
* 对解析后的SQL对象执行注入攻击分析有注入攻击的危险则抛出异常{@link JeecgSqlInjectionException}
*
* @param sqlParserInfo
* @throws JeecgSqlInjectionException
*/
public ParserSupport.SqlParserInfo injectAnalyse(ParserSupport.SqlParserInfo sqlParserInfo) throws JeecgSqlInjectionException {
if (null != sqlParserInfo && injectCheckEnable) {
/** SQL注入攻击检查 */
sqlParserInfo.statement.accept(injectionChecker);
sqlParserInfo.simpleNode.jjtAccept(injectionVisitor, null);
}
return sqlParserInfo;
}
/**
* sql校验
*/
public static void checkSql(String sql,boolean check){
SqlInjectionAnalyzer sqlInjectionAnalyzer = new SqlInjectionAnalyzer();
sqlInjectionAnalyzer.injectCheckEnable(check);
ParserSupport.SqlParserInfo sqlParserInfo = ParserSupport.parse0(sql, null,null);
sqlInjectionAnalyzer.injectAnalyse(sqlParserInfo);
}
}

View File

@ -0,0 +1,569 @@
package org.jeecg.common.util.sqlInjection.parse;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseOr;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseRightShift;
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor;
import net.sf.jsqlparser.expression.operators.arithmetic.Concat;
import net.sf.jsqlparser.expression.operators.arithmetic.Division;
import net.sf.jsqlparser.expression.operators.arithmetic.IntegerDivision;
import net.sf.jsqlparser.expression.operators.arithmetic.Modulo;
import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication;
import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.conditional.XorExpression;
import net.sf.jsqlparser.expression.operators.relational.Between;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.FullTextSearch;
import net.sf.jsqlparser.expression.operators.relational.GeometryDistance;
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression;
import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression;
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitor;
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.Matches;
import net.sf.jsqlparser.expression.operators.relational.MinorThan;
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.expression.operators.relational.NamedExpressionList;
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
import net.sf.jsqlparser.expression.operators.relational.RegExpMatchOperator;
import net.sf.jsqlparser.expression.operators.relational.RegExpMySQLOperator;
import net.sf.jsqlparser.expression.operators.relational.SimilarToExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.AllTableColumns;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.SubSelect;
/**
* 判断表达是否为常量的分析器
*
* @author guyadong
*/
public class ConstAnalyzer implements ExpressionVisitor, ItemsListVisitor {
private static ThreadLocal<Boolean> constFlag = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return true;
}
};
@Override
public void visit(NullValue value) {
}
@Override
public void visit(Function function) {
constFlag.set(false);
}
@Override
public void visit(SignedExpression expr) {
expr.getExpression().accept(this);
}
@Override
public void visit(JdbcParameter parameter) {
constFlag.set(false);
}
@Override
public void visit(JdbcNamedParameter parameter) {
constFlag.set(false);
}
@Override
public void visit(DoubleValue value) {
}
@Override
public void visit(LongValue value) {
}
@Override
public void visit(DateValue value) {
}
@Override
public void visit(TimeValue value) {
}
@Override
public void visit(TimestampValue value) {
}
@Override
public void visit(Parenthesis parenthesis) {
parenthesis.getExpression().accept(this);
}
@Override
public void visit(StringValue value) {
}
@Override
public void visit(Addition expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(Division expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(IntegerDivision expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(Multiplication expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(Subtraction expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(AndExpression expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(OrExpression expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(XorExpression expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(Between expr) {
expr.getLeftExpression().accept(this);
expr.getBetweenExpressionStart().accept(this);
expr.getBetweenExpressionEnd().accept(this);
}
// /**
// * 用于处理 OverlapsCondition 类型的表达式
// * @param overlapsCondition
// */
// @Override
// public void visit(OverlapsCondition overlapsCondition) {
// constFlag.set(false);
// }
// /**
// * 用于处理 SafeCastExpression 类型的表达式。
// * @param safeCastExpression
// */
// @Override
// public void visit(SafeCastExpression safeCastExpression) {
// constFlag.set(false);
// }
@Override
public void visit(EqualsTo expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(GreaterThan expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(GreaterThanEquals expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(InExpression expr) {
if (expr.getLeftExpression() != null) {
expr.getLeftExpression().accept(this);
}
}
@Override
public void visit(IsNullExpression expr) {
expr.getLeftExpression().accept(this);
}
@Override
public void visit(FullTextSearch expr) {
constFlag.set(false);
}
@Override
public void visit(IsBooleanExpression expr) {
expr.getLeftExpression().accept(this);
}
@Override
public void visit(LikeExpression expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(MinorThan expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(MinorThanEquals expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(NotEqualsTo expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(Column column) {
if (!ParserSupport.isBoolean(column)) {
constFlag.set(false);
}
}
@Override
public void visit(SubSelect subSelect) {
constFlag.set(false);
}
@Override
public void visit(CaseExpression expr) {
constFlag.set(false);
}
@Override
public void visit(WhenClause expr) {
constFlag.set(false);
}
@Override
public void visit(ExistsExpression expr) {
constFlag.set(false);
}
@Override
public void visit(AnyComparisonExpression expr) {
constFlag.set(false);
}
@Override
public void visit(Concat expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(Matches expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(BitwiseAnd expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(BitwiseOr expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(BitwiseXor expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(CastExpression expr) {
expr.getLeftExpression().accept(this);
}
@Override
public void visit(TryCastExpression expr) {
constFlag.set(false);
}
@Override
public void visit(Modulo expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(AnalyticExpression expr) {
constFlag.set(false);
}
@Override
public void visit(ExtractExpression expr) {
expr.getExpression().accept(this);
}
@Override
public void visit(IntervalExpression expr) {
constFlag.set(false);
}
@Override
public void visit(OracleHierarchicalExpression expr) {
constFlag.set(false);
}
@Override
public void visit(RegExpMatchOperator expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(ExpressionList expressionList) {
for (Expression expr : expressionList.getExpressions()) {
expr.accept(this);
}
}
@Override
public void visit(NamedExpressionList namedExpressionList) {
for (Expression expr : namedExpressionList.getExpressions()) {
expr.accept(this);
}
}
@Override
public void visit(MultiExpressionList multiExprList) {
for (ExpressionList list : multiExprList.getExpressionLists()) {
visit(list);
}
}
@Override
public void visit(NotExpression notExpr) {
notExpr.getExpression().accept(this);
}
@Override
public void visit(BitwiseRightShift expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(BitwiseLeftShift expr) {
visitBinaryExpression(expr);
}
protected void visitBinaryExpression(BinaryExpression expr) {
expr.getLeftExpression().accept(this);
expr.getRightExpression().accept(this);
}
@Override
public void visit(JsonExpression jsonExpr) {
jsonExpr.getExpression().accept(this);
}
@Override
public void visit(JsonOperator expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(RegExpMySQLOperator expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(UserVariable var) {
constFlag.set(false);
}
@Override
public void visit(NumericBind bind) {
constFlag.set(false);
}
@Override
public void visit(KeepExpression expr) {
for (OrderByElement element : expr.getOrderByElements()) {
element.getExpression().accept(this);
}
}
@Override
public void visit(MySQLGroupConcat groupConcat) {
constFlag.set(false);
}
@Override
public void visit(ValueListExpression valueListExpression) {
for (Expression expr : valueListExpression.getExpressionList().getExpressions()) {
expr.accept(this);
}
}
@Override
public void visit(AllColumns allColumns) {
}
@Override
public void visit(AllTableColumns allTableColumns) {
}
@Override
public void visit(AllValue allValue) {
}
@Override
public void visit(IsDistinctExpression isDistinctExpression) {
visitBinaryExpression(isDistinctExpression);
}
@Override
public void visit(RowGetExpression rowGetExpression) {
rowGetExpression.getExpression().accept(this);
}
@Override
public void visit(HexValue hexValue) {
}
@Override
public void visit(OracleHint hint) {
}
@Override
public void visit(TimeKeyExpression timeKeyExpression) {
}
@Override
public void visit(DateTimeLiteralExpression literal) {
}
@Override
public void visit(NextValExpression nextVal) {
constFlag.set(false);
}
@Override
public void visit(CollateExpression col) {
constFlag.set(false);
}
@Override
public void visit(SimilarToExpression expr) {
visitBinaryExpression(expr);
}
@Override
public void visit(ArrayExpression array) {
array.getObjExpression().accept(this);
if (array.getIndexExpression() != null) {
array.getIndexExpression().accept(this);
}
if (array.getStartIndexExpression() != null) {
array.getStartIndexExpression().accept(this);
}
if (array.getStopIndexExpression() != null) {
array.getStopIndexExpression().accept(this);
}
}
@Override
public void visit(ArrayConstructor aThis) {
for (Expression expression : aThis.getExpressions()) {
expression.accept(this);
}
}
@Override
public void visit(VariableAssignment var) {
constFlag.set(false);
}
@Override
public void visit(XMLSerializeExpr expr) {
constFlag.set(false);
}
@Override
public void visit(TimezoneExpression expr) {
expr.getLeftExpression().accept(this);
}
@Override
public void visit(JsonAggregateFunction expression) {
Expression expr = expression.getExpression();
if (expr != null) {
expr.accept(this);
}
expr = expression.getFilterExpression();
if (expr != null) {
expr.accept(this);
}
}
@Override
public void visit(JsonFunction expression) {
for (JsonFunctionExpression expr : expression.getExpressions()) {
expr.getExpression().accept(this);
}
}
@Override
public void visit(ConnectByRootOperator connectByRootOperator) {
constFlag.set(false);
}
@Override
public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) {
constFlag.set(false);
}
@Override
public void visit(GeometryDistance geometryDistance) {
visitBinaryExpression(geometryDistance);
}
@Override
public void visit(RowConstructor rowConstructor) {
constFlag.set(false);
}
public boolean isConstExpression(Expression expression) {
if (null != expression) {
constFlag.set(true);
expression.accept(this);
return constFlag.get();
}
return false;
}
}

View File

@ -0,0 +1,177 @@
package org.jeecg.common.util.sqlInjection.parse;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.*;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Throwables;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.exception.JeecgSqlInjectionException;
/**
* 解析sql支持
*/
@Slf4j
public class ParserSupport {
/**
* 解析SELECT SQL语句,解析失败或非SELECT语句则抛出异常
*
* @param sql
* @return
*/
public static Select parseSelect(String sql) {
Statement stmt;
try {
stmt = CCJSqlParserUtil.parse(checkNotNull(sql, "sql is null"));
} catch (JSQLParserException e) {
throw new JeecgBootException(e);
}
checkArgument(stmt instanceof Select, "%s is not SELECT statment", sql);
Select select = (Select) stmt;
SelectBody selectBody = select.getSelectBody();
// 暂时只支持简单的SELECT xxxx FROM ....语句不支持复杂语句如WITH
checkArgument(selectBody instanceof PlainSelect, "ONLY SUPPORT plain select statement %s", sql);
return (Select) stmt;
}
/**
* 解析SELECT SQL语句,解析失败或非SELECT语句则
*
* @param sql
* @return
*/
public static Select parseSelectUnchecked(String sql) {
try {
return parseSelect(sql);
} catch (Exception e) {
return null;
}
}
/**
* 实现SQL语句解析,解析成功则返回解析后的{@link Statement}
* 并通过{@code visitor}参数提供基于AST(抽象语法树)的遍历所有节点的能力。
*
* @param sql SQL语句
* @param visitor 遍历所有节点的{@link SimpleNodeVisitor}接口实例,为{@code null}忽略
* @param sqlSyntaxNormalizer SQL语句分析转换器为{@code null}忽略
* @throws JSQLParserException 输入的SQL语句有语法错误
* @see #parse0(String, CCJSqlParserVisitor, SqlSyntaxNormalizer)
*/
public static Statement parse(String sql, CCJSqlParserVisitor visitor, SqlSyntaxNormalizer sqlSyntaxNormalizer) throws JSQLParserException {
return parse0(sql, visitor, sqlSyntaxNormalizer).statement;
}
/**
* 参照{@link CCJSqlParserUtil#parseAST(String)}和{@link CCJSqlParserUtil#parse(String)}实现SQL语句解析,
* 解析成功则返回解析后的{@link SqlParserInfo}对象,
* 并通过{@code visitor}参数提供基于AST(抽象语法树)的遍历所有节点的能力。
*
* @param sql SQL语句
* @param visitor 遍历所有节点的{@link SimpleNodeVisitor}接口实例,为{@code null}忽略
* @param sqlSyntaxAnalyzer SQL语句分析转换器为{@code null}忽略
* @throws JSQLParserException 输入的SQL语句有语法错误
* @see net.sf.jsqlparser.parser.Node#jjtAccept(SimpleNodeVisitor, Object)
*/
public static SqlParserInfo parse0(String sql, CCJSqlParserVisitor visitor, SqlSyntaxNormalizer sqlSyntaxAnalyzer) throws JeecgSqlInjectionException {
//检查是否非select开头暂不支持
if(!sql.toLowerCase().trim().startsWith("select ")) {
log.warn("传入sql 非select开头不支持非select开头的语句解析");
return null;
}
//检查是否存储过程,暂不支持
if(sql.toLowerCase().trim().startsWith("call ")){
log.warn("传入call 开头存储过程,不支持存储过程解析!");
return null;
}
//检查特殊语义的特殊字符,目前检查冒号、$、#三种特殊语义字符
String specialCharacters = "[:$#]";
Pattern pattern = Pattern.compile(specialCharacters);
Matcher matcher = pattern.matcher(sql);
if (matcher.find()) {
sql = sql.replaceAll("[:$#]", "@");
}
checkArgument(null != sql, "sql is null");
boolean allowComplexParsing = CCJSqlParserUtil.getNestingDepth(sql) <= CCJSqlParserUtil.ALLOWED_NESTING_DEPTH;
CCJSqlParser parser = CCJSqlParserUtil.newParser(sql).withAllowComplexParsing(allowComplexParsing);
Statement stmt;
try {
stmt = parser.Statement();
} catch (Exception ex) {
log.error("请注意SQL语法可能存在问题---> {}", ex.getMessage());
throw new JeecgSqlInjectionException("请注意SQL语法可能存在问题:"+sql);
}
if (null != visitor) {
parser.getASTRoot().jjtAccept(visitor, null);
}
if (null != sqlSyntaxAnalyzer) {
stmt.accept(sqlSyntaxAnalyzer.resetChanged());
}
return new SqlParserInfo(stmt.toString(), stmt, (SimpleNode) parser.getASTRoot());
}
/**
* 调用{@link CCJSqlParser}解析SQL语句部件返回解析生成的对象,如{@code 'ORDER BY id DESC'}
*
* @param <T>
* @param input
* @param method 指定调用的{@link CCJSqlParser}解析方法
* @param targetType 返回的解析对象类型
* @return
* @since 3.18.3
*/
public static <T> T parseComponent(String input, String method, Class<T> targetType) {
try {
CCJSqlParser parser = new CCJSqlParser(new StringProvider(input));
try {
return checkNotNull(targetType, "targetType is null").cast(parser.getClass().getMethod(method).invoke(parser));
} catch (InvocationTargetException e) {
Throwables.throwIfUnchecked(e.getTargetException());
throw new RuntimeException(e.getTargetException());
}
} catch (IllegalAccessException | NoSuchMethodException | SecurityException e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
/**
* 如果{@link Column}没有定义table,且字段名为true/false(不区分大小写)则视为布尔常量
*
* @param column
*/
public static boolean isBoolean(Column column) {
return null != column && null == column.getTable() &&
Pattern.compile("(true|false)", Pattern.CASE_INSENSITIVE).matcher(column.getColumnName()).matches();
}
public static class SqlParserInfo {
public String nativeSql;
public Statement statement;
public SimpleNode simpleNode;
SqlParserInfo(String nativeSql, Statement statement, SimpleNode simpleNode) {
this.nativeSql = nativeSql;
this.statement = statement;
this.simpleNode = simpleNode;
}
}
}

View File

@ -0,0 +1,37 @@
package org.jeecg.common.util.sqlInjection.parse;
import net.sf.jsqlparser.util.TablesNamesFinder;
/**
* SQL语句分析转换器基类<br>
* 基于SQL语法对象实现对SQL的修改
* (暂时用不到)
*
* @author guyadong
* @since 3.17.0
*/
public class SqlSyntaxNormalizer extends TablesNamesFinder {
protected static final ThreadLocal<Boolean> changed = new ThreadLocal<>();
public SqlSyntaxNormalizer() {
super();
init(true);
}
/**
* 语句改变返回{@code true},否则返回{@code false}
*/
public boolean changed() {
return Boolean.TRUE.equals(changed.get());
}
/**
* 复位线程局部变量{@link #changed}状态
*/
public SqlSyntaxNormalizer resetChanged() {
changed.remove();
return this;
}
}

View File

@ -0,0 +1,255 @@
package org.jeecg.common.util.sqlparse;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 解析所有表名和字段的类
*/
@Slf4j
public class JSqlParserAllTableManager {
private final String sql;
private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
/**
* 别名对应实际表名
*/
private final Map<String, String> tableAliasMap = new HashMap<>();
/**
* 解析后的sql
*/
private String parsedSql = null;
JSqlParserAllTableManager(String selectSql) {
this.sql = selectSql;
}
/**
* 开始解析
*
* @return
* @throws JSQLParserException
*/
public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
// 1. 创建解析器
CCJSqlParserManager mgr = new CCJSqlParserManager();
// 2. 使用解析器解析sql生成具有层次结构的java类
Statement stmt = mgr.parse(new StringReader(this.sql));
if (stmt instanceof Select) {
Select selectStatement = (Select) stmt;
SelectBody selectBody = selectStatement.getSelectBody();
this.parsedSql = selectBody.toString();
// 3. 解析select查询sql的信息
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
// 4. 合并 fromItems
List<FromItem> fromItems = new ArrayList<>();
fromItems.add(plainSelect.getFromItem());
// 4.1 处理join的表
List<Join> joins = plainSelect.getJoins();
if (joins != null) {
joins.forEach(join -> fromItems.add(join.getRightItem()));
}
// 5. 处理 fromItems
for (FromItem fromItem : fromItems) {
// 5.1 通过表名的方式from
if (fromItem instanceof Table) {
this.addSqlInfoByTable((Table) fromItem);
}
// 5.2 通过子查询的方式from
else if (fromItem instanceof SubSelect) {
this.handleSubSelect((SubSelect) fromItem);
}
}
// 6. 解析 selectFields
List<SelectItem> selectItems = plainSelect.getSelectItems();
for (SelectItem selectItem : selectItems) {
// 6.1 查询的是全部字段
if (selectItem instanceof AllColumns) {
// 当 selectItem 为 AllColumns 时fromItem 必定为 Table
String tableName = plainSelect.getFromItem(Table.class).getName();
// 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
assert sqlInfo != null;
// 设置为查询全部字段
sqlInfo.setSelectAll(true);
sqlInfo.setSelectFields(null);
sqlInfo.setRealSelectFields(null);
}
// 6.2 查询的是带表别名( u.* )的全部字段
else if (selectItem instanceof AllTableColumns) {
AllTableColumns allTableColumns = (AllTableColumns) selectItem;
String aliasName = allTableColumns.getTable().getName();
// 通过别名获取表名
String tableName = this.tableAliasMap.get(aliasName);
if (tableName == null) {
tableName = aliasName;
}
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
// 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
if (sqlInfo != null) {
// 设置为查询全部字段
sqlInfo.setSelectAll(true);
sqlInfo.setSelectFields(null);
sqlInfo.setRealSelectFields(null);
}
}
// 6.3 各种字段表达式处理
else if (selectItem instanceof SelectExpressionItem) {
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
Expression expression = selectExpressionItem.getExpression();
Alias alias = selectExpressionItem.getAlias();
this.handleExpression(expression, alias, plainSelect.getFromItem());
}
}
} else {
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
}
} else {
// 非 select 查询sql不做处理
throw new JeecgBootException("非 select 查询sql不做处理");
}
return this.allTableMap;
}
/**
* 处理子查询
*
* @param subSelect
*/
private void handleSubSelect(SubSelect subSelect) {
try {
String subSelectSql = subSelect.getSelectBody().toString();
// 递归调用解析
Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
if (map != null) {
this.assignMap(map);
}
} catch (Exception e) {
log.error("解析子查询出错", e);
}
}
/**
* 处理查询字段表达式
*
* @param expression
*/
private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
// 处理函数式字段 CONCAT(name,'(',age,')')
if (expression instanceof Function) {
Function functionExp = (Function) expression;
List<Expression> expressions = functionExp.getParameters().getExpressions();
for (Expression expItem : expressions) {
this.handleExpression(expItem, null, fromItem);
}
return;
}
// 处理字段上的子查询
if (expression instanceof SubSelect) {
this.handleSubSelect((SubSelect) expression);
return;
}
// 不处理字面量
if (expression instanceof StringValue ||
expression instanceof NullValue ||
expression instanceof LongValue ||
expression instanceof DoubleValue ||
expression instanceof HexValue ||
expression instanceof DateValue ||
expression instanceof TimestampValue ||
expression instanceof TimeValue
) {
return;
}
// 处理字段
if (expression instanceof Column) {
Column column = (Column) expression;
// 查询字段名
String fieldName = column.getColumnName();
String aliasName = fieldName;
if (alias != null) {
aliasName = alias.getName();
}
String tableName;
if (column.getTable() != null) {
// 通过列的表名获取 sqlInfo
// 例如 user.name这里的 tableName 就是 user
tableName = column.getTable().getName();
// 有可能是别名,需要转换为真实表名
if (this.tableAliasMap.get(tableName) != null) {
tableName = this.tableAliasMap.get(tableName);
}
} else {
// 当column的table为空时说明是 fromItem 中的字段
tableName = ((Table) fromItem).getName();
}
SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
if ($sqlInfo != null) {
$sqlInfo.addSelectField(aliasName, fieldName);
} else {
log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
}
}
}
/**
* 根据表名添加sqlInfo
*
* @param table
*/
private void addSqlInfoByTable(Table table) {
String tableName = table.getName();
// 解析 aliasName
if (table.getAlias() != null) {
this.tableAliasMap.put(table.getAlias().getName(), tableName);
}
SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
sqlInfo.setFromTableName(table.getName());
this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
}
/**
* 合并map
*
* @param source
*/
private void assignMap(Map<String, SelectSqlInfo> source) {
for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
if (sqlInfo == null) {
this.allTableMap.put(entry.getKey(), entry.getValue());
} else {
// 合并
if (sqlInfo.getSelectFields() == null) {
sqlInfo.setSelectFields(entry.getValue().getSelectFields());
} else {
sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
}
if (sqlInfo.getRealSelectFields() == null) {
sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
} else {
sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
}
}
}
}
}

View File

@ -0,0 +1,184 @@
package org.jeecg.common.util.sqlparse;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
@Slf4j
public class JSqlParserUtils {
/**
* 解析 查询selectsql的信息
* 此方法会展开所有子查询到一个map里
* key只存真实的表名如果查询的没有真实的表名则会被忽略。
* value只存真实的字段名如果查询的没有真实的字段名则会被忽略。
* <p>
* 例如SELECT a.*,d.age,(SELECT count(1) FROM sys_depart) AS count FROM (SELECT username AS foo, realname FROM sys_user) a, demo d
* 解析后的结果为:{sys_user=[username, realname], demo=[age], sys_depart=[]}
*
* @param selectSql
* @return
*/
public static Map<String, SelectSqlInfo> parseAllSelectTable(String selectSql) throws JSQLParserException {
if (oConvertUtils.isEmpty(selectSql)) {
return null;
}
// log.info("解析查询Sql{}", selectSql);
JSqlParserAllTableManager allTableManager = new JSqlParserAllTableManager(selectSql);
return allTableManager.parse();
}
/**
* 解析 查询selectsql的信息子查询嵌套
*
* @param selectSql
* @return
*/
public static SelectSqlInfo parseSelectSqlInfo(String selectSql) throws JSQLParserException {
if (oConvertUtils.isEmpty(selectSql)) {
return null;
}
// log.info("解析查询Sql{}", selectSql);
// 使用 JSqlParer 解析sql
// 1、创建解析器
CCJSqlParserManager mgr = new CCJSqlParserManager();
// 2、使用解析器解析sql生成具有层次结构的java类
Statement stmt = mgr.parse(new StringReader(selectSql));
if (stmt instanceof Select) {
Select selectStatement = (Select) stmt;
// 3、解析select查询sql的信息
return JSqlParserUtils.parseBySelectBody(selectStatement.getSelectBody());
} else {
// 非 select 查询sql不做处理
throw new JeecgBootException("非 select 查询sql不做处理");
}
}
/**
* 解析 select 查询sql的信息
*
* @param selectBody
* @return
*/
private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
// 简单的select查询
if (selectBody instanceof PlainSelect) {
SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
PlainSelect plainSelect = (PlainSelect) selectBody;
FromItem fromItem = plainSelect.getFromItem();
// 解析 aliasName
if (fromItem.getAlias() != null) {
sqlInfo.setFromTableAliasName(fromItem.getAlias().getName());
}
// 解析 表名
if (fromItem instanceof Table) {
// 通过表名的方式from
Table fromTable = (Table) fromItem;
sqlInfo.setFromTableName(fromTable.getName());
} else if (fromItem instanceof SubSelect) {
// 通过子查询的方式from
SubSelect fromSubSelect = (SubSelect) fromItem;
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(fromSubSelect.getSelectBody());
sqlInfo.setFromSubSelect(subSqlInfo);
}
// 解析 selectFields
List<SelectItem> selectItems = plainSelect.getSelectItems();
for (SelectItem selectItem : selectItems) {
if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
// 全部字段
sqlInfo.setSelectAll(true);
sqlInfo.setSelectFields(null);
sqlInfo.setRealSelectFields(null);
break;
} else if (selectItem instanceof SelectExpressionItem) {
// 获取单个查询字段名
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
Expression expression = selectExpressionItem.getExpression();
Alias alias = selectExpressionItem.getAlias();
JSqlParserUtils.handleExpression(sqlInfo, expression, alias);
}
}
return sqlInfo;
} else {
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
}
}
/**
* 处理查询字段表达式
*
* @param sqlInfo
* @param expression
* @param alias 是否有别名无传null
*/
private static void handleExpression(SelectSqlInfo sqlInfo, Expression expression, Alias alias) {
// 处理函数式字段 CONCAT(name,'(',age,')')
if (expression instanceof Function) {
JSqlParserUtils.handleFunctionExpression((Function) expression, sqlInfo);
return;
}
// 处理字段上的子查询
if (expression instanceof SubSelect) {
SubSelect subSelect = (SubSelect) expression;
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(subSelect.getSelectBody());
// 注:字段上的子查询,必须只查询一个字段,否则会报错,所以可以放心合并
sqlInfo.getSelectFields().addAll(subSqlInfo.getSelectFields());
sqlInfo.getRealSelectFields().addAll(subSqlInfo.getAllRealSelectFields());
return;
}
// 不处理字面量
if (expression instanceof StringValue ||
expression instanceof NullValue ||
expression instanceof LongValue ||
expression instanceof DoubleValue ||
expression instanceof HexValue ||
expression instanceof DateValue ||
expression instanceof TimestampValue ||
expression instanceof TimeValue
) {
return;
}
// 查询字段名
String selectField = expression.toString();
// 实际查询字段名
String realSelectField = selectField;
// 判断是否有别名
if (alias != null) {
selectField = alias.getName();
}
// 获取真实字段名
if (expression instanceof Column) {
Column column = (Column) expression;
realSelectField = column.getColumnName();
}
sqlInfo.addSelectField(selectField, realSelectField);
}
/**
* 处理函数式字段
*
* @param functionExp
* @param sqlInfo
*/
private static void handleFunctionExpression(Function functionExp, SelectSqlInfo sqlInfo) {
List<Expression> expressions = functionExp.getParameters().getExpressions();
for (Expression expression : expressions) {
JSqlParserUtils.handleExpression(sqlInfo, expression, null);
}
}
}

View File

@ -0,0 +1,101 @@
package org.jeecg.common.util.sqlparse.vo;
import lombok.Data;
import net.sf.jsqlparser.statement.select.SelectBody;
import java.util.HashSet;
import java.util.Set;
/**
* select 查询 sql 的信息
*/
@Data
public class SelectSqlInfo {
/**
* 查询的表名如果是子查询则此处为null
*/
private String fromTableName;
/**
* 表别名
*/
private String fromTableAliasName;
/**
* 通过子查询获取的表信息例如select name from (select * from user) u
* 如果不是子查询则为null
*/
private SelectSqlInfo fromSubSelect;
/**
* 查询的字段集合,如果是 * 则为null如果设了别名则为别名
*/
private Set<String> selectFields;
/**
* 真实的查询字段集合,如果是 * 则为null如果设了别名则为原始字段名
*/
private Set<String> realSelectFields;
/**
* 是否是查询所有字段
*/
private boolean selectAll;
/**
* 解析之后的 SQL (关键字都是大写)
*/
private final String parsedSql;
public SelectSqlInfo(String parsedSql) {
this.parsedSql = parsedSql;
}
public SelectSqlInfo(SelectBody selectBody) {
this.parsedSql = selectBody.toString();
}
public void addSelectField(String selectField, String realSelectField) {
if (this.selectFields == null) {
this.selectFields = new HashSet<>();
}
if (this.realSelectFields == null) {
this.realSelectFields = new HashSet<>();
}
this.selectFields.add(selectField);
this.realSelectFields.add(realSelectField);
}
/**
* 获取所有字段,包括子查询里的。
*
* @return
*/
public Set<String> getAllRealSelectFields() {
Set<String> fields = new HashSet<>();
// 递归获取所有字段,起个直观的方法名为:
this.recursiveGetAllFields(this, fields);
return fields;
}
/**
* 递归获取所有字段
*/
private void recursiveGetAllFields(SelectSqlInfo sqlInfo, Set<String> fields) {
if (!sqlInfo.isSelectAll() && sqlInfo.getRealSelectFields() != null) {
fields.addAll(sqlInfo.getRealSelectFields());
}
if (sqlInfo.getFromSubSelect() != null) {
recursiveGetAllFields(sqlInfo.getFromSubSelect(), fields);
}
}
@Override
public String toString() {
return "SelectSqlInfo{" +
"fromTableName='" + fromTableName + '\'' +
", fromSubSelect=" + fromSubSelect +
", aliasName='" + fromTableAliasName + '\'' +
", selectFields=" + selectFields +
", realSelectFields=" + realSelectFields +
", selectAll=" + selectAll +
"}";
}
}

View File

@ -59,7 +59,9 @@ public class AutoPoiDictConfig implements AutoPoiDictServiceI {
for (DictModel t : dictList) { for (DictModel t : dictList) {
if(t!=null){ //update-begin---author:liusq Date:20230517 for[issues/4917]excel 导出异常---
if(t!=null && t.getText()!=null && t.getValue()!=null){
//update-end---author:liusq Date:20230517 for[issues/4917]excel 导出异常---
//update-begin---author:scott Date:20211220 for[issues/I4MBB3]@Excel dicText字段的值有下划线时导入功能不能正确解析--- //update-begin---author:scott Date:20211220 for[issues/I4MBB3]@Excel dicText字段的值有下划线时导入功能不能正确解析---
if(t.getValue().contains(EXCEL_SPLIT_TAG)){ if(t.getValue().contains(EXCEL_SPLIT_TAG)){
String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG); String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG);

View File

@ -0,0 +1,49 @@
package org.jeecg.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import java.util.HashMap;
import java.util.Map;
/**
* 启动程序修改DruidWallConfig配置
* 允许SELECT语句的WHERE子句是一个永真条件
* @author eightmonth@qq.com
* @date 2024/4/8 11:37
*/
public class DruidWallConfigRegister implements SpringApplicationRunListener {
public SpringApplication application;
private String[] args;
/**
* 必备,否则启动报错
* @param application
* @param args
*/
public DruidWallConfigRegister(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
ConfigurableEnvironment env = context.getEnvironment();
Map<String, Object> props = new HashMap<>();
props.put("spring.datasource.dynamic.druid.wall.selectWhereAlwayTrueCheck", false);
MutablePropertySources propertySources = env.getPropertySources();
PropertySource<Map<String, Object>> propertySource = new MapPropertySource("jeecg-datasource-config", props);
propertySources.addLast(propertySource);
}
}

View File

@ -1,9 +1,6 @@
package org.jeecg.config; package org.jeecg.config;
import org.jeecg.config.vo.DomainUrl; import org.jeecg.config.vo.*;
import org.jeecg.config.vo.Elasticsearch;
import org.jeecg.config.vo.Path;
import org.jeecg.config.vo.Shiro;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -29,10 +26,12 @@ public class JeecgBaseConfig {
* 本地local\Miniominio\阿里云alioss * 本地local\Miniominio\阿里云alioss
*/ */
private String uploadType; private String uploadType;
/** /**
* 是否启用安全模式 * 平台安全模式配置
*/ */
private Boolean safeMode = false; private Firewall firewall;
/** /**
* shiro拦截排除 * shiro拦截排除
*/ */
@ -58,6 +57,13 @@ public class JeecgBaseConfig {
*/ */
private Elasticsearch elasticsearch; private Elasticsearch elasticsearch;
/**
* 微信支付
* @return
*/
private WeiXinPay weiXinPay;
public Elasticsearch getElasticsearch() { public Elasticsearch getElasticsearch() {
return elasticsearch; return elasticsearch;
} }
@ -66,12 +72,12 @@ public class JeecgBaseConfig {
this.elasticsearch = elasticsearch; this.elasticsearch = elasticsearch;
} }
public Boolean getSafeMode() { public Firewall getFirewall() {
return safeMode; return firewall;
} }
public void setSafeMode(Boolean safeMode) { public void setFirewall(Firewall firewall) {
this.safeMode = safeMode; this.firewall = firewall;
} }
public String getSignatureSecret() { public String getSignatureSecret() {
@ -129,4 +135,13 @@ public class JeecgBaseConfig {
public void setUploadType(String uploadType) { public void setUploadType(String uploadType) {
this.uploadType = uploadType; this.uploadType = uploadType;
} }
public WeiXinPay getWeiXinPay() {
return weiXinPay;
}
public void setWeiXinPay(WeiXinPay weiXinPay) {
this.weiXinPay = weiXinPay;
}
} }

View File

@ -1,7 +1,6 @@
package org.jeecg.config; package org.jeecg.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -19,15 +18,13 @@ import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ModelRef; import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.*; import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,8 +36,7 @@ import java.util.stream.Collectors;
* @Author scott * @Author scott
*/ */
@Configuration @Configuration
@EnableSwagger2 //开启 Swagger2 @EnableSwagger2WebMvc
@EnableKnife4j //开启 knife4j可以不写
@Import(BeanValidatorPluginsConfiguration.class) @Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Config implements WebMvcConfigurer { public class Swagger2Config implements WebMvcConfigurer {
@ -116,7 +112,7 @@ public class Swagger2Config implements WebMvcConfigurer {
// 描述 // 描述
.description("后台API接口") .description("后台API接口")
// 作者 // 作者
.contact(new Contact("北京敲敲云科技有限公司","www.jeccg.com","jeecgos@163.com")) .contact(new Contact("北京国炬信息技术有限公司","www.jeccg.com","jeecgos@163.com"))
.license("The Apache License, Version 2.0") .license("The Apache License, Version 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.build(); .build();
@ -152,7 +148,7 @@ public class Swagger2Config implements WebMvcConfigurer {
@Override @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { if (bean instanceof WebMvcRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
} }
return bean; return bean;

View File

@ -31,7 +31,7 @@ public class WebSocketConfig {
FilterRegistrationBean bean = new FilterRegistrationBean(); FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(websocketFilter()); bean.setFilter(websocketFilter());
//TODO 临时注释掉测试下线上socket总断的问题 //TODO 临时注释掉测试下线上socket总断的问题
bean.addUrlPatterns("/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*"); bean.addUrlPatterns("/taskCountSocket/*", "/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
return bean; return bean;
} }

View File

@ -0,0 +1,38 @@
package org.jeecg.config.firewall.SqlInjection;
/**
* 字典表查询 :: 白名单配置
*
* @Author taoYan
* @Date 2022/3/17 11:21
**/
public interface IDictTableWhiteListHandler {
/**
* 校验【表名】【字段】是否合法允许查询,允许则返回 true
*
* @param sql
* @return
*/
boolean isPassBySql(String sql);
/**
* 校验字典是否通过
*
* @param dictCodeString 字典表配置
* @return
*/
boolean isPassByDict(String dictCodeString);
boolean isPassByDict(String tableName, String... fields);
/**
* 清空缓存,使更改生效
*
* @return
*/
boolean clear();
String getErrorMsg();
}

View File

@ -0,0 +1,102 @@
package org.jeecg.config.firewall.SqlInjection;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.oConvertUtils;
import java.util.HashSet;
import java.util.Set;
/**
* 查询的表的信息
*/
@Slf4j
public class SysDictTableWhite {
//表名
private String name;
//表的别名
private String alias;
// 字段名集合
private Set<String> fields;
// 是否查询所有字段
private boolean all;
public SysDictTableWhite() {
}
public SysDictTableWhite(String name, String alias) {
this.name = name;
this.alias = alias;
this.all = false;
this.fields = new HashSet<>();
}
public void addField(String field) {
this.fields.add(field);
}
public String getName() {
return name;
}
public Set<String> getFields() {
return new HashSet<>(fields);
}
public void setName(String name) {
this.name = name;
}
public void setFields(Set<String> fields) {
this.fields = fields;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public boolean isAll() {
return all;
}
public void setAll(boolean all) {
this.all = all;
}
/**
* 判断是否有相同字段
*
* @param fieldControlString
* @return
*/
public boolean isAllFieldsValid(String fieldControlString) {
//如果白名单中没有配置字段则返回false
String[] controlFields = fieldControlString.split(",");
if (oConvertUtils.isEmpty(fieldControlString)) {
return false;
}
for (String queryField : fields) {
if (oConvertUtils.isIn(queryField, controlFields)) {
log.warn("字典表白名单校验,表【" + name + "】中字段【" + queryField + "】无权限查询");
return false;
}
}
return true;
}
@Override
public String toString() {
return "QueryTable{" +
"name='" + name + '\'' +
", alias='" + alias + '\'' +
", fields=" + fields +
", all=" + all +
'}';
}
}

View File

@ -0,0 +1,19 @@
package org.jeecg.config.firewall.interceptor;
import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class LowCodeModeConfiguration implements WebMvcConfigurer {
public LowCodeModeInterceptor payInterceptor() {
return new LowCodeModeInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(payInterceptor()).addPathPatterns(LowCodeUrlsEnum.getLowCodeInterceptUrls());
}
}

View File

@ -0,0 +1,113 @@
package org.jeecg.config.firewall.interceptor;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
/**
* 低代码模式dev:开发模式prod:发布模式——关闭所有在线开发配置能力)
* <p>
* prod开启后会关闭以下功能只保留功能测试拥有admin角色账号可以使用配置能力
* 1.online表单的所有配置功能代码生成和导入表功能
* 2.online报表的所有配置功能和sql解析
* 3.online图表的所有配置功能和sql解析
* 4.仪表盘的在线配置功能和sql解析
* 5.大屏的在线配置功能和sql解析
*
* 积木的逻辑单独处理
* 1.积木报表的在线配置功能和sql解析
*
* @author qinfeng
* @date 20230904
*/
@Slf4j
public class LowCodeModeInterceptor implements HandlerInterceptor {
/**
* 低代码开发模式
*/
public static final String LOW_CODE_MODE_DEV = "dev";
public static final String LOW_CODE_MODE_PROD = "prod";
@Resource
private JeecgBaseConfig jeecgBaseConfig;
@Autowired
private CommonAPI commonAPI;
/**
* 在请求处理之前进行调用
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//1、验证是否开启低代码开发模式控制
if (jeecgBaseConfig == null) {
jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
}
if (jeecgBaseConfig.getFirewall()!=null && LowCodeModeInterceptor.LOW_CODE_MODE_PROD.equals(jeecgBaseConfig.getFirewall().getLowCodeMode())) {
String requestURI = request.getRequestURI().substring(request.getContextPath().length());
log.info("低代码模式,拦截请求路径:" + requestURI);
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
Set<String> hasRoles = null;
if (loginUser == null) {
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
//当前登录人拥有的角色
hasRoles = commonAPI.queryUserRoles(loginUser.getUsername());
}
log.info("get loginUser info: {}", loginUser);
log.info("get loginRoles info: {}", hasRoles != null ? hasRoles.toArray() : "");
//拥有的角色 和 允许开发角色存在交集
boolean hasIntersection = CommonUtils.hasIntersection(hasRoles, CommonConstant.allowDevRoles);
//如果是超级管理员 或者 允许开发的角色,则不做限制
if (loginUser!=null && ("admin".equals(loginUser.getUsername()) || hasIntersection)) {
return true;
}
this.returnErrorMessage(response);
return false;
}
return true;
}
/**
* 返回结果
*
* @param response
*/
private void returnErrorMessage(HttpServletResponse response) {
//校验失败返回前端
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = null;
try {
out = response.getWriter();
Result<?> result = Result.error("低代码开发模式为发布模式,不允许使用在线配置!!");
out.print(JSON.toJSON(result));
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,104 @@
package org.jeecg.config.firewall.interceptor.enums;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* @author: qinfeng
* @date: 2023/09/04 11:44
*/
public enum LowCodeUrlsEnum {
/**
* online表单配置请求 TODO 增改删
*/
NEW_LOW_APP_ADD_URL("/online/cgform/api/addAll", "添加online表单"),
NEW_LOW_APP_EDIT_URL("/online/cgform/api/editAll", "编辑online表单"),
ONLINE_DB_SYNC("/online/cgform/api/doDbSynch/**/**", "online表单同步数据库"),
ONLINE_DEL_BATCH("/online/cgform/head/deleteBatch", "online表单批量删除"),
ONLINE_DELETE("/online/cgform/head/delete", "online表单删除"),
ONLINE_REMOVE("/online/cgform/head/removeRecord", "online表单移除"),
ONLINE_COPY("/online/cgform/head/copyOnline", "online表单生成视图"),
ONLINE_TABLE("/online/cgform/head/copyOnlineTable", "online表单复制表"),
ONLINE_BUTTON_AI_TEST("/online/cgform/button/aitest", "online表单自定义按钮生成数据"),
ONLINE_BUTTON_ADD("/online/cgform/button/add", "online表单自定义按钮新增"),
ONLINE_BUTTON_EDIT("/online/cgform/button/edit", "online表单自定义按钮编辑"),
ONLINE_BUTTON_DEL("/online/cgform/button/deleteBatch", "online表单自定义按钮删除"),
ONLINE_ENHANCE_JS("/online/cgform/head/enhanceJs/**", "online表单JS增强"),
ONLINE_ENHANCE_JAVA("/online/cgform/head/enhanceJava/**", "online表单JAVA增强"),
/**
* online报表配置请求
*/
ONLINE_CG_REPORT_ADD("/online/cgreport/head/add", "online报表新增"),
ONLINE_CG_REPORT_EDIT("/online/cgreport/head/editAll", "online报表编辑"),
ONLINE_CG_REPORT_DEL("/online/cgreport/head/delete", "online报表删除"),
ONLINE_CG_REPORT_PARSE_SQL("/online/cgreport/head/parseSql", "online报表SQL解析"),
/**
* online图表配置请求
*/
ONLINE_GRAPH_REPORT_ADD("/online/graphreport/head/add", "online图表新增"),
ONLINE_GRAPH_REPORT_EDIT("/online/graphreport/head/edit", "online图表编辑"),
ONLINE_GRAPH_REPORT_DEL("/online/graphreport/head/deleteBatch", "online图表删除"),
ONLINE_GRAPH_REPORT_PARSE_SQL("/online/cgreport/head/parseSql", "online图表解析SQL"),
/**
* 大屏配置请求
*/
BIG_SCREEN_DB_ADD("/bigscreen/bigScreenDb/add", "大屏数据源新增"),
BIG_SCREEN_DB_EDIT("/bigscreen/bigScreenDb/edit", "大屏数据源编辑"),
BIG_SCREEN_DB_DEL("/bigscreen/bigScreenDb/delete", "大屏数据源删除"),
BIG_SCREEN_DB_TEST_CONNECTION("/bigscreen/bigScreenDb/testConnection", "大屏数据源连接测试"),
// BIG_SCREEN_SAVE("/bigscreen/visual/save", "大屏新增"),
// BIG_SCREEN_EDIT("/bigscreen/visual/update", "大屏编辑"),
// BIG_SCREEN_COPY("/bigscreen/visual/copy", "大屏复制"),
// BIG_SCREEN_REMOVE("/bigscreen/visual/remove", "大屏移除"),
// BIG_SCREEN_DEL("/bigscreen/visual/deleteById", "大屏删除"),
/**
* 仪表盘配置请求
*/
DRAG_DB_ADD("/drag/onlDragDataSource/add", "仪表盘数据源新增"),
DRAG_DB_TEST_CONNECTION("/drag/onlDragDataSource/testConnection", "仪表盘数据源连接测试"),
DRAG_PARSE_SQL("/drag/onlDragDatasetHead/queryFieldBySql", "仪表盘数据集SQL解析"),
DRAG_DATASET_ADD("/drag/onlDragDatasetHead/add", "仪表盘数据集新增");
/**
* 其他配置请求
*/
private String url;
private String title;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
LowCodeUrlsEnum(String url, String title) {
this.url = url;
this.title = title;
}
/**
* 根据code获取可用的数量
*
* @return
*/
public static List<String> getLowCodeInterceptUrls() {
return Arrays.stream(LowCodeUrlsEnum.values()).map(LowCodeUrlsEnum::getUrl).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,16 @@
package org.jeecg.config.shiro;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 免认证注解认证系统结合spring MVC的@RequestMapping获取请求路径进行免登录配置
* @author eightmonth@qq.com
* @date 2024/2/28 9:58
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreAuth {
}

View File

@ -1,5 +1,6 @@
package org.jeecg.config.shiro; package org.jeecg.config.shiro;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
@ -9,10 +10,7 @@ import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.IRedisManager; import org.crazycake.shiro.*;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisClusterManager;
import org.crazycake.shiro.RedisManager;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.JeecgBaseConfig; import org.jeecg.config.JeecgBaseConfig;
@ -20,17 +18,26 @@ import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
import org.jeecg.config.shiro.filters.JwtFilter; import org.jeecg.config.shiro.filters.JwtFilter;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Configuration; import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.DependsOn; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.filter.DelegatingFilterProxy;
import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisCluster;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.DispatcherType;
import javax.servlet.Filter; import javax.servlet.Filter;
import java.lang.reflect.Method;
import java.util.*; import java.util.*;
/** /**
@ -49,7 +56,8 @@ public class ShiroConfig {
private Environment env; private Environment env;
@Resource @Resource
private JeecgBaseConfig jeecgBaseConfig; private JeecgBaseConfig jeecgBaseConfig;
@Autowired(required = false)
private RedisProperties redisProperties;
/** /**
* Filter Chain定义说明 * Filter Chain定义说明
* *
@ -74,6 +82,7 @@ public class ShiroConfig {
} }
} }
} }
// 配置不会被拦截的链接 顺序判断 // 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录 filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除 filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
@ -92,6 +101,9 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/auth/2step-code", "anon");//登录验证码 filterChainDefinitionMap.put("/auth/2step-code", "anon");//登录验证码
filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token
filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf预览 filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf预览
//filterChainDefinitionMap.put("/sys/common/view/**", "anon");//图片预览不限制token
//filterChainDefinitionMap.put("/sys/common/download/**", "anon");//文件下载不限制token
filterChainDefinitionMap.put("/generic/**", "anon");//pdf预览需要文件 filterChainDefinitionMap.put("/generic/**", "anon");//pdf预览需要文件
filterChainDefinitionMap.put("/sys/getLoginQrcode/**", "anon"); //登录二维码 filterChainDefinitionMap.put("/sys/getLoginQrcode/**", "anon"); //登录二维码
@ -99,6 +111,7 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除 filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
//update-begin--Author:scott Date:20221116 for排除静态资源后缀
filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/doc.html", "anon"); filterChainDefinitionMap.put("/doc.html", "anon");
filterChainDefinitionMap.put("/**/*.js", "anon"); filterChainDefinitionMap.put("/**/*.js", "anon");
@ -113,24 +126,33 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/**/*.ttf", "anon"); filterChainDefinitionMap.put("/**/*.ttf", "anon");
filterChainDefinitionMap.put("/**/*.woff", "anon"); filterChainDefinitionMap.put("/**/*.woff", "anon");
filterChainDefinitionMap.put("/**/*.woff2", "anon"); filterChainDefinitionMap.put("/**/*.woff2", "anon");
//update-end--Author:scott Date:20221116 for排除静态资源后缀
filterChainDefinitionMap.put("/druid/**", "anon"); filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon"); filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger**/**", "anon"); filterChainDefinitionMap.put("/swagger**/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon"); filterChainDefinitionMap.put("/v2/**", "anon");
// update-begin--Author:sunjianlei Date:20210510 for排除消息通告查看详情页面用于第三方APP
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon"); filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
// update-end--Author:sunjianlei Date:20210510 for排除消息通告查看详情页面用于第三方APP
//积木报表排除 //积木报表排除
filterChainDefinitionMap.put("/jmreport/**", "anon"); filterChainDefinitionMap.put("/jmreport/**", "anon");
filterChainDefinitionMap.put("/**/*.js.map", "anon"); filterChainDefinitionMap.put("/**/*.js.map", "anon");
filterChainDefinitionMap.put("/**/*.css.map", "anon"); filterChainDefinitionMap.put("/**/*.css.map", "anon");
//拖拽仪表盘设计器排除
filterChainDefinitionMap.put("/drag/view", "anon");
filterChainDefinitionMap.put("/drag/page/queryById", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getAllChartData", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalData", "anon");
filterChainDefinitionMap.put("/drag/mock/json/**", "anon");
//大屏模板例子 //大屏模板例子
filterChainDefinitionMap.put("/test/bigScreen/**", "anon"); filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon"); filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon"); filterChainDefinitionMap.put("/bigscreen/template2/**", "anon");
//filterChainDefinitionMap.put("/test/jeecgDemo/rabbitMqClientTest/**", "anon"); //MQ测试 //filterChainDefinitionMap.put("/test/jeecgDemo/rabbitMqClientTest/**", "anon"); //MQ测试
//filterChainDefinitionMap.put("/test/jeecgDemo/html", "anon"); //模板页面 //filterChainDefinitionMap.put("/test/jeecgDemo/html", "anon"); //模板页面
//filterChainDefinitionMap.put("/test/jeecgDemo/redis/**", "anon"); //redis测试 //filterChainDefinitionMap.put("/test/jeecgDemo/redis/**", "anon"); //redis测试
@ -145,10 +167,10 @@ public class ShiroConfig {
//测试模块排除 //测试模块排除
filterChainDefinitionMap.put("/test/seata/**", "anon"); filterChainDefinitionMap.put("/test/seata/**", "anon");
// update-begin--author:liusq Date:20230522 for[issues/4829]访问不存在的url时会提示Token失效请重新登录呢
//错误路径排除 //错误路径排除
filterChainDefinitionMap.put("/error", "anon"); filterChainDefinitionMap.put("/error", "anon");
// update-end--author:liusq Date:20230522 for[issues/4829]访问不存在的url时会提示Token失效请重新登录呢 // 企业微信证书排除
filterChainDefinitionMap.put("/WW_verify*", "anon");
// 添加自己的过滤器并且取名为jwt // 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new HashMap<String, Filter>(1); Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
@ -166,6 +188,20 @@ public class ShiroConfig {
return shiroFilterFactoryBean; return shiroFilterFactoryBean;
} }
//update-begin---author:chenrui ---date:20240126 for【QQYUN-7932】AI助手------------
@Bean
public FilterRegistrationBean shiroFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new DelegatingFilterProxy("shiroFilterFactoryBean"));
registration.setEnabled(true);
registration.addUrlPatterns("/*");
//支持异步
registration.setAsyncSupported(true);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
return registration;
}
//update-end---author:chenrui ---date:20240126 for【QQYUN-7932】AI助手------------
@Bean("securityManager") @Bean("securityManager")
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
@ -243,11 +279,24 @@ public class ShiroConfig {
public IRedisManager redisManager() { public IRedisManager redisManager() {
log.info("===============(2)创建RedisManager,连接Redis.."); log.info("===============(2)创建RedisManager,连接Redis..");
IRedisManager manager; IRedisManager manager;
// sentinel cluster redis【issues/5569】shiro集成 redis 不支持 sentinel 方式部署的redis集群 #5569
if (Objects.nonNull(redisProperties)
&& Objects.nonNull(redisProperties.getSentinel())
&& !CollectionUtils.isEmpty(redisProperties.getSentinel().getNodes())) {
RedisSentinelManager sentinelManager = new RedisSentinelManager();
sentinelManager.setMasterName(redisProperties.getSentinel().getMaster());
sentinelManager.setHost(String.join(",", redisProperties.getSentinel().getNodes()));
sentinelManager.setPassword(redisProperties.getSentinel().getPassword());
sentinelManager.setDatabase(redisProperties.getDatabase());
return sentinelManager;
}
// redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com // redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) { if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
RedisManager redisManager = new RedisManager(); RedisManager redisManager = new RedisManager();
redisManager.setHost(lettuceConnectionFactory.getHostName()); redisManager.setHost(lettuceConnectionFactory.getHostName() + ":" + lettuceConnectionFactory.getPort());
redisManager.setPort(lettuceConnectionFactory.getPort()); //(lettuceConnectionFactory.getPort());
redisManager.setDatabase(lettuceConnectionFactory.getDatabase()); redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
redisManager.setTimeout(0); redisManager.setTimeout(0);
if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) { if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {

View File

@ -62,9 +62,11 @@ public class ShiroRealm extends AuthorizingRealm {
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.debug("===============Shiro权限认证开始============ [ roles、permissions]=========="); log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
String username = null; String username = null;
String userId = null;
if (principals != null) { if (principals != null) {
LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal(); LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
username = sysUser.getUsername(); username = sysUser.getUsername();
userId = sysUser.getId();
} }
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
@ -74,7 +76,7 @@ public class ShiroRealm extends AuthorizingRealm {
info.setRoles(roleSet); info.setRoles(roleSet);
// 设置用户拥有的权限集合比如“sys:role:add,sys:user:add” // 设置用户拥有的权限集合比如“sys:role:add,sys:user:add”
Set<String> permissionSet = commonApi.queryUserAuths(username); Set<String> permissionSet = commonApi.queryUserAuths(userId);
info.addStringPermissions(permissionSet); info.addStringPermissions(permissionSet);
//System.out.println(permissionSet); //System.out.println(permissionSet);
log.info("===============Shiro权限认证成功=============="); log.info("===============Shiro权限认证成功==============");

View File

@ -8,6 +8,7 @@ import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil; import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.shiro.JwtToken; import org.jeecg.config.shiro.JwtToken;
import org.jeecg.config.shiro.ignore.InMemoryIgnoreAuth;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -47,6 +48,10 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
@Override @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try { try {
// 判断当前路径是不是注解了@IngoreAuth路径如果是则放开验证
if (InMemoryIgnoreAuth.contains(((HttpServletRequest) request).getServletPath())) {
return true;
}
executeLogin(request, response); executeLogin(request, response);
return true; return true;
} catch (Exception e) { } catch (Exception e) {

View File

@ -0,0 +1,104 @@
package org.jeecg.config.shiro.ignore;
import lombok.AllArgsConstructor;
import org.jeecg.config.shiro.IgnoreAuth;
import org.springframework.aop.framework.Advised;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.*;
/**
* 在spring boot初始化时根据@RestController注解获取当前spring容器中的bean
* @author eightmonth@qq.com
* @date 2024/4/18 11:35
*/
@Component
@AllArgsConstructor
public class IgnoreAuthPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
private ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
List<String> ignoreAuthUrls = new ArrayList<>();
if (event.getApplicationContext().getParent() == null) {
// 只处理根应用上下文的事件,避免在子上下文中重复处理
Map<String, Object> restControllers = applicationContext.getBeansWithAnnotation(RestController.class);
for (Object restController : restControllers.values()) {
// 如 online系统的controller并不是spring 默认生成
if (restController instanceof Advised) {
ignoreAuthUrls.addAll(postProcessRestController(restController));
}
}
}
if (!CollectionUtils.isEmpty(ignoreAuthUrls)) {
InMemoryIgnoreAuth.set(ignoreAuthUrls);
}
}
private List<String> postProcessRestController(Object restController) {
List<String> ignoreAuthUrls = new ArrayList<>();
Class<?> clazz = ((Advised) restController).getTargetClass();
RequestMapping base = clazz.getAnnotation(RequestMapping.class);
String[] baseUrl = Objects.nonNull(base) ? base.value() : new String[]{};
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String[] uri = requestMapping.value();
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(GetMapping.class)) {
GetMapping requestMapping = method.getAnnotation(GetMapping.class);
String[] uri = requestMapping.value();
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PostMapping.class)) {
PostMapping requestMapping = method.getAnnotation(PostMapping.class);
String[] uri = requestMapping.value();
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PutMapping.class)) {
PutMapping requestMapping = method.getAnnotation(PutMapping.class);
String[] uri = requestMapping.value();
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(DeleteMapping.class)) {
DeleteMapping requestMapping = method.getAnnotation(DeleteMapping.class);
String[] uri = requestMapping.value();
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PatchMapping.class)) {
PatchMapping requestMapping = method.getAnnotation(PatchMapping.class);
String[] uri = requestMapping.value();
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
}
}
return ignoreAuthUrls;
}
private List<String> rebuildUrl(String[] bases, String[] uris) {
List<String> urls = new ArrayList<>();
if (bases.length > 0) {
for (String base : bases) {
for (String uri : uris) {
urls.add(prefix(base) + prefix(uri));
}
}
} else {
Arrays.stream(uris).forEach(uri -> {
urls.add(prefix(uri));
});
}
return urls;
}
private String prefix(String seg) {
return seg.startsWith("/") ? seg : "/"+seg;
}
}

View File

@ -0,0 +1,38 @@
package org.jeecg.config.shiro.ignore;
import java.util.ArrayList;
import java.util.List;
/**
* 使用内存存储通过@IgnoreAuth注解的url配合JwtFilter进行免登录校验
* PS无法使用ThreadLocal进行存储因为ThreadLocal装载时JwtFilter已经初始化完毕导致该类获取ThreadLocal为空
* @author eightmonth@qq.com
* @date 2024/4/18 15:02
*/
public class InMemoryIgnoreAuth {
private static final List<String> IGNORE_AUTH_LIST = new ArrayList<>();
public InMemoryIgnoreAuth() {}
public static void set(List<String> list) {
IGNORE_AUTH_LIST.addAll(list);
}
public static List<String> get() {
return IGNORE_AUTH_LIST;
}
public static void clear() {
IGNORE_AUTH_LIST.clear();
}
public static boolean contains(String url) {
for (String ignoreAuth : IGNORE_AUTH_LIST) {
if (url.endsWith(ignoreAuth)) {
return true;
}
}
return false;
}
}

View File

@ -42,7 +42,7 @@ public class SignAuthInterceptor implements HandlerInterceptor {
String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP); String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP);
if(oConvertUtils.isEmpty(xTimestamp)){ if(oConvertUtils.isEmpty(xTimestamp)){
Result<?> result = Result.error("Sign签名校验失败"); Result<?> result = Result.error("Sign签名校验失败,时间戳为空");
log.error("Sign 签名校验失败Header xTimestamp 为空"); log.error("Sign 签名校验失败Header xTimestamp 为空");
//校验失败返回前端 //校验失败返回前端
response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");
@ -79,6 +79,7 @@ public class SignAuthInterceptor implements HandlerInterceptor {
log.debug("Sign 签名通过Header Sign : {}",headerSign); log.debug("Sign 签名通过Header Sign : {}",headerSign);
return true; return true;
} else { } else {
log.info("sign allParams: {}", allParams);
log.error("request URI = " + request.getRequestURI()); log.error("request URI = " + request.getRequestURI());
log.error("Sign 签名校验失败Header Sign : {}",headerSign); log.error("Sign 签名校验失败Header Sign : {}",headerSign);
//校验失败返回前端 //校验失败返回前端

View File

@ -43,6 +43,16 @@ public class HttpUtils {
if (pathVariable.contains(SymbolConstant.COMMA)) { if (pathVariable.contains(SymbolConstant.COMMA)) {
log.info(" pathVariable: {}",pathVariable); log.info(" pathVariable: {}",pathVariable);
String deString = URLDecoder.decode(pathVariable, "UTF-8"); String deString = URLDecoder.decode(pathVariable, "UTF-8");
//https://www.52dianzi.com/category/article/37/565371.html
if(deString.contains("%")){
try {
deString = URLDecoder.decode(deString, "UTF-8");
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
} catch (Exception e) {
//e.printStackTrace();
}
}
log.info(" pathVariable decode: {}",deString); log.info(" pathVariable decode: {}",deString);
result.put(SignUtil.X_PATH_VARIABLE, deString); result.put(SignUtil.X_PATH_VARIABLE, deString);
} }
@ -81,6 +91,12 @@ public class HttpUtils {
if (pathVariable.contains(SymbolConstant.COMMA)) { if (pathVariable.contains(SymbolConstant.COMMA)) {
log.info(" pathVariable: {}",pathVariable); log.info(" pathVariable: {}",pathVariable);
String deString = URLDecoder.decode(pathVariable, "UTF-8"); String deString = URLDecoder.decode(pathVariable, "UTF-8");
//https://www.52dianzi.com/category/article/37/565371.html
if(deString.contains("%")){
deString = URLDecoder.decode(deString, "UTF-8");
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
}
log.info(" pathVariable decode: {}",deString); log.info(" pathVariable decode: {}",deString);
result.put(SignUtil.X_PATH_VARIABLE, deString); result.put(SignUtil.X_PATH_VARIABLE, deString);
} }
@ -156,7 +172,11 @@ public class HttpUtils {
String[] params = param.split("&"); String[] params = param.split("&");
for (String s : params) { for (String s : params) {
int index = s.indexOf("="); int index = s.indexOf("=");
result.put(s.substring(0, index), s.substring(index + 1)); //update-begin---author:chenrui ---date:20240222 for[issues/5879]数据查询传ds=“”造成的异常------------
if (index != -1) {
result.put(s.substring(0, index), s.substring(index + 1));
}
//update-end---author:chenrui ---date:20240222 for[issues/5879]数据查询传ds=“”造成的异常------------
} }
return result; return result;
} }
@ -180,7 +200,11 @@ public class HttpUtils {
String[] params = param.split("&"); String[] params = param.split("&");
for (String s : params) { for (String s : params) {
int index = s.indexOf("="); int index = s.indexOf("=");
result.put(s.substring(0, index), s.substring(index + 1)); //update-begin---author:chenrui ---date:20240222 for[issues/5879]数据查询传ds=“”造成的异常------------
if (index != -1) {
result.put(s.substring(0, index), s.substring(index + 1));
}
//update-end---author:chenrui ---date:20240222 for[issues/5879]数据查询传ds=“”造成的异常------------
} }
return result; return result;
} }

View File

@ -1,80 +0,0 @@
package org.jeecg.config.thirdapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* 第三方App对接配置
* @author: jeecg-boot
*/
@Configuration
public class ThirdAppConfig {
/**
* 钉钉
*/
public final static String DINGTALK = "DINGTALK";
/**
* 企业微信
*/
public final static String WECHAT_ENTERPRISE = "WECHAT_ENTERPRISE";
/**
* 是否启用 第三方App对接
*/
@Value("${third-app.enabled:false}")
private boolean enabled;
/**
* 系统类型目前支持WECHAT_ENTERPRISE企业微信DINGTALK (钉钉)
*/
@Autowired
private ThirdAppTypeConfig type;
public boolean isEnabled() {
return enabled;
}
public ThirdAppConfig setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
/**
* 获取企业微信配置
*/
public ThirdAppTypeItemVo getWechatEnterprise() {
return this.type.getWECHAT_ENTERPRISE();
}
/**
* 获取钉钉配置
*/
public ThirdAppTypeItemVo getDingtalk() {
return this.type.getDINGTALK();
}
/**
* 获取企业微信是否启用
*/
public boolean isWechatEnterpriseEnabled() {
try {
return this.enabled && this.getWechatEnterprise().isEnabled();
} catch (Exception e) {
return false;
}
}
/**
* 获取钉钉是否启用
*/
public boolean isDingtalkEnabled() {
try {
return this.enabled && this.getDingtalk().isEnabled();
} catch (Exception e) {
return false;
}
}
}

View File

@ -1,26 +0,0 @@
package org.jeecg.config.thirdapp;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 第三方APP配置
*
* @author sunjianlei
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "third-app.type")
public class ThirdAppTypeConfig {
/**
* 对应企业微信配置
*/
private ThirdAppTypeItemVo WECHAT_ENTERPRISE;
/**
* 对应钉钉配置
*/
private ThirdAppTypeItemVo DINGTALK;
}

View File

@ -1,37 +0,0 @@
package org.jeecg.config.thirdapp;
import lombok.Data;
/**
* 第三方App对接
* @author: jeecg-boot
*/
@Data
public class ThirdAppTypeItemVo {
/**
* 是否启用
*/
private boolean enabled;
/**
* 应用Key
*/
private String clientId;
/**
* 应用Secret
*/
private String clientSecret;
/**
* 应用ID
*/
private String agentId;
/**
* 目前仅企业微信用到自建应用Secret
*/
private String agentAppSecret;
public int getAgentIdInt() {
return Integer.parseInt(agentId);
}
}

View File

@ -0,0 +1,39 @@
package org.jeecg.config.vo;
/**
* 平台安全配置
*
* @author: scott
* @date: 2023年09月05日 9:25
*/
public class Firewall {
/**
* 数据源安全 (开启后Online报表和图表的数据源为必填)
*/
private Boolean dataSourceSafe = false;
/**
* 低代码模式dev:开发模式prod:发布模式——关闭所有在线开发配置能力)
*/
private String lowCodeMode;
// /**
// * 表字典安全模式white:白名单——配置了白名单的表才能通过表字典方式访问black:黑名单——配置了黑名单的表不允许表字典方式访问)
// */
// private String tableDictMode;
public Boolean getDataSourceSafe() {
return dataSourceSafe;
}
public void setDataSourceSafe(Boolean dataSourceSafe) {
this.dataSourceSafe = dataSourceSafe;
}
public String getLowCodeMode() {
return lowCodeMode;
}
public void setLowCodeMode(String lowCodeMode) {
this.lowCodeMode = lowCodeMode;
}
}

View File

@ -0,0 +1,31 @@
package org.jeecg.config.vo;
import lombok.Data;
@Data
public class WeiXinPay {
/**
* 微信公众号id
*/
private String appId;
/**
* 商户号id
*/
private String mchId;
/**
* 商户号秘钥
*/
private String apiKey;
/**
* 回调地址
*/
private String notifyUrl;
/**
* 是否开启会员认证
*/
private Boolean openVipLimit;
/**
* 证书路径
*/
private String certPath;
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.SpringApplicationRunListener=\
org.jeecg.config.DruidWallConfigRegister

View File

@ -0,0 +1,104 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<div class="box-content">
<div class="info-top">
<img src="https://www.jeecg.com/images/logo.png" style="float: left; margin: 0 10px 0 0; width: 32px;height:32px" /><div style="color:#fff"><strong>【重要】流程办理的通知</strong></div>
</div>
<div class="info-wrap">
<div class="tips" style="padding:15px;">
<p style="margin: 10px 0;">
您好,您有一个新的流程任务亟待处理,任务内容如下::
</p>
<table style="width: 400px; border-spacing: 0px; border-collapse: collapse; border: none; margin-top: 20px;"><tbody>
<tr style="height: 45px;">
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
流程名称
</td>
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
${bpm_name}<a style="color: #006eff;" href="${url}" target="_blank" rel="noopener">[立刻办理]</a>
</td>
</tr>
<tr style="height: 45px;">
<td style="width: 150px;height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
催办任务
</td>
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
${bpm_task}
</td>
</tr>
<tr style="height: 45px;">
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
催办时间
</td>
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
${datetime}
</td>
</tr>
<tr style="height: 45px;">
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
催办内容
</td>
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
${remark}
</td>
</tr>
</tbody>
</table>
</div>
<div class="footer">北京国炬平台</div>
</div>
<div style="margin-top: 60px;margin-bottom: 10px;">
<span style="font-size: 13px; font-weight: bold; color: #666;">温馨提醒</span>
<div style="line-height: 24px; margin-top: 10px;">
<div style="font-size: 13px; color: #666;">使用过程中如有任何问题,请联系系统管理员。</div>
</div>
</div>
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
<span class="appleLinks">Copyright © 2023-2024 北京国炬信息技术有限公司. 保留所有权利。</span>
</p>
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
</p>
</div>
</div>
</body>
<style>
.box-content{
width: 80%;
margin: 20px auto;
max-width: 800px;
min-width: 600px;
}
.info-top{
padding: 15px 25px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
background: #4ea3f2;
color: #fff;
overflow: hidden;
line-height: 32px;
}
.info-wrap{
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
border:1px solid #ddd;
overflow: hidden;
padding: 15px 15px 20px;
}
.footer{
text-align: right;
color: #999;
padding: 0 15px 15px;
}
</style>
</html>

View File

@ -0,0 +1,101 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<div class="box-content">
<div class="info-top">
<img src="https://jeecgdev.oss-cn-beijing.aliyuncs.com/temp/logo(1)_1697180761742.png"
style="float: left; margin: 0 10px 0 0; width: 32px;height:32px"/>
<div style="color:#fff"><strong>【重要】流程办理的通知</strong></div>
</div>
<div class="info-wrap">
<div class="tips" style="padding:15px;">
<p style="margin: 10px 0;">
您好, ${REALNAME}<br>您有一个新的流程任务需要处理,任务内容如下:
</p>
<table style="width: 400px; border-spacing: 0px; border-collapse: collapse; border: none; margin-top: 20px;">
<tbody>
<tr style="height: 45px;">
<td style="width: 150px;height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
业务标题
</td>
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
${title}
</td>
</tr>
<tr style="height: 45px;">
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
流程名称
</td>
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
${name}
<a style="color: #006eff;" href="${url}" target="_blank" rel="noopener">[立刻办理]</a>
</td>
</tr>
<tr style="height: 45px;">
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
任务节点
</td>
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
${task}
</td>
</tr>
</tbody>
</table>
</div>
<div class="footer">北京国炬平台</div>
</div>
<div style="margin-top: 60px;margin-bottom: 10px;">
<span style="font-size: 13px; font-weight: bold; color: #666;">温馨提醒</span>
<div style="line-height: 24px; margin-top: 10px;">
<div style="font-size: 13px; color: #666;">使用过程中如有任何问题,请联系系统管理员。</div>
</div>
</div>
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
<span class="appleLinks">Copyright © 2023-2024 北京国炬信息技术有限公司. 保留所有权利。</span>
</p>
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
</p>
</div>
</div>
</body>
<style>
.box-content {
width: 80%;
margin: 20px auto;
max-width: 800px;
min-width: 600px;
}
.info-top {
padding: 15px 25px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
background: #4ea3f2;
color: #fff;
overflow: hidden;
line-height: 32px;
}
.info-wrap {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
border: 1px solid #ddd;
overflow: hidden;
padding: 15px 15px 20px;
}
.footer {
text-align: right;
color: #999;
padding: 0 15px 15px;
}
</style>
</html>

View File

@ -0,0 +1,78 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<div class="box-content">
<div class="info-top">
<img src="https://qiaoqiaoyun.oss-cn-beijing.aliyuncs.com/site/qqyunemaillogo.png" style="width: 35px;height:35px; background: #5e8ee5; border-radius: 5px;" />
<div style="color:#fff;">
<strong>【重要】新数据提醒</strong>
</div>
</div>
<div class="info-wrap">
<div class="tips" style="padding:15px;">
<p style="margin: 10px 0;">
尊敬的 ${userName} 用户,您好:
</p>
你的表单 <a style="color: #006eff;" href="${formLink}" target="_blank" rel="noopener">【${formName}】</a>
在 ${createTime} 新增了1条数据。
${dataMarkdown}
<p>
如需查看更多请点击
<a style="color: #006eff;" href="${moreLink}" target="_blank" rel="noopener">[查看所有数据]</a>
</p>
</div>
<div class="footer">敲敲云平台</div>
<div class="footer" id="currentTime"></div>
</div>
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
<span class="appleLinks">Copyright © 2023-2024 北京敲敲云科技有限公司. 保留所有权利。</span>
</p>
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
</p>
</div>
</div>
</body>
<style>
.box-content{
width: 80%;
margin: 20px auto;
max-width: 800px;
min-width: 600px;
}
.info-top{
display: flex;
align-items: center;
padding: 15px 25px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
background: #4ea3f2;
color: #fff;
overflow: hidden;
line-height: 32px;
}
.info-wrap{
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
border:1px solid #ddd;
overflow: hidden;
padding: 15px 15px 20px;
}
.footer{
text-align: right;
color: #999;
padding: 0 15px 15px;
}
</style>
</html>

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