mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-01-03 20:35:29 +08:00
JeecgBoot 3.3.0 版本发布,基于代码生成器的企业级低代码平台
This commit is contained in:
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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("?"));
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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";
|
||||
|
||||
/**.do*/
|
||||
String SPOT_DO = ".do";
|
||||
|
||||
|
||||
/**前端vue版本标识*/
|
||||
/**前端vue3版本Header参数名*/
|
||||
String VERSION="X-Version";
|
||||
|
||||
/**前端vue版本*/
|
||||
String VERSION_VUE3="vue3";
|
||||
/**存储在线程变量里的动态表名*/
|
||||
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";
|
||||
|
||||
/**
|
||||
* 发消息 会传递一些信息到map
|
||||
*/
|
||||
String NOTICE_MSG_SUMMARY = "NOTICE_MSG_SUMMARY";
|
||||
|
||||
/**
|
||||
* 发消息 会传递一个业务ID到map
|
||||
*/
|
||||
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
|
||||
|
||||
/**
|
||||
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
|
||||
*/
|
||||
String LOGIN_TOKEN = "{LOGIN_TOKEN}";
|
||||
|
||||
/**
|
||||
* 模板消息中 跳转地址的对应的key
|
||||
*/
|
||||
String MSG_HREF_URL = "url";
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = "]";
|
||||
|
||||
}
|
||||
@ -19,14 +19,15 @@ 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风格"),
|
||||
/**
|
||||
* 多表(内嵌子表风格)
|
||||
*/
|
||||
|
||||
@ -15,6 +15,8 @@ public enum LowAppAopEnum {
|
||||
* 删除方法(包含单个和批量删除)
|
||||
*/
|
||||
DELETE,
|
||||
/** 复制表单操作 */
|
||||
COPY,
|
||||
|
||||
/**
|
||||
* Online表单专用:数据库表转Online表单
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package org.jeecg.common.desensitization.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 解密注解
|
||||
*
|
||||
* 在方法上定义 将方法返回对象中的敏感字段 解密,需要注意的是,如果没有加密过,解密会出问题,返回原字符串
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface SensitiveDecode {
|
||||
|
||||
/**
|
||||
* 指明需要脱敏的实体类class
|
||||
* @return
|
||||
*/
|
||||
Class entity() default Object.class;
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package org.jeecg.common.desensitization.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 加密注解
|
||||
*
|
||||
* 在方法上声明 将方法返回对象中的敏感字段 加密/格式化
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface SensitiveEncode {
|
||||
|
||||
/**
|
||||
* 指明需要脱敏的实体类class
|
||||
* @return
|
||||
*/
|
||||
Class entity() default Object.class;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.jeecg.common.desensitization.annotation;
|
||||
|
||||
|
||||
import org.jeecg.common.desensitization.enums.SensitiveEnum;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 在字段上定义 标识字段存储的信息是敏感的
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface SensitiveField {
|
||||
|
||||
/**
|
||||
* 不同类型处理不同
|
||||
* @return
|
||||
*/
|
||||
SensitiveEnum type() default SensitiveEnum.ENCODE;
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package org.jeecg.common.desensitization.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.desensitization.annotation.SensitiveDecode;
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveEncode;
|
||||
import org.jeecg.common.desensitization.util.SensitiveInfoUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 敏感数据切面处理类
|
||||
* @Author taoYan
|
||||
* @Date 2022/4/20 17:45
|
||||
**/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class SensitiveDataAspect {
|
||||
|
||||
/**
|
||||
* 定义切点Pointcut
|
||||
*/
|
||||
@Pointcut("@annotation(org.jeecg.common.desensitization.annotation.SensitiveEncode) || @annotation(org.jeecg.common.desensitization.annotation.SensitiveDecode)")
|
||||
public void sensitivePointCut() {
|
||||
}
|
||||
|
||||
@Around("sensitivePointCut()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
// 处理结果
|
||||
Object result = point.proceed();
|
||||
if(result == null){
|
||||
return result;
|
||||
}
|
||||
Class resultClass = result.getClass();
|
||||
log.debug(" resultClass = {}" , resultClass);
|
||||
|
||||
if(resultClass.isPrimitive()){
|
||||
//是基本类型 直接返回 不需要处理
|
||||
return result;
|
||||
}
|
||||
// 获取方法注解信息:是哪个实体、是加密还是解密
|
||||
boolean isEncode = true;
|
||||
Class entity = null;
|
||||
MethodSignature methodSignature = (MethodSignature) point.getSignature();
|
||||
Method method = methodSignature.getMethod();
|
||||
SensitiveEncode encode = method.getAnnotation(SensitiveEncode.class);
|
||||
if(encode==null){
|
||||
SensitiveDecode decode = method.getAnnotation(SensitiveDecode.class);
|
||||
if(decode!=null){
|
||||
entity = decode.entity();
|
||||
isEncode = false;
|
||||
}
|
||||
}else{
|
||||
entity = encode.entity();
|
||||
}
|
||||
|
||||
long startTime=System.currentTimeMillis();
|
||||
if(resultClass.equals(entity) || entity.equals(Object.class)){
|
||||
// 方法返回实体和注解的entity一样,如果注解没有申明entity属性则认为是(方法返回实体和注解的entity一样)
|
||||
SensitiveInfoUtil.handlerObject(result, isEncode);
|
||||
} else if(result instanceof List){
|
||||
// 方法返回List<实体>
|
||||
SensitiveInfoUtil.handleList(result, entity, isEncode);
|
||||
}else{
|
||||
// 方法返回一个对象
|
||||
SensitiveInfoUtil.handleNestedObject(result, entity, isEncode);
|
||||
}
|
||||
long endTime=System.currentTimeMillis();
|
||||
log.info((isEncode ? "加密操作," : "解密操作,") + "Aspect程序耗时:" + (endTime - startTime) + "ms");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package org.jeecg.common.desensitization.enums;
|
||||
|
||||
/**
|
||||
* 敏感字段信息类型
|
||||
*/
|
||||
public enum SensitiveEnum {
|
||||
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*/
|
||||
ENCODE,
|
||||
|
||||
/**
|
||||
* 中文名
|
||||
*/
|
||||
CHINESE_NAME,
|
||||
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
ID_CARD,
|
||||
|
||||
/**
|
||||
* 座机号
|
||||
*/
|
||||
FIXED_PHONE,
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
MOBILE_PHONE,
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
ADDRESS,
|
||||
|
||||
/**
|
||||
* 电子邮件
|
||||
*/
|
||||
EMAIL,
|
||||
|
||||
/**
|
||||
* 银行卡
|
||||
*/
|
||||
BANK_CARD,
|
||||
|
||||
/**
|
||||
* 公司开户银行联号
|
||||
*/
|
||||
CNAPS_CODE;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,362 @@
|
||||
package org.jeecg.common.desensitization.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||
import org.jeecg.common.desensitization.enums.SensitiveEnum;
|
||||
import org.jeecg.common.util.encryption.AesEncryptUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 敏感信息处理工具类
|
||||
* @author taoYan
|
||||
* @date 2022/4/20 18:01
|
||||
**/
|
||||
@Slf4j
|
||||
public class SensitiveInfoUtil {
|
||||
|
||||
/**
|
||||
* 处理嵌套对象
|
||||
* @param obj 方法返回值
|
||||
* @param entity 实体class
|
||||
* @param isEncode 是否加密(true: 加密操作 / false:解密操作)
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
public static void handleNestedObject(Object obj, Class entity, boolean isEncode) throws IllegalAccessException {
|
||||
Field[] fields = obj.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if(field.getType().isPrimitive()){
|
||||
continue;
|
||||
}
|
||||
if(field.getType().equals(entity)){
|
||||
// 对象里面是实体
|
||||
field.setAccessible(true);
|
||||
Object nestedObject = field.get(obj);
|
||||
handlerObject(nestedObject, isEncode);
|
||||
break;
|
||||
}else{
|
||||
// 对象里面是List<实体>
|
||||
if(field.getGenericType() instanceof ParameterizedType){
|
||||
ParameterizedType pt = (ParameterizedType)field.getGenericType();
|
||||
if(pt.getRawType().equals(List.class)){
|
||||
if(pt.getActualTypeArguments()[0].equals(entity)){
|
||||
field.setAccessible(true);
|
||||
Object nestedObject = field.get(obj);
|
||||
handleList(nestedObject, entity, isEncode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Object
|
||||
* @param obj 方法返回值
|
||||
* @param isEncode 是否加密(true: 加密操作 / false:解密操作)
|
||||
* @return
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
public static Object handlerObject(Object obj, boolean isEncode) throws IllegalAccessException {
|
||||
log.debug(" obj --> "+ obj.toString());
|
||||
long startTime=System.currentTimeMillis();
|
||||
if (oConvertUtils.isEmpty(obj)) {
|
||||
return obj;
|
||||
}
|
||||
// 判断是不是一个对象
|
||||
Field[] fields = obj.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
boolean isSensitiveField = field.isAnnotationPresent(SensitiveField.class);
|
||||
if(isSensitiveField){
|
||||
// 必须有SensitiveField注解 才作处理
|
||||
if(field.getType().isAssignableFrom(String.class)){
|
||||
//必须是字符串类型 才作处理
|
||||
field.setAccessible(true);
|
||||
String realValue = (String) field.get(obj);
|
||||
if(realValue==null || "".equals(realValue)){
|
||||
continue;
|
||||
}
|
||||
SensitiveField sf = field.getAnnotation(SensitiveField.class);
|
||||
if(isEncode==true){
|
||||
//加密
|
||||
String value = SensitiveInfoUtil.getEncodeData(realValue, sf.type());
|
||||
field.set(obj, value);
|
||||
}else{
|
||||
//解密只处理 encode类型的
|
||||
if(sf.type().equals(SensitiveEnum.ENCODE)){
|
||||
String value = SensitiveInfoUtil.getDecodeData(realValue);
|
||||
field.set(obj, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//long endTime=System.currentTimeMillis();
|
||||
//log.info((isEncode ? "加密操作," : "解密操作,") + "当前程序耗时:" + (endTime - startTime) + "ms");
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 List<实体>
|
||||
* @param obj
|
||||
* @param entity
|
||||
* @param isEncode(true: 加密操作 / false:解密操作)
|
||||
*/
|
||||
public static void handleList(Object obj, Class entity, boolean isEncode){
|
||||
List list = (List)obj;
|
||||
if(list.size()>0){
|
||||
Object first = list.get(0);
|
||||
if(first.getClass().equals(entity)){
|
||||
for(int i=0; i<list.size(); i++){
|
||||
Object temp = list.get(i);
|
||||
try {
|
||||
handlerObject(temp, isEncode);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理数据 获取解密后的数据
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static String getDecodeData(String data){
|
||||
String result = null;
|
||||
try {
|
||||
result = AesEncryptUtil.desEncrypt(data);
|
||||
} catch (Exception exception) {
|
||||
log.warn("数据解密错误,原数据:"+data);
|
||||
}
|
||||
//解决debug模式下,加解密失效导致中文被解密变成空的问题
|
||||
if(oConvertUtils.isEmpty(result) && oConvertUtils.isNotEmpty(data)){
|
||||
result = data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据 获取加密后的数据 或是格式化后的数据
|
||||
* @param data 字符串
|
||||
* @param sensitiveEnum 类型
|
||||
* @return 处理后的字符串
|
||||
*/
|
||||
public static String getEncodeData(String data, SensitiveEnum sensitiveEnum){
|
||||
String result;
|
||||
switch (sensitiveEnum){
|
||||
case ENCODE:
|
||||
try {
|
||||
result = AesEncryptUtil.encrypt(data);
|
||||
} catch (Exception exception) {
|
||||
log.error("数据加密错误", exception.getMessage());
|
||||
result = data;
|
||||
}
|
||||
break;
|
||||
case CHINESE_NAME:
|
||||
result = chineseName(data);
|
||||
break;
|
||||
case ID_CARD:
|
||||
result = idCardNum(data);
|
||||
break;
|
||||
case FIXED_PHONE:
|
||||
result = fixedPhone(data);
|
||||
break;
|
||||
case MOBILE_PHONE:
|
||||
result = mobilePhone(data);
|
||||
break;
|
||||
case ADDRESS:
|
||||
result = address(data, 3);
|
||||
break;
|
||||
case EMAIL:
|
||||
result = email(data);
|
||||
break;
|
||||
case BANK_CARD:
|
||||
result = bankCard(data);
|
||||
break;
|
||||
case CNAPS_CODE:
|
||||
result = cnapsCode(data);
|
||||
break;
|
||||
default:
|
||||
result = data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [中文姓名] 只显示第一个汉字,其他隐藏为2个星号
|
||||
* @param fullName 全名
|
||||
* @return <例子:李**>
|
||||
*/
|
||||
private static String chineseName(String fullName) {
|
||||
if (oConvertUtils.isEmpty(fullName)) {
|
||||
return "";
|
||||
}
|
||||
return formatRight(fullName, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* [中文姓名] 只显示第一个汉字,其他隐藏为2个星号
|
||||
* @param familyName 姓
|
||||
* @param firstName 名
|
||||
* @return <例子:李**>
|
||||
*/
|
||||
private static String chineseName(String familyName, String firstName) {
|
||||
if (oConvertUtils.isEmpty(familyName) || oConvertUtils.isEmpty(firstName)) {
|
||||
return "";
|
||||
}
|
||||
return chineseName(familyName + firstName);
|
||||
}
|
||||
|
||||
/**
|
||||
* [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。
|
||||
* @param id 身份证号
|
||||
* @return <例子:*************5762>
|
||||
*/
|
||||
private static String idCardNum(String id) {
|
||||
if (oConvertUtils.isEmpty(id)) {
|
||||
return "";
|
||||
}
|
||||
return formatLeft(id, 4);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* [固定电话] 后四位,其他隐藏
|
||||
* @param num 固定电话
|
||||
* @return <例子:****1234>
|
||||
*/
|
||||
private static String fixedPhone(String num) {
|
||||
if (oConvertUtils.isEmpty(num)) {
|
||||
return "";
|
||||
}
|
||||
return formatLeft(num, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* [手机号码] 前三位,后四位,其他隐藏
|
||||
* @param num 手机号码
|
||||
* @return <例子:138******1234>
|
||||
*/
|
||||
private static String mobilePhone(String num) {
|
||||
if (oConvertUtils.isEmpty(num)) {
|
||||
return "";
|
||||
}
|
||||
int len = num.length();
|
||||
if(len<11){
|
||||
return num;
|
||||
}
|
||||
return formatBetween(num, 3, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护
|
||||
* @param address 地址
|
||||
* @param sensitiveSize 敏感信息长度
|
||||
* @return <例子:北京市海淀区****>
|
||||
*/
|
||||
private static String address(String address, int sensitiveSize) {
|
||||
if (oConvertUtils.isEmpty(address)) {
|
||||
return "";
|
||||
}
|
||||
int len = address.length();
|
||||
if(len<sensitiveSize){
|
||||
return address;
|
||||
}
|
||||
return formatRight(address, sensitiveSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示
|
||||
* @param email 电子邮箱
|
||||
* @return <例子:g**@163.com>
|
||||
*/
|
||||
private static String email(String email) {
|
||||
if (oConvertUtils.isEmpty(email)) {
|
||||
return "";
|
||||
}
|
||||
int index = email.indexOf("@");
|
||||
if (index <= 1){
|
||||
return email;
|
||||
}
|
||||
String begin = email.substring(0, 1);
|
||||
String end = email.substring(index);
|
||||
String stars = "**";
|
||||
return begin + stars + end;
|
||||
}
|
||||
|
||||
/**
|
||||
* [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号
|
||||
* @param cardNum 银行卡号
|
||||
* @return <例子:6222600**********1234>
|
||||
*/
|
||||
private static String bankCard(String cardNum) {
|
||||
if (oConvertUtils.isEmpty(cardNum)) {
|
||||
return "";
|
||||
}
|
||||
return formatBetween(cardNum, 6, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号
|
||||
* @param code 公司开户银行联号
|
||||
* @return <例子:12********>
|
||||
*/
|
||||
private static String cnapsCode(String code) {
|
||||
if (oConvertUtils.isEmpty(code)) {
|
||||
return "";
|
||||
}
|
||||
return formatRight(code, 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将右边的格式化成*
|
||||
* @param str 字符串
|
||||
* @param reservedLength 保留长度
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
private static String formatRight(String str, int reservedLength){
|
||||
String name = str.substring(0, reservedLength);
|
||||
String stars = String.join("", Collections.nCopies(str.length()-reservedLength, "*"));
|
||||
return name + stars;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将左边的格式化成*
|
||||
* @param str 字符串
|
||||
* @param reservedLength 保留长度
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
private static String formatLeft(String str, int reservedLength){
|
||||
int len = str.length();
|
||||
String show = str.substring(len-reservedLength);
|
||||
String stars = String.join("", Collections.nCopies(len-reservedLength, "*"));
|
||||
return stars + show;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将中间的格式化成*
|
||||
* @param str 字符串
|
||||
* @param beginLen 开始保留长度
|
||||
* @param endLen 结尾保留长度
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
private static String formatBetween(String str, int beginLen, int endLen){
|
||||
int len = str.length();
|
||||
String begin = str.substring(0, beginLen);
|
||||
String end = str.substring(len-endLen);
|
||||
String stars = String.join("", Collections.nCopies(len-beginLen-endLen, "*"));
|
||||
return begin + stars + end;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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 "";
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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 数据权限规则问题
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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配置,编辑用户的时候设置*/
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -214,7 +214,7 @@ public class RestUtil {
|
||||
}
|
||||
}
|
||||
// 拼接 url 参数
|
||||
if (variables != null) {
|
||||
if (variables != null && !variables.isEmpty()) {
|
||||
url += ("?" + asUrlVariables(variables));
|
||||
}
|
||||
// 发送请求
|
||||
|
||||
@ -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,19 +79,39 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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、解密报错 ---解码失败应该把异常抛出去,在外面处理
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -168,7 +168,7 @@ public abstract class AbstractQueryBlackListHandler {
|
||||
|
||||
public String getError(){
|
||||
// TODO
|
||||
return "sql黑名单校验不通过,请联系管理员!";
|
||||
return "系统设置了安全规则,敏感表和敏感字段禁止查询,联系管理员授权!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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/**");
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package org.jeecg.config;
|
||||
|
||||
import org.jeecg.config.filter.WebsocketFilter;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
@ -19,4 +21,17 @@ public class WebSocketConfig {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebsocketFilter websocketFilter(){
|
||||
return new WebsocketFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean getFilterRegistrationBean(){
|
||||
FilterRegistrationBean bean = new FilterRegistrationBean();
|
||||
bean.setFilter(websocketFilter());
|
||||
bean.addUrlPatterns("/websocket/*", "/eoaSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package org.jeecg.config.filter;
|
||||
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 针对post请求,将HttpServletRequest包一层 保留body里的参数
|
||||
* @Author taoYan
|
||||
* @Date 2022/4/25 19:19
|
||||
**/
|
||||
public class RequestBodyReserveFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
ServletRequest requestWrapper = null;
|
||||
|
||||
if(servletRequest instanceof HttpServletRequest) {
|
||||
HttpServletRequest req = (HttpServletRequest) servletRequest;
|
||||
// POST请求类型,才获取POST请求体
|
||||
if(CommonConstant.HTTP_POST.equals(req.getMethod())){
|
||||
requestWrapper = new BodyReaderHttpServletRequestWrapper(req);
|
||||
}
|
||||
}
|
||||
|
||||
if(requestWrapper == null) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
} else {
|
||||
filterChain.doFilter(requestWrapper, servletResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package org.jeecg.config.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* websocket 前端将token放到子协议里传入 与后端建立连接时需要用到http协议,此处用于校验token的有效性
|
||||
* @Author taoYan
|
||||
* @Date 2022/4/21 17:01
|
||||
**/
|
||||
@Slf4j
|
||||
public class WebsocketFilter implements Filter {
|
||||
|
||||
private static final String TOKEN_KEY = "Sec-WebSocket-Protocol";
|
||||
|
||||
private static CommonAPI commonApi;
|
||||
|
||||
private static RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
if (commonApi == null) {
|
||||
commonApi = SpringContextUtils.getBean(CommonAPI.class);
|
||||
}
|
||||
if (redisUtil == null) {
|
||||
redisUtil = SpringContextUtils.getBean(RedisUtil.class);
|
||||
}
|
||||
HttpServletRequest request = (HttpServletRequest)servletRequest;
|
||||
String token = request.getHeader(TOKEN_KEY);
|
||||
|
||||
log.info("websocket连接 Token安全校验,Path = {},token:{}", request.getRequestURI(), token);
|
||||
|
||||
try {
|
||||
TokenUtils.verifyToken(token, commonApi, redisUtil);
|
||||
} catch (Exception exception) {
|
||||
log.error("websocket连接校验失败,{},token:{}", exception.getMessage(), token);
|
||||
return;
|
||||
}
|
||||
HttpServletResponse response = (HttpServletResponse)servletResponse;
|
||||
response.setHeader(TOKEN_KEY, token);
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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");
|
||||
|
||||
@ -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("用户不存在!");
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 为空
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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} 缺少配置 !!");
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
Reference in New Issue
Block a user