mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-26 16:26:41 +08:00
JeecgBoot 3.6.0大版本发布
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<version>3.5.5</version>
|
||||
<version>3.6.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
@ -175,6 +175,10 @@
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
@ -102,12 +102,12 @@ public interface CommonAPI {
|
||||
|
||||
/**
|
||||
* 13获取表数据字典
|
||||
* @param table
|
||||
* @param tableFilterSql
|
||||
* @param text
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
List<DictModel> queryTableDictItemsByCode(String table, String text, String code);
|
||||
List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code);
|
||||
|
||||
/**
|
||||
* 14 普通字典的翻译,根据多个dictCode和多条数据,多个以逗号分割
|
||||
|
||||
@ -4,7 +4,7 @@ import lombok.Data;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 普通消息
|
||||
@ -43,14 +43,7 @@ public class MessageDTO implements Serializable {
|
||||
* 消息类型 1:消息 2:系统消息
|
||||
*/
|
||||
protected String category;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
//update-begin---author:taoyan ---date:20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
||||
|
||||
/**
|
||||
* 模板消息对应的模板编码
|
||||
*/
|
||||
protected String templateCode;
|
||||
|
||||
/**
|
||||
* 消息类型:org.jeecg.common.constant.enums.MessageTypeEnum
|
||||
* XT("system", "系统消息")
|
||||
@ -60,23 +53,38 @@ public class MessageDTO implements Serializable {
|
||||
*/
|
||||
protected String type;
|
||||
|
||||
|
||||
//---【推送模板相关参数】-------------------------------------------------------------
|
||||
/**
|
||||
* 是否发送Markdown格式的消息
|
||||
*/
|
||||
protected boolean isMarkdown;
|
||||
|
||||
/**
|
||||
* 模板消息对应的模板编码
|
||||
*/
|
||||
protected String templateCode;
|
||||
/**
|
||||
* 解析模板内容 对应的数据
|
||||
*/
|
||||
protected Map<String, Object> data;
|
||||
//update-end---author:taoyan ---date::20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
//---【推送模板相关参数】-------------------------------------------------------------
|
||||
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
/**
|
||||
* 抄送人
|
||||
* 邮件抄送人
|
||||
*/
|
||||
private String copyToUser;
|
||||
|
||||
/**
|
||||
* 邮件推送地址
|
||||
*/
|
||||
protected Set<String> toEmailList;
|
||||
|
||||
/**
|
||||
* 邮件抄送地址
|
||||
*/
|
||||
protected Set<String> ccEmailList;
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
|
||||
public MessageDTO(){
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -112,8 +112,8 @@ public interface CommonConstant {
|
||||
String HAS_CANCLE = "2";
|
||||
|
||||
/**阅读状态(0未读,1已读)*/
|
||||
String HAS_READ_FLAG = "1";
|
||||
String NO_READ_FLAG = "0";
|
||||
Integer HAS_READ_FLAG = 1;
|
||||
Integer NO_READ_FLAG = 0;
|
||||
|
||||
/**优先级(L低,M中,H高)*/
|
||||
String PRIORITY_L = "L";
|
||||
@ -160,6 +160,8 @@ public interface CommonConstant {
|
||||
|
||||
/**字典翻译文本后缀*/
|
||||
String DICT_TEXT_SUFFIX = "_dictText";
|
||||
/**字典翻译颜色后缀*/
|
||||
String DICT_COLOR_SUFFIX = "_dictColor";
|
||||
|
||||
/**
|
||||
* 表单设计器主表类型
|
||||
@ -315,6 +317,8 @@ public interface CommonConstant {
|
||||
String X_TIMESTAMP = "X-TIMESTAMP";
|
||||
/** 租户请求头 更名为:X-Tenant-Id */
|
||||
String TENANT_ID = "X-Tenant-Id";
|
||||
/** 简流接口请求头,用于排除不支持的控件字段 */
|
||||
String X_MiniFlowExclusionFieldMode = "X-Miniflowexclusionfieldmode";
|
||||
/**===============================================================================================*/
|
||||
|
||||
String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
|
||||
@ -388,6 +392,7 @@ public interface CommonConstant {
|
||||
/** 部门表唯一key,orgCode */
|
||||
String DEPART_KEY_ORG_CODE = "orgCode";
|
||||
|
||||
/**======【消息推送相关】==============================================================================*/
|
||||
/**
|
||||
* 发消息 会传递一些信息到map
|
||||
*/
|
||||
@ -398,6 +403,11 @@ public interface CommonConstant {
|
||||
*/
|
||||
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
|
||||
|
||||
/**
|
||||
* 发消息 消息业务类型
|
||||
*/
|
||||
String NOTICE_MSG_BUS_TYPE = "NOTICE_MSG_BUS_TYPE";
|
||||
|
||||
/**
|
||||
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
|
||||
*/
|
||||
@ -420,6 +430,7 @@ public interface CommonConstant {
|
||||
|
||||
/** 消息模板:markdown */
|
||||
String MSG_TEMPLATE_TYPE_MD = "5";
|
||||
/**========【消息推送相关】==========================================================================*/
|
||||
|
||||
/**
|
||||
* 短信验证码redis-key的前缀
|
||||
@ -481,6 +492,11 @@ public interface CommonConstant {
|
||||
*/
|
||||
String USER_TENANT_REFUSE = "4";
|
||||
|
||||
/**
|
||||
* 用户租户状态(邀请)
|
||||
*/
|
||||
String USER_TENANT_INVITE = "5";
|
||||
|
||||
/**
|
||||
* 不是叶子节点
|
||||
*/
|
||||
@ -490,4 +506,71 @@ public interface CommonConstant {
|
||||
* 是叶子节点
|
||||
*/
|
||||
Integer IS_LEAF = 1;
|
||||
|
||||
/**
|
||||
* 钉钉
|
||||
*/
|
||||
String DINGTALK = "DINGTALK";
|
||||
|
||||
/**
|
||||
* 企业微信
|
||||
*/
|
||||
String WECHAT_ENTERPRISE = "WECHAT_ENTERPRISE";
|
||||
|
||||
/**
|
||||
* 系统默认租户id 0
|
||||
*/
|
||||
Integer TENANT_ID_DEFAULT_VALUE = 0;
|
||||
|
||||
/**
|
||||
* 【low-app用】 应用级别的复制
|
||||
*/
|
||||
String COPY_LEVEL_APP = "app";
|
||||
|
||||
/**
|
||||
* 【low-app用】 菜单级别的复制
|
||||
*/
|
||||
String COPY_LEVEL_MENU = "menu";
|
||||
|
||||
|
||||
/**
|
||||
* 【low-app用】 应用备份
|
||||
*/
|
||||
String COPY_LEVEL_BAK = "backup";
|
||||
|
||||
/**
|
||||
* 【low-app用】 从备份还原
|
||||
*/
|
||||
String COPY_LEVEL_COVER = "cover";
|
||||
|
||||
/** 【QQYUN-6034】关联字段变更历史值,缓存半个小时 */
|
||||
String CACHE_REL_FIELD_OLD_VAL = "sys:cache:desform:relFieldOldVal:";
|
||||
|
||||
/**
|
||||
* 排序类型:升序
|
||||
*/
|
||||
String ORDER_TYPE_ASC = "ASC";
|
||||
/**
|
||||
* 排序类型:降序
|
||||
*/
|
||||
String ORDER_TYPE_DESC = "DESC";
|
||||
|
||||
|
||||
//update-begin---author:scott ---date:2023-09-10 for:积木报表常量----
|
||||
/**
|
||||
* 报表允许设计开发的角色
|
||||
*/
|
||||
public static String[] allowDevRoles = new String[]{"lowdeveloper", "admin"};
|
||||
/**
|
||||
* 【对应积木报表的常量】
|
||||
* 数据隔离模式: 按照创建人隔离
|
||||
*/
|
||||
public static final String SAAS_MODE_CREATED = "created";
|
||||
/**
|
||||
* 【对应积木报表的常量】
|
||||
* 数据隔离模式: 按照租户隔离
|
||||
*/
|
||||
public static final String SAAS_MODE_TENANT = "tenant";
|
||||
//update-end---author:scott ---date::2023-09-10 for:积木报表常量----
|
||||
|
||||
}
|
||||
|
||||
@ -28,12 +28,21 @@ public interface CommonSendStatus {
|
||||
public static final String APP_SESSION_SUFFIX = "_app";
|
||||
|
||||
|
||||
/**-----【流程相关通知模板code】------------------------------------------------------------*/
|
||||
/**流程催办——系统通知消息模板*/
|
||||
public static final String TZMB_BPM_CUIBAN = "bpm_cuiban";
|
||||
/**流程抄送——系统通知消息模板*/
|
||||
public static final String TZMB_BPM_CC = "bpm_cc";
|
||||
/**流程催办——邮件通知消息模板*/
|
||||
public static final String TZMB_BPM_CUIBAN_EMAIL = "bpm_cuiban_email";
|
||||
/**标准模板—系统消息通知*/
|
||||
public static final String TZMB_SYS_TS_NOTE = "sys_ts_note";
|
||||
/**流程超时提醒——系统通知消息模板*/
|
||||
public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip";
|
||||
/**-----【流程相关通知模板code】-----------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 系统通知拓展参数(比如:用于流程抄送和催办通知,这里额外传递流程跳转页面所需要的路由参数)
|
||||
*/
|
||||
public static final String MSG_ABSTRACT_JSON = "msg_abstract";
|
||||
}
|
||||
|
||||
@ -116,4 +116,8 @@ public class SymbolConstant {
|
||||
*/
|
||||
public static final String SQUARE_BRACKETS_RIGHT = "]";
|
||||
|
||||
/**
|
||||
* 拼接字符串符号 分号 ;
|
||||
*/
|
||||
public static final String SEMICOLON = ";";
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package org.jeecg.common.util;
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@ -17,7 +17,11 @@ public enum DySmsEnum {
|
||||
/**会议通知*/
|
||||
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"),
|
||||
/**我的计划通知*/
|
||||
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time");
|
||||
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time"),
|
||||
/**支付成功短信通知*/
|
||||
PAY_SUCCESS_NOTICE_CODE("SMS_461735163","敲敲云","realname,money,endTime"),
|
||||
/**会员到期通知提醒*/
|
||||
VIP_EXPIRE_NOTICE_CODE("SMS_461885023","敲敲云","realname,endTime");
|
||||
|
||||
/**
|
||||
* 短信模板编码
|
||||
@ -0,0 +1,66 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
/**
|
||||
* 邮件html模板配置地址美剧
|
||||
*
|
||||
* @author: liusq
|
||||
* @Date: 2023-10-13
|
||||
*/
|
||||
public enum EmailTemplateEnum {
|
||||
/**
|
||||
* 流程催办
|
||||
*/
|
||||
BPM_CUIBAN_EMAIL("bpm_cuiban_email", "/templates/email/bpm_cuiban_email.ftl"),
|
||||
/**
|
||||
* 流程新任务
|
||||
*/
|
||||
BPM_NEW_TASK_EMAIL("bpm_new_task_email", "/templates/email/bpm_new_task_email.ftl"),
|
||||
/**
|
||||
* 表单新增记录
|
||||
*/
|
||||
DESFORM_NEW_DATA_EMAIL("desform_new_data_email", "/templates/email/desform_new_data_email.ftl");
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 模板地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
EmailTemplateEnum(String name, String url) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public static EmailTemplateEnum getByName(String name) {
|
||||
if (oConvertUtils.isEmpty(name)) {
|
||||
return null;
|
||||
}
|
||||
for (EmailTemplateEnum val : values()) {
|
||||
if (val.getName().equals(name)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ import org.jeecg.common.util.oConvertUtils;
|
||||
* 文件类型
|
||||
*/
|
||||
public enum FileTypeEnum {
|
||||
// 文档类型(folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频)
|
||||
// 文档类型(folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频 voice:语音)
|
||||
// FOLDER
|
||||
xls(".xls","excel","excel"),
|
||||
xlsx(".xlsx","excel","excel"),
|
||||
@ -26,7 +26,8 @@ public enum FileTypeEnum {
|
||||
flv(".flv","video","视频"),
|
||||
mp4(".mp4","video","视频"),
|
||||
zip(".zip","zip","压缩包"),
|
||||
pdf(".pdf","pdf","pdf");
|
||||
pdf(".pdf","pdf","pdf"),
|
||||
mp3(".mp3","mp3","语音");
|
||||
|
||||
private String type;
|
||||
private String value;
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
/**
|
||||
* LowApp 切面注解枚举
|
||||
* @date 2022-1-5
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public enum LowAppAopEnum {
|
||||
|
||||
/**
|
||||
* 新增方法
|
||||
*/
|
||||
ADD,
|
||||
/**
|
||||
* 删除方法(包含单个和批量删除)
|
||||
*/
|
||||
DELETE,
|
||||
/** 复制表单操作 */
|
||||
COPY,
|
||||
|
||||
/**
|
||||
* Online表单专用:数据库表转Online表单
|
||||
*/
|
||||
CGFORM_DB_IMPORT,
|
||||
|
||||
/**
|
||||
* 表单设计器专用:子表转工作表
|
||||
*/
|
||||
DESFORM_SUB2WORK
|
||||
}
|
||||
@ -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:流程)
|
||||
@ -0,0 +1,75 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.jeecg.common.system.annotation.EnumDict;
|
||||
import org.jeecg.common.system.vo.DictModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息跳转【vue3】
|
||||
* @Author taoYan
|
||||
* @Date 2022/8/19 20:41
|
||||
**/
|
||||
@EnumDict("messageHref")
|
||||
public enum Vue3MessageHrefEnum {
|
||||
|
||||
/**
|
||||
* 流程催办
|
||||
*/
|
||||
BPM("bpm", "/task/myHandleTaskInfo"),
|
||||
|
||||
/**
|
||||
* 系统消息通知
|
||||
*/
|
||||
BPM_SYSTEM_MSG("bpm_msg_node", ""),
|
||||
|
||||
/**
|
||||
* 流程抄送任务
|
||||
*/
|
||||
BPM_VIEW("bpm_cc", "/task/myHandleTaskInfo"),
|
||||
|
||||
/**
|
||||
* 节点通知
|
||||
*/
|
||||
BPM_TASK("bpm_task", "/task/myHandleTaskInfo"),
|
||||
|
||||
/**
|
||||
* 邮件消息
|
||||
*/
|
||||
EMAIL("email", "/eoa/email");
|
||||
|
||||
String busType;
|
||||
|
||||
String path;
|
||||
|
||||
Vue3MessageHrefEnum(String busType, String path) {
|
||||
this.busType = busType;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getBusType() {
|
||||
return busType;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典数据
|
||||
* @return
|
||||
*/
|
||||
public static List<DictModel> getDictList(){
|
||||
List<DictModel> list = new ArrayList<>();
|
||||
DictModel dictModel = null;
|
||||
for(Vue3MessageHrefEnum e: Vue3MessageHrefEnum.values()){
|
||||
dictModel = new DictModel();
|
||||
dictModel.setValue(e.getBusType());
|
||||
dictModel.setText(e.getPath());
|
||||
list.add(dictModel);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,7 +19,6 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
|
||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
@ -29,7 +28,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @Description: Controller基类
|
||||
@ -70,7 +68,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
||||
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
||||
//update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
||||
//update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
|
||||
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
||||
@ -110,7 +108,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
IPage<T> pageList = service.page(page, queryWrapper);
|
||||
List<T> exportList = pageList.getRecords();
|
||||
Map<String, Object> map = new HashMap<>(5);
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
//map.put("title",exportParams);
|
||||
//表格Title
|
||||
|
||||
@ -20,6 +20,14 @@ public class QueryCondition implements Serializable {
|
||||
private String dbType;
|
||||
private String rule;
|
||||
private String val;
|
||||
|
||||
public QueryCondition(String field, String type, String dbType, String rule, String val) {
|
||||
this.field = field;
|
||||
this.type = type;
|
||||
this.dbType = dbType;
|
||||
this.rule = rule;
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
|
||||
@ -19,11 +19,9 @@ import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.util.JeecgDataAutorUtils;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.system.util.SqlConcatUtil;
|
||||
import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.*;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
@ -143,7 +141,7 @@ public class QueryGenerator {
|
||||
}
|
||||
|
||||
Object value = PropertyUtils.getSimpleProperty(searchObj, name);
|
||||
column = getTableFieldName(searchObj.getClass(), name);
|
||||
column = ReflectHelper.getTableFieldName(searchObj.getClass(), name);
|
||||
if(column==null){
|
||||
//column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了
|
||||
continue;
|
||||
@ -283,15 +281,9 @@ public class QueryGenerator {
|
||||
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
|
||||
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
|
||||
if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) {
|
||||
//queryWrapper.orderByAsc(oConvertUtils.camelToUnderline(column));
|
||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
||||
String[] columnArray = columnStr.split(",");
|
||||
queryWrapper.orderByAsc(Arrays.asList(columnArray));
|
||||
queryWrapper.orderByAsc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||
} else {
|
||||
//queryWrapper.orderByDesc(oConvertUtils.camelToUnderline(column));
|
||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
||||
String[] columnArray = columnStr.split(",");
|
||||
queryWrapper.orderByDesc(Arrays.asList(columnArray));
|
||||
queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||
}
|
||||
//update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
||||
}
|
||||
@ -347,7 +339,7 @@ public class QueryGenerator {
|
||||
return;
|
||||
}
|
||||
// update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
||||
log.info("---高级查询参数-->" + filterConditions);
|
||||
log.debug("---高级查询参数-->" + filterConditions);
|
||||
|
||||
queryWrapper.and(andWrapper -> {
|
||||
for (int i = 0; i < filterConditions.size(); i++) {
|
||||
@ -641,11 +633,11 @@ public class QueryGenerator {
|
||||
* @param value 查询条件值
|
||||
*/
|
||||
public static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value) {
|
||||
if (value == null || rule == null || oConvertUtils.isEmpty(value)) {
|
||||
if (name==null || value == null || rule == null || oConvertUtils.isEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
name = oConvertUtils.camelToUnderline(name);
|
||||
log.info("---查询过滤器,Query规则---field:{}, rule:{}, value:{}",name,rule.getValue(),value);
|
||||
log.debug("---高级查询 Query规则---field:{} , rule:{} , value:{}",name,rule.getValue(),value);
|
||||
switch (rule) {
|
||||
case GT:
|
||||
queryWrapper.gt(name, value);
|
||||
@ -713,7 +705,14 @@ public class QueryGenerator {
|
||||
*/
|
||||
public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
|
||||
Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5);
|
||||
List<SysPermissionDataRuleModel> list =JeecgDataAutorUtils.loadDataSearchConditon();
|
||||
List<SysPermissionDataRuleModel> list = null;
|
||||
//update-begin-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
||||
try {
|
||||
list = JeecgDataAutorUtils.loadDataSearchConditon();
|
||||
}catch (Exception e){
|
||||
log.error("根据request对象获取权限数据失败,可能是定时任务中执行的。", e);
|
||||
}
|
||||
//update-end-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
||||
if(list != null&&list.size()>0){
|
||||
if(list.get(0)==null){
|
||||
return ruleMap;
|
||||
@ -821,223 +820,7 @@ public class QueryGenerator {
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) {
|
||||
return getSingleQueryConditionSql(field, alias, value, isString,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报表获取查询条件 支持多数据源
|
||||
* @param field
|
||||
* @param alias
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
field = alias+oConvertUtils.camelToUnderline(field);
|
||||
QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
|
||||
return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
|
||||
String res = "";
|
||||
switch (rule) {
|
||||
case GT:
|
||||
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case GE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LT:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ_WITH_ADD:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case NE:
|
||||
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case IN:
|
||||
res = field + " in "+getInConditionValue(value, isString);
|
||||
break;
|
||||
case LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
|
||||
break;
|
||||
case LEFT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
|
||||
break;
|
||||
case RIGHT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
|
||||
break;
|
||||
default:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @return
|
||||
*/
|
||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
|
||||
return getSingleSqlByRule(rule, field, value, isString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询条件的值
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
|
||||
String str = value.toString().trim();
|
||||
if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.indexOf(QUERY_COMMA_ESCAPE)>0) {
|
||||
str = str.replaceAll("\\+\\+", COMMA);
|
||||
}
|
||||
if(dataBaseType==null){
|
||||
dataBaseType = getDbType();
|
||||
}
|
||||
if(isString) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
|
||||
return " N'"+str+"' ";
|
||||
}else{
|
||||
return " '"+str+"' ";
|
||||
}
|
||||
}else {
|
||||
// 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“‘admin’”这种格式的
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return " N"+str;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getInConditionValue(Object value,boolean isString) {
|
||||
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
String[] temp = value.toString().split(",");
|
||||
if(temp.length==0){
|
||||
return "('')";
|
||||
}
|
||||
if(isString) {
|
||||
List<String> res = new ArrayList<>();
|
||||
for (String string : temp) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
res.add("N'"+string+"'");
|
||||
}else{
|
||||
res.add("'"+string+"'");
|
||||
}
|
||||
}
|
||||
return "("+String.join("," ,res)+")";
|
||||
}else {
|
||||
return "("+value.toString()+")";
|
||||
}
|
||||
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
}
|
||||
|
||||
/**
|
||||
* 先根据值判断 走左模糊还是右模糊
|
||||
* 最后如果值不带任何标识(*或者%),则再根据ruleEnum判断
|
||||
* @param value
|
||||
* @param ruleEnum
|
||||
* @return
|
||||
*/
|
||||
private static String getLikeConditionValue(Object value, QueryRuleEnum ruleEnum) {
|
||||
String str = value.toString().trim();
|
||||
if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}
|
||||
}else if(str.startsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'%"+str.substring(1)+"'";
|
||||
}else{
|
||||
return "'%"+str.substring(1)+"'";
|
||||
}
|
||||
}else if(str.endsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'"+str.substring(0,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'"+str.substring(0,str.length()-1)+"%'";
|
||||
}
|
||||
}else {
|
||||
if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return "N"+str;
|
||||
}else{
|
||||
return "N"+"'"+str+"'";
|
||||
}
|
||||
}else{
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return str;
|
||||
}else{
|
||||
return "'"+str+"'";
|
||||
}
|
||||
}
|
||||
}else {
|
||||
|
||||
//update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
||||
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
|
||||
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
|
||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
||||
return "N'%" + str + "'";
|
||||
} else {
|
||||
return "'%" + str + "'";
|
||||
}
|
||||
} else if (ruleEnum == QueryRuleEnum.RIGHT_LIKE) {
|
||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
||||
return "N'" + str + "%'";
|
||||
} else {
|
||||
return "'" + str + "%'";
|
||||
}
|
||||
} else {
|
||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
||||
return "N'%" + str + "%'";
|
||||
} else {
|
||||
return "'%" + str + "%'";
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
||||
|
||||
}
|
||||
}
|
||||
return SqlConcatUtil.getSingleQueryConditionSql(field, alias, value, isString,null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1064,7 +847,7 @@ public class QueryGenerator {
|
||||
continue;
|
||||
}
|
||||
if(ruleMap.containsKey(name)) {
|
||||
column = getTableFieldName(clazz, name);
|
||||
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||
if(column==null){
|
||||
continue;
|
||||
}
|
||||
@ -1078,7 +861,7 @@ public class QueryGenerator {
|
||||
}else {
|
||||
value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
|
||||
}
|
||||
String filedSql = getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
|
||||
String filedSql = SqlConcatUtil.getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
|
||||
sb.append(sqlAnd+filedSql);
|
||||
}
|
||||
}
|
||||
@ -1107,7 +890,7 @@ public class QueryGenerator {
|
||||
if (judgedIsUselessField(name)) {
|
||||
continue;
|
||||
}
|
||||
column = getTableFieldName(clazz, name);
|
||||
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||
if(column==null){
|
||||
continue;
|
||||
}
|
||||
@ -1126,42 +909,6 @@ public class QueryGenerator {
|
||||
return getSqlRuleValue(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置的权限 返回sql字符串 不受字段限制 配置什么就拿到什么
|
||||
* @return
|
||||
*/
|
||||
public static String getAllConfigAuth() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
//权限查询
|
||||
Map<String,SysPermissionDataRuleModel> ruleMap = getRuleMap();
|
||||
String sqlAnd = " and ";
|
||||
for (String c : ruleMap.keySet()) {
|
||||
SysPermissionDataRuleModel dataRule = ruleMap.get(c);
|
||||
String ruleValue = dataRule.getRuleValue();
|
||||
if(oConvertUtils.isEmpty(ruleValue)){
|
||||
continue;
|
||||
}
|
||||
if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
|
||||
sb.append(sqlAnd+getSqlRuleValue(ruleValue));
|
||||
}else{
|
||||
boolean isString = false;
|
||||
ruleValue = ruleValue.trim();
|
||||
if(ruleValue.startsWith("'") && ruleValue.endsWith("'")){
|
||||
isString = true;
|
||||
ruleValue = ruleValue.substring(1,ruleValue.length()-1);
|
||||
}
|
||||
QueryRuleEnum rule = QueryRuleEnum.getByValue(dataRule.getRuleConditions());
|
||||
String value = converRuleValue(ruleValue);
|
||||
String filedSql = getSingleSqlByRule(rule, c, value,isString);
|
||||
sb.append(sqlAnd+filedSql);
|
||||
}
|
||||
}
|
||||
log.info("query auth sql is = "+sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取系统数据库类型
|
||||
*/
|
||||
@ -1169,71 +916,6 @@ public class QueryGenerator {
|
||||
return CommonUtils.getDatabaseType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取class的 包括父类的
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
private static List<Field> getClassFields(Class<?> clazz) {
|
||||
List<Field> list = new ArrayList<Field>();
|
||||
Field[] fields;
|
||||
do{
|
||||
fields = clazz.getDeclaredFields();
|
||||
for(int i = 0;i<fields.length;i++){
|
||||
list.add(fields[i]);
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}while(clazz!= Object.class&&clazz!=null);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表字段名
|
||||
* @param clazz
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
private static String getTableFieldName(Class<?> clazz, String name) {
|
||||
try {
|
||||
//如果字段加注解了@TableField(exist = false),不走DB查询
|
||||
Field field = null;
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
|
||||
//如果为空,则去父类查找字段
|
||||
if (field == null) {
|
||||
List<Field> allFields = getClassFields(clazz);
|
||||
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
|
||||
if(searchFields!=null && searchFields.size()>0){
|
||||
field = searchFields.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (field != null) {
|
||||
TableField tableField = field.getAnnotation(TableField.class);
|
||||
if (tableField != null){
|
||||
if(tableField.exist() == false){
|
||||
//如果设置了TableField false 这个字段不需要处理
|
||||
return null;
|
||||
}else{
|
||||
String column = tableField.value();
|
||||
//如果设置了TableField value 这个字段是实体字段
|
||||
if(!"".equals(column)){
|
||||
return column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* mysql 模糊查询之特殊字符下划线 (_、\)
|
||||
*
|
||||
|
||||
@ -25,12 +25,19 @@ public enum QueryRuleEnum {
|
||||
IN("IN","in","包含"),
|
||||
/**查询规则 全模糊*/
|
||||
LIKE("LIKE","like","全模糊"),
|
||||
/**查询规则 不模糊包含*/
|
||||
NOT_LIKE("NOT_LIKE","not_like","不模糊包含"),
|
||||
/**查询规则 左模糊*/
|
||||
LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
|
||||
/**查询规则 右模糊*/
|
||||
RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
|
||||
/**查询规则 带加号等于*/
|
||||
EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"),
|
||||
/**查询规则 多词模糊匹配*/
|
||||
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
|
||||
/**查询规则 自定义SQL片段*/
|
||||
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
|
||||
|
||||
// ------- 当前表单设计器内专用 -------
|
||||
/** 值为空 */
|
||||
EMPTY("EMPTY","empty","值为空"),
|
||||
@ -38,15 +45,12 @@ public enum QueryRuleEnum {
|
||||
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
|
||||
/**查询规则 不包含*/
|
||||
NOT_IN("NOT_IN","not_in","不包含"),
|
||||
// ------- 当前表单设计器内专用 -------
|
||||
/**查询规则 多词模糊匹配*/
|
||||
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
|
||||
/**查询规则 自定义SQL片段*/
|
||||
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
|
||||
/**查询规则 多词匹配*/
|
||||
ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"),
|
||||
/**查询规则 范围查询*/
|
||||
RANGE("RANGE","range","范围查询");
|
||||
RANGE("RANGE","range","范围查询"),
|
||||
NOT_RANGE("NOT_RANGE","not_range","不在范围查询");
|
||||
// ------- 当前表单设计器内专用 -------
|
||||
|
||||
private String value;
|
||||
|
||||
@ -89,7 +93,7 @@ public enum QueryRuleEnum {
|
||||
return null;
|
||||
}
|
||||
for(QueryRuleEnum val :values()){
|
||||
if (val.getValue().equals(value) || val.getCondition().equals(value)){
|
||||
if (val.getValue().equals(value) || val.getCondition().equalsIgnoreCase(value)){
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
@ -34,6 +35,7 @@ import org.jeecg.common.util.oConvertUtils;
|
||||
* @Date 2018-07-12 14:23
|
||||
* @Desc JWT工具类
|
||||
**/
|
||||
@Slf4j
|
||||
public class JwtUtil {
|
||||
|
||||
/**Token有效期为7天(Token在reids中缓存时间为两倍)*/
|
||||
@ -163,15 +165,24 @@ public class JwtUtil {
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public static String getUserSystemData(String key,SysUserCacheInfo user) {
|
||||
public static String getUserSystemData(String key, SysUserCacheInfo user) {
|
||||
//1.优先获取 SysUserCacheInfo
|
||||
if(user==null) {
|
||||
user = JeecgDataAutorUtils.loadUserInfo();
|
||||
try {
|
||||
user = JeecgDataAutorUtils.loadUserInfo();
|
||||
} catch (Exception e) {
|
||||
log.warn("获取用户信息异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
//2.通过shiro获取登录用户信息
|
||||
LoginUser sysUser = null;
|
||||
try {
|
||||
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
} catch (Exception e) {
|
||||
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
//#{sys_user_code}%
|
||||
|
||||
// 获取登录用户信息
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
String moshi = "";
|
||||
String wellNumber = WELL_NUMBER;
|
||||
if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
|
||||
@ -184,6 +195,24 @@ public class JwtUtil {
|
||||
} else {
|
||||
key = key;
|
||||
}
|
||||
//替换为当前系统时间(年月日)
|
||||
if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
||||
returnValue = DateUtils.formatDate();
|
||||
}
|
||||
//替换为当前系统时间(年月日时分秒)
|
||||
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
|
||||
returnValue = DateUtils.now();
|
||||
}
|
||||
//流程状态默认值(默认未发起)
|
||||
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
|
||||
returnValue = "1";
|
||||
}
|
||||
|
||||
//后台任务获取用户信息异常,导致程序中断
|
||||
if(sysUser==null && user==null){
|
||||
return null;
|
||||
}
|
||||
|
||||
//替换为系统登录用户帐号
|
||||
if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
|
||||
if(user==null) {
|
||||
@ -222,21 +251,13 @@ public class JwtUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
//替换为当前系统时间(年月日)
|
||||
else if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
||||
returnValue = DateUtils.formatDate();
|
||||
}
|
||||
//替换为当前系统时间(年月日时分秒)
|
||||
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
|
||||
returnValue = DateUtils.now();
|
||||
}
|
||||
//流程状态默认值(默认未发起)
|
||||
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
|
||||
returnValue = "1";
|
||||
}
|
||||
//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
|
||||
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
|
||||
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
|
||||
try {
|
||||
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
|
||||
} catch (Exception e) {
|
||||
log.warn("获取系统租户异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
|
||||
if(returnValue!=null){returnValue = returnValue + moshi;}
|
||||
|
||||
@ -0,0 +1,243 @@
|
||||
package org.jeecg.common.system.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.query.QueryRuleEnum;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: 查询过滤器,SQL拼接写法拆成独立工具类
|
||||
* @author:qinfeng
|
||||
* @date 20230904
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlConcatUtil {
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
|
||||
return getSingleSqlByRule(rule, field, value, isString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报表获取查询条件 支持多数据源
|
||||
* @param field
|
||||
* @param alias
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
field = alias+oConvertUtils.camelToUnderline(field);
|
||||
QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
|
||||
return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
|
||||
String res = "";
|
||||
switch (rule) {
|
||||
case GT:
|
||||
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case GE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LT:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ_WITH_ADD:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case NE:
|
||||
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case IN:
|
||||
res = field + " in "+getInConditionValue(value, isString);
|
||||
break;
|
||||
case LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
|
||||
break;
|
||||
case LEFT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
|
||||
break;
|
||||
case RIGHT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
|
||||
break;
|
||||
default:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询条件的值
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
|
||||
String str = value.toString().trim();
|
||||
if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.indexOf(QueryGenerator.QUERY_COMMA_ESCAPE)>0) {
|
||||
str = str.replaceAll("\\+\\+", SymbolConstant.COMMA);
|
||||
}
|
||||
if(dataBaseType==null){
|
||||
dataBaseType = getDbType();
|
||||
}
|
||||
if(isString) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
|
||||
return " N'"+str+"' ";
|
||||
}else{
|
||||
return " '"+str+"' ";
|
||||
}
|
||||
}else {
|
||||
// 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“‘admin’”这种格式的
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return " N"+str;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getInConditionValue(Object value,boolean isString) {
|
||||
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
String[] temp = value.toString().split(",");
|
||||
if(temp.length==0){
|
||||
return "('')";
|
||||
}
|
||||
if(isString) {
|
||||
List<String> res = new ArrayList<>();
|
||||
for (String string : temp) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
res.add("N'"+string+"'");
|
||||
}else{
|
||||
res.add("'"+string+"'");
|
||||
}
|
||||
}
|
||||
return "("+String.join("," ,res)+")";
|
||||
}else {
|
||||
return "("+value.toString()+")";
|
||||
}
|
||||
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
}
|
||||
|
||||
/**
|
||||
* 先根据值判断 走左模糊还是右模糊
|
||||
* 最后如果值不带任何标识(*或者%),则再根据ruleEnum判断
|
||||
* @param value
|
||||
* @param ruleEnum
|
||||
* @return
|
||||
*/
|
||||
private static String getLikeConditionValue(Object value, QueryRuleEnum ruleEnum) {
|
||||
String str = value.toString().trim();
|
||||
if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}
|
||||
}else if(str.startsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'%"+str.substring(1)+"'";
|
||||
}else{
|
||||
return "'%"+str.substring(1)+"'";
|
||||
}
|
||||
}else if(str.endsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'"+str.substring(0,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'"+str.substring(0,str.length()-1)+"%'";
|
||||
}
|
||||
}else {
|
||||
if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return "N"+str;
|
||||
}else{
|
||||
return "N"+"'"+str+"'";
|
||||
}
|
||||
}else{
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return str;
|
||||
}else{
|
||||
return "'"+str+"'";
|
||||
}
|
||||
}
|
||||
}else {
|
||||
|
||||
//update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
||||
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
|
||||
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
|
||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
||||
return "N'%" + str + "'";
|
||||
} else {
|
||||
return "'%" + str + "'";
|
||||
}
|
||||
} else if (ruleEnum == QueryRuleEnum.RIGHT_LIKE) {
|
||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
||||
return "N'" + str + "%'";
|
||||
} else {
|
||||
return "'" + str + "%'";
|
||||
}
|
||||
} else {
|
||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
||||
return "N'%" + str + "%'";
|
||||
} else {
|
||||
return "'%" + str + "%'";
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统数据库类型
|
||||
*/
|
||||
private static String getDbType() {
|
||||
return CommonUtils.getDatabaseType();
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,6 +2,7 @@ package org.jeecg.common.system.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import lombok.Data;
|
||||
@ -26,7 +27,13 @@ public class DictModel implements Serializable{
|
||||
this.value = value;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
public DictModel(String value, String text, String color) {
|
||||
this.value = value;
|
||||
this.text = text;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典value
|
||||
*/
|
||||
@ -35,6 +42,10 @@ public class DictModel implements Serializable{
|
||||
* 字典文本
|
||||
*/
|
||||
private String text;
|
||||
/**
|
||||
* 字典颜色
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 特殊用途: JgEditableTable
|
||||
@ -50,4 +61,11 @@ public class DictModel implements Serializable{
|
||||
return this.text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用于表单设计器 关联记录表数据存储
|
||||
* QQYUN-5595【表单设计器】他表字段 导入没有翻译
|
||||
*/
|
||||
private JSONObject jsonObject;
|
||||
|
||||
}
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
package org.jeecg.common.system.vo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -51,6 +49,7 @@ public class LoginUser {
|
||||
/**
|
||||
* 当前登录部门code
|
||||
*/
|
||||
@SensitiveField
|
||||
private String orgCode;
|
||||
/**
|
||||
* 头像
|
||||
@ -61,7 +60,6 @@ public class LoginUser {
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
@SensitiveField
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date birthday;
|
||||
@ -107,6 +105,7 @@ public class LoginUser {
|
||||
/**
|
||||
* 管理部门ids
|
||||
*/
|
||||
@SensitiveField
|
||||
private String departIds;
|
||||
|
||||
/**
|
||||
@ -122,6 +121,7 @@ public class LoginUser {
|
||||
private String telephone;
|
||||
|
||||
/** 多租户ids临时用,不持久化数据库(数据库字段不存在) */
|
||||
@SensitiveField
|
||||
private String relTenantIds;
|
||||
|
||||
/**设备id uniapp推送用*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -11,7 +11,8 @@ import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
import org.jeecg.common.constant.ServiceNameConstants;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.oss.OssBootUtil;
|
||||
import org.jeecgframework.poi.util.PoiPublicUtil;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
@ -27,8 +28,7 @@ import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -138,6 +138,7 @@ public class CommonUtils {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new JeecgBootException(e.getMessage());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
@ -147,10 +148,10 @@ public class CommonUtils {
|
||||
* @param bizPath 自定义路径
|
||||
* @return
|
||||
*/
|
||||
public static String uploadLocal(MultipartFile mf, String bizPath, String uploadpath){
|
||||
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
|
||||
try {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(mf);
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
String fileName = null;
|
||||
File file = new File(uploadpath + File.separator + bizPath + File.separator );
|
||||
@ -274,7 +275,7 @@ public class CommonUtils {
|
||||
if(db==null){
|
||||
return null;
|
||||
}
|
||||
DriverManagerDataSource ds = new DriverManagerDataSource();
|
||||
DriverManagerDataSource ds = new DriverManagerDataSource ();
|
||||
ds.setDriverClassName(db.getDriverClassName());
|
||||
ds.setUrl(db.getUrl());
|
||||
ds.setUsername(db.getUsername());
|
||||
@ -414,6 +415,10 @@ public class CommonUtils {
|
||||
* @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])) {
|
||||
@ -430,6 +435,10 @@ public class CommonUtils {
|
||||
* @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();
|
||||
@ -437,4 +446,25 @@ public class CommonUtils {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,5 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import org.jeecg.config.StaticConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
@ -12,6 +8,10 @@ import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.aliyuncs.profile.IClientProfile;
|
||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||
import org.jeecg.config.StaticConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Created on 17/6/7.
|
||||
@ -55,7 +55,7 @@ public class DySmsHelper {
|
||||
}
|
||||
|
||||
|
||||
public static boolean sendSms(String phone,JSONObject templateParamJson,DySmsEnum dySmsEnum) throws ClientException {
|
||||
public static boolean sendSms(String phone, JSONObject templateParamJson, DySmsEnum dySmsEnum) throws ClientException {
|
||||
//可自助调整超时时间
|
||||
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
|
||||
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
|
||||
|
||||
@ -4,7 +4,7 @@ import io.minio.*;
|
||||
import io.minio.http.Method;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@ -60,7 +60,7 @@ public class MinioUtil {
|
||||
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(file);
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String newBucket = bucketName;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -7,6 +8,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author 张代浩
|
||||
@ -252,4 +254,86 @@ public class ReflectHelper {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的字段是不是类中的属性
|
||||
* @param field 字段名
|
||||
* @param clazz 类对象
|
||||
* @return
|
||||
*/
|
||||
public static boolean isClassField(String field, Class clazz){
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for(int i=0;i<fields.length;i++){
|
||||
String fieldName = fields[i].getName();
|
||||
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
|
||||
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取class的 包括父类的
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
public static List<Field> getClassFields(Class<?> clazz) {
|
||||
List<Field> list = new ArrayList<Field>();
|
||||
Field[] fields;
|
||||
do{
|
||||
fields = clazz.getDeclaredFields();
|
||||
for(int i = 0;i<fields.length;i++){
|
||||
list.add(fields[i]);
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}while(clazz!= Object.class&&clazz!=null);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表字段名
|
||||
* @param clazz
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public static String getTableFieldName(Class<?> clazz, String name) {
|
||||
try {
|
||||
//如果字段加注解了@TableField(exist = false),不走DB查询
|
||||
Field field = null;
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
|
||||
//如果为空,则去父类查找字段
|
||||
if (field == null) {
|
||||
List<Field> allFields = getClassFields(clazz);
|
||||
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
|
||||
if(searchFields!=null && searchFields.size()>0){
|
||||
field = searchFields.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (field != null) {
|
||||
TableField tableField = field.getAnnotation(TableField.class);
|
||||
if (tableField != null){
|
||||
if(tableField.exist() == false){
|
||||
//如果设置了TableField false 这个字段不需要处理
|
||||
return null;
|
||||
}else{
|
||||
String column = tableField.value();
|
||||
//如果设置了TableField value 这个字段是实体字段
|
||||
if(!"".equals(column)){
|
||||
return column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +1,12 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Set;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -18,50 +16,243 @@ import java.util.regex.Pattern;
|
||||
* @author zhoujf
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlInjectionUtil {
|
||||
public class SqlInjectionUtil {
|
||||
/**
|
||||
* sign 用于表字典加签的盐值【SQL漏洞】
|
||||
* (上线修改值 20200501,同步修改前端的盐值)
|
||||
* 默认—sql注入关键词
|
||||
*/
|
||||
private final static String TABLE_DICT_SIGN_SALT = "20200501";
|
||||
private final static String XSS_STR = "and |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
|
||||
|
||||
private final static String XSS_STR = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
|
||||
/**
|
||||
* 正则 user() 匹配更严谨
|
||||
* online报表专用—sql注入关键词
|
||||
*/
|
||||
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)";
|
||||
/**正则 show tables*/
|
||||
private final static String SHOW_TABLES = "show\\s+tables";
|
||||
|
||||
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 |--";
|
||||
/**
|
||||
* sleep函数
|
||||
* 字典专用—sql注入关键词
|
||||
*/
|
||||
private final static Pattern FUN_SLEEP = Pattern.compile("sleep\\(.*\\)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
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 |;|+|--";
|
||||
/**
|
||||
* sql注入风险的 正则关键字
|
||||
*
|
||||
* 函数匹配,需要用正则模式
|
||||
*/
|
||||
private final static String[] XSS_REGULAR_STR_ARRAY = new String[]{
|
||||
"chr\\s*\\(",
|
||||
"mid\\s*\\(",
|
||||
" char\\s*\\(",
|
||||
"sleep\\s*\\(",
|
||||
"user\\s*\\(",
|
||||
"show\\s+tables",
|
||||
"user[\\s]*\\([\\s]*\\)",
|
||||
"show\\s+databases",
|
||||
"sleep\\(\\d*\\)",
|
||||
"sleep\\(.*\\)",
|
||||
};
|
||||
/**
|
||||
* sql注释的正则
|
||||
*/
|
||||
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
||||
/**
|
||||
* sql注入提示语
|
||||
*/
|
||||
private final static String SQL_INJECTION_KEYWORD_TIP = "请注意,存在SQL注入关键词---> {}";
|
||||
private final static String SQL_INJECTION_TIP = "请注意,值可能存在SQL注入风险!--->";
|
||||
private final static String SQL_INJECTION_TIP_VARIABLE = "请注意,值可能存在SQL注入风险!---> {}";
|
||||
|
||||
|
||||
/**
|
||||
* 针对表字典进行额外的sign签名校验(增加安全机制)
|
||||
* @param dictCode:
|
||||
* @param sign:
|
||||
* @param request:
|
||||
* @Return: void
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
* @param values
|
||||
*/
|
||||
private static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
|
||||
//表字典SQL注入漏洞,签名校验
|
||||
String accessToken = request.getHeader("X-Access-Token");
|
||||
String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
|
||||
String javaSign = SecureUtil.md5(signStr);
|
||||
if (!javaSign.equals(sign)) {
|
||||
log.error("表字典,SQL注入漏洞签名校验失败 :" + sign + "!=" + javaSign+ ",dictCode=" + dictCode);
|
||||
throw new JeecgBootException("无权限访问!");
|
||||
}
|
||||
log.info(" 表字典,SQL注入漏洞签名校验成功!sign=" + sign + ",dictCode=" + dictCode);
|
||||
public static void filterContent(String... values) {
|
||||
filterContent(values, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验比较严格
|
||||
*
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static void filterContent(String value, String customXssString) {
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 转为小写进行后续比较
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
//https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
String[] xssArr = XSS_STR.split("\\|");
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
// 三、SQL注入检测存在绕过风险 (自定义传入普通文本校验)
|
||||
if (customXssString != null) {
|
||||
String[] xssArr2 = customXssString.split("\\|");
|
||||
for (int i = 0; i < xssArr2.length; i++) {
|
||||
if (value.indexOf(xssArr2[i]) > -1) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr2[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 四、SQL注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
String regular = ".*" + regularOriginal + ".*";
|
||||
if (Pattern.matches(regular, value)) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在SQL注入关键词字符串
|
||||
*
|
||||
* @param keyword
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("AlibabaUndefineMagicConstant")
|
||||
private static boolean isExistSqlInjectKeyword(String sql, String keyword) {
|
||||
if (sql.startsWith(keyword.trim())) {
|
||||
return true;
|
||||
} else if (sql.contains(keyword)) {
|
||||
if (sql.contains(" " + keyword)) {
|
||||
return true;
|
||||
} else {
|
||||
String regularStr = "\\s+\\S+" + keyword;
|
||||
List<String> resultFindAll = ReUtil.findAll(regularStr, sql, 0, new ArrayList<String>());
|
||||
for (String res : resultFindAll) {
|
||||
log.info("isExistSqlInjectKeyword —- 匹配到的SQL注入关键词:{}", res);
|
||||
/**
|
||||
* SQL注入中可以替换空格的字符(%09 %0A %0D +都可以替代空格)
|
||||
* http://blog.chinaunix.net/uid-12501104-id-2932639.html
|
||||
* https://www.cnblogs.com/Vinson404/p/7253255.html
|
||||
* */
|
||||
if (res.contains("%") || res.contains("+") || res.contains("#") || res.contains("/") || res.contains(")")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static void filterContent(String[] values, String customXssString) {
|
||||
for (String val : values) {
|
||||
if (oConvertUtils.isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
filterContent(val, customXssString);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【提醒:不通用】
|
||||
* 仅用于字典条件SQL参数,注入过滤
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static void specialFilterContentForDictSql(String value) {
|
||||
String[] xssArr = specialDictSqlXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (isExistSqlInjectKeyword(value, xssArr[i])) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
|
||||
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
String regular = ".*" + regularOriginal + ".*";
|
||||
if (Pattern.matches(regular, value)) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【提醒:不通用】
|
||||
* 仅用于Online报表SQL解析,注入过滤
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static void specialFilterContentForOnlineReport(String value) {
|
||||
String[] xssArr = specialReportXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (isExistSqlInjectKeyword(value, xssArr[i])) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
|
||||
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
String regular = ".*" + regularOriginal + ".*";
|
||||
if (Pattern.matches(regular, value)) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验是否有sql注释
|
||||
* @return
|
||||
*/
|
||||
public static void checkSqlAnnotation(String str){
|
||||
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
||||
if(matcher.find()){
|
||||
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
||||
log.error(error);
|
||||
throw new JeecgSqlInjectionException(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回查询表名
|
||||
* <p>
|
||||
@ -71,6 +262,10 @@ public class SqlInjectionUtil {
|
||||
*/
|
||||
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();
|
||||
/**
|
||||
* 检验表名是否合法
|
||||
@ -102,7 +297,7 @@ public class SqlInjectionUtil {
|
||||
static final Pattern fieldPattern = Pattern.compile("^[a-zA-Z0-9_]+$");
|
||||
public static String getSqlInjectField(String field) {
|
||||
if(oConvertUtils.isEmpty(field)){
|
||||
return null;
|
||||
return field;
|
||||
}
|
||||
|
||||
field = field.trim();
|
||||
@ -110,7 +305,7 @@ public class SqlInjectionUtil {
|
||||
if (field.contains(SymbolConstant.COMMA)) {
|
||||
return getSqlInjectField(field.split(SymbolConstant.COMMA));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验表字段是否有效
|
||||
*
|
||||
@ -128,6 +323,13 @@ public class SqlInjectionUtil {
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多个字段
|
||||
* 返回: 逗号拼接
|
||||
*
|
||||
* @param fields
|
||||
* @return
|
||||
*/
|
||||
public static String getSqlInjectField(String... fields) {
|
||||
for (String s : fields) {
|
||||
getSqlInjectField(s);
|
||||
@ -135,233 +337,58 @@ public class SqlInjectionUtil {
|
||||
return String.join(SymbolConstant.COMMA, fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static void filterContent(String value, String customXssString) {
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
//value = value.replaceAll("/\\*.*\\*/","");
|
||||
|
||||
String[] xssArr = XSS_STR.split("\\|");
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new JeecgSqlInjectionException("请注意,值可能存在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 JeecgSqlInjectionException("请注意,值可能存在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 JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
* @param values
|
||||
*/
|
||||
public static void filterContent(String... values) {
|
||||
filterContent(values, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static void filterContent(String[] values, String customXssString) {
|
||||
String[] xssArr = XSS_STR.split("\\|");
|
||||
for (String value : values) {
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
//value = value.replaceAll("/\\*.*\\*/","");
|
||||
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new JeecgSqlInjectionException("请注意,值可能存在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 JeecgSqlInjectionException("请注意,值可能存在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 JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【提醒:不通用】
|
||||
* 仅用于字典条件SQL参数,注入过滤
|
||||
* 获取排序字段
|
||||
* 返回:字符串
|
||||
*
|
||||
* @param value
|
||||
* 1.将驼峰命名转化成下划线
|
||||
* 2.限制sql注入
|
||||
* @param sortField 排序字段
|
||||
* @return
|
||||
*/
|
||||
//@Deprecated
|
||||
public static void specialFilterContentForDictSql(String value) {
|
||||
String specialXssStr = " exec |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract| insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
|
||||
String[] xssArr = specialXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
//value = value.replaceAll("/\\*.*\\*/","");
|
||||
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
}
|
||||
}
|
||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
||||
throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 【提醒:不通用】
|
||||
* 仅用于Online报表SQL解析,注入过滤
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
//@Deprecated
|
||||
public static void specialFilterContentForOnlineReport(String value) {
|
||||
String specialXssStr = " exec |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract| insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()";
|
||||
String[] xssArr = specialXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
//value = value.replaceAll("/\\*.*\\*/"," ");
|
||||
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
}
|
||||
}
|
||||
|
||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
||||
throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断给定的字段是不是类中的属性
|
||||
* @param field 字段名
|
||||
* @param clazz 类对象
|
||||
* @return
|
||||
*/
|
||||
public static boolean isClassField(String field, Class clazz){
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for(int i=0;i<fields.length;i++){
|
||||
String fieldName = fields[i].getName();
|
||||
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
|
||||
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public static String getSqlInjectSortField(String sortField) {
|
||||
String field = SqlInjectionUtil.getSqlInjectField(oConvertUtils.camelToUnderline(sortField));
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的多个字段是不是类中的属性
|
||||
* @param fieldSet 字段名set
|
||||
* @param clazz 类对象
|
||||
* 获取多个排序字段
|
||||
* 返回:数组
|
||||
*
|
||||
* 1.将驼峰命名转化成下划线
|
||||
* 2.限制sql注入
|
||||
* @param sortFields 多个排序字段
|
||||
* @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;
|
||||
}
|
||||
public static List getSqlInjectSortFields(String... sortFields) {
|
||||
List list = new ArrayList<String>();
|
||||
for (String sortField : sortFields) {
|
||||
list.add(getSqlInjectSortField(sortField));
|
||||
}
|
||||
return true;
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验是否有sql注释
|
||||
* 获取 orderBy type
|
||||
* 返回:字符串
|
||||
* <p>
|
||||
* 1.检测是否为 asc 或 desc 其中的一个
|
||||
* 2.限制sql注入
|
||||
*
|
||||
* @param orderType
|
||||
* @return
|
||||
*/
|
||||
public static void checkSqlAnnotation(String str){
|
||||
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
||||
if(matcher.find()){
|
||||
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
||||
log.error(error);
|
||||
throw new JeecgSqlInjectionException(error);
|
||||
public static String getSqlInjectOrderType(String orderType) {
|
||||
if (orderType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// issues/4737 sys/duplicate/check SQL注入 #4737
|
||||
Matcher sleepMatcher = FUN_SLEEP.matcher(str);
|
||||
if(sleepMatcher.find()){
|
||||
String error = "请注意,值可能存在SQL注入风险---> sleep";
|
||||
log.error(error);
|
||||
throw new JeecgSqlInjectionException(error);
|
||||
orderType = orderType.trim();
|
||||
if (CommonConstant.ORDER_TYPE_ASC.equalsIgnoreCase(orderType)) {
|
||||
return CommonConstant.ORDER_TYPE_ASC;
|
||||
} else {
|
||||
return CommonConstant.ORDER_TYPE_DESC;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -34,6 +34,21 @@ public class TokenUtils {
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 里传递的 token
|
||||
* @return
|
||||
*/
|
||||
public static String getTokenByRequest() {
|
||||
String token = null;
|
||||
try {
|
||||
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
|
||||
token = TokenUtils.getTokenByRequest(request);
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 里传递的 tenantId (租户ID)
|
||||
|
||||
@ -169,7 +169,7 @@ public class FreemarkerParseFactory {
|
||||
//"where and"
|
||||
String whereAnd = DataBaseConstant.SQL_WHERE+" and";
|
||||
//", where"
|
||||
String commaWhere = SymbolConstant.COMMA+" "+ DataBaseConstant.SQL_WHERE;
|
||||
String commaWhere = SymbolConstant.COMMA+" "+DataBaseConstant.SQL_WHERE;
|
||||
//", "
|
||||
String commaSpace = SymbolConstant.COMMA + " ";
|
||||
if (sql.endsWith(DataBaseConstant.SQL_WHERE) || sql.endsWith(whereSpace)) {
|
||||
|
||||
@ -4,27 +4,68 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: 校验上传文件敏感后缀
|
||||
* @Description: 校验文件敏感后缀
|
||||
* @author: lsq
|
||||
* @date: 2021年08月09日 15:29
|
||||
* @date: 2023年09月12日 15:29
|
||||
*/
|
||||
@Slf4j
|
||||
public class FileTypeFilter {
|
||||
|
||||
/**文件后缀*/
|
||||
private static String[] forbidType = {"jsp","php"};
|
||||
public class SsrfFileTypeFilter {
|
||||
|
||||
/**
|
||||
* 允许操作文件类型白名单
|
||||
*/
|
||||
private final static List<String> FILE_TYPE_WHITE_LIST = new ArrayList<>();
|
||||
/**初始化文件头类型,不够的自行补充*/
|
||||
final static HashMap<String, String> FILE_TYPE_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
//图片文件
|
||||
FILE_TYPE_WHITE_LIST.add("jpg");
|
||||
FILE_TYPE_WHITE_LIST.add("jpeg");
|
||||
FILE_TYPE_WHITE_LIST.add("png");
|
||||
FILE_TYPE_WHITE_LIST.add("gif");
|
||||
FILE_TYPE_WHITE_LIST.add("bmp");
|
||||
FILE_TYPE_WHITE_LIST.add("svg");
|
||||
FILE_TYPE_WHITE_LIST.add("ico");
|
||||
|
||||
//文本文件
|
||||
FILE_TYPE_WHITE_LIST.add("txt");
|
||||
FILE_TYPE_WHITE_LIST.add("doc");
|
||||
FILE_TYPE_WHITE_LIST.add("docx");
|
||||
FILE_TYPE_WHITE_LIST.add("pdf");
|
||||
FILE_TYPE_WHITE_LIST.add("csv");
|
||||
// FILE_TYPE_WHITE_LIST.add("xml");
|
||||
|
||||
//音视频文件
|
||||
FILE_TYPE_WHITE_LIST.add("mp4");
|
||||
FILE_TYPE_WHITE_LIST.add("avi");
|
||||
FILE_TYPE_WHITE_LIST.add("mov");
|
||||
FILE_TYPE_WHITE_LIST.add("wmv");
|
||||
FILE_TYPE_WHITE_LIST.add("mp3");
|
||||
FILE_TYPE_WHITE_LIST.add("wav");
|
||||
|
||||
//表格文件
|
||||
FILE_TYPE_WHITE_LIST.add("xls");
|
||||
FILE_TYPE_WHITE_LIST.add("xlsx");
|
||||
|
||||
//压缩文件
|
||||
FILE_TYPE_WHITE_LIST.add("zip");
|
||||
FILE_TYPE_WHITE_LIST.add("rar");
|
||||
FILE_TYPE_WHITE_LIST.add("7z");
|
||||
FILE_TYPE_WHITE_LIST.add("tar");
|
||||
|
||||
//设置禁止文件的头部标记
|
||||
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");
|
||||
FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php");
|
||||
FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");
|
||||
FILE_TYPE_MAP.put("494e5345525420494e54", "sql");
|
||||
/* fileTypeMap.put("ffd8ffe000104a464946", "jpg");
|
||||
fileTypeMap.put("89504e470d0a1a0a0000", "png");
|
||||
fileTypeMap.put("47494638396126026f01", "gif");
|
||||
@ -89,17 +130,38 @@ public class FileTypeFilter {
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 文件类型过滤
|
||||
* 下载文件类型过滤
|
||||
*
|
||||
* @param filePath
|
||||
*/
|
||||
public static void checkDownloadFileType(String filePath) throws IOException {
|
||||
//文件后缀
|
||||
String suffix = getFileTypeBySuffix(filePath);
|
||||
log.info("suffix:{}", suffix);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new IOException("下载失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件类型过滤
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public static void fileTypeFilter(MultipartFile file) throws Exception {
|
||||
public static void checkUploadFileType(MultipartFile file) throws Exception {
|
||||
//获取文件真是后缀
|
||||
String suffix = getFileType(file);
|
||||
for (String type : forbidType) {
|
||||
if (type.contains(suffix)) {
|
||||
throw new Exception("上传失败,非法文件类型:" + suffix);
|
||||
}
|
||||
|
||||
log.info("suffix:{}", suffix);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new Exception("上传失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
if (isEmpty(object)) {
|
||||
return null;
|
||||
@ -702,9 +713,20 @@ public class oConvertUtils {
|
||||
if (isArray(oldVal)) {
|
||||
return equalityOfArrays((Object[]) oldVal, (Object[]) newVal);
|
||||
}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 {
|
||||
if (oldVal == null && newVal == null) {
|
||||
return true;
|
||||
@ -742,7 +764,7 @@ public class oConvertUtils {
|
||||
Object[] newValArray = newVal.toArray();
|
||||
return equalityOfArrays(oldValArray,newValArray);
|
||||
} else {
|
||||
if (oldVal == null && newVal == null) {
|
||||
if ((oldVal == null || oldVal.size() == 0) && (newVal == null || newVal.size() == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
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);
|
||||
return Arrays.equals(oldVal, newVal);
|
||||
} else {
|
||||
if (oldVal == null && newVal == null) {
|
||||
if ((oldVal == null || oldVal.length == 0) && (newVal == null || newVal.length == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@ -807,4 +861,85 @@ public class oConvertUtils {
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将List 转成 JSONArray
|
||||
* @return
|
||||
*/
|
||||
public static JSONArray list2JSONArray(List<String> list){
|
||||
if(list==null || list.size()==0){
|
||||
return null;
|
||||
}
|
||||
JSONArray array = new JSONArray();
|
||||
for(String str: list){
|
||||
array.add(str);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个list中的元素是否完全一致
|
||||
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
||||
* @return
|
||||
*/
|
||||
public static boolean isEqList(List<String> list1, List<String> list2){
|
||||
if(list1.size() != list2.size()){
|
||||
return false;
|
||||
}
|
||||
for(String str1: list1){
|
||||
boolean flag = false;
|
||||
for(String str2: list2){
|
||||
if(str1.equals(str2)){
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!flag){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断 list1中的元素是否在list2中出现
|
||||
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
||||
* @param list1
|
||||
* @param list2
|
||||
* @return
|
||||
*/
|
||||
public static boolean isInList(List<String> list1, List<String> list2){
|
||||
for(String str1: list1){
|
||||
boolean flag = false;
|
||||
for(String str2: list2){
|
||||
if(str1.equals(str2)){
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(flag){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件大小转成MB
|
||||
* @param uploadCount
|
||||
* @return
|
||||
*/
|
||||
public static Double calculateFileSizeToMb(Long uploadCount){
|
||||
double count = 0.0;
|
||||
if(uploadCount>0) {
|
||||
BigDecimal bigDecimal = new BigDecimal(uploadCount);
|
||||
//换算成MB
|
||||
BigDecimal divide = bigDecimal.divide(new BigDecimal(1048576));
|
||||
count = divide.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
|
||||
return count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import org.apache.commons.fileupload.FileItemStream;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@ -98,7 +98,7 @@ public class OssBootUtil {
|
||||
*/
|
||||
public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(file);
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String filePath = null;
|
||||
|
||||
@ -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) {
|
||||
if(t!=null){
|
||||
//update-begin---author:liusq Date:20230517 for:[issues/4917]excel 导出异常---
|
||||
if(t!=null && t.getText()!=null && t.getValue()!=null){
|
||||
//update-end---author:liusq Date:20230517 for:[issues/4917]excel 导出异常---
|
||||
//update-begin---author:scott Date:20211220 for:[issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析---
|
||||
if(t.getValue().contains(EXCEL_SPLIT_TAG)){
|
||||
String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG);
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
package org.jeecg.config;
|
||||
|
||||
import org.jeecg.config.vo.DomainUrl;
|
||||
import org.jeecg.config.vo.Elasticsearch;
|
||||
import org.jeecg.config.vo.Path;
|
||||
import org.jeecg.config.vo.Shiro;
|
||||
import org.jeecg.config.vo.*;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -29,10 +26,12 @@ public class JeecgBaseConfig {
|
||||
* 本地:local\Minio:minio\阿里云:alioss
|
||||
*/
|
||||
private String uploadType;
|
||||
|
||||
/**
|
||||
* 是否启用安全模式
|
||||
* 平台安全模式配置
|
||||
*/
|
||||
private Boolean safeMode = false;
|
||||
private Firewall firewall;
|
||||
|
||||
/**
|
||||
* shiro拦截排除
|
||||
*/
|
||||
@ -58,6 +57,13 @@ public class JeecgBaseConfig {
|
||||
*/
|
||||
private Elasticsearch elasticsearch;
|
||||
|
||||
/**
|
||||
* 微信支付
|
||||
* @return
|
||||
*/
|
||||
private WeiXinPay weiXinPay;
|
||||
|
||||
|
||||
public Elasticsearch getElasticsearch() {
|
||||
return elasticsearch;
|
||||
}
|
||||
@ -66,12 +72,12 @@ public class JeecgBaseConfig {
|
||||
this.elasticsearch = elasticsearch;
|
||||
}
|
||||
|
||||
public Boolean getSafeMode() {
|
||||
return safeMode;
|
||||
public Firewall getFirewall() {
|
||||
return firewall;
|
||||
}
|
||||
|
||||
public void setSafeMode(Boolean safeMode) {
|
||||
this.safeMode = safeMode;
|
||||
public void setFirewall(Firewall firewall) {
|
||||
this.firewall = firewall;
|
||||
}
|
||||
|
||||
public String getSignatureSecret() {
|
||||
@ -129,4 +135,13 @@ public class JeecgBaseConfig {
|
||||
public void setUploadType(String uploadType) {
|
||||
this.uploadType = uploadType;
|
||||
}
|
||||
|
||||
public WeiXinPay getWeiXinPay() {
|
||||
return weiXinPay;
|
||||
}
|
||||
|
||||
public void setWeiXinPay(WeiXinPay weiXinPay) {
|
||||
this.weiXinPay = weiXinPay;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -127,6 +127,12 @@ public class ShiroConfig {
|
||||
filterChainDefinitionMap.put("/**/*.js.map", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.css.map", "anon");
|
||||
|
||||
//拖拽仪表盘设计器排除
|
||||
filterChainDefinitionMap.put("/drag/view", "anon");
|
||||
filterChainDefinitionMap.put("/drag/page/queryById", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getAllChartData", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalData", "anon");
|
||||
filterChainDefinitionMap.put("/drag/mock/json/**", "anon");
|
||||
//大屏模板例子
|
||||
filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
|
||||
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
|
||||
@ -246,8 +252,8 @@ public class ShiroConfig {
|
||||
// redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com
|
||||
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
|
||||
RedisManager redisManager = new RedisManager();
|
||||
redisManager.setHost(lettuceConnectionFactory.getHostName());
|
||||
redisManager.setPort(lettuceConnectionFactory.getPort());
|
||||
redisManager.setHost(lettuceConnectionFactory.getHostName() + ":" + lettuceConnectionFactory.getPort());
|
||||
//(lettuceConnectionFactory.getPort());
|
||||
redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
|
||||
redisManager.setTimeout(0);
|
||||
if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
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.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
@ -17,6 +10,15 @@ import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
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 工具类 获取请求中的参数
|
||||
*
|
||||
@ -46,10 +48,10 @@ public class HttpUtils {
|
||||
if(deString.contains("%")){
|
||||
try {
|
||||
deString = URLDecoder.decode(deString, "UTF-8");
|
||||
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
||||
}
|
||||
log.info(" pathVariable decode: {}",deString);
|
||||
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
||||
|
||||
@ -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,38 @@
|
||||
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,73 @@
|
||||
<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;">
|
||||
尊敬的 ${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{
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user