JeecgBoot3.5.0 版本发布,开源的企业级低代码平台

This commit is contained in:
zhangdaiscott
2023-03-05 11:29:47 +08:00
parent a3c0127a7a
commit b679fb75ba
180 changed files with 15263 additions and 57727 deletions

View File

@@ -2,14 +2,43 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jeecg-boot-parent</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.4</version>
<artifactId>jeecg-boot-parent</artifactId>
<version>3.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-boot-base-core</artifactId>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg-snapshots</id>
<name>jeecg-snapshots Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!--jeecg-tools-->
<dependency>
@@ -174,8 +203,18 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<artifactId>xercesImpl</artifactId>
<groupId>xerces</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
<optional>true</optional>
</dependency>
<!-- mini文件存储服务 -->
<dependency>
@@ -209,6 +248,10 @@
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -50,6 +50,11 @@ public class LogDTO implements Serializable {
/**操作人用户账户*/
private String userid;
/**
* 租户ID
*/
private Integer tenantId;
public LogDTO(){
}

View File

@@ -71,7 +71,12 @@ public class MessageDTO implements Serializable {
protected Map<String, Object> data;
//update-end---author:taoyan ---date::20220705 for支持自定义推送类型邮件、钉钉、企业微信、系统消息-----------
//-----------------------------------------------------------------------
/**
* 抄送人
*/
private String copyToUser;
public MessageDTO(){
}

View File

@@ -152,10 +152,11 @@ public interface CommonConstant {
Integer RULE_FLAG_1 = 1;
/**
* 是否用户已被冻结 1正常(解冻) 2冻结
* 是否用户已被冻结 1正常(解冻) 2冻结 3离职
*/
Integer USER_UNFREEZE = 1;
Integer USER_FREEZE = 2;
Integer USER_QUIT = 3;
/**字典翻译文本后缀*/
String DICT_TEXT_SUFFIX = "_dictText";
@@ -312,8 +313,8 @@ public interface CommonConstant {
String X_ACCESS_TOKEN = "X-Access-Token";
String X_SIGN = "X-Sign";
String X_TIMESTAMP = "X-TIMESTAMP";
/** 租户 请求头*/
String TENANT_ID = "tenant-id";
/** 租户请求头 更名为X-Tenant-Id */
String TENANT_ID = "X-Tenant-Id";
/**===============================================================================================*/
String TOKEN_IS_INVALID_MSG = "Token失效请重新登录!";
@@ -425,4 +426,68 @@ public interface CommonConstant {
*/
String PHONE_REDIS_KEY_PRE = "phone_msg";
/**
* 是文件夹
*/
String IT_IS_FOLDER = "1";
/**
* 文件拥有者
*/
String FILE_OWNER = "owner";
/**
* 文件管理员
*/
String FILE_ADMIN = "admin";
/**
* 只允许编辑
*/
String FILE_EDITABLE = "editable";
/**
* 登录失败用于记录失败次数的key
*/
String LOGIN_FAIL = "LOGIN_FAIL_";
/**
* 入职事件
*/
Integer BPM_USER_EVENT_ADD = 1;
/**
* 离职事件
*/
Integer BPM_USER_EVENT_LEVEL = 2;
/**
* 用户租户状态(正常/已通过审核的)
*/
String USER_TENANT_NORMAL = "1";
/**
* 用户租户状态(离职)
*/
String USER_TENANT_QUIT = "2";
/**
* 用户租户状态(审核中)
*/
String USER_TENANT_UNDER_REVIEW = "3";
/**
* 用户租户状态(拒绝)
*/
String USER_TENANT_REFUSE = "4";
/**
* 不是叶子节点
*/
Integer NOT_LEAF = 0;
/**
* 是叶子节点
*/
Integer IS_LEAF = 1;
}

View File

@@ -139,16 +139,6 @@ public interface DataBaseConstant {
public static final String BPM_STATUS_TABLE = "bpm_status";
//*********系统建表标准字段****************************************
/**
* 租户ID 实体字段名
*/
String TENANT_ID = "tenantId";
/**
* 租户ID 数据库字段名
*/
String TENANT_ID_TABLE = "tenant_id";
/**
* sql语句 where
*/

View File

@@ -33,6 +33,18 @@ public interface ServiceNameConstants {
* 微服务名: demo模块
*/
String SERVICE_DEMO = "jeecg-demo";
/**
* 微服务名online在线模块
*/
String SERVICE_ONLINE = "jeecg-online";
/**
* 微服务名OA模块
*/
String SERVICE_EOA = "jeecg-eoa";
/**
* 微服务名:表单设计模块
*/
String SERVICE_FORM = "jeecg-desform";
/**
* gateway通过header传递根路径 basePath

View File

@@ -6,17 +6,45 @@ package org.jeecg.common.constant;
* @date: 2022年08月29日 15:29
*/
public interface TenantConstant {
/*------【低代码应用参数】----------------------------------------------*/
/**
* 应用ID——表字段
* header的lowAppId标识
*/
String DB_FIELD_LOW_APP_ID = "low_app_id";
String X_LOW_APP_ID = "X-Low-App-ID";
/**
* 应用ID——实体字段
*/
String FIELD_LOW_APP_ID = "lowAppId";
/**
* 租户ID
* 应用ID——表字段
*/
String DB_FIELD_LOW_APP_ID = "low_app_id";
/*------【低代码应用参数】---------------------------------------------*/
/*--------【租户参数】-----------------------------------------------*/
/**
* 租户ID实体字段名 和 url参数名
*/
String TENANT_ID = "tenantId";
/**
* 租户ID 数据库字段名
*/
String TENANT_ID_TABLE = "tenant_id";
/*-------【租户参数】-----------------------------------------------*/
/**
* 超级管理员
*/
String SUPER_ADMIN = "superAdmin";
/**
* 组织账户管理员
*/
String ACCOUNT_ADMIN = "accountAdmin";
/**
* 组织应用管理员
*/
String APP_ADMIN = "appAdmin";
}

View File

@@ -21,5 +21,10 @@ public enum LowAppAopEnum {
/**
* Online表单专用数据库表转Online表单
*/
CGFORM_DB_IMPORT
CGFORM_DB_IMPORT,
/**
* 表单设计器专用:子表转工作表
*/
DESFORM_SUB2WORK
}

View File

@@ -63,11 +63,12 @@ public class SensitiveInfoUtil {
* @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;
}
long startTime=System.currentTimeMillis();
log.debug(" obj --> "+ obj.toString());
// 判断是不是一个对象
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {

View File

@@ -238,7 +238,7 @@ public class QueryGenerator {
if(parameterMap!=null&& parameterMap.containsKey(ORDER_TYPE)) {
order = parameterMap.get(ORDER_TYPE)[0];
}
log.info("排序规则>>列:" + column + ",排序方式:" + order);
log.debug("排序规则>>列:" + column + ",排序方式:" + order);
//update-begin-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
//TODO 避免用户自定义表无默认字段创建时间,导致排序报错

View File

@@ -31,10 +31,22 @@ public enum QueryRuleEnum {
RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
/**查询规则 带加号等于*/
EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"),
// ------- 当前表单设计器内专用 -------
/** 值为空 */
EMPTY("EMPTY","empty","值为空"),
/** 值不为空 */
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
/**查询规则 不包含*/
NOT_IN("NOT_IN","not_in","不包含"),
// ------- 当前表单设计器内专用 -------
/**查询规则 多词模糊匹配*/
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
/**查询规则 自定义SQL片段*/
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段");
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
/**查询规则 多词匹配*/
ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"),
/**查询规则 范围查询*/
RANGE("RANGE","range","范围查询");
private String value;

View File

@@ -21,6 +21,7 @@ import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.constant.TenantConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.system.vo.SysUserCacheInfo;
@@ -50,6 +51,7 @@ public class JwtUtil {
// issues/I4YH95浏览器显示乱码问题
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
Result jsonResult = new Result(code, errorMsg);
jsonResult.setSuccess(false);
OutputStream os = null;
try {
os = httpServletResponse.getOutputStream();
@@ -233,12 +235,8 @@ public class JwtUtil {
returnValue = "1";
}
//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
else if (key.equals(DataBaseConstant.TENANT_ID) || key.toLowerCase().equals(DataBaseConstant.TENANT_ID_TABLE)){
returnValue = sysUser.getRelTenantIds();
boolean flag = returnValue != null && returnValue.indexOf(SymbolConstant.COMMA) > 0;
if(oConvertUtils.isEmpty(returnValue) || flag){
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
}
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
}
//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
if(returnValue!=null){returnValue = returnValue + moshi;}

View File

@@ -121,7 +121,7 @@ public class LoginUser {
@SensitiveField
private String telephone;
/**多租户id配置,编辑用户的时候设置*/
/** 多租户ids临时用不持久化数据库(数据库字段不存在) */
private String relTenantIds;
/**设备id uniapp推送用*/

View File

@@ -1,5 +1,6 @@
package org.jeecg.common.util;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.mybatisplus.annotation.DbType;
@@ -351,4 +352,44 @@ public class CommonUtils {
log.debug("-----Common getBaseUrl----- : " + baseDomainPath);
return baseDomainPath;
}
/**
* 递归合并 fastJSON 对象
*
* @param target 目标对象
* @param sources 来源对象,允许多个,优先级从左到右,最右侧的优先级最高
*/
public static JSONObject mergeJSON(JSONObject target, JSONObject... sources) {
for (JSONObject source : sources) {
CommonUtils.mergeJSON(target, source);
}
return target;
}
/**
* 递归合并 fastJSON 对象
*
* @param target 目标对象
* @param source 来源对象
*/
public static JSONObject mergeJSON(JSONObject target, JSONObject source) {
for (String key : source.keySet()) {
Object sourceItem = source.get(key);
// 是否是 JSONObject
if (sourceItem instanceof Map) {
// target中存在此key
if (target.containsKey(key)) {
// 两个都是 JSONObject继续合并
if (target.get(key) instanceof Map) {
CommonUtils.mergeJSON(target.getJSONObject(key), source.getJSONObject(key));
continue;
}
}
}
// target不存在此key或不是 JSONObject则覆盖
target.put(key, sourceItem);
}
return target;
}
}

View File

@@ -1,5 +1,8 @@
package org.jeecg.common.util;
import org.jeecg.common.constant.SymbolConstant;
import org.springframework.util.StringUtils;
import java.beans.PropertyEditorSupport;
import java.sql.Timestamp;
import java.text.DateFormat;
@@ -9,9 +12,6 @@ import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.jeecg.common.constant.SymbolConstant;
import org.springframework.util.StringUtils;
/**
* 类描述:时间操作定义类
*
@@ -684,4 +684,80 @@ public class DateUtils extends PropertyEditorSupport {
return null;
}
/**
* 判断两个时间是否是同一天
*
* @param date1
* @param date2
* @return
*/
public static boolean isSameDay(Date date1, Date date2) {
if (date1 == null || date2 == null) {
return false;
}
Calendar calendar1 = Calendar.getInstance();
calendar1.setTime(date1);
Calendar calendar2 = Calendar.getInstance();
calendar2.setTime(date2);
boolean isSameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
boolean isSameMonth = isSameYear && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
return isSameMonth && calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH);
}
/**
* 判断两个时间是否是同一周
*
* @param date1
* @param date2
* @return
*/
public static boolean isSameWeek(Date date1, Date date2) {
if (date1 == null || date2 == null) {
return false;
}
Calendar calendar1 = Calendar.getInstance();
calendar1.setTime(date1);
Calendar calendar2 = Calendar.getInstance();
calendar2.setTime(date2);
boolean isSameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
return isSameYear && calendar1.get(Calendar.WEEK_OF_YEAR) == calendar2.get(Calendar.WEEK_OF_YEAR);
}
/**
* 判断两个时间是否是同一月
*
* @param date1
* @param date2
* @return
*/
public static boolean isSameMonth(Date date1, Date date2) {
if (date1 == null || date2 == null) {
return false;
}
Calendar calendar1 = Calendar.getInstance();
calendar1.setTime(date1);
Calendar calendar2 = Calendar.getInstance();
calendar2.setTime(date2);
boolean isSameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
return isSameYear && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
}
/**
* 判断两个时间是否是同一年
*
* @param date1
* @param date2
* @return
*/
public static boolean isSameYear(Date date1, Date date2) {
if (date1 == null || date2 == null) {
return false;
}
Calendar calendar1 = Calendar.getInstance();
calendar1.setTime(date1);
Calendar calendar2 = Calendar.getInstance();
calendar2.setTime(date2);
return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
}
}

View File

@@ -9,15 +9,15 @@ import org.apache.commons.lang3.StringUtils;
public enum DySmsEnum {
/**登录短信模板编码*/
LOGIN_TEMPLATE_CODE("SMS_175435174","JEECG","code"),
LOGIN_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
/**忘记密码短信模板编码*/
FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","JEECG","code"),
/**注册账号短信模板编码*/
REGISTER_TEMPLATE_CODE("SMS_175430166","JEECG","code"),
FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
/**注册账号短信模板编码*/
REGISTER_TEMPLATE_CODE("SMS_175430166","敲敲云","code"),
/**会议通知*/
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","H5活动之家","username,title,minute,time"),
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"),
/**我的计划通知*/
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","H5活动之家","username,title,time");
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time");
/**
* 短信模板编码

View File

@@ -3,7 +3,6 @@ package org.jeecg.common.util;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.config.JeecgBaseConfig;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
@@ -22,8 +21,9 @@ import java.util.Map;
public class RestUtil {
private static String domain = null;
public static String getDomain() {
private static String path = null;
private static String getDomain() {
if (domain == null) {
domain = SpringContextUtils.getDomain();
// issues/2959
@@ -37,9 +37,7 @@ public class RestUtil {
return domain;
}
public static String path = null;
public static String getPath() {
private static String getPath() {
if (path == null) {
path = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("server.servlet.context-path");
}
@@ -47,18 +45,7 @@ public class RestUtil {
}
public static String getBaseUrl() {
String basepath = null;
try {
basepath = getDomain() + getPath();
} catch (Exception e) {
log.warn(e.getMessage(),e);
}
//定时任务情况下通过request是获取不到domain的这种情况下通过配置获取pc后台域名
if(oConvertUtils.isEmpty(basepath)){
JeecgBaseConfig jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
basepath = jeecgBaseConfig.getDomainUrl().getPc();
}
String basepath = getDomain() + getPath();
log.info(" RestUtil.getBaseUrl: " + basepath);
return basepath;
}

View File

@@ -1,5 +1,8 @@
package org.jeecg.common.util;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.ServiceNameConstants;
import org.springframework.beans.BeansException;
@@ -9,9 +12,6 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description: spring上下文工具类
* @author: jeecg-boot

View File

@@ -3,7 +3,6 @@ package org.jeecg.common.util;
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;

View File

@@ -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.constant.TenantConstant;
import org.jeecg.common.desensitization.util.SensitiveInfoUtil;
import org.jeecg.common.exception.JeecgBoot401Exception;
import org.jeecg.common.system.util.JwtUtil;
@@ -34,6 +35,34 @@ public class TokenUtils {
return token;
}
/**
* 获取 request 里传递的 tenantId (租户ID)
*
* @param request
* @return
*/
public static String getTenantIdByRequest(HttpServletRequest request) {
String tenantId = request.getParameter(TenantConstant.TENANT_ID);
if (tenantId == null) {
tenantId = oConvertUtils.getString(request.getHeader(CommonConstant.TENANT_ID));
}
return tenantId;
}
/**
* 获取 request 里传递的 lowAppId (低代码应用ID)
*
* @param request
* @return
*/
public static String getLowAppIdByRequest(HttpServletRequest request) {
String lowAppId = request.getParameter(TenantConstant.FIELD_LOW_APP_ID);
if (lowAppId == null) {
lowAppId = oConvertUtils.getString(request.getHeader(TenantConstant.X_LOW_APP_ID));
}
return lowAppId;
}
/**
* 验证Token
*/

View File

@@ -7,7 +7,7 @@ import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileItemStream;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.util.CommonUtils;
@@ -20,6 +20,7 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Date;
import java.util.UUID;
@@ -141,10 +142,10 @@ public class OssBootUtil {
log.info("------OSS文件上传成功------" + fileUrl);
}
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage(),e);
return null;
}catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(),e);
return null;
}
return filePath;

View File

@@ -108,4 +108,12 @@ public class JeecgBaseConfig {
public void setFileViewDomain(String fileViewDomain) {
this.fileViewDomain = fileViewDomain;
}
public String getUploadType() {
return uploadType;
}
public void setUploadType(String uploadType) {
this.uploadType = uploadType;
}
}

View File

@@ -31,7 +31,7 @@ public class WebSocketConfig {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(websocketFilter());
//TODO 临时注释掉测试下线上socket总断的问题
bean.addUrlPatterns("/websocket/*","/eoaSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
bean.addUrlPatterns("/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
return bean;
}

View File

@@ -7,6 +7,8 @@ import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.TenantConstant;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.stereotype.Component;
@@ -82,6 +84,23 @@ public class MybatisInterceptor implements Interceptor {
}
}
}
//------------------------------------------------------------------------------------------------
//注入租户ID是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
if (TenantConstant.TENANT_ID.equals(field.getName())) {
field.setAccessible(true);
Object localTenantId = field.get(parameter);
field.setAccessible(false);
if (localTenantId == null) {
field.setAccessible(true);
field.set(parameter, oConvertUtils.getInt(TenantContext.getTenant(),0));
field.setAccessible(false);
}
}
}
//------------------------------------------------------------------------------------------------
} catch (Exception e) {
}
}

View File

@@ -5,8 +5,12 @@ import java.util.List;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.TenantConstant;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.common.util.oConvertUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
@@ -28,22 +32,41 @@ import net.sf.jsqlparser.expression.LongValue;
@Configuration
@MapperScan(value={"org.jeecg.modules.**.mapper*"})
public class MybatisPlusSaasConfig {
/**
* tenant_id 字段名
* 是否开启系统模块的租户隔离
* 控制范围:用户、角色、部门、我的部门、字典、分类字典、多数据源、职务、通知公告
*
* 实现功能
* 1.用户表通过硬编码实现租户ID隔离
* 2.角色、部门、我的部门、字典、分类字典、多数据源、职务、通知公告除了硬编码还加入的 TENANT_TABLE 配置中,实现租户隔离更安全
* 3.菜单表、租户表不做租户隔离
* 4.通过拦截器MybatisInterceptor实现增删改查数据 自动注入租户ID
*/
private static final String TENANT_FIELD_NAME = "tenant_id";
public static final Boolean OPEN_SYSTEM_TENANT_CONTROL = false;
/**
* 哪些表需要做多租户 表需要添加一个字段 tenant_id
*/
public static final List<String> TENANT_TABLE = new ArrayList<String>();
static {
TENANT_TABLE.add("demo");
//1.需要租户隔离的表请在此配置
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
//a.系统管理表
TENANT_TABLE.add("sys_role");
TENANT_TABLE.add("sys_user_role");
TENANT_TABLE.add("sys_depart");
TENANT_TABLE.add("sys_category");
TENANT_TABLE.add("sys_data_source");
TENANT_TABLE.add("sys_position");
TENANT_TABLE.add("sys_announcement");
}
// //角色、菜单、部门
// tenantTable.add("sys_role");
// tenantTable.add("sys_permission");
// tenantTable.add("sys_depart");
//2.示例测试
//TENANT_TABLE.add("demo");
//3.online租户隔离测试
//TENANT_TABLE.add("ceapp_issue");
}
@@ -54,13 +77,24 @@ public class MybatisPlusSaasConfig {
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
String tenantId = oConvertUtils.getString(TenantContext.getTenant(),"0");
String tenantId = TenantContext.getTenant();
//如果通过线程获取租户ID为空则通过当前请求的request获取租户shiro排除拦截器的请求会获取不到租户ID
if(oConvertUtils.isEmpty(tenantId)){
try {
tenantId = TokenUtils.getTenantIdByRequest(SpringContextUtils.getHttpServletRequest());
} catch (Exception e) {
//e.printStackTrace();
}
}
if(oConvertUtils.isEmpty(tenantId)){
tenantId = "0";
}
return new LongValue(tenantId);
}
@Override
public String getTenantIdColumn(){
return TENANT_FIELD_NAME;
return TenantConstant.TENANT_ID_TABLE;
}
// 返回 true 表示不走租户逻辑
@@ -74,10 +108,12 @@ public class MybatisPlusSaasConfig {
return true;
}
}));
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】注入动态表名适配拦截器解决多表名问题
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//【jeecg-boot/issues/3847】增加@Version乐观锁支持
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}

View File

@@ -111,19 +111,16 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/**/*.png", "anon");
filterChainDefinitionMap.put("/**/*.gif", "anon");
filterChainDefinitionMap.put("/**/*.ico", "anon");
// update-begin--Author:sunjianlei Date:20190813 for排除字体格式的后缀
filterChainDefinitionMap.put("/**/*.ttf", "anon");
filterChainDefinitionMap.put("/**/*.woff", "anon");
filterChainDefinitionMap.put("/**/*.woff2", "anon");
// update-begin--Author:sunjianlei Date:20190813 for排除字体格式的后缀
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger**/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
//积木报表排除
@@ -144,10 +141,8 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模块
filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable无痕刷新示例
//性能监控放开排除会存在安全漏洞泄露TOEKNdurid连接池也有
//性能监控——安全隐患泄露TOEKNdurid连接池也有
//filterChainDefinitionMap.put("/actuator/**", "anon");
//测试模块排除
filterChainDefinitionMap.put("/test/seata/**", "anon");

View File

@@ -140,12 +140,17 @@ public class ShiroRealm extends AuthorizingRealm {
String userTenantIds = loginUser.getRelTenantIds();
if(oConvertUtils.isNotEmpty(userTenantIds)){
String contextTenantId = TenantContext.getTenant();
log.debug("登录租户:" + contextTenantId);
log.debug("用户拥有那些租户:" + userTenantIds);
//登录用户无租户前端header中租户ID值为 0
String str ="0";
if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){
//update-begin-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
String[] arr = userTenantIds.split(",");
if(!oConvertUtils.isIn(contextTenantId, arr)){
throw new AuthenticationException("用户租户信息变更,请重新登陆!");
log.info("租户异常——登录租户:" + contextTenantId);
log.info("租户异常——用户拥有租户组:" + userTenantIds);
throw new AuthenticationException("登录租户授权变更,请重新登陆!");
}
//update-end-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
}

View File

@@ -10,6 +10,7 @@ import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper;
import org.jeecg.config.sign.util.HttpUtils;
import org.jeecg.config.sign.util.SignUtil;
@@ -39,6 +40,18 @@ public class SignAuthInterceptor implements HandlerInterceptor {
//对参数进行签名验证
String headerSign = request.getHeader(CommonConstant.X_SIGN);
String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP);
if(oConvertUtils.isEmpty(xTimestamp)){
Result<?> result = Result.error("Sign签名校验失败");
log.error("Sign 签名校验失败Header xTimestamp 为空");
//校验失败返回前端
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
out.print(JSON.toJSON(result));
return false;
}
//客户端时间
Long clientTimestamp = Long.parseLong(xTimestamp);

View File

@@ -4,7 +4,7 @@
<!-- 保存日志11 -->
<insert id="saveLog" parameterType="Object">
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)
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, tenant_id)
values(
#{dto.id,jdbcType=VARCHAR},
#{dto.logType,jdbcType=INTEGER},
@@ -19,7 +19,8 @@
#{dto.username,jdbcType=VARCHAR},
#{dto.costTime,jdbcType=BIGINT},
#{dto.createTime,jdbcType=TIMESTAMP},
#{dto.createBy,jdbcType=VARCHAR}
#{dto.createBy,jdbcType=VARCHAR},
#{dto.tenantId,jdbcType=INTEGER}
)
</insert>