diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java index 5e68fd7..777c2c4 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java @@ -4,6 +4,7 @@ import lombok.Data; import org.jeecg.common.constant.CommonConstant; import java.io.Serializable; +import java.util.Map; /** * 普通消息 @@ -72,4 +73,18 @@ public class MessageDTO implements Serializable { this.category = category; } + /** + * 模板消息对应的模板编码 + */ + protected String templateCode; + /** + * 消息类型:org.jeecg.common.constant.enums.MessageTypeEnum + */ + protected String type; + + /** + * 解析模板内容 对应的数据 + */ + protected Map<String, Object> data; + } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java index 292e209..ab2c940 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java @@ -40,6 +40,8 @@ public class PermissionDataAspect { @Autowired private CommonAPI commonApi; + private static final String SPOT_DO = ".do"; + @Pointcut("@annotation(org.jeecg.common.aspect.annotation.PermissionData)") public void pointCut() { @@ -113,7 +115,7 @@ public class PermissionDataAspect { requestPath = requestPath.substring(0, requestPath.indexOf("&")); } if(requestPath.indexOf(QueryRuleEnum.EQ.getValue())!=-1){ - if(requestPath.indexOf(CommonConstant.SPOT_DO)!=-1){ + if(requestPath.indexOf(SPOT_DO)!=-1){ requestPath = requestPath.substring(0,requestPath.indexOf(".do")+3); }else{ requestPath = requestPath.substring(0,requestPath.indexOf("?")); diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/DynamicTable.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/DynamicTable.java new file mode 100644 index 0000000..e377c8f --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/DynamicTable.java @@ -0,0 +1,20 @@ +package org.jeecg.common.aspect.annotation; + +import java.lang.annotation.*; + +/** + * 动态table切换 + * + * @author :zyf + * @date:2020-04-25 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DynamicTable { + /** + * 需要动态解析的表名 + * @return + */ + String value(); +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/CommonConstant.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/CommonConstant.java index 507dd72..a818c67 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/CommonConstant.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/CommonConstant.java @@ -9,320 +9,321 @@ public interface CommonConstant { /** * 正常状态 */ - public static final Integer STATUS_NORMAL = 0; + Integer STATUS_NORMAL = 0; /** * 禁用状态 */ - public static final Integer STATUS_DISABLE = -1; + Integer STATUS_DISABLE = -1; /** * 删除标志 */ - public static final Integer DEL_FLAG_1 = 1; + Integer DEL_FLAG_1 = 1; /** * 未删除 */ - public static final Integer DEL_FLAG_0 = 0; + Integer DEL_FLAG_0 = 0; /** * 系统日志类型: 登录 */ - public static final int LOG_TYPE_1 = 1; + int LOG_TYPE_1 = 1; /** * 系统日志类型: 操作 */ - public static final int LOG_TYPE_2 = 2; + int LOG_TYPE_2 = 2; /** * 操作日志类型: 查询 */ - public static final int OPERATE_TYPE_1 = 1; + int OPERATE_TYPE_1 = 1; /** * 操作日志类型: 添加 */ - public static final int OPERATE_TYPE_2 = 2; + int OPERATE_TYPE_2 = 2; /** * 操作日志类型: 更新 */ - public static final int OPERATE_TYPE_3 = 3; + int OPERATE_TYPE_3 = 3; /** * 操作日志类型: 删除 */ - public static final int OPERATE_TYPE_4 = 4; + int OPERATE_TYPE_4 = 4; /** * 操作日志类型: 倒入 */ - public static final int OPERATE_TYPE_5 = 5; + int OPERATE_TYPE_5 = 5; /** * 操作日志类型: 导出 */ - public static final int OPERATE_TYPE_6 = 6; + int OPERATE_TYPE_6 = 6; /** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */ - public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500; + Integer SC_INTERNAL_SERVER_ERROR_500 = 500; /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */ - public static final Integer SC_OK_200 = 200; + Integer SC_OK_200 = 200; /**访问权限认证未通过 510*/ - public static final Integer SC_JEECG_NO_AUTHZ=510; + Integer SC_JEECG_NO_AUTHZ=510; /** 登录用户Shiro权限缓存KEY前缀 */ public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:"; /** 登录用户Token令牌缓存KEY前缀 */ - public static final String PREFIX_USER_TOKEN = "prefix_user_token_"; + String PREFIX_USER_TOKEN = "prefix_user_token_"; // /** Token缓存时间:3600秒即一小时 */ -// public static final int TOKEN_EXPIRE_TIME = 3600; +// int TOKEN_EXPIRE_TIME = 3600; /** 登录二维码 */ - public static final String LOGIN_QRCODE_PRE = "QRCODELOGIN:"; - public static final String LOGIN_QRCODE = "LQ:"; + String LOGIN_QRCODE_PRE = "QRCODELOGIN:"; + String LOGIN_QRCODE = "LQ:"; /** 登录二维码token */ - public static final String LOGIN_QRCODE_TOKEN = "LQT:"; + String LOGIN_QRCODE_TOKEN = "LQT:"; /** * 0:一级菜单 */ - public static final Integer MENU_TYPE_0 = 0; + Integer MENU_TYPE_0 = 0; /** * 1:子菜单 */ - public static final Integer MENU_TYPE_1 = 1; + Integer MENU_TYPE_1 = 1; /** * 2:按钮权限 */ - public static final Integer MENU_TYPE_2 = 2; + Integer MENU_TYPE_2 = 2; /**通告对象类型(USER:指定用户,ALL:全体用户)*/ - public static final String MSG_TYPE_UESR = "USER"; - public static final String MSG_TYPE_ALL = "ALL"; + String MSG_TYPE_UESR = "USER"; + String MSG_TYPE_ALL = "ALL"; /**发布状态(0未发布,1已发布,2已撤销)*/ - public static final String NO_SEND = "0"; - public static final String HAS_SEND = "1"; - public static final String HAS_CANCLE = "2"; + String NO_SEND = "0"; + String HAS_SEND = "1"; + String HAS_CANCLE = "2"; /**阅读状态(0未读,1已读)*/ - public static final String HAS_READ_FLAG = "1"; - public static final String NO_READ_FLAG = "0"; + String HAS_READ_FLAG = "1"; + String NO_READ_FLAG = "0"; /**优先级(L低,M中,H高)*/ - public static final String PRIORITY_L = "L"; - public static final String PRIORITY_M = "M"; - public static final String PRIORITY_H = "H"; + String PRIORITY_L = "L"; + String PRIORITY_M = "M"; + String PRIORITY_H = "H"; /** * 短信模板方式 0 .登录模板、1.注册模板、2.忘记密码模板 */ - public static final String SMS_TPL_TYPE_0 = "0"; - public static final String SMS_TPL_TYPE_1 = "1"; - public static final String SMS_TPL_TYPE_2 = "2"; + String SMS_TPL_TYPE_0 = "0"; + String SMS_TPL_TYPE_1 = "1"; + String SMS_TPL_TYPE_2 = "2"; /** * 状态(0无效1有效) */ - public static final String STATUS_0 = "0"; - public static final String STATUS_1 = "1"; + String STATUS_0 = "0"; + String STATUS_1 = "1"; /** * 同步工作流引擎1同步0不同步 */ - public static final Integer ACT_SYNC_1 = 1; - public static final Integer ACT_SYNC_0 = 0; + Integer ACT_SYNC_1 = 1; + Integer ACT_SYNC_0 = 0; /** * 消息类型1:通知公告2:系统消息 */ - public static final String MSG_CATEGORY_1 = "1"; - public static final String MSG_CATEGORY_2 = "2"; + String MSG_CATEGORY_1 = "1"; + String MSG_CATEGORY_2 = "2"; /** * 是否配置菜单的数据权限 1是0否 */ - public static final Integer RULE_FLAG_0 = 0; - public static final Integer RULE_FLAG_1 = 1; + Integer RULE_FLAG_0 = 0; + Integer RULE_FLAG_1 = 1; /** * 是否用户已被冻结 1正常(解冻) 2冻结 */ - public static final Integer USER_UNFREEZE = 1; - public static final Integer USER_FREEZE = 2; + Integer USER_UNFREEZE = 1; + Integer USER_FREEZE = 2; /**字典翻译文本后缀*/ - public static final String DICT_TEXT_SUFFIX = "_dictText"; + String DICT_TEXT_SUFFIX = "_dictText"; /** * 表单设计器主表类型 */ - public static final Integer DESIGN_FORM_TYPE_MAIN = 1; + Integer DESIGN_FORM_TYPE_MAIN = 1; /** * 表单设计器子表表类型 */ - public static final Integer DESIGN_FORM_TYPE_SUB = 2; + Integer DESIGN_FORM_TYPE_SUB = 2; /** * 表单设计器URL授权通过 */ - public static final Integer DESIGN_FORM_URL_STATUS_PASSED = 1; + Integer DESIGN_FORM_URL_STATUS_PASSED = 1; /** * 表单设计器URL授权未通过 */ - public static final Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2; + Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2; /** * 表单设计器新增 Flag */ - public static final String DESIGN_FORM_URL_TYPE_ADD = "add"; + String DESIGN_FORM_URL_TYPE_ADD = "add"; /** * 表单设计器修改 Flag */ - public static final String DESIGN_FORM_URL_TYPE_EDIT = "edit"; + String DESIGN_FORM_URL_TYPE_EDIT = "edit"; /** * 表单设计器详情 Flag */ - public static final String DESIGN_FORM_URL_TYPE_DETAIL = "detail"; + String DESIGN_FORM_URL_TYPE_DETAIL = "detail"; /** * 表单设计器复用数据 Flag */ - public static final String DESIGN_FORM_URL_TYPE_REUSE = "reuse"; + String DESIGN_FORM_URL_TYPE_REUSE = "reuse"; /** * 表单设计器编辑 Flag (已弃用) */ - public static final String DESIGN_FORM_URL_TYPE_VIEW = "view"; + String DESIGN_FORM_URL_TYPE_VIEW = "view"; /** * online参数值设置(是:Y, 否:N) */ - public static final String ONLINE_PARAM_VAL_IS_TURE = "Y"; - public static final String ONLINE_PARAM_VAL_IS_FALSE = "N"; + String ONLINE_PARAM_VAL_IS_TURE = "Y"; + String ONLINE_PARAM_VAL_IS_FALSE = "N"; /** * 文件上传类型(本地:local,Minio:minio,阿里云:alioss) */ - public static final String UPLOAD_TYPE_LOCAL = "local"; - public static final String UPLOAD_TYPE_MINIO = "minio"; - public static final String UPLOAD_TYPE_OSS = "alioss"; + String UPLOAD_TYPE_LOCAL = "local"; + String UPLOAD_TYPE_MINIO = "minio"; + String UPLOAD_TYPE_OSS = "alioss"; /** * 文档上传自定义桶名称 */ - public static final String UPLOAD_CUSTOM_BUCKET = "eoafile"; + String UPLOAD_CUSTOM_BUCKET = "eoafile"; /** * 文档上传自定义路径 */ - public static final String UPLOAD_CUSTOM_PATH = "eoafile"; + String UPLOAD_CUSTOM_PATH = "eoafile"; /** * 文件外链接有效天数 */ - public static final Integer UPLOAD_EFFECTIVE_DAYS = 1; + Integer UPLOAD_EFFECTIVE_DAYS = 1; /** * 员工身份 (1:普通员工 2:上级) */ - public static final Integer USER_IDENTITY_1 = 1; - public static final Integer USER_IDENTITY_2 = 2; + Integer USER_IDENTITY_1 = 1; + Integer USER_IDENTITY_2 = 2; /** sys_user 表 username 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_SYS_USER_USERNAME = "uniq_sys_user_username"; + String SQL_INDEX_UNIQ_SYS_USER_USERNAME = "uniq_sys_user_username"; /** sys_user 表 work_no 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_SYS_USER_WORK_NO = "uniq_sys_user_work_no"; + String SQL_INDEX_UNIQ_SYS_USER_WORK_NO = "uniq_sys_user_work_no"; /** sys_user 表 phone 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_SYS_USER_PHONE = "uniq_sys_user_phone"; + String SQL_INDEX_UNIQ_SYS_USER_PHONE = "uniq_sys_user_phone"; /** 达梦数据库升提示。违反表[SYS_USER]唯一性约束 */ - public static final String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束"; + String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束"; /** sys_user 表 email 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_SYS_USER_EMAIL = "uniq_sys_user_email"; + String SQL_INDEX_UNIQ_SYS_USER_EMAIL = "uniq_sys_user_email"; /** sys_quartz_job 表 job_class_name 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name"; + String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name"; /** sys_position 表 code 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_CODE = "uniq_code"; + String SQL_INDEX_UNIQ_CODE = "uniq_code"; /** sys_role 表 code 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code"; + String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code"; /** sys_depart 表 code 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code"; + String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code"; /** sys_category 表 code 唯一键索引 */ - public static final String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code"; + String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code"; /** * 在线聊天 是否为默认分组 */ - public static final String IM_DEFAULT_GROUP = "1"; + String IM_DEFAULT_GROUP = "1"; /** * 在线聊天 图片文件保存路径 */ - public static final String IM_UPLOAD_CUSTOM_PATH = "imfile"; + String IM_UPLOAD_CUSTOM_PATH = "imfile"; /** * 在线聊天 用户状态 */ - public static final String IM_STATUS_ONLINE = "online"; + String IM_STATUS_ONLINE = "online"; /** * 在线聊天 SOCKET消息类型 */ - public static final String IM_SOCKET_TYPE = "chatMessage"; + String IM_SOCKET_TYPE = "chatMessage"; /** * 在线聊天 是否开启默认添加好友 1是 0否 */ - public static final String IM_DEFAULT_ADD_FRIEND = "1"; + String IM_DEFAULT_ADD_FRIEND = "1"; /** * 在线聊天 用户好友缓存前缀 */ - public static final String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_"; + String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_"; /** * 考勤补卡业务状态 (1:同意 2:不同意) */ - public static final String SIGN_PATCH_BIZ_STATUS_1 = "1"; - public static final String SIGN_PATCH_BIZ_STATUS_2 = "2"; + String SIGN_PATCH_BIZ_STATUS_1 = "1"; + String SIGN_PATCH_BIZ_STATUS_2 = "2"; /** * 公文文档上传自定义路径 */ - public static final String UPLOAD_CUSTOM_PATH_OFFICIAL = "officialdoc"; + String UPLOAD_CUSTOM_PATH_OFFICIAL = "officialdoc"; /** * 公文文档下载自定义路径 */ - public static final String DOWNLOAD_CUSTOM_PATH_OFFICIAL = "officaldown"; + String DOWNLOAD_CUSTOM_PATH_OFFICIAL = "officaldown"; /** * WPS存储值类别(1 code文号 2 text(WPS模板还是公文发文模板)) */ - public static final String WPS_TYPE_1="1"; - public static final String WPS_TYPE_2="2"; + String WPS_TYPE_1="1"; + String WPS_TYPE_2="2"; - public final static String X_ACCESS_TOKEN = "X-Access-Token"; - public final static String X_SIGN = "X-Sign"; - public final static String X_TIMESTAMP = "X-TIMESTAMP"; - public final static String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!"; + String X_ACCESS_TOKEN = "X-Access-Token"; + String X_SIGN = "X-Sign"; + String X_TIMESTAMP = "X-TIMESTAMP"; + String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!"; + String X_FORWARDED_SCHEME = "X-Forwarded-Scheme"; /** * 多租户 请求头 */ - public final static String TENANT_ID = "tenant-id"; + String TENANT_ID = "tenant-id"; /** * 微服务读取配置文件属性 服务地址 */ - public final static String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr"; + String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr"; /** * 第三方登录 验证密码/创建用户 都需要设置一个操作码 防止被恶意调用 */ - public final static String THIRD_LOGIN_CODE = "third_login_code"; + String THIRD_LOGIN_CODE = "third_login_code"; /** * 第三方APP同步方向:本地 --> 第三方APP @@ -361,16 +362,43 @@ public interface CommonConstant { /**String 类型的空值*/ String STRING_NULL = "null"; - /**java.util.Date 包*/ - String JAVA_UTIL_DATE = "java.util.Date"; + /**前端vue3版本Header参数名*/ + String VERSION="X-Version"; - /**.do*/ - String SPOT_DO = ".do"; + /**存储在线程变量里的动态表名*/ + String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME"; + /** + * http:// http协议 + */ + String HTTP_PROTOCOL = "http://"; + /** + * https:// https协议 + */ + String HTTPS_PROTOCOL = "https://"; + + /** 部门表唯一key,id */ + String DEPART_KEY_ID = "id"; + /** 部门表唯一key,orgCode */ + String DEPART_KEY_ORG_CODE = "orgCode"; - /**前端vue版本标识*/ - String VERSION="X-Version"; + /** + * 发消息 会传递一些信息到map + */ + String NOTICE_MSG_SUMMARY = "NOTICE_MSG_SUMMARY"; - /**前端vue版本*/ - String VERSION_VUE3="vue3"; + /** + * 发消息 会传递一个业务ID到map + */ + String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID"; + + /** + * 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值 + */ + String LOGIN_TOKEN = "{LOGIN_TOKEN}"; + + /** + * 模板消息中 跳转地址的对应的key + */ + String MSG_HREF_URL = "url"; } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java index 95f1bc3..e94a024 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java @@ -153,4 +153,14 @@ public interface DataBaseConstant { * sql语句 where */ String SQL_WHERE = "where"; + + /** + * sql语句 asc + */ + String SQL_ASC = "asc"; + + /** + * sqlserver数据库,中间有空格 + */ + String DB_TYPE_SQL_SERVER_BLANK = "sql server"; } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DynamicTableConstant.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DynamicTableConstant.java new file mode 100644 index 0000000..0dbff6c --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DynamicTableConstant.java @@ -0,0 +1,16 @@ +package org.jeecg.common.constant; + +/** + * 动态切换表配置常量 + * + * @author: scott + * @date: 2022年04月25日 22:30 + */ +public class DynamicTableConstant { + /** + * 角色首页配置表 + * vue2表名: sys_role_index + * vue3表名: sys_role_index_vue3 + */ + public static final String SYS_ROLE_INDEX = "sys_role_index"; +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ProvinceCityArea.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ProvinceCityArea.java index 2c4596b..6c2f170 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ProvinceCityArea.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ProvinceCityArea.java @@ -35,9 +35,13 @@ public class ProvinceCityArea { this.initAreaList(); if(areaList!=null && areaList.size()>0){ for(int i=areaList.size()-1;i>=0;i--){ - if(text.indexOf(areaList.get(i).getText())>=0){ + //update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区 + String areaText = areaList.get(i).getText(); + String cityText = areaList.get(i).getAheadText(); + if(text.indexOf(areaText)>=0 && (cityText!=null && text.indexOf(cityText)>=0)){ return areaList.get(i).getId(); } + //update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区 } } return null; @@ -145,6 +149,9 @@ public class ProvinceCityArea { for(String areaKey:areaJson.keySet()){ //System.out.println("········"+areaKey); Area area = new Area(areaKey,areaJson.getString(areaKey),cityKey); + //update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区 + area.setAheadText(cityJson.getString(cityKey)); + //update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区 this.areaList.add(area); } } @@ -180,6 +187,8 @@ public class ProvinceCityArea { String id; String text; String pid; + // 用于存储上级文本数据,区的上级文本 是市的数据 + String aheadText; public Area(String id,String text,String pid){ this.id = id; @@ -198,5 +207,12 @@ public class ProvinceCityArea { public String getPid() { return pid; } + + public String getAheadText() { + return aheadText; + } + public void setAheadText(String aheadText) { + this.aheadText = aheadText; + } } } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java index 5156a91..e18e6aa 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java @@ -86,4 +86,34 @@ public class SymbolConstant { * 符号:和 & */ public static final String AND = "&"; + + /** + * 符号:../ + */ + public static final String SPOT_SINGLE_SLASH = "../"; + + /** + * 符号:..\\ + */ + public static final String SPOT_DOUBLE_BACKSLASH = "..\\"; + + /** + * 系统变量前缀 #{ + */ + public static final String SYS_VAR_PREFIX = "#{"; + + /** + * 符号 {{ + */ + public static final String DOUBLE_LEFT_CURLY_BRACKET = "{{"; + + /** + * 符号:[ + */ + public static final String SQUARE_BRACKETS_LEFT = "["; + /** + * 符号:] + */ + public static final String SQUARE_BRACKETS_RIGHT = "]"; + } \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java index 0265e75..e97c553 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java @@ -20,13 +20,14 @@ public enum CgformEnum { */ MANY(2, "many", "/jeecg/code-template-online", "default.onetomany", "经典风格"), /** - * 多表 - */ - ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格"), - /** * 多表(jvxe风格) * */ JVXE_TABLE(2, "jvxe", "/jeecg/code-template-online", "jvxe.onetomany", "JVXE风格"), + + /** + * 多表 + */ + ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格"), /** * 多表(内嵌子表风格) */ diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java index b865cad..81c22aa 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java @@ -15,6 +15,8 @@ public enum LowAppAopEnum { * 删除方法(包含单个和批量删除) */ DELETE, + /** 复制表单操作 */ + COPY, /** * Online表单专用:数据库表转Online表单 diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java new file mode 100644 index 0000000..b2e65e0 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java @@ -0,0 +1,68 @@ +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; + +/** + * 消息类型 + * @author: jeecg-boot + */ +@EnumDict("messageType") +public enum MessageTypeEnum { + + XT("system", "系统消息"), + YJ("email", "邮件消息"), + DD("dingtalk", "钉钉消息"), + QYWX("wechat_enterprise", "企业微信"); + + MessageTypeEnum(String type, String note){ + this.type = type; + this.note = note; + } + + /** + * 消息类型 + */ + String type; + + /** + * 类型说明 + */ + String note; + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + /** + * 获取字典数据 + * @return + */ + public static List<DictModel> getDictList(){ + List<DictModel> list = new ArrayList<>(); + DictModel dictModel = null; + for(MessageTypeEnum e: MessageTypeEnum.values()){ + dictModel = new DictModel(); + dictModel.setValue(e.getType()); + dictModel.setText(e.getNote()); + list.add(dictModel); + } + return list; + } +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java index 7140721..6d5d8ef 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java @@ -123,7 +123,8 @@ public class JeecgBootExceptionHandler { @ExceptionHandler(DataIntegrityViolationException.class) public Result<?> handleDataIntegrityViolationException(DataIntegrityViolationException e) { log.error(e.getMessage(), e); - return Result.error("字段太长,超出数据库字段的长度"); + //【issues/3624】数据库执行异常handleDataIntegrityViolationException提示有误 #3624 + return Result.error("执行数据库异常,违反了完整性例如:违反惟一约束、违反非空限制、字段内容超出长度等"); } @ExceptionHandler(PoolException.class) diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java new file mode 100644 index 0000000..1ffb760 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java @@ -0,0 +1,19 @@ +package org.jeecg.common.system.annotation; + +import java.lang.annotation.*; + +/** + * 将枚举类转化成字典数据 + * @Author taoYan + * @Date 2022/7/8 10:34 + **/ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EnumDict { + + /** + * 作为字典数据的唯一编码 + */ + String value() default ""; +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java index c665a09..fdb1506 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java @@ -53,18 +53,14 @@ public class JeecgController<T, S extends IService<T>> { QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap()); LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); - // Step.2 获取导出数据 - List<T> pageList = service.list(queryWrapper); - List<T> exportList = null; - // 过滤选中数据 String selections = request.getParameter("selections"); if (oConvertUtils.isNotEmpty(selections)) { List<String> selectionList = Arrays.asList(selections.split(",")); - exportList = pageList.stream().filter(item -> selectionList.contains(getId(item))).collect(Collectors.toList()); - } else { - exportList = pageList; + queryWrapper.in("id",selectionList); } + // Step.2 获取导出数据 + List<T> exportList = service.list(queryWrapper); // Step.3 AutoPoi 导出Excel ModelAndView mv = new ModelAndView(new JeecgEntityExcelView()); @@ -97,21 +93,20 @@ public class JeecgController<T, S extends IService<T>> { // Step.2 计算分页sheet数据 double total = service.count(); int count = (int)Math.ceil(total/pageNum); - // Step.3 多sheet处理 + //update-begin-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 --- + // Step.3 过滤选中数据 + String selections = request.getParameter("selections"); + if (oConvertUtils.isNotEmpty(selections)) { + List<String> selectionList = Arrays.asList(selections.split(",")); + queryWrapper.in("id",selectionList); + } + //update-end-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 --- + // Step.4 多sheet处理 List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>(); for (int i = 1; i <=count ; i++) { Page<T> page = new Page<T>(i, pageNum); IPage<T> pageList = service.page(page, queryWrapper); - List<T> records = pageList.getRecords(); - List<T> exportList = null; - // 过滤选中数据 - String selections = request.getParameter("selections"); - if (oConvertUtils.isNotEmpty(selections)) { - List<String> selectionList = Arrays.asList(selections.split(",")); - exportList = records.stream().filter(item -> selectionList.contains(getId(item))).collect(Collectors.toList()); - } else { - exportList = records; - } + List<T> exportList = pageList.getRecords(); Map<String, Object> map = new HashMap<>(5); ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,upLoadPath); exportParams.setType(ExcelType.XSSF); diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java index 523ffd0..4a1411f 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java @@ -16,6 +16,7 @@ import org.apache.commons.beanutils.PropertyUtils; import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.DataBaseConstant; 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.vo.SysPermissionDataRuleModel; @@ -191,8 +192,8 @@ public class QueryGenerator { log.error(e.getMessage(), e); } } - // 排序逻辑 处理 - doMultiFieldsOrder(queryWrapper, parameterMap); + // 排序逻辑 处理 + doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap.keySet()); //高级查询 doSuperQuery(queryWrapper, parameterMap, fieldColumnMap); @@ -228,8 +229,7 @@ public class QueryGenerator { } } - /**多字段排序 TODO 需要修改前端*/ - private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap) { + private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap, Set<String> allFields) { String column=null,order=null; if(parameterMap!=null&& parameterMap.containsKey(ORDER_COLUMN)) { column = parameterMap.get(ORDER_COLUMN)[0]; @@ -243,6 +243,15 @@ public class QueryGenerator { if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) { column = column.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX)); } + + //update-begin-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效 + //判断column是不是当前实体的 + log.info("当前字段有:"+ allFields); + if (!allColumnExist(column, allFields)) { + throw new JeecgBootException("请注意,将要排序的列字段不存在:" + column); + } + //update-end-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效 + //SQL注入check SqlInjectionUtil.filterContent(column); @@ -264,6 +273,28 @@ public class QueryGenerator { //update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正------- } } + + //update-begin-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效 + /** + * 多字段排序 判断所传字段是否存在 + * @return + */ + private static boolean allColumnExist(String columnStr, Set<String> allFields){ + boolean exist = true; + if(columnStr.indexOf(COMMA)>=0){ + String[] arr = columnStr.split(COMMA); + for(String column: arr){ + if(!allFields.contains(column)){ + exist = false; + break; + } + } + }else{ + exist = allFields.contains(columnStr); + } + return exist; + } + //update-end-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效 /** * 高级查询 @@ -825,13 +856,13 @@ public class QueryGenerator { res = field + " in "+getInConditionValue(value, isString); break; case LIKE: - res = field + " like "+getLikeConditionValue(value); + res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE); break; case LEFT_LIKE: - res = field + " like "+getLikeConditionValue(value); + res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE); break; case RIGHT_LIKE: - res = field + " like "+getLikeConditionValue(value); + res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE); break; default: res = field+" = "+getFieldConditionValue(value, isString, dataBaseType); @@ -914,8 +945,15 @@ public class QueryGenerator { } //update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错 } - - private static String getLikeConditionValue(Object value) { + + /** + * 先根据值判断 走左模糊还是右模糊 + * 最后如果值不带任何标识(*或者%),则再根据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())){ @@ -951,11 +989,30 @@ public class QueryGenerator { } } }else { - if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){ - return "N'%"+str+"%'"; - }else{ - return "'%"+str+"%'"; + + //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 数据权限规则问题 + } } } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java index 73fc14a..4c82608 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java @@ -54,7 +54,7 @@ public class JwtUtil { try { os = httpServletResponse.getOutputStream(); httpServletResponse.setCharacterEncoding("UTF-8"); - httpServletResponse.setStatus(401); + httpServletResponse.setStatus(code); os.write(new ObjectMapper().writeValueAsString(jsonResult).getBytes("UTF-8")); os.flush(); os.close(); diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java new file mode 100644 index 0000000..d3260c6 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java @@ -0,0 +1,111 @@ +package org.jeecg.common.system.util; + +import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.system.annotation.EnumDict; +import org.jeecg.common.system.vo.DictModel; +import org.jeecg.common.util.oConvertUtils; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 资源加载工具类 + * @Author taoYan + * @Date 2022/7/8 10:40 + **/ +@Slf4j +public class ResourceUtil { + + + /** + * 枚举字典数据 + */ + private final static Map<String, List<DictModel>> enumDictData = new HashMap<>(5); + + /** + * 所有java类 + */ + private final static String CLASS_PATTERN="/**/*.class"; + + /** + * 包路径 org.jeecg + */ + private final static String BASE_PACKAGE = "org.jeecg"; + + /** + * 枚举类中获取字典数据的方法名 + */ + private final static String METHOD_NAME = "getDictList"; + + /** + * 获取枚举类对应的字典数据 SysDictServiceImpl#queryAllDictItems() + * @return + */ + public static Map<String, List<DictModel>> getEnumDictData(){ + if(enumDictData.keySet().size()>0){ + return enumDictData; + } + ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_PATTERN; + try { + Resource[] resources = resourcePatternResolver.getResources(pattern); + MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + for (Resource resource : resources) { + MetadataReader reader = readerFactory.getMetadataReader(resource); + String classname = reader.getClassMetadata().getClassName(); + Class<?> clazz = Class.forName(classname); + EnumDict enumDict = clazz.getAnnotation(EnumDict.class); + if (enumDict != null) { + EnumDict annotation = clazz.getAnnotation(EnumDict.class); + String key = annotation.value(); + if(oConvertUtils.isNotEmpty(key)){ + List<DictModel> list = (List<DictModel>) clazz.getDeclaredMethod(METHOD_NAME).invoke(null); + enumDictData.put(key, list); + } + } + } + }catch (Exception e){ + log.error("获取枚举类字典数据异常", e.getMessage()); + // e.printStackTrace(); + } + return enumDictData; + } + + /** + * 用于后端字典翻译 SysDictServiceImpl#queryManyDictByKeys(java.util.List, java.util.List) + * @param dictCodeList + * @param keys + * @return + */ + public static Map<String, List<DictModel>> queryManyDictByKeys(List<String> dictCodeList, List<String> keys){ + if(enumDictData.keySet().size()==0){ + getEnumDictData(); + } + Map<String, List<DictModel>> map = new HashMap<>(); + for (String code : enumDictData.keySet()) { + if(dictCodeList.indexOf(code)>=0){ + List<DictModel> dictItemList = enumDictData.get(code); + for(DictModel dm: dictItemList){ + String value = dm.getValue(); + if(keys.indexOf(value)>=0){ + List<DictModel> list = new ArrayList<>(); + list.add(new DictModel(value, dm.getText())); + map.put(code,list); + break; + } + } + } + } + return map; + } + +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java index c40f2e6..251db42 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java @@ -2,6 +2,7 @@ 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; @@ -26,21 +27,25 @@ public class LoginUser { /** * 登录人id */ + @SensitiveField private String id; /** * 登录人账号 */ + @SensitiveField private String username; /** * 登录人名字 */ + @SensitiveField private String realname; /** * 登录人密码 */ + @SensitiveField private String password; /** @@ -50,11 +55,13 @@ public class LoginUser { /** * 头像 */ + @SensitiveField private String avatar; /** * 生日 */ + @SensitiveField @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birthday; @@ -67,11 +74,13 @@ public class LoginUser { /** * 电子邮件 */ + @SensitiveField private String email; /** * 电话 */ + @SensitiveField private String phone; /** @@ -103,11 +112,13 @@ public class LoginUser { /** * 职务,关联职务表 */ + @SensitiveField private String post; /** * 座机号 */ + @SensitiveField private String telephone; /**多租户id配置,编辑用户的时候设置*/ diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java index 89acf02..80672f3 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java @@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils; import lombok.extern.slf4j.Slf4j; 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.util.oss.OssBootUtil; @@ -314,14 +315,14 @@ public class CommonUtils { */ public static String getBaseUrl(HttpServletRequest request) { //1.【兼容】兼容微服务下的 base path------- - String xGatewayBasePath = request.getHeader("X_GATEWAY_BASE_PATH"); + String xGatewayBasePath = request.getHeader(ServiceNameConstants.X_GATEWAY_BASE_PATH); if(oConvertUtils.isNotEmpty(xGatewayBasePath)){ log.info("x_gateway_base_path = "+ xGatewayBasePath); return xGatewayBasePath; } //2.【兼容】SSL认证之后,request.getScheme()获取不到https的问题 // https://blog.csdn.net/weixin_34376986/article/details/89767950 - String scheme = request.getHeader("X-Forwarded-Scheme"); + String scheme = request.getHeader(CommonConstant.X_FORWARDED_SCHEME); if(oConvertUtils.isEmpty(scheme)){ scheme = request.getScheme(); } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java index e0ec2e6..0fb4606 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java @@ -214,7 +214,7 @@ public class RestUtil { } } // 拼接 url 参数 - if (variables != null) { + if (variables != null && !variables.isEmpty()) { url += ("?" + asUrlVariables(variables)); } // 发送请求 diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java index f2ac657..99c4caf 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java @@ -4,6 +4,8 @@ import cn.hutool.crypto.SecureUtil; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.exception.JeecgBootException; import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Field; +import java.util.Set; import java.util.regex.Pattern; /** @@ -20,7 +22,11 @@ public class SqlInjectionUtil { private final static String TABLE_DICT_SIGN_SALT = "20200501"; private final static String XSS_STR = "and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()"; - /**show tables*/ + /** + * 正则 user() 匹配更严谨 + */ + private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)"; + /**正则 show tables*/ private final static String SHOW_TABLES = "show\\s+tables"; /** @@ -42,6 +48,13 @@ public class SqlInjectionUtil { log.info(" 表字典,SQL注入漏洞签名校验成功!sign=" + sign + ",dictCode=" + dictCode); } + /** + * sql注入过滤处理,遇到注入关键字抛异常 + * @param value + */ + public static void filterContent(String value) { + filterContent(value, null); + } /** * sql注入过滤处理,遇到注入关键字抛异常 @@ -49,7 +62,7 @@ public class SqlInjectionUtil { * @param value * @return */ - public static void filterContent(String value) { + public static void filterContent(String value, String customXssString) { if (value == null || "".equals(value)) { return; } @@ -66,7 +79,19 @@ public class SqlInjectionUtil { throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); } } - if(Pattern.matches(SHOW_TABLES, value)){ + //update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号 + if (customXssString != null) { + String[] xssArr2 = customXssString.split("\\|"); + for (int i = 0; i < xssArr2.length; i++) { + if (value.indexOf(xssArr2[i]) > -1) { + log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]); + log.error("请注意,值可能存在SQL注入风险!---> {}", value); + throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); + } + } + } + //update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号 + if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); } return; @@ -74,11 +99,19 @@ public class SqlInjectionUtil { /** * sql注入过滤处理,遇到注入关键字抛异常 + * @param values + */ + public static void filterContent(String[] values) { + filterContent(values, null); + } + + /** + * sql注入过滤处理,遇到注入关键字抛异常 * * @param values * @return */ - public static void filterContent(String[] values) { + public static void filterContent(String[] values, String customXssString) { String[] xssArr = XSS_STR.split("\\|"); for (String value : values) { if (value == null || "".equals(value)) { @@ -96,7 +129,19 @@ public class SqlInjectionUtil { throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); } } - if(Pattern.matches(SHOW_TABLES, value)){ + //update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号 + if (customXssString != null) { + String[] xssArr2 = customXssString.split("\\|"); + for (int i = 0; i < xssArr2.length; i++) { + if (value.indexOf(xssArr2[i]) > -1) { + log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]); + log.error("请注意,值可能存在SQL注入风险!---> {}", value); + throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); + } + } + } + //update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号 + if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); } } @@ -111,8 +156,8 @@ public class SqlInjectionUtil { * @return */ //@Deprecated - public static void specialFilterContent(String value) { - String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|"; + public static void specialFilterContentForDictSql(String value) { + String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()"; String[] xssArr = specialXssStr.split("\\|"); if (value == null || "".equals(value)) { return; @@ -129,7 +174,7 @@ public class SqlInjectionUtil { throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); } } - if(Pattern.matches(SHOW_TABLES, value)){ + if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value); } return; @@ -144,7 +189,7 @@ public class SqlInjectionUtil { */ //@Deprecated public static void specialFilterContentForOnlineReport(String value) { - String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |"; + String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()"; String[] xssArr = specialXssStr.split("\\|"); if (value == null || "".equals(value)) { return; @@ -162,10 +207,53 @@ public class SqlInjectionUtil { } } - if(Pattern.matches(SHOW_TABLES, value)){ + if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){ throw new RuntimeException("请注意,值可能存在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; + } + + /** + * 判断给定的多个字段是不是类中的属性 + * @param fieldSet 字段名set + * @param clazz 类对象 + * @return + */ + public static boolean isClassField(Set<String> fieldSet, Class clazz){ + Field[] fields = clazz.getDeclaredFields(); + for(String field: fieldSet){ + boolean exist = false; + for(int i=0;i<fields.length;i++){ + String fieldName = fields[i].getName(); + String tableColumnName = oConvertUtils.camelToUnderline(fieldName); + if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){ + exist = true; + break; + } + } + if(!exist){ + return false; + } + } + return true; + } } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java index b85615c..bd2fa07 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import org.jeecg.common.api.CommonAPI; import org.jeecg.common.constant.CacheConstant; import org.jeecg.common.constant.CommonConstant; +import org.jeecg.common.desensitization.util.SensitiveInfoUtil; import org.jeecg.common.exception.JeecgBoot401Exception; import org.jeecg.common.system.util.JwtUtil; import org.jeecg.common.system.vo.LoginUser; @@ -106,9 +107,16 @@ public class TokenUtils { public static LoginUser getLoginUser(String username, CommonAPI commonApi, RedisUtil redisUtil) { LoginUser loginUser = null; String loginUserKey = CacheConstant.SYS_USERS_CACHE + "::" + username; - if(redisUtil.hasKey(loginUserKey)){ - loginUser = (LoginUser) redisUtil.get(loginUserKey); - }else{ + //【重要】此处通过redis原生获取缓存用户,是为了解决微服务下system服务挂了,其他服务互调不通问题--- + if (redisUtil.hasKey(loginUserKey)) { + try { + loginUser = (LoginUser) redisUtil.get(loginUserKey); + //解密用户 + SensitiveInfoUtil.handlerObject(loginUser, false); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } else { // 查询用户信息 loginUser = commonApi.getUserByName(username); } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java index ea628dd..670f3eb 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java @@ -66,22 +66,20 @@ public class AesEncryptUtil { * @throws Exception */ public static String desEncrypt(String data, String key, String iv) throws Exception { - try { - byte[] encrypted1 = Base64.decode(data); + //update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理 + byte[] encrypted1 = Base64.decode(data); - Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); - SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); - IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes()); + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); + IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes()); - cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); + cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); - byte[] original = cipher.doFinal(encrypted1); - String originalString = new String(original); - return originalString; - } catch (Exception e) { - e.printStackTrace(); - return null; - } + byte[] original = cipher.doFinal(encrypted1); + String originalString = new String(original); + //加密解码后的字符串会出现\u0000 + return originalString.replaceAll("\\u0000", ""); + //update-end-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理 } /** diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java index 2806f31..85af28d 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java @@ -168,7 +168,7 @@ public abstract class AbstractQueryBlackListHandler { public String getError(){ // TODO - return "sql黑名单校验不通过,请联系管理员!"; + return "系统设置了安全规则,敏感表和敏感字段禁止查询,联系管理员授权!"; } } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeeccgBaseConfig.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeecgBaseConfig.java index 6f8a345..12a9cc0 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeeccgBaseConfig.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeecgBaseConfig.java @@ -1,5 +1,7 @@ package org.jeecg.config; +import org.jeecg.config.vo.DomainUrl; +import org.jeecg.config.vo.Path; import org.jeecg.config.vo.Shiro; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -9,9 +11,15 @@ import org.springframework.stereotype.Component; * 加载项目配置 * @author: jeecg-boot */ -@Component("jeeccgBaseConfig") +@Component("jeecgBaseConfig") @ConfigurationProperties(prefix = "jeecg") -public class JeeccgBaseConfig { +public class JeecgBaseConfig { + /** + * 签名密钥串(字典等敏感接口) + * @TODO 降低使用成本加的默认值,实际以 yml配置 为准 + */ + private String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a"; + /** * 是否启用安全模式 */ @@ -21,10 +29,16 @@ public class JeeccgBaseConfig { */ private Shiro shiro; /** - * 签名密钥串(字典等敏感接口) - * @TODO 降低使用成本加的默认值,实际以 yml配置 为准 + * 上传文件配置 */ - private String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a"; + private Path path; + + /** + * 前端页面访问地址 + * pc: http://localhost:3100 + * app: http://localhost:8051 + */ + private DomainUrl domainUrl; public Boolean getSafeMode() { return safeMode; @@ -49,4 +63,20 @@ public class JeeccgBaseConfig { public void setShiro(Shiro shiro) { this.shiro = shiro; } + + public Path getPath() { + return path; + } + + public void setPath(Path path) { + this.path = path; + } + + public DomainUrl getDomainUrl() { + return domainUrl; + } + + public void setDomainUrl(DomainUrl domainUrl) { + this.domainUrl = domainUrl; + } } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java index a56b79b..69d804e 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java @@ -145,4 +145,13 @@ public class WebMvcConfiguration implements WebMvcConfigurer { return () -> meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, ""); } +// /** +// * 注册拦截器【拦截器拦截参数,自动切换数据源——后期实现多租户切换数据源功能】 +// * @param registry +// */ +// @Override +// public void addInterceptors(InterceptorRegistry registry) { +// registry.addInterceptor(new DynamicDatasourceInterceptor()).addPathPatterns("/test/dynamic/**"); +// } + } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java index 99c075a..0960fc3 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java @@ -3,6 +3,9 @@ package org.jeecg.config.mybatis; import java.util.ArrayList; import java.util.List; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor; +import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.util.oConvertUtils; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; @@ -71,9 +74,35 @@ public class MybatisPlusSaasConfig { } })); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); + //update-begin-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题 + interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor()); + //update-end-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题 return interceptor; } + /** + * 动态表名切换拦截器,用于适配vue2和vue3同一个表有多个的情况,如sys_role_index在vue3情况下表名为sys_role_index_v3 + * @return + */ + private DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() { + DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor(); + dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> { + //获取需要动态解析的表名 + String dynamicTableName = ThreadLocalDataHelper.get(CommonConstant.DYNAMIC_TABLE_NAME); + //当dynamicTableName不为空时才走动态表名处理逻辑,否则返回原始表名 + if (ObjectUtil.isNotEmpty(dynamicTableName) && dynamicTableName.equals(tableName)) { + // 获取前端传递的版本号标识 + Object version = ThreadLocalDataHelper.get(CommonConstant.VERSION); + if (ObjectUtil.isNotEmpty(version)) { + //拼接表名规则(原始表名+下划线+前端传递的版本号) + return tableName + "_" + version; + } + } + return tableName; + }); + return dynamicTableNameInnerInterceptor; + } + // /** // * 下个版本会删除,现在为了避免缓存出现问题不得不配置 // * @return diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/ThreadLocalDataHelper.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/ThreadLocalDataHelper.java new file mode 100644 index 0000000..0b60721 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/ThreadLocalDataHelper.java @@ -0,0 +1,62 @@ +package org.jeecg.config.mybatis; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * @Description: 本地线程变量存储工具类 + * @author: lsq + * @date: 2022年03月25日 11:42 + */ +public class ThreadLocalDataHelper { + /** + * 线程的本地变量 + */ + private static final ThreadLocal<ConcurrentHashMap> REQUEST_DATA = new ThreadLocal<>(); + + /** + * 存储本地参数 + */ + private static final ConcurrentHashMap DATA_MAP = new ConcurrentHashMap<>(); + + /** + * 设置请求参数 + * + * @param key 参数key + * @param value 参数值 + */ + public static void put(String key, Object value) { + if(ObjectUtil.isNotEmpty(value)) { + DATA_MAP.put(key, value); + REQUEST_DATA.set(DATA_MAP); + } + } + + /** + * 获取请求参数值 + * + * @param key 请求参数 + * @return + */ + public static <T> T get(String key) { + ConcurrentHashMap dataMap = REQUEST_DATA.get(); + if (CollectionUtils.isNotEmpty(dataMap)) { + return (T) dataMap.get(key); + } + return null; + } + + /** + * 获取请求参数 + * + * @return 请求参数 MAP 对象 + */ + public static void clear() { + DATA_MAP.clear(); + REQUEST_DATA.remove(); + } + +} + diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/aspect/DynamicTableAspect.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/aspect/DynamicTableAspect.java new file mode 100644 index 0000000..b0d67cb --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/aspect/DynamicTableAspect.java @@ -0,0 +1,55 @@ +package org.jeecg.config.mybatis.aspect; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.jeecg.common.aspect.annotation.DynamicTable; +import org.jeecg.common.constant.CommonConstant; +import org.jeecg.common.util.SpringContextUtils; +import org.jeecg.config.mybatis.ThreadLocalDataHelper; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; + +/** + * 动态table切换 切面处理 + * + * @author :zyf + * @date:2020-04-25 + */ +@Aspect +@Component +public class DynamicTableAspect { + + + /** + * 定义切面拦截切入点 + */ + @Pointcut("@annotation(org.jeecg.common.aspect.annotation.DynamicTable)") + public void dynamicTable() { + } + + + @Around("dynamicTable()") + public Object around(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + DynamicTable dynamicTable = method.getAnnotation(DynamicTable.class); + HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); + //获取前端传递的版本标记 + String version = request.getHeader(CommonConstant.VERSION); + //存储版本号到本地线程变量 + ThreadLocalDataHelper.put(CommonConstant.VERSION, version); + //存储表名到本地线程变量 + ThreadLocalDataHelper.put(CommonConstant.DYNAMIC_TABLE_NAME, dynamicTable.value()); + //执行方法 + Object result = point.proceed(); + //清空本地变量 + ThreadLocalDataHelper.clear(); + return result; + } + +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.java new file mode 100644 index 0000000..3461cbd --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.java @@ -0,0 +1,55 @@ +package org.jeecg.config.mybatis.interceptor; + +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 动态数据源切换拦截器 + * + * 测试:拦截参数,自动切换数据源 + * 未来规划:后面通过此机制,实现多租户切换数据源功能 + * @author zyf + */ +@Slf4j +public class DynamicDatasourceInterceptor implements HandlerInterceptor { + + /** + * 在请求处理之前进行调用(Controller方法调用之前) + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String requestURI = request.getRequestURI(); + log.info("经过多数据源Interceptor,当前路径是{}", requestURI); + //获取动态数据源名称 + String dsName = request.getParameter("dsName"); + String dsKey = "master"; + if (StringUtils.isNotEmpty(dsName)) { + dsKey = dsName; + } + DynamicDataSourceContextHolder.push(dsKey); + return true; + } + + /** + * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后) + */ + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { + + } + + /** + * 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作) + */ + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + DynamicDataSourceContextHolder.clear(); + } + +} \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java index f0fe2d2..b31f538 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java @@ -15,7 +15,7 @@ import org.crazycake.shiro.RedisClusterManager; import org.crazycake.shiro.RedisManager; import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.util.oConvertUtils; -import org.jeecg.config.JeeccgBaseConfig; +import org.jeecg.config.JeecgBaseConfig; import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean; import org.jeecg.config.shiro.filters.JwtFilter; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; @@ -45,11 +45,11 @@ import java.util.*; public class ShiroConfig { @Resource - LettuceConnectionFactory lettuceConnectionFactory; + private LettuceConnectionFactory lettuceConnectionFactory; @Autowired private Environment env; - @Autowired - JeeccgBaseConfig jeeccgBaseConfig; + @Resource + private JeecgBaseConfig jeecgBaseConfig; /** * Filter Chain定义说明 @@ -64,11 +64,15 @@ public class ShiroConfig { shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); - String shiroExcludeUrls = jeeccgBaseConfig.getShiro().getExcludeUrls(); - if(oConvertUtils.isNotEmpty(shiroExcludeUrls)){ - String[] permissionUrl = shiroExcludeUrls.split(","); - for(String url : permissionUrl){ - filterChainDefinitionMap.put(url,"anon"); + + //支持yml方式,配置拦截排除 + if(jeecgBaseConfig.getShiro()!=null){ + String shiroExcludeUrls = jeecgBaseConfig.getShiro().getExcludeUrls(); + if(oConvertUtils.isNotEmpty(shiroExcludeUrls)){ + String[] permissionUrl = shiroExcludeUrls.split(","); + for(String url : permissionUrl){ + filterChainDefinitionMap.put(url,"anon"); + } } } // 配置不会被拦截的链接 顺序判断 @@ -125,9 +129,11 @@ public class ShiroConfig { filterChainDefinitionMap.put("/jmreport/**", "anon"); filterChainDefinitionMap.put("/**/*.js.map", "anon"); filterChainDefinitionMap.put("/**/*.css.map", "anon"); - - //测试示例 - filterChainDefinitionMap.put("/test/bigScreen/**", "anon"); //大屏模板例子 + + //大屏模板例子 + filterChainDefinitionMap.put("/test/bigScreen/**", "anon"); + filterChainDefinitionMap.put("/bigscreen/template1/**", "anon"); + filterChainDefinitionMap.put("/bigscreen/template1/**", "anon"); //filterChainDefinitionMap.put("/test/jeecgDemo/rabbitMqClientTest/**", "anon"); //MQ测试 //filterChainDefinitionMap.put("/test/jeecgDemo/html", "anon"); //模板页面 //filterChainDefinitionMap.put("/test/jeecgDemo/redis/**", "anon"); //redis测试 @@ -137,8 +143,6 @@ public class ShiroConfig { filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模块 filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable无痕刷新示例 - //wps - filterChainDefinitionMap.put("/v1/**","anon"); //性能监控 TODO 存在安全漏洞泄露TOEKN(durid连接池也有) filterChainDefinitionMap.put("/actuator/**", "anon"); diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java index acf80f8..6c203dc 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java @@ -69,13 +69,13 @@ public class ShiroRealm extends AuthorizingRealm { // 设置用户拥有的角色集合,比如“admin,test” Set<String> roleSet = commonApi.queryUserRoles(username); - System.out.println(roleSet.toString()); + //System.out.println(roleSet.toString()); info.setRoles(roleSet); // 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add” Set<String> permissionSet = commonApi.queryUserAuths(username); info.addStringPermissions(permissionSet); - System.out.println(permissionSet); + //System.out.println(permissionSet); log.info("===============Shiro权限认证成功=============="); return info; } @@ -123,7 +123,7 @@ public class ShiroRealm extends AuthorizingRealm { // 查询用户信息 log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token); - LoginUser loginUser = TokenUtils.getLoginUser(username,commonApi,redisUtil); + LoginUser loginUser = TokenUtils.getLoginUser(username, commonApi, redisUtil); //LoginUser loginUser = commonApi.getUserByName(username); if (loginUser == null) { throw new AuthenticationException("用户不存在!"); diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java index ded143f..ca8de21 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java @@ -107,4 +107,18 @@ public class JwtFilter extends BasicHttpAuthenticationFilter { return super.preHandle(request, response); } + + /** + * JwtFilter中ThreadLocal需要及时清除 #3634 + * + * @param request + * @param response + * @param exception + * @throws Exception + */ + @Override + public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception { + //log.info("------清空线程中多租户的ID={}------",TenantContext.getTenant()); + TenantContext.clear(); + } } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java index e78fa35..137ecc5 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java @@ -1,5 +1,7 @@ package org.jeecg.config.sign.interceptor; +import org.jeecg.config.filter.RequestBodyReserveFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -24,4 +26,22 @@ public class SignAuthConfiguration implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(signAuthInterceptor()).addPathPatterns(SIGN_URL_LIST); } + + //update-begin-author:taoyan date:20220427 for: issues/I53J5E post请求X_SIGN签名拦截校验后报错, request body 为空 + @Bean + public RequestBodyReserveFilter requestBodyReserveFilter(){ + return new RequestBodyReserveFilter(); + } + + @Bean + public FilterRegistrationBean reqBodyFilterRegistrationBean(){ + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(requestBodyReserveFilter()); + registration.setName("requestBodyReserveFilter"); + // 建议此处只添加post请求地址而不是所有的都需要走过滤器 + registration.addUrlPatterns(SIGN_URL_LIST); + return registration; + } + //update-end-author:taoyan date:20220427 for: issues/I53J5E post请求X_SIGN签名拦截校验后报错, request body 为空 + } diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java index 3cdbaea..27fb811 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java @@ -32,7 +32,7 @@ public class SignAuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - log.info("request URI = " + request.getRequestURI()); + log.info("Sign Interceptor request URI = " + request.getRequestURI()); HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request); //获取全部参数(包括URL和body上的) SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper); diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java index 3f82822..54cc320 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java @@ -6,7 +6,7 @@ import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.util.SpringContextUtils; import org.jeecg.common.util.oConvertUtils; -import org.jeecg.config.JeeccgBaseConfig; +import org.jeecg.config.JeecgBaseConfig; import org.springframework.util.DigestUtils; import org.springframework.util.StringUtils; @@ -48,8 +48,8 @@ public class SignUtil { String paramsJsonStr = JSONObject.toJSONString(params); log.info("Param paramsJsonStr : {}", paramsJsonStr); //设置签名秘钥 - JeeccgBaseConfig jeeccgBaseConfig = SpringContextUtils.getBean(JeeccgBaseConfig.class); - String signatureSecret = jeeccgBaseConfig.getSignatureSecret(); + JeecgBaseConfig jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class); + String signatureSecret = jeecgBaseConfig.getSignatureSecret(); String curlyBracket = SymbolConstant.DOLLAR + SymbolConstant.LEFT_CURLY_BRACKET; if(oConvertUtils.isEmpty(signatureSecret) || signatureSecret.contains(curlyBracket)){ throw new JeecgBootException("签名密钥 ${jeecg.signatureSecret} 缺少配置 !!"); diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/DomainUrl.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/DomainUrl.java new file mode 100644 index 0000000..df6b8c3 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/DomainUrl.java @@ -0,0 +1,15 @@ +package org.jeecg.config.vo; + +import lombok.Data; + +/** + * @Author taoYan + * @Date 2022/7/5 21:16 + **/ +@Data +public class DomainUrl { + + private String pc; + + private String app; +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/Path.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/Path.java new file mode 100644 index 0000000..9227a84 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/Path.java @@ -0,0 +1,29 @@ +package org.jeecg.config.vo; + +import javax.print.DocFlavor; + +/** + * + * @author: scott + * @date: 2022年04月18日 20:35 + */ +public class Path { + private String upload; + private String webapp; + + public String getUpload() { + return upload; + } + + public void setUpload(String upload) { + this.upload = upload; + } + + public String getWebapp() { + return webapp; + } + + public void setWebapp(String webapp) { + this.webapp = webapp; + } +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml index 1bf49d9..e558e3a 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml @@ -4,19 +4,22 @@ <!-- 保存日志11 --> <insert id="saveLog" parameterType="Object"> - insert into sys_log (id, log_type, log_content, method, operate_type, request_param, ip, userid, username, cost_time, create_time) + insert into sys_log (id, log_type, log_content, method, operate_type, request_url, request_type, request_param, ip, userid, username, cost_time, create_time,create_by) values( #{dto.id,jdbcType=VARCHAR}, #{dto.logType,jdbcType=INTEGER}, #{dto.logContent,jdbcType=VARCHAR}, #{dto.method,jdbcType=VARCHAR}, #{dto.operateType,jdbcType=INTEGER}, + #{dto.requestUrl,jdbcType=VARCHAR}, + #{dto.requestType,jdbcType=VARCHAR}, #{dto.requestParam,jdbcType=VARCHAR}, #{dto.ip,jdbcType=VARCHAR}, #{dto.userid,jdbcType=VARCHAR}, #{dto.username,jdbcType=VARCHAR}, #{dto.costTime,jdbcType=BIGINT}, - #{dto.createTime,jdbcType=TIMESTAMP} + #{dto.createTime,jdbcType=TIMESTAMP}, + #{dto.createBy,jdbcType=VARCHAR} ) </insert> diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/service/impl/BaseCommonServiceImpl.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/service/impl/BaseCommonServiceImpl.java index 2c1919d..3bd057c 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/service/impl/BaseCommonServiceImpl.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/service/impl/BaseCommonServiceImpl.java @@ -34,6 +34,7 @@ public class BaseCommonServiceImpl implements BaseCommonService { } //保存日志(异常捕获处理,防止数据太大存储失败,导致业务失败)JT-238 try { + logDTO.setCreateTime(new Date()); baseCommonMapper.saveLog(logDTO); } catch (Exception e) { log.warn(" LogContent length : "+logDTO.getLogContent().length());