升级jeecg-boot3.6.3
This commit is contained in:
parent
0de3fb15ab
commit
ac5a285e63
72
README.md
72
README.md
|
|
@ -7,13 +7,13 @@
|
||||||
JEECG BOOT 低代码开发平台
|
JEECG BOOT 低代码开发平台
|
||||||
===============
|
===============
|
||||||
|
|
||||||
当前最新版本: 3.5.3(发布日期:2023-07-24)
|
当前最新版本: 3.6.3(发布日期:2024-03-11)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||||
[](http://www.jeecg.com)
|
[](http://jeecg.com/aboutusIndex)
|
||||||
[](https://jeecg.blog.csdn.net)
|
[](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)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
|
|
||||||
|
|
@ -44,22 +44,22 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
||||||
|
|
||||||
项目源码
|
项目源码
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
| 仓库 |前端 Vue3版 | 前端 Vue2版 | 后端源码 |
|
| 仓库 |前端源码 Vue3版 | 后端JAVA源码 |
|
||||||
|-|-|-|-|
|
|-|-|-|
|
||||||
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [ant-design-vue-jeecg](https://github.com/jeecgboot/ant-design-vue-jeecg) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
|
| 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) | [ant-design-vue-jeecg](https://gitee.com/jeecg/ant-design-vue-jeecg) | [jeecg-boot](https://gitee.com/jeecg/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版本) |
|
||||||
| `jeecgboot-vue3` | Vue3+TS 新版前端源码 |
|
| `jeecg-boot` | 后端JAVA源码(支持微服务) |
|
||||||
| `ant-design-vue-jeecg` |Vue2版前端源码 |
|
|
||||||
| `jeecg-uniapp` | [APP开发框架,一份代码多终端适配,同时支持APP、小程序、H5](https://github.com/jeecgboot/jeecg-uniapp) |
|
| `jeecg-uniapp` | [APP开发框架,一份代码多终端适配,同时支持APP、小程序、H5](https://github.com/jeecgboot/jeecg-uniapp) |
|
||||||
| `jeecg-boot-starter` | [Stater依赖项目单独维护,点击下载](https://gitee.com/jeecg/jeecg-boot-starter) |
|
| `更多开源项目` | [更多底层源码下载](http://jeecg.com/download) |
|
||||||
| `更多开源项目` | [更多源码下载](http://jeecg.com/download) |
|
|
||||||
|
|
||||||
|
|
||||||
快速搭建开发环境
|
快速搭建开发环境
|
||||||
|
|
@ -83,16 +83,22 @@ Docker快速启动项目
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
- 项目官网: [http://www.jeecg.com](http://www.jeecg.com)
|
- 项目官网: [http://www.jeecg.com](http://www.jeecg.com)
|
||||||
- 开发文档: [http://help.jeecg.com](http://help.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)
|
- 新手指南: [快速入门](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) | [Vue2演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
|
- 在线演示 : [Vue3演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [敲敲云零代码](https://qiaoqiaoyun.com)
|
||||||
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
||||||
>
|
>
|
||||||
- QQ交流群 : ⑦791696430、⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
|
- QQ交流群 : ⑧825232878、⑦791696430(满)、⑥730954414(满)、683903138(满)、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
|
||||||
> ` 提醒:【QQ群是自助服务群,建议给帮助您解决问题的同学发送指定红包,表示感谢!】 `
|
> ` 提醒:【QQ群是自助服务群,建议给帮助您解决问题的同学发送指定红包,表示感谢!】 `
|
||||||
|
|
||||||
|
|
||||||
|
大龄码农的思考
|
||||||
|
-----------------------------------
|
||||||
|
> 作为码农年纪大了写不动代码了怎么办??哎!!
|
||||||
|
所以我们团队在追求不写代码也可实现复杂业务系统!目前已经做到了,不信你到敲敲云零代码试试(通过流程串联修改业务数据)
|
||||||
|
|
||||||
|
- https://www.qiaoqiaoyun.com
|
||||||
|
|
||||||
|
|
||||||
技术支持
|
技术支持
|
||||||
|
|
@ -105,39 +111,32 @@ Docker快速启动项目
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=======【VUE2版本专题介绍】============================================
|
|
||||||
|
|
||||||
VUE2版本专题介绍
|
VUE2版本专题介绍
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
#### 项目介绍
|
#### 项目介绍
|
||||||
- 项目名称:ant-design-vue-jeecg
|
- 项目名称:ant-design-vue-jeecg
|
||||||
- 说明:JeecgBoot前端提供两套解决方案,一套VUE2和一套VUE3版本,目前vue2版本最新代码只支持到jeecgboot 3.4.3版本,一定注意。
|
- 说明:JeecgBoot前端提供两套解决方案,一套VUE2和一套VUE3版本,目前vue2版本最新代码只支持到jeecgboot 3.4.3版本,一定注意。
|
||||||
- 更多介绍:[Vue2版演示](http://boot.jeecg.com) |[开发文档](http://doc.jeecg.com)
|
|
||||||
- [快速启动——Vue2前端](http://doc.jeecg.com/2678320)
|
|
||||||
- [Docker启动——Vue2前端](http://doc.jeecg.com/3043612)
|
|
||||||
|
|
||||||
|
|
||||||
#### Vue2与Vue3版本区别
|
|
||||||
> - VUE3版本彻底抛弃IE兼容,不兼容IE和低版本浏览器,只适配高版本谷歌和Edge
|
|
||||||
(政府、事业类单位项目需要谨慎选择——国产化迁移是一个漫长的过程,万一过程中要求IE兼容,这个不可逆)
|
|
||||||
> - 所以如果对浏览器有要求的项目,请选择VUE2版本。
|
|
||||||
> - VUE3版是全新的技术栈,紧跟主流(前端重写),各个功能都做了优化,拥有更好的体验效果
|
|
||||||
|
|
||||||
|
|
||||||
#### 源码下载
|
#### 源码下载
|
||||||
| 源码 | 源码地址 |
|
| 源码 | 源码地址 |
|
||||||
|--------------------|------------------------|
|
|--------------------|------------------------|
|
||||||
| 后端源码 `Vue2版` |https://gitee.com/jeecg/jeecg-boot/tree/v3.4.3last |
|
| 后端JAVA源码 `Vue2版` |https://gitee.com/jeecg/jeecg-boot/tree/v3.4.3last |
|
||||||
| 前端源码 `Vue2版` |https://gitee.com/jeecg/ant-design-vue-jeecg |
|
| 前端vue2源码 `Vue2版` |https://gitee.com/jeecg/ant-design-vue-jeecg |
|
||||||
|
|
||||||
=========【VUE2版本专题介绍】=========================================
|
#### 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走势图
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
##### Star走势图
|
|
||||||
|
|
||||||
[](https://star-history.com/#jeecgboot/jeecg-boot)
|
[](https://star-history.com/#jeecgboot/jeecg-boot)
|
||||||
|
|
||||||
|
|
@ -468,8 +467,11 @@ VUE2版本专题介绍
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 流程引擎推荐
|
||||||
|
|
||||||
|
JeecgBoot企业版本默认集成了activiti和flowable两套方案,大家在使用本开源项目时,如果想进一步集成流程引擎,推荐结合贺波老师的书 [《深入Activiti流程引擎:核心原理与高阶实战》](https://item.m.jd.com/product/13928958.html?gx=RnAomTM2bmCImZxDqYAkVCoIHuIYVqc)
|
||||||
|
|
||||||
|
<img src="https://jeecgos.oss-cn-beijing.aliyuncs.com/files/tuijian20231220161656.png" width="25%" height="auto">
|
||||||
|
|
||||||
|
|
||||||
### 系统效果
|
### 系统效果
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-parent</artifactId>
|
<artifactId>jeecg-boot-parent</artifactId>
|
||||||
<version>3.5.3</version>
|
<version>3.6.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jeecg-boot-base-core</artifactId>
|
<artifactId>jeecg-boot-base-core</artifactId>
|
||||||
|
|
@ -175,6 +175,10 @@
|
||||||
<groupId>org.apache.shiro</groupId>
|
<groupId>org.apache.shiro</groupId>
|
||||||
<artifactId>shiro-core</artifactId>
|
<artifactId>shiro-core</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>checkstyle</artifactId>
|
||||||
|
<groupId>com.puppycrawl.tools</groupId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
@ -252,6 +256,15 @@
|
||||||
<groupId>commons-fileupload</groupId>
|
<groupId>commons-fileupload</groupId>
|
||||||
<artifactId>commons-fileupload</artifactId>
|
<artifactId>commons-fileupload</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!--加载hutool-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -22,10 +22,10 @@ public interface CommonAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2查询用户权限信息
|
* 2查询用户权限信息
|
||||||
* @param username
|
* @param userId
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Set<String> queryUserAuths(String username);
|
Set<String> queryUserAuths(String userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel
|
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel
|
||||||
|
|
@ -102,12 +102,12 @@ public interface CommonAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 13获取表数据字典
|
* 13获取表数据字典
|
||||||
* @param table
|
* @param tableFilterSql
|
||||||
* @param text
|
* @param text
|
||||||
* @param code
|
* @param code
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<DictModel> queryTableDictItemsByCode(String table, String text, String code);
|
List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 14 普通字典的翻译,根据多个dictCode和多条数据,多个以逗号分割
|
* 14 普通字典的翻译,根据多个dictCode和多条数据,多个以逗号分割
|
||||||
|
|
@ -117,14 +117,17 @@ public interface CommonAPI {
|
||||||
*/
|
*/
|
||||||
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
/**
|
/**
|
||||||
* 15 字典表的 翻译,可批量
|
* 15 字典表的 翻译,可批量
|
||||||
* @param table
|
* @param table
|
||||||
* @param text
|
* @param text
|
||||||
* @param code
|
* @param code
|
||||||
* @param keys 多个用逗号分割
|
* @param keys 多个用逗号分割
|
||||||
|
* @param dataSource 数据源
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys);
|
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ public class DataLogDTO {
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
private String createName;
|
||||||
|
|
||||||
public DataLogDTO(){
|
public DataLogDTO(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,13 @@ public class OnlineAuthDTO implements Serializable {
|
||||||
*/
|
*/
|
||||||
private String onlineFormUrl;
|
private String onlineFormUrl;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
||||||
|
/**
|
||||||
|
* online工单的地址
|
||||||
|
*/
|
||||||
|
private String onlineWorkOrderUrl;
|
||||||
|
//update-end---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
||||||
|
|
||||||
public OnlineAuthDTO(){
|
public OnlineAuthDTO(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import lombok.Data;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 普通消息
|
* 普通消息
|
||||||
|
|
@ -44,13 +44,6 @@ public class MessageDTO implements Serializable {
|
||||||
*/
|
*/
|
||||||
protected String category;
|
protected String category;
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
//update-begin---author:taoyan ---date:20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模板消息对应的模板编码
|
|
||||||
*/
|
|
||||||
protected String templateCode;
|
|
||||||
/**
|
/**
|
||||||
* 消息类型:org.jeecg.common.constant.enums.MessageTypeEnum
|
* 消息类型:org.jeecg.common.constant.enums.MessageTypeEnum
|
||||||
* XT("system", "系统消息")
|
* XT("system", "系统消息")
|
||||||
|
|
@ -60,24 +53,39 @@ public class MessageDTO implements Serializable {
|
||||||
*/
|
*/
|
||||||
protected String type;
|
protected String type;
|
||||||
|
|
||||||
|
|
||||||
|
//---【推送模板相关参数】-------------------------------------------------------------
|
||||||
/**
|
/**
|
||||||
* 是否发送Markdown格式的消息
|
* 是否发送Markdown格式的消息
|
||||||
*/
|
*/
|
||||||
protected boolean isMarkdown;
|
protected boolean isMarkdown;
|
||||||
|
/**
|
||||||
|
* 模板消息对应的模板编码
|
||||||
|
*/
|
||||||
|
protected String templateCode;
|
||||||
/**
|
/**
|
||||||
* 解析模板内容 对应的数据
|
* 解析模板内容 对应的数据
|
||||||
*/
|
*/
|
||||||
protected Map<String, Object> data;
|
protected Map<String, Object> data;
|
||||||
//update-end---author:taoyan ---date::20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
//---【推送模板相关参数】-------------------------------------------------------------
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
//---【邮件相关参数】-------------------------------------------------------------
|
||||||
/**
|
/**
|
||||||
* 抄送人
|
* 邮件抄送人
|
||||||
*/
|
*/
|
||||||
private String copyToUser;
|
private String copyToUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件推送地址
|
||||||
|
*/
|
||||||
|
protected Set<String> toEmailList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件抄送地址
|
||||||
|
*/
|
||||||
|
protected Set<String> ccEmailList;
|
||||||
|
//---【邮件相关参数】-------------------------------------------------------------
|
||||||
|
|
||||||
public MessageDTO(){
|
public MessageDTO(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,15 @@ public class DictAspect {
|
||||||
String code = field.getAnnotation(Dict.class).dicCode();
|
String code = field.getAnnotation(Dict.class).dicCode();
|
||||||
String text = field.getAnnotation(Dict.class).dicText();
|
String text = field.getAnnotation(Dict.class).dicText();
|
||||||
String table = field.getAnnotation(Dict.class).dictTable();
|
String table = field.getAnnotation(Dict.class).dictTable();
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
List<String> dataList;
|
List<String> dataList;
|
||||||
String dictCode = code;
|
String dictCode = code;
|
||||||
if (!StringUtils.isEmpty(table)) {
|
if (!StringUtils.isEmpty(table)) {
|
||||||
dictCode = String.format("%s,%s,%s", table, text, code);
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
}
|
}
|
||||||
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||||
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
||||||
|
|
@ -169,10 +173,15 @@ public class DictAspect {
|
||||||
String code = field.getAnnotation(Dict.class).dicCode();
|
String code = field.getAnnotation(Dict.class).dicCode();
|
||||||
String text = field.getAnnotation(Dict.class).dicText();
|
String text = field.getAnnotation(Dict.class).dicText();
|
||||||
String table = field.getAnnotation(Dict.class).dictTable();
|
String table = field.getAnnotation(Dict.class).dictTable();
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
// 自定义的字典表数据源
|
||||||
|
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
String fieldDictCode = code;
|
String fieldDictCode = code;
|
||||||
if (!StringUtils.isEmpty(table)) {
|
if (!StringUtils.isEmpty(table)) {
|
||||||
fieldDictCode = String.format("%s,%s,%s", table, text, code);
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
}
|
}
|
||||||
|
|
||||||
String value = record.getString(field.getName());
|
String value = record.getString(field.getName());
|
||||||
|
|
@ -274,9 +283,25 @@ public class DictAspect {
|
||||||
String[] arr = dictCode.split(",");
|
String[] arr = dictCode.split(",");
|
||||||
String table = arr[0], text = arr[1], code = arr[2];
|
String table = arr[0], text = arr[1], code = arr[2];
|
||||||
String values = String.join(",", needTranslDataTable);
|
String values = String.join(",", needTranslDataTable);
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
// 自定义的数据源
|
||||||
|
String dataSource = null;
|
||||||
|
if (arr.length > 3) {
|
||||||
|
dataSource = arr[3];
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
|
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
|
||||||
log.debug("translateDictFromTableByKeys.values:" + values);
|
log.debug("translateDictFromTableByKeys.values:" + values);
|
||||||
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values);
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
|
||||||
|
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||||
|
if(null == dataSource){
|
||||||
|
dataSource = "";
|
||||||
|
}
|
||||||
|
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||||
|
|
||||||
|
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
log.debug("translateDictFromTableByKeys.result:" + texts);
|
log.debug("translateDictFromTableByKeys.result:" + texts);
|
||||||
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||||
list.addAll(texts);
|
list.addAll(texts);
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,7 @@ public class PermissionDataAspect {
|
||||||
requestPath = filterUrl(requestPath);
|
requestPath = filterUrl(requestPath);
|
||||||
//update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
//update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
||||||
//先判断是否online报表请求
|
//先判断是否online报表请求
|
||||||
// TODO 参数顺序调整有隐患
|
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
|
||||||
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0){
|
|
||||||
// 获取地址栏参数
|
// 获取地址栏参数
|
||||||
String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR);
|
String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR);
|
||||||
if(oConvertUtils.isNotEmpty(urlParamString)){
|
if(oConvertUtils.isNotEmpty(urlParamString)){
|
||||||
|
|
@ -68,7 +67,7 @@ public class PermissionDataAspect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
//update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
||||||
log.info("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
||||||
String username = JwtUtil.getUserNameByToken(request);
|
String username = JwtUtil.getUserNameByToken(request);
|
||||||
//查询数据权限信息
|
//查询数据权限信息
|
||||||
//TODO 微服务情况下也得支持缓存机制
|
//TODO 微服务情况下也得支持缓存机制
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ public enum UrlMatchEnum {
|
||||||
CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"),
|
CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"),
|
||||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */
|
/**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */
|
||||||
CGREPORT_DATA("/online/cgreport/api/getColumnsAndData/", "/online/cgreport/"),
|
CGREPORT_DATA("/online/cgreport/api/getColumnsAndData/", "/online/cgreport/"),
|
||||||
|
/** 求URL与菜单路由URL转换规则/online/cgreport/api/getData/ 【vue3报表数据请求地址】 */
|
||||||
|
CGREPORT_ONLY_DATA("/online/cgreport/api/getData/", "/online/cgreport/"),
|
||||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportXls/ */
|
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportXls/ */
|
||||||
CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"),
|
CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"),
|
||||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */
|
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */
|
||||||
|
|
|
||||||
|
|
@ -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
|
* @return 返回类型: String
|
||||||
*/
|
*/
|
||||||
String dictTable() default "";
|
String dictTable() default "";
|
||||||
|
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
/**
|
||||||
|
* 方法描述: 数据字典表所在数据源名称
|
||||||
|
* 作 者: chenrui
|
||||||
|
* 日 期: 2023年12月20日-下午4:58
|
||||||
|
*
|
||||||
|
* @return 返回类型: String
|
||||||
|
*/
|
||||||
|
String ds() default "";
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ public interface CommonConstant {
|
||||||
|
|
||||||
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
|
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
|
||||||
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
|
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
|
||||||
|
/** {@code 404 Not Found} (HTTP/1.0 - RFC 1945) */
|
||||||
|
Integer SC_INTERNAL_NOT_FOUND_404 = 404;
|
||||||
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
|
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
|
||||||
Integer SC_OK_200 = 200;
|
Integer SC_OK_200 = 200;
|
||||||
|
|
||||||
|
|
@ -112,8 +114,8 @@ public interface CommonConstant {
|
||||||
String HAS_CANCLE = "2";
|
String HAS_CANCLE = "2";
|
||||||
|
|
||||||
/**阅读状态(0未读,1已读)*/
|
/**阅读状态(0未读,1已读)*/
|
||||||
String HAS_READ_FLAG = "1";
|
Integer HAS_READ_FLAG = 1;
|
||||||
String NO_READ_FLAG = "0";
|
Integer NO_READ_FLAG = 0;
|
||||||
|
|
||||||
/**优先级(L低,M中,H高)*/
|
/**优先级(L低,M中,H高)*/
|
||||||
String PRIORITY_L = "L";
|
String PRIORITY_L = "L";
|
||||||
|
|
@ -160,6 +162,8 @@ public interface CommonConstant {
|
||||||
|
|
||||||
/**字典翻译文本后缀*/
|
/**字典翻译文本后缀*/
|
||||||
String DICT_TEXT_SUFFIX = "_dictText";
|
String DICT_TEXT_SUFFIX = "_dictText";
|
||||||
|
/**字典翻译颜色后缀*/
|
||||||
|
String DICT_COLOR_SUFFIX = "_dictColor";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单设计器主表类型
|
* 表单设计器主表类型
|
||||||
|
|
@ -315,6 +319,8 @@ public interface CommonConstant {
|
||||||
String X_TIMESTAMP = "X-TIMESTAMP";
|
String X_TIMESTAMP = "X-TIMESTAMP";
|
||||||
/** 租户请求头 更名为:X-Tenant-Id */
|
/** 租户请求头 更名为:X-Tenant-Id */
|
||||||
String TENANT_ID = "X-Tenant-Id";
|
String TENANT_ID = "X-Tenant-Id";
|
||||||
|
/** 简流接口请求头,用于排除不支持的控件字段 */
|
||||||
|
String X_MiniFlowExclusionFieldMode = "X-Miniflowexclusionfieldmode";
|
||||||
/**===============================================================================================*/
|
/**===============================================================================================*/
|
||||||
|
|
||||||
String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
|
String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
|
||||||
|
|
@ -371,6 +377,8 @@ public interface CommonConstant {
|
||||||
/**前端vue3版本Header参数名*/
|
/**前端vue3版本Header参数名*/
|
||||||
String VERSION="X-Version";
|
String VERSION="X-Version";
|
||||||
|
|
||||||
|
String VERSION_V3 = "v3";
|
||||||
|
|
||||||
/**存储在线程变量里的动态表名*/
|
/**存储在线程变量里的动态表名*/
|
||||||
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
|
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
|
||||||
/**
|
/**
|
||||||
|
|
@ -388,6 +396,7 @@ public interface CommonConstant {
|
||||||
/** 部门表唯一key,orgCode */
|
/** 部门表唯一key,orgCode */
|
||||||
String DEPART_KEY_ORG_CODE = "orgCode";
|
String DEPART_KEY_ORG_CODE = "orgCode";
|
||||||
|
|
||||||
|
/**======【消息推送相关】==============================================================================*/
|
||||||
/**
|
/**
|
||||||
* 发消息 会传递一些信息到map
|
* 发消息 会传递一些信息到map
|
||||||
*/
|
*/
|
||||||
|
|
@ -398,6 +407,11 @@ public interface CommonConstant {
|
||||||
*/
|
*/
|
||||||
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
|
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发消息 消息业务类型
|
||||||
|
*/
|
||||||
|
String NOTICE_MSG_BUS_TYPE = "NOTICE_MSG_BUS_TYPE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
|
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
|
||||||
*/
|
*/
|
||||||
|
|
@ -420,6 +434,7 @@ public interface CommonConstant {
|
||||||
|
|
||||||
/** 消息模板:markdown */
|
/** 消息模板:markdown */
|
||||||
String MSG_TEMPLATE_TYPE_MD = "5";
|
String MSG_TEMPLATE_TYPE_MD = "5";
|
||||||
|
/**========【消息推送相关】==========================================================================*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信验证码redis-key的前缀
|
* 短信验证码redis-key的前缀
|
||||||
|
|
@ -481,6 +496,11 @@ public interface CommonConstant {
|
||||||
*/
|
*/
|
||||||
String USER_TENANT_REFUSE = "4";
|
String USER_TENANT_REFUSE = "4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户租户状态(邀请)
|
||||||
|
*/
|
||||||
|
String USER_TENANT_INVITE = "5";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不是叶子节点
|
* 不是叶子节点
|
||||||
*/
|
*/
|
||||||
|
|
@ -490,4 +510,71 @@ public interface CommonConstant {
|
||||||
* 是叶子节点
|
* 是叶子节点
|
||||||
*/
|
*/
|
||||||
Integer IS_LEAF = 1;
|
Integer IS_LEAF = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 钉钉
|
||||||
|
*/
|
||||||
|
String DINGTALK = "DINGTALK";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信
|
||||||
|
*/
|
||||||
|
String WECHAT_ENTERPRISE = "WECHAT_ENTERPRISE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统默认租户id 0
|
||||||
|
*/
|
||||||
|
Integer TENANT_ID_DEFAULT_VALUE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【low-app用】 应用级别的复制
|
||||||
|
*/
|
||||||
|
String COPY_LEVEL_APP = "app";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【low-app用】 菜单级别的复制
|
||||||
|
*/
|
||||||
|
String COPY_LEVEL_MENU = "menu";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【low-app用】 应用备份
|
||||||
|
*/
|
||||||
|
String COPY_LEVEL_BAK = "backup";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【low-app用】 从备份还原
|
||||||
|
*/
|
||||||
|
String COPY_LEVEL_COVER = "cover";
|
||||||
|
|
||||||
|
/** 【QQYUN-6034】关联字段变更历史值,缓存半个小时 */
|
||||||
|
String CACHE_REL_FIELD_OLD_VAL = "sys:cache:desform:relFieldOldVal:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序类型:升序
|
||||||
|
*/
|
||||||
|
String ORDER_TYPE_ASC = "ASC";
|
||||||
|
/**
|
||||||
|
* 排序类型:降序
|
||||||
|
*/
|
||||||
|
String ORDER_TYPE_DESC = "DESC";
|
||||||
|
|
||||||
|
|
||||||
|
//update-begin---author:scott ---date:2023-09-10 for:积木报表常量----
|
||||||
|
/**
|
||||||
|
* 报表允许设计开发的角色
|
||||||
|
*/
|
||||||
|
public static String[] allowDevRoles = new String[]{"lowdeveloper", "admin"};
|
||||||
|
/**
|
||||||
|
* 【对应积木报表的常量】
|
||||||
|
* 数据隔离模式: 按照创建人隔离
|
||||||
|
*/
|
||||||
|
public static final String SAAS_MODE_CREATED = "created";
|
||||||
|
/**
|
||||||
|
* 【对应积木报表的常量】
|
||||||
|
* 数据隔离模式: 按照租户隔离
|
||||||
|
*/
|
||||||
|
public static final String SAAS_MODE_TENANT = "tenant";
|
||||||
|
//update-end---author:scott ---date::2023-09-10 for:积木报表常量----
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,21 @@ public interface CommonSendStatus {
|
||||||
public static final String APP_SESSION_SUFFIX = "_app";
|
public static final String APP_SESSION_SUFFIX = "_app";
|
||||||
|
|
||||||
|
|
||||||
|
/**-----【流程相关通知模板code】------------------------------------------------------------*/
|
||||||
/**流程催办——系统通知消息模板*/
|
/**流程催办——系统通知消息模板*/
|
||||||
public static final String TZMB_BPM_CUIBAN = "bpm_cuiban";
|
public static final String TZMB_BPM_CUIBAN = "bpm_cuiban";
|
||||||
|
/**流程抄送——系统通知消息模板*/
|
||||||
|
public static final String TZMB_BPM_CC = "bpm_cc";
|
||||||
/**流程催办——邮件通知消息模板*/
|
/**流程催办——邮件通知消息模板*/
|
||||||
public static final String TZMB_BPM_CUIBAN_EMAIL = "bpm_cuiban_email";
|
public static final String TZMB_BPM_CUIBAN_EMAIL = "bpm_cuiban_email";
|
||||||
/**标准模板—系统消息通知*/
|
/**标准模板—系统消息通知*/
|
||||||
public static final String TZMB_SYS_TS_NOTE = "sys_ts_note";
|
public static final String TZMB_SYS_TS_NOTE = "sys_ts_note";
|
||||||
/**流程超时提醒——系统通知消息模板*/
|
/**流程超时提醒——系统通知消息模板*/
|
||||||
public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip";
|
public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip";
|
||||||
|
/**-----【流程相关通知模板code】-----------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统通知拓展参数(比如:用于流程抄送和催办通知,这里额外传递流程跳转页面所需要的路由参数)
|
||||||
|
*/
|
||||||
|
public static final String MSG_ABSTRACT_JSON = "msg_abstract";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ public interface DataBaseConstant {
|
||||||
/**postgreSQL达梦数据库*/
|
/**postgreSQL达梦数据库*/
|
||||||
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
|
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
|
||||||
|
|
||||||
|
/**人大金仓数据库*/
|
||||||
|
public static final String DB_TYPE_KINGBASEES = "KINGBASEES";
|
||||||
|
|
||||||
/**sqlserver数据库*/
|
/**sqlserver数据库*/
|
||||||
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
|
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,4 +116,8 @@ public class SymbolConstant {
|
||||||
*/
|
*/
|
||||||
public static final String SQUARE_BRACKETS_RIGHT = "]";
|
public static final String SQUARE_BRACKETS_RIGHT = "]";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接字符串符号 分号 ;
|
||||||
|
*/
|
||||||
|
public static final String SEMICOLON = ";";
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ public enum CgformEnum {
|
||||||
/**
|
/**
|
||||||
* 多表 (erp风格)
|
* 多表 (erp风格)
|
||||||
*/
|
*/
|
||||||
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格" ,new String[]{"vue3","vue"}),
|
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格" ,new String[]{"vue3","vue","vue3Native"}),
|
||||||
/**
|
/**
|
||||||
* 多表(内嵌子表风格)
|
* 多表(内嵌子表风格)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.jeecg.common.util;
|
package org.jeecg.common.constant.enums;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
|
@ -17,7 +17,11 @@ public enum DySmsEnum {
|
||||||
/**会议通知*/
|
/**会议通知*/
|
||||||
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"),
|
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"),
|
||||||
/**我的计划通知*/
|
/**我的计划通知*/
|
||||||
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time");
|
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time"),
|
||||||
|
/**支付成功短信通知*/
|
||||||
|
PAY_SUCCESS_NOTICE_CODE("SMS_461735163","敲敲云","realname,money,endTime"),
|
||||||
|
/**会员到期通知提醒*/
|
||||||
|
VIP_EXPIRE_NOTICE_CODE("SMS_461885023","敲敲云","realname,endTime");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信模板编码
|
* 短信模板编码
|
||||||
|
|
@ -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 {
|
public enum FileTypeEnum {
|
||||||
// 文档类型(folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频)
|
// 文档类型(folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频 voice:语音)
|
||||||
// FOLDER
|
// FOLDER
|
||||||
xls(".xls","excel","excel"),
|
xls(".xls","excel","excel"),
|
||||||
xlsx(".xlsx","excel","excel"),
|
xlsx(".xlsx","excel","excel"),
|
||||||
|
|
@ -26,7 +26,8 @@ public enum FileTypeEnum {
|
||||||
flv(".flv","video","视频"),
|
flv(".flv","video","视频"),
|
||||||
mp4(".mp4","video","视频"),
|
mp4(".mp4","video","视频"),
|
||||||
zip(".zip","zip","压缩包"),
|
zip(".zip","zip","压缩包"),
|
||||||
pdf(".pdf","pdf","pdf");
|
pdf(".pdf","pdf","pdf"),
|
||||||
|
mp3(".mp3","mp3","语音");
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
private String value;
|
private String value;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
public enum RoleIndexConfigEnum {
|
||||||
|
|
||||||
/**首页自定义 admin*/
|
/**首页自定义 admin*/
|
||||||
ADMIN("admin", "dashboard/Analysis"),
|
// ADMIN("admin", "dashboard/Analysis"),
|
||||||
//TEST("test", "dashboard/IndexChart"),
|
//TEST("test", "dashboard/IndexChart"),
|
||||||
/**首页自定义 hr*/
|
/**首页自定义 hr*/
|
||||||
HR("hr", "dashboard/IndexBdc");
|
// HR("hr", "dashboard/IndexBdc");
|
||||||
|
|
||||||
//DM("dm", "dashboard/IndexTask"),
|
//DM("dm", "dashboard/IndexTask"),
|
||||||
|
|
||||||
|
// 注:此值仅为防止报错,无任何实际意义
|
||||||
|
ROLE_INDEX_CONFIG_ENUM("RoleIndexConfigEnumDefault", "dashboard/Analysis");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色编码
|
* 角色编码
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
package org.jeecg.common.util;
|
package org.jeecg.common.constant.enums;
|
||||||
|
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统公告自定义跳转方式
|
* 系统公告自定义跳转方式
|
||||||
|
|
@ -12,7 +14,16 @@ public enum SysAnnmentTypeEnum {
|
||||||
/**
|
/**
|
||||||
* 流程跳转到我的任务
|
* 流程跳转到我的任务
|
||||||
*/
|
*/
|
||||||
BPM("bpm", "url", "/bpm/task/MyTaskList");
|
BPM("bpm", "url", "/bpm/task/MyTaskList"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程抄送任务
|
||||||
|
*/
|
||||||
|
BPM_VIEW("bpm_cc", "url", "/bpm/task/MyTaskList"),
|
||||||
|
/**
|
||||||
|
* 邀请用户跳转到个人设置
|
||||||
|
*/
|
||||||
|
TENANT_INVITE("tenant_invite", "url", "/system/usersetting");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务类型(email:邮件 bpm:流程)
|
* 业务类型(email:邮件 bpm:流程)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.jeecg.modules.message.enums;
|
package org.jeecg.common.constant.enums;
|
||||||
|
|
||||||
import org.jeecg.common.system.annotation.EnumDict;
|
import org.jeecg.common.system.annotation.EnumDict;
|
||||||
import org.jeecg.common.system.vo.DictModel;
|
import org.jeecg.common.system.vo.DictModel;
|
||||||
|
|
@ -19,6 +19,16 @@ public enum Vue3MessageHrefEnum {
|
||||||
*/
|
*/
|
||||||
BPM("bpm", "/task/myHandleTaskInfo"),
|
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;
|
package org.jeecg.common.exception;
|
||||||
|
|
||||||
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: jeecg-boot自定义异常
|
* @Description: jeecg-boot自定义异常
|
||||||
* @author: jeecg-boot
|
* @author: jeecg-boot
|
||||||
|
|
@ -7,10 +9,24 @@ package org.jeecg.common.exception;
|
||||||
public class JeecgBootException extends RuntimeException {
|
public class JeecgBootException extends RuntimeException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回给前端的错误code
|
||||||
|
*/
|
||||||
|
private int errCode = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
|
||||||
|
|
||||||
public JeecgBootException(String message){
|
public JeecgBootException(String message){
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JeecgBootException(String message, int errCode){
|
||||||
|
super(message);
|
||||||
|
this.errCode = errCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrCode() {
|
||||||
|
return errCode;
|
||||||
|
}
|
||||||
|
|
||||||
public JeecgBootException(Throwable cause)
|
public JeecgBootException(Throwable cause)
|
||||||
{
|
{
|
||||||
super(cause);
|
super(cause);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.jeecg.common.exception;
|
package org.jeecg.common.exception;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.shiro.authz.AuthorizationException;
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
import org.apache.shiro.authz.UnauthorizedException;
|
import org.apache.shiro.authz.UnauthorizedException;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
|
@ -16,8 +17,6 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异常处理器
|
* 异常处理器
|
||||||
*
|
*
|
||||||
|
|
@ -34,7 +33,7 @@ public class JeecgBootExceptionHandler {
|
||||||
@ExceptionHandler(JeecgBootException.class)
|
@ExceptionHandler(JeecgBootException.class)
|
||||||
public Result<?> handleJeecgBootException(JeecgBootException e){
|
public Result<?> handleJeecgBootException(JeecgBootException e){
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getErrCode(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -133,4 +132,24 @@ public class JeecgBootExceptionHandler {
|
||||||
return Result.error("Redis 连接异常!");
|
return Result.error("Redis 连接异常!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL注入风险,全局异常处理
|
||||||
|
*
|
||||||
|
* @param exception
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(JeecgSqlInjectionException.class)
|
||||||
|
public Result<?> handleSQLException(Exception exception) {
|
||||||
|
String msg = exception.getMessage().toLowerCase();
|
||||||
|
final String extractvalue = "extractvalue";
|
||||||
|
final String updatexml = "updatexml";
|
||||||
|
boolean hasSensitiveInformation = msg.indexOf(extractvalue) >= 0 || msg.indexOf(updatexml) >= 0;
|
||||||
|
if (msg != null && hasSensitiveInformation) {
|
||||||
|
log.error("校验失败,存在SQL注入风险!{}", msg);
|
||||||
|
return Result.error("校验失败,存在SQL注入风险!");
|
||||||
|
}
|
||||||
|
return Result.error("校验失败,存在SQL注入风险!" + msg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,6 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
|
||||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
@ -29,7 +28,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: Controller基类
|
* @Description: Controller基类
|
||||||
|
|
@ -70,7 +68,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||||
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
||||||
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
||||||
//update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
|
//update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
|
||||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||||
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
||||||
//update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
|
//update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
|
||||||
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
||||||
|
|
@ -110,7 +108,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||||
IPage<T> pageList = service.page(page, queryWrapper);
|
IPage<T> pageList = service.page(page, queryWrapper);
|
||||||
List<T> exportList = pageList.getRecords();
|
List<T> exportList = pageList.getRecords();
|
||||||
Map<String, Object> map = new HashMap<>(5);
|
Map<String, Object> map = new HashMap<>(5);
|
||||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
|
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
|
||||||
exportParams.setType(ExcelType.XSSF);
|
exportParams.setType(ExcelType.XSSF);
|
||||||
//map.put("title",exportParams);
|
//map.put("title",exportParams);
|
||||||
//表格Title
|
//表格Title
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,14 @@ public class QueryCondition implements Serializable {
|
||||||
private String rule;
|
private String rule;
|
||||||
private String val;
|
private String val;
|
||||||
|
|
||||||
|
public QueryCondition(String field, String type, String dbType, String rule, String val) {
|
||||||
|
this.field = field;
|
||||||
|
this.type = type;
|
||||||
|
this.dbType = dbType;
|
||||||
|
this.rule = rule;
|
||||||
|
this.val = val;
|
||||||
|
}
|
||||||
|
|
||||||
public String getField() {
|
public String getField() {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,9 @@ import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
import org.jeecg.common.system.util.JeecgDataAutorUtils;
|
import org.jeecg.common.system.util.JeecgDataAutorUtils;
|
||||||
import org.jeecg.common.system.util.JwtUtil;
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
|
import org.jeecg.common.system.util.SqlConcatUtil;
|
||||||
import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
|
import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
|
||||||
import org.jeecg.common.util.CommonUtils;
|
import org.jeecg.common.util.*;
|
||||||
import org.jeecg.common.util.DateUtils;
|
|
||||||
import org.jeecg.common.util.SqlInjectionUtil;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.springframework.util.NumberUtils;
|
import org.springframework.util.NumberUtils;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
|
@ -143,7 +141,7 @@ public class QueryGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object value = PropertyUtils.getSimpleProperty(searchObj, name);
|
Object value = PropertyUtils.getSimpleProperty(searchObj, name);
|
||||||
column = getTableFieldName(searchObj.getClass(), name);
|
column = ReflectHelper.getTableFieldName(searchObj.getClass(), name);
|
||||||
if(column==null){
|
if(column==null){
|
||||||
//column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了
|
//column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -283,15 +281,9 @@ public class QueryGenerator {
|
||||||
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
|
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
|
||||||
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
|
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
|
||||||
if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) {
|
if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) {
|
||||||
//queryWrapper.orderByAsc(oConvertUtils.camelToUnderline(column));
|
queryWrapper.orderByAsc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
|
||||||
String[] columnArray = columnStr.split(",");
|
|
||||||
queryWrapper.orderByAsc(Arrays.asList(columnArray));
|
|
||||||
} else {
|
} else {
|
||||||
//queryWrapper.orderByDesc(oConvertUtils.camelToUnderline(column));
|
queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
|
||||||
String[] columnArray = columnStr.split(",");
|
|
||||||
queryWrapper.orderByDesc(Arrays.asList(columnArray));
|
|
||||||
}
|
}
|
||||||
//update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
//update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +339,7 @@ public class QueryGenerator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
// update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
||||||
log.info("---高级查询参数-->" + filterConditions);
|
log.debug("---高级查询参数-->" + filterConditions);
|
||||||
|
|
||||||
queryWrapper.and(andWrapper -> {
|
queryWrapper.and(andWrapper -> {
|
||||||
for (int i = 0; i < filterConditions.size(); i++) {
|
for (int i = 0; i < filterConditions.size(); i++) {
|
||||||
|
|
@ -641,11 +633,11 @@ public class QueryGenerator {
|
||||||
* @param value 查询条件值
|
* @param value 查询条件值
|
||||||
*/
|
*/
|
||||||
public static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value) {
|
public static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value) {
|
||||||
if (value == null || rule == null || oConvertUtils.isEmpty(value)) {
|
if (name==null || value == null || rule == null || oConvertUtils.isEmpty(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
name = oConvertUtils.camelToUnderline(name);
|
name = oConvertUtils.camelToUnderline(name);
|
||||||
log.info("---查询过滤器,Query规则---field:{}, rule:{}, value:{}",name,rule.getValue(),value);
|
log.debug("---高级查询 Query规则---field:{} , rule:{} , value:{}",name,rule.getValue(),value);
|
||||||
switch (rule) {
|
switch (rule) {
|
||||||
case GT:
|
case GT:
|
||||||
queryWrapper.gt(name, value);
|
queryWrapper.gt(name, value);
|
||||||
|
|
@ -713,7 +705,14 @@ public class QueryGenerator {
|
||||||
*/
|
*/
|
||||||
public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
|
public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
|
||||||
Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5);
|
Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5);
|
||||||
List<SysPermissionDataRuleModel> list =JeecgDataAutorUtils.loadDataSearchConditon();
|
List<SysPermissionDataRuleModel> list = null;
|
||||||
|
//update-begin-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
||||||
|
try {
|
||||||
|
list = JeecgDataAutorUtils.loadDataSearchConditon();
|
||||||
|
}catch (Exception e){
|
||||||
|
log.error("根据request对象获取权限数据失败,可能是定时任务中执行的。", e);
|
||||||
|
}
|
||||||
|
//update-end-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
||||||
if(list != null&&list.size()>0){
|
if(list != null&&list.size()>0){
|
||||||
if(list.get(0)==null){
|
if(list.get(0)==null){
|
||||||
return ruleMap;
|
return ruleMap;
|
||||||
|
|
@ -821,223 +820,7 @@ public class QueryGenerator {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) {
|
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) {
|
||||||
return getSingleQueryConditionSql(field, alias, value, isString,null);
|
return SqlConcatUtil.getSingleQueryConditionSql(field, alias, value, isString,null);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 报表获取查询条件 支持多数据源
|
|
||||||
* @param field
|
|
||||||
* @param alias
|
|
||||||
* @param value
|
|
||||||
* @param isString
|
|
||||||
* @param dataBaseType
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
|
|
||||||
if (value == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
field = alias+oConvertUtils.camelToUnderline(field);
|
|
||||||
QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
|
|
||||||
return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单个查询条件的值
|
|
||||||
* @param rule
|
|
||||||
* @param field
|
|
||||||
* @param value
|
|
||||||
* @param isString
|
|
||||||
* @param dataBaseType
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
|
|
||||||
String res = "";
|
|
||||||
switch (rule) {
|
|
||||||
case GT:
|
|
||||||
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
case GE:
|
|
||||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
case LT:
|
|
||||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
case LE:
|
|
||||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
case EQ:
|
|
||||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
case EQ_WITH_ADD:
|
|
||||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
case NE:
|
|
||||||
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
case IN:
|
|
||||||
res = field + " in "+getInConditionValue(value, isString);
|
|
||||||
break;
|
|
||||||
case LIKE:
|
|
||||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
|
|
||||||
break;
|
|
||||||
case LEFT_LIKE:
|
|
||||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
|
|
||||||
break;
|
|
||||||
case RIGHT_LIKE:
|
|
||||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单个查询条件的值
|
|
||||||
* @param rule
|
|
||||||
* @param field
|
|
||||||
* @param value
|
|
||||||
* @param isString
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
|
|
||||||
return getSingleSqlByRule(rule, field, value, isString, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取查询条件的值
|
|
||||||
* @param value
|
|
||||||
* @param isString
|
|
||||||
* @param dataBaseType
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
|
|
||||||
String str = value.toString().trim();
|
|
||||||
if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
|
|
||||||
str = str.substring(1);
|
|
||||||
}else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
|
|
||||||
str = str.substring(2);
|
|
||||||
}else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
|
|
||||||
str = str.substring(2);
|
|
||||||
}else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
|
|
||||||
str = str.substring(1);
|
|
||||||
}else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
|
|
||||||
str = str.substring(1);
|
|
||||||
}else if(str.indexOf(QUERY_COMMA_ESCAPE)>0) {
|
|
||||||
str = str.replaceAll("\\+\\+", COMMA);
|
|
||||||
}
|
|
||||||
if(dataBaseType==null){
|
|
||||||
dataBaseType = getDbType();
|
|
||||||
}
|
|
||||||
if(isString) {
|
|
||||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
|
|
||||||
return " N'"+str+"' ";
|
|
||||||
}else{
|
|
||||||
return " '"+str+"' ";
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
// 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“‘admin’”这种格式的
|
|
||||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
|
||||||
return " N"+str;
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getInConditionValue(Object value,boolean isString) {
|
|
||||||
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
|
||||||
String[] temp = value.toString().split(",");
|
|
||||||
if(temp.length==0){
|
|
||||||
return "('')";
|
|
||||||
}
|
|
||||||
if(isString) {
|
|
||||||
List<String> res = new ArrayList<>();
|
|
||||||
for (String string : temp) {
|
|
||||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
|
||||||
res.add("N'"+string+"'");
|
|
||||||
}else{
|
|
||||||
res.add("'"+string+"'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "("+String.join("," ,res)+")";
|
|
||||||
}else {
|
|
||||||
return "("+value.toString()+")";
|
|
||||||
}
|
|
||||||
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 先根据值判断 走左模糊还是右模糊
|
|
||||||
* 最后如果值不带任何标识(*或者%),则再根据ruleEnum判断
|
|
||||||
* @param value
|
|
||||||
* @param ruleEnum
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static String getLikeConditionValue(Object value, QueryRuleEnum ruleEnum) {
|
|
||||||
String str = value.toString().trim();
|
|
||||||
if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
|
|
||||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
|
||||||
return "N'%"+str.substring(1,str.length()-1)+"%'";
|
|
||||||
}else{
|
|
||||||
return "'%"+str.substring(1,str.length()-1)+"%'";
|
|
||||||
}
|
|
||||||
}else if(str.startsWith(SymbolConstant.ASTERISK)) {
|
|
||||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
|
||||||
return "N'%"+str.substring(1)+"'";
|
|
||||||
}else{
|
|
||||||
return "'%"+str.substring(1)+"'";
|
|
||||||
}
|
|
||||||
}else if(str.endsWith(SymbolConstant.ASTERISK)) {
|
|
||||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
|
||||||
return "N'"+str.substring(0,str.length()-1)+"%'";
|
|
||||||
}else{
|
|
||||||
return "'"+str.substring(0,str.length()-1)+"%'";
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
|
|
||||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
|
||||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
|
||||||
return "N"+str;
|
|
||||||
}else{
|
|
||||||
return "N"+"'"+str+"'";
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
|
||||||
return str;
|
|
||||||
}else{
|
|
||||||
return "'"+str+"'";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
|
|
||||||
//update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
|
||||||
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
|
|
||||||
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
|
|
||||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
|
||||||
return "N'%" + str + "'";
|
|
||||||
} else {
|
|
||||||
return "'%" + str + "'";
|
|
||||||
}
|
|
||||||
} else if (ruleEnum == QueryRuleEnum.RIGHT_LIKE) {
|
|
||||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
|
||||||
return "N'" + str + "%'";
|
|
||||||
} else {
|
|
||||||
return "'" + str + "%'";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
|
||||||
return "N'%" + str + "%'";
|
|
||||||
} else {
|
|
||||||
return "'%" + str + "%'";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1064,7 +847,7 @@ public class QueryGenerator {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(ruleMap.containsKey(name)) {
|
if(ruleMap.containsKey(name)) {
|
||||||
column = getTableFieldName(clazz, name);
|
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||||
if(column==null){
|
if(column==null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1078,7 +861,7 @@ public class QueryGenerator {
|
||||||
}else {
|
}else {
|
||||||
value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
|
value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
|
||||||
}
|
}
|
||||||
String filedSql = getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
|
String filedSql = SqlConcatUtil.getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
|
||||||
sb.append(sqlAnd+filedSql);
|
sb.append(sqlAnd+filedSql);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1107,7 +890,7 @@ public class QueryGenerator {
|
||||||
if (judgedIsUselessField(name)) {
|
if (judgedIsUselessField(name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
column = getTableFieldName(clazz, name);
|
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||||
if(column==null){
|
if(column==null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1126,42 +909,6 @@ public class QueryGenerator {
|
||||||
return getSqlRuleValue(sql);
|
return getSqlRuleValue(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有配置的权限 返回sql字符串 不受字段限制 配置什么就拿到什么
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getAllConfigAuth() {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
//权限查询
|
|
||||||
Map<String,SysPermissionDataRuleModel> ruleMap = getRuleMap();
|
|
||||||
String sqlAnd = " and ";
|
|
||||||
for (String c : ruleMap.keySet()) {
|
|
||||||
SysPermissionDataRuleModel dataRule = ruleMap.get(c);
|
|
||||||
String ruleValue = dataRule.getRuleValue();
|
|
||||||
if(oConvertUtils.isEmpty(ruleValue)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
|
|
||||||
sb.append(sqlAnd+getSqlRuleValue(ruleValue));
|
|
||||||
}else{
|
|
||||||
boolean isString = false;
|
|
||||||
ruleValue = ruleValue.trim();
|
|
||||||
if(ruleValue.startsWith("'") && ruleValue.endsWith("'")){
|
|
||||||
isString = true;
|
|
||||||
ruleValue = ruleValue.substring(1,ruleValue.length()-1);
|
|
||||||
}
|
|
||||||
QueryRuleEnum rule = QueryRuleEnum.getByValue(dataRule.getRuleConditions());
|
|
||||||
String value = converRuleValue(ruleValue);
|
|
||||||
String filedSql = getSingleSqlByRule(rule, c, value,isString);
|
|
||||||
sb.append(sqlAnd+filedSql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("query auth sql is = "+sb.toString());
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统数据库类型
|
* 获取系统数据库类型
|
||||||
*/
|
*/
|
||||||
|
|
@ -1169,71 +916,6 @@ public class QueryGenerator {
|
||||||
return CommonUtils.getDatabaseType();
|
return CommonUtils.getDatabaseType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取class的 包括父类的
|
|
||||||
* @param clazz
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static List<Field> getClassFields(Class<?> clazz) {
|
|
||||||
List<Field> list = new ArrayList<Field>();
|
|
||||||
Field[] fields;
|
|
||||||
do{
|
|
||||||
fields = clazz.getDeclaredFields();
|
|
||||||
for(int i = 0;i<fields.length;i++){
|
|
||||||
list.add(fields[i]);
|
|
||||||
}
|
|
||||||
clazz = clazz.getSuperclass();
|
|
||||||
}while(clazz!= Object.class&&clazz!=null);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取表字段名
|
|
||||||
* @param clazz
|
|
||||||
* @param name
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static String getTableFieldName(Class<?> clazz, String name) {
|
|
||||||
try {
|
|
||||||
//如果字段加注解了@TableField(exist = false),不走DB查询
|
|
||||||
Field field = null;
|
|
||||||
try {
|
|
||||||
field = clazz.getDeclaredField(name);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
//e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
//如果为空,则去父类查找字段
|
|
||||||
if (field == null) {
|
|
||||||
List<Field> allFields = getClassFields(clazz);
|
|
||||||
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
|
|
||||||
if(searchFields!=null && searchFields.size()>0){
|
|
||||||
field = searchFields.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field != null) {
|
|
||||||
TableField tableField = field.getAnnotation(TableField.class);
|
|
||||||
if (tableField != null){
|
|
||||||
if(tableField.exist() == false){
|
|
||||||
//如果设置了TableField false 这个字段不需要处理
|
|
||||||
return null;
|
|
||||||
}else{
|
|
||||||
String column = tableField.value();
|
|
||||||
//如果设置了TableField value 这个字段是实体字段
|
|
||||||
if(!"".equals(column)){
|
|
||||||
return column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mysql 模糊查询之特殊字符下划线 (_、\)
|
* mysql 模糊查询之特殊字符下划线 (_、\)
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,19 @@ public enum QueryRuleEnum {
|
||||||
IN("IN","in","包含"),
|
IN("IN","in","包含"),
|
||||||
/**查询规则 全模糊*/
|
/**查询规则 全模糊*/
|
||||||
LIKE("LIKE","like","全模糊"),
|
LIKE("LIKE","like","全模糊"),
|
||||||
|
/**查询规则 不模糊包含*/
|
||||||
|
NOT_LIKE("NOT_LIKE","not_like","不模糊包含"),
|
||||||
/**查询规则 左模糊*/
|
/**查询规则 左模糊*/
|
||||||
LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
|
LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
|
||||||
/**查询规则 右模糊*/
|
/**查询规则 右模糊*/
|
||||||
RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
|
RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
|
||||||
/**查询规则 带加号等于*/
|
/**查询规则 带加号等于*/
|
||||||
EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"),
|
EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"),
|
||||||
|
/**查询规则 多词模糊匹配*/
|
||||||
|
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
|
||||||
|
/**查询规则 自定义SQL片段*/
|
||||||
|
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
|
||||||
|
|
||||||
// ------- 当前表单设计器内专用 -------
|
// ------- 当前表单设计器内专用 -------
|
||||||
/** 值为空 */
|
/** 值为空 */
|
||||||
EMPTY("EMPTY","empty","值为空"),
|
EMPTY("EMPTY","empty","值为空"),
|
||||||
|
|
@ -38,15 +45,12 @@ public enum QueryRuleEnum {
|
||||||
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
|
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
|
||||||
/**查询规则 不包含*/
|
/**查询规则 不包含*/
|
||||||
NOT_IN("NOT_IN","not_in","不包含"),
|
NOT_IN("NOT_IN","not_in","不包含"),
|
||||||
// ------- 当前表单设计器内专用 -------
|
|
||||||
/**查询规则 多词模糊匹配*/
|
|
||||||
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
|
|
||||||
/**查询规则 自定义SQL片段*/
|
|
||||||
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
|
|
||||||
/**查询规则 多词匹配*/
|
/**查询规则 多词匹配*/
|
||||||
ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"),
|
ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"),
|
||||||
/**查询规则 范围查询*/
|
/**查询规则 范围查询*/
|
||||||
RANGE("RANGE","range","范围查询");
|
RANGE("RANGE","range","范围查询"),
|
||||||
|
NOT_RANGE("NOT_RANGE","not_range","不在范围查询");
|
||||||
|
// ------- 当前表单设计器内专用 -------
|
||||||
|
|
||||||
private String value;
|
private String value;
|
||||||
|
|
||||||
|
|
@ -89,7 +93,7 @@ public enum QueryRuleEnum {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for(QueryRuleEnum val :values()){
|
for(QueryRuleEnum val :values()){
|
||||||
if (val.getValue().equals(value) || val.getCondition().equals(value)){
|
if (val.getValue().equals(value) || val.getCondition().equalsIgnoreCase(value)){
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|
@ -34,6 +35,7 @@ import org.jeecg.common.util.oConvertUtils;
|
||||||
* @Date 2018-07-12 14:23
|
* @Date 2018-07-12 14:23
|
||||||
* @Desc JWT工具类
|
* @Desc JWT工具类
|
||||||
**/
|
**/
|
||||||
|
@Slf4j
|
||||||
public class JwtUtil {
|
public class JwtUtil {
|
||||||
|
|
||||||
/**Token有效期为7天(Token在reids中缓存时间为两倍)*/
|
/**Token有效期为7天(Token在reids中缓存时间为两倍)*/
|
||||||
|
|
@ -163,15 +165,24 @@ public class JwtUtil {
|
||||||
* @param user
|
* @param user
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String getUserSystemData(String key,SysUserCacheInfo user) {
|
public static String getUserSystemData(String key, SysUserCacheInfo user) {
|
||||||
|
//1.优先获取 SysUserCacheInfo
|
||||||
if(user==null) {
|
if(user==null) {
|
||||||
user = JeecgDataAutorUtils.loadUserInfo();
|
try {
|
||||||
|
user = JeecgDataAutorUtils.loadUserInfo();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("获取用户信息异常:" + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
//2.通过shiro获取登录用户信息
|
||||||
|
LoginUser sysUser = null;
|
||||||
|
try {
|
||||||
|
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
//#{sys_user_code}%
|
//#{sys_user_code}%
|
||||||
|
|
||||||
// 获取登录用户信息
|
|
||||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
|
||||||
|
|
||||||
String moshi = "";
|
String moshi = "";
|
||||||
String wellNumber = WELL_NUMBER;
|
String wellNumber = WELL_NUMBER;
|
||||||
if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
|
if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
|
||||||
|
|
@ -184,6 +195,24 @@ public class JwtUtil {
|
||||||
} else {
|
} else {
|
||||||
key = key;
|
key = key;
|
||||||
}
|
}
|
||||||
|
//替换为当前系统时间(年月日)
|
||||||
|
if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
||||||
|
returnValue = DateUtils.formatDate();
|
||||||
|
}
|
||||||
|
//替换为当前系统时间(年月日时分秒)
|
||||||
|
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
|
||||||
|
returnValue = DateUtils.now();
|
||||||
|
}
|
||||||
|
//流程状态默认值(默认未发起)
|
||||||
|
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
|
||||||
|
returnValue = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
//后台任务获取用户信息异常,导致程序中断
|
||||||
|
if(sysUser==null && user==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//替换为系统登录用户帐号
|
//替换为系统登录用户帐号
|
||||||
if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
|
if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
|
||||||
if(user==null) {
|
if(user==null) {
|
||||||
|
|
@ -222,21 +251,13 @@ public class JwtUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//替换为当前系统时间(年月日)
|
|
||||||
else if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
|
||||||
returnValue = DateUtils.formatDate();
|
|
||||||
}
|
|
||||||
//替换为当前系统时间(年月日时分秒)
|
|
||||||
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
|
|
||||||
returnValue = DateUtils.now();
|
|
||||||
}
|
|
||||||
//流程状态默认值(默认未发起)
|
|
||||||
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
|
|
||||||
returnValue = "1";
|
|
||||||
}
|
|
||||||
//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
|
//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
|
||||||
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
|
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
|
||||||
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
|
try {
|
||||||
|
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("获取系统租户异常:" + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
|
//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
|
||||||
if(returnValue!=null){returnValue = returnValue + moshi;}
|
if(returnValue!=null){returnValue = returnValue + moshi;}
|
||||||
|
|
|
||||||
|
|
@ -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 java.io.Serializable;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -27,6 +28,12 @@ public class DictModel implements Serializable{
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DictModel(String value, String text, String color) {
|
||||||
|
this.value = value;
|
||||||
|
this.text = text;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典value
|
* 字典value
|
||||||
*/
|
*/
|
||||||
|
|
@ -35,6 +42,10 @@ public class DictModel implements Serializable{
|
||||||
* 字典文本
|
* 字典文本
|
||||||
*/
|
*/
|
||||||
private String text;
|
private String text;
|
||||||
|
/**
|
||||||
|
* 字典颜色
|
||||||
|
*/
|
||||||
|
private String color;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 特殊用途: JgEditableTable
|
* 特殊用途: JgEditableTable
|
||||||
|
|
@ -50,4 +61,11 @@ public class DictModel implements Serializable{
|
||||||
return this.text;
|
return this.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于表单设计器 关联记录表数据存储
|
||||||
|
* QQYUN-5595【表单设计器】他表字段 导入没有翻译
|
||||||
|
*/
|
||||||
|
private JSONObject jsonObject;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
package org.jeecg.common.system.vo;
|
package org.jeecg.common.system.vo;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -53,6 +50,7 @@ public class LoginUser {
|
||||||
/**
|
/**
|
||||||
* 当前登录部门code
|
* 当前登录部门code
|
||||||
*/
|
*/
|
||||||
|
@SensitiveField
|
||||||
private String orgCode;
|
private String orgCode;
|
||||||
/**
|
/**
|
||||||
* 头像
|
* 头像
|
||||||
|
|
@ -63,7 +61,6 @@ public class LoginUser {
|
||||||
/**
|
/**
|
||||||
* 生日
|
* 生日
|
||||||
*/
|
*/
|
||||||
@SensitiveField
|
|
||||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
private Date birthday;
|
private Date birthday;
|
||||||
|
|
@ -109,6 +106,7 @@ public class LoginUser {
|
||||||
/**
|
/**
|
||||||
* 管理部门ids
|
* 管理部门ids
|
||||||
*/
|
*/
|
||||||
|
@SensitiveField
|
||||||
private String departIds;
|
private String departIds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -124,6 +122,7 @@ public class LoginUser {
|
||||||
private String telephone;
|
private String telephone;
|
||||||
|
|
||||||
/** 多租户ids临时用,不持久化数据库(数据库字段不存在) */
|
/** 多租户ids临时用,不持久化数据库(数据库字段不存在) */
|
||||||
|
@SensitiveField
|
||||||
private String relTenantIds;
|
private String relTenantIds;
|
||||||
|
|
||||||
/**设备id uniapp推送用*/
|
/**设备id uniapp推送用*/
|
||||||
|
|
@ -131,7 +130,7 @@ public class LoginUser {
|
||||||
|
|
||||||
private String openId;
|
private String openId;
|
||||||
|
|
||||||
@ApiModelProperty(value = "是否是vip")
|
|
||||||
/**是否是vip*/
|
/**是否是vip*/
|
||||||
|
@ApiModelProperty(value = "是否是vip")
|
||||||
private boolean vipFlag=false;
|
private boolean vipFlag=false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ public class SysFilesModel {
|
||||||
private String storeType;
|
private String storeType;
|
||||||
/**文件大小(kb)*/
|
/**文件大小(kb)*/
|
||||||
private Double fileSize;
|
private Double fileSize;
|
||||||
|
/**租户id*/
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
|
@ -67,4 +69,12 @@ public class SysFilesModel {
|
||||||
public void setFileSize(Double fileSize) {
|
public void setFileSize(Double fileSize) {
|
||||||
this.fileSize = fileSize;
|
this.fileSize = fileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(String tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
|
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
||||||
import com.baomidou.mybatisplus.annotation.DbType;
|
import com.baomidou.mybatisplus.annotation.DbType;
|
||||||
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
|
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.constant.DataBaseConstant;
|
import org.jeecg.common.constant.DataBaseConstant;
|
||||||
import org.jeecg.common.constant.ServiceNameConstants;
|
import org.jeecg.common.constant.ServiceNameConstants;
|
||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
|
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||||
import org.jeecg.common.util.oss.OssBootUtil;
|
import org.jeecg.common.util.oss.OssBootUtil;
|
||||||
import org.jeecgframework.poi.util.PoiPublicUtil;
|
import org.jeecgframework.poi.util.PoiPublicUtil;
|
||||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||||
|
|
@ -26,7 +28,9 @@ import java.io.InputStream;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DatabaseMetaData;
|
import java.sql.DatabaseMetaData;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
@ -136,6 +140,7 @@ public class CommonUtils {
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
|
throw new JeecgBootException(e.getMessage());
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +153,7 @@ public class CommonUtils {
|
||||||
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
|
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
|
||||||
try {
|
try {
|
||||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||||
FileTypeFilter.fileTypeFilter(mf);
|
SsrfFileTypeFilter.checkUploadFileType(mf);
|
||||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||||
String fileName = null;
|
String fileName = null;
|
||||||
File file = new File(uploadpath + File.separator + bizPath + File.separator );
|
File file = new File(uploadpath + File.separator + bizPath + File.separator );
|
||||||
|
|
@ -299,7 +304,7 @@ public class CommonUtils {
|
||||||
DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
|
DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
|
||||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) {
|
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) {
|
||||||
DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER;
|
DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER;
|
||||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0) {
|
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0 || dbType.indexOf(DataBaseConstant.DB_TYPE_KINGBASEES)>=0) {
|
||||||
DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL;
|
DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL;
|
||||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) {
|
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) {
|
||||||
DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
|
DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
|
||||||
|
|
@ -392,4 +397,91 @@ public class CommonUtils {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list集合以分割符的方式进行分割
|
||||||
|
* @param list String类型的集合文本
|
||||||
|
* @param separator 分隔符
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getSplitText(List<String> list, String separator) {
|
||||||
|
if (null != list && list.size() > 0) {
|
||||||
|
return StringUtils.join(list, separator);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过table的条件SQL
|
||||||
|
*
|
||||||
|
* @param tableSql sys_user where name = '1212'
|
||||||
|
* @return name = '1212'
|
||||||
|
*/
|
||||||
|
public static String getFilterSqlByTableSql(String tableSql) {
|
||||||
|
if(oConvertUtils.isEmpty(tableSql)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
|
||||||
|
String[] arr = tableSql.split(" (?i)where ");
|
||||||
|
if (arr != null && oConvertUtils.isNotEmpty(arr[1])) {
|
||||||
|
return arr[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过table获取表名
|
||||||
|
*
|
||||||
|
* @param tableSql sys_user where name = '1212'
|
||||||
|
* @return sys_user
|
||||||
|
*/
|
||||||
|
public static String getTableNameByTableSql(String tableSql) {
|
||||||
|
if(oConvertUtils.isEmpty(tableSql)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
|
||||||
|
String[] arr = tableSql.split(" (?i)where ");
|
||||||
|
return arr[0].trim();
|
||||||
|
} else {
|
||||||
|
return tableSql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两个数组是否存在交集
|
||||||
|
* @param set1
|
||||||
|
* @param arr2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean hasIntersection(Set<String> set1, String[] arr2) {
|
||||||
|
if (set1 == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(set1.size()>0){
|
||||||
|
for (String str : arr2) {
|
||||||
|
if (set1.contains(str)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出info日志,会捕获异常,防止因为日志问题导致程序异常
|
||||||
|
*
|
||||||
|
* @param msg
|
||||||
|
* @param objects
|
||||||
|
*/
|
||||||
|
public static void logInfo(String msg, Object... objects) {
|
||||||
|
try {
|
||||||
|
log.info(msg, objects);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("{} —— {}", msg, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
package org.jeecg.common.util;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
import org.jeecg.config.StaticConfig;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.aliyuncs.DefaultAcsClient;
|
import com.aliyuncs.DefaultAcsClient;
|
||||||
import com.aliyuncs.IAcsClient;
|
import com.aliyuncs.IAcsClient;
|
||||||
|
|
@ -12,6 +8,10 @@ import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||||
import com.aliyuncs.exceptions.ClientException;
|
import com.aliyuncs.exceptions.ClientException;
|
||||||
import com.aliyuncs.profile.DefaultProfile;
|
import com.aliyuncs.profile.DefaultProfile;
|
||||||
import com.aliyuncs.profile.IClientProfile;
|
import com.aliyuncs.profile.IClientProfile;
|
||||||
|
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||||
|
import org.jeecg.config.StaticConfig;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created on 17/6/7.
|
* Created on 17/6/7.
|
||||||
|
|
@ -55,15 +55,15 @@ public class DySmsHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean sendSms(String phone,JSONObject templateParamJson,DySmsEnum dySmsEnum) throws ClientException {
|
public static boolean sendSms(String phone, JSONObject templateParamJson, DySmsEnum dySmsEnum) throws ClientException {
|
||||||
//可自助调整超时时间
|
//可自助调整超时时间
|
||||||
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
|
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
|
||||||
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
|
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
|
||||||
|
|
||||||
//update-begin-author:taoyan date:20200811 for:配置类数据获取
|
//update-begin-author:taoyan date:20200811 for:配置类数据获取
|
||||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||||
logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
//logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
||||||
logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
//logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
||||||
setAccessKeyId(staticConfig.getAccessKeyId());
|
setAccessKeyId(staticConfig.getAccessKeyId());
|
||||||
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
||||||
//update-end-author:taoyan date:20200811 for:配置类数据获取
|
//update-end-author:taoyan date:20200811 for:配置类数据获取
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import io.minio.*;
|
||||||
import io.minio.http.Method;
|
import io.minio.http.Method;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ public class MinioUtil {
|
||||||
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||||
|
|
||||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||||
FileTypeFilter.fileTypeFilter(file);
|
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||||
|
|
||||||
String newBucket = bucketName;
|
String newBucket = bucketName;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package org.jeecg.common.util;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
@ -7,6 +8,7 @@ import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author 张代浩
|
* @author 张代浩
|
||||||
|
|
@ -252,4 +254,86 @@ public class ReflectHelper {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断给定的字段是不是类中的属性
|
||||||
|
* @param field 字段名
|
||||||
|
* @param clazz 类对象
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isClassField(String field, Class clazz){
|
||||||
|
Field[] fields = clazz.getDeclaredFields();
|
||||||
|
for(int i=0;i<fields.length;i++){
|
||||||
|
String fieldName = fields[i].getName();
|
||||||
|
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
|
||||||
|
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取class的 包括父类的
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<Field> getClassFields(Class<?> clazz) {
|
||||||
|
List<Field> list = new ArrayList<Field>();
|
||||||
|
Field[] fields;
|
||||||
|
do{
|
||||||
|
fields = clazz.getDeclaredFields();
|
||||||
|
for(int i = 0;i<fields.length;i++){
|
||||||
|
list.add(fields[i]);
|
||||||
|
}
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}while(clazz!= Object.class&&clazz!=null);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表字段名
|
||||||
|
* @param clazz
|
||||||
|
* @param name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getTableFieldName(Class<?> clazz, String name) {
|
||||||
|
try {
|
||||||
|
//如果字段加注解了@TableField(exist = false),不走DB查询
|
||||||
|
Field field = null;
|
||||||
|
try {
|
||||||
|
field = clazz.getDeclaredField(name);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
//如果为空,则去父类查找字段
|
||||||
|
if (field == null) {
|
||||||
|
List<Field> allFields = getClassFields(clazz);
|
||||||
|
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
|
||||||
|
if(searchFields!=null && searchFields.size()>0){
|
||||||
|
field = searchFields.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field != null) {
|
||||||
|
TableField tableField = field.getAnnotation(TableField.class);
|
||||||
|
if (tableField != null){
|
||||||
|
if(tableField.exist() == false){
|
||||||
|
//如果设置了TableField false 这个字段不需要处理
|
||||||
|
return null;
|
||||||
|
}else{
|
||||||
|
String column = tableField.value();
|
||||||
|
//如果设置了TableField value 这个字段是实体字段
|
||||||
|
if(!"".equals(column)){
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package org.jeecg.common.util;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
import cn.hutool.crypto.SecureUtil;
|
import cn.hutool.core.util.ReUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import java.lang.reflect.Field;
|
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||||
import java.util.Set;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
@ -17,57 +18,70 @@ import java.util.regex.Pattern;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SqlInjectionUtil {
|
public class SqlInjectionUtil {
|
||||||
/**
|
/**
|
||||||
* sign 用于表字典加签的盐值【SQL漏洞】
|
* 默认—sql注入关键词
|
||||||
* (上线修改值 20200501,同步修改前端的盐值)
|
|
||||||
*/
|
*/
|
||||||
private final static String TABLE_DICT_SIGN_SALT = "20200501";
|
private final static String XSS_STR = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
|
||||||
private final static String XSS_STR = "and |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
|
/**
|
||||||
|
* 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]*\\)";
|
private final static String[] XSS_REGULAR_STR_ARRAY = new String[]{
|
||||||
/**正则 show tables*/
|
"chr\\s*\\(",
|
||||||
private final static String SHOW_TABLES = "show\\s+tables";
|
"mid\\s*\\(",
|
||||||
|
" char\\s*\\(",
|
||||||
/**
|
"sleep\\s*\\(",
|
||||||
* sleep函数
|
"user\\s*\\(",
|
||||||
*/
|
"show\\s+tables",
|
||||||
private final static Pattern FUN_SLEEP = Pattern.compile("sleep\\([\\d\\.]*\\)", Pattern.CASE_INSENSITIVE);
|
"user[\\s]*\\([\\s]*\\)",
|
||||||
|
"show\\s+databases",
|
||||||
|
"sleep\\(\\d*\\)",
|
||||||
|
"sleep\\(.*\\)",
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* sql注释的正则
|
* sql注释的正则
|
||||||
*/
|
*/
|
||||||
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
||||||
|
private final static String SQL_ANNOTATION2 = "--";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 针对表字典进行额外的sign签名校验(增加安全机制)
|
* sql注入提示语
|
||||||
* @param dictCode:
|
|
||||||
* @param sign:
|
|
||||||
* @param request:
|
|
||||||
* @Return: void
|
|
||||||
*/
|
*/
|
||||||
public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
|
private final static String SQL_INJECTION_KEYWORD_TIP = "请注意,存在SQL注入关键词---> {}";
|
||||||
//表字典SQL注入漏洞,签名校验
|
private final static String SQL_INJECTION_TIP = "请注意,值可能存在SQL注入风险!--->";
|
||||||
String accessToken = request.getHeader("X-Access-Token");
|
private final static String SQL_INJECTION_TIP_VARIABLE = "请注意,值可能存在SQL注入风险!---> {}";
|
||||||
String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
|
|
||||||
String javaSign = SecureUtil.md5(signStr);
|
|
||||||
if (!javaSign.equals(sign)) {
|
|
||||||
log.error("表字典,SQL注入漏洞签名校验失败 :" + sign + "!=" + javaSign+ ",dictCode=" + dictCode);
|
|
||||||
throw new JeecgBootException("无权限访问!");
|
|
||||||
}
|
|
||||||
log.info(" 表字典,SQL注入漏洞签名校验成功!sign=" + sign + ",dictCode=" + dictCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sql注入过滤处理,遇到注入关键字抛异常
|
* sql注入过滤处理,遇到注入关键字抛异常
|
||||||
* @param value
|
* @param values
|
||||||
*/
|
*/
|
||||||
public static void filterContent(String value) {
|
public static void filterContent(String... values) {
|
||||||
filterContent(value, null);
|
filterContent(values, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 校验比较严格
|
||||||
|
*
|
||||||
* sql注入过滤处理,遇到注入关键字抛异常
|
* sql注入过滤处理,遇到注入关键字抛异常
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
|
|
@ -77,45 +91,81 @@ public class SqlInjectionUtil {
|
||||||
if (value == null || "".equals(value)) {
|
if (value == null || "".equals(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 校验sql注释 不允许有sql注释
|
// 一、校验sql注释 不允许有sql注释
|
||||||
checkSqlAnnotation(value);
|
checkSqlAnnotation(value);
|
||||||
// 统一转为小写
|
// 转为小写进行后续比较
|
||||||
value = value.toLowerCase();
|
value = value.toLowerCase().trim();
|
||||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
|
||||||
//value = value.replaceAll("/\\*.*\\*/","");
|
|
||||||
|
|
||||||
|
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||||
|
//https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||||
String[] xssArr = XSS_STR.split("\\|");
|
String[] xssArr = XSS_STR.split("\\|");
|
||||||
for (int i = 0; i < xssArr.length; i++) {
|
for (int i = 0; i < xssArr.length; i++) {
|
||||||
if (value.indexOf(xssArr[i]) > -1) {
|
if (value.indexOf(xssArr[i]) > -1) {
|
||||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
|
// 三、SQL注入检测存在绕过风险 (自定义传入普通文本校验)
|
||||||
if (customXssString != null) {
|
if (customXssString != null) {
|
||||||
String[] xssArr2 = customXssString.split("\\|");
|
String[] xssArr2 = customXssString.split("\\|");
|
||||||
for (int i = 0; i < xssArr2.length; i++) {
|
for (int i = 0; i < xssArr2.length; i++) {
|
||||||
if (value.indexOf(xssArr2[i]) > -1) {
|
if (value.indexOf(xssArr2[i]) > -1) {
|
||||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr2[i]);
|
||||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
|
|
||||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
// 四、SQL注入检测存在绕过风险 (正则校验)
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||||
|
String regular = ".*" + regularOriginal + ".*";
|
||||||
|
if (Pattern.matches(regular, value)) {
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sql注入过滤处理,遇到注入关键字抛异常
|
* 判断是否存在SQL注入关键词字符串
|
||||||
* @param values
|
*
|
||||||
|
* @param keyword
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
public static void filterContent(String[] values) {
|
@SuppressWarnings("AlibabaUndefineMagicConstant")
|
||||||
filterContent(values, null);
|
private static boolean isExistSqlInjectKeyword(String sql, String keyword) {
|
||||||
|
if (sql.startsWith(keyword.trim())) {
|
||||||
|
return true;
|
||||||
|
} else if (sql.contains(keyword)) {
|
||||||
|
// 需要匹配的,sql注入关键词
|
||||||
|
String matchingText = " " + keyword;
|
||||||
|
if(FULL_MATCHING_KEYWRODS.contains(keyword)){
|
||||||
|
matchingText = keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sql.contains(matchingText)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
String regularStr = "\\s+\\S+" + keyword;
|
||||||
|
List<String> resultFindAll = ReUtil.findAll(regularStr, sql, 0, new ArrayList<String>());
|
||||||
|
for (String res : resultFindAll) {
|
||||||
|
log.info("isExistSqlInjectKeyword —- 匹配到的SQL注入关键词:{}", res);
|
||||||
|
/**
|
||||||
|
* SQL注入中可以替换空格的字符(%09 %0A %0D +都可以替代空格)
|
||||||
|
* http://blog.chinaunix.net/uid-12501104-id-2932639.html
|
||||||
|
* https://www.cnblogs.com/Vinson404/p/7253255.html
|
||||||
|
* */
|
||||||
|
if (res.contains("%") || res.contains("+") || res.contains("#") || res.contains("/") || res.contains(")")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -125,40 +175,11 @@ public class SqlInjectionUtil {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static void filterContent(String[] values, String customXssString) {
|
public static void filterContent(String[] values, String customXssString) {
|
||||||
String[] xssArr = XSS_STR.split("\\|");
|
for (String val : values) {
|
||||||
for (String value : values) {
|
if (oConvertUtils.isEmpty(val)) {
|
||||||
if (value == null || "".equals(value)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 校验sql注释 不允许有sql注释
|
filterContent(val, customXssString);
|
||||||
checkSqlAnnotation(value);
|
|
||||||
// 统一转为小写
|
|
||||||
value = value.toLowerCase();
|
|
||||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
|
||||||
//value = value.replaceAll("/\\*.*\\*/","");
|
|
||||||
|
|
||||||
for (int i = 0; i < xssArr.length; i++) {
|
|
||||||
if (value.indexOf(xssArr[i]) > -1) {
|
|
||||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
|
||||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
|
|
||||||
if (customXssString != null) {
|
|
||||||
String[] xssArr2 = customXssString.split("\\|");
|
|
||||||
for (int i = 0; i < xssArr2.length; i++) {
|
|
||||||
if (value.indexOf(xssArr2[i]) > -1) {
|
|
||||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
|
|
||||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
|
|
||||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -170,130 +191,230 @@ public class SqlInjectionUtil {
|
||||||
* @param value
|
* @param value
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
//@Deprecated
|
|
||||||
public static void specialFilterContentForDictSql(String value) {
|
public static void specialFilterContentForDictSql(String value) {
|
||||||
String specialXssStr = " exec |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract| insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
|
String[] xssArr = specialDictSqlXssStr.split("\\|");
|
||||||
String[] xssArr = specialXssStr.split("\\|");
|
|
||||||
if (value == null || "".equals(value)) {
|
if (value == null || "".equals(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 校验sql注释 不允许有sql注释
|
// 一、校验sql注释 不允许有sql注释
|
||||||
checkSqlAnnotation(value);
|
checkSqlAnnotation(value);
|
||||||
// 统一转为小写
|
value = value.toLowerCase().trim();
|
||||||
value = value.toLowerCase();
|
|
||||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
|
||||||
//value = value.replaceAll("/\\*.*\\*/","");
|
|
||||||
|
|
||||||
|
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||||
for (int i = 0; i < xssArr.length; i++) {
|
for (int i = 0; i < xssArr.length; i++) {
|
||||||
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
|
if (isExistSqlInjectKeyword(value, xssArr[i])) {
|
||||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||||
|
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||||
|
String regular = ".*" + regularOriginal + ".*";
|
||||||
|
if (Pattern.matches(regular, value)) {
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 【提醒:不通用】
|
* 【提醒:不通用】
|
||||||
* 仅用于Online报表SQL解析,注入过滤
|
* 仅用于Online报表SQL解析,注入过滤
|
||||||
* @param value
|
* @param value
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
//@Deprecated
|
|
||||||
public static void specialFilterContentForOnlineReport(String value) {
|
public static void specialFilterContentForOnlineReport(String value) {
|
||||||
String specialXssStr = " exec |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract| insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()";
|
String[] xssArr = specialReportXssStr.split("\\|");
|
||||||
String[] xssArr = specialXssStr.split("\\|");
|
|
||||||
if (value == null || "".equals(value)) {
|
if (value == null || "".equals(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 校验sql注释 不允许有sql注释
|
// 一、校验sql注释 不允许有sql注释
|
||||||
checkSqlAnnotation(value);
|
checkSqlAnnotation(value);
|
||||||
// 统一转为小写
|
value = value.toLowerCase().trim();
|
||||||
value = value.toLowerCase();
|
|
||||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
|
||||||
//value = value.replaceAll("/\\*.*\\*/"," ");
|
|
||||||
|
|
||||||
|
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||||
for (int i = 0; i < xssArr.length; i++) {
|
for (int i = 0; i < xssArr.length; i++) {
|
||||||
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
|
if (isExistSqlInjectKeyword(value, xssArr[i])) {
|
||||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||||
|
String regular = ".*" + regularOriginal + ".*";
|
||||||
|
if (Pattern.matches(regular, value)) {
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断给定的字段是不是类中的属性
|
|
||||||
* @param field 字段名
|
|
||||||
* @param clazz 类对象
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean isClassField(String field, Class clazz){
|
|
||||||
Field[] fields = clazz.getDeclaredFields();
|
|
||||||
for(int i=0;i<fields.length;i++){
|
|
||||||
String fieldName = fields[i].getName();
|
|
||||||
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
|
|
||||||
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断给定的多个字段是不是类中的属性
|
|
||||||
* @param fieldSet 字段名set
|
|
||||||
* @param clazz 类对象
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean isClassField(Set<String> fieldSet, Class clazz){
|
|
||||||
Field[] fields = clazz.getDeclaredFields();
|
|
||||||
for(String field: fieldSet){
|
|
||||||
boolean exist = false;
|
|
||||||
for(int i=0;i<fields.length;i++){
|
|
||||||
String fieldName = fields[i].getName();
|
|
||||||
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
|
|
||||||
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
|
|
||||||
exist = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!exist){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验是否有sql注释
|
* 校验是否有sql注释
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static void checkSqlAnnotation(String str){
|
public static void checkSqlAnnotation(String str){
|
||||||
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
if(str.contains(SQL_ANNOTATION2)){
|
||||||
if(matcher.find()){
|
String error = "请注意,SQL中不允许含注释,有安全风险!";
|
||||||
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
|
||||||
log.error(error);
|
log.error(error);
|
||||||
throw new RuntimeException(error);
|
throw new RuntimeException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// issues/4737 sys/duplicate/check SQL注入 #4737
|
|
||||||
Matcher sleepMatcher = FUN_SLEEP.matcher(str);
|
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
||||||
if(sleepMatcher.find()){
|
if(matcher.find()){
|
||||||
String error = "请注意,值可能存在SQL注入风险---> sleep";
|
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
||||||
log.error(error);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,21 @@ public class TokenUtils {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 request 里传递的 token
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getTokenByRequest() {
|
||||||
|
String token = null;
|
||||||
|
try {
|
||||||
|
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
|
||||||
|
token = TokenUtils.getTokenByRequest(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 request 里传递的 tenantId (租户ID)
|
* 获取 request 里传递的 tenantId (租户ID)
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ public class FreemarkerParseFactory {
|
||||||
//"where and"
|
//"where and"
|
||||||
String whereAnd = DataBaseConstant.SQL_WHERE+" and";
|
String whereAnd = DataBaseConstant.SQL_WHERE+" and";
|
||||||
//", where"
|
//", where"
|
||||||
String commaWhere = SymbolConstant.COMMA+" "+ DataBaseConstant.SQL_WHERE;
|
String commaWhere = SymbolConstant.COMMA+" "+DataBaseConstant.SQL_WHERE;
|
||||||
//", "
|
//", "
|
||||||
String commaSpace = SymbolConstant.COMMA + " ";
|
String commaSpace = SymbolConstant.COMMA + " ";
|
||||||
if (sql.endsWith(DataBaseConstant.SQL_WHERE) || sql.endsWith(whereSpace)) {
|
if (sql.endsWith(DataBaseConstant.SQL_WHERE) || sql.endsWith(whereSpace)) {
|
||||||
|
|
|
||||||
|
|
@ -4,27 +4,72 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 校验上传文件敏感后缀
|
* @Description: 校验文件敏感后缀
|
||||||
* @author: lsq
|
* @author: lsq
|
||||||
* @date: 2021年08月09日 15:29
|
* @date: 2023年09月12日 15:29
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class FileTypeFilter {
|
public class SsrfFileTypeFilter {
|
||||||
|
|
||||||
/**文件后缀*/
|
|
||||||
private static String[] forbidType = {"jsp","php"};
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 允许操作文件类型白名单
|
||||||
|
*/
|
||||||
|
private final static List<String> FILE_TYPE_WHITE_LIST = new ArrayList<>();
|
||||||
/**初始化文件头类型,不够的自行补充*/
|
/**初始化文件头类型,不够的自行补充*/
|
||||||
final static HashMap<String, String> FILE_TYPE_MAP = new HashMap<>();
|
final static HashMap<String, String> FILE_TYPE_MAP = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
//图片文件
|
||||||
|
FILE_TYPE_WHITE_LIST.add("jpg");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("jpeg");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("png");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("gif");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("bmp");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("svg");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("ico");
|
||||||
|
|
||||||
|
//文本文件
|
||||||
|
FILE_TYPE_WHITE_LIST.add("txt");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("doc");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("docx");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("pdf");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("csv");
|
||||||
|
// FILE_TYPE_WHITE_LIST.add("xml");
|
||||||
|
|
||||||
|
//音视频文件
|
||||||
|
FILE_TYPE_WHITE_LIST.add("mp4");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("avi");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("mov");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("wmv");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("mp3");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("wav");
|
||||||
|
|
||||||
|
//表格文件
|
||||||
|
FILE_TYPE_WHITE_LIST.add("xls");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("xlsx");
|
||||||
|
|
||||||
|
//压缩文件
|
||||||
|
FILE_TYPE_WHITE_LIST.add("zip");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("rar");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("7z");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("tar");
|
||||||
|
|
||||||
|
//app文件后缀
|
||||||
|
FILE_TYPE_WHITE_LIST.add("apk");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("wgt");
|
||||||
|
|
||||||
|
//设置禁止文件的头部标记
|
||||||
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");
|
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");
|
||||||
FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php");
|
FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php");
|
||||||
|
FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");
|
||||||
|
FILE_TYPE_MAP.put("494e5345525420494e54", "sql");
|
||||||
/* fileTypeMap.put("ffd8ffe000104a464946", "jpg");
|
/* fileTypeMap.put("ffd8ffe000104a464946", "jpg");
|
||||||
fileTypeMap.put("89504e470d0a1a0a0000", "png");
|
fileTypeMap.put("89504e470d0a1a0a0000", "png");
|
||||||
fileTypeMap.put("47494638396126026f01", "gif");
|
fileTypeMap.put("47494638396126026f01", "gif");
|
||||||
|
|
@ -89,17 +134,38 @@ public class FileTypeFilter {
|
||||||
return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
|
return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件类型过滤
|
* 下载文件类型过滤
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
public static void checkDownloadFileType(String filePath) throws IOException {
|
||||||
|
//文件后缀
|
||||||
|
String suffix = getFileTypeBySuffix(filePath);
|
||||||
|
log.info("suffix:{}", suffix);
|
||||||
|
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||||
|
//是否允许下载的文件
|
||||||
|
if (!isAllowExtension) {
|
||||||
|
throw new IOException("下载失败,存在非法文件类型:" + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件类型过滤
|
||||||
*
|
*
|
||||||
* @param file
|
* @param file
|
||||||
*/
|
*/
|
||||||
public static void fileTypeFilter(MultipartFile file) throws Exception {
|
public static void checkUploadFileType(MultipartFile file) throws Exception {
|
||||||
|
//获取文件真是后缀
|
||||||
String suffix = getFileType(file);
|
String suffix = getFileType(file);
|
||||||
for (String type : forbidType) {
|
|
||||||
if (type.contains(suffix)) {
|
log.info("suffix:{}", suffix);
|
||||||
throw new Exception("上传失败,非法文件类型:" + suffix);
|
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||||
}
|
//是否允许下载的文件
|
||||||
|
if (!isAllowExtension) {
|
||||||
|
throw new Exception("上传失败,存在非法文件类型:" + suffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,6 +168,17 @@ public class oConvertUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Integer getInteger(Object object, Integer defval) {
|
||||||
|
if (isEmpty(object)) {
|
||||||
|
return (defval);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return (Integer.parseInt(object.toString()));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return (defval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Integer getInt(Object object) {
|
public static Integer getInt(Object object) {
|
||||||
if (isEmpty(object)) {
|
if (isEmpty(object)) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -702,9 +713,20 @@ public class oConvertUtils {
|
||||||
if (isArray(oldVal)) {
|
if (isArray(oldVal)) {
|
||||||
return equalityOfArrays((Object[]) oldVal, (Object[]) newVal);
|
return equalityOfArrays((Object[]) oldVal, (Object[]) newVal);
|
||||||
}else if(oldVal instanceof JSONArray){
|
}else if(oldVal instanceof JSONArray){
|
||||||
return equalityOfJSONArray((JSONArray) oldVal, (JSONArray) newVal);
|
if(newVal instanceof JSONArray){
|
||||||
|
return equalityOfJSONArray((JSONArray) oldVal, (JSONArray) newVal);
|
||||||
|
}else{
|
||||||
|
if (isEmpty(newVal) && (oldVal == null || ((JSONArray) oldVal).size() == 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
List<Object> arrayStr = Arrays.asList(newVal.toString().split(","));
|
||||||
|
JSONArray newValArray = new JSONArray(arrayStr);
|
||||||
|
return equalityOfJSONArray((JSONArray) oldVal, newValArray);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return oldVal.equals(newVal);
|
||||||
}
|
}
|
||||||
return oldVal.equals(newVal);
|
|
||||||
} else {
|
} else {
|
||||||
if (oldVal == null && newVal == null) {
|
if (oldVal == null && newVal == null) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -742,7 +764,7 @@ public class oConvertUtils {
|
||||||
Object[] newValArray = newVal.toArray();
|
Object[] newValArray = newVal.toArray();
|
||||||
return equalityOfArrays(oldValArray,newValArray);
|
return equalityOfArrays(oldValArray,newValArray);
|
||||||
} else {
|
} else {
|
||||||
if (oldVal == null && newVal == null) {
|
if ((oldVal == null || oldVal.size() == 0) && (newVal == null || newVal.size() == 0)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -750,6 +772,38 @@ public class oConvertUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较带逗号的字符串
|
||||||
|
* QQYUN-5212【简流】按日期触发 多选 人员组件 选择顺序不一致时 不触发,应该是统一问题 包括多选部门组件
|
||||||
|
* @param oldVal
|
||||||
|
* @param newVal
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean equalityOfStringArrays(String oldVal, String newVal) {
|
||||||
|
if(oldVal.equals(newVal)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(oldVal.indexOf(",")>=0 && newVal.indexOf(",")>=0){
|
||||||
|
String[] arr1 = oldVal.split(",");
|
||||||
|
String[] arr2 = newVal.split(",");
|
||||||
|
if(arr1.length == arr2.length){
|
||||||
|
boolean flag = true;
|
||||||
|
Map<String, Integer> map = new HashMap<>();
|
||||||
|
for(String s1: arr1){
|
||||||
|
map.put(s1, 1);
|
||||||
|
}
|
||||||
|
for(String s2: arr2){
|
||||||
|
if(map.get(s2) == null){
|
||||||
|
flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断两个数组是否相等(数组元素不分顺序)
|
* 判断两个数组是否相等(数组元素不分顺序)
|
||||||
*
|
*
|
||||||
|
|
@ -763,7 +817,7 @@ public class oConvertUtils {
|
||||||
Arrays.sort(newVal);
|
Arrays.sort(newVal);
|
||||||
return Arrays.equals(oldVal, newVal);
|
return Arrays.equals(oldVal, newVal);
|
||||||
} else {
|
} else {
|
||||||
if (oldVal == null && newVal == null) {
|
if ((oldVal == null || oldVal.length == 0) && (newVal == null || newVal.length == 0)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -807,4 +861,85 @@ public class oConvertUtils {
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将List 转成 JSONArray
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONArray list2JSONArray(List<String> list){
|
||||||
|
if(list==null || list.size()==0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
for(String str: list){
|
||||||
|
array.add(str);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两个list中的元素是否完全一致
|
||||||
|
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isEqList(List<String> list1, List<String> list2){
|
||||||
|
if(list1.size() != list2.size()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for(String str1: list1){
|
||||||
|
boolean flag = false;
|
||||||
|
for(String str2: list2){
|
||||||
|
if(str1.equals(str2)){
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!flag){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断 list1中的元素是否在list2中出现
|
||||||
|
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
||||||
|
* @param list1
|
||||||
|
* @param list2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isInList(List<String> list1, List<String> list2){
|
||||||
|
for(String str1: list1){
|
||||||
|
boolean flag = false;
|
||||||
|
for(String str2: list2){
|
||||||
|
if(str1.equals(str2)){
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(flag){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算文件大小转成MB
|
||||||
|
* @param uploadCount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Double calculateFileSizeToMb(Long uploadCount){
|
||||||
|
double count = 0.0;
|
||||||
|
if(uploadCount>0) {
|
||||||
|
BigDecimal bigDecimal = new BigDecimal(uploadCount);
|
||||||
|
//换算成MB
|
||||||
|
BigDecimal divide = bigDecimal.divide(new BigDecimal(1048576));
|
||||||
|
count = divide.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import org.apache.commons.fileupload.FileItemStream;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.jeecg.common.util.CommonUtils;
|
import org.jeecg.common.util.CommonUtils;
|
||||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
@ -98,7 +98,7 @@ public class OssBootUtil {
|
||||||
*/
|
*/
|
||||||
public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {
|
public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {
|
||||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||||
FileTypeFilter.fileTypeFilter(file);
|
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||||
|
|
||||||
String filePath = null;
|
String filePath = null;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package org.jeecg.common.util.security;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
|
@ -81,6 +82,12 @@ public abstract class AbstractQueryBlackListHandler {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回黑名单校验结果(不合法直接抛出异常)
|
||||||
|
if(!flag){
|
||||||
|
log.error(this.getError());
|
||||||
|
throw new JeecgSqlInjectionException(this.getError());
|
||||||
|
}
|
||||||
return flag;
|
return flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,601 @@
|
||||||
|
package org.jeecg.common.util.sqlInjection.parse;
|
||||||
|
|
||||||
|
import net.sf.jsqlparser.expression.AllValue;
|
||||||
|
import net.sf.jsqlparser.expression.AnalyticExpression;
|
||||||
|
import net.sf.jsqlparser.expression.AnyComparisonExpression;
|
||||||
|
import net.sf.jsqlparser.expression.ArrayConstructor;
|
||||||
|
import net.sf.jsqlparser.expression.ArrayExpression;
|
||||||
|
import net.sf.jsqlparser.expression.BinaryExpression;
|
||||||
|
import net.sf.jsqlparser.expression.CaseExpression;
|
||||||
|
import net.sf.jsqlparser.expression.CastExpression;
|
||||||
|
import net.sf.jsqlparser.expression.CollateExpression;
|
||||||
|
import net.sf.jsqlparser.expression.ConnectByRootOperator;
|
||||||
|
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;
|
||||||
|
import net.sf.jsqlparser.expression.DateValue;
|
||||||
|
import net.sf.jsqlparser.expression.DoubleValue;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.ExpressionVisitor;
|
||||||
|
import net.sf.jsqlparser.expression.ExtractExpression;
|
||||||
|
import net.sf.jsqlparser.expression.Function;
|
||||||
|
import net.sf.jsqlparser.expression.HexValue;
|
||||||
|
import net.sf.jsqlparser.expression.IntervalExpression;
|
||||||
|
import net.sf.jsqlparser.expression.JdbcNamedParameter;
|
||||||
|
import net.sf.jsqlparser.expression.JdbcParameter;
|
||||||
|
import net.sf.jsqlparser.expression.JsonAggregateFunction;
|
||||||
|
import net.sf.jsqlparser.expression.JsonExpression;
|
||||||
|
import net.sf.jsqlparser.expression.JsonFunction;
|
||||||
|
import net.sf.jsqlparser.expression.JsonFunctionExpression;
|
||||||
|
import net.sf.jsqlparser.expression.KeepExpression;
|
||||||
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import net.sf.jsqlparser.expression.MySQLGroupConcat;
|
||||||
|
import net.sf.jsqlparser.expression.NextValExpression;
|
||||||
|
import net.sf.jsqlparser.expression.NotExpression;
|
||||||
|
import net.sf.jsqlparser.expression.NullValue;
|
||||||
|
import net.sf.jsqlparser.expression.NumericBind;
|
||||||
|
import net.sf.jsqlparser.expression.OracleHierarchicalExpression;
|
||||||
|
import net.sf.jsqlparser.expression.OracleHint;
|
||||||
|
import net.sf.jsqlparser.expression.OracleNamedFunctionParameter;
|
||||||
|
import net.sf.jsqlparser.expression.Parenthesis;
|
||||||
|
import net.sf.jsqlparser.expression.RowConstructor;
|
||||||
|
import net.sf.jsqlparser.expression.RowGetExpression;
|
||||||
|
import net.sf.jsqlparser.expression.SignedExpression;
|
||||||
|
import net.sf.jsqlparser.expression.StringValue;
|
||||||
|
import net.sf.jsqlparser.expression.TimeKeyExpression;
|
||||||
|
import net.sf.jsqlparser.expression.TimeValue;
|
||||||
|
import net.sf.jsqlparser.expression.TimestampValue;
|
||||||
|
import net.sf.jsqlparser.expression.TimezoneExpression;
|
||||||
|
import net.sf.jsqlparser.expression.TryCastExpression;
|
||||||
|
import net.sf.jsqlparser.expression.UserVariable;
|
||||||
|
import net.sf.jsqlparser.expression.ValueListExpression;
|
||||||
|
import net.sf.jsqlparser.expression.VariableAssignment;
|
||||||
|
import net.sf.jsqlparser.expression.WhenClause;
|
||||||
|
import net.sf.jsqlparser.expression.XMLSerializeExpr;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
for (DictModel t : dictList) {
|
||||||
if(t!=null){
|
//update-begin---author:liusq Date:20230517 for:[issues/4917]excel 导出异常---
|
||||||
|
if(t!=null && t.getText()!=null && t.getValue()!=null){
|
||||||
|
//update-end---author:liusq Date:20230517 for:[issues/4917]excel 导出异常---
|
||||||
//update-begin---author:scott Date:20211220 for:[issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析---
|
//update-begin---author:scott Date:20211220 for:[issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析---
|
||||||
if(t.getValue().contains(EXCEL_SPLIT_TAG)){
|
if(t.getValue().contains(EXCEL_SPLIT_TAG)){
|
||||||
String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG);
|
String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package org.jeecg.config;
|
package org.jeecg.config;
|
||||||
|
|
||||||
import org.jeecg.config.vo.DomainUrl;
|
import org.jeecg.config.vo.*;
|
||||||
import org.jeecg.config.vo.Elasticsearch;
|
|
||||||
import org.jeecg.config.vo.Path;
|
|
||||||
import org.jeecg.config.vo.Shiro;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
@ -29,10 +26,12 @@ public class JeecgBaseConfig {
|
||||||
* 本地:local\Minio:minio\阿里云:alioss
|
* 本地:local\Minio:minio\阿里云:alioss
|
||||||
*/
|
*/
|
||||||
private String uploadType;
|
private String uploadType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否启用安全模式
|
* 平台安全模式配置
|
||||||
*/
|
*/
|
||||||
private Boolean safeMode = false;
|
private Firewall firewall;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shiro拦截排除
|
* shiro拦截排除
|
||||||
*/
|
*/
|
||||||
|
|
@ -58,6 +57,13 @@ public class JeecgBaseConfig {
|
||||||
*/
|
*/
|
||||||
private Elasticsearch elasticsearch;
|
private Elasticsearch elasticsearch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private WeiXinPay weiXinPay;
|
||||||
|
|
||||||
|
|
||||||
public Elasticsearch getElasticsearch() {
|
public Elasticsearch getElasticsearch() {
|
||||||
return elasticsearch;
|
return elasticsearch;
|
||||||
}
|
}
|
||||||
|
|
@ -66,12 +72,12 @@ public class JeecgBaseConfig {
|
||||||
this.elasticsearch = elasticsearch;
|
this.elasticsearch = elasticsearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getSafeMode() {
|
public Firewall getFirewall() {
|
||||||
return safeMode;
|
return firewall;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSafeMode(Boolean safeMode) {
|
public void setFirewall(Firewall firewall) {
|
||||||
this.safeMode = safeMode;
|
this.firewall = firewall;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSignatureSecret() {
|
public String getSignatureSecret() {
|
||||||
|
|
@ -129,4 +135,13 @@ public class JeecgBaseConfig {
|
||||||
public void setUploadType(String uploadType) {
|
public void setUploadType(String uploadType) {
|
||||||
this.uploadType = uploadType;
|
this.uploadType = uploadType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WeiXinPay getWeiXinPay() {
|
||||||
|
return weiXinPay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWeiXinPay(WeiXinPay weiXinPay) {
|
||||||
|
this.weiXinPay = weiXinPay;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ public class Swagger2Config implements WebMvcConfigurer {
|
||||||
// 描述
|
// 描述
|
||||||
.description("后台API接口")
|
.description("后台API接口")
|
||||||
// 作者
|
// 作者
|
||||||
.contact(new Contact("北京敲敲云科技有限公司","www.jeccg.com","jeecgos@163.com"))
|
.contact(new Contact("北京国炬信息技术有限公司","www.jeccg.com","jeecgos@163.com"))
|
||||||
.license("The Apache License, Version 2.0")
|
.license("The Apache License, Version 2.0")
|
||||||
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
|
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class WebSocketConfig {
|
||||||
FilterRegistrationBean bean = new FilterRegistrationBean();
|
FilterRegistrationBean bean = new FilterRegistrationBean();
|
||||||
bean.setFilter(websocketFilter());
|
bean.setFilter(websocketFilter());
|
||||||
//TODO 临时注释掉,测试下线上socket总断的问题
|
//TODO 临时注释掉,测试下线上socket总断的问题
|
||||||
bean.addUrlPatterns("/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
bean.addUrlPatterns("/taskCountSocket/*", "/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,10 +9,7 @@ import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||||
import org.crazycake.shiro.IRedisManager;
|
import org.crazycake.shiro.*;
|
||||||
import org.crazycake.shiro.RedisCacheManager;
|
|
||||||
import org.crazycake.shiro.RedisClusterManager;
|
|
||||||
import org.crazycake.shiro.RedisManager;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
|
|
@ -20,16 +17,21 @@ import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
|
||||||
import org.jeecg.config.shiro.filters.JwtFilter;
|
import org.jeecg.config.shiro.filters.JwtFilter;
|
||||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
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.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||||
import redis.clients.jedis.HostAndPort;
|
import redis.clients.jedis.HostAndPort;
|
||||||
import redis.clients.jedis.JedisCluster;
|
import redis.clients.jedis.JedisCluster;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
@ -49,6 +51,8 @@ public class ShiroConfig {
|
||||||
private Environment env;
|
private Environment env;
|
||||||
@Resource
|
@Resource
|
||||||
private JeecgBaseConfig jeecgBaseConfig;
|
private JeecgBaseConfig jeecgBaseConfig;
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RedisProperties redisProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter Chain定义说明
|
* Filter Chain定义说明
|
||||||
|
|
@ -74,7 +78,6 @@ public class ShiroConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChainDefinitionMap.put("/mini/user/phoneLogin","anon");//小程序手机号登录
|
filterChainDefinitionMap.put("/mini/user/phoneLogin","anon");//小程序手机号登录
|
||||||
filterChainDefinitionMap.put("/mini/user/login","anon");//小程序登录
|
filterChainDefinitionMap.put("/mini/user/login","anon");//小程序登录
|
||||||
filterChainDefinitionMap.put("/mini/article/**","anon");//小程序-文章
|
filterChainDefinitionMap.put("/mini/article/**","anon");//小程序-文章
|
||||||
|
|
@ -104,8 +107,6 @@ public class ShiroConfig {
|
||||||
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
|
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
|
||||||
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
|
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
|
||||||
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
|
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
|
||||||
filterChainDefinitionMap.put("/sys/register", "anon"); //注册接口排除
|
|
||||||
filterChainDefinitionMap.put("/sys/findPassWord", "anon"); //找回密码接口排除
|
|
||||||
filterChainDefinitionMap.put("/sys/mLogin", "anon"); //登录接口排除
|
filterChainDefinitionMap.put("/sys/mLogin", "anon"); //登录接口排除
|
||||||
filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
|
filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
|
||||||
filterChainDefinitionMap.put("/sys/thirdLogin/**", "anon"); //第三方登录
|
filterChainDefinitionMap.put("/sys/thirdLogin/**", "anon"); //第三方登录
|
||||||
|
|
@ -119,6 +120,9 @@ public class ShiroConfig {
|
||||||
filterChainDefinitionMap.put("/auth/2step-code", "anon");//登录验证码
|
filterChainDefinitionMap.put("/auth/2step-code", "anon");//登录验证码
|
||||||
filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token
|
filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token
|
||||||
filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf预览
|
filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf预览
|
||||||
|
|
||||||
|
//filterChainDefinitionMap.put("/sys/common/view/**", "anon");//图片预览不限制token
|
||||||
|
//filterChainDefinitionMap.put("/sys/common/download/**", "anon");//文件下载不限制token
|
||||||
filterChainDefinitionMap.put("/generic/**", "anon");//pdf预览需要文件
|
filterChainDefinitionMap.put("/generic/**", "anon");//pdf预览需要文件
|
||||||
|
|
||||||
filterChainDefinitionMap.put("/sys/getLoginQrcode/**", "anon"); //登录二维码
|
filterChainDefinitionMap.put("/sys/getLoginQrcode/**", "anon"); //登录二维码
|
||||||
|
|
@ -126,6 +130,7 @@ public class ShiroConfig {
|
||||||
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
||||||
|
|
||||||
|
|
||||||
|
//update-begin--Author:scott Date:20221116 for:排除静态资源后缀
|
||||||
filterChainDefinitionMap.put("/", "anon");
|
filterChainDefinitionMap.put("/", "anon");
|
||||||
filterChainDefinitionMap.put("/doc.html", "anon");
|
filterChainDefinitionMap.put("/doc.html", "anon");
|
||||||
filterChainDefinitionMap.put("/**/*.js", "anon");
|
filterChainDefinitionMap.put("/**/*.js", "anon");
|
||||||
|
|
@ -140,6 +145,7 @@ public class ShiroConfig {
|
||||||
filterChainDefinitionMap.put("/**/*.ttf", "anon");
|
filterChainDefinitionMap.put("/**/*.ttf", "anon");
|
||||||
filterChainDefinitionMap.put("/**/*.woff", "anon");
|
filterChainDefinitionMap.put("/**/*.woff", "anon");
|
||||||
filterChainDefinitionMap.put("/**/*.woff2", "anon");
|
filterChainDefinitionMap.put("/**/*.woff2", "anon");
|
||||||
|
//update-end--Author:scott Date:20221116 for:排除静态资源后缀
|
||||||
|
|
||||||
filterChainDefinitionMap.put("/druid/**", "anon");
|
filterChainDefinitionMap.put("/druid/**", "anon");
|
||||||
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
|
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
|
||||||
|
|
@ -147,13 +153,21 @@ public class ShiroConfig {
|
||||||
filterChainDefinitionMap.put("/webjars/**", "anon");
|
filterChainDefinitionMap.put("/webjars/**", "anon");
|
||||||
filterChainDefinitionMap.put("/v2/**", "anon");
|
filterChainDefinitionMap.put("/v2/**", "anon");
|
||||||
|
|
||||||
|
// update-begin--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||||
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
|
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
|
||||||
|
// update-end--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||||
|
|
||||||
//积木报表排除
|
//积木报表排除
|
||||||
filterChainDefinitionMap.put("/jmreport/**", "anon");
|
filterChainDefinitionMap.put("/jmreport/**", "anon");
|
||||||
filterChainDefinitionMap.put("/**/*.js.map", "anon");
|
filterChainDefinitionMap.put("/**/*.js.map", "anon");
|
||||||
filterChainDefinitionMap.put("/**/*.css.map", "anon");
|
filterChainDefinitionMap.put("/**/*.css.map", "anon");
|
||||||
|
|
||||||
|
//拖拽仪表盘设计器排除
|
||||||
|
filterChainDefinitionMap.put("/drag/view", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/page/queryById", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getAllChartData", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalData", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/mock/json/**", "anon");
|
||||||
//大屏模板例子
|
//大屏模板例子
|
||||||
filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
|
filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
|
||||||
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
|
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
|
||||||
|
|
@ -172,10 +186,10 @@ public class ShiroConfig {
|
||||||
//测试模块排除
|
//测试模块排除
|
||||||
filterChainDefinitionMap.put("/test/seata/**", "anon");
|
filterChainDefinitionMap.put("/test/seata/**", "anon");
|
||||||
|
|
||||||
// update-begin--author:liusq Date:20230522 for:[issues/4829]访问不存在的url时会提示Token失效,请重新登录呢
|
|
||||||
//错误路径排除
|
//错误路径排除
|
||||||
filterChainDefinitionMap.put("/error", "anon");
|
filterChainDefinitionMap.put("/error", "anon");
|
||||||
// update-end--author:liusq Date:20230522 for:[issues/4829]访问不存在的url时会提示Token失效,请重新登录呢
|
// 企业微信证书排除
|
||||||
|
filterChainDefinitionMap.put("/WW_verify*", "anon");
|
||||||
|
|
||||||
// 添加自己的过滤器并且取名为jwt
|
// 添加自己的过滤器并且取名为jwt
|
||||||
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||||
|
|
@ -193,6 +207,20 @@ public class ShiroConfig {
|
||||||
return shiroFilterFactoryBean;
|
return shiroFilterFactoryBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean shiroFilterRegistration() {
|
||||||
|
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||||
|
registration.setFilter(new DelegatingFilterProxy("shiroFilterFactoryBean"));
|
||||||
|
registration.setEnabled(true);
|
||||||
|
registration.addUrlPatterns("/*");
|
||||||
|
//支持异步
|
||||||
|
registration.setAsyncSupported(true);
|
||||||
|
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
||||||
@Bean("securityManager")
|
@Bean("securityManager")
|
||||||
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
||||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||||
|
|
@ -270,11 +298,24 @@ public class ShiroConfig {
|
||||||
public IRedisManager redisManager() {
|
public IRedisManager redisManager() {
|
||||||
log.info("===============(2)创建RedisManager,连接Redis..");
|
log.info("===============(2)创建RedisManager,连接Redis..");
|
||||||
IRedisManager manager;
|
IRedisManager manager;
|
||||||
|
// sentinel cluster redis(【issues/5569】shiro集成 redis 不支持 sentinel 方式部署的redis集群 #5569)
|
||||||
|
if (Objects.nonNull(redisProperties)
|
||||||
|
&& Objects.nonNull(redisProperties.getSentinel())
|
||||||
|
&& !CollectionUtils.isEmpty(redisProperties.getSentinel().getNodes())) {
|
||||||
|
RedisSentinelManager sentinelManager = new RedisSentinelManager();
|
||||||
|
sentinelManager.setMasterName(redisProperties.getSentinel().getMaster());
|
||||||
|
sentinelManager.setHost(String.join(",", redisProperties.getSentinel().getNodes()));
|
||||||
|
sentinelManager.setPassword(redisProperties.getSentinel().getPassword());
|
||||||
|
sentinelManager.setDatabase(redisProperties.getDatabase());
|
||||||
|
|
||||||
|
return sentinelManager;
|
||||||
|
}
|
||||||
|
|
||||||
// redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com
|
// redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com
|
||||||
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
|
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
|
||||||
RedisManager redisManager = new RedisManager();
|
RedisManager redisManager = new RedisManager();
|
||||||
redisManager.setHost(lettuceConnectionFactory.getHostName());
|
redisManager.setHost(lettuceConnectionFactory.getHostName() + ":" + lettuceConnectionFactory.getPort());
|
||||||
redisManager.setPort(lettuceConnectionFactory.getPort());
|
//(lettuceConnectionFactory.getPort());
|
||||||
redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
|
redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
|
||||||
redisManager.setTimeout(0);
|
redisManager.setTimeout(0);
|
||||||
if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
|
if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,11 @@ public class ShiroRealm extends AuthorizingRealm {
|
||||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||||
log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
|
log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
|
||||||
String username = null;
|
String username = null;
|
||||||
|
String userId = null;
|
||||||
if (principals != null) {
|
if (principals != null) {
|
||||||
LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
|
LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
|
||||||
username = sysUser.getUsername();
|
username = sysUser.getUsername();
|
||||||
|
userId = sysUser.getId();
|
||||||
}
|
}
|
||||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||||
|
|
||||||
|
|
@ -74,7 +76,7 @@ public class ShiroRealm extends AuthorizingRealm {
|
||||||
info.setRoles(roleSet);
|
info.setRoles(roleSet);
|
||||||
|
|
||||||
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
|
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
|
||||||
Set<String> permissionSet = commonApi.queryUserAuths(username);
|
Set<String> permissionSet = commonApi.queryUserAuths(userId);
|
||||||
info.addStringPermissions(permissionSet);
|
info.addStringPermissions(permissionSet);
|
||||||
//System.out.println(permissionSet);
|
//System.out.println(permissionSet);
|
||||||
log.info("===============Shiro权限认证成功==============");
|
log.info("===============Shiro权限认证成功==============");
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class SignAuthInterceptor implements HandlerInterceptor {
|
||||||
String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP);
|
String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP);
|
||||||
|
|
||||||
if(oConvertUtils.isEmpty(xTimestamp)){
|
if(oConvertUtils.isEmpty(xTimestamp)){
|
||||||
Result<?> result = Result.error("Sign签名校验失败!");
|
Result<?> result = Result.error("Sign签名校验失败,时间戳为空!");
|
||||||
log.error("Sign 签名校验失败!Header xTimestamp 为空");
|
log.error("Sign 签名校验失败!Header xTimestamp 为空");
|
||||||
//校验失败返回前端
|
//校验失败返回前端
|
||||||
response.setCharacterEncoding("UTF-8");
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
|
@ -79,6 +79,7 @@ public class SignAuthInterceptor implements HandlerInterceptor {
|
||||||
log.debug("Sign 签名通过!Header Sign : {}",headerSign);
|
log.debug("Sign 签名通过!Header Sign : {}",headerSign);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
log.info("sign allParams: {}", allParams);
|
||||||
log.error("request URI = " + request.getRequestURI());
|
log.error("request URI = " + request.getRequestURI());
|
||||||
log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
|
log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
|
||||||
//校验失败返回前端
|
//校验失败返回前端
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
package org.jeecg.config.sign.util;
|
package org.jeecg.config.sign.util;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jeecg.common.constant.SymbolConstant;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
@ -17,6 +10,15 @@ import java.util.Map;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* http 工具类 获取请求中的参数
|
* http 工具类 获取请求中的参数
|
||||||
*
|
*
|
||||||
|
|
@ -44,8 +46,12 @@ public class HttpUtils {
|
||||||
|
|
||||||
//https://www.52dianzi.com/category/article/37/565371.html
|
//https://www.52dianzi.com/category/article/37/565371.html
|
||||||
if(deString.contains("%")){
|
if(deString.contains("%")){
|
||||||
deString = URLDecoder.decode(deString, "UTF-8");
|
try {
|
||||||
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
deString = URLDecoder.decode(deString, "UTF-8");
|
||||||
|
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
||||||
|
} catch (Exception e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.info(" pathVariable decode: {}",deString);
|
log.info(" pathVariable decode: {}",deString);
|
||||||
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
||||||
|
|
@ -166,7 +172,11 @@ public class HttpUtils {
|
||||||
String[] params = param.split("&");
|
String[] params = param.split("&");
|
||||||
for (String s : params) {
|
for (String s : params) {
|
||||||
int index = s.indexOf("=");
|
int index = s.indexOf("=");
|
||||||
result.put(s.substring(0, index), s.substring(index + 1));
|
//update-begin---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
|
if (index != -1) {
|
||||||
|
result.put(s.substring(0, index), s.substring(index + 1));
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +200,11 @@ public class HttpUtils {
|
||||||
String[] params = param.split("&");
|
String[] params = param.split("&");
|
||||||
for (String s : params) {
|
for (String s : params) {
|
||||||
int index = s.indexOf("=");
|
int index = s.indexOf("=");
|
||||||
result.put(s.substring(0, index), s.substring(index + 1));
|
//update-begin---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
|
if (index != -1) {
|
||||||
|
result.put(s.substring(0, index), s.substring(index + 1));
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>jeecg-boot-parent</artifactId>
|
||||||
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
|
<version>3.6.3</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>jeecg-module-demo</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<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>
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
//package org.jeecg.modules.demo.cloud.controller;
|
||||||
|
//
|
||||||
|
//import com.alibaba.csp.sentinel.annotation.SentinelResource;
|
||||||
|
//import io.swagger.annotations.Api;
|
||||||
|
//import io.swagger.annotations.ApiOperation;
|
||||||
|
//import lombok.extern.slf4j.Slf4j;
|
||||||
|
//import org.jeecg.common.api.vo.Result;
|
||||||
|
//import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
//import org.jeecg.common.system.vo.DictModel;
|
||||||
|
//import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
//import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
//import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
//import org.springframework.web.bind.annotation.RestController;
|
||||||
|
//import javax.annotation.Resource;
|
||||||
|
//import java.util.List;
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// *
|
||||||
|
// */
|
||||||
|
//@Slf4j
|
||||||
|
//@Api(tags = "【微服务】单元测试")
|
||||||
|
//@RestController
|
||||||
|
//@RequestMapping("/test")
|
||||||
|
//public class JcloudDemoFeignController {
|
||||||
|
// @Resource
|
||||||
|
// private ISysBaseAPI sysBaseApi;
|
||||||
|
//// @Autowired
|
||||||
|
//// private ErpHelloApi erpHelloApi;
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 测试
|
||||||
|
// *
|
||||||
|
// * @return
|
||||||
|
// */
|
||||||
|
// @GetMapping("/callSystem")
|
||||||
|
// //@SentinelResource(value = "remoteDict",fallback = "getDefaultHandler")
|
||||||
|
// @ApiOperation(value = "通过feign调用system服务", notes = "测试jeecg-demo服务,是否通过fegin调用system服务接口")
|
||||||
|
// public Result getRemoteDict() {
|
||||||
|
// List<DictModel> list = sysBaseApi.queryAllDict();
|
||||||
|
// return Result.OK(list);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//// /**
|
||||||
|
//// * 测试调用 erp 微服务接口
|
||||||
|
//// * 【如何测试:通过archetype生成微服务模块,快速集成测试】
|
||||||
|
//// * https://help.jeecg.com/java/springcloud/archetype.html
|
||||||
|
//// * @return
|
||||||
|
//// */
|
||||||
|
//// @GetMapping("/callErp")
|
||||||
|
//// @ApiOperation(value = "测试feign erp", notes = "测试feign erp")
|
||||||
|
//// public Result callErp() {
|
||||||
|
//// log.info("call erp 服务");
|
||||||
|
//// String res = erpHelloApi.callHello();
|
||||||
|
//// return Result.OK(res);
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 熔断,默认回调函数
|
||||||
|
// *
|
||||||
|
// * @return
|
||||||
|
// */
|
||||||
|
// public Result<Object> getDefaultHandler() {
|
||||||
|
// log.info("测试JcloudDemoController-remoteDict 熔断降级");
|
||||||
|
// return Result.error("测试JcloudDemoController-remoteDict 熔断降级");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.jeecg.modules.demo.cloud.controller;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.modules.demo.cloud.service.JcloudDemoService;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端提供方——feign接口
|
||||||
|
* 【提供给system-start调用测试,看feign是否畅通】
|
||||||
|
* @author: jeecg-boot
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/test")
|
||||||
|
public class JcloudDemoProviderController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JcloudDemoService jcloudDemoService;
|
||||||
|
|
||||||
|
@GetMapping("/getMessage")
|
||||||
|
public String getMessage(@RequestParam(name = "name") String name) {
|
||||||
|
String msg = jcloudDemoService.getMessage(name);
|
||||||
|
log.info(" 微服务被调用:{} ",msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.jeecg.modules.demo.cloud.service;
|
||||||
|
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: JcloudDemoService接口
|
||||||
|
* @author: jeecg-boot
|
||||||
|
*/
|
||||||
|
public interface JcloudDemoService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取信息(测试)
|
||||||
|
* @param name 姓名
|
||||||
|
* @return "Hello," + name
|
||||||
|
*/
|
||||||
|
String getMessage(String name);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.jeecg.modules.demo.cloud.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.modules.demo.cloud.service.JcloudDemoService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: JcloudDemoServiceImpl实现类
|
||||||
|
* @author: jeecg-boot
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class JcloudDemoServiceImpl implements JcloudDemoService {
|
||||||
|
@Override
|
||||||
|
public String getMessage(String name) {
|
||||||
|
String resMsg = "Hello,我是jeecg-demo服务节点,收到你的消息:【 "+ name +" 】";
|
||||||
|
return resMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
//
|
||||||
|
//package org.jeecg.modules.demo.cloud.xxljob;
|
||||||
|
//
|
||||||
|
//import com.xxl.job.core.biz.model.ReturnT;
|
||||||
|
//import com.xxl.job.core.handler.annotation.XxlJob;
|
||||||
|
//import lombok.extern.slf4j.Slf4j;
|
||||||
|
//import org.jeecg.common.config.mqtoken.UserTokenContext;
|
||||||
|
//import org.jeecg.common.constant.CommonConstant;
|
||||||
|
//import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
//import org.jeecg.common.system.util.JwtUtil;
|
||||||
|
//import org.jeecg.common.util.RedisUtil;
|
||||||
|
//import org.jeecg.common.util.SpringContextUtils;
|
||||||
|
//import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
//import org.springframework.stereotype.Component;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * xxl-job定时任务测试
|
||||||
|
// */
|
||||||
|
//@Slf4j
|
||||||
|
//@Component
|
||||||
|
//public class TestJobHandler {
|
||||||
|
// @Autowired
|
||||||
|
// ISysBaseAPI sysBaseApi;
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 简单任务
|
||||||
|
// *
|
||||||
|
// * 测试:无token调用feign接口
|
||||||
|
// *
|
||||||
|
// * @param params
|
||||||
|
// * @return
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
// @XxlJob(value = "testJob")
|
||||||
|
// public ReturnT<String> demoJobHandler(String params) {
|
||||||
|
// //1.生成临时令牌Token到线程中
|
||||||
|
// UserTokenContext.setToken(getTemporaryToken());
|
||||||
|
//
|
||||||
|
// log.info("我是 jeecg-demo 服务里的定时任务 testJob , 我执行了...............................");
|
||||||
|
// log.info("我调用 jeecg-system 服务的字典接口:{}",sysBaseApi.queryAllDict());
|
||||||
|
// //。。。此处可以写多个feign接口调用
|
||||||
|
//
|
||||||
|
// //2.使用完,删除临时令牌Token
|
||||||
|
// UserTokenContext.remove();
|
||||||
|
// return ReturnT.SUCCESS;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void init() {
|
||||||
|
// log.info("init");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void destroy() {
|
||||||
|
// log.info("destory");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 获取临时令牌
|
||||||
|
// *
|
||||||
|
// * 模拟登陆接口,获取模拟 Token
|
||||||
|
// * @return
|
||||||
|
// */
|
||||||
|
// public static String getTemporaryToken() {
|
||||||
|
// RedisUtil redisUtil = SpringContextUtils.getBean(RedisUtil.class);
|
||||||
|
// // 模拟登录生成Token
|
||||||
|
// String token = JwtUtil.sign("??", "??");
|
||||||
|
// // 设置Token缓存有效时间为 5 分钟
|
||||||
|
// redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
|
||||||
|
// redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, 5 * 60 * 1000);
|
||||||
|
// return token;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
34
jeecg-module-demo/src/main/java/org/jeecg/modules/demo/gpt/cache/LocalCache.java
vendored
Normal file
34
jeecg-module-demo/src/main/java/org/jeecg/modules/demo/gpt/cache/LocalCache.java
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.jeecg.modules.demo.gpt.cache;
|
||||||
|
|
||||||
|
import cn.hutool.cache.CacheUtil;
|
||||||
|
import cn.hutool.cache.impl.TimedCache;
|
||||||
|
import cn.hutool.core.date.DateUnit;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天记录本地缓存
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/1/26 20:06
|
||||||
|
*/
|
||||||
|
public class LocalCache {
|
||||||
|
/**
|
||||||
|
* 缓存时长
|
||||||
|
*/
|
||||||
|
public static final long TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
|
||||||
|
/**
|
||||||
|
* 清理间隔
|
||||||
|
*/
|
||||||
|
private static final long CLEAN_TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
|
||||||
|
/**
|
||||||
|
* 缓存对象
|
||||||
|
*/
|
||||||
|
public static final TimedCache<String, Object> CACHE = CacheUtil.newTimedCache(TIMEOUT);
|
||||||
|
|
||||||
|
static {
|
||||||
|
//启动定时任务
|
||||||
|
CACHE.schedulePrune(CLEAN_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.jeecg.modules.demo.gpt.controller;
|
||||||
|
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.modules.demo.gpt.service.ChatService;
|
||||||
|
import org.jeecg.modules.demo.gpt.vo.ChatHistoryVO;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: chatGpt-聊天接口
|
||||||
|
* @Author: chenrui
|
||||||
|
* @Date: 2024/1/9 16:30
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/ai/chat")
|
||||||
|
public class ChatController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ChatService chatService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建sse连接
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/send")
|
||||||
|
public SseEmitter createConnect(@RequestParam(name = "topicId", required = false) String topicId, @RequestParam(name = "message", required = true) String message) {
|
||||||
|
SseEmitter sse = chatService.createChat();
|
||||||
|
chatService.sendMessage(topicId, message);
|
||||||
|
return sse;
|
||||||
|
}
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
/**
|
||||||
|
* 保存聊天记录
|
||||||
|
* @param chatHistoryVO
|
||||||
|
* @return
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/2/22 13:54
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/history/save")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<?> saveHistory(@RequestBody ChatHistoryVO chatHistoryVO) {
|
||||||
|
return chatService.saveHistory(chatHistoryVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询聊天记录
|
||||||
|
* @return
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/2/22 14:03
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/history/get")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<ChatHistoryVO> getHistoryByTopic() {
|
||||||
|
return chatService.getHistoryByTopic();
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭连接
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/close")
|
||||||
|
public void closeConnect() {
|
||||||
|
chatService.closeChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
package org.jeecg.modules.demo.gpt.listeners;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
|
||||||
|
import com.unfbx.chatgpt.entity.chat.Message;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSourceListener;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
/**
|
||||||
|
* OpenAI的SSE监听
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/1/26 20:06
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class OpenAISSEEventSourceListener extends EventSourceListener {
|
||||||
|
|
||||||
|
private long tokens;
|
||||||
|
|
||||||
|
private SseEmitter sseEmitter;
|
||||||
|
|
||||||
|
private String topicId;
|
||||||
|
|
||||||
|
public OpenAISSEEventSourceListener(SseEmitter sseEmitter) {
|
||||||
|
this.sseEmitter = sseEmitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenAISSEEventSourceListener(String topicId,SseEmitter sseEmitter){
|
||||||
|
this.topicId = topicId;
|
||||||
|
this.sseEmitter = sseEmitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
|
||||||
|
log.info("OpenAI建立sse连接...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) {
|
||||||
|
log.debug("OpenAI返回数据:{}", data);
|
||||||
|
tokens += 1;
|
||||||
|
if (data.equals("[DONE]")) {
|
||||||
|
log.info("OpenAI返回数据结束了");
|
||||||
|
sseEmitter.send(SseEmitter.event()
|
||||||
|
.id("[TOKENS]")
|
||||||
|
.data("<br/><br/>tokens:" + tokens())
|
||||||
|
.reconnectTime(3000));
|
||||||
|
sseEmitter.send(SseEmitter.event()
|
||||||
|
.id("[DONE]")
|
||||||
|
.data("[DONE]")
|
||||||
|
.reconnectTime(3000));
|
||||||
|
// 传输完成后自动关闭sse
|
||||||
|
sseEmitter.complete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
|
||||||
|
try {
|
||||||
|
sseEmitter.send(SseEmitter.event()
|
||||||
|
.id(this.topicId)
|
||||||
|
.data(completionResponse.getChoices().get(0).getDelta())
|
||||||
|
.reconnectTime(3000));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(),e);
|
||||||
|
eventSource.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClosed(@NotNull EventSource eventSource) {
|
||||||
|
log.info("流式输出返回值总共{}tokens", tokens() - 2);
|
||||||
|
log.info("OpenAI关闭sse连接...");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NotNull EventSource eventSource, Throwable t, Response response) {
|
||||||
|
String errMsg = "";
|
||||||
|
ResponseBody body = null == response ? null:response.body();
|
||||||
|
if (Objects.nonNull(body)) {
|
||||||
|
log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t.getMessage());
|
||||||
|
errMsg = body.string();
|
||||||
|
} else {
|
||||||
|
log.error("OpenAI sse连接异常data:{},异常:{}", response, t.getMessage());
|
||||||
|
errMsg = t.getMessage();
|
||||||
|
}
|
||||||
|
eventSource.cancel();
|
||||||
|
sseEmitter.send(SseEmitter.event()
|
||||||
|
.id("[ERR]")
|
||||||
|
.data(Message.builder().content(explainErr(errMsg)).build())
|
||||||
|
.reconnectTime(3000));
|
||||||
|
sseEmitter.send(SseEmitter.event()
|
||||||
|
.id("[DONE]")
|
||||||
|
.data("[DONE]")
|
||||||
|
.reconnectTime(3000));
|
||||||
|
sseEmitter.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String explainErr(String errMsg){
|
||||||
|
if(StringUtils.isEmpty(errMsg)){
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if(errMsg.contains("Rate limit")){
|
||||||
|
return "请求频率太快了,请等待20秒再试.";
|
||||||
|
}
|
||||||
|
return errMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tokens
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long tokens() {
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.jeecg.modules.demo.gpt.service;
|
||||||
|
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.modules.demo.gpt.vo.ChatHistoryVO;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI助手聊天Service
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/1/26 20:08
|
||||||
|
*/
|
||||||
|
public interface ChatService {
|
||||||
|
/**
|
||||||
|
* 创建SSE
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
SseEmitter createChat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭SSE
|
||||||
|
*/
|
||||||
|
void closeChat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端发送消息到服务端
|
||||||
|
*
|
||||||
|
* @param topicId
|
||||||
|
* @param message
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/1/26 20:01
|
||||||
|
*/
|
||||||
|
void sendMessage(String topicId, String message);
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
/**
|
||||||
|
* 保存聊天记录
|
||||||
|
* @param chatHistoryVO
|
||||||
|
* @return
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/2/22 13:37
|
||||||
|
*/
|
||||||
|
Result<?> saveHistory(ChatHistoryVO chatHistoryVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询聊天记录
|
||||||
|
* @return
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/2/22 13:59
|
||||||
|
*/
|
||||||
|
Result<ChatHistoryVO> getHistoryByTopic();
|
||||||
|
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
}
|
||||||
|
|
||||||
|
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
package org.jeecg.modules.demo.gpt.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
|
import com.unfbx.chatgpt.OpenAiStreamClient;
|
||||||
|
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
|
||||||
|
import com.unfbx.chatgpt.entity.chat.Message;
|
||||||
|
import com.unfbx.chatgpt.exception.BaseException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
|
import org.jeecg.common.util.UUIDGenerator;
|
||||||
|
import org.jeecg.modules.demo.gpt.cache.LocalCache;
|
||||||
|
import org.jeecg.modules.demo.gpt.listeners.OpenAISSEEventSourceListener;
|
||||||
|
import org.jeecg.modules.demo.gpt.service.ChatService;
|
||||||
|
import org.jeecg.modules.demo.gpt.vo.ChatHistoryVO;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI助手聊天Service
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/1/26 20:07
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class ChatServiceImpl implements ChatService {
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
private static final String CACHE_KEY_PREFIX = "ai:chart:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final String CACHE_KEY_MSG_CONTEXT = "msg_content";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final String CACHE_KEY_MSG_HISTORY = "msg_history";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RedisTemplate redisTemplate;
|
||||||
|
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
|
||||||
|
private OpenAiStreamClient openAiStreamClient = null;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240131 for:[QQYUN-8212]fix 没有配置启动报错------------
|
||||||
|
public ChatServiceImpl() {
|
||||||
|
try {
|
||||||
|
this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防止client不能成功注入
|
||||||
|
* @return
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2024/2/3 23:08
|
||||||
|
*/
|
||||||
|
private OpenAiStreamClient ensureClient(){
|
||||||
|
if(null == this.openAiStreamClient){
|
||||||
|
this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
|
||||||
|
}
|
||||||
|
return this.openAiStreamClient;
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240131 for:[QQYUN-8212]fix 没有配置启动报错------------
|
||||||
|
|
||||||
|
private String getUserId() {
|
||||||
|
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
|
return sysUser.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SseEmitter createChat() {
|
||||||
|
String uid = getUserId();
|
||||||
|
//默认30秒超时,设置为0L则永不超时
|
||||||
|
SseEmitter sseEmitter = new SseEmitter(-0L);
|
||||||
|
//完成后回调
|
||||||
|
sseEmitter.onCompletion(() -> {
|
||||||
|
log.info("[{}]结束连接...................",uid);
|
||||||
|
LocalCache.CACHE.remove(uid);
|
||||||
|
});
|
||||||
|
//超时回调
|
||||||
|
sseEmitter.onTimeout(() -> {
|
||||||
|
log.info("[{}]连接超时...................", uid);
|
||||||
|
});
|
||||||
|
//异常回调
|
||||||
|
sseEmitter.onError(
|
||||||
|
throwable -> {
|
||||||
|
try {
|
||||||
|
log.info("[{}]连接异常,{}", uid, throwable.toString());
|
||||||
|
sseEmitter.send(SseEmitter.event()
|
||||||
|
.id(uid)
|
||||||
|
.name("发生异常!")
|
||||||
|
.data(Message.builder().content("发生异常请重试!").build())
|
||||||
|
.reconnectTime(3000));
|
||||||
|
LocalCache.CACHE.put(uid, sseEmitter);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage(),e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
sseEmitter.send(SseEmitter.event().reconnectTime(5000));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage(),e);
|
||||||
|
}
|
||||||
|
LocalCache.CACHE.put(uid, sseEmitter);
|
||||||
|
log.info("[{}]创建sse连接成功!", uid);
|
||||||
|
return sseEmitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeChat() {
|
||||||
|
String uid = getUserId();
|
||||||
|
SseEmitter sse = (SseEmitter) LocalCache.CACHE.get(uid);
|
||||||
|
if (sse != null) {
|
||||||
|
sse.complete();
|
||||||
|
//移除
|
||||||
|
LocalCache.CACHE.remove(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String topicId, String message) {
|
||||||
|
String uid = getUserId();
|
||||||
|
if (StrUtil.isBlank(message)) {
|
||||||
|
log.info("参数异常,message为null");
|
||||||
|
throw new BaseException("参数异常,message不能为空~");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(topicId)) {
|
||||||
|
topicId = UUIDGenerator.generate();
|
||||||
|
}
|
||||||
|
//update-begin---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
log.info("话题id:{}", topicId);
|
||||||
|
String cacheKey = CACHE_KEY_PREFIX + uid + "_" + topicId;
|
||||||
|
String messageContext = (String) redisTemplate.opsForHash().get(cacheKey, CACHE_KEY_MSG_CONTEXT);
|
||||||
|
List<Message> msgHistory = new ArrayList<>();
|
||||||
|
if (StrUtil.isNotBlank(messageContext)) {
|
||||||
|
List<Message> messages = JSONArray.parseArray(messageContext, Message.class);
|
||||||
|
msgHistory = messages == null ? new ArrayList<>() : messages;
|
||||||
|
}
|
||||||
|
Message currentMessage = Message.builder().content(message).role(Message.Role.USER).build();
|
||||||
|
msgHistory.add(currentMessage);
|
||||||
|
|
||||||
|
SseEmitter sseEmitter = (SseEmitter) LocalCache.CACHE.get(uid);
|
||||||
|
if (sseEmitter == null) {
|
||||||
|
log.info("聊天消息推送失败uid:[{}],没有创建连接,请重试。", uid);
|
||||||
|
throw new JeecgBootException("聊天消息推送失败uid:[{}],没有创建连接,请重试。~");
|
||||||
|
}
|
||||||
|
OpenAISSEEventSourceListener openAIEventSourceListener = new OpenAISSEEventSourceListener(topicId, sseEmitter);
|
||||||
|
ChatCompletion completion = ChatCompletion
|
||||||
|
.builder()
|
||||||
|
.messages(msgHistory)
|
||||||
|
.model(ChatCompletion.Model.GPT_3_5_TURBO.getName())
|
||||||
|
.build();
|
||||||
|
ensureClient().streamChatCompletion(completion, openAIEventSourceListener);
|
||||||
|
redisTemplate.opsForHash().put(cacheKey, CACHE_KEY_MSG_CONTEXT, JSONUtil.toJsonStr(msgHistory));
|
||||||
|
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
Result.ok(completion.tokens());
|
||||||
|
}
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
@Override
|
||||||
|
public Result<?> saveHistory(ChatHistoryVO chatHistoryVO) {
|
||||||
|
String uid = getUserId();
|
||||||
|
String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
|
||||||
|
redisTemplate.opsForValue().set(cacheKey, chatHistoryVO.getContent());
|
||||||
|
return Result.OK("保存成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<ChatHistoryVO> getHistoryByTopic() {
|
||||||
|
String uid = getUserId();
|
||||||
|
String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
|
||||||
|
String historyContent = (String) redisTemplate.opsForValue().get(cacheKey);
|
||||||
|
ChatHistoryVO chatHistoryVO = new ChatHistoryVO();
|
||||||
|
chatHistoryVO.setContent(historyContent);
|
||||||
|
return Result.OK(chatHistoryVO);
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||||
|
}
|
||||||
|
|
||||||
|
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.jeecg.modules.demo.gpt.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 聊天记录
|
||||||
|
* @Author: chenrui
|
||||||
|
* @Date: 2024/2/22 13:36
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ChatHistoryVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 3238429500037511283L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 话题id
|
||||||
|
*/
|
||||||
|
String topicId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天记录内容
|
||||||
|
*/
|
||||||
|
String content;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
package org.jeecg.modules.demo.mock;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.swing.filechooser.FileSystemView;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: MockController
|
||||||
|
* @author: jeecg-boot
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/mock/api")
|
||||||
|
@Slf4j
|
||||||
|
public class MockController {
|
||||||
|
|
||||||
|
private final String JSON_PATH = "classpath:org/jeecg/modules/demo/mock/json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用json访问接口
|
||||||
|
* 格式: http://localhost:8080/jeecg-boot/api/json/{filename}
|
||||||
|
* @param filename
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@RequestMapping(value = "/json/{filename}", method = RequestMethod.GET)
|
||||||
|
public String getJsonData(@PathVariable("filename") String filename) {
|
||||||
|
String jsonpath = "classpath:org/jeecg/modules/demo/mock/json/"+filename+".json";
|
||||||
|
return readJson(jsonpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/asynTreeList")
|
||||||
|
public Result asynTreeList(String id) {
|
||||||
|
String json = readJson(JSON_PATH + "/asyn_tree_list_" + id + ".json");
|
||||||
|
return Result.OK(JSON.parseArray(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/user")
|
||||||
|
public String user() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/user.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 老的登录获取用户信息接口
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/user/info")
|
||||||
|
public String userInfo() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/user_info.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/role")
|
||||||
|
public String role() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/role.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/service")
|
||||||
|
public String service() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/service.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/permission")
|
||||||
|
public String permission() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/permission.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/permission/no-pager")
|
||||||
|
public String permissionNoPage() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/permission_no_page.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 省市县
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/area")
|
||||||
|
public String area() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/area.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试报表数据
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/report/getYearCountInfo")
|
||||||
|
public String getYearCountInfo() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
|
||||||
|
}
|
||||||
|
@GetMapping(value = "/report/getMonthCountInfo")
|
||||||
|
public String getMonthCountInfo() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
|
||||||
|
}
|
||||||
|
@GetMapping(value = "/report/getCntrNoCountInfo")
|
||||||
|
public String getCntrNoCountInfo() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
|
||||||
|
}
|
||||||
|
@GetMapping(value = "/report/getCabinetCountInfo")
|
||||||
|
public String getCabinetCountInfo() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
|
||||||
|
}
|
||||||
|
@GetMapping(value = "/report/getTubiao")
|
||||||
|
public String getTubiao() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/getTubiao.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时磁盘监控
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/queryDiskInfo")
|
||||||
|
public Result<List<Map<String,Object>>> queryDiskInfo(HttpServletRequest request, HttpServletResponse response){
|
||||||
|
Result<List<Map<String,Object>>> res = new Result<>();
|
||||||
|
try {
|
||||||
|
// 当前文件系统类
|
||||||
|
FileSystemView fsv = FileSystemView.getFileSystemView();
|
||||||
|
// 列出所有windows 磁盘
|
||||||
|
File[] fs = File.listRoots();
|
||||||
|
log.info("查询磁盘信息:"+fs.length+"个");
|
||||||
|
List<Map<String,Object>> list = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < fs.length; i++) {
|
||||||
|
if(fs[i].getTotalSpace()==0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<String,Object> map = new HashMap<>(5);
|
||||||
|
map.put("name", fsv.getSystemDisplayName(fs[i]));
|
||||||
|
map.put("max", fs[i].getTotalSpace());
|
||||||
|
map.put("rest", fs[i].getFreeSpace());
|
||||||
|
map.put("restPPT", fs[i].getFreeSpace()*100/fs[i].getTotalSpace());
|
||||||
|
list.add(map);
|
||||||
|
log.info(map.toString());
|
||||||
|
}
|
||||||
|
res.setResult(list);
|
||||||
|
res.success("查询成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
res.error500("查询失败"+e.getMessage());
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* 工作台首页的数据
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/list/search/projects")
|
||||||
|
public String projects() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_projects.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/workplace/activity")
|
||||||
|
public String activity() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_activity.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/workplace/teams")
|
||||||
|
public String teams() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_teams.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/workplace/radar")
|
||||||
|
public String radar() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_radar.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/task/process")
|
||||||
|
public String taskProcess() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/task_process.json");
|
||||||
|
}
|
||||||
|
//-------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
//author:lvdandan-----date:20190315---for:添加数据日志json----
|
||||||
|
/**
|
||||||
|
* 数据日志
|
||||||
|
*/
|
||||||
|
public String sysDataLogJson() {
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/sysdatalog.json");
|
||||||
|
}
|
||||||
|
//author:lvdandan-----date:20190315---for:添加数据日志json----
|
||||||
|
|
||||||
|
//--update-begin--author:wangshuai-----date:20201023---for:返回用户信息json数据----
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/getUserInfo")
|
||||||
|
public String getUserInfo(){
|
||||||
|
return readJson("classpath:org/jeecg/modules/demo/mock/json/userinfo.json");
|
||||||
|
}
|
||||||
|
//--update-end--author:wangshuai-----date:20201023---for:返回用户信息json数据----
|
||||||
|
/**
|
||||||
|
* 读取json格式文件
|
||||||
|
* @param jsonSrc
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String readJson(String jsonSrc) {
|
||||||
|
String json = "";
|
||||||
|
try {
|
||||||
|
//File jsonFile = ResourceUtils.getFile(jsonSrc);
|
||||||
|
//json = FileUtils.re.readFileToString(jsonFile);
|
||||||
|
//换个写法,解决springboot读取jar包中文件的问题
|
||||||
|
InputStream stream = getClass().getClassLoader().getResourceAsStream(jsonSrc.replace("classpath:", ""));
|
||||||
|
json = IOUtils.toString(stream,"UTF-8");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage(),e);
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,36 @@
|
||||||
|
[
|
||||||
|
{"value": "110000", "label": "北京市"},
|
||||||
|
{"value": "120000", "label": "天津市"},
|
||||||
|
{"value": "130000", "label": "河北省"},
|
||||||
|
{"value": "140000", "label": "山西省"},
|
||||||
|
{"value": "150000", "label": "内蒙古自治区"},
|
||||||
|
{"value": "210000", "label": "辽宁省"},
|
||||||
|
{"value": "220000", "label": "吉林省"},
|
||||||
|
{"value": "230000", "label": "黑龙江省"},
|
||||||
|
{"value": "310000", "label": "上海市"},
|
||||||
|
{"value": "320000", "label": "江苏省"},
|
||||||
|
{"value": "330000", "label": "浙江省"},
|
||||||
|
{"value": "340000", "label": "安徽省"},
|
||||||
|
{"value": "350000", "label": "福建省"},
|
||||||
|
{"value": "360000", "label": "江西省"},
|
||||||
|
{"value": "370000", "label": "山东省"},
|
||||||
|
{"value": "410000", "label": "河南省"},
|
||||||
|
{"value": "420000", "label": "湖北省"},
|
||||||
|
{"value": "430000", "label": "湖南省"},
|
||||||
|
{"value": "440000", "label": "广东省"},
|
||||||
|
{"value": "450000", "label": "广西壮族自治区"},
|
||||||
|
{"value": "460000", "label": "海南省"},
|
||||||
|
{"value": "500000", "label": "重庆市"},
|
||||||
|
{"value": "510000", "label": "四川省"},
|
||||||
|
{"value": "520000", "label": "贵州省"},
|
||||||
|
{"value": "530000", "label": "云南省"},
|
||||||
|
{"value": "540000", "label": "西藏自治区"},
|
||||||
|
{"value": "610000", "label": "陕西省"},
|
||||||
|
{"value": "620000", "label": "甘肃省"},
|
||||||
|
{"value": "630000", "label": "青海省"},
|
||||||
|
{"value": "640000", "label": "宁夏回族自治区"},
|
||||||
|
{"value": "650000", "label": "新疆维吾尔自治区"},
|
||||||
|
{"value": "710000", "label": "台湾省"},
|
||||||
|
{"value": "810000", "label": "香港特别行政区"},
|
||||||
|
{"value": "820000", "label": "澳门特别行政区"}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "首页",
|
||||||
|
"component": "dashboard/Analysis",
|
||||||
|
"orderNum": 1,
|
||||||
|
"hasChildren": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "常见案例",
|
||||||
|
"component": "layouts/RouteView",
|
||||||
|
"orderNum": 2,
|
||||||
|
"hasChildren": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "系统监控",
|
||||||
|
"component": "layouts/RouteView",
|
||||||
|
"orderNum": 3,
|
||||||
|
"hasChildren": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "首页",
|
||||||
|
"component": "dashboard/Analysis",
|
||||||
|
"orderNum": 1,
|
||||||
|
"hasChildren": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "系统管理",
|
||||||
|
"component": "layouts/RouteView",
|
||||||
|
"orderNum": 2,
|
||||||
|
"hasChildren": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "常见案例",
|
||||||
|
"component": "layouts/RouteView",
|
||||||
|
"orderNum": 3,
|
||||||
|
"hasChildren": true
|
||||||
|
}
|
||||||
|
]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue