mirror of https://github.com/jeecgboot/jeecg-boot
commit
8f81e84c54
|
@ -1,21 +0,0 @@
|
|||
##### 版本号:
|
||||
|
||||
|
||||
##### 前端版本:vue3版?还是 vue2版?
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
##### 截图&代码:
|
||||
|
||||
|
||||
|
||||
|
||||
#### 友情提示(为了提高issue处理效率):
|
||||
- 未按格式要求发帖,会被直接删掉;
|
||||
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;
|
||||
- 请自己初判问题描述是否清楚,是否方便我们调查处理;
|
||||
- 针对问题请说明是Online在线功能(需说明用的主题模板),还是生成的代码功能;
|
||||
|
||||
|
|
@ -9,3 +9,7 @@ rebel.xml
|
|||
|
||||
## front
|
||||
**/*.lock
|
||||
os_del.cmd
|
||||
os_del_doc.cmd
|
||||
.svn
|
||||
derby.log
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -203,7 +203,7 @@
|
|||
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.允许基于本平台软件开展业务系统开发。
|
||||
|
|
175
README-EN.md
175
README-EN.md
|
@ -7,13 +7,13 @@
|
|||
JEECG BOOT Low Code Development Platform
|
||||
===============
|
||||
|
||||
The Latest Version: 3.5.0(Release date:2023-03-08)
|
||||
当前最新版本: 3.6.3(发布日期:2024-03-11)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://www.jeecg.com)
|
||||
[](http://www.jeecg.com)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
@ -44,10 +44,12 @@ Official Support: http://jeecg.com/doc/help
|
|||
|
||||
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
|
||||
- APP Supporting framework:https://github.com/jeecgboot/jeecg-uniapp
|
||||
项目源码
|
||||
-----------------------------------
|
||||
| Source |Front-end source (Vue3 version) | The background source |
|
||||
|-|-|-|
|
||||
| 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
|
||||
|
||||
|
@ -55,11 +57,9 @@ Download the source code
|
|||
|--------------------|------------------------|
|
||||
| `jeecg-boot` | SpringBoot background source code (support microservices) |
|
||||
| `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 |
|
||||
| `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](https://github.com/jeecgboot) |
|
||||
|
||||
| `jeecg-uniapp` | [APP development framework, a code multi terminal adaptation, and support APP, small program, H5](https://github.com/jeecgboot/jeecg-uniapp) |
|
||||
| `SpringBoot3+JDK17` | [BranchSourceCode](https://github.com/jeecgboot/jeecg-boot/tree/springboot3) [UpgradeBlog](https://blog.csdn.net/zhangdaiscott/article/details/134805602) |
|
||||
| `More` | [Download more source code](http://jeecg.com/download) |
|
||||
|
||||
|
||||
|
||||
|
@ -73,12 +73,10 @@ Jeecg-Boot low code development platform can be applied in the development of an
|
|||
Docker starts the project
|
||||
-----------------------------------
|
||||
|
||||
- [Docker starts the monomer background](http://doc.jeecg.com/2043889)
|
||||
- [Docker starts the Vue3 front-end](http://vue3.jeecg.com/3028878)
|
||||
|
||||
- [Docker starts the micro-service background](http://doc.jeecg.com/3043472)
|
||||
- [Docker starts the Vue2 front-end](http://doc.jeecg.com/3043612)
|
||||
|
||||
- [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 micro-service background](https://help.jeecg.com/java/springcloud/docker.html)
|
||||
- [ChatGPT AI Config](https://help.jeecg.com/java/chatgpt.html)
|
||||
|
||||
|
||||
|
||||
|
@ -86,21 +84,23 @@ Technical documentation
|
|||
-----------------------------------
|
||||
|
||||
- Website: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
|
||||
- Demo : [Vue3](http://boot3.jeecg.com) | [Vue2](http://boot.jeecg.com)
|
||||
|
||||
- Doc: [Vue3](http://vue3.jeecg.com) | [Main](http://doc.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)
|
||||
|
||||
- Microservice Development: [Monomer upgrade to microservice](http://doc.jeecg.com/3043471)
|
||||
|
||||
- QQ group : ⑥730954414、683903138、⑤860162132(full)、④774126647(full)、③816531124(full)、②769925425(full)、①284271917(full)
|
||||
- Microservice Development: [Monomer upgrade to microservice](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- QQ group : ⑧825232878、⑦791696430、⑥730954414(full)、683903138(full)、⑤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
|
||||
-----------------------------------
|
||||
|
||||
[](https://star-history.com/#jeecgboot/jeecg-boot)
|
||||
|
||||
|
@ -207,15 +207,15 @@ Technical Architecture:
|
|||
|
||||
#### backend
|
||||
|
||||
- Basic framework: Spring Boot 2.6.6
|
||||
- Basic framework: Spring Boot 2.6.14
|
||||
|
||||
- Microservice framework: Spring Cloud Alibaba 2021.0.1.0
|
||||
|
||||
- Persistence layer framework: MybatisPlus 3.5.1
|
||||
|
||||
- Report tool: JimuReport 1.5.2
|
||||
- Report tool: JimuReport 1.5.8
|
||||
|
||||
- Security framework: Apache Shiro 1.8.0, Jwt 3.11.0
|
||||
- Security framework: Apache Shiro 1.10.0, Jwt 3.11.0
|
||||
|
||||
- Microservice technology stack: Spring Cloud Alibaba, Nacos, Gateway, Sentinel, Skywalking
|
||||
|
||||
|
@ -263,7 +263,7 @@ Technical Architecture:
|
|||
|
||||
8. Service monitoring SpringBootAdmin√
|
||||
|
||||
9. link tracking Skywalking [reference document](http://doc.jeecg.com/2350293)
|
||||
9. link tracking Skywalking [reference document](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
|
||||
10. Messaging middleware RabbitMQ √
|
||||
|
||||
|
@ -437,51 +437,120 @@ Technical Architecture:
|
|||
|
||||
|
||||
|
||||
Effect of system
|
||||
----
|
||||
##### Screen template
|
||||

|
||||
|
||||

|
||||
|
||||
### 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.
|
||||

|
||||
|
||||
|
||||
##### PC
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### interactive
|
||||

|
||||
|
||||
##### Online interface documentation
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
##### Report
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
##### process Designer
|
||||

|
||||
|
||||
##### Process
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
##### App
|
||||

|
||||
|
||||
##### min process
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### dashboard Designer
|
||||

|
||||
|
||||

|
||||
|
||||
##### report Designer
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### form Designer
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### bigscreen Designer
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### uniapp
|
||||

|
||||
|
||||

|
||||
|
||||
##### low app
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### app
|
||||

|
||||

|
||||
|
||||
##### PAD
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
##### chart
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
##### swagger
|
||||

|
||||

|
||||
|
||||
|
||||
## donation
|
||||
|
||||
If so, buy the author a cup of coffee ☺
|
||||
|
|
264
README.md
264
README.md
|
@ -7,13 +7,13 @@
|
|||
JEECG BOOT 低代码开发平台
|
||||
===============
|
||||
|
||||
当前最新版本: 3.5.0(发布日期:2023-03-08)
|
||||
当前最新版本: 3.6.3(发布日期:2024-03-11)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://www.jeecg.com)
|
||||
[](http://jeecg.com/aboutusIndex)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
@ -33,93 +33,72 @@ JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零
|
|||
|
||||
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计(松耦合)、并支持任务节点灵活配置,既保证了公司流程的保密性,又减少了开发人员的工作量。
|
||||
|
||||
遇到技术问题,[请在这里反馈BUG](https://github.com/jeecgboot/jeecg-boot/issues/new)
|
||||
|
||||
源码下载
|
||||
适用项目
|
||||
-----------------------------------
|
||||
Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
||||
|
||||
| 源码 | 源码地址 |
|
||||
|--------------------|------------------------|
|
||||
| 后台源码 JAVA | https://github.com/jeecgboot/jeecg-boot |
|
||||
| 前端源码 `Vue3版` | https://github.com/jeecgboot/jeecgboot-vue3 |
|
||||
| APP配套框架 | https://github.com/jeecgboot/jeecg-uniapp |
|
||||
|
||||
##### 快速搭建开发环境
|
||||
|
||||
- [通过IDEA启动前后端项目](http://doc.jeecg.com/2043874)
|
||||
- [Vue3前端项目快速启动](http://vue3.jeecg.com/2398848)
|
||||
- [单体快速切换为微服务版](http://doc.jeecg.com/3043471)
|
||||
|
||||
##### 项目说明
|
||||
项目源码
|
||||
-----------------------------------
|
||||
| 仓库 |前端源码 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)
|
||||
|
||||
#### 项目说明
|
||||
|
||||
| 项目名 | 说明 |
|
||||
|--------------------|------------------------|
|
||||
| `jeecg-boot` | SpringBoot后台源码(支持微服务) |
|
||||
| `jeecgboot-vue3` | Vue3+TS 新版前端源码 |
|
||||
| `jeecg-uniapp` | APP开发框架,一份代码多终端适配,同时支持APP、小程序、H5 |
|
||||
| `jeecg-boot-starter` | [Stater依赖项目单独维护,点击下载](https://gitee.com/jeecg/jeecg-boot-starter) |
|
||||
| `更多开源插件` | [更多源码下载](https://github.com/jeecgboot) |
|
||||
| `jeecgboot-vue3` | 前端源码 (Vue3版本) |
|
||||
| `jeecg-boot` | 后端JAVA源码(支持微服务) |
|
||||
| `jeecg-uniapp` | [APP开发框架,一份代码多终端适配,同时支持APP、小程序、H5](https://github.com/jeecgboot/jeecg-uniapp) |
|
||||
| `更多开源项目` | [更多底层源码下载](http://jeecg.com/download) |
|
||||
|
||||
|
||||
|
||||
快速搭建开发环境
|
||||
-----------------------------------
|
||||
|
||||
- [通过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) | [敲敲云(零代码)](http://app.qiaoqiaoyun.com)
|
||||
|
||||
- 开发文档: [Vue3文档](http://vue3.jeecg.com) | [主项目文档](http://doc.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交流群 : ⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
|
||||
- 在线演示 : [Vue3演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [敲敲云零代码](https://qiaoqiaoyun.com)
|
||||
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
||||
>
|
||||
- QQ交流群 : ⑧825232878、⑦791696430(满)、⑥730954414(满)、683903138(满)、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
|
||||
> ` 提醒:【QQ群是自助服务群,建议给帮助您解决问题的同学发送指定红包,表示感谢!】 `
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Docker启动项目
|
||||
大龄码农的思考
|
||||
-----------------------------------
|
||||
> 作为码农年纪大了写不动代码了怎么办??哎!!
|
||||
所以我们团队在追求不写代码也可实现复杂业务系统!目前已经做到了,不信你到敲敲云零代码试试(通过流程串联修改业务数据)
|
||||
|
||||
- [Docker启动单体后台](http://doc.jeecg.com/2043889)
|
||||
- [Docker启动Vue3前端](http://vue3.jeecg.com/3028878)
|
||||
- [Docker启动微服务后台](http://doc.jeecg.com/3043472)
|
||||
|
||||
|
||||
************
|
||||
|
||||
VUE2版本专题说明
|
||||
-----------------------------------
|
||||
#### 项目介绍
|
||||
- 项目名称:ant-design-vue-jeecg
|
||||
- 说明: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)
|
||||
|
||||
|
||||
#### Vue2与Vue3版本区别
|
||||
> - VUE3版本彻底抛弃IE兼容,不兼容IE和低版本浏览器,只适配高版本谷歌和Edge
|
||||
(政府、事业类单位项目需要谨慎选择——国产化迁移是一个漫长的过程,万一过程中要求IE兼容,这个不可逆)
|
||||
> - 所以如果对浏览器有要求的项目,请选择VUE2版本。
|
||||
> - VUE3版是全新的技术栈,紧跟主流(前端重写),各个功能都做了优化,拥有更好的体验效果
|
||||
|
||||
|
||||
#### 源码下载
|
||||
| 源码 | 源码地址 |
|
||||
|--------------------|------------------------|
|
||||
| 后端源码 `Vue2版` |https://gitee.com/jeecg/jeecg-boot/tree/v3.4.3last |
|
||||
| 前端源码 `Vue2版` |https://gitee.com/jeecg/ant-design-vue-jeecg |
|
||||
|
||||
************
|
||||
|
||||
|
||||
适用项目
|
||||
-----------------------------------
|
||||
Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
||||
- https://www.qiaoqiaoyun.com
|
||||
|
||||
|
||||
技术支持
|
||||
|
@ -130,7 +109,34 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
官方支持: [http://jeecg.com/doc/help](http://jeecg.com/doc/help)
|
||||
|
||||
|
||||
##### Star走势图
|
||||
|
||||
|
||||
VUE2版本专题介绍
|
||||
-----------------------------------
|
||||
#### 项目介绍
|
||||
- 项目名称:ant-design-vue-jeecg
|
||||
- 说明:JeecgBoot前端提供两套解决方案,一套VUE2和一套VUE3版本,目前vue2版本最新代码只支持到jeecgboot 3.4.3版本,一定注意。
|
||||
|
||||
#### 源码下载
|
||||
| 源码 | 源码地址 |
|
||||
|--------------------|------------------------|
|
||||
| 后端JAVA源码 `Vue2版` |https://gitee.com/jeecg/jeecg-boot/tree/v3.4.3last |
|
||||
| 前端vue2源码 `Vue2版` |https://gitee.com/jeecg/ant-design-vue-jeecg |
|
||||
|
||||
#### Vue2与Vue3版本区别
|
||||
> - VUE3版本彻底抛弃IE兼容,不兼容IE和低版本浏览器,只适配高版本谷歌和Edge
|
||||
(政府、事业类单位项目需要谨慎选择——国产化迁移是一个漫长的过程,万一过程中要求IE兼容,这个不可逆)
|
||||
> - 所以如果对浏览器有要求的项目,请选择VUE2版本。
|
||||
> - VUE3版是全新的技术栈,紧跟主流(前端重写),各个功能都做了优化,拥有更好的体验效果
|
||||
|
||||
#### 技术文档
|
||||
- 在线演示:[Vue2版演示](http://boot.jeecg.com)
|
||||
- 开发文档:| [开发文档](http://doc.jeecg.com) | [Vue2前端快速启动](http://doc.jeecg.com/2678320) | [Vue2前端采用Docker启动](http://doc.jeecg.com/3043612)
|
||||
|
||||
|
||||
|
||||
Star走势图
|
||||
-----------------------------------
|
||||
|
||||
[](https://star-history.com/#jeecgboot/jeecg-boot)
|
||||
|
||||
|
@ -190,8 +196,8 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
* 17.支持SAAS服务模式,提供SaaS多租户架构方案。
|
||||
* 18.分布式文件服务,集成minio、阿里OSS等优秀的第三方,提供便捷的文件上传与管理,同时也支持本地存储。
|
||||
* 19.主流数据库兼容,一套代码完全兼容Mysql、Postgresql、Oracle、Sqlserver、MariaDB、达梦等主流数据库。
|
||||
* 20.集成工作流activiti、flowable,并实现了只需在页面配置流程转向,可极大的简化bpm工作流的开发;用bpm的流程设计器画出了流程走向,一个工作流基本就完成了,只需写很少量的java代码;
|
||||
* 21.低代码能力:在线流程设计,采用开源Activiti流程引擎,实现在线画流程,自定义表单,表单挂靠,业务流转
|
||||
* 20.集成工作流flowable、activiti,并实现了只需在页面配置流程转向,可极大的简化bpm工作流的开发;用bpm的流程设计器画出了流程走向,一个工作流基本就完成了,只需写很少量的java代码;
|
||||
* 21.低代码能力:在线流程设计,采用开源flowable、activiti流程引擎,实现在线画流程,自定义表单,表单挂靠,业务流转
|
||||
* 22.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
|
||||
* 23.提供单点登录CAS集成方案,项目中已经提供完善的对接代码
|
||||
* 24.低代码能力:表单设计器,支持用户自定义表单布局,支持单表,一对多表单、支持select、radio、checkbox、textarea、date、popup、列表、宏等控件
|
||||
|
@ -235,15 +241,15 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
|
||||
#### 后端
|
||||
|
||||
- 基础框架:Spring Boot 2.6.6
|
||||
- 基础框架:Spring Boot 2.6.14
|
||||
|
||||
- 微服务框架: Spring Cloud Alibaba 2021.0.1.0
|
||||
|
||||
- 持久层框架:MybatisPlus 3.5.1
|
||||
|
||||
- 报表工具: JimuReport 1.5.2
|
||||
- 报表工具: JimuReport 1.5.8
|
||||
|
||||
- 安全框架:Apache Shiro 1.8.0,Jwt 3.11.0
|
||||
- 安全框架:Apache Shiro 1.10.0,Jwt 3.11.0
|
||||
|
||||
- 微服务技术栈:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
|
||||
|
||||
|
@ -291,7 +297,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
|
||||
8、服务监控 SpringBootAdmin√
|
||||
|
||||
9、链路跟踪 Skywalking [参考文档](http://doc.jeecg.com/2350293)
|
||||
9、链路跟踪 Skywalking [参考文档](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
|
||||
10、消息中间件 RabbitMQ √
|
||||
|
||||
|
@ -438,7 +444,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
│ ├─数据报表设计
|
||||
│ ├─图形报表设计(支持echart)
|
||||
│ ├─大屏设计器(未开源)
|
||||
│─流程模块功能 (未开源)
|
||||
│─更多商业功能 (未开源)
|
||||
│ ├─流程设计器
|
||||
│ ├─表单设计器
|
||||
├─大屏设计器
|
||||
|
@ -461,43 +467,103 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
|
||||
```
|
||||
|
||||
### 流程引擎推荐
|
||||
|
||||
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助手对话界面。
|
||||

|
||||
|
||||

|
||||
|
||||
##### PC端
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 系统交互
|
||||

|
||||
|
||||
##### 在线接口文档
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
##### 报表
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
##### 流程设计
|
||||

|
||||
|
||||
##### 流程
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 简版流程设计
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 仪表盘设计器
|
||||

|
||||
|
||||

|
||||
|
||||
##### 报表设计器
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 表单设计器
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 大屏设计器
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### UNIAPP效果
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 零代码应用
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 手机端
|
||||

|
||||
|
@ -506,10 +572,20 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
##### PAD端
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
##### 图表示例
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
##### 在线接口文档
|
||||

|
||||

|
||||
## 捐赠
|
||||
|
||||
如果觉得还不错,请作者喝杯咖啡吧 ☺
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,37 @@
|
|||
CREATE VIEW all_invoices AS
|
||||
WITH shipping AS (
|
||||
SELECT
|
||||
s.id AS id,
|
||||
s.create_by AS create_by,
|
||||
s.create_time AS create_time,
|
||||
s.client_id AS client_id,
|
||||
s.currency_id AS currency_id,
|
||||
s.invoice_number AS invoice_number,
|
||||
IFNULL(s.total_amount + p.total_amount, s.total_amount) AS total_amount,
|
||||
IFNULL(s.discount_amount + p.discount_amount, s.discount_amount) AS discount_amount,
|
||||
IFNULL(s.final_amount + p.final_amount, s.final_amount) AS final_amount,
|
||||
IFNULL(s.paid_amount + p.paid_amount, s.paid_amount) AS paid_amount,
|
||||
IF(SUBSTRING(s.invoice_number,9,1) = '2', 'shipping', 'complete') AS 'type'
|
||||
FROM shipping_invoice s
|
||||
LEFT JOIN purchase_order p ON s.invoice_number = p.invoice_number
|
||||
AND s.client_id = p.client_id
|
||||
),
|
||||
purchase AS (
|
||||
SELECT
|
||||
p.id AS id,
|
||||
p.create_by AS create_by,
|
||||
p.create_time AS create_time,
|
||||
p.client_id AS client_id,
|
||||
p.currency_id AS currency_id,
|
||||
p.invoice_number AS invoice_number,
|
||||
p.total_amount AS total_amount,
|
||||
p.discount_amount AS discount_amount,
|
||||
p.final_amount AS final_amount,
|
||||
p.paid_amount AS paid_amount,
|
||||
IF(SUBSTRING(p.invoice_number,9,1) = '1', 'purchase', 'error') AS 'type'
|
||||
FROM purchase_order p
|
||||
WHERE p.invoice_number NOT IN (SELECT invoice_number FROM shipping_invoice)
|
||||
)
|
||||
SELECT s.* FROM shipping s
|
||||
UNION ALL SELECT p.* FROM purchase p
|
||||
ORDER BY create_time DESC;
|
|
@ -17,4 +17,13 @@ SELECT poc.sku_id AS sku_id, SUM(poc.quantity) AS quantity
|
|||
FROM platform_order_content poc
|
||||
JOIN platform_order po ON poc.platform_order_id = po.id
|
||||
WHERE po.order_time BETWEEN DATE_SUB(CURDATE(), INTERVAL 28 DAY) AND CURDATE()
|
||||
GROUP BY poc.sku_id;
|
||||
GROUP BY poc.sku_id;
|
||||
|
||||
create view sales_42 as
|
||||
select `poc`.`sku_id` AS `sku_id`, sum(`poc`.`quantity`) AS `quantity`
|
||||
from (`wia_app`.`platform_order_content` `poc`
|
||||
join `wia_app`.`platform_order` `po`
|
||||
on ((`poc`.`platform_order_id` = `po`.`id`)))
|
||||
where (`po`.`order_time` between (curdate() - interval 42 day) and curdate())
|
||||
and po.erp_status <> 5
|
||||
group by `poc`.`sku_id`;
|
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,5 +0,0 @@
|
|||
oracle导出编码: export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
|
||||
|
||||
导出用户: jeecgboot
|
||||
|
||||
导入命令: imp scott/tiger@orcl file=jeecgboot-oracle11g.dmp
|
|
@ -1,10 +0,0 @@
|
|||
版本升级方法?
|
||||
|
||||
JeecgBoot属于平台级产品,每次升级改动内容较多,目前做不到平滑升级。
|
||||
|
||||
这里给用户的升级建议是这样的:
|
||||
1.代码升级 => 本地版本通过svn或者git做好主干,在分支上做业务开发,jeecg每次版本发布,可以手工覆盖主干的代码,对比代码进行提交;
|
||||
2.数据库升级 => 针对数据库我们每次发布会提供增量升级SQL,可以通过增量SQL实现数据库的升级。
|
||||
3.兼容问题 => 每次版本发布会针对不兼容地方标注说明,需要手工修改不兼容的代码。
|
||||
|
||||
注意: 升级sql目前只提供mysql版本,执行完脚步后,新菜单需要手工进行角色授权,刷新首页才会出现。
|
|
@ -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.兼容问题
|
||||
每次发版,会针对不兼容地方重点说明。
|
|
@ -19,6 +19,8 @@ services:
|
|||
--default-authentication-plugin=caching_sha2_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-redis:
|
||||
image: redis:5.0
|
||||
|
@ -27,6 +29,8 @@ services:
|
|||
restart: always
|
||||
hostname: jeecg-boot-redis
|
||||
container_name: jeecg-boot-redis
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-system:
|
||||
build:
|
||||
|
@ -39,4 +43,10 @@ services:
|
|||
image: jeecg-boot-system
|
||||
hostname: jeecg-boot-system
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 8080:8080
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
networks:
|
||||
jeecg-boot:
|
||||
name: jeecg_boot
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<version>3.6.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
|
@ -82,7 +82,7 @@
|
|||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons.version}</version>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
|
@ -175,18 +175,27 @@
|
|||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- knife4j -->
|
||||
<dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成器 -->
|
||||
<!-- 如下载失败,请参考此文档 http://doc.jeecg.com/2043876 -->
|
||||
<!-- 如下载失败,请参考此文档 https://help.jeecg.com/java/setup/maven.html -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>codegenerate</artifactId>
|
||||
|
@ -252,6 +261,15 @@
|
|||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
</dependency>
|
||||
<!--加载hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -22,10 +22,10 @@ public interface CommonAPI {
|
|||
|
||||
/**
|
||||
* 2查询用户权限信息
|
||||
* @param username
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Set<String> queryUserAuths(String username);
|
||||
Set<String> queryUserAuths(String userId);
|
||||
|
||||
/**
|
||||
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel
|
||||
|
@ -102,12 +102,12 @@ public interface CommonAPI {
|
|||
|
||||
/**
|
||||
* 13获取表数据字典
|
||||
* @param table
|
||||
* @param tableFilterSql
|
||||
* @param text
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
List<DictModel> queryTableDictItemsByCode(String table, String text, String code);
|
||||
List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code);
|
||||
|
||||
/**
|
||||
* 14 普通字典的翻译,根据多个dictCode和多条数据,多个以逗号分割
|
||||
|
@ -117,14 +117,17 @@ public interface CommonAPI {
|
|||
*/
|
||||
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
||||
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
/**
|
||||
* 15 字典表的 翻译,可批量
|
||||
* @param table
|
||||
* @param text
|
||||
* @param code
|
||||
* @param keys 多个用逗号分割
|
||||
* @param dataSource 数据源
|
||||
* @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]解决分布式下表字典跨库无法查询问题------------
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ public class DataLogDTO {
|
|||
|
||||
private String type;
|
||||
|
||||
private String createName;
|
||||
|
||||
public DataLogDTO(){
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,13 @@ public class OnlineAuthDTO implements Serializable {
|
|||
*/
|
||||
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(){
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import lombok.Data;
|
|||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 普通消息
|
||||
|
@ -43,14 +43,7 @@ public class MessageDTO implements Serializable {
|
|||
* 消息类型 1:消息 2:系统消息
|
||||
*/
|
||||
protected String category;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
//update-begin---author:taoyan ---date:20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
||||
|
||||
/**
|
||||
* 模板消息对应的模板编码
|
||||
*/
|
||||
protected String templateCode;
|
||||
|
||||
/**
|
||||
* 消息类型:org.jeecg.common.constant.enums.MessageTypeEnum
|
||||
* XT("system", "系统消息")
|
||||
|
@ -60,23 +53,38 @@ public class MessageDTO implements Serializable {
|
|||
*/
|
||||
protected String type;
|
||||
|
||||
|
||||
//---【推送模板相关参数】-------------------------------------------------------------
|
||||
/**
|
||||
* 是否发送Markdown格式的消息
|
||||
*/
|
||||
protected boolean isMarkdown;
|
||||
|
||||
/**
|
||||
* 模板消息对应的模板编码
|
||||
*/
|
||||
protected String templateCode;
|
||||
/**
|
||||
* 解析模板内容 对应的数据
|
||||
*/
|
||||
protected Map<String, Object> data;
|
||||
//update-end---author:taoyan ---date::20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
//---【推送模板相关参数】-------------------------------------------------------------
|
||||
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
/**
|
||||
* 抄送人
|
||||
* 邮件抄送人
|
||||
*/
|
||||
private String copyToUser;
|
||||
|
||||
/**
|
||||
* 邮件推送地址
|
||||
*/
|
||||
protected Set<String> toEmailList;
|
||||
|
||||
/**
|
||||
* 邮件抄送地址
|
||||
*/
|
||||
protected Set<String> ccEmailList;
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
|
||||
public MessageDTO(){
|
||||
}
|
||||
|
|
|
@ -140,11 +140,15 @@ public class DictAspect {
|
|||
String code = field.getAnnotation(Dict.class).dicCode();
|
||||
String text = field.getAnnotation(Dict.class).dicText();
|
||||
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;
|
||||
String dictCode = code;
|
||||
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<>());
|
||||
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
||||
|
@ -169,10 +173,15 @@ public class DictAspect {
|
|||
String code = field.getAnnotation(Dict.class).dicCode();
|
||||
String text = field.getAnnotation(Dict.class).dicText();
|
||||
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;
|
||||
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());
|
||||
|
@ -274,9 +283,25 @@ public class DictAspect {
|
|||
String[] arr = dictCode.split(",");
|
||||
String table = arr[0], text = arr[1], code = arr[2];
|
||||
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.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);
|
||||
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||
list.addAll(texts);
|
||||
|
|
|
@ -59,8 +59,7 @@ public class PermissionDataAspect {
|
|||
requestPath = filterUrl(requestPath);
|
||||
//update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
||||
//先判断是否online报表请求
|
||||
// TODO 参数顺序调整有隐患
|
||||
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0){
|
||||
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
|
||||
// 获取地址栏参数
|
||||
String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR);
|
||||
if(oConvertUtils.isNotEmpty(urlParamString)){
|
||||
|
@ -68,7 +67,7 @@ public class PermissionDataAspect {
|
|||
}
|
||||
}
|
||||
//update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
||||
log.info("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
||||
log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
||||
String username = JwtUtil.getUserNameByToken(request);
|
||||
//查询数据权限信息
|
||||
//TODO 微服务情况下也得支持缓存机制
|
||||
|
|
|
@ -14,6 +14,8 @@ public enum UrlMatchEnum {
|
|||
CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"),
|
||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */
|
||||
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/ */
|
||||
CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"),
|
||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -39,4 +39,16 @@ public @interface Dict {
|
|||
* @return 返回类型: String
|
||||
*/
|
||||
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]解决分布式下表字典跨库无法查询问题------------
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ public interface CommonConstant {
|
|||
|
||||
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
|
||||
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) */
|
||||
Integer SC_OK_200 = 200;
|
||||
|
||||
|
@ -112,8 +114,8 @@ public interface CommonConstant {
|
|||
String HAS_CANCLE = "2";
|
||||
|
||||
/**阅读状态(0未读,1已读)*/
|
||||
String HAS_READ_FLAG = "1";
|
||||
String NO_READ_FLAG = "0";
|
||||
Integer HAS_READ_FLAG = 1;
|
||||
Integer NO_READ_FLAG = 0;
|
||||
|
||||
/**优先级(L低,M中,H高)*/
|
||||
String PRIORITY_L = "L";
|
||||
|
@ -160,6 +162,8 @@ public interface CommonConstant {
|
|||
|
||||
/**字典翻译文本后缀*/
|
||||
String DICT_TEXT_SUFFIX = "_dictText";
|
||||
/**字典翻译颜色后缀*/
|
||||
String DICT_COLOR_SUFFIX = "_dictColor";
|
||||
|
||||
/**
|
||||
* 表单设计器主表类型
|
||||
|
@ -315,6 +319,8 @@ public interface CommonConstant {
|
|||
String X_TIMESTAMP = "X-TIMESTAMP";
|
||||
/** 租户请求头 更名为:X-Tenant-Id */
|
||||
String TENANT_ID = "X-Tenant-Id";
|
||||
/** 简流接口请求头,用于排除不支持的控件字段 */
|
||||
String X_MiniFlowExclusionFieldMode = "X-Miniflowexclusionfieldmode";
|
||||
/**===============================================================================================*/
|
||||
|
||||
String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
|
||||
|
@ -371,6 +377,8 @@ public interface CommonConstant {
|
|||
/**前端vue3版本Header参数名*/
|
||||
String VERSION="X-Version";
|
||||
|
||||
String VERSION_V3 = "v3";
|
||||
|
||||
/**存储在线程变量里的动态表名*/
|
||||
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
|
||||
/**
|
||||
|
@ -388,6 +396,7 @@ public interface CommonConstant {
|
|||
/** 部门表唯一key,orgCode */
|
||||
String DEPART_KEY_ORG_CODE = "orgCode";
|
||||
|
||||
/**======【消息推送相关】==============================================================================*/
|
||||
/**
|
||||
* 发消息 会传递一些信息到map
|
||||
*/
|
||||
|
@ -398,6 +407,11 @@ public interface CommonConstant {
|
|||
*/
|
||||
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
|
||||
|
||||
/**
|
||||
* 发消息 消息业务类型
|
||||
*/
|
||||
String NOTICE_MSG_BUS_TYPE = "NOTICE_MSG_BUS_TYPE";
|
||||
|
||||
/**
|
||||
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
|
||||
*/
|
||||
|
@ -420,6 +434,7 @@ public interface CommonConstant {
|
|||
|
||||
/** 消息模板:markdown */
|
||||
String MSG_TEMPLATE_TYPE_MD = "5";
|
||||
/**========【消息推送相关】==========================================================================*/
|
||||
|
||||
/**
|
||||
* 短信验证码redis-key的前缀
|
||||
|
@ -481,6 +496,11 @@ public interface CommonConstant {
|
|||
*/
|
||||
String USER_TENANT_REFUSE = "4";
|
||||
|
||||
/**
|
||||
* 用户租户状态(邀请)
|
||||
*/
|
||||
String USER_TENANT_INVITE = "5";
|
||||
|
||||
/**
|
||||
* 不是叶子节点
|
||||
*/
|
||||
|
@ -490,4 +510,71 @@ public interface CommonConstant {
|
|||
* 是叶子节点
|
||||
*/
|
||||
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:积木报表常量----
|
||||
|
||||
}
|
||||
|
|
|
@ -28,12 +28,21 @@ public interface CommonSendStatus {
|
|||
public static final String APP_SESSION_SUFFIX = "_app";
|
||||
|
||||
|
||||
/**-----【流程相关通知模板code】------------------------------------------------------------*/
|
||||
/**流程催办——系统通知消息模板*/
|
||||
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_SYS_TS_NOTE = "sys_ts_note";
|
||||
/**流程超时提醒——系统通知消息模板*/
|
||||
public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip";
|
||||
/**-----【流程相关通知模板code】-----------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 系统通知拓展参数(比如:用于流程抄送和催办通知,这里额外传递流程跳转页面所需要的路由参数)
|
||||
*/
|
||||
public static final String MSG_ABSTRACT_JSON = "msg_abstract";
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ public interface DataBaseConstant {
|
|||
|
||||
/**postgreSQL达梦数据库*/
|
||||
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
|
||||
|
||||
/**人大金仓数据库*/
|
||||
public static final String DB_TYPE_KINGBASEES = "KINGBASEES";
|
||||
|
||||
/**sqlserver数据库*/
|
||||
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
|
||||
|
|
|
@ -116,4 +116,8 @@ public class SymbolConstant {
|
|||
*/
|
||||
public static final String SQUARE_BRACKETS_RIGHT = "]";
|
||||
|
||||
/**
|
||||
* 拼接字符串符号 分号 ;
|
||||
*/
|
||||
public static final String SEMICOLON = ";";
|
||||
}
|
|
@ -28,7 +28,7 @@ public enum CgformEnum {
|
|||
/**
|
||||
* 多表 (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"}),
|
||||
/**
|
||||
* 多表(内嵌子表风格)
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.jeecg.common.util;
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
@ -17,7 +17,11 @@ public enum DySmsEnum {
|
|||
/**会议通知*/
|
||||
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");
|
||||
|
||||
/**
|
||||
* 短信模板编码
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import org.jeecg.common.util.oConvertUtils;
|
|||
* 文件类型
|
||||
*/
|
||||
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
|
||||
xls(".xls","excel","excel"),
|
||||
xlsx(".xlsx","excel","excel"),
|
||||
|
@ -26,7 +26,8 @@ public enum FileTypeEnum {
|
|||
flv(".flv","video","视频"),
|
||||
mp4(".mp4","video","视频"),
|
||||
zip(".zip","zip","压缩包"),
|
||||
pdf(".pdf","pdf","pdf");
|
||||
pdf(".pdf","pdf","pdf"),
|
||||
mp3(".mp3","mp3","语音");
|
||||
|
||||
private String type;
|
||||
private String value;
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -13,12 +13,16 @@ import java.util.List;
|
|||
public enum RoleIndexConfigEnum {
|
||||
|
||||
/**首页自定义 admin*/
|
||||
ADMIN("admin", "dashboard/Analysis"),
|
||||
// ADMIN("admin", "dashboard/Analysis"),
|
||||
//TEST("test", "dashboard/IndexChart"),
|
||||
/**首页自定义 hr*/
|
||||
HR("hr", "dashboard/IndexBdc");
|
||||
// HR("hr", "dashboard/IndexBdc");
|
||||
|
||||
//DM("dm", "dashboard/IndexTask"),
|
||||
|
||||
// 注:此值仅为防止报错,无任何实际意义
|
||||
ROLE_INDEX_CONFIG_ENUM("RoleIndexConfigEnumDefault", "dashboard/Analysis");
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
|
|
|
@ -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:流程)
|
|
@ -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.vo.DictModel;
|
||||
|
@ -18,6 +18,16 @@ public enum Vue3MessageHrefEnum {
|
|||
* 流程催办
|
||||
*/
|
||||
BPM("bpm", "/task/myHandleTaskInfo"),
|
||||
|
||||
/**
|
||||
* 系统消息通知
|
||||
*/
|
||||
BPM_SYSTEM_MSG("bpm_msg_node", ""),
|
||||
|
||||
/**
|
||||
* 流程抄送任务
|
||||
*/
|
||||
BPM_VIEW("bpm_cc", "/task/myHandleTaskInfo"),
|
||||
|
||||
/**
|
||||
* 节点通知
|
|
@ -1,5 +1,7 @@
|
|||
package org.jeecg.common.exception;
|
||||
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
/**
|
||||
* @Description: jeecg-boot自定义异常
|
||||
* @author: jeecg-boot
|
||||
|
@ -7,10 +9,24 @@ package org.jeecg.common.exception;
|
|||
public class JeecgBootException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 返回给前端的错误code
|
||||
*/
|
||||
private int errCode = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
|
||||
|
||||
public JeecgBootException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public JeecgBootException(String message, int errCode){
|
||||
super(message);
|
||||
this.errCode = errCode;
|
||||
}
|
||||
|
||||
public int getErrCode() {
|
||||
return errCode;
|
||||
}
|
||||
|
||||
public JeecgBootException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.jeecg.common.exception;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
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.servlet.NoHandlerFoundException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 异常处理器
|
||||
*
|
||||
|
@ -34,7 +33,7 @@ public class JeecgBootExceptionHandler {
|
|||
@ExceptionHandler(JeecgBootException.class)
|
||||
public Result<?> handleJeecgBootException(JeecgBootException 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 连接异常!");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import org.jeecg.common.api.vo.Result;
|
|||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecgframework.poi.excel.ExcelImportUtil;
|
||||
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
||||
import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||
|
@ -18,16 +19,15 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
|
|||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
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.MultipartHttpServletRequest;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @Description: Controller基类
|
||||
|
@ -40,9 +40,9 @@ public class JeecgController<T, S extends IService<T>> {
|
|||
/**issues/2933 JeecgController注入service时改用protected修饰,能避免重复引用service*/
|
||||
@Autowired
|
||||
protected S service;
|
||||
|
||||
@Value("${jeecg.path.upload}")
|
||||
private String upLoadPath;
|
||||
@Resource
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
|
@ -61,15 +61,15 @@ public class JeecgController<T, S extends IService<T>> {
|
|||
}
|
||||
// Step.2 获取导出数据
|
||||
List<T> exportList = service.list(queryWrapper);
|
||||
System.out.println("Export LIST : " + exportList.toString());
|
||||
|
||||
// Step.3 AutoPoi 导出Excel
|
||||
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
|
||||
//此处设置的filename无效 ,前端会重更新设置一下
|
||||
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
||||
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
||||
//update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
exportParams.setImageBasePath(upLoadPath);
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
||||
//update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
|
||||
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
||||
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
|
||||
|
@ -108,7 +108,7 @@ public class JeecgController<T, S extends IService<T>> {
|
|||
IPage<T> pageList = service.page(page, queryWrapper);
|
||||
List<T> exportList = pageList.getRecords();
|
||||
Map<String, Object> map = new HashMap<>(5);
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,upLoadPath);
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
//map.put("title",exportParams);
|
||||
//表格Title
|
||||
|
|
|
@ -20,6 +20,14 @@ public class QueryCondition implements Serializable {
|
|||
private String dbType;
|
||||
private String rule;
|
||||
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() {
|
||||
return field;
|
||||
|
|
|
@ -19,11 +19,9 @@ import org.jeecg.common.constant.SymbolConstant;
|
|||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.util.JeecgDataAutorUtils;
|
||||
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.util.CommonUtils;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.*;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
@ -143,7 +141,7 @@ public class QueryGenerator {
|
|||
}
|
||||
|
||||
Object value = PropertyUtils.getSimpleProperty(searchObj, name);
|
||||
column = getTableFieldName(searchObj.getClass(), name);
|
||||
column = ReflectHelper.getTableFieldName(searchObj.getClass(), name);
|
||||
if(column==null){
|
||||
//column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了
|
||||
continue;
|
||||
|
@ -283,15 +281,9 @@ public class QueryGenerator {
|
|||
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
|
||||
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
|
||||
if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) {
|
||||
//queryWrapper.orderByAsc(oConvertUtils.camelToUnderline(column));
|
||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
||||
String[] columnArray = columnStr.split(",");
|
||||
queryWrapper.orderByAsc(Arrays.asList(columnArray));
|
||||
queryWrapper.orderByAsc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||
} else {
|
||||
//queryWrapper.orderByDesc(oConvertUtils.camelToUnderline(column));
|
||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
||||
String[] columnArray = columnStr.split(",");
|
||||
queryWrapper.orderByDesc(Arrays.asList(columnArray));
|
||||
queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||
}
|
||||
//update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
||||
}
|
||||
|
@ -347,7 +339,7 @@ public class QueryGenerator {
|
|||
return;
|
||||
}
|
||||
// update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
||||
log.info("---高级查询参数-->" + filterConditions);
|
||||
log.debug("---高级查询参数-->" + filterConditions);
|
||||
|
||||
queryWrapper.and(andWrapper -> {
|
||||
for (int i = 0; i < filterConditions.size(); i++) {
|
||||
|
@ -641,11 +633,11 @@ public class QueryGenerator {
|
|||
* @param 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;
|
||||
}
|
||||
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) {
|
||||
case GT:
|
||||
queryWrapper.gt(name, value);
|
||||
|
@ -713,7 +705,14 @@ public class QueryGenerator {
|
|||
*/
|
||||
public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
|
||||
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.get(0)==null){
|
||||
return ruleMap;
|
||||
|
@ -821,223 +820,7 @@ public class QueryGenerator {
|
|||
* @return
|
||||
*/
|
||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) {
|
||||
return 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 数据权限规则问题
|
||||
|
||||
}
|
||||
}
|
||||
return SqlConcatUtil.getSingleQueryConditionSql(field, alias, value, isString,null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1064,7 +847,7 @@ public class QueryGenerator {
|
|||
continue;
|
||||
}
|
||||
if(ruleMap.containsKey(name)) {
|
||||
column = getTableFieldName(clazz, name);
|
||||
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||
if(column==null){
|
||||
continue;
|
||||
}
|
||||
|
@ -1078,7 +861,7 @@ public class QueryGenerator {
|
|||
}else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1107,7 +890,7 @@ public class QueryGenerator {
|
|||
if (judgedIsUselessField(name)) {
|
||||
continue;
|
||||
}
|
||||
column = getTableFieldName(clazz, name);
|
||||
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||
if(column==null){
|
||||
continue;
|
||||
}
|
||||
|
@ -1126,42 +909,6 @@ public class QueryGenerator {
|
|||
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();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取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 模糊查询之特殊字符下划线 (_、\)
|
||||
*
|
||||
|
|
|
@ -25,12 +25,19 @@ public enum QueryRuleEnum {
|
|||
IN("IN","in","包含"),
|
||||
/**查询规则 全模糊*/
|
||||
LIKE("LIKE","like","全模糊"),
|
||||
/**查询规则 不模糊包含*/
|
||||
NOT_LIKE("NOT_LIKE","not_like","不模糊包含"),
|
||||
/**查询规则 左模糊*/
|
||||
LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
|
||||
/**查询规则 右模糊*/
|
||||
RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
|
||||
/**查询规则 带加号等于*/
|
||||
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","值为空"),
|
||||
|
@ -38,15 +45,12 @@ public enum QueryRuleEnum {
|
|||
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
|
||||
/**查询规则 不包含*/
|
||||
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","多词匹配"),
|
||||
/**查询规则 范围查询*/
|
||||
RANGE("RANGE","range","范围查询");
|
||||
RANGE("RANGE","range","范围查询"),
|
||||
NOT_RANGE("NOT_RANGE","not_range","不在范围查询");
|
||||
// ------- 当前表单设计器内专用 -------
|
||||
|
||||
private String value;
|
||||
|
||||
|
@ -89,7 +93,7 @@ public enum QueryRuleEnum {
|
|||
return null;
|
||||
}
|
||||
for(QueryRuleEnum val :values()){
|
||||
if (val.getValue().equals(value) || val.getCondition().equals(value)){
|
||||
if (val.getValue().equals(value) || val.getCondition().equalsIgnoreCase(value)){
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
@ -34,6 +35,7 @@ import org.jeecg.common.util.oConvertUtils;
|
|||
* @Date 2018-07-12 14:23
|
||||
* @Desc JWT工具类
|
||||
**/
|
||||
@Slf4j
|
||||
public class JwtUtil {
|
||||
|
||||
/**Token有效期为1小时(Token在reids中缓存时间为两倍)*/
|
||||
|
@ -163,15 +165,24 @@ public class JwtUtil {
|
|||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public static String getUserSystemData(String key,SysUserCacheInfo user) {
|
||||
public static String getUserSystemData(String key, SysUserCacheInfo user) {
|
||||
//1.优先获取 SysUserCacheInfo
|
||||
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}%
|
||||
|
||||
// 获取登录用户信息
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
String moshi = "";
|
||||
String wellNumber = WELL_NUMBER;
|
||||
if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
|
||||
|
@ -184,6 +195,24 @@ public class JwtUtil {
|
|||
} else {
|
||||
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(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作为系统变量
|
||||
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作为系统变量
|
||||
if(returnValue!=null){returnValue = returnValue + moshi;}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ package org.jeecg.common.system.vo;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import lombok.Data;
|
||||
|
@ -26,7 +27,13 @@ public class DictModel implements Serializable{
|
|||
this.value = value;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
public DictModel(String value, String text, String color) {
|
||||
this.value = value;
|
||||
this.text = text;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典value
|
||||
*/
|
||||
|
@ -35,6 +42,10 @@ public class DictModel implements Serializable{
|
|||
* 字典文本
|
||||
*/
|
||||
private String text;
|
||||
/**
|
||||
* 字典颜色
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 特殊用途: JgEditableTable
|
||||
|
@ -50,4 +61,11 @@ public class DictModel implements Serializable{
|
|||
return this.text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用于表单设计器 关联记录表数据存储
|
||||
* QQYUN-5595【表单设计器】他表字段 导入没有翻译
|
||||
*/
|
||||
private JSONObject jsonObject;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
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 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>
|
||||
|
@ -51,6 +49,7 @@ public class LoginUser {
|
|||
/**
|
||||
* 当前登录部门code
|
||||
*/
|
||||
@SensitiveField
|
||||
private String orgCode;
|
||||
/**
|
||||
* 头像
|
||||
|
@ -61,7 +60,6 @@ public class LoginUser {
|
|||
/**
|
||||
* 生日
|
||||
*/
|
||||
@SensitiveField
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date birthday;
|
||||
|
@ -107,6 +105,7 @@ public class LoginUser {
|
|||
/**
|
||||
* 管理部门ids
|
||||
*/
|
||||
@SensitiveField
|
||||
private String departIds;
|
||||
|
||||
/**
|
||||
|
@ -122,6 +121,7 @@ public class LoginUser {
|
|||
private String telephone;
|
||||
|
||||
/** 多租户ids临时用,不持久化数据库(数据库字段不存在) */
|
||||
@SensitiveField
|
||||
private String relTenantIds;
|
||||
|
||||
/**设备id uniapp推送用*/
|
||||
|
|
|
@ -19,6 +19,8 @@ public class SysFilesModel {
|
|||
private String storeType;
|
||||
/**文件大小(kb)*/
|
||||
private Double fileSize;
|
||||
/**租户id*/
|
||||
private String tenantId;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -67,4 +69,12 @@ public class SysFilesModel {
|
|||
public void setFileSize(Double fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public String getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(String tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
package org.jeecg.common.util;
|
||||
|
||||
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.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
import org.jeecg.common.constant.ServiceNameConstants;
|
||||
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.jeecgframework.poi.util.PoiPublicUtil;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
|
@ -26,7 +28,9 @@ import java.io.InputStream;
|
|||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -136,6 +140,7 @@ public class CommonUtils {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new JeecgBootException(e.getMessage());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
@ -148,7 +153,7 @@ public class CommonUtils {
|
|||
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
|
||||
try {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(mf);
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
String fileName = null;
|
||||
File file = new File(uploadpath + File.separator + bizPath + File.separator );
|
||||
|
@ -299,7 +304,7 @@ public class CommonUtils {
|
|||
DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
|
||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) {
|
||||
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;
|
||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) {
|
||||
DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
|
||||
|
@ -343,8 +348,11 @@ public class CommonUtils {
|
|||
|
||||
//返回 host domain
|
||||
String baseDomainPath = null;
|
||||
int length = 80;
|
||||
if(length == serverPort){
|
||||
//update-begin---author:wangshuai---date:2024-03-15---for:【QQYUN-8561】企业微信登陆请求接口设置上下文不一致,导致接口404---
|
||||
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 ;
|
||||
}else{
|
||||
baseDomainPath = scheme + "://" + serverName + ":" + serverPort + contextPath ;
|
||||
|
@ -392,4 +400,91 @@ public class CommonUtils {
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,5 @@
|
|||
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.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
|
@ -12,6 +8,10 @@ import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
|||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
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.
|
||||
|
@ -55,13 +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.defaultReadTimeout", "10000");
|
||||
|
||||
//update-begin-author:taoyan date:20200811 for:配置类数据获取
|
||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||
//logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
||||
//logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
||||
setAccessKeyId(staticConfig.getAccessKeyId());
|
||||
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
||||
//update-end-author:taoyan date:20200811 for:配置类数据获取
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.minio.*;
|
|||
import io.minio.http.Method;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.springframework.web.multipart.MultipartFile;
|
||||
|
||||
|
@ -60,7 +60,7 @@ public class MinioUtil {
|
|||
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(file);
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String newBucket = bucketName;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.jeecg.common.util;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
@ -7,6 +8,7 @@ import java.lang.reflect.Method;
|
|||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author 张代浩
|
||||
|
@ -252,4 +254,86 @@ public class ReflectHelper {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
package org.jeecg.common.util;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Set;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -17,54 +18,72 @@ import java.util.regex.Pattern;
|
|||
@Slf4j
|
||||
public class SqlInjectionUtil {
|
||||
/**
|
||||
* sign 用于表字典加签的盐值【SQL漏洞】
|
||||
* (上线修改值 20200501,同步修改前端的盐值)
|
||||
* 默认—sql注入关键词
|
||||
*/
|
||||
private final static String TABLE_DICT_SIGN_SALT = "20200501";
|
||||
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()";
|
||||
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 |+|--";
|
||||
/**
|
||||
* online报表专用—sql注入关键词
|
||||
*/
|
||||
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 |--";
|
||||
/**
|
||||
* 字典专用—sql注入关键词
|
||||
*/
|
||||
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("--");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 正则 user() 匹配更严谨
|
||||
* sql注入风险的 正则关键字
|
||||
*
|
||||
* 函数匹配,需要用正则模式
|
||||
*/
|
||||
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)";
|
||||
/**正则 show tables*/
|
||||
private final static String SHOW_TABLES = "show\\s+tables";
|
||||
|
||||
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注释的正则
|
||||
*/
|
||||
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
||||
private final static String SQL_ANNOTATION2 = "--";
|
||||
|
||||
/**
|
||||
* 针对表字典进行额外的sign签名校验(增加安全机制)
|
||||
* @param dictCode:
|
||||
* @param sign:
|
||||
* @param request:
|
||||
* @Return: void
|
||||
* sql注入提示语
|
||||
*/
|
||||
public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
|
||||
//表字典SQL注入漏洞,签名校验
|
||||
String accessToken = request.getHeader("X-Access-Token");
|
||||
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);
|
||||
}
|
||||
private final static String SQL_INJECTION_KEYWORD_TIP = "请注意,存在SQL注入关键词---> {}";
|
||||
private final static String SQL_INJECTION_TIP = "请注意,值可能存在SQL注入风险!--->";
|
||||
private final static String SQL_INJECTION_TIP_VARIABLE = "请注意,值可能存在SQL注入风险!---> {}";
|
||||
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
* @param value
|
||||
* @param values
|
||||
*/
|
||||
public static void filterContent(String value) {
|
||||
filterContent(value, null);
|
||||
public static void filterContent(String... values) {
|
||||
filterContent(values, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验比较严格
|
||||
*
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
|
@ -72,45 +91,81 @@ public class SqlInjectionUtil {
|
|||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
//value = value.replaceAll("/\\*.*\\*/","");
|
||||
// 转为小写进行后续比较
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
//https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
String[] xssArr = XSS_STR.split("\\|");
|
||||
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);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
|
||||
// 三、SQL注入检测存在绕过风险 (自定义传入普通文本校验)
|
||||
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);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr2[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, 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)){
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
* @param values
|
||||
* 判断是否存在SQL注入关键词字符串
|
||||
*
|
||||
* @param keyword
|
||||
* @return
|
||||
*/
|
||||
public static void filterContent(String[] values) {
|
||||
filterContent(values, null);
|
||||
@SuppressWarnings("AlibabaUndefineMagicConstant")
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,40 +175,11 @@ public class SqlInjectionUtil {
|
|||
* @return
|
||||
*/
|
||||
public static void filterContent(String[] values, String customXssString) {
|
||||
String[] xssArr = XSS_STR.split("\\|");
|
||||
for (String value : values) {
|
||||
if (value == null || "".equals(value)) {
|
||||
for (String val : values) {
|
||||
if (oConvertUtils.isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
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);
|
||||
}
|
||||
filterContent(val, customXssString);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -165,122 +191,230 @@ public class SqlInjectionUtil {
|
|||
* @param value
|
||||
* @return
|
||||
*/
|
||||
//@Deprecated
|
||||
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 = specialXssStr.split("\\|");
|
||||
String[] xssArr = specialDictSqlXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
//value = value.replaceAll("/\\*.*\\*/","");
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if ((value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) && value.length() == xssArr[i].trim().length()) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 【提醒:不通用】
|
||||
* 仅用于Online报表SQL解析,注入过滤
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
//@Deprecated
|
||||
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 = specialXssStr.split("\\|");
|
||||
String[] xssArr = specialReportXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
//value = value.replaceAll("/\\*.*\\*/"," ");
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
if (isExistSqlInjectKeyword(value, xssArr[i])) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断给定的字段是不是类中的属性
|
||||
* @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注释
|
||||
* @return
|
||||
*/
|
||||
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);
|
||||
if(matcher.find()){
|
||||
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
||||
log.error(error);
|
||||
throw new RuntimeException(error);
|
||||
throw new JeecgSqlInjectionException(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,6 +34,21 @@ public class TokenUtils {
|
|||
}
|
||||
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)
|
||||
|
|
|
@ -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 缓存
|
||||
*/
|
||||
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();
|
||||
|
||||
|
@ -47,8 +47,7 @@ public class FreemarkerParseFactory {
|
|||
.compile("(?ms)/\\*.*?\\*/|^\\s*//.*?$");
|
||||
|
||||
static {
|
||||
TPL_CONFIG.setClassForTemplateLoading(
|
||||
new FreemarkerParseFactory().getClass(), "/");
|
||||
TPL_CONFIG.setClassForTemplateLoading(new FreemarkerParseFactory().getClass(), "/");
|
||||
TPL_CONFIG.setNumberFormat("0.#####################");
|
||||
SQL_CONFIG.setTemplateLoader(stringTemplateLoader);
|
||||
SQL_CONFIG.setNumberFormat("0.#####################");
|
||||
|
@ -57,6 +56,7 @@ public class FreemarkerParseFactory {
|
|||
|
||||
//update-begin-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
|
||||
//https://ackcent.com/in-depth-freemarker-template-injection/
|
||||
TPL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||
SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||
//update-end-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
|
||||
}
|
||||
|
|
|
@ -1,30 +1,75 @@
|
|||
package org.jeecg.common.util.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: 校验上传文件敏感后缀
|
||||
* @Description: 校验文件敏感后缀
|
||||
* @author: lsq
|
||||
* @date: 2021年08月09日 15:29
|
||||
* @date: 2023年09月12日 15:29
|
||||
*/
|
||||
public class FileTypeFilter {
|
||||
|
||||
/**文件后缀*/
|
||||
private static String[] forbidType = {"jsp","php"};
|
||||
@Slf4j
|
||||
public class SsrfFileTypeFilter {
|
||||
|
||||
/**
|
||||
* 允许操作文件类型白名单
|
||||
*/
|
||||
private final static List<String> FILE_TYPE_WHITE_LIST = new ArrayList<>();
|
||||
/**初始化文件头类型,不够的自行补充*/
|
||||
final static HashMap<String, String> FILE_TYPE_MAP = new HashMap<>();
|
||||
|
||||
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("3c3f7068700a0a2f2a2a0a202a205048", "php");
|
||||
FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");
|
||||
FILE_TYPE_MAP.put("494e5345525420494e54", "sql");
|
||||
/* fileTypeMap.put("ffd8ffe000104a464946", "jpg");
|
||||
fileTypeMap.put("89504e470d0a1a0a0000", "png");
|
||||
fileTypeMap.put("47494638396126026f01", "gif");
|
||||
|
@ -89,17 +134,38 @@ public class FileTypeFilter {
|
|||
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
|
||||
*/
|
||||
public static void fileTypeFilter(MultipartFile file) throws Exception {
|
||||
public static void checkUploadFileType(MultipartFile file) throws Exception {
|
||||
//获取文件真是后缀
|
||||
String suffix = getFileType(file);
|
||||
for (String type : forbidType) {
|
||||
if (type.contains(suffix)) {
|
||||
throw new Exception("上传失败,非法文件类型:" + suffix);
|
||||
}
|
||||
|
||||
log.info("suffix:{}", suffix);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new Exception("上传失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,8 +178,9 @@ public class FileTypeFilter {
|
|||
*/
|
||||
|
||||
private static String getFileType(MultipartFile file) throws Exception {
|
||||
//update-begin-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
||||
String fileExtendName = null;
|
||||
InputStream is;
|
||||
InputStream is = null;
|
||||
try {
|
||||
//is = new FileInputStream(file);
|
||||
is = file.getInputStream();
|
||||
|
@ -130,16 +197,29 @@ public class FileTypeFilter {
|
|||
break;
|
||||
}
|
||||
}
|
||||
log.info("-----获取到的指定文件类型------"+fileExtendName);
|
||||
// 如果不是上述类型,则判断扩展名
|
||||
if (StringUtils.isBlank(fileExtendName)) {
|
||||
String fileName = file.getOriginalFilename();
|
||||
// 如果无扩展名,则直接返回空串
|
||||
if (-1 == fileName.indexOf(".")) {
|
||||
return "";
|
||||
}
|
||||
// 如果有扩展名,则返回扩展名
|
||||
return getFileTypeBySuffix(fileName);
|
||||
}
|
||||
log.info("-----最終的文件类型------"+fileExtendName);
|
||||
is.close();
|
||||
return fileExtendName;
|
||||
} catch (Exception exception) {
|
||||
throw new Exception(exception.getMessage(), exception);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return "";
|
||||
}finally {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
//update-end-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,5 +1,6 @@
|
|||
package org.jeecg.common.util;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
@ -86,7 +87,7 @@ public class oConvertUtils {
|
|||
}
|
||||
|
||||
public static int getInt(String s, int defval) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return (defval);
|
||||
}
|
||||
try {
|
||||
|
@ -97,7 +98,7 @@ public class oConvertUtils {
|
|||
}
|
||||
|
||||
public static int getInt(String s) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
|
@ -108,7 +109,7 @@ public class oConvertUtils {
|
|||
}
|
||||
|
||||
public static int getInt(String s, Integer df) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return df;
|
||||
}
|
||||
try {
|
||||
|
@ -131,7 +132,7 @@ public class oConvertUtils {
|
|||
}
|
||||
|
||||
public static double getDouble(String s, double defval) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return (defval);
|
||||
}
|
||||
try {
|
||||
|
@ -167,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) {
|
||||
if (isEmpty(object)) {
|
||||
return null;
|
||||
|
@ -353,23 +365,63 @@ public class oConvertUtils {
|
|||
/**
|
||||
* 判断元素是否在数组内
|
||||
*
|
||||
* @param substring
|
||||
* @param source
|
||||
* @param child
|
||||
* @param all
|
||||
* @return
|
||||
*/
|
||||
public static boolean isIn(String substring, String[] source) {
|
||||
if (source == null || source.length == 0) {
|
||||
public static boolean isIn(String child, String[] all) {
|
||||
if (all == null || all.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < source.length; i++) {
|
||||
String aSource = source[i];
|
||||
if (aSource.equals(substring)) {
|
||||
for (int i = 0; i < all.length; i++) {
|
||||
String aSource = all[i];
|
||||
if (aSource.equals(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断元素是否在数组内
|
||||
*
|
||||
* @param childArray
|
||||
* @param all
|
||||
* @return
|
||||
*/
|
||||
public static boolean isArrayIn(String[] childArray, String[] all) {
|
||||
if (all == null || all.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (String v : childArray) {
|
||||
if (!isIn(v, all)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断元素是否在数组内
|
||||
*
|
||||
* @param childArray
|
||||
* @param all
|
||||
* @return
|
||||
*/
|
||||
public static boolean isJsonArrayIn(JSONArray childArray, String[] all) {
|
||||
if (all == null || all.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] childs = childArray.toArray(new String[]{});
|
||||
for (String v : childs) {
|
||||
if (!isIn(v, all)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Map对象
|
||||
*/
|
||||
|
@ -649,6 +701,138 @@ public class oConvertUtils {
|
|||
return (list == null || list.size() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断旧值与新值 是否相等
|
||||
*
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
* @return
|
||||
*/
|
||||
public static boolean isEqual(Object oldVal, Object newVal) {
|
||||
if (oldVal != null && newVal != null) {
|
||||
if (isArray(oldVal)) {
|
||||
return equalityOfArrays((Object[]) oldVal, (Object[]) newVal);
|
||||
}else if(oldVal instanceof JSONArray){
|
||||
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);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (oldVal == null && newVal == null) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法描述 判断一个对象是否是一个数组
|
||||
*
|
||||
* @param obj
|
||||
* @return
|
||||
* @author yaomy
|
||||
* @date 2018年2月5日 下午5:03:00
|
||||
*/
|
||||
public static boolean isArray(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
return obj.getClass().isArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个数组是否相等(数组元素不分顺序)
|
||||
*
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
* @return
|
||||
*/
|
||||
public static boolean equalityOfJSONArray(JSONArray oldVal, JSONArray newVal) {
|
||||
if (oldVal != null && newVal != null) {
|
||||
Object[] oldValArray = oldVal.toArray();
|
||||
Object[] newValArray = newVal.toArray();
|
||||
return equalityOfArrays(oldValArray,newValArray);
|
||||
} else {
|
||||
if ((oldVal == null || oldVal.size() == 0) && (newVal == null || newVal.size() == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较带逗号的字符串
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个数组是否相等(数组元素不分顺序)
|
||||
*
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
* @return
|
||||
*/
|
||||
public static boolean equalityOfArrays(Object[] oldVal, Object newVal[]) {
|
||||
if (oldVal != null && newVal != null) {
|
||||
Arrays.sort(oldVal);
|
||||
Arrays.sort(newVal);
|
||||
return Arrays.equals(oldVal, newVal);
|
||||
} else {
|
||||
if ((oldVal == null || oldVal.length == 0) && (newVal == null || newVal.length == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
//// String[] a = new String[]{"1", "2"};
|
||||
//// String[] b = new String[]{"2", "1"};
|
||||
// Integer a = null;
|
||||
// Integer b = 1;
|
||||
// System.out.println(oConvertUtils.isEqual(a, b));
|
||||
// }
|
||||
|
||||
/**
|
||||
* 判断 list 是否不为空
|
||||
*
|
||||
|
@ -677,4 +861,85 @@ public class oConvertUtils {
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import org.apache.commons.fileupload.FileItemStream;
|
|||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
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.oConvertUtils;
|
||||
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 {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(file);
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String filePath = null;
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package org.jeecg.common.util.security;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 查询表/字段 黑名单处理
|
||||
|
@ -21,6 +25,11 @@ public abstract class AbstractQueryBlackListHandler {
|
|||
*/
|
||||
public static Map<String, String> ruleMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 以下字符不能出现在表名中或是字段名中
|
||||
*/
|
||||
public static final Pattern ILLEGAL_NAME_REG = Pattern.compile("[-]{2,}");
|
||||
|
||||
static {
|
||||
ruleMap.put("sys_user", "password,salt");
|
||||
}
|
||||
|
@ -52,27 +61,76 @@ public abstract class AbstractQueryBlackListHandler {
|
|||
if(list==null){
|
||||
return true;
|
||||
}
|
||||
log.info("--获取sql信息--", list.toString());
|
||||
boolean flag = true;
|
||||
log.info(" 获取sql信息 :{} ", list.toString());
|
||||
boolean flag = checkTableAndFieldsName(list);
|
||||
if(flag == false){
|
||||
return false;
|
||||
}
|
||||
for (QueryTable table : list) {
|
||||
String name = table.getName();
|
||||
String fieldString = ruleMap.get(name);
|
||||
String fieldRule = ruleMap.get(name);
|
||||
// 有没有配置这张表
|
||||
if (fieldString != null) {
|
||||
if ("*".equals(fieldString) || table.isAll()) {
|
||||
if (fieldRule != null) {
|
||||
if ("*".equals(fieldRule) || table.isAll()) {
|
||||
flag = false;
|
||||
log.warn("sql黑名单校验,表【"+name+"】禁止查询");
|
||||
break;
|
||||
} else if (table.existSameField(fieldString)) {
|
||||
} else if (table.existSameField(fieldRule)) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 返回黑名单校验结果(不合法直接抛出异常)
|
||||
if(!flag){
|
||||
log.error(this.getError());
|
||||
throw new JeecgSqlInjectionException(this.getError());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验表名和字段名是否有效,或是是否会带些特殊的字符串进行sql注入
|
||||
* issues/4983 SQL Injection in 3.5.1 #4983
|
||||
* @return
|
||||
*/
|
||||
private boolean checkTableAndFieldsName(List<QueryTable> list){
|
||||
boolean flag = true;
|
||||
for(QueryTable queryTable: list){
|
||||
String tableName = queryTable.getName();
|
||||
if(hasSpecialString(tableName)){
|
||||
flag = false;
|
||||
log.warn("sql黑名单校验,表名【"+tableName+"】包含特殊字符");
|
||||
break;
|
||||
}
|
||||
Set<String> fields = queryTable.getFields();
|
||||
for(String name: fields){
|
||||
if(hasSpecialString(name)){
|
||||
flag = false;
|
||||
log.warn("sql黑名单校验,字段名【"+name+"】包含特殊字符");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含特殊的字符串
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
private boolean hasSpecialString(String name){
|
||||
Matcher m = ILLEGAL_NAME_REG.matcher(name);
|
||||
if (m.find()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询的表的信息
|
||||
*/
|
||||
|
@ -139,21 +197,21 @@ public abstract class AbstractQueryBlackListHandler {
|
|||
* @return
|
||||
*/
|
||||
public boolean existSameField(String fieldString) {
|
||||
String[] arr = fieldString.split(",");
|
||||
for (String exp : fields) {
|
||||
for (String config : arr) {
|
||||
if (exp.equals(config)) {
|
||||
String[] controlFields = fieldString.split(",");
|
||||
for (String sqlField : fields) {
|
||||
for (String controlField : controlFields) {
|
||||
if (sqlField.equals(controlField)) {
|
||||
// 非常明确的列直接比较
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+config+"】禁止查询");
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+controlField+"】禁止查询");
|
||||
return true;
|
||||
} else {
|
||||
// 使用表达式的列 只能判读字符串包含了
|
||||
String aliasColumn = config;
|
||||
if (alias != null && alias.length() > 0) {
|
||||
aliasColumn = alias + "." + config;
|
||||
String aliasColumn = controlField;
|
||||
if (StringUtils.isNotBlank(alias)) {
|
||||
aliasColumn = alias + "." + controlField;
|
||||
}
|
||||
if (exp.indexOf(aliasColumn) > 0) {
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+config+"】禁止查询");
|
||||
if (sqlField.indexOf(aliasColumn) != -1) {
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+controlField+"】禁止查询");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
/**
|
||||
* 解析 查询(select)sql的信息,
|
||||
* 此方法会展开所有子查询到一个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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 查询(select)sql的信息,子查询嵌套
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 +
|
||||
"}";
|
||||
}
|
||||
|
||||
}
|
|
@ -59,7 +59,9 @@ public class AutoPoiDictConfig implements AutoPoiDictServiceI {
|
|||
|
||||
|
||||
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字段的值有下划线时,导入功能不能正确解析---
|
||||
if(t.getValue().contains(EXCEL_SPLIT_TAG)){
|
||||
String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package org.jeecg.config;
|
||||
|
||||
import org.jeecg.config.vo.DomainUrl;
|
||||
import org.jeecg.config.vo.Path;
|
||||
import org.jeecg.config.vo.Shiro;
|
||||
import org.jeecg.config.vo.*;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -28,10 +26,12 @@ public class JeecgBaseConfig {
|
|||
* 本地:local\Minio:minio\阿里云:alioss
|
||||
*/
|
||||
private String uploadType;
|
||||
|
||||
/**
|
||||
* 是否启用安全模式
|
||||
* 平台安全模式配置
|
||||
*/
|
||||
private Boolean safeMode = false;
|
||||
private Firewall firewall;
|
||||
|
||||
/**
|
||||
* shiro拦截排除
|
||||
*/
|
||||
|
@ -52,13 +52,32 @@ public class JeecgBaseConfig {
|
|||
* 文件预览
|
||||
*/
|
||||
private String fileViewDomain;
|
||||
/**
|
||||
* ES配置
|
||||
*/
|
||||
private Elasticsearch elasticsearch;
|
||||
|
||||
public Boolean getSafeMode() {
|
||||
return safeMode;
|
||||
/**
|
||||
* 微信支付
|
||||
* @return
|
||||
*/
|
||||
private WeiXinPay weiXinPay;
|
||||
|
||||
|
||||
public Elasticsearch getElasticsearch() {
|
||||
return elasticsearch;
|
||||
}
|
||||
|
||||
public void setSafeMode(Boolean safeMode) {
|
||||
this.safeMode = safeMode;
|
||||
public void setElasticsearch(Elasticsearch elasticsearch) {
|
||||
this.elasticsearch = elasticsearch;
|
||||
}
|
||||
|
||||
public Firewall getFirewall() {
|
||||
return firewall;
|
||||
}
|
||||
|
||||
public void setFirewall(Firewall firewall) {
|
||||
this.firewall = firewall;
|
||||
}
|
||||
|
||||
public String getSignatureSecret() {
|
||||
|
@ -116,4 +135,13 @@ public class JeecgBaseConfig {
|
|||
public void setUploadType(String uploadType) {
|
||||
this.uploadType = uploadType;
|
||||
}
|
||||
|
||||
public WeiXinPay getWeiXinPay() {
|
||||
return weiXinPay;
|
||||
}
|
||||
|
||||
public void setWeiXinPay(WeiXinPay weiXinPay) {
|
||||
this.weiXinPay = weiXinPay;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.jeecg.config;
|
||||
|
||||
|
||||
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.springframework.beans.BeansException;
|
||||
|
@ -19,15 +18,13 @@ import springfox.documentation.builders.ApiInfoBuilder;
|
|||
import springfox.documentation.builders.ParameterBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.oas.annotations.EnableOpenApi;
|
||||
import springfox.documentation.schema.ModelRef;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
|
||||
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.util.ArrayList;
|
||||
|
@ -39,8 +36,7 @@ import java.util.stream.Collectors;
|
|||
* @Author scott
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2 //开启 Swagger2
|
||||
@EnableKnife4j //开启 knife4j,可以不写
|
||||
@EnableSwagger2WebMvc
|
||||
@Import(BeanValidatorPluginsConfiguration.class)
|
||||
public class Swagger2Config implements WebMvcConfigurer {
|
||||
|
||||
|
@ -116,7 +112,7 @@ public class Swagger2Config implements WebMvcConfigurer {
|
|||
// 描述
|
||||
.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")
|
||||
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
|
||||
.build();
|
||||
|
@ -152,7 +148,7 @@ public class Swagger2Config implements WebMvcConfigurer {
|
|||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
|
||||
if (bean instanceof WebMvcRequestHandlerProvider) {
|
||||
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
|
||||
}
|
||||
return bean;
|
||||
|
|
|
@ -31,7 +31,7 @@ public class WebSocketConfig {
|
|||
FilterRegistrationBean bean = new FilterRegistrationBean();
|
||||
bean.setFilter(websocketFilter());
|
||||
//TODO 临时注释掉,测试下线上socket总断的问题
|
||||
bean.addUrlPatterns("/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
||||
bean.addUrlPatterns("/taskCountSocket/*", "/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
||||
return bean;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.jeecg.config.shiro;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
|
||||
|
@ -9,29 +10,36 @@ import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
|||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.crazycake.shiro.IRedisManager;
|
||||
import org.crazycake.shiro.RedisCacheManager;
|
||||
import org.crazycake.shiro.RedisClusterManager;
|
||||
import org.crazycake.shiro.RedisManager;
|
||||
import org.crazycake.shiro.*;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
|
||||
import org.jeecg.config.shiro.filters.JwtFilter;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
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.JedisCluster;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
|
@ -50,7 +58,11 @@ public class ShiroConfig {
|
|||
private Environment env;
|
||||
@Resource
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
@Autowired(required = false)
|
||||
private RedisProperties redisProperties;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext ctx;
|
||||
/**
|
||||
* Filter Chain定义说明
|
||||
*
|
||||
|
@ -75,6 +87,7 @@ public class ShiroConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 配置不会被拦截的链接 顺序判断
|
||||
filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
|
||||
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
|
||||
|
@ -93,6 +106,9 @@ public class ShiroConfig {
|
|||
filterChainDefinitionMap.put("/auth/2step-code", "anon");//登录验证码
|
||||
filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token
|
||||
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("/sys/getLoginQrcode/**", "anon"); //登录二维码
|
||||
|
@ -100,6 +116,7 @@ public class ShiroConfig {
|
|||
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
||||
|
||||
|
||||
//update-begin--Author:scott Date:20221116 for:排除静态资源后缀
|
||||
filterChainDefinitionMap.put("/", "anon");
|
||||
filterChainDefinitionMap.put("/doc.html", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.js", "anon");
|
||||
|
@ -114,20 +131,29 @@ public class ShiroConfig {
|
|||
filterChainDefinitionMap.put("/**/*.ttf", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.woff", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.woff2", "anon");
|
||||
//update-end--Author:scott Date:20221116 for:排除静态资源后缀
|
||||
|
||||
filterChainDefinitionMap.put("/druid/**", "anon");
|
||||
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
|
||||
filterChainDefinitionMap.put("/swagger**/**", "anon");
|
||||
filterChainDefinitionMap.put("/webjars/**", "anon");
|
||||
filterChainDefinitionMap.put("/v2/**", "anon");
|
||||
|
||||
|
||||
// update-begin--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
|
||||
// update-end--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||
|
||||
//积木报表排除
|
||||
filterChainDefinitionMap.put("/jmreport/**", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.js.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("/bigscreen/template1/**", "anon");
|
||||
|
@ -149,6 +175,19 @@ public class ShiroConfig {
|
|||
//测试模块排除
|
||||
filterChainDefinitionMap.put("/test/seata/**", "anon");
|
||||
|
||||
//错误路径排除
|
||||
filterChainDefinitionMap.put("/error", "anon");
|
||||
// 企业微信证书排除
|
||||
filterChainDefinitionMap.put("/WW_verify*", "anon");
|
||||
|
||||
// // 通过注解免登录url
|
||||
// List<String> ignoreAuthUrlList = collectIgnoreAuthUrl(ctx);
|
||||
// if (!CollectionUtils.isEmpty(ignoreAuthUrlList)) {
|
||||
// for (String url : ignoreAuthUrlList) {
|
||||
// filterChainDefinitionMap.put(url, "anon");
|
||||
// }
|
||||
// }
|
||||
|
||||
// 添加自己的过滤器并且取名为jwt
|
||||
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||
//如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
|
||||
|
@ -165,6 +204,20 @@ public class ShiroConfig {
|
|||
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")
|
||||
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
|
@ -242,11 +295,24 @@ public class ShiroConfig {
|
|||
public IRedisManager redisManager() {
|
||||
log.info("===============(2)创建RedisManager,连接Redis..");
|
||||
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
|
||||
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
|
||||
RedisManager redisManager = new RedisManager();
|
||||
redisManager.setHost(lettuceConnectionFactory.getHostName());
|
||||
redisManager.setPort(lettuceConnectionFactory.getPort());
|
||||
redisManager.setHost(lettuceConnectionFactory.getHostName() + ":" + lettuceConnectionFactory.getPort());
|
||||
//(lettuceConnectionFactory.getPort());
|
||||
redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
|
||||
redisManager.setTimeout(0);
|
||||
if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
|
||||
|
@ -274,4 +340,67 @@ public class ShiroConfig {
|
|||
return manager;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public List<String> collectIgnoreAuthUrl(ApplicationContext context) {
|
||||
List<String> ignoreAuthUrls = new ArrayList<>();
|
||||
Map<String, Object> controllers = context.getBeansWithAnnotation(RestController.class);
|
||||
for (Object bean : controllers.values()) {
|
||||
if (!(bean instanceof Advised)) {
|
||||
continue;
|
||||
}
|
||||
Class<?> beanClass = ((Advised) bean).getTargetSource().getTarget().getClass();
|
||||
RequestMapping base = beanClass.getAnnotation(RequestMapping.class);
|
||||
String[] baseUrl = {};
|
||||
if (Objects.nonNull(base)) {
|
||||
baseUrl = base.value();
|
||||
}
|
||||
Method[] methods = beanClass.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<>();
|
||||
for (String base : bases) {
|
||||
for (String uri : uris) {
|
||||
urls.add(prefix(base)+prefix(uri));
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
private String prefix(String seg) {
|
||||
return seg.startsWith("/") ? seg : "/"+seg;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,9 +62,11 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||
log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
|
||||
String username = null;
|
||||
String userId = null;
|
||||
if (principals != null) {
|
||||
LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
|
||||
username = sysUser.getUsername();
|
||||
userId = sysUser.getId();
|
||||
}
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||
|
||||
|
@ -74,7 +76,7 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||
info.setRoles(roleSet);
|
||||
|
||||
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
|
||||
Set<String> permissionSet = commonApi.queryUserAuths(username);
|
||||
Set<String> permissionSet = commonApi.queryUserAuths(userId);
|
||||
info.addStringPermissions(permissionSet);
|
||||
//System.out.println(permissionSet);
|
||||
log.info("===============Shiro权限认证成功==============");
|
||||
|
|
|
@ -42,7 +42,7 @@ public class SignAuthInterceptor implements HandlerInterceptor {
|
|||
String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP);
|
||||
|
||||
if(oConvertUtils.isEmpty(xTimestamp)){
|
||||
Result<?> result = Result.error("Sign签名校验失败!");
|
||||
Result<?> result = Result.error("Sign签名校验失败,时间戳为空!");
|
||||
log.error("Sign 签名校验失败!Header xTimestamp 为空");
|
||||
//校验失败返回前端
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
@ -79,6 +79,7 @@ public class SignAuthInterceptor implements HandlerInterceptor {
|
|||
log.debug("Sign 签名通过!Header Sign : {}",headerSign);
|
||||
return true;
|
||||
} else {
|
||||
log.info("sign allParams: {}", allParams);
|
||||
log.error("request URI = " + request.getRequestURI());
|
||||
log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
|
||||
//校验失败返回前端
|
||||
|
|
|
@ -43,6 +43,16 @@ public class HttpUtils {
|
|||
if (pathVariable.contains(SymbolConstant.COMMA)) {
|
||||
log.info(" pathVariable: {}",pathVariable);
|
||||
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);
|
||||
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
||||
}
|
||||
|
@ -81,6 +91,12 @@ public class HttpUtils {
|
|||
if (pathVariable.contains(SymbolConstant.COMMA)) {
|
||||
log.info(" pathVariable: {}",pathVariable);
|
||||
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);
|
||||
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
||||
}
|
||||
|
@ -156,7 +172,11 @@ public class HttpUtils {
|
|||
String[] params = param.split("&");
|
||||
for (String s : params) {
|
||||
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;
|
||||
}
|
||||
|
@ -180,7 +200,11 @@ public class HttpUtils {
|
|||
String[] params = param.split("&");
|
||||
for (String s : params) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.jeecg.config.vo;
|
||||
|
||||
/**
|
||||
* @author: scott
|
||||
* @date: 2023年05月10日 16:06
|
||||
*/
|
||||
public class Elasticsearch {
|
||||
private String clusterNodes;
|
||||
private boolean checkEnabled;
|
||||
|
||||
public String getClusterNodes() {
|
||||
return clusterNodes;
|
||||
}
|
||||
|
||||
public void setClusterNodes(String clusterNodes) {
|
||||
this.clusterNodes = clusterNodes;
|
||||
}
|
||||
|
||||
public boolean isCheckEnabled() {
|
||||
return checkEnabled;
|
||||
}
|
||||
|
||||
public void setCheckEnabled(boolean checkEnabled) {
|
||||
this.checkEnabled = checkEnabled;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,75 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* SQL注入攻击检查测试
|
||||
* @author: liusq
|
||||
* @date: 2023年09月08日
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestInjectWithSqlParser {
|
||||
/**
|
||||
* 注入测试
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
private boolean isExistSqlInject(String sql) {
|
||||
try {
|
||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.info("===================================================");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() throws JSQLParserException {
|
||||
//不存在sql注入
|
||||
assertFalse(isExistSqlInject("select * from fm_time where dept_id=:sqlparamsmap.id and time=:sqlparamsmap.time"));
|
||||
assertFalse(isExistSqlInject("select * from test"));
|
||||
assertFalse(isExistSqlInject("select load_file(\"C:\\\\benben.txt\")"));
|
||||
assertFalse(isExistSqlInject("WITH SUB1 AS (SELECT user FROM t1) SELECT * FROM T2 WHERE id > 123 "));
|
||||
|
||||
//存在sql注入
|
||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
||||
assertTrue(isExistSqlInject("select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));"));
|
||||
assertTrue(isExistSqlInject("select * from users;show databases;"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id=1 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13"));
|
||||
assertTrue(isExistSqlInject("update user set name = '123'"));
|
||||
assertTrue(isExistSqlInject("SELECT * FROM users WHERE username = 'admin' AND password = '123456' OR 1=1;--"));
|
||||
assertTrue(isExistSqlInject("select * from users where id=1 and (select count(*) from information_schema.tables where table_schema='数据库名')>4 %23"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where sleep(5) %23"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 2=2.0 or 2 != 4"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 1!=2.0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id=floor(2.0)"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where not true"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 1 or id > 0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 'tom' or id > 0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where '-2.3' "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 2 "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where (3+2) "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where -1 IS TRUE"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where 'hello' is null "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where '2022-10-31' and id > 0"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id > 0 or 1!=2.0 "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id > 0 or 1 in (1,3,4) "));
|
||||
assertTrue(isExistSqlInject("select * from dc_device UNION select name from other"));
|
||||
assertTrue(isExistSqlInject("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* SQL注入攻击检查测试
|
||||
* @author: liusq
|
||||
* @date: 2023年09月08日
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestSqlInjectForDict {
|
||||
/**
|
||||
* 注入测试
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
private boolean isExistSqlInject(String sql) {
|
||||
try {
|
||||
SqlInjectionUtil.specialFilterContentForDictSql(sql);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.info("===================================================");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() throws JSQLParserException {
|
||||
//不存在sql注入
|
||||
assertFalse(isExistSqlInject("sys_user,realname,id"));
|
||||
assertFalse(isExistSqlInject("oa_officialdoc_organcode,organ_name,id"));
|
||||
assertFalse(isExistSqlInject("onl_cgform_head where table_type!=3 and copy_type=0,table_txt,table_name"));
|
||||
assertFalse(isExistSqlInject("onl_cgform_head where copy_type = 0,table_txt,table_name"));
|
||||
|
||||
//存在sql注入
|
||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* SQL注入攻击检查测试
|
||||
* @author: liusq
|
||||
* @date: 2023年09月08日
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestSqlInjectForOnlineReport {
|
||||
/**
|
||||
* 注入测试
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
private boolean isExistSqlInject(String sql) {
|
||||
try {
|
||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.info("===================================================");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() throws JSQLParserException {
|
||||
//不存在sql注入
|
||||
assertFalse(isExistSqlInject("select * from fm_time where dept_id=:sqlparamsmap.id and time=:sqlparamsmap.time"));
|
||||
assertFalse(isExistSqlInject("select * from test"));
|
||||
assertFalse(isExistSqlInject("select load_file(\"C:\\\\benben.txt\")"));
|
||||
assertFalse(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
||||
assertFalse(isExistSqlInject("select * from dc_device UNION select name from other"));
|
||||
|
||||
//存在sql注入
|
||||
assertTrue(isExistSqlInject("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)"));
|
||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
||||
assertTrue(isExistSqlInject("select * from test where SLEEP(3)"));
|
||||
assertTrue(isExistSqlInject("select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));"));
|
||||
assertTrue(isExistSqlInject("select * from users;show databases;"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where id=1 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13"));
|
||||
assertTrue(isExistSqlInject("update user set name = '123'"));
|
||||
assertTrue(isExistSqlInject("SELECT * FROM users WHERE username = 'admin' AND password = '123456' OR 1=1;--"));
|
||||
assertTrue(isExistSqlInject("select * from users where id=1 and (select count(*) from information_schema.tables where table_schema='数据库名')>4 %23"));
|
||||
assertTrue(isExistSqlInject("select * from dc_device where sleep(5) %23"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package org.jeecg.test.sqlinjection;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @Description: SQL注入测试类
|
||||
* @author: scott
|
||||
* @date: 2023年08月14日 9:55
|
||||
*/
|
||||
public class TestSqlInjection {
|
||||
|
||||
|
||||
/**
|
||||
* 表名带别名,同时有html编码字符
|
||||
*/
|
||||
@Test
|
||||
public void testSpecialSQL() {
|
||||
String tableName = "sys_user t";
|
||||
//解决使用参数tableName=sys_user t&复测,漏洞仍然存在
|
||||
if (tableName.contains(" ")) {
|
||||
tableName = tableName.substring(0, tableName.indexOf(" "));
|
||||
}
|
||||
//【issues/4393】 sys_user , (sys_user), sys_user%20, %60sys_user%60
|
||||
String reg = "\\s+|\\(|\\)|`";
|
||||
tableName = tableName.replaceAll(reg, "");
|
||||
System.out.println(tableName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 测试sql是否含sql注入风险
|
||||
* <p>
|
||||
* mybatis plus的方法
|
||||
*/
|
||||
@Test
|
||||
public void sqlInjectionCheck() {
|
||||
String sql = "select * from sys_user";
|
||||
System.out.println(SqlInjectionUtils.check(sql));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 测试sql是否有SLEEP风险
|
||||
* <p>
|
||||
* mybatisPlus的方法
|
||||
*/
|
||||
@Test
|
||||
public void sqlSleepCheck() {
|
||||
SqlInjectionUtil.checkSqlAnnotation("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试sql是否含sql注入风险
|
||||
* <p>
|
||||
* 自定义方法
|
||||
*/
|
||||
@Test
|
||||
public void sqlInjectionCheck2() {
|
||||
String sql = "select * from sys_user";
|
||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
|
||||
* <p>
|
||||
* 判断字段名是否符合规范
|
||||
*/
|
||||
@Test
|
||||
public void testFieldSpecification() {
|
||||
List<String> list = new ArrayList();
|
||||
list.add("Hello World!");
|
||||
list.add("Hello%20World!");
|
||||
list.add("HelloWorld!");
|
||||
list.add("Hello World");
|
||||
list.add("age");
|
||||
list.add("user_name");
|
||||
list.add("user_name%20");
|
||||
list.add("user_name%20 ");
|
||||
|
||||
for (String input : list) {
|
||||
boolean containsSpecialChars = isValidString(input);
|
||||
System.out.println("input:" + input + " ,包含空格和特殊字符: " + containsSpecialChars);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
private static boolean isValidString(String input) {
|
||||
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]+$");
|
||||
return pattern.matcher(input).matches();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package org.jeecg.test.sqlparse;
|
||||
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.sqlparse.JSqlParserUtils;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 针对 JSqlParserUtils 的单元测试
|
||||
*/
|
||||
public class JSqlParserUtilsTest {
|
||||
|
||||
private static final String[] sqlList = new String[]{
|
||||
"select * from sys_user",
|
||||
"select u.* from sys_user u",
|
||||
"select u.*, c.name from sys_user u, demo c",
|
||||
"select u.age, c.name from sys_user u, demo c",
|
||||
"select sex, age, c.name from sys_user, demo c",
|
||||
// 别名测试
|
||||
"select username as realname from sys_user",
|
||||
"select username as realname, u.realname as aaa, u.id bbb from sys_user u",
|
||||
// 不存在真实地查询字段
|
||||
"select count(1) from sys_user",
|
||||
// 函数式字段
|
||||
"select max(sex), id from sys_user",
|
||||
// 复杂嵌套函数式字段
|
||||
"select CONCAT(CONCAT(' _ ', sex), ' - ' , birthday) as info, id from sys_user",
|
||||
// 更复杂的嵌套函数式字段
|
||||
"select CONCAT(CONCAT(101,'_',NULL, DATE(create_time),'_',sex),' - ',birthday) as info, id from sys_user",
|
||||
// 子查询SQL
|
||||
"select u.name1 as name2 from (select username as name1 from sys_user) u",
|
||||
// 多层嵌套子查询SQL
|
||||
"select u2.name2 as name3 from (select u1.name1 as name2 from (select username as name1 from sys_user) u1) u2",
|
||||
// 字段子查询SQL
|
||||
"select id, (select username as name1 from sys_user u2 where u1.id = u2.id) as name2 from sys_user u1",
|
||||
// 带条件的SQL(不解析where条件里的字段,但不影响解析查询字段)
|
||||
"select username as name1 from sys_user where realname LIKE '%张%'",
|
||||
// 多重复杂关联表查询解析,包含的表为:sys_user, sys_depart, sys_dict_item, demo
|
||||
"" +
|
||||
"SELECT " +
|
||||
" u.*, d.age, sd.item_text AS sex, (SELECT count(sd.id) FROM sys_depart sd) AS count " +
|
||||
"FROM " +
|
||||
" (SELECT sd.username AS foo, sd.realname FROM sys_user sd) u, " +
|
||||
" demo d " +
|
||||
"LEFT JOIN sys_dict_item AS sd ON d.sex = sd.item_value " +
|
||||
"WHERE sd.dict_id = '3d9a351be3436fbefb1307d4cfb49bf2'",
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testParseSelectSql() {
|
||||
System.out.println("-----------------------------------------");
|
||||
for (String sql : sqlList) {
|
||||
System.out.println("待测试的sql:" + sql);
|
||||
try {
|
||||
// 解析所有的表名,key=表名,value=解析后的sql信息
|
||||
Map<String, SelectSqlInfo> parsedMap = JSqlParserUtils.parseAllSelectTable(sql);
|
||||
assert parsedMap != null;
|
||||
for (Map.Entry<String, SelectSqlInfo> entry : parsedMap.entrySet()) {
|
||||
System.out.println("表名:" + entry.getKey());
|
||||
this.printSqlInfo(entry.getValue(), 1);
|
||||
}
|
||||
} catch (JSQLParserException e) {
|
||||
System.out.println("SQL解析出现异常:" + e.getMessage());
|
||||
}
|
||||
System.out.println("-----------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
private void printSqlInfo(SelectSqlInfo sqlInfo, int level) {
|
||||
String beforeStr = this.getBeforeStr(level);
|
||||
if (sqlInfo.getFromTableName() == null) {
|
||||
// 子查询
|
||||
System.out.println(beforeStr + "子查询:" + sqlInfo.getFromSubSelect().getParsedSql());
|
||||
this.printSqlInfo(sqlInfo.getFromSubSelect(), level + 1);
|
||||
} else {
|
||||
// 非子查询
|
||||
System.out.println(beforeStr + "查询的表名:" + sqlInfo.getFromTableName());
|
||||
}
|
||||
if (oConvertUtils.isNotEmpty(sqlInfo.getFromTableAliasName())) {
|
||||
System.out.println(beforeStr + "查询的表别名:" + sqlInfo.getFromTableAliasName());
|
||||
}
|
||||
if (sqlInfo.isSelectAll()) {
|
||||
System.out.println(beforeStr + "查询的字段:*");
|
||||
} else {
|
||||
System.out.println(beforeStr + "查询的字段:" + sqlInfo.getSelectFields());
|
||||
System.out.println(beforeStr + "真实的字段:" + sqlInfo.getRealSelectFields());
|
||||
if (sqlInfo.getFromTableName() == null) {
|
||||
System.out.println(beforeStr + "所有的字段(包括子查询):" + sqlInfo.getAllRealSelectFields());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打印前缀,根据层级来打印
|
||||
private String getBeforeStr(int level) {
|
||||
if (level == 0) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder beforeStr = new StringBuilder();
|
||||
for (int i = 0; i < level; i++) {
|
||||
beforeStr.append(" ");
|
||||
}
|
||||
beforeStr.append("- ");
|
||||
return beforeStr.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.5.1</version>
|
||||
<version>3.6.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -16,6 +16,12 @@
|
|||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- chatgpt -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue