Selaa lähdekoodia

升级jeecgboot版本到3.5.5

yangfeng 1 vuosi sitten
vanhempi
commit
704fd21f85
100 muutettua tiedostoa jossa 4291 lisäystä ja 968 poistoa
  1. 5 1
      core/pom.xml
  2. 5 0
      core/src/main/java/org/jeecg/common/api/dto/LogDTO.java
  3. 6 1
      core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java
  4. 2 3
      core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java
  5. 2 0
      core/src/main/java/org/jeecg/common/aspect/UrlMatchEnum.java
  6. 69 4
      core/src/main/java/org/jeecg/common/constant/CommonConstant.java
  7. 0 10
      core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java
  8. 12 0
      core/src/main/java/org/jeecg/common/constant/ServiceNameConstants.java
  9. 32 4
      core/src/main/java/org/jeecg/common/constant/TenantConstant.java
  10. 6 1
      core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java
  11. 3 2
      core/src/main/java/org/jeecg/common/desensitization/util/SensitiveInfoUtil.java
  12. 546 0
      core/src/main/java/org/jeecg/common/es/JeecgElasticsearchTemplate.java
  13. 98 0
      core/src/main/java/org/jeecg/common/es/QueryStringBuilder.java
  14. 21 2
      core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
  15. 23 0
      core/src/main/java/org/jeecg/common/exception/JeecgSqlInjectionException.java
  16. 7 5
      core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java
  17. 3 3
      core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java
  18. 1 1
      core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java
  19. 13 1
      core/src/main/java/org/jeecg/common/system/query/QueryRuleEnum.java
  20. 192 196
      core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
  21. 2 2
      core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java
  22. 7 7
      core/src/main/java/org/jeecg/common/system/vo/DynamicDataSourceModel.java
  23. 1 1
      core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
  24. 4 4
      core/src/main/java/org/jeecg/common/system/vo/SysCategoryModel.java
  25. 88 2
      core/src/main/java/org/jeecg/common/util/CommonUtils.java
  26. 80 4
      core/src/main/java/org/jeecg/common/util/DateUtils.java
  27. 6 6
      core/src/main/java/org/jeecg/common/util/DySmsEnum.java
  28. 2 0
      core/src/main/java/org/jeecg/common/util/DySmsHelper.java
  29. 3 3
      core/src/main/java/org/jeecg/common/util/MyClassLoader.java
  30. 1 1
      core/src/main/java/org/jeecg/common/util/PmsUtil.java
  31. 5 18
      core/src/main/java/org/jeecg/common/util/RestUtil.java
  32. 3 3
      core/src/main/java/org/jeecg/common/util/SpringContextUtils.java
  33. 96 16
      core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java
  34. 29 0
      core/src/main/java/org/jeecg/common/util/TokenUtils.java
  35. 5 5
      core/src/main/java/org/jeecg/common/util/dynamic/db/FreemarkerParseFactory.java
  36. 19 5
      core/src/main/java/org/jeecg/common/util/filter/FileTypeFilter.java
  37. 141 11
      core/src/main/java/org/jeecg/common/util/oConvertUtils.java
  38. 4 3
      core/src/main/java/org/jeecg/common/util/oss/OssBootUtil.java
  39. 74 16
      core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java
  40. 1 1
      core/src/main/java/org/jeecg/common/util/superSearch/ObjectParseUtil.java
  41. 1 1
      core/src/main/java/org/jeecg/common/util/superSearch/QueryRuleEnum.java
  42. 1 1
      core/src/main/java/org/jeecg/common/util/superSearch/QueryRuleVo.java
  43. 1 1
      core/src/main/java/org/jeecg/config/AutoPoiConfig.java
  44. 21 0
      core/src/main/java/org/jeecg/config/JeecgBaseConfig.java
  45. 3 3
      core/src/main/java/org/jeecg/config/StaticConfig.java
  46. 26 75
      core/src/main/java/org/jeecg/config/Swagger2Config.java
  47. 8 6
      core/src/main/java/org/jeecg/config/WebMvcConfiguration.java
  48. 1 1
      core/src/main/java/org/jeecg/config/WebSocketConfig.java
  49. 1 1
      core/src/main/java/org/jeecg/config/mybatis/JeecgTenantParser.java
  50. 19 0
      core/src/main/java/org/jeecg/config/mybatis/MybatisInterceptor.java
  51. 46 10
      core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java
  52. 1 1
      core/src/main/java/org/jeecg/config/mybatis/TenantContext.java
  53. 2 0
      core/src/main/java/org/jeecg/config/oss/MinioConfig.java
  54. 2 0
      core/src/main/java/org/jeecg/config/oss/OssConfiguration.java
  55. 7 8
      core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
  56. 25 1
      core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java
  57. 14 0
      core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java
  58. 23 9
      core/src/main/java/org/jeecg/config/sign/util/HttpUtils.java
  59. 26 0
      core/src/main/java/org/jeecg/config/vo/Elasticsearch.java
  60. 16 32
      pom.xml
  61. 1 1
      system/pom.xml
  62. 1 1
      system/system-api/pom.xml
  63. 1 1
      system/system-api/system-local-api/pom.xml
  64. 1 1
      system/system-biz/pom.xml
  65. 0 1
      system/system-biz/src/main/java/org/jeecg/config/init/CodeTemplateInitListener.java
  66. 27 0
      system/system-biz/src/main/java/org/jeecg/modules/aop/TenantLog.java
  67. 99 0
      system/system-biz/src/main/java/org/jeecg/modules/aop/TenantPackUserLogAspect.java
  68. 111 0
      system/system-biz/src/main/java/org/jeecg/modules/cas/controller/CasClientController.java
  69. 107 0
      system/system-biz/src/main/java/org/jeecg/modules/cas/util/CasServiceUtil.java
  70. 304 0
      system/system-biz/src/main/java/org/jeecg/modules/cas/util/XmlUtils.java
  71. 9 9
      system/system-biz/src/main/java/org/jeecg/modules/message/entity/SysMessage.java
  72. 5 5
      system/system-biz/src/main/java/org/jeecg/modules/message/entity/SysMessageTemplate.java
  73. 74 12
      system/system-biz/src/main/java/org/jeecg/modules/message/handle/impl/EmailSendMsgHandle.java
  74. 11 11
      system/system-biz/src/main/java/org/jeecg/modules/message/websocket/WebSocket.java
  75. 1 1
      system/system-biz/src/main/java/org/jeecg/modules/monitor/controller/ActuatorRedisController.java
  76. 1 1
      system/system-biz/src/main/java/org/jeecg/modules/ngalain/aop/LogRecordAspect.java
  77. 1 1
      system/system-biz/src/main/java/org/jeecg/modules/ngalain/controller/NgAlainController.java
  78. 2 0
      system/system-biz/src/main/java/org/jeecg/modules/oss/controller/OssFileController.java
  79. 5 0
      system/system-biz/src/main/java/org/jeecg/modules/oss/service/impl/OssFileServiceImpl.java
  80. 8 0
      system/system-biz/src/main/java/org/jeecg/modules/quartz/controller/QuartzJobController.java
  81. 9 9
      system/system-biz/src/main/java/org/jeecg/modules/quartz/entity/QuartzJob.java
  82. 161 163
      system/system-biz/src/main/java/org/jeecg/modules/quartz/service/impl/QuartzJobServiceImpl.java
  83. 13 37
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/DuplicateCheckController.java
  84. 87 26
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java
  85. 33 0
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysAnnouncementController.java
  86. 21 0
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java
  87. 3 3
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysCommentController.java
  88. 25 7
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDataSourceController.java
  89. 63 15
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartController.java
  90. 6 5
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java
  91. 105 28
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java
  92. 5 4
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java
  93. 2 1
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysGatewayRouteController.java
  94. 15 11
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysPermissionController.java
  95. 27 2
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysPositionController.java
  96. 87 22
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleController.java
  97. 4 0
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
  98. 627 11
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysTenantController.java
  99. 353 78
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java
  100. 15 9
      system/system-biz/src/main/java/org/jeecg/modules/system/controller/ThirdLoginController.java

+ 5 - 1
core/pom.xml

@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.ynfy</groupId>
     <artifactId>exam</artifactId>
-    <version>3.4.4</version>
+    <version>3.5.5</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.ynfy</groupId>
@@ -189,6 +189,10 @@
       <artifactId>jackson-module-kotlin</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>commons-fileupload</groupId>
+      <artifactId>commons-fileupload</artifactId>
+    </dependency>
     <!--国标SM3消息摘要算法和SM4对称加解密算法相关依赖-->
     <dependency>
       <groupId>org.bouncycastle</groupId>

+ 5 - 0
core/src/main/java/org/jeecg/common/api/dto/LogDTO.java

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

+ 6 - 1
core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java

@@ -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(){
     }

+ 2 - 3
core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java

@@ -59,8 +59,7 @@ public class PermissionDataAspect {
         requestPath = filterUrl(requestPath);
         //update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
         //先判断是否online报表请求
-        // TODO 参数顺序调整有隐患
-        if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0){
+        if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
             // 获取地址栏参数
             String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR);
             if(oConvertUtils.isNotEmpty(urlParamString)){
@@ -68,7 +67,7 @@ public class PermissionDataAspect {
             }
         }
         //update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
-        log.info("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
+        log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
         String username = JwtUtil.getUserNameByToken(request);
         //查询数据权限信息
         //TODO 微服务情况下也得支持缓存机制

+ 2 - 0
core/src/main/java/org/jeecg/common/aspect/UrlMatchEnum.java

@@ -14,6 +14,8 @@ public enum UrlMatchEnum {
     CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"),
     /**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */
     CGREPORT_DATA("/online/cgreport/api/getColumnsAndData/", "/online/cgreport/"),
+    /** 求URL与菜单路由URL转换规则/online/cgreport/api/getData/ 【vue3报表数据请求地址】 */
+    CGREPORT_ONLY_DATA("/online/cgreport/api/getData/", "/online/cgreport/"),
     /**求URL与菜单路由URL转换规则 /online/cgreport/api/exportXls/ */
     CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"),
     /**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */

+ 69 - 4
core/src/main/java/org/jeecg/common/constant/CommonConstant.java

@@ -78,7 +78,7 @@ public interface CommonConstant {
     /** 登录用户Shiro权限缓存KEY前缀 */
     public static String PREFIX_USER_SHIRO_CACHE  = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
     /** 登录用户Token令牌缓存KEY前缀 */
-    String PREFIX_USER_TOKEN  = "prefix_user_token_";
+    String PREFIX_USER_TOKEN  = "prefix_user_token:";
 //    /** Token缓存时间:3600秒即一小时 */
 //    int  TOKEN_EXPIRE_TIME  = 3600;
 
@@ -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;
 }

+ 0 - 10
core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java

@@ -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
      */

+ 12 - 0
core/src/main/java/org/jeecg/common/constant/ServiceNameConstants.java

@@ -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

+ 32 - 4
core/src/main/java/org/jeecg/common/constant/TenantConstant.java

@@ -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";
+
 }

+ 6 - 1
core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java

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

+ 3 - 2
core/src/main/java/org/jeecg/common/desensitization/util/SensitiveInfoUtil.java

@@ -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) {

+ 546 - 0
core/src/main/java/org/jeecg/common/es/JeecgElasticsearchTemplate.java

@@ -0,0 +1,546 @@
+package org.jeecg.common.es;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.common.util.RestUtil;
+import org.jeecg.common.util.oConvertUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * 关于 ElasticSearch 的一些方法(创建索引、添加数据、查询等)
+ *
+ * @author sunjianlei
+ */
+@Slf4j
+@Component
+@ConditionalOnProperty(prefix = "jeecg.elasticsearch", name = "cluster-nodes")
+public class JeecgElasticsearchTemplate {
+    /** es服务地址 */
+    private String baseUrl;
+    private final String FORMAT_JSON = "format=json";
+    /** Elasticsearch 的版本号 */
+    private String version = null;
+
+    /**ElasticSearch 最大可返回条目数*/
+    public static final int ES_MAX_SIZE = 10000;
+
+    /**es7*/
+    public static final String IE_SEVEN = "7";
+
+    /**url not found 404*/
+    public static final String URL_NOT_FOUND = "404 Not Found";
+
+    public JeecgElasticsearchTemplate(@Value("${jeecg.elasticsearch.cluster-nodes}") String baseUrl, @Value("${jeecg.elasticsearch.check-enabled}") boolean checkEnabled) {
+        log.debug("JeecgElasticsearchTemplate BaseURL:" + baseUrl);
+        if (StringUtils.isNotEmpty(baseUrl)) {
+            this.baseUrl = baseUrl;
+            // 验证配置的ES地址是否有效
+            if (checkEnabled) {
+                try {
+                    this.getElasticsearchVersion();
+                    log.info("ElasticSearch 服务连接成功");
+                    log.info("ElasticSearch version: " + this.version);
+                } catch (Exception e) {
+                    this.version = "";
+                    log.warn("ElasticSearch 服务连接失败,原因:配置未通过。可能是BaseURL未配置或配置有误,也可能是Elasticsearch服务未启动。接下来将会拒绝执行任何方法!");
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取 Elasticsearch 的版本号信息,失败返回null
+     */
+    private void getElasticsearchVersion() {
+        if (this.version == null) {
+            String url = this.getBaseUrl().toString();
+            JSONObject result = RestUtil.get(url);
+            if (result != null) {
+                JSONObject v = result.getJSONObject("version");
+                this.version = v.getString("number");
+            }
+        }
+    }
+
+    public StringBuilder getBaseUrl(String indexName, String typeName) {
+        typeName = typeName.trim().toLowerCase();
+        return this.getBaseUrl(indexName).append("/").append(typeName);
+    }
+
+    public StringBuilder getBaseUrl(String indexName) {
+        indexName = indexName.trim().toLowerCase();
+        return this.getBaseUrl().append("/").append(indexName);
+    }
+
+    public StringBuilder getBaseUrl() {
+        return new StringBuilder("http://").append(this.baseUrl);
+    }
+
+    /**
+     * cat 查询ElasticSearch系统数据,返回json
+     */
+    private <T> ResponseEntity<T> cat(String urlAfter, Class<T> responseType) {
+        String url = this.getBaseUrl().append("/_cat").append(urlAfter).append("?").append(FORMAT_JSON).toString();
+        return RestUtil.request(url, HttpMethod.GET, null, null, null, responseType);
+    }
+
+    /**
+     * 查询所有索引
+     * <p>
+     * 查询地址:GET http://{baseUrl}/_cat/indices
+     */
+    public JSONArray getIndices() {
+        return getIndices(null);
+    }
+
+
+    /**
+     * 查询单个索引
+     * <p>
+     * 查询地址:GET http://{baseUrl}/_cat/indices/{indexName}
+     */
+    public JSONArray getIndices(String indexName) {
+        StringBuilder urlAfter = new StringBuilder("/indices");
+        if (!StringUtils.isEmpty(indexName)) {
+            urlAfter.append("/").append(indexName.trim().toLowerCase());
+        }
+        return cat(urlAfter.toString(), JSONArray.class).getBody();
+    }
+
+    /**
+     * 索引是否存在
+     */
+    public boolean indexExists(String indexName) {
+        try {
+            JSONArray array = getIndices(indexName);
+            return array != null;
+        } catch (org.springframework.web.client.HttpClientErrorException ex) {
+            if (HttpStatus.NOT_FOUND == ex.getStatusCode()) {
+                return false;
+            } else {
+                throw ex;
+            }
+        }
+    }
+
+    /**
+     * 根据ID获取索引数据,未查询到返回null
+     * <p>
+     * 查询地址:GET http://{baseUrl}/{indexName}/{typeName}/{dataId}
+     *
+     * @param indexName 索引名称
+     * @param typeName  type,一个任意字符串,用于分类
+     * @param dataId    数据id
+     * @return
+     */
+    public JSONObject getDataById(String indexName, String typeName, String dataId) {
+        String url = this.getBaseUrl(indexName, typeName).append("/").append(dataId).toString();
+        log.info("url:" + url);
+        JSONObject result = RestUtil.get(url);
+        boolean found = result.getBoolean("found");
+        if (found) {
+            return result.getJSONObject("_source");
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 创建索引
+     * <p>
+     * 查询地址:PUT http://{baseUrl}/{indexName}
+     */
+    public boolean createIndex(String indexName) {
+        String url = this.getBaseUrl(indexName).toString();
+
+        /* 返回结果 (仅供参考)
+        "createIndex": {
+            "shards_acknowledged": true,
+            "acknowledged": true,
+            "index": "hello_world"
+        }
+        */
+        try {
+            return RestUtil.put(url).getBoolean("acknowledged");
+        } catch (org.springframework.web.client.HttpClientErrorException ex) {
+            if (HttpStatus.BAD_REQUEST == ex.getStatusCode()) {
+                log.warn("索引创建失败:" + indexName + " 已存在,无需再创建");
+            } else {
+                ex.printStackTrace();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 删除索引
+     * <p>
+     * 查询地址:DELETE http://{baseUrl}/{indexName}
+     */
+    public boolean removeIndex(String indexName) {
+        String url = this.getBaseUrl(indexName).toString();
+        try {
+            return RestUtil.delete(url).getBoolean("acknowledged");
+        } catch (org.springframework.web.client.HttpClientErrorException ex) {
+            if (HttpStatus.NOT_FOUND == ex.getStatusCode()) {
+                log.warn("索引删除失败:" + indexName + " 不存在,无需删除");
+            } else {
+                ex.printStackTrace();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取索引字段映射(可获取字段类型)
+     * <p>
+     *
+     * @param indexName 索引名称
+     * @param typeName  分类名称
+     * @return
+     */
+    public JSONObject getIndexMapping(String indexName, String typeName) {
+        String url = this.getBaseUrl(indexName, typeName).append("/_mapping?").append(FORMAT_JSON).toString();
+        // 针对 es 7.x 版本做兼容
+        this.getElasticsearchVersion();
+        if (oConvertUtils.isNotEmpty(this.version) && this.version.startsWith(IE_SEVEN)) {
+            url += "&include_type_name=true";
+        }
+        log.info("getIndexMapping-url:" + url);
+        /*
+         * 参考返回JSON结构:
+         *
+         *{
+         *    // 索引名称
+         *    "[indexName]": {
+         *        "mappings": {
+         *            // 分类名称
+         *            "[typeName]": {
+         *                "properties": {
+         *                    // 字段名
+         *                    "input_number": {
+         *                        // 字段类型
+         *                        "type": "long"
+         *                    },
+         *                    "input_string": {
+         *                        "type": "text",
+         *                        "fields": {
+         *                            "keyword": {
+         *                                "type": "keyword",
+         *                                "ignore_above": 256
+         *                            }
+         *                        }
+         *                    }
+         *                 }
+         *            }
+         *        }
+         *    }
+         * }
+         */
+        try {
+            return RestUtil.get(url);
+        } catch (org.springframework.web.client.HttpClientErrorException e) {
+            String message = e.getMessage();
+            if (message != null && message.contains(URL_NOT_FOUND)) {
+                return null;
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * 获取索引字段映射,返回Java实体类
+     *
+     * @param indexName
+     * @param typeName
+     * @return
+     */
+    public <T> Map<String, T> getIndexMappingFormat(String indexName, String typeName, Class<T> clazz) {
+        JSONObject mapping = this.getIndexMapping(indexName, typeName);
+        Map<String, T> map = new HashMap<>(5);
+        if (mapping == null) {
+            return map;
+        }
+        // 获取字段属性
+        JSONObject properties = mapping.getJSONObject(indexName)
+                .getJSONObject("mappings")
+                .getJSONObject(typeName)
+                .getJSONObject("properties");
+        // 封装成 java类型
+        for (String key : properties.keySet()) {
+            T entity = properties.getJSONObject(key).toJavaObject(clazz);
+            map.put(key, entity);
+        }
+        return map;
+    }
+
+    /**
+     * 保存数据,详见:saveOrUpdate
+     */
+    public boolean save(String indexName, String typeName, String dataId, JSONObject data) {
+        return this.saveOrUpdate(indexName, typeName, dataId, data);
+    }
+
+    /**
+     * 更新数据,详见:saveOrUpdate
+     */
+    public boolean update(String indexName, String typeName, String dataId, JSONObject data) {
+        return this.saveOrUpdate(indexName, typeName, dataId, data);
+    }
+
+    /**
+     * 保存或修改索引数据
+     * <p>
+     * 查询地址:PUT http://{baseUrl}/{indexName}/{typeName}/{dataId}
+     *
+     * @param indexName 索引名称
+     * @param typeName  type,一个任意字符串,用于分类
+     * @param dataId    数据id
+     * @param data      要存储的数据
+     * @return
+     */
+    public boolean saveOrUpdate(String indexName, String typeName, String dataId, JSONObject data) {
+        String url = this.getBaseUrl(indexName, typeName).append("/").append(dataId).append("?refresh=wait_for").toString();
+        /* 返回结果(仅供参考)
+       "createIndexA2": {
+            "result": "created",
+            "_shards": {
+                "total": 2,
+                "successful": 1,
+                "failed": 0
+            },
+            "_seq_no": 0,
+            "_index": "test_index_1",
+            "_type": "test_type_1",
+            "_id": "a2",
+            "_version": 1,
+            "_primary_term": 1
+        }
+         */
+
+        try {
+            // 去掉 data 中为空的值
+            Set<String> keys = data.keySet();
+            List<String> emptyKeys = new ArrayList<>(keys.size());
+            for (String key : keys) {
+                String value = data.getString(key);
+                //1、剔除空值
+                if (oConvertUtils.isEmpty(value) || "[]".equals(value)) {
+                    emptyKeys.add(key);
+                }
+                //2、剔除上传控件值(会导致ES同步失败,报异常failed to parse field [ge_pic] of type [text] )
+                if (oConvertUtils.isNotEmpty(value) && value.indexOf("[{")!=-1) {
+                    emptyKeys.add(key);
+                    log.info("-------剔除上传控件字段------------key: "+ key);
+                }
+            }
+            for (String key : emptyKeys) {
+                data.remove(key);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        try {
+            String result = RestUtil.put(url, data).getString("result");
+            return "created".equals(result) || "updated".equals(result);
+        } catch (Exception e) {
+            log.error(e.getMessage() + "\n-- url: " + url + "\n-- data: " + data.toJSONString());
+            //TODO 打印接口返回异常json
+            return false;
+        }
+    }
+
+    /**
+     * 批量保存数据
+     *
+     * @param indexName 索引名称
+     * @param typeName  type,一个任意字符串,用于分类
+     * @param dataList  要存储的数据数组,每行数据必须包含id
+     * @return
+     */
+    public boolean saveBatch(String indexName, String typeName, JSONArray dataList) {
+        String url = this.getBaseUrl().append("/_bulk").append("?refresh=wait_for").toString();
+        StringBuilder bodySb = new StringBuilder();
+        for (int i = 0; i < dataList.size(); i++) {
+            JSONObject data = dataList.getJSONObject(i);
+            String id = data.getString("id");
+            // 该行的操作
+            // {"create": {"_id":"${id}", "_index": "${indexName}", "_type": "${typeName}"}}
+            JSONObject action = new JSONObject();
+            JSONObject actionInfo = new JSONObject();
+            actionInfo.put("_id", id);
+            actionInfo.put("_index", indexName);
+            actionInfo.put("_type", typeName);
+            action.put("create", actionInfo);
+            bodySb.append(action.toJSONString()).append("\n");
+            // 该行的数据
+            data.remove("id");
+            bodySb.append(data.toJSONString()).append("\n");
+        }
+        System.out.println("+-+-+-: bodySb.toString(): " + bodySb.toString());
+        HttpHeaders headers = RestUtil.getHeaderApplicationJson();
+        RestUtil.request(url, HttpMethod.PUT, headers, null, bodySb, JSONObject.class);
+        return true;
+    }
+
+    /**
+     * 删除索引数据
+     * <p>
+     * 请求地址:DELETE http://{baseUrl}/{indexName}/{typeName}/{dataId}
+     */
+    public boolean delete(String indexName, String typeName, String dataId) {
+        String url = this.getBaseUrl(indexName, typeName).append("/").append(dataId).toString();
+        /* 返回结果(仅供参考)
+        {
+            "_index": "es_demo",
+            "_type": "docs",
+            "_id": "001",
+            "_version": 3,
+            "result": "deleted",
+            "_shards": {
+                "total": 1,
+                "successful": 1,
+                "failed": 0
+            },
+            "_seq_no": 28,
+            "_primary_term": 18
+        }
+        */
+        try {
+            return "deleted".equals(RestUtil.delete(url).getString("result"));
+        } catch (org.springframework.web.client.HttpClientErrorException ex) {
+            if (HttpStatus.NOT_FOUND == ex.getStatusCode()) {
+                return false;
+            } else {
+                throw ex;
+            }
+        }
+    }
+
+
+    /* = = = 以下关于查询和查询条件的方法 = = =*/
+
+    /**
+     * 查询数据
+     * <p>
+     * 请求地址:POST http://{baseUrl}/{indexName}/{typeName}/_search
+     */
+    public JSONObject search(String indexName, String typeName, JSONObject queryObject) {
+        String url = this.getBaseUrl(indexName, typeName).append("/_search").toString();
+
+        log.info("url:" + url + " ,search: " + queryObject.toJSONString());
+        JSONObject res = RestUtil.post(url, queryObject);
+        log.info("url:" + url + " ,return res: \n" + res.toJSONString());
+        return res;
+    }
+
+    /**
+     * @param source (源滤波器)指定返回的字段,传null返回所有字段
+     * @param query
+     * @param from    从第几条数据开始
+     * @param size    返回条目数
+     * @return { "query": query }
+     */
+    public JSONObject buildQuery(List<String> source, JSONObject query, int from, int size) {
+        JSONObject json = new JSONObject();
+        if (source != null) {
+            json.put("_source", source);
+        }
+        json.put("query", query);
+        json.put("from", from);
+        json.put("size", size);
+        return json;
+    }
+
+    /**
+     * @return { "bool" : { "must": must, "must_not": mustNot, "should": should } }
+     */
+    public JSONObject buildBoolQuery(JSONArray must, JSONArray mustNot, JSONArray should) {
+        JSONObject bool = new JSONObject();
+        if (must != null) {
+            bool.put("must", must);
+        }
+        if (mustNot != null) {
+            bool.put("must_not", mustNot);
+        }
+        if (should != null) {
+            bool.put("should", should);
+        }
+        JSONObject json = new JSONObject();
+        json.put("bool", bool);
+        return json;
+    }
+
+    /**
+     * @param field 要查询的字段
+     * @param args  查询参数,参考: *哈哈* OR *哒* NOT *呵* OR *啊*
+     * @return
+     */
+    public JSONObject buildQueryString(String field, String... args) {
+        if (field == null) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder(field).append(":(");
+        if (args != null) {
+            for (String arg : args) {
+                sb.append(arg).append(" ");
+            }
+        }
+        sb.append(")");
+        return this.buildQueryString(sb.toString());
+    }
+
+    /**
+     * @return { "query_string": { "query": query }  }
+     */
+    public JSONObject buildQueryString(String query) {
+        JSONObject queryString = new JSONObject();
+        queryString.put("query", query);
+        JSONObject json = new JSONObject();
+        json.put("query_string", queryString);
+        return json;
+    }
+
+    /**
+     * @param field      查询字段
+     * @param min        最小值
+     * @param max        最大值
+     * @param containMin 范围内是否包含最小值
+     * @param containMax 范围内是否包含最大值
+     * @return { "range" : { field : { 『 "gt『e』?containMin" : min 』?min!=null , 『 "lt『e』?containMax" : max 』}} }
+     */
+    public JSONObject buildRangeQuery(String field, Object min, Object max, boolean containMin, boolean containMax) {
+        JSONObject inner = new JSONObject();
+        if (min != null) {
+            if (containMin) {
+                inner.put("gte", min);
+            } else {
+                inner.put("gt", min);
+            }
+        }
+        if (max != null) {
+            if (containMax) {
+                inner.put("lte", max);
+            } else {
+                inner.put("lt", max);
+            }
+        }
+        JSONObject range = new JSONObject();
+        range.put(field, inner);
+        JSONObject json = new JSONObject();
+        json.put("range", range);
+        return json;
+    }
+
+}
+

+ 98 - 0
core/src/main/java/org/jeecg/common/es/QueryStringBuilder.java

@@ -0,0 +1,98 @@
+package org.jeecg.common.es;
+
+/**
+ * 用于创建 ElasticSearch 的 queryString
+ *
+ * @author sunjianlei
+ */
+public class QueryStringBuilder {
+
+    StringBuilder builder;
+
+    public QueryStringBuilder(String field, String str, boolean not, boolean addQuot) {
+        builder = this.createBuilder(field, str, not, addQuot);
+    }
+
+    public QueryStringBuilder(String field, String str, boolean not) {
+        builder = this.createBuilder(field, str, not, true);
+    }
+
+    /**
+     * 创建 StringBuilder
+     *
+     * @param field
+     * @param str
+     * @param not     是否是不匹配
+     * @param addQuot 是否添加双引号
+     * @return
+     */
+    public StringBuilder createBuilder(String field, String str, boolean not, boolean addQuot) {
+        StringBuilder sb = new StringBuilder(field).append(":(");
+        if (not) {
+            sb.append(" NOT ");
+        }
+        this.addQuotEffect(sb, str, addQuot);
+        return sb;
+    }
+
+    public QueryStringBuilder and(String str) {
+        return this.and(str, true);
+    }
+
+    public QueryStringBuilder and(String str, boolean addQuot) {
+        builder.append(" AND ");
+        this.addQuot(str, addQuot);
+        return this;
+    }
+
+    public QueryStringBuilder or(String str) {
+        return this.or(str, true);
+    }
+
+    public QueryStringBuilder or(String str, boolean addQuot) {
+        builder.append(" OR ");
+        this.addQuot(str, addQuot);
+        return this;
+    }
+
+    public QueryStringBuilder not(String str) {
+        return this.not(str, true);
+    }
+
+    public QueryStringBuilder not(String str, boolean addQuot) {
+        builder.append(" NOT ");
+        this.addQuot(str, addQuot);
+        return this;
+    }
+
+    /**
+    * 添加双引号(模糊查询,不能加双引号)
+    */
+    private QueryStringBuilder addQuot(String str, boolean addQuot) {
+        return this.addQuotEffect(this.builder, str, addQuot);
+    }
+
+    /**
+     * 是否在两边加上双引号
+     * @param builder
+     * @param str
+     * @param addQuot
+     * @return
+     */
+    private QueryStringBuilder addQuotEffect(StringBuilder builder, String str, boolean addQuot) {
+        if (addQuot) {
+            builder.append('"');
+        }
+        builder.append(str);
+        if (addQuot) {
+            builder.append('"');
+        }
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return builder.append(")").toString();
+    }
+
+}

+ 21 - 2
core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java

@@ -1,6 +1,7 @@
 package org.jeecg.common.exception;
 
 import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.authz.UnauthorizedException;
 import org.jeecg.common.api.vo.Result;
@@ -16,8 +17,6 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.multipart.MaxUploadSizeExceededException;
 import org.springframework.web.servlet.NoHandlerFoundException;
 
-import lombok.extern.slf4j.Slf4j;
-
 /**
  * 异常处理器
  * 
@@ -133,4 +132,24 @@ public class JeecgBootExceptionHandler {
         return Result.error("Redis 连接异常!");
     }
 
+
+	/**
+	 * SQL注入风险,全局异常处理
+	 *
+	 * @param exception
+	 * @return
+	 */
+	@ExceptionHandler(JeecgSqlInjectionException.class)
+	public Result<?> handleSQLException(Exception exception) {
+		String msg = exception.getMessage().toLowerCase();
+		final String extractvalue = "extractvalue";
+		final String updatexml = "updatexml";
+		boolean hasSensitiveInformation = msg.indexOf(extractvalue) >= 0 || msg.indexOf(updatexml) >= 0;
+		if (msg != null && hasSensitiveInformation) {
+			log.error("校验失败,存在SQL注入风险!{}", msg);
+			return Result.error("校验失败,存在SQL注入风险!");
+		}
+		return Result.error("校验失败,存在SQL注入风险!" + msg);
+	}
+
 }

+ 23 - 0
core/src/main/java/org/jeecg/common/exception/JeecgSqlInjectionException.java

@@ -0,0 +1,23 @@
+package org.jeecg.common.exception;
+
+/**
+ * @Description: jeecg-boot自定义SQL注入异常
+ * @author: jeecg-boot
+ */
+public class JeecgSqlInjectionException extends RuntimeException {
+	private static final long serialVersionUID = 1L;
+
+	public JeecgSqlInjectionException(String message){
+		super(message);
+	}
+	
+	public JeecgSqlInjectionException(Throwable cause)
+	{
+		super(cause);
+	}
+	
+	public JeecgSqlInjectionException(String message, Throwable cause)
+	{
+		super(message,cause);
+	}
+}

+ 7 - 5
core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java

@@ -11,6 +11,7 @@ import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.system.vo.LoginUser;
 import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.JeecgBaseConfig;
 import org.jeecgframework.poi.excel.ExcelImportUtil;
 import org.jeecgframework.poi.excel.def.NormalExcelConstants;
 import org.jeecgframework.poi.excel.entity.ExportParams;
@@ -23,6 +24,7 @@ import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartHttpServletRequest;
 import org.springframework.web.servlet.ModelAndView;
 
+import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
@@ -40,9 +42,9 @@ public class JeecgController<T, S extends IService<T>> {
     /**issues/2933 JeecgController注入service时改用protected修饰,能避免重复引用service*/
     @Autowired
     protected S service;
-
-    @Value("${jeecg.path.upload}")
-    private String upLoadPath;
+    @Resource
+    private JeecgBaseConfig jeecgBaseConfig;
+    
     /**
      * 导出excel
      *
@@ -69,7 +71,7 @@ public class JeecgController<T, S extends IService<T>> {
         mv.addObject(NormalExcelConstants.CLASS, clazz);
         //update-begin--Author:liusq  Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
         ExportParams  exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
-        exportParams.setImageBasePath(upLoadPath);
+        exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
         //update-end--Author:liusq  Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
         mv.addObject(NormalExcelConstants.PARAMS,exportParams);
         mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
@@ -108,7 +110,7 @@ public class JeecgController<T, S extends IService<T>> {
             IPage<T> pageList = service.page(page, queryWrapper);
             List<T> exportList = pageList.getRecords();
             Map<String, Object> map = new HashMap<>(5);
-            ExportParams  exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,upLoadPath);
+            ExportParams  exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
             exportParams.setType(ExcelType.XSSF);
             //map.put("title",exportParams);
             //表格Title

+ 3 - 3
core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java

@@ -31,14 +31,14 @@ public class JeecgEntity implements Serializable {
      */
     @TableId(type = IdType.ASSIGN_ID)
     @ApiModelProperty(value = "ID")
-    private java.lang.String id;
+    private String id;
 
     /**
      * 创建人
      */
     @ApiModelProperty(value = "创建人")
     @Excel(name = "创建人", width = 15)
-    private java.lang.String createBy;
+    private String createBy;
 
     /**
      * 创建时间
@@ -54,7 +54,7 @@ public class JeecgEntity implements Serializable {
      */
     @ApiModelProperty(value = "更新人")
     @Excel(name = "更新人", width = 15)
-    private java.lang.String updateBy;
+    private String updateBy;
 
     /**
      * 更新时间

+ 1 - 1
core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java

@@ -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 避免用户自定义表无默认字段创建时间,导致排序报错

+ 13 - 1
core/src/main/java/org/jeecg/common/system/query/QueryRuleEnum.java

@@ -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;
     

+ 192 - 196
core/src/main/java/org/jeecg/common/system/util/JwtUtil.java

@@ -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;
@@ -35,27 +36,27 @@ import org.jeecg.common.util.oConvertUtils;
  **/
 public class JwtUtil {
 
-    /**
-     * Token有效期(Token在reids中缓存时间为两倍)
-     */
-    public static final long EXPIRE_TIME = 60 * 60 * 24 * 7 * 1000;
-    static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
+	/**Token有效期为7天(Token在reids中缓存时间为两倍)*/
+	public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
+	static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
 
     /**
+     *
      * @param response
      * @param code
      * @param errorMsg
      */
     public static void responseError(ServletResponse response, Integer code, String errorMsg) {
-        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
-        // issues/I4YH95浏览器显示乱码问题
-        httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
+		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+		// 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();
-            httpServletResponse.setCharacterEncoding("UTF-8");
-            httpServletResponse.setStatus(code);
+			httpServletResponse.setCharacterEncoding("UTF-8");
+			httpServletResponse.setStatus(code);
             os.write(new ObjectMapper().writeValueAsString(jsonResult).getBytes("UTF-8"));
             os.flush();
             os.close();
@@ -64,191 +65,186 @@ public class JwtUtil {
         }
     }
 
-    /**
-     * 校验token是否正确
-     *
-     * @param token  密钥
-     * @param secret 用户的密码
-     * @return 是否正确
-     */
-    public static boolean verify(String token, String username, String secret) {
-        try {
-            // 根据密码生成JWT效验器
-            Algorithm algorithm = Algorithm.HMAC256(secret);
-            JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
-            // 效验TOKEN
-            DecodedJWT jwt = verifier.verify(token);
-            return true;
-        } catch (Exception exception) {
-            return false;
-        }
-    }
-
-    /**
-     * 获得token中的信息无需secret解密也能获得
-     *
-     * @return token中包含的用户名
-     */
-    public static String getUsername(String token) {
-        try {
-            DecodedJWT jwt = JWT.decode(token);
-            return jwt.getClaim("username").asString();
-        } catch (JWTDecodeException e) {
-            return null;
-        }
-    }
-
-    /**
-     * 生成签名,5min后过期
-     *
-     * @param username 用户名
-     * @param secret   用户的密码
-     * @return 加密的token
-     */
-    public static String sign(String username, String secret) {
-        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
-        Algorithm algorithm = Algorithm.HMAC256(secret);
-        // 附带username信息
-        return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
-
-    }
-
-    /**
-     * 根据request中的token获取用户账号
-     *
-     * @param request
-     * @return
-     * @throws JeecgBootException
-     */
-    public static String getUserNameByToken(HttpServletRequest request) throws JeecgBootException {
-        String accessToken = request.getHeader("X-Access-Token");
-        String username = getUsername(accessToken);
-        if (oConvertUtils.isEmpty(username)) {
-            throw new JeecgBootException("未获取到用户");
-        }
-        return username;
-    }
-
-    /**
-     * 从session中获取变量
-     *
-     * @param key
-     * @return
-     */
-    public static String getSessionData(String key) {
-        //${myVar}%
-        //得到${} 后面的值
-        String moshi = "";
+	/**
+	 * 校验token是否正确
+	 *
+	 * @param token  密钥
+	 * @param secret 用户的密码
+	 * @return 是否正确
+	 */
+	public static boolean verify(String token, String username, String secret) {
+		try {
+			// 根据密码生成JWT效验器
+			Algorithm algorithm = Algorithm.HMAC256(secret);
+			JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
+			// 效验TOKEN
+			DecodedJWT jwt = verifier.verify(token);
+			return true;
+		} catch (Exception exception) {
+			return false;
+		}
+	}
+
+	/**
+	 * 获得token中的信息无需secret解密也能获得
+	 *
+	 * @return token中包含的用户名
+	 */
+	public static String getUsername(String token) {
+		try {
+			DecodedJWT jwt = JWT.decode(token);
+			return jwt.getClaim("username").asString();
+		} catch (JWTDecodeException e) {
+			return null;
+		}
+	}
+
+	/**
+	 * 生成签名,5min后过期
+	 *
+	 * @param username 用户名
+	 * @param secret   用户的密码
+	 * @return 加密的token
+	 */
+	public static String sign(String username, String secret) {
+		Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
+		Algorithm algorithm = Algorithm.HMAC256(secret);
+		// 附带username信息
+		return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
+
+	}
+
+	/**
+	 * 根据request中的token获取用户账号
+	 * 
+	 * @param request
+	 * @return
+	 * @throws JeecgBootException
+	 */
+	public static String getUserNameByToken(HttpServletRequest request) throws JeecgBootException {
+		String accessToken = request.getHeader("X-Access-Token");
+		String username = getUsername(accessToken);
+		if (oConvertUtils.isEmpty(username)) {
+			throw new JeecgBootException("未获取到用户");
+		}
+		return username;
+	}
+	
+	/**
+	  *  从session中获取变量
+	 * @param key
+	 * @return
+	 */
+	public static String getSessionData(String key) {
+		//${myVar}%
+		//得到${} 后面的值
+		String moshi = "";
+		String wellNumber = WELL_NUMBER;
+
+		if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
+			 moshi = key.substring(key.indexOf("}")+1);
+		}
+		String returnValue = null;
+		if (key.contains(wellNumber)) {
+			key = key.substring(2,key.indexOf("}"));
+		}
+		if (oConvertUtils.isNotEmpty(key)) {
+			HttpSession session = SpringContextUtils.getHttpServletRequest().getSession();
+			returnValue = (String) session.getAttribute(key);
+		}
+		//结果加上${} 后面的值
+		if(returnValue!=null){returnValue = returnValue + moshi;}
+		return returnValue;
+	}
+	
+	/**
+	  * 从当前用户中获取变量
+	 * @param key
+	 * @param user
+	 * @return
+	 */
+	public static String getUserSystemData(String key,SysUserCacheInfo user) {
+		if(user==null) {
+			user = JeecgDataAutorUtils.loadUserInfo();
+		}
+		//#{sys_user_code}%
+		
+		// 获取登录用户信息
+		LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+		
+		String moshi = "";
         String wellNumber = WELL_NUMBER;
-
-        if (key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET) != -1) {
-            moshi = key.substring(key.indexOf("}") + 1);
-        }
-        String returnValue = null;
-        if (key.contains(wellNumber)) {
-            key = key.substring(2, key.indexOf("}"));
-        }
-        if (oConvertUtils.isNotEmpty(key)) {
-            HttpSession session = SpringContextUtils.getHttpServletRequest().getSession();
-            returnValue = (String) session.getAttribute(key);
-        }
-        //结果加上${} 后面的值
-        if (returnValue != null) {
-            returnValue = returnValue + moshi;
-        }
-        return returnValue;
-    }
-
-    /**
-     * 从当前用户中获取变量
-     *
-     * @param key
-     * @param user
-     * @return
-     */
-    public static String getUserSystemData(String key, SysUserCacheInfo user) {
-        if (user == null) {
-            user = JeecgDataAutorUtils.loadUserInfo();
-        }
-        //#{sys_user_code}%
-
-        // 获取登录用户信息
-        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
-
-        String moshi = "";
-        String wellNumber = WELL_NUMBER;
-        if (key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET) != -1) {
-            moshi = key.substring(key.indexOf("}") + 1);
-        }
-        String returnValue = null;
-        //针对特殊标示处理#{sysOrgCode},判断替换
-        if (key.contains(wellNumber)) {
-            key = key.substring(2, key.indexOf("}"));
-        } else {
-            key = key;
-        }
-        //替换为系统登录用户帐号
-        if (key.equals(DataBaseConstant.SYS_USER_CODE) || key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
-            if (user == null) {
-                returnValue = sysUser.getUsername();
-            } else {
-                returnValue = user.getSysUserCode();
-            }
-        }
-        //替换为系统登录用户真实名字
-        else if (key.equals(DataBaseConstant.SYS_USER_NAME) || key.toLowerCase().equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
-            if (user == null) {
-                returnValue = sysUser.getRealname();
-            } else {
-                returnValue = user.getSysUserName();
-            }
-        }
-
-        //替换为系统用户登录所使用的机构编码
-        else if (key.equals(DataBaseConstant.SYS_ORG_CODE) || key.toLowerCase().equals(DataBaseConstant.SYS_ORG_CODE_TABLE)) {
-            if (user == null) {
-                returnValue = sysUser.getOrgCode();
-            } else {
-                returnValue = user.getSysOrgCode();
-            }
-        }
-        //替换为系统用户所拥有的所有机构编码
-        else if (key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE) || key.toLowerCase().equals(DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE)) {
-            if (user == null) {
-                //TODO 暂时使用用户登录部门,存在逻辑缺陷,不是用户所拥有的部门
-                returnValue = sysUser.getOrgCode();
-            } else {
-                if (user.isOneDepart()) {
-                    returnValue = user.getSysMultiOrgCode().get(0);
-                } else {
-                    returnValue = Joiner.on(",").join(user.getSysMultiOrgCode());
-                }
-            }
-        }
-        //替换为当前系统时间(年月日)
-        else if (key.equals(DataBaseConstant.SYS_DATE) || key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
-            returnValue = DateUtils.formatDate();
-        }
-        //替换为当前系统时间(年月日时分秒)
-        else if (key.equals(DataBaseConstant.SYS_TIME) || key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
-            returnValue = DateUtils.now();
-        }
-        //流程状态默认值(默认未发起)
-        else if (key.equals(DataBaseConstant.BPM_STATUS) || key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
-            returnValue = "1";
-        }
-        //update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
-        else if (key.equals(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);
-            }
-        }
-        //update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
-        if (returnValue != null) {
-            returnValue = returnValue + moshi;
-        }
-        return returnValue;
-    }
+		if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
+			 moshi = key.substring(key.indexOf("}")+1);
+		}
+		String returnValue = null;
+		//针对特殊标示处理#{sysOrgCode},判断替换
+		if (key.contains(wellNumber)) {
+			key = key.substring(2,key.indexOf("}"));
+		} else {
+			key = key;
+		}
+		//替换为系统登录用户帐号
+		if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
+			if(user==null) {
+				returnValue = sysUser.getUsername();
+			}else {
+				returnValue = user.getSysUserCode();
+			}
+		}
+		//替换为系统登录用户真实名字
+		else if (key.equals(DataBaseConstant.SYS_USER_NAME)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
+			if(user==null) {
+				returnValue = sysUser.getRealname();
+			}else {
+				returnValue = user.getSysUserName();
+			}
+		}
+		
+		//替换为系统用户登录所使用的机构编码
+		else if (key.equals(DataBaseConstant.SYS_ORG_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_ORG_CODE_TABLE)) {
+			if(user==null) {
+				returnValue = sysUser.getOrgCode();
+			}else {
+				returnValue = user.getSysOrgCode();
+			}
+		}
+		//替换为系统用户所拥有的所有机构编码
+		else if (key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE)) {
+			if(user==null){
+				//TODO 暂时使用用户登录部门,存在逻辑缺陷,不是用户所拥有的部门
+				returnValue = sysUser.getOrgCode();
+			}else{
+				if(user.isOneDepart()) {
+					returnValue = user.getSysMultiOrgCode().get(0);
+				}else {
+					returnValue = Joiner.on(",").join(user.getSysMultiOrgCode());
+				}
+			}
+		}
+		//替换为当前系统时间(年月日)
+		else if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
+			returnValue = DateUtils.formatDate();
+		}
+		//替换为当前系统时间(年月日时分秒)
+		else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
+			returnValue = DateUtils.now();
+		}
+		//流程状态默认值(默认未发起)
+		else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
+			returnValue = "1";
+		}
+		//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
+		else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
+			returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
+		}
+		//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
+		if(returnValue!=null){returnValue = returnValue + moshi;}
+		return returnValue;
+	}
+	
+//	public static void main(String[] args) {
+//		 String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjUzMzY1MTMsInVzZXJuYW1lIjoiYWRtaW4ifQ.xjhud_tWCNYBOg_aRlMgOdlZoWFFKB_givNElHNw3X0";
+//		 System.out.println(JwtUtil.getUsername(token));
+//	}
 }

+ 2 - 2
core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java

@@ -40,7 +40,7 @@ public class ResourceUtil {
      * 所有枚举java类
      */
 
-    private final static String CLASS_ENMU_PATTERN="/**/*Enum.class";
+    private final static String CLASS_ENUM_PATTERN="/**/*Enum.class";
 
     /**
      * 包路径 org.jeecg
@@ -61,7 +61,7 @@ public class ResourceUtil {
             return enumDictData;
         }
         ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
-        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_ENMU_PATTERN;
+        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_ENUM_PATTERN;
         try {
             Resource[] resources = resourcePatternResolver.getResources(pattern);
             MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

+ 7 - 7
core/src/main/java/org/jeecg/common/system/vo/DynamicDataSourceModel.java

@@ -23,23 +23,23 @@ public class DynamicDataSourceModel {
     /**
      * id
      */
-    private java.lang.String id;
+    private String id;
     /**
      * 数据源编码
      */
-    private java.lang.String code;
+    private String code;
     /**
      * 数据库类型
      */
-    private java.lang.String dbType;
+    private String dbType;
     /**
      * 驱动类
      */
-    private java.lang.String dbDriver;
+    private String dbDriver;
     /**
      * 数据源地址
      */
-    private java.lang.String dbUrl;
+    private String dbUrl;
 
 //    /**
 //     * 数据库名称
@@ -49,10 +49,10 @@ public class DynamicDataSourceModel {
     /**
      * 用户名
      */
-    private java.lang.String dbUsername;
+    private String dbUsername;
     /**
      * 密码
      */
-    private java.lang.String dbPassword;
+    private String dbPassword;
 
 }

+ 1 - 1
core/src/main/java/org/jeecg/common/system/vo/LoginUser.java

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

+ 4 - 4
core/src/main/java/org/jeecg/common/system/vo/SysCategoryModel.java

@@ -10,13 +10,13 @@ import org.jeecgframework.poi.excel.annotation.Excel;
  */
 public class SysCategoryModel {
     /**主键*/
-    private java.lang.String id;
+    private String id;
     /**父级节点*/
-    private java.lang.String pid;
+    private String pid;
     /**类型名称*/
-    private java.lang.String name;
+    private String name;
     /**类型编码*/
-    private java.lang.String code;
+    private String code;
 
     public String getId() {
         return id;

+ 88 - 2
core/src/main/java/org/jeecg/common/util/CommonUtils.java

@@ -1,10 +1,12 @@
 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;
 import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.constant.DataBaseConstant;
 import org.jeecg.common.constant.ServiceNameConstants;
@@ -25,6 +27,7 @@ import java.io.InputStream;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -144,7 +147,7 @@ public class CommonUtils {
      * @param bizPath  自定义路径
      * @return
      */
-    public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
+    public static String uploadLocal(MultipartFile mf, String bizPath, String uploadpath){
         try {
             //update-begin-author:liusq date:20210809 for: 过滤上传文件类型
             FileTypeFilter.fileTypeFilter(mf);
@@ -271,7 +274,7 @@ public class CommonUtils {
         if(db==null){
             return null;
         }
-        DriverManagerDataSource ds = new DriverManagerDataSource ();
+        DriverManagerDataSource ds = new DriverManagerDataSource();
         ds.setDriverClassName(db.getDriverClassName());
         ds.setUrl(db.getUrl());
         ds.setUsername(db.getUsername());
@@ -351,4 +354,87 @@ 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;
+    }
+
+    /**
+     * 将list集合以分割符的方式进行分割
+     * @param list      String类型的集合文本
+     * @param separator 分隔符
+     * @return
+     */
+    public static String getSplitText(List<String> list, String separator) {
+        if (null != list && list.size() > 0) {
+            return StringUtils.join(list, separator);
+        }
+        return "";
+    }
+ 
+    /**
+     * 通过table的条件SQL
+     *
+     * @param tableSql sys_user where name = '1212'
+     * @return name = '1212'
+     */
+    public static String getFilterSqlByTableSql(String tableSql) {
+        if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
+            String[] arr = tableSql.split(" (?i)where ");
+            if (arr != null && oConvertUtils.isNotEmpty(arr[1])) {
+                return arr[1];
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 通过table获取表名
+     *
+     * @param tableSql sys_user where name = '1212'
+     * @return sys_user
+     */
+    public static String getTableNameByTableSql(String tableSql) {
+        if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
+            String[] arr = tableSql.split(" (?i)where ");
+            return arr[0].trim();
+        } else {
+            return tableSql;
+        }
+    }
 }

+ 80 - 4
core/src/main/java/org/jeecg/common/util/DateUtils.java

@@ -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;
-
 /**
  * 类描述:时间操作定义类
  *
@@ -302,7 +302,7 @@ public class DateUtils extends PropertyEditorSupport {
         Date dt = new Date();
         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         String nowTime = df.format(dt);
-        java.sql.Timestamp buydate = java.sql.Timestamp.valueOf(nowTime);
+        Timestamp buydate = Timestamp.valueOf(nowTime);
         return buydate;
     }
 
@@ -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);
+    }
+
 }

+ 6 - 6
core/src/main/java/org/jeecg/common/util/DySmsEnum.java

@@ -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");
 
 	/**
 	 * 短信模板编码

+ 2 - 0
core/src/main/java/org/jeecg/common/util/DySmsHelper.java

@@ -62,6 +62,8 @@ public class DySmsHelper {
 
         //update-begin-author:taoyan date:20200811 for:配置类数据获取
         StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
+        logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
+        logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
         setAccessKeyId(staticConfig.getAccessKeyId());
         setAccessKeySecret(staticConfig.getAccessKeySecret());
         //update-end-author:taoyan date:20200811 for:配置类数据获取

+ 3 - 3
core/src/main/java/org/jeecg/common/util/MyClassLoader.java

@@ -25,7 +25,7 @@ public class MyClassLoader extends ClassLoader {
 	public static String getPackPath(Object object) {
 		// 检查用户传入的参数是否为空
 		if (object == null) {
-			throw new java.lang.IllegalArgumentException("参数不能为空!");
+			throw new IllegalArgumentException("参数不能为空!");
 		}
 		// 获得类的全名,包括包名
 		String clsName = object.getClass().getName();
@@ -35,7 +35,7 @@ public class MyClassLoader extends ClassLoader {
 	public static String getAppPath(Class cls) {
 		// 检查用户传入的参数是否为空
 		if (cls == null) {
-			throw new java.lang.IllegalArgumentException("参数不能为空!");
+			throw new IllegalArgumentException("参数不能为空!");
 		}
 		ClassLoader loader = cls.getClassLoader();
 		// 获得类的全名,包括包名
@@ -50,7 +50,7 @@ public class MyClassLoader extends ClassLoader {
 			String javaxSpot="javax.";
 			// 此处简单判定是否是Java基础类库,防止用户传入JDK内置的类库
 			if (packName.startsWith(javaSpot) || packName.startsWith(javaxSpot)) {
-				throw new java.lang.IllegalArgumentException("不要传送系统类!");
+				throw new IllegalArgumentException("不要传送系统类!");
 			}
 			// 在类的名称中,去掉包名的部分,获得类的文件名
 			clsName = clsName.substring(packName.length() + 1);

+ 1 - 1
core/src/main/java/org/jeecg/common/util/PmsUtil.java

@@ -21,7 +21,7 @@ public class PmsUtil {
 
     private static String uploadPath;
 
-    @Value("${jeecg.path.upload}")
+    @Value("${jeecg.path.upload:}")
     public void setUploadPath(String uploadPath) {
         PmsUtil.uploadPath = uploadPath;
     }

+ 5 - 18
core/src/main/java/org/jeecg/common/util/RestUtil.java

@@ -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;
     }

+ 3 - 3
core/src/main/java/org/jeecg/common/util/SpringContextUtils.java

@@ -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

+ 96 - 16
core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java

@@ -2,7 +2,9 @@ package org.jeecg.common.util;
 
 import cn.hutool.crypto.SecureUtil;
 import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.constant.SymbolConstant;
 import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.common.exception.JeecgSqlInjectionException;
 
 import javax.servlet.http.HttpServletRequest;
 import java.lang.reflect.Field;
@@ -31,6 +33,11 @@ public class SqlInjectionUtil {
     /**正则 show tables*/
 	private final static String SHOW_TABLES = "show\\s+tables";
 
+	/**
+	 * sleep函数
+	 */
+	private final static Pattern FUN_SLEEP = Pattern.compile("sleep\\(.*\\)", Pattern.CASE_INSENSITIVE);
+
 	/**
 	 * sql注释的正则
 	 */
@@ -43,7 +50,7 @@ public class SqlInjectionUtil {
 	 * @param request:
 	 * @Return: void
 	 */
-	public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
+	private static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
 		//表字典SQL注入漏洞,签名校验
 		String accessToken = request.getHeader("X-Access-Token");
 		String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
@@ -56,11 +63,76 @@ public class SqlInjectionUtil {
 	}
 
 	/**
+	 * 返回查询表名
+	 * <p>
 	 * sql注入过滤处理,遇到注入关键字抛异常
-	 * @param value
+	 *
+	 * @param table
+	 */
+	private static Pattern tableNamePattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{0,63}$");
+	public static String getSqlInjectTableName(String table) {
+		table = table.trim();
+		/**
+		 * 检验表名是否合法
+		 *
+		 * 表名只能由字母、数字和下划线组成。
+		 * 表名必须以字母开头。
+		 * 表名长度通常有限制,例如最多为 64 个字符。
+		 */
+		boolean isValidTableName = tableNamePattern.matcher(table).matches();
+		if (!isValidTableName) {
+			String errorMsg = "表名不合法,存在SQL注入风险!--->" + table;
+			log.error(errorMsg);
+			throw new JeecgSqlInjectionException(errorMsg);
+		}
+
+		//进一步验证是否存在SQL注入风险
+		filterContent(table);
+		return table;
+	}
+
+
+	/**
+	 * 返回查询字段
+	 * <p>
+	 * sql注入过滤处理,遇到注入关键字抛异常
+	 *
+	 * @param field
 	 */
-	public static void filterContent(String value) {
-		filterContent(value, null);
+	static final Pattern fieldPattern = Pattern.compile("^[a-zA-Z0-9_]+$");
+	public static String getSqlInjectField(String field) {
+		if(oConvertUtils.isEmpty(field)){
+			return null;
+		}
+		
+		field = field.trim();
+
+		if (field.contains(SymbolConstant.COMMA)) {
+			return getSqlInjectField(field.split(SymbolConstant.COMMA));
+		}
+		
+		/**
+		 * 校验表字段是否有效
+		 *
+		 * 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
+		 */
+		boolean isValidField = fieldPattern.matcher(field).matches();
+		if (!isValidField) {
+			String errorMsg = "字段不合法,存在SQL注入风险!--->" + field;
+			log.error(errorMsg);
+			throw new JeecgSqlInjectionException(errorMsg);
+		}
+
+		//进一步验证是否存在SQL注入风险
+		filterContent(field);
+		return field;
+	}
+
+	public static String getSqlInjectField(String... fields) {
+		for (String s : fields) {
+			getSqlInjectField(s);
+		}
+		return String.join(SymbolConstant.COMMA, fields);
 	}
 
 	/**
@@ -85,7 +157,7 @@ public class SqlInjectionUtil {
 			if (value.indexOf(xssArr[i]) > -1) {
 				log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
 				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
-				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+				throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 			}
 		}
 		//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
@@ -95,13 +167,13 @@ public class SqlInjectionUtil {
 				if (value.indexOf(xssArr2[i]) > -1) {
 					log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
 					log.error("请注意,值可能存在SQL注入风险!---> {}", value);
-					throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+					throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 				}
 			}
 		}
 		//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
 		if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
-			throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 		}
 		return;
 	}
@@ -110,7 +182,7 @@ public class SqlInjectionUtil {
 	 * sql注入过滤处理,遇到注入关键字抛异常
 	 * @param values
 	 */
-	public static void filterContent(String[] values) {
+	public static void filterContent(String... values) {
 		filterContent(values, null);
 	}
 
@@ -137,7 +209,7 @@ public class SqlInjectionUtil {
 				if (value.indexOf(xssArr[i]) > -1) {
 					log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
 					log.error("请注意,值可能存在SQL注入风险!---> {}", value);
-					throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+					throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 				}
 			}
 			//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
@@ -147,13 +219,13 @@ public class SqlInjectionUtil {
 					if (value.indexOf(xssArr2[i]) > -1) {
 						log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
 						log.error("请注意,值可能存在SQL注入风险!---> {}", value);
-						throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+						throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 					}
 				}
 			}
 			//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
 			if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
-				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+				throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 			}
 		}
 		return;
@@ -184,11 +256,11 @@ public class SqlInjectionUtil {
 			if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
 				log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
 				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
-				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+				throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 			}
 		}
 		if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
-			throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 		}
 		return;
 	}
@@ -218,12 +290,12 @@ public class SqlInjectionUtil {
 			if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
 				log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
 				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
-				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+				throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 			}
 		}
 
 		if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
-			throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			throw new JeecgSqlInjectionException("请注意,值可能存在SQL注入风险!--->" + value);
 		}
 		return;
 	}
@@ -281,7 +353,15 @@ public class SqlInjectionUtil {
 		if(matcher.find()){
 			String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
 			log.error(error);
-			throw new RuntimeException(error);
+			throw new JeecgSqlInjectionException(error);
+		}
+		
+		// issues/4737 sys/duplicate/check SQL注入 #4737
+		Matcher sleepMatcher = FUN_SLEEP.matcher(str);
+		if(sleepMatcher.find()){
+			String error = "请注意,值可能存在SQL注入风险---> sleep";
+			log.error(error);
+			throw new JeecgSqlInjectionException(error);
 		}
 	}
 }

+ 29 - 0
core/src/main/java/org/jeecg/common/util/TokenUtils.java

@@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.jeecg.common.api.CommonAPI;
 import org.jeecg.common.constant.CacheConstant;
 import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.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
      */

+ 5 - 5
core/src/main/java/org/jeecg/common/util/dynamic/db/FreemarkerParseFactory.java

@@ -34,11 +34,11 @@ public class FreemarkerParseFactory {
     /**
      * 文件缓存
      */
-    private static final Configuration TPL_CONFIG = new Configuration();
+    private static final Configuration TPL_CONFIG = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
     /**
      * SQL 缓存
      */
-    private static final Configuration SQL_CONFIG = new Configuration();
+    private static final Configuration SQL_CONFIG = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
 
     private static StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
 
@@ -47,8 +47,7 @@ public class FreemarkerParseFactory {
             .compile("(?ms)/\\*.*?\\*/|^\\s*//.*?$");
 
     static {
-        TPL_CONFIG.setClassForTemplateLoading(
-                new FreemarkerParseFactory().getClass(), "/");
+        TPL_CONFIG.setClassForTemplateLoading(new FreemarkerParseFactory().getClass(), "/");
         TPL_CONFIG.setNumberFormat("0.#####################");
         SQL_CONFIG.setTemplateLoader(stringTemplateLoader);
         SQL_CONFIG.setNumberFormat("0.#####################");
@@ -57,6 +56,7 @@ public class FreemarkerParseFactory {
 
         //update-begin-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
         //https://ackcent.com/in-depth-freemarker-template-injection/
+        TPL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
         SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
         //update-end-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
     }
@@ -169,7 +169,7 @@ public class FreemarkerParseFactory {
         //"where and"
         String whereAnd = DataBaseConstant.SQL_WHERE+" and";
         //", where"
-        String commaWhere = SymbolConstant.COMMA+" "+DataBaseConstant.SQL_WHERE;
+        String commaWhere = SymbolConstant.COMMA+" "+ DataBaseConstant.SQL_WHERE;
         //", "
         String commaSpace = SymbolConstant.COMMA + " ";
         if (sql.endsWith(DataBaseConstant.SQL_WHERE) || sql.endsWith(whereSpace)) {

+ 19 - 5
core/src/main/java/org/jeecg/common/util/filter/FileTypeFilter.java

@@ -1,19 +1,19 @@
 package org.jeecg.common.util.filter;
 
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * @Description: 校验上传文件敏感后缀
  * @author: lsq
  * @date: 2021年08月09日 15:29
  */
+@Slf4j
 public class FileTypeFilter {
 
     /**文件后缀*/
@@ -112,8 +112,9 @@ public class FileTypeFilter {
      */
 
     private static String getFileType(MultipartFile file) throws Exception {
+        //update-begin-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
         String fileExtendName = null;
-        InputStream is;
+        InputStream is = null;
         try {
             //is = new FileInputStream(file);
             is = file.getInputStream();
@@ -130,16 +131,29 @@ public class FileTypeFilter {
                     break;
                 }
             }
+            log.info("-----获取到的指定文件类型------"+fileExtendName);
             // 如果不是上述类型,则判断扩展名
             if (StringUtils.isBlank(fileExtendName)) {
                 String fileName = file.getOriginalFilename();
+                // 如果无扩展名,则直接返回空串
+                if (-1 == fileName.indexOf(".")) {
+                    return "";
+                }
+                // 如果有扩展名,则返回扩展名
                 return getFileTypeBySuffix(fileName);
             }
+            log.info("-----最終的文件类型------"+fileExtendName);
             is.close();
             return fileExtendName;
-        } catch (Exception exception) {
-            throw new Exception(exception.getMessage(), exception);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return "";
+        }finally {
+            if (is != null) {
+                is.close();
+            }
         }
+        //update-end-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
     }
 
     /**

+ 141 - 11
core/src/main/java/org/jeecg/common/util/oConvertUtils.java

@@ -1,5 +1,6 @@
 package org.jeecg.common.util;
 
+import com.alibaba.fastjson.JSONArray;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.IOUtils;
 import org.jeecg.common.constant.CommonConstant;
@@ -86,7 +87,7 @@ public class oConvertUtils {
 	}
 
 	public static int getInt(String s, int defval) {
-		if (s == null || s == "") {
+		if (s == null || "".equals(s)) {
 			return (defval);
 		}
 		try {
@@ -97,7 +98,7 @@ public class oConvertUtils {
 	}
 
 	public static int getInt(String s) {
-		if (s == null || s == "") {
+		if (s == null || "".equals(s)) {
 			return 0;
 		}
 		try {
@@ -108,7 +109,7 @@ public class oConvertUtils {
 	}
 
 	public static int getInt(String s, Integer df) {
-		if (s == null || s == "") {
+		if (s == null || "".equals(s)) {
 			return df;
 		}
 		try {
@@ -131,7 +132,7 @@ public class oConvertUtils {
 	}
 
 	public static double getDouble(String s, double defval) {
-		if (s == null || s == "") {
+		if (s == null || "".equals(s)) {
 			return (defval);
 		}
 		try {
@@ -353,23 +354,63 @@ public class oConvertUtils {
 	/**
 	 * 判断元素是否在数组内
 	 * 
-	 * @param substring
-	 * @param source
+	 * @param child
+	 * @param all
 	 * @return
 	 */
-	public static boolean isIn(String substring, String[] source) {
-		if (source == null || source.length == 0) {
+	public static boolean isIn(String child, String[] all) {
+		if (all == null || all.length == 0) {
 			return false;
 		}
-		for (int i = 0; i < source.length; i++) {
-			String aSource = source[i];
-			if (aSource.equals(substring)) {
+		for (int i = 0; i < all.length; i++) {
+			String aSource = all[i];
+			if (aSource.equals(child)) {
 				return true;
 			}
 		}
 		return false;
 	}
 
+	/**
+	 * 判断元素是否在数组内
+	 *
+	 * @param childArray
+	 * @param all
+	 * @return
+	 */
+	public static boolean isArrayIn(String[] childArray, String[] all) {
+		if (all == null || all.length == 0) {
+			return false;
+		}
+		for (String v : childArray) {
+			if (!isIn(v, all)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * 判断元素是否在数组内
+	 *
+	 * @param childArray
+	 * @param all
+	 * @return
+	 */
+	public static boolean isJsonArrayIn(JSONArray childArray, String[] all) {
+		if (all == null || all.length == 0) {
+			return false;
+		}
+
+		String[] childs = childArray.toArray(new String[]{});
+		for (String v : childs) {
+			if (!isIn(v, all)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
 	/**
 	 * 获取Map对象
 	 */
@@ -649,6 +690,95 @@ public class oConvertUtils {
 		return (list == null || list.size() == 0);
 	}
 
+	/**
+	 * 判断旧值与新值 是否相等
+	 *
+	 * @param oldVal
+	 * @param newVal
+	 * @return
+	 */
+	public static boolean isEqual(Object oldVal, Object newVal) {
+		if (oldVal != null && newVal != null) {
+			if (isArray(oldVal)) {
+				return equalityOfArrays((Object[]) oldVal, (Object[]) newVal);
+			}else if(oldVal instanceof JSONArray){
+				return equalityOfJSONArray((JSONArray) oldVal, (JSONArray) newVal);
+			}
+			return oldVal.equals(newVal);
+		} else {
+			if (oldVal == null && newVal == null) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 * 方法描述 判断一个对象是否是一个数组
+	 *
+	 * @param obj
+	 * @return
+	 * @author yaomy
+	 * @date 2018年2月5日 下午5:03:00
+	 */
+	public static boolean isArray(Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		return obj.getClass().isArray();
+	}
+	
+	/**
+	 * 判断两个数组是否相等(数组元素不分顺序)
+	 *
+	 * @param oldVal
+	 * @param newVal
+	 * @return
+	 */
+	public static boolean equalityOfJSONArray(JSONArray oldVal, JSONArray newVal) {
+		if (oldVal != null && newVal != null) {
+			Object[] oldValArray = oldVal.toArray();
+			Object[] newValArray = newVal.toArray();
+			return equalityOfArrays(oldValArray,newValArray);
+		} else {
+			if (oldVal == null && newVal == null) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 * 判断两个数组是否相等(数组元素不分顺序)
+	 *
+	 * @param oldVal
+	 * @param newVal
+	 * @return
+	 */
+	public static boolean equalityOfArrays(Object[] oldVal, Object newVal[]) {
+		if (oldVal != null && newVal != null) {
+			Arrays.sort(oldVal);
+			Arrays.sort(newVal);
+			return Arrays.equals(oldVal, newVal);
+		} else {
+			if (oldVal == null && newVal == null) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+//	public static void main(String[] args) {
+////		String[] a = new String[]{"1", "2"};
+////		String[] b = new String[]{"2", "1"};
+//		Integer a = null;
+//		Integer b = 1;
+//		System.out.println(oConvertUtils.isEqual(a, b));
+//	}
+	
 	/**
 	 * 判断 list 是否不为空
 	 *

+ 4 - 3
core/src/main/java/org/jeecg/common/util/oss/OssBootUtil.java

@@ -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;

+ 74 - 16
core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java

@@ -1,8 +1,12 @@
 package org.jeecg.common.util.security;
 
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.jeecg.common.exception.JeecgSqlInjectionException;
 
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * 查询表/字段 黑名单处理
@@ -21,6 +25,11 @@ public abstract class AbstractQueryBlackListHandler {
      */
     public static Map<String, String> ruleMap = new HashMap<>();
 
+    /**
+     * 以下字符不能出现在表名中或是字段名中
+     */
+    public static final Pattern ILLEGAL_NAME_REG = Pattern.compile("[-]{2,}");
+
     static {
         ruleMap.put("sys_user", "password,salt");
     }
@@ -52,27 +61,76 @@ public abstract class AbstractQueryBlackListHandler {
         if(list==null){
             return true;
         }
-        log.info("--获取sql信息--", list.toString());
-        boolean flag = true;
+        log.info("  获取sql信息 :{} ", list.toString());
+        boolean flag = checkTableAndFieldsName(list);
+        if(flag == false){
+            return false;
+        }
         for (QueryTable table : list) {
             String name = table.getName();
-            String fieldString = ruleMap.get(name);
+            String fieldRule = ruleMap.get(name);
             // 有没有配置这张表
-            if (fieldString != null) {
-                if ("*".equals(fieldString) || table.isAll()) {
+            if (fieldRule != null) {
+                if ("*".equals(fieldRule) || table.isAll()) {
                     flag = false;
                     log.warn("sql黑名单校验,表【"+name+"】禁止查询");
                     break;
-                } else if (table.existSameField(fieldString)) {
+                } else if (table.existSameField(fieldRule)) {
                     flag = false;
                     break;
                 }
 
             }
         }
+
+        // 返回黑名单校验结果(不合法直接抛出异常)
+        if(!flag){
+            log.error(this.getError());
+            throw new JeecgSqlInjectionException(this.getError());
+        }
         return flag;
     }
 
+    /**
+     * 校验表名和字段名是否有效,或是是否会带些特殊的字符串进行sql注入
+     * issues/4983 SQL Injection in 3.5.1 #4983
+     * @return
+     */
+    private boolean checkTableAndFieldsName(List<QueryTable> list){
+        boolean flag = true;
+        for(QueryTable queryTable: list){
+            String tableName = queryTable.getName();
+            if(hasSpecialString(tableName)){
+                flag = false;
+                log.warn("sql黑名单校验,表名【"+tableName+"】包含特殊字符");
+                break;
+            }
+            Set<String> fields = queryTable.getFields();
+            for(String name: fields){
+                if(hasSpecialString(name)){
+                    flag = false;
+                    log.warn("sql黑名单校验,字段名【"+name+"】包含特殊字符");
+                    break;
+                } 
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 是否包含特殊的字符串
+     * @param name
+     * @return
+     */
+    private boolean hasSpecialString(String name){
+        Matcher m = ILLEGAL_NAME_REG.matcher(name);
+        if (m.find()) {
+            return true;
+        }
+        return false;
+    }
+    
+
     /**
      * 查询的表的信息
      */
@@ -139,21 +197,21 @@ public abstract class AbstractQueryBlackListHandler {
          * @return
          */
         public boolean existSameField(String fieldString) {
-            String[] arr = fieldString.split(",");
-            for (String exp : fields) {
-                for (String config : arr) {
-                    if (exp.equals(config)) {
+            String[] controlFields = fieldString.split(",");
+            for (String sqlField : fields) {
+                for (String controlField : controlFields) {
+                    if (sqlField.equals(controlField)) {
                         // 非常明确的列直接比较
-                        log.warn("sql黑名单校验,表【"+name+"】中字段【"+config+"】禁止查询");
+                        log.warn("sql黑名单校验,表【"+name+"】中字段【"+controlField+"】禁止查询");
                         return true;
                     } else {
                         // 使用表达式的列 只能判读字符串包含了
-                        String aliasColumn = config;
-                        if (alias != null && alias.length() > 0) {
-                            aliasColumn = alias + "." + config;
+                        String aliasColumn = controlField;
+                        if (StringUtils.isNotBlank(alias)) {
+                            aliasColumn = alias + "." + controlField;
                         }
-                        if (exp.indexOf(aliasColumn) > 0) {
-                            log.warn("sql黑名单校验,表【"+name+"】中字段【"+config+"】禁止查询");
+                        if (sqlField.indexOf(aliasColumn) != -1) {
+                            log.warn("sql黑名单校验,表【"+name+"】中字段【"+controlField+"】禁止查询");
                             return true;
                         }
                     }

+ 1 - 1
core/src/main/java/org/jeecg/common/util/superSearch/ObjectParseUtil.java

@@ -1,4 +1,4 @@
-//package org.jeecg.common.util.superSearch;
+package org.jeecg.common.util.superSearch;//package org.jeecg.common.util.superSearch;
 //
 //import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 //

+ 1 - 1
core/src/main/java/org/jeecg/common/util/superSearch/QueryRuleEnum.java

@@ -1,4 +1,4 @@
-//package org.jeecg.common.util.superSearch;
+package org.jeecg.common.util.superSearch;//package org.jeecg.common.util.superSearch;
 //
 //import org.jeecg.common.util.oConvertUtils;
 //

+ 1 - 1
core/src/main/java/org/jeecg/common/util/superSearch/QueryRuleVo.java

@@ -1,4 +1,4 @@
-//package org.jeecg.common.util.superSearch;
+package org.jeecg.common.util.superSearch;//package org.jeecg.common.util.superSearch;
 //
 //import lombok.Data;
 //

+ 1 - 1
core/src/main/java/org/jeecg/config/AutoPoiConfig.java

@@ -22,7 +22,7 @@ public class AutoPoiConfig {
      */
     @Bean
     public ApplicationContextUtil applicationContextUtil() {
-        return new org.jeecgframework.core.util.ApplicationContextUtil();
+        return new ApplicationContextUtil();
     }
 
 }

+ 21 - 0
core/src/main/java/org/jeecg/config/JeecgBaseConfig.java

@@ -1,6 +1,7 @@
 package org.jeecg.config;
 
 import org.jeecg.config.vo.DomainUrl;
+import org.jeecg.config.vo.Elasticsearch;
 import org.jeecg.config.vo.Path;
 import org.jeecg.config.vo.Shiro;
 import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -52,6 +53,18 @@ public class JeecgBaseConfig {
      * 文件预览
      */
     private String fileViewDomain;
+     /**
+     * ES配置
+     */
+    private Elasticsearch elasticsearch;
+
+    public Elasticsearch getElasticsearch() {
+        return elasticsearch;
+    }
+
+    public void setElasticsearch(Elasticsearch elasticsearch) {
+        this.elasticsearch = elasticsearch;
+    }
 
     public Boolean getSafeMode() {
         return safeMode;
@@ -108,4 +121,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;
+    }
 }

+ 3 - 3
core/src/main/java/org/jeecg/config/StaticConfig.java

@@ -12,13 +12,13 @@ import org.springframework.stereotype.Component;
 @Data
 public class StaticConfig {
 
-    @Value("${jeecg.oss.accessKey}")
+    @Value("${jeecg.oss.accessKey:}")
     private String accessKeyId;
 
-    @Value("${jeecg.oss.secretKey}")
+    @Value("${jeecg.oss.secretKey:}")
     private String accessKeySecret;
 
-    @Value(value = "${spring.mail.username}")
+    @Value(value = "${spring.mail.username:}")
     private String emailFrom;
 
 //    /**

+ 26 - 75
core/src/main/java/org/jeecg/config/Swagger2Config.java

@@ -3,18 +3,12 @@ package org.jeecg.config;
 
 import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
 import io.swagger.annotations.ApiOperation;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
 import org.jeecg.common.constant.CommonConstant;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
-import org.springframework.context.annotation.Profile;
 import org.springframework.util.ReflectionUtils;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
@@ -22,18 +16,12 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
 import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
 import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.ParameterBuilder;
 import springfox.documentation.builders.PathSelectors;
 import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.builders.RequestParameterBuilder;
-import springfox.documentation.schema.ScalarType;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.ApiKey;
-import springfox.documentation.service.AuthorizationScope;
-import springfox.documentation.service.Contact;
-import springfox.documentation.service.ParameterType;
-import springfox.documentation.service.RequestParameter;
-import springfox.documentation.service.SecurityReference;
-import springfox.documentation.service.SecurityScheme;
+import springfox.documentation.oas.annotations.EnableOpenApi;
+import springfox.documentation.schema.ModelRef;
+import springfox.documentation.service.*;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spi.service.contexts.SecurityContext;
 import springfox.documentation.spring.web.plugins.Docket;
@@ -41,14 +29,19 @@ import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
 import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
 /**
- * @author  scott
+ * @Author scott
  */
 @Configuration
 @EnableSwagger2    //开启 Swagger2
 @EnableKnife4j     //开启 knife4j,可以不写
 @Import(BeanValidatorPluginsConfiguration.class)
-@Profile({ "prod", "dev", "test" }) //上线删除prod
 public class Swagger2Config implements WebMvcConfigurer {
 
     /**
@@ -73,7 +66,6 @@ public class Swagger2Config implements WebMvcConfigurer {
     public Docket defaultApi2() {
         return new Docket(DocumentationType.SWAGGER_2)
                 .apiInfo(apiInfo())
-                .groupName("后台管理")
                 .select()
                 //此包路径下的类,才生成接口文档
                 .apis(RequestHandlerSelectors.basePackage("org.jeecg"))
@@ -84,31 +76,9 @@ public class Swagger2Config implements WebMvcConfigurer {
                 .build()
                 .securitySchemes(Collections.singletonList(securityScheme()))
                 .securityContexts(securityContexts())
-                .globalRequestParameters(setHeaderToken());
-    }
-    /**
-     * 配置模块
-     *
-     * @param moduleCode  模块Code
-     * @param moduleName  模块名称
-     * @param basePackage 基础包
-     * @return
-     */
-    public static Docket docket(String moduleCode, String moduleName, String basePackage) {
-        return new Docket(DocumentationType.SWAGGER_2)
-                .apiInfo(apiInfo(moduleName))
-                .groupName(moduleCode)
-                .select()
-                //此包路径下的类,才生成接口文档
-                .apis(RequestHandlerSelectors.basePackage(basePackage))
-                .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
-                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
-                .paths(PathSelectors.any())
-                .build()
-                .securitySchemes(Collections.singletonList(securityScheme()))
-                .securityContexts(securityContexts())
-                .globalRequestParameters(setHeaderToken());
+                .globalOperationParameters(setHeaderToken());
     }
+
     /***
      * oauth2配置
      * 需要增加swagger授权回调地址
@@ -116,23 +86,18 @@ public class Swagger2Config implements WebMvcConfigurer {
      * @return
      */
     @Bean
-    static SecurityScheme securityScheme() {
+    SecurityScheme securityScheme() {
         return new ApiKey(CommonConstant.X_ACCESS_TOKEN, CommonConstant.X_ACCESS_TOKEN, "header");
     }
     /**
      * JWT token
      * @return
      */
-    private static List<RequestParameter> setHeaderToken() {
-        RequestParameterBuilder tokenBuilder = new RequestParameterBuilder();
-        List<RequestParameter> pars = new ArrayList<>();
-        tokenBuilder.name(CommonConstant.X_ACCESS_TOKEN)
-                .description("token")
-                .required(false)
-                .in(ParameterType.HEADER)
-                .query(param -> param.model(model -> model.scalarModel(ScalarType.STRING)))
-                .build();
-        pars.add(tokenBuilder.build());
+    private List<Parameter> setHeaderToken() {
+        ParameterBuilder tokenPar = new ParameterBuilder();
+        List<Parameter> pars = new ArrayList<>();
+        tokenPar.name(CommonConstant.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
+        pars.add(tokenPar.build());
         return pars;
     }
 
@@ -144,37 +109,23 @@ public class Swagger2Config implements WebMvcConfigurer {
     private ApiInfo apiInfo() {
         return new ApiInfoBuilder()
                 // //大标题
-                .title("在线考试服务API接口文档")
+                .title("JeecgBoot 后台服务API接口文档")
                 // 版本号
                 .version("1.0")
 //				.termsOfServiceUrl("NO terms of service")
                 // 描述
                 .description("后台API接口")
                 // 作者
+                .contact(new Contact("北京敲敲云科技有限公司","www.jeccg.com","jeecgos@163.com"))
+                .license("The Apache License, Version 2.0")
+                .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
                 .build();
     }
-    /**
-     * api文档的详细信息函数,注意这里的注解引用的是哪个
-     *
-     * @return
-     */
-    private static ApiInfo apiInfo(String moduleName) {
-        return new ApiInfoBuilder()
-                // //大标题
-                .title(moduleName)
-                // 版本号
-                .version("1.0")
-//				.termsOfServiceUrl("NO terms of service")
-                // 描述
-                .description(moduleName + "API接口")
-                // 作者
-                .contact(new Contact("ynfy","www.ynfy.net.cn",""))
-                .build();
-    }
+
     /**
      * 新增 securityContexts 保持登录状态
      */
-    private static List<SecurityContext> securityContexts() {
+    private List<SecurityContext> securityContexts() {
         return new ArrayList(
                 Collections.singleton(SecurityContext.builder()
                         .securityReferences(defaultAuth())
@@ -183,7 +134,7 @@ public class Swagger2Config implements WebMvcConfigurer {
         );
     }
 
-    private static List<SecurityReference> defaultAuth() {
+    private List<SecurityReference> defaultAuth() {
         AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
         AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
         authorizationScopes[0] = authorizationScope;

+ 8 - 6
core/src/main/java/org/jeecg/config/WebMvcConfiguration.java

@@ -25,6 +25,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -59,12 +60,13 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
      */
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry) {
-        registry.addResourceHandler("/**")
-                //update-begin-author:taoyan date:20211116 for: jeecg.path.webapp配置无效 #3126
-                .addResourceLocations("file:" + jeecgBaseConfig.getPath().getUpload() + "//")
-                .addResourceLocations("file:" + jeecgBaseConfig.getPath().getWebapp() + "//")
-                //update-end-author:taoyan date:20211116 for: jeecg.path.webapp配置无效 #3126
-                .addResourceLocations(staticLocations.split(","));
+        ResourceHandlerRegistration resourceHandlerRegistration = registry.addResourceHandler("/**");
+        if (jeecgBaseConfig.getPath() != null && jeecgBaseConfig.getPath().getUpload() != null) {
+            resourceHandlerRegistration
+                    .addResourceLocations("file:" + jeecgBaseConfig.getPath().getUpload() + "//")
+                    .addResourceLocations("file:" + jeecgBaseConfig.getPath().getWebapp() + "//");
+        }
+        resourceHandlerRegistration.addResourceLocations(staticLocations.split(","));
     }
 
     /**

+ 1 - 1
core/src/main/java/org/jeecg/config/WebSocketConfig.java

@@ -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;
     }
 

+ 1 - 1
core/src/main/java/org/jeecg/config/mybatis/JeecgTenantParser.java

@@ -1,4 +1,4 @@
-//package org.jeecg.config.mybatis;
+package org.jeecg.config.mybatis;//package org.jeecg.config.mybatis;
 //
 //import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
 //import net.sf.jsqlparser.expression.BinaryExpression;

+ 19 - 0
core/src/main/java/org/jeecg/config/mybatis/MybatisInterceptor.java

@@ -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) {
 				}
 			}

+ 46 - 10
core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java

@@ -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={"com.ynfy.buss.**.mapper*","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;
     }
 

+ 1 - 1
core/src/main/java/org/jeecg/config/mybatis/TenantContext.java

@@ -1,4 +1,4 @@
-//package org.jeecg.config.mybatis;
+package org.jeecg.config.mybatis;//package org.jeecg.config.mybatis;
 //
 //import lombok.extern.slf4j.Slf4j;
 //

+ 2 - 0
core/src/main/java/org/jeecg/config/oss/MinioConfig.java

@@ -5,6 +5,7 @@ import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.constant.SymbolConstant;
 import org.jeecg.common.util.MinioUtil;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -14,6 +15,7 @@ import org.springframework.context.annotation.Configuration;
  */
 @Slf4j
 @Configuration
+@ConditionalOnProperty(prefix = "jeecg.minio", name = "minio_url")
 public class MinioConfig {
     @Value(value = "${jeecg.minio.minio_url}")
     private String minioUrl;

+ 2 - 0
core/src/main/java/org/jeecg/config/oss/OssConfiguration.java

@@ -2,6 +2,7 @@ package org.jeecg.config.oss;
 
 import org.jeecg.common.util.oss.OssBootUtil;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -10,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
  * @author: jeecg-boot
  */
 @Configuration
+@ConditionalOnProperty(prefix = "jeecg.oss", name = "endpoint")
 public class OssConfiguration {
 
     @Value("${jeecg.oss.endpoint}")

+ 7 - 8
core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java

@@ -20,7 +20,6 @@ import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
 import org.jeecg.config.shiro.filters.JwtFilter;
 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.DependsOn;
@@ -111,19 +110,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,13 +140,16 @@ public class ShiroConfig {
         filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模块
         filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable无痕刷新示例
 
-
-        //性能监控,放开排除会存在安全漏洞泄露TOEKN(durid连接池也有)
+        //性能监控——安全隐患泄露TOEKN(durid连接池也有)
         //filterChainDefinitionMap.put("/actuator/**", "anon");
-
         //测试模块排除
         filterChainDefinitionMap.put("/test/seata/**", "anon");
 
+        // update-begin--author:liusq Date:20230522 for:[issues/4829]访问不存在的url时会提示Token失效,请重新登录呢
+        //错误路径排除
+        filterChainDefinitionMap.put("/error", "anon");
+        // update-end--author:liusq Date:20230522 for:[issues/4829]访问不存在的url时会提示Token失效,请重新登录呢
+
         // 添加自己的过滤器并且取名为jwt
         Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
         //如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】

+ 25 - 1
core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java

@@ -11,6 +11,7 @@ import org.apache.shiro.realm.AuthorizingRealm;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.jeecg.common.api.CommonAPI;
 import org.jeecg.common.config.TenantContext;
+import org.jeecg.common.constant.CacheConstant;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.util.JwtUtil;
 import org.jeecg.common.system.vo.LoginUser;
@@ -140,12 +141,35 @@ 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("用户租户信息变更,请重新登陆!");
+                    boolean isAuthorization = false;
+                    //========================================================================
+                    // 查询用户信息(如果租户不匹配从数据库中重新查询一次用户信息)
+                    String loginUserKey = CacheConstant.SYS_USERS_CACHE + "::" + username;
+                    redisUtil.del(loginUserKey);
+                    LoginUser loginUserFromDb = commonApi.getUserByName(username);
+                    if (oConvertUtils.isNotEmpty(loginUserFromDb.getRelTenantIds())) {
+                        String[] newArray = loginUserFromDb.getRelTenantIds().split(",");
+                        if (oConvertUtils.isIn(contextTenantId, newArray)) { 
+                            isAuthorization = true;
+                        }
+                    }
+                    //========================================================================
+
+                    //*********************************************
+                    if(!isAuthorization){
+                        log.info("租户异常——登录租户:" + contextTenantId);
+                        log.info("租户异常——用户拥有租户组:" + userTenantIds);
+                        throw new AuthenticationException("登录租户授权变更,请重新登陆!");
+                    }
+                    //*********************************************
                 }
                 //update-end-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
             }

+ 14 - 0
core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java

@@ -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);
 
@@ -66,6 +79,7 @@ public class SignAuthInterceptor implements HandlerInterceptor {
             log.debug("Sign 签名通过!Header Sign : {}",headerSign);
             return true;
         } else {
+            log.info("sign allParams: {}", allParams);
             log.error("request URI = " + request.getRequestURI());
             log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
             //校验失败返回前端

+ 23 - 9
core/src/main/java/org/jeecg/config/sign/util/HttpUtils.java

@@ -1,5 +1,12 @@
 package org.jeecg.config.sign.util;
 
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.constant.SymbolConstant;
+import org.jeecg.common.util.oConvertUtils;
+import org.springframework.http.HttpMethod;
+
+import javax.servlet.http.HttpServletRequest;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -10,15 +17,6 @@ import java.util.Map;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
-import javax.servlet.http.HttpServletRequest;
-
-import lombok.extern.slf4j.Slf4j;
-import org.jeecg.common.constant.SymbolConstant;
-import org.jeecg.common.util.oConvertUtils;
-import org.springframework.http.HttpMethod;
-
-import com.alibaba.fastjson.JSONObject;
-
 /**
  * http 工具类 获取请求中的参数
  *
@@ -43,6 +41,16 @@ public class HttpUtils {
         if (pathVariable.contains(SymbolConstant.COMMA)) {
             log.info(" pathVariable: {}",pathVariable);
             String deString = URLDecoder.decode(pathVariable, "UTF-8");
+          
+            //https://www.52dianzi.com/category/article/37/565371.html
+            if(deString.contains("%")){
+                try {
+                    deString = URLDecoder.decode(deString, "UTF-8");
+                } catch (Exception e) {
+                    //e.printStackTrace();
+                }
+                log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
+            }
             log.info(" pathVariable decode: {}",deString);
             result.put(SignUtil.X_PATH_VARIABLE, deString);
         }
@@ -81,6 +89,12 @@ public class HttpUtils {
         if (pathVariable.contains(SymbolConstant.COMMA)) {
             log.info(" pathVariable: {}",pathVariable);
             String deString = URLDecoder.decode(pathVariable, "UTF-8");
+           
+            //https://www.52dianzi.com/category/article/37/565371.html
+            if(deString.contains("%")){
+                deString = URLDecoder.decode(deString, "UTF-8");
+                log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
+            }
             log.info(" pathVariable decode: {}",deString);
             result.put(SignUtil.X_PATH_VARIABLE, deString);
         }

+ 26 - 0
core/src/main/java/org/jeecg/config/vo/Elasticsearch.java

@@ -0,0 +1,26 @@
+package org.jeecg.config.vo;
+
+/**
+ * @author: scott
+ * @date: 2023年05月10日 16:06
+ */
+public class Elasticsearch {
+    private String clusterNodes;
+    private boolean checkEnabled;
+
+    public String getClusterNodes() {
+        return clusterNodes;
+    }
+
+    public void setClusterNodes(String clusterNodes) {
+        this.clusterNodes = clusterNodes;
+    }
+
+    public boolean isCheckEnabled() {
+        return checkEnabled;
+    }
+
+    public void setCheckEnabled(boolean checkEnabled) {
+        this.checkEnabled = checkEnabled;
+    }
+}

+ 16 - 32
pom.xml

@@ -4,44 +4,47 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.ynfy</groupId>
   <artifactId>exam</artifactId>
-  <version>3.4.4</version>
+  <version>3.5.5</version>
   <packaging>pom</packaging>
 
   <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
-    <version>2.6.14</version>
+    <version>2.7.10</version>
     <relativePath/>
   </parent>
 
   <properties>
-    <jeecgboot.version>3.4.4</jeecgboot.version>
+    <jeecgboot.version>3.5.5</jeecgboot.version>
     <java.version>1.8</java.version>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
 
+    <xxl-job-core.version>2.2.0</xxl-job-core.version>
     <fastjson.version>1.2.83</fastjson.version>
     <pegdown.version>1.6.0</pegdown.version>
     <knife4j-spring-boot-starter.version>3.0.3</knife4j-spring-boot-starter.version>
     <knife4j-spring-ui.version>2.0.9</knife4j-spring-ui.version>
     <!-- 数据库驱动 -->
-    <hutool.version>5.3.8</hutool.version>
     <mysql-connector-java.version>8.0.27</mysql-connector-java.version>
+    <hutool.version>5.3.8</hutool.version>
 
     <!-- 持久层 -->
-    <mybatis-plus.version>3.5.1</mybatis-plus.version>
-    <dynamic-datasource-spring-boot-starter.version>3.2.0</dynamic-datasource-spring-boot-starter.version>
-    <druid.version>1.1.22</druid.version>
-    <minidao.version>1.9.0</minidao.version>
+    <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
+    <dynamic-datasource-spring-boot-starter.version>3.5.2</dynamic-datasource-spring-boot-starter.version>
+    <druid.version>1.2.15</druid.version>
+    <minidao.version>1.9.4</minidao.version>
 
+    <!-- 积木报表-->
+    <jimureport-spring-boot-starter.version>1.6.2</jimureport-spring-boot-starter.version>
     <commons.version>2.6</commons.version>
     <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
     <aliyun.oss.version>3.11.2</aliyun.oss.version>
     <!-- shiro -->
-    <shiro.version>1.10.0</shiro.version>
+    <shiro.version>1.12.0</shiro.version>
     <java-jwt.version>3.11.0</java-jwt.version>
     <shiro-redis.version>3.1.0</shiro-redis.version>
-    <codegenerate.version>1.4.3</codegenerate.version>
+    <codegenerate.version>1.4.4</codegenerate.version>
     <autopoi-web.version>1.4.5</autopoi-web.version>
     <minio.version>8.0.3</minio.version>
     <justauth-spring-boot-starter.version>1.3.4</justauth-spring-boot-starter.version>
@@ -67,25 +70,6 @@
         <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>
@@ -188,7 +172,7 @@
       <dependency>
         <groupId>org.jeecgframework.boot</groupId>
         <artifactId>hibernate-re</artifactId>
-        <version>3.4.4-beta</version>
+        <version>3.5.3</version>
       </dependency>
 
       <!-- 七牛云SDK -->
@@ -213,7 +197,7 @@
       <dependency>
         <groupId>commons-fileupload</groupId>
         <artifactId>commons-fileupload</artifactId>
-        <version>1.4</version>
+        <version>1.5</version>
         <exclusions>
           <exclusion>
             <artifactId>commons-io</artifactId>
@@ -263,7 +247,7 @@
       <dependency>
         <groupId>org.jeecgframework</groupId>
         <artifactId>jeewx-api</artifactId>
-        <version>1.5.0</version>
+        <version>1.5.2</version>
         <exclusions>
           <exclusion>
             <artifactId>commons-beanutils</artifactId>

+ 1 - 1
system/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ynfy</groupId>
         <artifactId>exam</artifactId>
-        <version>3.4.4</version>
+        <version>3.5.5</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.ynfy</groupId>

+ 1 - 1
system/system-api/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.ynfy</groupId>
         <artifactId>system</artifactId>
-        <version>3.4.4</version>
+        <version>3.5.5</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.ynfy</groupId>

+ 1 - 1
system/system-api/system-local-api/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>system-api</artifactId>
         <groupId>com.ynfy</groupId>
-        <version>3.4.4</version>
+        <version>3.5.5</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.ynfy</groupId>

+ 1 - 1
system/system-biz/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ynfy</groupId>
 		<artifactId>system</artifactId>
-		<version>3.4.4</version>
+		<version>3.5.5</version>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>com.ynfy</groupId>

+ 0 - 1
system/system-biz/src/main/java/org/jeecg/config/init/CodeTemplateInitListener.java

@@ -17,7 +17,6 @@ import java.nio.charset.StandardCharsets;
  * 自动初始化代码生成器模板
  * <p>
  * 解决JAR发布需要手工配置代码生成器模板问题
- * http://doc.jeecg.com/2043922
  * @author zhang
  */
 @Slf4j

+ 27 - 0
system/system-biz/src/main/java/org/jeecg/modules/aop/TenantLog.java

@@ -0,0 +1,27 @@
+package org.jeecg.modules.aop;
+
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.constant.enums.ModuleType;
+
+import java.lang.annotation.*;
+
+/**
+ * 系统日志注解
+ * 
+ * @Author scott
+ * @email jeecgos@163.com
+ * @Date 2019年1月14日
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface TenantLog {
+
+	/**
+	 * 操作日志类型(1查询,2添加,3修改,4删除)
+	 * 
+	 * @return
+	 */
+	int value() default 0;
+
+}

+ 99 - 0
system/system-biz/src/main/java/org/jeecg/modules/aop/TenantPackUserLogAspect.java

@@ -0,0 +1,99 @@
+package org.jeecg.modules.aop;
+
+import org.apache.shiro.SecurityUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.AfterThrowing;
+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.api.dto.LogDTO;
+import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.modules.base.service.BaseCommonService;
+import org.jeecg.modules.system.entity.SysTenantPack;
+import org.jeecg.modules.system.entity.SysTenantPackUser;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Method;
+import java.util.Date;
+
+/**
+ * @Author taoYan
+ * @Date 2023/2/16 14:27
+ **/
+@Aspect
+@Component
+public class TenantPackUserLogAspect {
+
+    @Resource
+    private BaseCommonService baseCommonService;
+
+    @Pointcut("@annotation(org.jeecg.modules.aop.TenantLog)")
+    public void tenantLogPointCut() {
+
+    }
+
+    @Around("tenantLogPointCut()")
+    public Object aroundMethod(ProceedingJoinPoint joinPoint)throws Throwable {
+        //System.out.println("环绕通知>>>>>>>>>");
+
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        TenantLog log = method.getAnnotation(TenantLog.class);
+        if(log != null){
+            int opType = log.value();
+            Integer logType = null;
+            String content = null;
+            Integer tenantId = null;
+            //获取参数
+            Object[] args = joinPoint.getArgs();
+            if(args.length>0){
+                for(Object obj: args){
+                    if(obj instanceof SysTenantPack){
+                        // logType=3 租户操作日志
+                        logType = 3;
+                        SysTenantPack pack = (SysTenantPack)obj;
+                        if(opType==2){
+                            content = "创建了角色权限 "+ pack.getPackName();
+                        }
+                        tenantId = pack.getTenantId();
+                        break;
+                    }else if(obj instanceof SysTenantPackUser){
+                        logType = 3;
+                        SysTenantPackUser packUser = (SysTenantPackUser)obj;
+                        if(opType==2){
+                            content = "将 "+packUser.getRealname()+" 添加到角色 "+ packUser.getPackName();
+                        }else if(opType==4){
+                            content = "移除了 "+packUser.getPackName()+" 成员 "+ packUser.getRealname();
+                        }
+                        tenantId = packUser.getTenantId();
+                    }
+                } 
+            }
+            if(logType!=null){
+                LogDTO dto = new LogDTO();
+                dto.setLogType(logType);
+                dto.setLogContent(content);
+                dto.setOperateType(opType);
+                dto.setTenantId(tenantId);
+                //获取登录用户信息
+                LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+                if(sysUser!=null){
+                    dto.setUserid(sysUser.getUsername());
+                    dto.setUsername(sysUser.getRealname());
+
+                }
+                dto.setCreateTime(new Date());
+                //保存系统日志
+                baseCommonService.addLog(dto);
+            }
+        }
+        return joinPoint.proceed();
+    }
+
+    @AfterThrowing("tenantLogPointCut()")
+    public void afterThrowing()throws Throwable{
+        System.out.println("异常通知");
+    }
+}

+ 111 - 0
system/system-biz/src/main/java/org/jeecg/modules/cas/controller/CasClientController.java

@@ -0,0 +1,111 @@
+package org.jeecg.modules.cas.controller;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.util.JwtUtil;
+import org.jeecg.common.util.RedisUtil;
+import org.jeecg.modules.cas.util.CasServiceUtil;
+import org.jeecg.modules.cas.util.XmlUtils;
+import org.jeecg.modules.system.entity.SysDepart;
+import org.jeecg.modules.system.entity.SysUser;
+import org.jeecg.modules.system.service.ISysDepartService;
+import org.jeecg.modules.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.alibaba.fastjson.JSONObject;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * <p>
+ * CAS单点登录客户端登录认证
+ * </p>
+ *
+ * @Author zhoujf
+ * @since 2018-12-20
+ */
+@Slf4j
+@RestController
+@RequestMapping("/sys/cas/client")
+public class CasClientController {
+
+	@Autowired
+	private ISysUserService sysUserService;
+	@Autowired
+    private ISysDepartService sysDepartService;
+	@Autowired
+    private RedisUtil redisUtil;
+	
+	@Value("${cas.prefixUrl}")
+    private String prefixUrl;
+	
+	
+	@GetMapping("/validateLogin")
+	public Object validateLogin(@RequestParam(name="ticket") String ticket,
+								@RequestParam(name="service") String service,
+								HttpServletRequest request,
+								HttpServletResponse response) throws Exception {
+		Result<JSONObject> result = new Result<JSONObject>();
+		log.info("Rest api login.");
+		try {
+			String validateUrl = prefixUrl+"/p3/serviceValidate";
+			String res = CasServiceUtil.getStValidate(validateUrl, ticket, service);
+			log.info("res."+res);
+			final String error = XmlUtils.getTextForElement(res, "authenticationFailure");
+			if(StringUtils.isNotEmpty(error)) {
+				throw new Exception(error);
+			}
+			final String principal = XmlUtils.getTextForElement(res, "user");
+			if (StringUtils.isEmpty(principal)) {
+	            throw new Exception("No principal was found in the response from the CAS server.");
+	        }
+			log.info("-------token----username---"+principal);
+		    //1. 校验用户是否有效
+	  		SysUser sysUser = sysUserService.getUserByName(principal);
+	  		result = sysUserService.checkUserIsEffective(sysUser);
+	  		if(!result.isSuccess()) {
+	  			return result;
+	  		}
+	 		String token = JwtUtil.sign(sysUser.getUsername(), sysUser.getPassword());
+	 		// 设置超时时间
+	 		redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
+	 		redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME*2 / 1000);
+
+	 		//获取用户部门信息
+			JSONObject obj = new JSONObject();
+			List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
+			obj.put("departs", departs);
+			if (departs == null || departs.size() == 0) {
+				obj.put("multi_depart", 0);
+			} else if (departs.size() == 1) {
+				sysUserService.updateUserDepart(principal, departs.get(0).getOrgCode(),null);
+				obj.put("multi_depart", 1);
+			} else {
+				obj.put("multi_depart", 2);
+			}
+			obj.put("token", token);
+			obj.put("userInfo", sysUser);
+			result.setResult(obj);
+			result.success("登录成功");
+	  		
+		} catch (Exception e) {
+			//e.printStackTrace();
+			result.error500(e.getMessage());
+		}
+		return new HttpEntity<>(result);
+	}
+
+	
+}

+ 107 - 0
system/system-biz/src/main/java/org/jeecg/modules/cas/util/CasServiceUtil.java

@@ -0,0 +1,107 @@
+package org.jeecg.modules.cas.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+/**
+ * @Description: CasServiceUtil
+ * @author: jeecg-boot
+ */
+public class CasServiceUtil {
+	
+	public static void main(String[] args) {
+		String serviceUrl = "https://cas.8f8.com.cn:8443/cas/p3/serviceValidate";
+		String service = "http://localhost:3003/user/login";
+		String ticket = "ST-5-1g-9cNES6KXNRwq-GuRET103sm0-DESKTOP-VKLS8B3";
+		String res = getStValidate(serviceUrl,ticket, service);
+		
+		System.out.println("---------res-----"+res);
+	}
+	
+	
+	/**
+     * 验证ST
+     */
+    public static String getStValidate(String url, String st, String service){
+		try {
+			url = url+"?service="+service+"&ticket="+st;
+			CloseableHttpClient httpclient = createHttpClientWithNoSsl();
+			HttpGet httpget = new HttpGet(url);
+			HttpResponse response = httpclient.execute(httpget);
+	        String res = readResponse(response);
+	        return res == null ? null : (res == "" ? null : res);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return "";
+	}
+
+    
+    /**
+     * 读取 response body 内容为字符串
+     *
+     * @param response
+     * @return
+     * @throws IOException
+     */
+    private static String readResponse(HttpResponse response) throws IOException {
+        BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
+        String result = new String();
+        String line;
+        while ((line = in.readLine()) != null) {
+            result += line;
+        }
+        return result;
+    }
+    
+    
+    /**
+     * 创建模拟客户端(针对 https 客户端禁用 SSL 验证)
+     *
+     * @param cookieStore 缓存的 Cookies 信息
+     * @return
+     * @throws Exception
+     */
+    private static CloseableHttpClient createHttpClientWithNoSsl() throws Exception {
+        // Create a trust manager that does not validate certificate chains
+        TrustManager[] trustAllCerts = new TrustManager[]{
+                new X509TrustManager() {
+                    @Override
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+
+                    @Override
+                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+
+                    @Override
+                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+                }
+        };
+
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(null, trustAllCerts, null);
+        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);
+        return HttpClients.custom()
+                .setSSLSocketFactory(sslSocketFactory)
+                .build();
+    }
+
+}

+ 304 - 0
system/system-biz/src/main/java/org/jeecg/modules/cas/util/XmlUtils.java

@@ -0,0 +1,304 @@
+package org.jeecg.modules.cas.util;
+
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.jeecg.common.constant.CommonConstant;
+import org.w3c.dom.Document;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 解析cas,ST验证后的xml
+ * @author: jeecg-boot
+ */
+@Slf4j
+public final class XmlUtils {
+
+    /**
+     * attributes
+     */
+    private static final String ATTRIBUTES = "attributes";
+
+    /**
+     * Creates a new namespace-aware DOM document object by parsing the given XML.
+     *
+     * @param xml XML content.
+     *
+     * @return DOM document.
+     */
+    public static Document newDocument(final String xml) {
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        final Map<String, Boolean> features = new HashMap(5);
+        features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+        for (final Map.Entry<String, Boolean> entry : features.entrySet()) {
+            try {
+                factory.setFeature(entry.getKey(), entry.getValue());
+            } catch (ParserConfigurationException e) {
+                log.warn("Failed setting XML feature {}: {}", entry.getKey(), e);
+            }
+        }
+        factory.setNamespaceAware(true);
+        try {
+            return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
+        } catch (Exception e) {
+            throw new RuntimeException("XML parsing error: " + e);
+        }
+    }
+
+    /**
+     * Get an instance of an XML reader from the XMLReaderFactory.
+     *
+     * @return the XMLReader.
+     */
+    public static XMLReader getXmlReader() {
+        try {
+            final XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+            reader.setFeature("http://xml.org/sax/features/namespaces", true);
+            reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+            reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+            return reader;
+        } catch (final Exception e) {
+            throw new RuntimeException("Unable to create XMLReader", e);
+        }
+    }
+
+
+    /**
+     * Retrieve the text for a group of elements. Each text element is an entry
+     * in a list.
+     * <p>This method is currently optimized for the use case of two elements in a list.
+     *
+     * @param xmlAsString the xml response
+     * @param element     the element to look for
+     * @return the list of text from the elements.
+     */
+    public static List<String> getTextForElements(final String xmlAsString, final String element) {
+        final List<String> elements = new ArrayList<String>(2);
+        final XMLReader reader = getXmlReader();
+
+        final DefaultHandler handler = new DefaultHandler() {
+
+            private boolean foundElement = false;
+
+            private StringBuilder buffer = new StringBuilder();
+
+            @Override
+            public void startElement(final String uri, final String localName, final String qName,
+                                     final Attributes attributes) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = true;
+                }
+            }
+
+            @Override
+            public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = false;
+                    elements.add(this.buffer.toString());
+                    this.buffer = new StringBuilder();
+                }
+            }
+
+            @Override
+            public void characters(char[] ch, int start, int length) throws SAXException {
+                if (this.foundElement) {
+                    this.buffer.append(ch, start, length);
+                }
+            }
+        };
+
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+
+        try {
+            reader.parse(new InputSource(new StringReader(xmlAsString)));
+        } catch (final Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+
+        return elements;
+    }
+
+    /**
+     * Retrieve the text for a specific element (when we know there is only
+     * one).
+     *
+     * @param xmlAsString the xml response
+     * @param element     the element to look for
+     * @return the text value of the element.
+     */
+    public static String getTextForElement(final String xmlAsString, final String element) {
+        final XMLReader reader = getXmlReader();
+        final StringBuilder builder = new StringBuilder();
+
+        final DefaultHandler handler = new DefaultHandler() {
+
+            private boolean foundElement = false;
+
+            @Override
+            public void startElement(final String uri, final String localName, final String qName,
+                                     final Attributes attributes) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = true;
+                }
+            }
+
+            @Override
+            public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = false;
+                }
+            }
+
+            @Override
+            public void characters(char[] ch, int start, int length) throws SAXException {
+                if (this.foundElement) {
+                    builder.append(ch, start, length);
+                }
+            }
+        };
+
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+
+        try {
+            reader.parse(new InputSource(new StringReader(xmlAsString)));
+        } catch (final Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+
+        return builder.toString();
+    }
+    
+    
+    public static Map<String, Object> extractCustomAttributes(final String xml) {
+        final SAXParserFactory spf = SAXParserFactory.newInstance();
+        spf.setNamespaceAware(true);
+        spf.setValidating(false);
+        try {
+            final SAXParser saxParser = spf.newSAXParser();
+            final XMLReader xmlReader = saxParser.getXMLReader();
+            final CustomAttributeHandler handler = new CustomAttributeHandler();
+            xmlReader.setContentHandler(handler);
+            xmlReader.parse(new InputSource(new StringReader(xml)));
+            return handler.getAttributes();
+        } catch (final Exception e) {
+        	log.error(e.getMessage(), e);
+            return Collections.emptyMap();
+        }
+    }
+    
+    private static class CustomAttributeHandler extends DefaultHandler {
+
+        private Map<String, Object> attributes;
+
+        private boolean foundAttributes;
+
+        private String currentAttribute;
+
+        private StringBuilder value;
+
+        @Override
+        public void startDocument() throws SAXException {
+            this.attributes = new HashMap(5);
+        }
+
+        @Override
+        public void startElement(final String nameSpaceUri, final String localName, final String qName,
+                                 final Attributes attributes) throws SAXException {
+            if (ATTRIBUTES.equals(localName)) {
+                this.foundAttributes = true;
+            } else if (this.foundAttributes) {
+                this.value = new StringBuilder();
+                this.currentAttribute = localName;
+            }
+        }
+
+        @Override
+        public void characters(final char[] chars, final int start, final int length) throws SAXException {
+            if (this.currentAttribute != null) {
+                value.append(chars, start, length);
+            }
+        }
+
+        @Override
+        public void endElement(final String nameSpaceUri, final String localName, final String qName)
+                throws SAXException {
+            if (ATTRIBUTES.equals(localName)) {
+                this.foundAttributes = false;
+                this.currentAttribute = null;
+            } else if (this.foundAttributes) {
+                final Object o = this.attributes.get(this.currentAttribute);
+
+                if (o == null) {
+                    this.attributes.put(this.currentAttribute, this.value.toString());
+                } else {
+                    final List<Object> items;
+                    if (o instanceof List) {
+                        items = (List<Object>) o;
+                    } else {
+                        items = new LinkedList<Object>();
+                        items.add(o);
+                        this.attributes.put(this.currentAttribute, items);
+                    }
+                    items.add(this.value.toString());
+                }
+            }
+        }
+
+        public Map<String, Object> getAttributes() {
+            return this.attributes;
+        }
+    }
+    
+    
+    public static void main(String[] args) {
+		String result = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\r\n" + 
+				"    <cas:authenticationSuccess>\r\n" + 
+				"        <cas:user>admin</cas:user>\r\n" + 
+				"        <cas:attributes>\r\n" + 
+				"            <cas:credentialType>UsernamePasswordCredential</cas:credentialType>\r\n" + 
+				"            <cas:isFromNewLogin>true</cas:isFromNewLogin>\r\n" + 
+				"            <cas:authenticationDate>2019-08-01T19:33:21.527+08:00[Asia/Shanghai]</cas:authenticationDate>\r\n" + 
+				"            <cas:authenticationMethod>RestAuthenticationHandler</cas:authenticationMethod>\r\n" + 
+				"            <cas:successfulAuthenticationHandlers>RestAuthenticationHandler</cas:successfulAuthenticationHandlers>\r\n" + 
+				"            <cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>\r\n" + 
+				"        </cas:attributes>\r\n" + 
+				"    </cas:authenticationSuccess>\r\n" + 
+				"</cas:serviceResponse>";
+		
+		String errorRes = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\r\n" + 
+				"    <cas:authenticationFailure code=\"INVALID_TICKET\">未能够识别出目标 &#39;ST-5-1g-9cNES6KXNRwq-GuRET103sm0-DESKTOP-VKLS8B3&#39;票根</cas:authenticationFailure>\r\n" + 
+				"</cas:serviceResponse>";
+		
+		String error = XmlUtils.getTextForElement(errorRes, "authenticationFailure");
+		System.out.println("------"+error);
+		
+		String error2 = XmlUtils.getTextForElement(result, "authenticationFailure");
+		System.out.println("------"+error2);
+		String principal = XmlUtils.getTextForElement(result, "user");
+		System.out.println("---principal---"+principal);
+		Map<String, Object> attributes = XmlUtils.extractCustomAttributes(result);
+		System.out.println("---attributes---"+attributes);
+	}
+}

+ 9 - 9
system/system-biz/src/main/java/org/jeecg/modules/message/entity/SysMessage.java

@@ -25,23 +25,23 @@ import lombok.experimental.Accessors;
 public class SysMessage extends JeecgEntity {
 	/**推送内容*/
 	@Excel(name = "推送内容", width = 15)
-	private java.lang.String esContent;
+	private String esContent;
 	/**推送所需参数Json格式*/
 	@Excel(name = "推送所需参数Json格式", width = 15)
-	private java.lang.String esParam;
+	private String esParam;
 	/**接收人*/
 	@Excel(name = "接收人", width = 15)
-	private java.lang.String esReceiver;
+	private String esReceiver;
 	/**推送失败原因*/
 	@Excel(name = "推送失败原因", width = 15)
-	private java.lang.String esResult;
+	private String esResult;
 	/**发送次数*/
 	@Excel(name = "发送次数", width = 15)
-	private java.lang.Integer esSendNum;
+	private Integer esSendNum;
 	/**推送状态 0未推送 1推送成功 2推送失败*/
 	@Excel(name = "推送状态 0未推送 1推送成功 2推送失败", width = 15)
 	@Dict(dicCode = "msgSendStatus")
-	private java.lang.String esSendStatus;
+	private String esSendStatus;
 	/**推送时间*/
 	@Excel(name = "推送时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
 	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@@ -49,14 +49,14 @@ public class SysMessage extends JeecgEntity {
 	private java.util.Date esSendTime;
 	/**消息标题*/
 	@Excel(name = "消息标题", width = 15)
-	private java.lang.String esTitle;
+	private String esTitle;
 	/**
 	 * 推送方式:参考枚举类MessageTypeEnum
 	 */
 	@Excel(name = "推送方式", width = 15)
 	@Dict(dicCode = "messageType")
-	private java.lang.String esType;
+	private String esType;
 	/**备注*/
 	@Excel(name = "备注", width = 15)
-	private java.lang.String remark;
+	private String remark;
 }

+ 5 - 5
system/system-biz/src/main/java/org/jeecg/modules/message/entity/SysMessageTemplate.java

@@ -22,19 +22,19 @@ import lombok.experimental.Accessors;
 public class SysMessageTemplate extends JeecgEntity{
 	/**模板CODE*/
 	@Excel(name = "模板CODE", width = 15)
-	private java.lang.String templateCode;
+	private String templateCode;
 	/**模板标题*/
 	@Excel(name = "模板标题", width = 30)
-	private java.lang.String templateName;
+	private String templateName;
 	/**模板内容*/
 	@Excel(name = "模板内容", width = 50)
-	private java.lang.String templateContent;
+	private String templateContent;
 	/**模板测试json*/
 	@Excel(name = "模板测试json", width = 15)
-	private java.lang.String templateTestJson;
+	private String templateTestJson;
 	/**模板类型*/
 	@Excel(name = "模板类型", width = 15)
-	private java.lang.String templateType;
+	private String templateType;
 
 	/**已经应用/未应用  1是0否*/
 	@Excel(name = "应用状态", width = 15)

+ 74 - 12
system/system-biz/src/main/java/org/jeecg/modules/message/handle/impl/EmailSendMsgHandle.java

@@ -2,6 +2,7 @@ package org.jeecg.modules.message.handle.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
 import org.jeecg.common.api.dto.message.MessageDTO;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.util.JwtUtil;
@@ -42,6 +43,11 @@ public class EmailSendMsgHandle implements ISendMsgHandle {
     @Autowired
     private RedisUtil redisUtil;
 
+    /**
+     * 真实姓名变量
+     */
+    private static final String  realNameExp = "{REALNAME}";
+
 
 
     @Override
@@ -52,6 +58,7 @@ public class EmailSendMsgHandle implements ISendMsgHandle {
         //update-begin-author:taoyan date:20200811 for:配置类数据获取
         if(oConvertUtils.isEmpty(emailFrom)){
             StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
+            log.info("邮件配置 emailFrom:" + emailFrom);
             setEmailFrom(staticConfig.getEmailFrom());
         }
         //update-end-author:taoyan date:20200811 for:配置类数据获取
@@ -76,28 +83,83 @@ public class EmailSendMsgHandle implements ISendMsgHandle {
         List<SysUser> list = sysUserMapper.selectList(query);
         String content = messageDTO.getContent();
         String title = messageDTO.getTitle();
-        String realNameExp = "{REALNAME}";
         for(SysUser user: list){
             String email = user.getEmail();
-            if(email==null || "".equals(email)){
+            if (ObjectUtils.isEmpty(email)) {
                 continue;
             }
-            if(content.indexOf(realNameExp)>0){
-                content = content.replace(realNameExp, user.getRealname());
-            }
-            if(content.indexOf(CommonConstant.LOGIN_TOKEN)>0){
-                String token = getToken(user);
+            content=replaceContent(user,content);
+            log.info("邮件内容:"+ content);
+            sendMsg(email, title, content);
+        }
+        //发送给抄送人
+        sendMessageToCopyUser(messageDTO);
+    }
+
+    /**
+     * 发送邮件给抄送人
+     * @param messageDTO
+     */
+    public void sendMessageToCopyUser(MessageDTO messageDTO) {
+        String copyToUser = messageDTO.getCopyToUser();
+        if(ObjectUtils.isNotEmpty(copyToUser)) {
+            LambdaQueryWrapper<SysUser> query = new LambdaQueryWrapper<SysUser>().in(SysUser::getUsername, copyToUser.split(","));
+            List<SysUser> list = sysUserMapper.selectList(query);
+            String content = messageDTO.getContent();
+            String title = messageDTO.getTitle();
+
+            for (SysUser user : list) {
+                String email = user.getEmail();
+                if (ObjectUtils.isEmpty(email)) {
+                    continue;
+                }
+                content=replaceContent(user,content);
+                log.info("邮件内容:" + content);
+                JavaMailSender mailSender = (JavaMailSender) SpringContextUtils.getBean("mailSender");
+                MimeMessage message = mailSender.createMimeMessage();
+                MimeMessageHelper helper = null;
+                if (oConvertUtils.isEmpty(emailFrom)) {
+                    StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
+                    setEmailFrom(staticConfig.getEmailFrom());
+                }
                 try {
-                    content = content.replace(CommonConstant.LOGIN_TOKEN, URLEncoder.encode(token, "UTF-8"));
-                } catch (UnsupportedEncodingException e) {
-                    log.error("邮件消息token编码失败", e.getMessage());
+                    helper = new MimeMessageHelper(message, true);
+                    // 设置发送方邮箱地址
+                    helper.setFrom(emailFrom);
+                    helper.setTo(email);
+                    //设置抄送人
+                    helper.setCc(email);
+                    helper.setSubject(title);
+                    helper.setText(content, true);
+                    mailSender.send(message);
+                } catch (MessagingException e) {
+                    e.printStackTrace();
                 }
             }
-            log.info("邮件内容:"+ content);
-            sendMsg(email, title, content);
         }
     }
 
+    /**
+     * 替换邮件内容变量
+     * @param user
+     * @param content
+     * @return
+     */
+    private String replaceContent(SysUser user,String content){
+        if (content.indexOf(realNameExp) > 0) {
+            content = content.replace("$"+realNameExp,user.getRealname()).replace(realNameExp, user.getRealname());
+        }
+        if (content.indexOf(CommonConstant.LOGIN_TOKEN) > 0) {
+            String token = getToken(user);
+            try {
+                content = content.replace(CommonConstant.LOGIN_TOKEN, URLEncoder.encode(token, "UTF-8"));
+            } catch (UnsupportedEncodingException e) {
+                log.error("邮件消息token编码失败", e.getMessage());
+            }
+        }
+        return content;
+    }
+
     /**
      * 获取token
      * @param user

+ 11 - 11
system/system-biz/src/main/java/org/jeecg/modules/message/websocket/WebSocket.java

@@ -2,7 +2,6 @@ package org.jeecg.modules.message.websocket;
 
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import javax.annotation.Resource;
 import javax.websocket.*;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpoint;
@@ -11,6 +10,7 @@ import com.alibaba.fastjson.JSONObject;
 import org.jeecg.common.base.BaseMap;
 import org.jeecg.common.constant.WebsocketConst;
 import org.jeecg.common.modules.redis.client.JeecgRedisClient;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import lombok.extern.slf4j.Slf4j;
 
@@ -31,7 +31,7 @@ public class WebSocket {
      * Redis触发监听名字
      */
     public static final String REDIS_TOPIC_NAME = "socketHandler";
-    @Resource
+    @Autowired
     private JeecgRedisClient jeecgRedisClient;
 
 
@@ -111,14 +111,14 @@ public class WebSocket {
             log.debug("【系统 WebSocket】收到客户端消息:" + message);
         }
         
-        //------------------------------------------------------------------------------
-        JSONObject obj = new JSONObject();
-        //业务类型
-        obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_CHECK);
-        //消息内容
-        obj.put(WebsocketConst.MSG_TXT, "心跳响应");
-        this.pushMessage(userId, obj.toJSONString());
-        //------------------------------------------------------------------------------
+//        //------------------------------------------------------------------------------
+//        JSONObject obj = new JSONObject();
+//        //业务类型
+//        obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_CHECK);
+//        //消息内容
+//        obj.put(WebsocketConst.MSG_TXT, "心跳响应");
+//        this.pushMessage(userId, obj.toJSONString());
+//        //------------------------------------------------------------------------------
     }
 
     /**
@@ -130,7 +130,7 @@ public class WebSocket {
     @OnError
     public void onError(Session session, Throwable t) {
         log.warn("【系统 WebSocket】消息出现错误");
-        //t.printStackTrace();
+        t.printStackTrace();
     }
     //==========【系统 WebSocket接受、推送消息等方法 —— 具体服务节点推送ws消息】========================================================================================
     

+ 1 - 1
system/system-biz/src/main/java/org/jeecg/modules/monitor/controller/ActuatorRedisController.java

@@ -39,7 +39,7 @@ public class ActuatorRedisController {
     @GetMapping("/info")
     public Result<?> getRedisInfo() throws Exception {
         List<RedisInfo> infoList = this.redisService.getRedisInfo();
-        log.info(infoList.toString());
+        //log.info(infoList.toString());
         return Result.ok(infoList);
     }
 

+ 1 - 1
system/system-biz/src/main/java/org/jeecg/modules/ngalain/aop/LogRecordAspect.java

@@ -1,4 +1,4 @@
-//package org.jeecg.modules.ngalain.aop;
+package org.jeecg.modules.ngalain.aop;//package org.jeecg.modules.ngalain.aop;
 //
 //import javax.servlet.http.HttpServletRequest;
 //

+ 1 - 1
system/system-biz/src/main/java/org/jeecg/modules/ngalain/controller/NgAlainController.java

@@ -1,4 +1,4 @@
-//package org.jeecg.modules.ngalain.controller;
+package org.jeecg.modules.ngalain.controller;//package org.jeecg.modules.ngalain.controller;
 //
 //import java.util.ArrayList;
 //import java.util.List;

+ 2 - 0
system/system-biz/src/main/java/org/jeecg/modules/oss/controller/OssFileController.java

@@ -2,6 +2,7 @@ package org.jeecg.modules.oss.controller;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.system.query.QueryGenerator;
@@ -47,6 +48,7 @@ public class OssFileController {
 	@ResponseBody
 	@PostMapping("/upload")
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:ossFile:upload")
 	public Result upload(@RequestParam("file") MultipartFile multipartFile) {
 		Result result = new Result();
 		try {

+ 5 - 0
system/system-biz/src/main/java/org/jeecg/modules/oss/service/impl/OssFileServiceImpl.java

@@ -1,7 +1,9 @@
 package org.jeecg.modules.oss.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.jeecg.common.exception.JeecgBootException;
 import org.jeecg.common.util.CommonUtils;
+import org.jeecg.common.util.oConvertUtils;
 import org.jeecg.common.util.oss.OssBootUtil;
 import org.jeecg.modules.oss.entity.OssFile;
 import org.jeecg.modules.oss.mapper.OssFileMapper;
@@ -25,6 +27,9 @@ public class OssFileServiceImpl extends ServiceImpl<OssFileMapper, OssFile> impl
 		OssFile ossFile = new OssFile();
 		ossFile.setFileName(fileName);
 		String url = OssBootUtil.upload(multipartFile,"upload/test");
+		if(oConvertUtils.isEmpty(url)){
+			throw new JeecgBootException("上传文件失败! ");
+		}
 		//update-begin--Author:scott  Date:20201227 for:JT-361【文件预览】阿里云原生域名可以文件预览,自己映射域名kkfileview提示文件下载失败-------------------
 		// 返回阿里云原生域名前缀URL
 		ossFile.setUrl(OssBootUtil.getOriginalUrl(url));

+ 8 - 0
system/system-biz/src/main/java/org/jeecg/modules/quartz/controller/QuartzJobController.java

@@ -7,6 +7,7 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.constant.CommonConstant;
@@ -79,6 +80,7 @@ public class QuartzJobController {
 	 * @return
 	 */
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:quartzJob:add")
 	@RequestMapping(value = "/add", method = RequestMethod.POST)
 	public Result<?> add(@RequestBody QuartzJob quartzJob) {
 		quartzJobService.saveAndScheduleJob(quartzJob);
@@ -92,6 +94,7 @@ public class QuartzJobController {
 	 * @return
 	 */
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:quartzJob:edit")
 	@RequestMapping(value = "/edit", method ={RequestMethod.PUT, RequestMethod.POST})
 	public Result<?> eidt(@RequestBody QuartzJob quartzJob) {
 		try {
@@ -110,6 +113,7 @@ public class QuartzJobController {
 	 * @return
 	 */
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:quartzJob:delete")
 	@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 	public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
 		QuartzJob quartzJob = quartzJobService.getById(id);
@@ -128,6 +132,7 @@ public class QuartzJobController {
 	 * @return
 	 */
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:quartzJob:deleteBatch")
 	@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
 	public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
 		if (ids == null || "".equals(ids.trim())) {
@@ -147,6 +152,7 @@ public class QuartzJobController {
 	 * @return
 	 */
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:quartzJob:pause")
 	@GetMapping(value = "/pause")
 	@ApiOperation(value = "停止定时任务")
 	public Result<Object> pauseJob(@RequestParam(name = "id") String id) {
@@ -165,6 +171,7 @@ public class QuartzJobController {
 	 * @return
 	 */
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:quartzJob:resume")
 	@GetMapping(value = "/resume")
 	@ApiOperation(value = "启动定时任务")
 	public Result<Object> resumeJob(@RequestParam(name = "id") String id) {
@@ -265,6 +272,7 @@ public class QuartzJobController {
 	 * @return
 	 */
 	//@RequiresRoles("admin")
+    @RequiresPermissions("system:quartzJob:execute")
 	@GetMapping("/execute")
 	public Result<?> execute(@RequestParam(name = "id", required = true) String id) {
 		QuartzJob quartzJob = quartzJobService.getById(id);

+ 9 - 9
system/system-biz/src/main/java/org/jeecg/modules/quartz/entity/QuartzJob.java

@@ -27,36 +27,36 @@ public class QuartzJob implements Serializable {
     
 	/**id*/
 	@TableId(type = IdType.ASSIGN_ID)
-	private java.lang.String id;
+	private String id;
 	/**创建人*/
-	private java.lang.String createBy;
+	private String createBy;
 	/**创建时间*/
 	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
 	private java.util.Date createTime;
 	/**删除状态*/
-	private java.lang.Integer delFlag;
+	private Integer delFlag;
 	/**修改人*/
-	private java.lang.String updateBy;
+	private String updateBy;
 	/**修改时间*/
 	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
 	private java.util.Date updateTime;
 	/**任务类名*/
 	@Excel(name="任务类名",width=40)
-	private java.lang.String jobClassName;
+	private String jobClassName;
 	/**cron表达式*/
 	@Excel(name="cron表达式",width=30)
-	private java.lang.String cronExpression;
+	private String cronExpression;
 	/**参数*/
 	@Excel(name="参数",width=15)
-	private java.lang.String parameter;
+	private String parameter;
 	/**描述*/
 	@Excel(name="描述",width=40)
-	private java.lang.String description;
+	private String description;
 	/**状态 0正常 -1停止*/
 	@Excel(name="状态",width=15,dicCode="quartz_status")
 	@Dict(dicCode = "quartz_status")
-	private java.lang.Integer status;
+	private Integer status;
 
 }

+ 161 - 163
system/system-biz/src/main/java/org/jeecg/modules/quartz/service/impl/QuartzJobServiceImpl.java

@@ -26,167 +26,165 @@ import java.util.List;
 @Slf4j
 @Service
 public class QuartzJobServiceImpl extends ServiceImpl<QuartzJobMapper, QuartzJob> implements IQuartzJobService {
-    @Autowired
-    private QuartzJobMapper quartzJobMapper;
-    @Autowired
-    private Scheduler scheduler;
-
-    /**
-     * 立即执行的任务分组
-     */
-    private static final String JOB_TEST_GROUP = "test_group";
-
-    @Override
-    public List<QuartzJob> findByJobClassName(String jobClassName) {
-        return quartzJobMapper.findByJobClassName(jobClassName);
-    }
-
-    /**
-     * 保存&启动定时任务
-     */
-    @Override
-    @Transactional(rollbackFor = JeecgBootException.class)
-    public boolean saveAndScheduleJob(QuartzJob quartzJob) {
-        // DB设置修改
-        quartzJob.setDelFlag(CommonConstant.DEL_FLAG_0);
-        boolean success = this.save(quartzJob);
-        if (success) {
-            if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
-                // 定时器添加
-                this.schedulerAdd(quartzJob.getId(), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
-            }
-        }
-        return success;
-    }
-
-    /**
-     * 恢复定时任务
-     */
-    @Override
-    @Transactional(rollbackFor = JeecgBootException.class)
-    public boolean resumeJob(QuartzJob quartzJob) {
-        schedulerDelete(quartzJob.getId());
-        schedulerAdd(quartzJob.getId(), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
-        quartzJob.setStatus(CommonConstant.STATUS_NORMAL);
-        return this.updateById(quartzJob);
-    }
-
-    /**
-     * 编辑&启停定时任务
-     *
-     * @throws SchedulerException
-     */
-    @Override
-    @Transactional(rollbackFor = JeecgBootException.class)
-    public boolean editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException {
-        if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
-            schedulerDelete(quartzJob.getId());
-            schedulerAdd(quartzJob.getId(), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
-        } else {
-            scheduler.pauseJob(JobKey.jobKey(quartzJob.getId()));
-        }
-        return this.updateById(quartzJob);
-    }
-
-    /**
-     * 删除&停止删除定时任务
-     */
-    @Override
-    @Transactional(rollbackFor = JeecgBootException.class)
-    public boolean deleteAndStopJob(QuartzJob job) {
-        schedulerDelete(job.getId());
-        boolean ok = this.removeById(job.getId());
-        return ok;
-    }
-
-    @Override
-    public void execute(QuartzJob quartzJob) throws Exception {
-        String jobName = quartzJob.getJobClassName().trim();
-        Date startDate = new Date();
-        String ymd = DateUtils.date2Str(startDate, DateUtils.yyyymmddhhmmss.get());
-        String identity = jobName + ymd;
-        //3秒后执行 只执行一次
-        // update-begin--author:sunjianlei ---- date:20210511--- for:定时任务立即执行,延迟3秒改成0.1秒-------
-        startDate.setTime(startDate.getTime() + 100L);
-        // update-end--author:sunjianlei ---- date:20210511--- for:定时任务立即执行,延迟3秒改成0.1秒-------
-        // 定义一个Trigger
-        SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
-                .withIdentity(identity, JOB_TEST_GROUP)
-                .startAt(startDate)
-                .build();
-        // 构建job信息
-        JobDetail jobDetail = JobBuilder.newJob(getClass(jobName).getClass()).withIdentity(identity).usingJobData("parameter", quartzJob.getParameter()).build();
-        // 将trigger和 jobDetail 加入这个调度
-        scheduler.scheduleJob(jobDetail, trigger);
-        // 启动scheduler
-        scheduler.start();
-    }
-
-    @Override
-    @Transactional(rollbackFor = JeecgBootException.class)
-    public void pause(QuartzJob quartzJob) {
-        schedulerDelete(quartzJob.getId());
-        quartzJob.setStatus(CommonConstant.STATUS_DISABLE);
-        this.updateById(quartzJob);
-    }
-
-    /**
-     * 添加定时任务
-     *
-     * @param jobClassName
-     * @param cronExpression
-     * @param parameter
-     */
-    private void schedulerAdd(String id, String jobClassName, String cronExpression, String parameter) {
-        try {
-            // 启动调度器
-            scheduler.start();
-
-            // 构建job信息
-            JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(id).usingJobData("parameter", parameter).build();
-
-            // 表达式调度构建器(即任务执行的时间)
-            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
-
-            // 按新的cronExpression表达式构建一个新的trigger
-            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(id).withSchedule(scheduleBuilder).build();
-
-            scheduler.scheduleJob(jobDetail, trigger);
-        } catch (SchedulerException e) {
-            throw new JeecgBootException("创建定时任务失败", e);
-        } catch (RuntimeException e) {
-            throw new JeecgBootException(e.getMessage(), e);
-        } catch (Exception e) {
-            throw new JeecgBootException("后台找不到该类名:" + jobClassName, e);
-        }
-    }
-
-    /**
-     * 删除定时任务
-     *
-     * @param id
-     */
-    private void schedulerDelete(String id) {
-        try {
-            scheduler.pauseTrigger(TriggerKey.triggerKey(id));
-            scheduler.unscheduleJob(TriggerKey.triggerKey(id));
-            scheduler.deleteJob(JobKey.jobKey(id));
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new JeecgBootException("删除定时任务失败");
-        }
-    }
-
-    private static Job getClass(String classname) throws Exception {
-        Class<?> class1 = Class.forName(classname);
-        return (Job) class1.newInstance();
-    }
-
-    @Override
-    public List<QuartzJob> findByCondition(String jobClassName, String parameter) {
-        LambdaQueryWrapper<QuartzJob> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(QuartzJob::getJobClassName, jobClassName).eq(QuartzJob::getParameter, parameter);
-        return this.list(queryWrapper);
-    }
-
+	@Autowired
+	private QuartzJobMapper quartzJobMapper;
+	@Autowired
+	private Scheduler scheduler;
+
+	/**
+	 * 立即执行的任务分组
+	 */
+	private static final String JOB_TEST_GROUP = "test_group";
+
+	@Override
+	public List<QuartzJob> findByJobClassName(String jobClassName) {
+		return quartzJobMapper.findByJobClassName(jobClassName);
+	}
+
+	/**
+	 * 保存&启动定时任务
+	 */
+	@Override
+	@Transactional(rollbackFor = JeecgBootException.class)
+	public boolean saveAndScheduleJob(QuartzJob quartzJob) {
+		// DB设置修改
+		quartzJob.setDelFlag(CommonConstant.DEL_FLAG_0);
+		boolean success = this.save(quartzJob);
+		if (success) {
+			if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
+				// 定时器添加
+				this.schedulerAdd(quartzJob.getId(), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
+			}
+		}
+		return success;
+	}
+
+	/**
+	 * 恢复定时任务
+	 */
+	@Override
+	@Transactional(rollbackFor = JeecgBootException.class)
+	public boolean resumeJob(QuartzJob quartzJob) {
+		schedulerDelete(quartzJob.getId());
+		schedulerAdd(quartzJob.getId(), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
+		quartzJob.setStatus(CommonConstant.STATUS_NORMAL);
+		return this.updateById(quartzJob);
+	}
+
+	/**
+	 * 编辑&启停定时任务
+	 * @throws SchedulerException 
+	 */
+	@Override
+	@Transactional(rollbackFor = JeecgBootException.class)
+	public boolean editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException {
+		if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
+			schedulerDelete(quartzJob.getId());
+			schedulerAdd(quartzJob.getId(), quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
+		}else{
+			scheduler.pauseJob(JobKey.jobKey(quartzJob.getId()));
+		}
+		return this.updateById(quartzJob);
+	}
+
+	/**
+	 * 删除&停止删除定时任务
+	 */
+	@Override
+	@Transactional(rollbackFor = JeecgBootException.class)
+	public boolean deleteAndStopJob(QuartzJob job) {
+		schedulerDelete(job.getId());
+		boolean ok = this.removeById(job.getId());
+		return ok;
+	}
+
+	@Override
+	public void execute(QuartzJob quartzJob) throws Exception {
+		String jobName = quartzJob.getJobClassName().trim();
+		Date startDate = new Date();
+		String ymd = DateUtils.date2Str(startDate,DateUtils.yyyymmddhhmmss.get());
+		String identity =  jobName + ymd;
+		//3秒后执行 只执行一次
+		// update-begin--author:sunjianlei ---- date:20210511--- for:定时任务立即执行,延迟3秒改成0.1秒-------
+		startDate.setTime(startDate.getTime() + 100L);
+		// update-end--author:sunjianlei ---- date:20210511--- for:定时任务立即执行,延迟3秒改成0.1秒-------
+		// 定义一个Trigger
+		SimpleTrigger trigger = (SimpleTrigger)TriggerBuilder.newTrigger()
+				.withIdentity(identity, JOB_TEST_GROUP)
+				.startAt(startDate)
+				.build();
+		// 构建job信息
+		JobDetail jobDetail = JobBuilder.newJob(getClass(jobName).getClass()).withIdentity(identity).usingJobData("parameter", quartzJob.getParameter()).build();
+		// 将trigger和 jobDetail 加入这个调度
+		scheduler.scheduleJob(jobDetail, trigger);
+		// 启动scheduler
+		scheduler.start();
+	}
+
+	@Override
+	@Transactional(rollbackFor = JeecgBootException.class)
+	public void pause(QuartzJob quartzJob){
+		schedulerDelete(quartzJob.getId());
+		quartzJob.setStatus(CommonConstant.STATUS_DISABLE);
+		this.updateById(quartzJob);
+	}
+
+	/**
+	 * 添加定时任务
+	 *
+	 * @param jobClassName
+	 * @param cronExpression
+	 * @param parameter
+	 */
+	private void schedulerAdd(String id, String jobClassName, String cronExpression, String parameter) {
+		try {
+			// 启动调度器
+			scheduler.start();
+
+			// 构建job信息
+			JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(id).usingJobData("parameter", parameter).build();
+
+			// 表达式调度构建器(即任务执行的时间)
+			CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
+
+			// 按新的cronExpression表达式构建一个新的trigger
+			CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(id).withSchedule(scheduleBuilder).build();
+
+			scheduler.scheduleJob(jobDetail, trigger);
+		} catch (SchedulerException e) {
+			throw new JeecgBootException("创建定时任务失败", e);
+		} catch (RuntimeException e) {
+			throw new JeecgBootException(e.getMessage(), e);
+		}catch (Exception e) {
+			throw new JeecgBootException("后台找不到该类名:" + jobClassName, e);
+		}
+	}
+
+	/**
+	 * 删除定时任务
+	 * 
+	 * @param id
+	 */
+	private void schedulerDelete(String id) {
+		try {
+			scheduler.pauseTrigger(TriggerKey.triggerKey(id));
+			scheduler.unscheduleJob(TriggerKey.triggerKey(id));
+			scheduler.deleteJob(JobKey.jobKey(id));
+		} catch (Exception e) {
+			log.error(e.getMessage(), e);
+			throw new JeecgBootException("删除定时任务失败");
+		}
+	}
+
+	private static Job getClass(String classname) throws Exception {
+		Class<?> class1 = Class.forName(classname);
+		return (Job) class1.newInstance();
+	}
+
+	@Override
+	public List<QuartzJob> findByCondition(String jobClassName, String parameter) {
+		LambdaQueryWrapper<QuartzJob> queryWrapper = new LambdaQueryWrapper<>();
+		queryWrapper.eq(QuartzJob::getJobClassName, jobClassName).eq(QuartzJob::getParameter, parameter);
+		return this.list(queryWrapper);
+	}
 }

+ 13 - 37
system/system-biz/src/main/java/org/jeecg/modules/system/controller/DuplicateCheckController.java

@@ -1,22 +1,18 @@
 package org.jeecg.modules.system.controller;
 
-import javax.servlet.http.HttpServletRequest;
-
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.jeecg.common.api.vo.Result;
-import org.jeecg.common.constant.SymbolConstant;
-import org.jeecg.common.util.SqlInjectionUtil;
-import org.jeecg.modules.system.mapper.SysDictMapper;
 import org.jeecg.modules.system.model.DuplicateCheckVo;
-import org.jeecg.modules.system.security.DictQueryBlackListHandler;
+import org.jeecg.modules.system.service.ISysDictService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
 
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
+import javax.servlet.http.HttpServletRequest;
 
 /**
  * @Title: DuplicateCheckAction
@@ -32,10 +28,7 @@ import lombok.extern.slf4j.Slf4j;
 public class DuplicateCheckController {
 
 	@Autowired
-	SysDictMapper sysDictMapper;
-
-	@Autowired
-	DictQueryBlackListHandler dictQueryBlackListHandler;
+    ISysDictService sysDictService;
 
 	/**
 	 * 校验数据是否在系统中是否存在
@@ -45,14 +38,9 @@ public class DuplicateCheckController {
 	@RequestMapping(value = "/check", method = RequestMethod.GET)
 	@ApiOperation("重复校验接口")
 	public Result<String> doDuplicateCheck(DuplicateCheckVo duplicateCheckVo, HttpServletRequest request) {
-		Long num = null;
-
-		log.info("----duplicate check------:"+ duplicateCheckVo.toString());
-		//关联表字典(举例:sys_user,realname,id)
-		//SQL注入校验(只限制非法串改数据库)
-		final String[] sqlInjCheck = {duplicateCheckVo.getTableName(),duplicateCheckVo.getFieldName()};
-		SqlInjectionUtil.filterContent(sqlInjCheck);
-		// update-begin-author:taoyan date:20211227 for: JTC-25 【online报表】oracle 操作问题 录入弹框啥都不填直接保存 ①编码不是应该提示必填么?②报错也应该是具体文字提示,不是后台错误日志
+		log.debug("----duplicate check------:"+ duplicateCheckVo.toString());
+		
+		// 1.填值为空,直接返回
 		if(StringUtils.isEmpty(duplicateCheckVo.getFieldVal())){
 			Result rs = new Result();
 			rs.setCode(500);
@@ -60,22 +48,9 @@ public class DuplicateCheckController {
 			rs.setMessage("数据为空,不作处理!");
 			return rs;
 		}
-		//update-begin-author:taoyan date:20220329 for: VUEN-223【安全漏洞】当前被攻击的接口
-		String checkSql = duplicateCheckVo.getTableName() + SymbolConstant.COMMA + duplicateCheckVo.getFieldName() + SymbolConstant.COMMA;
-		if(!dictQueryBlackListHandler.isPass(checkSql)){
-			return Result.error(dictQueryBlackListHandler.getError());
-		}
-		//update-end-author:taoyan date:20220329 for: VUEN-223【安全漏洞】当前被攻击的接口
-		// update-end-author:taoyan date:20211227 for: JTC-25 【online报表】oracle 操作问题 录入弹框啥都不填直接保存 ①编码不是应该提示必填么?②报错也应该是具体文字提示,不是后台错误日志
-		if (StringUtils.isNotBlank(duplicateCheckVo.getDataId())) {
-			// [2].编辑页面校验
-			num = sysDictMapper.duplicateCheckCountSql(duplicateCheckVo);
-		} else {
-			// [1].添加页面校验
-			num = sysDictMapper.duplicateCheckCountSqlNoDataId(duplicateCheckVo);
-		}
-
-		if (num == null || num == 0) {
+		
+		// 2.返回结果
+		if (sysDictService.duplicateCheckData(duplicateCheckVo)) {
 			// 该值可用
 			return Result.ok("该值可用!");
 		} else {
@@ -84,4 +59,5 @@ public class DuplicateCheckController {
 			return Result.error("该值不可用,系统中已存在!");
 		}
 	}
+	
 }

+ 87 - 26
system/system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java

@@ -9,6 +9,7 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.constant.CacheConstant;
 import org.jeecg.common.constant.CommonConstant;
@@ -76,6 +77,11 @@ public class LoginController {
 		Result<JSONObject> result = new Result<JSONObject>();
 		String username = sysLoginModel.getUsername();
 		String password = sysLoginModel.getPassword();
+		//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
+		if(isLoginFailOvertimes(username)){
+			return result.error500("该用户登录失败次数过多,请于10分钟后再次登录!");
+		}
+		//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
 		//update-begin--Author:scott  Date:20190805 for:暂时注释掉密码加密逻辑,有点问题
 		//前端密码加密,后端进行密码解密
 		//password = AesEncryptUtil.desEncrypt(sysLoginModel.getPassword().replaceAll("%2B", "\\+")).trim();//密码解密
@@ -119,6 +125,9 @@ public class LoginController {
 		String userpassword = PasswordUtil.encrypt(username, password, sysUser.getSalt());
 		String syspassword = sysUser.getPassword();
 		if (!syspassword.equals(userpassword)) {
+			//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
+			addLoginFailOvertimes(username);
+			//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
 			result.error500("用户名或密码错误");
 			return result;
 		}
@@ -128,6 +137,7 @@ public class LoginController {
 		//update-begin--Author:liusq  Date:20210126  for:登录成功,删除redis中的验证码
 		redisUtil.del(realKey);
 		//update-begin--Author:liusq  Date:20210126  for:登录成功,删除redis中的验证码
+		redisUtil.del(CommonConstant.LOGIN_FAIL + username);
 		LoginUser loginUser = new LoginUser();
 		BeanUtils.copyProperties(sysUser, loginUser);
 		baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
@@ -271,8 +281,13 @@ public class LoginController {
 			LoginUser sysUser = (LoginUser)SecurityUtils.getSubject().getPrincipal();
 			username = sysUser.getUsername();
 		}
+		
+		//获取登录部门
 		String orgCode= user.getOrgCode();
-		this.sysUserService.updateUserDepart(username, orgCode);
+		//获取登录租户
+		Integer tenantId = user.getLoginTenantId();
+		//设置用户登录部门和登录租户
+		this.sysUserService.updateUserDepart(username, orgCode,tenantId);
 		SysUser sysUser = sysUserService.getUserByName(username);
 		JSONObject obj = new JSONObject();
 		obj.put("userInfo", sysUser);
@@ -386,7 +401,11 @@ public class LoginController {
 	public Result<JSONObject> phoneLogin(@RequestBody JSONObject jsonObject) {
 		Result<JSONObject> result = new Result<JSONObject>();
 		String phone = jsonObject.getString("mobile");
-		
+		//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
+		if(isLoginFailOvertimes(phone)){
+			return result.error500("该用户登录失败次数过多,请于10分钟后再次登录!");
+		}
+		//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
 		//校验用户有效性
 		SysUser sysUser = sysUserService.getUserByPhone(phone);
 		result = sysUserService.checkUserIsEffective(sysUser);
@@ -402,6 +421,9 @@ public class LoginController {
 		//update-end-author:taoyan date:2022-9-13 for: VUEN-2245 【漏洞】发现新漏洞待处理20220906
 
 		if (!smscode.equals(code)) {
+			//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
+			addLoginFailOvertimes(phone);
+			//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
 			result.setMessage("手机验证码错误");
 			return result;
 		}
@@ -427,46 +449,36 @@ public class LoginController {
 		// 获取用户部门信息
 		JSONObject obj = new JSONObject(new LinkedHashMap<>());
 
-		// 生成token
+		//1.生成token
 		String token = JwtUtil.sign(username, syspassword);
 		// 设置token缓存有效时间
 		redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
 		redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
 		obj.put("token", token);
 
-		// update-begin--Author:sunjianlei Date:20210802 for:获取用户租户信息
-		String tenantIds = sysUser.getRelTenantIds();
-		if (oConvertUtils.isNotEmpty(tenantIds)) {
-			List<Integer> tenantIdList = new ArrayList<>();
-			for(String id: tenantIds.split(SymbolConstant.COMMA)){
-				tenantIdList.add(Integer.valueOf(id));
-			}
-			// 该方法仅查询有效的租户,如果返回0个就说明所有的租户均无效。
-			List<SysTenant> tenantList = sysTenantService.queryEffectiveTenant(tenantIdList);
-			if (tenantList.size() == 0) {
-				result.error500("与该用户关联的租户均已被冻结,无法登录!");
-				return result;
-			} else {
-				obj.put("tenantList", tenantList);
-			}
+		//2.设置登录租户
+		Result<JSONObject> loginTenantError = sysUserService.setLoginTenant(sysUser, obj, username,result);
+		if (loginTenantError != null) {
+			return loginTenantError;
 		}
-		// update-end--Author:sunjianlei Date:20210802 for:获取用户租户信息
 
+		//3.设置登录用户信息
 		obj.put("userInfo", sysUser);
-
+		
+		//4.设置登录部门
 		List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
 		obj.put("departs", departs);
 		if (departs == null || departs.size() == 0) {
 			obj.put("multi_depart", 0);
 		} else if (departs.size() == 1) {
-			sysUserService.updateUserDepart(username, departs.get(0).getOrgCode());
+			sysUserService.updateUserDepart(username, departs.get(0).getOrgCode(),null);
 			obj.put("multi_depart", 1);
 		} else {
 			//查询当前是否有登录部门
 			// update-begin--Author:wangshuai Date:20200805 for:如果用戶为选择部门,数据库为存在上一次登录部门,则取一条存进去
 			SysUser sysUserById = sysUserService.getById(sysUser.getId());
 			if(oConvertUtils.isEmpty(sysUserById.getOrgCode())){
-				sysUserService.updateUserDepart(username, departs.get(0).getOrgCode());
+				sysUserService.updateUserDepart(username, departs.get(0).getOrgCode(),null);
 			}
 			// update-end--Author:wangshuai Date:20200805 for:如果用戶为选择部门,数据库为存在上一次登录部门,则取一条存进去
 			obj.put("multi_depart", 2);
@@ -529,6 +541,7 @@ public class LoginController {
 	/**
 	 * 切换菜单表为vue3的表
 	 */
+	@RequiresRoles({"admin"})
 	@GetMapping(value = "/switchVue3Menu")
 	public Result<String> switchVue3Menu(HttpServletResponse response) {
 		Result<String> res = new Result<String>();
@@ -547,7 +560,13 @@ public class LoginController {
 		Result<JSONObject> result = new Result<JSONObject>();
 		String username = sysLoginModel.getUsername();
 		String password = sysLoginModel.getPassword();
+		JSONObject obj = new JSONObject();
 		
+		//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
+		if(isLoginFailOvertimes(username)){
+			return result.error500("该用户登录失败次数过多,请于10分钟后再次登录!");
+		}
+		//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
 		//1. 校验用户是否有效
 		SysUser sysUser = sysUserService.getUserByName(username);
 		result = sysUserService.checkUserIsEffective(sysUser);
@@ -559,10 +578,14 @@ public class LoginController {
 		String userpassword = PasswordUtil.encrypt(username, password, sysUser.getSalt());
 		String syspassword = sysUser.getPassword();
 		if (!syspassword.equals(userpassword)) {
+			//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
+			addLoginFailOvertimes(username);
+			//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
 			result.error500("用户名或密码错误");
 			return result;
 		}
 		
+		//3.设置登录部门
 		String orgCode = sysUser.getOrgCode();
 		if(oConvertUtils.isEmpty(orgCode)) {
 			//如果当前用户无选择部门 查看部门关联信息
@@ -574,15 +597,21 @@ public class LoginController {
 			}else{
 				orgCode = departs.get(0).getOrgCode();
 				sysUser.setOrgCode(orgCode);
-				this.sysUserService.updateUserDepart(username, orgCode);
+				this.sysUserService.updateUserDepart(username, orgCode,null);
 			}
 			//update-end-author:taoyan date:20220117 for: JTC-1068【app】新建用户,没有设置部门及角色,点击登录提示暂未归属部,一直在登录页面 使用手机号登录 可正常
 		}
-		JSONObject obj = new JSONObject();
-		//用户登录信息
+
+		//4. 设置登录租户
+		Result<JSONObject> loginTenantError = sysUserService.setLoginTenant(sysUser, obj, username, result);
+		if (loginTenantError != null) {
+			return loginTenantError;
+		}
+
+		//5. 设置登录用户信息
 		obj.put("userInfo", sysUser);
 		
-		// 生成token
+		//6. 生成token
 		String token = JwtUtil.sign(username, syspassword);
 		// 设置超时时间
 		redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
@@ -671,4 +700,36 @@ public class LoginController {
 		return Result.OK(result);
 	}
 
+	/**
+	 * 登录失败超出次数5 返回true
+	 * @param username
+	 * @return
+	 */
+	private boolean isLoginFailOvertimes(String username){
+		String key = CommonConstant.LOGIN_FAIL + username;
+		Object failTime = redisUtil.get(key);
+		if(failTime!=null){
+			Integer val = Integer.parseInt(failTime.toString());
+			if(val>5){
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 记录登录失败次数
+	 * @param username
+	 */
+	private void addLoginFailOvertimes(String username){
+		String key = CommonConstant.LOGIN_FAIL + username;
+		Object failTime = redisUtil.get(key);
+		Integer val = 0;
+		if(failTime!=null){
+			val = Integer.parseInt(failTime.toString());
+		}
+		// 10分钟
+		redisUtil.set(key, ++val, 10);
+	}
+
 }

+ 33 - 0
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysAnnouncementController.java

@@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.constant.CommonSendStatus;
 import org.jeecg.common.constant.WebsocketConst;
@@ -21,6 +22,7 @@ import org.jeecg.common.util.DateUtils;
 import org.jeecg.common.util.RedisUtil;
 import org.jeecg.common.util.TokenUtils;
 import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.modules.message.enums.RangeDateEnum;
 import org.jeecg.modules.message.websocket.WebSocket;
 import org.jeecg.modules.system.entity.SysAnnouncement;
@@ -93,6 +95,12 @@ public class SysAnnouncementController {
 									  @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
 									  @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
 									  HttpServletRequest req) {
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			sysAnnouncement.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+		}
+		//------------------------------------------------------------------------------------------------
 		Result<IPage<SysAnnouncement>> result = new Result<IPage<SysAnnouncement>>();
 		sysAnnouncement.setDelFlag(CommonConstant.DEL_FLAG_0.toString());
 		QueryWrapper<SysAnnouncement> queryWrapper = QueryGenerator.initQueryWrapper(sysAnnouncement, req.getParameterMap());
@@ -547,7 +555,32 @@ public class SysAnnouncementController {
 				sysAnnouncementService.updateReaded(annoceIdList);
 			}
 		}
+		//update-begin-author:taoyan date:2022-9-25 for: VUEN-2261【移动端 系统消息】通知公告显示7条消息,点进去查看后,仍然显示7条;其他地方已读后,未读条数减少
+		JSONObject obj = new JSONObject();
+		obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_USER);
+		LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+		webSocket.sendMessage(sysUser.getId(), obj.toJSONString());
+		//update-end-author:taoyan date:2022-9-25 for: VUEN-2261【移动端 系统消息】通知公告显示7条消息,点进去查看后,仍然显示7条;其他地方已读后,未读条数减少
 		return Result.ok(ls);
 	}
 
+
+    /**
+     * 根据用户id获取最新一条消息发送时间(创建时间)
+     * @param userId
+     * @return
+     */
+	@GetMapping("/getLastAnnountTime")
+	public Result<Page<SysAnnouncementSend>> getLastAnnountTime(@RequestParam(name = "userId") String userId){
+        Result<Page<SysAnnouncementSend>> result = new Result<>();
+        Page<SysAnnouncementSend> page = new Page<>(1,1);
+        LambdaQueryWrapper<SysAnnouncementSend> query = new LambdaQueryWrapper<>();
+        query.eq(SysAnnouncementSend::getUserId,userId);
+        query.select(SysAnnouncementSend::getCreateTime);
+        query.orderByDesc(SysAnnouncementSend::getCreateTime);
+        Page<SysAnnouncementSend> pageList = sysAnnouncementSendService.page(page, query);
+        result.setSuccess(true);
+        result.setResult(pageList);
+        return result;
+    }
 }

+ 21 - 0
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysCategoryController.java

@@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.system.vo.DictModel;
@@ -16,6 +17,7 @@ import org.jeecg.common.system.vo.LoginUser;
 import org.jeecg.common.util.ImportExcelUtil;
 import org.jeecg.common.util.SqlInjectionUtil;
 import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.modules.system.entity.SysCategory;
 import org.jeecg.modules.system.model.TreeSelectModel;
 import org.jeecg.modules.system.service.ISysCategoryService;
@@ -71,6 +73,12 @@ public class SysCategoryController {
 			sysCategory.setPid("0");
 		}
 		Result<IPage<SysCategory>> result = new Result<IPage<SysCategory>>();
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			sysCategory.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(),0));
+		}
+		//------------------------------------------------------------------------------------------------
 		
 		//--author:os_chengtgen---date:20190804 -----for: 分类字典页面显示错误,issues:377--------start
 		//--author:liusq---date:20211119 -----for: 【vue3】分类字典页面查询条件配置--------start
@@ -93,6 +101,12 @@ public class SysCategoryController {
 	
 	@GetMapping(value = "/childList")
 	public Result<List<SysCategory>> queryPageList(SysCategory sysCategory,HttpServletRequest req) {
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			sysCategory.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+		}
+		//------------------------------------------------------------------------------------------------
 		Result<List<SysCategory>> result = new Result<List<SysCategory>>();
 		QueryWrapper<SysCategory> queryWrapper = QueryGenerator.initQueryWrapper(sysCategory, req.getParameterMap());
 		List<SysCategory> list = sysCategoryService.list(queryWrapper);
@@ -199,6 +213,13 @@ public class SysCategoryController {
    */
   @RequestMapping(value = "/exportXls")
   public ModelAndView exportXls(HttpServletRequest request, SysCategory sysCategory) {
+	  //------------------------------------------------------------------------------------------------
+	  //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+	  if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+		  sysCategory.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+	  }
+	  //------------------------------------------------------------------------------------------------
+	  
       // Step.1 组装查询条件查询数据
       QueryWrapper<SysCategory> queryWrapper = QueryGenerator.initQueryWrapper(sysCategory, request.getParameterMap());
       List<SysCategory> pageList = sysCategoryService.list(queryWrapper);

+ 3 - 3
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysCommentController.java

@@ -97,7 +97,7 @@ public class SysCommentController extends JeecgController<SysComment, ISysCommen
             sysCommentService.saveOneFileComment(request);
             return Result.OK("success");
         } catch (Exception e) {
-            log.error("评论文件上传失败", e.getMessage());
+            log.error("评论文件上传失败:{}", e.getMessage());
             return Result.error("操作失败," + e.getMessage());
         }
     }
@@ -144,7 +144,7 @@ public class SysCommentController extends JeecgController<SysComment, ISysCommen
      * @param req
      * @return
      */
-    ////@AutoLog(value = "系统评论回复表-分页列表查询")
+    //@AutoLog(value = "系统评论回复表-分页列表查询")
     @ApiOperation(value = "系统评论回复表-分页列表查询", notes = "系统评论回复表-分页列表查询")
     @GetMapping(value = "/list")
     public Result<IPage<SysComment>> queryPageList(SysComment sysComment,
@@ -223,7 +223,7 @@ public class SysCommentController extends JeecgController<SysComment, ISysCommen
      * @param id
      * @return
      */
-    ////@AutoLog(value = "系统评论回复表-通过id查询")
+    //@AutoLog(value = "系统评论回复表-通过id查询")
     @ApiOperation(value = "系统评论回复表-通过id查询", notes = "系统评论回复表-通过id查询")
     @GetMapping(value = "/queryById")
     public Result<SysComment> queryById(@RequestParam(name = "id", required = true) String id) {

+ 25 - 7
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDataSourceController.java

@@ -3,9 +3,6 @@ package org.jeecg.modules.system.controller;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
-import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
-import com.baomidou.dynamic.datasource.creator.DruidDataSourceCreator;
-import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -13,14 +10,17 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
-import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.annotation.AutoLog;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.exception.JeecgBootException;
 import org.jeecg.common.system.base.controller.JeecgController;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.util.dynamic.db.DataSourceCachePool;
+import org.jeecg.common.util.oConvertUtils;
 import org.jeecg.common.util.security.JdbcSecurityUtil;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.modules.system.entity.SysDataSource;
 import org.jeecg.modules.system.service.ISysDataSourceService;
 import org.jeecg.modules.system.util.SecurityUtil;
@@ -61,14 +61,19 @@ public class SysDataSourceController extends JeecgController<SysDataSource, ISys
      */
     @AutoLog(value = "多数据源管理-分页列表查询")
     @ApiOperation(value = "多数据源管理-分页列表查询", notes = "多数据源管理-分页列表查询")
-    //@RequiresRoles("admin")
+    @RequiresPermissions("system:datasource:list")
     @GetMapping(value = "/list")
     public Result<?> queryPageList(
             SysDataSource sysDataSource,
             @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
             @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
-            HttpServletRequest req
-    ) {
+            HttpServletRequest req) {
+        //------------------------------------------------------------------------------------------------
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+            sysDataSource.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+        }
+        //------------------------------------------------------------------------------------------------
         QueryWrapper<SysDataSource> queryWrapper = QueryGenerator.initQueryWrapper(sysDataSource, req.getParameterMap());
         Page<SysDataSource> page = new Page<>(pageNo, pageSize);
         IPage<SysDataSource> pageList = sysDataSourceService.page(page, queryWrapper);
@@ -77,6 +82,13 @@ public class SysDataSourceController extends JeecgController<SysDataSource, ISys
 
     @GetMapping(value = "/options")
     public Result<?> queryOptions(SysDataSource sysDataSource, HttpServletRequest req) {
+        //------------------------------------------------------------------------------------------------
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+            sysDataSource.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+        }
+        //------------------------------------------------------------------------------------------------
+        
         QueryWrapper<SysDataSource> queryWrapper = QueryGenerator.initQueryWrapper(sysDataSource, req.getParameterMap());
         List<SysDataSource> pageList = sysDataSourceService.list(queryWrapper);
         JSONArray array = new JSONArray(pageList.size());
@@ -192,6 +204,12 @@ public class SysDataSourceController extends JeecgController<SysDataSource, ISys
      */
     @RequestMapping(value = "/exportXls")
     public ModelAndView exportXls(HttpServletRequest request, SysDataSource sysDataSource) {
+        //------------------------------------------------------------------------------------------------
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+            sysDataSource.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+        }
+        //------------------------------------------------------------------------------------------------
         return super.exportXls(request, sysDataSource, SysDataSource.class, "多数据源管理");
     }
 

+ 63 - 15
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartController.java

@@ -6,8 +6,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.constant.CacheConstant;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.query.QueryGenerator;
@@ -16,6 +17,7 @@ import org.jeecg.common.system.vo.LoginUser;
 import org.jeecg.common.util.ImportExcelUtil;
 import org.jeecg.common.util.YouBianCodeUtil;
 import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.modules.system.entity.SysDepart;
 import org.jeecg.modules.system.entity.SysUser;
 import org.jeecg.modules.system.model.DepartIdModel;
@@ -148,8 +150,7 @@ public class SysDepartController {
 	@GetMapping("/queryAllParentId")
 	public Result queryParentIds(
 			@RequestParam(name = "departId", required = false) String departId,
-			@RequestParam(name = "orgCode", required = false) String orgCode
-	) {
+			@RequestParam(name = "orgCode", required = false) String orgCode) {
 		try {
 			JSONObject data;
 			if (oConvertUtils.isNotEmpty(departId)) {
@@ -172,7 +173,7 @@ public class SysDepartController {
 	 * @param sysDepart
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:depart:add")
 	@RequestMapping(value = "/add", method = RequestMethod.POST)
 	@CacheEvict(value= {CacheConstant.SYS_DEPARTS_CACHE,CacheConstant.SYS_DEPART_IDS_CACHE}, allEntries=true)
 	public Result<SysDepart> add(@RequestBody SysDepart sysDepart, HttpServletRequest request) {
@@ -198,7 +199,7 @@ public class SysDepartController {
 	 * @param sysDepart
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:depart:edit")
 	@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
 	@CacheEvict(value= {CacheConstant.SYS_DEPARTS_CACHE,CacheConstant.SYS_DEPART_IDS_CACHE}, allEntries=true)
 	public Result<SysDepart> edit(@RequestBody SysDepart sysDepart, HttpServletRequest request) {
@@ -226,7 +227,7 @@ public class SysDepartController {
     * @param id
     * @return
     */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:depart:delete")
     @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 	@CacheEvict(value= {CacheConstant.SYS_DEPARTS_CACHE,CacheConstant.SYS_DEPART_IDS_CACHE}, allEntries=true)
    public Result<SysDepart> delete(@RequestParam(name="id",required=true) String id) {
@@ -236,13 +237,11 @@ public class SysDepartController {
        if(sysDepart==null) {
            result.error500("未找到对应实体");
        }else {
-           boolean ok = sysDepartService.delete(id);
-           if(ok) {
-	            //清除部门树内存
-	   		   //FindsDepartsChildrenUtil.clearSysDepartTreeList();
-	   		   // FindsDepartsChildrenUtil.clearDepartIdModel();
-               result.success("删除成功!");
-           }
+           sysDepartService.deleteDepart(id);
+			//清除部门树内存
+		   //FindsDepartsChildrenUtil.clearSysDepartTreeList();
+		   // FindsDepartsChildrenUtil.clearDepartIdModel();
+		   result.success("删除成功!");
        }
        return result;
    }
@@ -254,7 +253,7 @@ public class SysDepartController {
 	 * @param ids
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:depart:deleteBatch")
 	@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
 	@CacheEvict(value= {CacheConstant.SYS_DEPARTS_CACHE,CacheConstant.SYS_DEPART_IDS_CACHE}, allEntries=true)
 	public Result<SysDepart> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
@@ -341,6 +340,13 @@ public class SysDepartController {
      */
     @RequestMapping(value = "/exportXls")
     public ModelAndView exportXls(SysDepart sysDepart,HttpServletRequest request) {
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			sysDepart.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+		}
+		//------------------------------------------------------------------------------------------------
+		
         // Step.1 组装查询条件
         QueryWrapper<SysDepart> queryWrapper = QueryGenerator.initQueryWrapper(sysDepart, request.getParameterMap());
         //Step.2 AutoPoi 导出Excel
@@ -371,7 +377,7 @@ public class SysDepartController {
      * @param response
      * @return
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:depart:importExcel")
     @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
 	@CacheEvict(value= {CacheConstant.SYS_DEPARTS_CACHE,CacheConstant.SYS_DEPART_IDS_CACHE}, allEntries=true)
     public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
@@ -526,4 +532,46 @@ public class SysDepartController {
 		result.setResult(sysUsers);
 		return result;
 	}
+
+	/**
+	 * @功能:根据id 批量查询
+	 * @param deptIds
+	 * @return
+	 */
+	@RequestMapping(value = "/queryByIds", method = RequestMethod.GET)
+	public Result<Collection<SysDepart>> queryByIds(@RequestParam String deptIds) {
+		Result<Collection<SysDepart>> result = new Result<>();
+		String[] ids = deptIds.split(",");
+		Collection<String> idList = Arrays.asList(ids);
+		Collection<SysDepart> deptList = sysDepartService.listByIds(idList);
+		result.setSuccess(true);
+		result.setResult(deptList);
+		return result;
+	}
+
+	@GetMapping("/getMyDepartList")
+    public Result<List<SysDepart>> getMyDepartList(){
+        List<SysDepart> list = sysDepartService.getMyDepartList();
+        return Result.ok(list);
+    }
+
+	/**
+	 * 异步查询部门list
+	 * @param parentId 父节点 异步加载时传递
+	 * @return
+	 */
+	@RequestMapping(value = "/queryBookDepTreeSync", method = RequestMethod.GET)
+	public Result<List<SysDepartTreeModel>> queryBookDepTreeSync(@RequestParam(name = "pid", required = false) String parentId,
+																 @RequestParam(name = "tenantId") Integer tenantId,
+																 @RequestParam(name = "departName",required = false) String departName) {
+		Result<List<SysDepartTreeModel>> result = new Result<>();
+		try {
+			List<SysDepartTreeModel> list = sysDepartService.queryBookDepTreeSync(parentId, tenantId, departName);
+			result.setResult(list);
+			result.setSuccess(true);
+		} catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+		return result;
+	}
 }

+ 6 - 5
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java

@@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletResponse;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.constant.CommonConstant;
@@ -103,7 +104,7 @@ public class SysDepartRoleController extends JeecgController<SysDepartRole, ISys
 	 * @param sysDepartRole
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:depart:role:add")
 	@ApiOperation(value="部门角色-添加", notes="部门角色-添加")
 	@PostMapping(value = "/add")
 	public Result<?> add(@RequestBody SysDepartRole sysDepartRole) {
@@ -117,8 +118,8 @@ public class SysDepartRoleController extends JeecgController<SysDepartRole, ISys
 	 * @param sysDepartRole
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
 	@ApiOperation(value="部门角色-编辑", notes="部门角色-编辑")
+    @RequiresPermissions("system:depart:role:edit")
 	@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
 	public Result<?> edit(@RequestBody SysDepartRole sysDepartRole) {
 		sysDepartRoleService.updateById(sysDepartRole);
@@ -131,9 +132,9 @@ public class SysDepartRoleController extends JeecgController<SysDepartRole, ISys
 	 * @param id
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
 	@AutoLog(value = "部门角色-通过id删除")
 	@ApiOperation(value="部门角色-通过id删除", notes="部门角色-通过id删除")
+    @RequiresPermissions("system:depart:role:delete")
 	@DeleteMapping(value = "/delete")
 	public Result<?> delete(@RequestParam(name="id",required=true) String id) {
 		sysDepartRoleService.removeById(id);
@@ -146,9 +147,9 @@ public class SysDepartRoleController extends JeecgController<SysDepartRole, ISys
 	 * @param ids
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
 	@AutoLog(value = "部门角色-批量删除")
 	@ApiOperation(value="部门角色-批量删除", notes="部门角色-批量删除")
+    @RequiresPermissions("system:depart:role:deleteBatch")
 	@DeleteMapping(value = "/deleteBatch")
 	public Result<?> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
 		this.sysDepartRoleService.removeByIds(Arrays.asList(ids.split(",")));
@@ -188,7 +189,7 @@ public class SysDepartRoleController extends JeecgController<SysDepartRole, ISys
 	  * @param json
 	  * @return
 	  */
-	 //@RequiresRoles({"admin"})
+     @RequiresPermissions("system:depart:role:userAdd")
 	 @RequestMapping(value = "/deptRoleUserAdd", method = RequestMethod.POST)
 	 public Result<?> deptRoleAdd(@RequestBody JSONObject json) {
 		 String newRoleId = json.getString("newRoleId");

+ 105 - 28
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java

@@ -8,8 +8,9 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.constant.CacheConstant;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.constant.SymbolConstant;
@@ -17,9 +18,8 @@ import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.system.vo.DictModel;
 import org.jeecg.common.system.vo.DictQuery;
 import org.jeecg.common.system.vo.LoginUser;
-import org.jeecg.common.util.ImportExcelUtil;
-import org.jeecg.common.util.SqlInjectionUtil;
-import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.common.util.*;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.modules.system.entity.SysDict;
 import org.jeecg.modules.system.entity.SysDictItem;
 import org.jeecg.modules.system.model.SysDictTree;
@@ -28,6 +28,7 @@ import org.jeecg.modules.system.security.DictQueryBlackListHandler;
 import org.jeecg.modules.system.service.ISysDictItemService;
 import org.jeecg.modules.system.service.ISysDictService;
 import org.jeecg.modules.system.vo.SysDictPage;
+import org.jeecg.modules.system.vo.lowapp.SysDictVo;
 import org.jeecgframework.poi.excel.ExcelImportCheckUtil;
 import org.jeecgframework.poi.excel.ExcelImportUtil;
 import org.jeecgframework.poi.excel.def.NormalExcelConstants;
@@ -45,6 +46,8 @@ import org.springframework.web.servlet.ModelAndView;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
 import java.util.*;
 
 /**
@@ -68,11 +71,19 @@ public class SysDictController {
 	public RedisTemplate<String, Object> redisTemplate;
 	@Autowired
 	private DictQueryBlackListHandler dictQueryBlackListHandler;
+	@Autowired
+	private RedisUtil redisUtil;
 
 	@RequestMapping(value = "/list", method = RequestMethod.GET)
 	public Result<IPage<SysDict>> queryPageList(SysDict sysDict,@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
 									  @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,HttpServletRequest req) {
 		Result<IPage<SysDict>> result = new Result<IPage<SysDict>>();
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			sysDict.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(),0));
+		}
+		//------------------------------------------------------------------------------------------------
 		QueryWrapper<SysDict> queryWrapper = QueryGenerator.initQueryWrapper(sysDict, req.getParameterMap());
 		Page<SysDict> page = new Page<SysDict>(pageNo, pageSize);
 		IPage<SysDict> pageList = sysDictService.page(page, queryWrapper);
@@ -191,9 +202,20 @@ public class SysDictController {
 	 */
 	@RequestMapping(value = "/loadDict/{dictCode}", method = RequestMethod.GET)
 	public Result<List<DictModel>> loadDict(@PathVariable("dictCode") String dictCode,
-			@RequestParam(name="keyword",required = false) String keyword,
-			@RequestParam(value = "sign",required = false) String sign,
-			@RequestParam(value = "pageSize", required = false) Integer pageSize) {
+											@RequestParam(name="keyword",required = false) String keyword,
+											@RequestParam(value = "sign",required = false) String sign,
+											@RequestParam(value = "pageSize", required = false) Integer pageSize) {
+		
+		//update-begin-author:taoyan date:2023-5-22 for: /issues/4905 因为中括号(%5)的问题导致的 表单生成器字段配置时,选择关联字段,在进行高级配置时,无法加载数据库列表,提示 Sgin签名校验错误! #4905 RouteToRequestUrlFilter
+		if(keyword!=null && keyword.indexOf("%5")>=0){
+			try {
+				keyword = URLDecoder.decode(keyword, "UTF-8");
+			} catch (UnsupportedEncodingException e) {
+				log.error("下拉搜索关键字解码失败", e);
+			}
+		}
+		//update-end-author:taoyan date:2023-5-22 for: /issues/4905 因为中括号(%5)的问题导致的  表单生成器字段配置时,选择关联字段,在进行高级配置时,无法加载数据库列表,提示 Sgin签名校验错误! #4905
+		
 		log.info(" 加载字典表数据,加载关键字: "+ keyword);
 		Result<List<DictModel>> result = new Result<List<DictModel>>();
 		//update-begin-author:taoyan date:20220317 for: VUEN-222【安全机制】字典接口、online报表、online图表等接口,加一些安全机制
@@ -325,6 +347,11 @@ public class SysDictController {
 		// SQL注入漏洞 sign签名校验(表名,label字段,val字段,条件)
 		String dictCode = tbname+","+text+","+code+","+condition;
         SqlInjectionUtil.filterContent(dictCode);
+		//update-begin-author:scott date:20230723 for:【issues/5173】SQL注入
+		if(!dictQueryBlackListHandler.isPass(dictCode)){
+			return result.error500(dictQueryBlackListHandler.getError());
+		}
+		//update-end-author:scott date:20230723 for:【issues/5173】SQL注入
 		List<TreeSelectModel> ls = sysDictService.queryTreeList(query,tbname, text, code, pidField, pid,hasChildField,converIsLeafVal);
 		result.setSuccess(true);
 		result.setResult(ls);
@@ -364,7 +391,7 @@ public class SysDictController {
 	 * @param sysDict
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:add")
 	@RequestMapping(value = "/add", method = RequestMethod.POST)
 	public Result<SysDict> add(@RequestBody SysDict sysDict) {
 		Result<SysDict> result = new Result<SysDict>();
@@ -385,7 +412,7 @@ public class SysDictController {
 	 * @param sysDict
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:edit")
 	@RequestMapping(value = "/edit", method = { RequestMethod.PUT,RequestMethod.POST })
 	public Result<SysDict> edit(@RequestBody SysDict sysDict) {
 		Result<SysDict> result = new Result<SysDict>();
@@ -407,7 +434,7 @@ public class SysDictController {
 	 * @param id
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:delete")
 	@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 	@CacheEvict(value={CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries=true)
 	public Result<SysDict> delete(@RequestParam(name="id",required=true) String id) {
@@ -426,7 +453,7 @@ public class SysDictController {
 	 * @param ids
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:deleteBatch")
 	@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
 	@CacheEvict(value= {CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries=true)
 	public Result<SysDict> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
@@ -448,22 +475,33 @@ public class SysDictController {
 	public Result<?> refleshCache() {
 		Result<?> result = new Result<SysDict>();
 		//清空字典缓存
-		Set keys = redisTemplate.keys(CacheConstant.SYS_DICT_CACHE + "*");
-		Set keys7 = redisTemplate.keys(CacheConstant.SYS_ENABLE_DICT_CACHE + "*");
-		Set keys2 = redisTemplate.keys(CacheConstant.SYS_DICT_TABLE_CACHE + "*");
-		Set keys21 = redisTemplate.keys(CacheConstant.SYS_DICT_TABLE_BY_KEYS_CACHE + "*");
-		Set keys3 = redisTemplate.keys(CacheConstant.SYS_DEPARTS_CACHE + "*");
-		Set keys4 = redisTemplate.keys(CacheConstant.SYS_DEPART_IDS_CACHE + "*");
-		Set keys5 = redisTemplate.keys( "jmreport:cache:dict*");
-		Set keys6 = redisTemplate.keys( "jmreport:cache:dictTable*");
-		redisTemplate.delete(keys);
-		redisTemplate.delete(keys2);
-		redisTemplate.delete(keys21);
-		redisTemplate.delete(keys3);
-		redisTemplate.delete(keys4);
-		redisTemplate.delete(keys5);
-		redisTemplate.delete(keys6);
-		redisTemplate.delete(keys7);
+//		Set keys = redisTemplate.keys(CacheConstant.SYS_DICT_CACHE + "*");
+//		Set keys7 = redisTemplate.keys(CacheConstant.SYS_ENABLE_DICT_CACHE + "*");
+//		Set keys2 = redisTemplate.keys(CacheConstant.SYS_DICT_TABLE_CACHE + "*");
+//		Set keys21 = redisTemplate.keys(CacheConstant.SYS_DICT_TABLE_BY_KEYS_CACHE + "*");
+//		Set keys3 = redisTemplate.keys(CacheConstant.SYS_DEPARTS_CACHE + "*");
+//		Set keys4 = redisTemplate.keys(CacheConstant.SYS_DEPART_IDS_CACHE + "*");
+//		Set keys5 = redisTemplate.keys( "jmreport:cache:dict*");
+//		Set keys6 = redisTemplate.keys( "jmreport:cache:dictTable*");
+//		redisTemplate.delete(keys);
+//		redisTemplate.delete(keys2);
+//		redisTemplate.delete(keys21);
+//		redisTemplate.delete(keys3);
+//		redisTemplate.delete(keys4);
+//		redisTemplate.delete(keys5);
+//		redisTemplate.delete(keys6);
+//		redisTemplate.delete(keys7);
+
+		//update-begin-author:liusq date:20230404 for:  [issue/4358]springCache中的清除缓存的操作使用了“keys”
+		redisUtil.removeAll(CacheConstant.SYS_DICT_CACHE);
+		redisUtil.removeAll(CacheConstant.SYS_ENABLE_DICT_CACHE);
+		redisUtil.removeAll(CacheConstant.SYS_DICT_TABLE_CACHE);
+		redisUtil.removeAll(CacheConstant.SYS_DICT_TABLE_BY_KEYS_CACHE);
+		redisUtil.removeAll(CacheConstant.SYS_DEPARTS_CACHE);
+		redisUtil.removeAll(CacheConstant.SYS_DEPART_IDS_CACHE);
+		redisUtil.removeAll("jmreport:cache:dict");
+		redisUtil.removeAll("jmreport:cache:dictTable");
+		//update-end-author:liusq date:20230404 for:  [issue/4358]springCache中的清除缓存的操作使用了“keys”
 		return result;
 	}
 
@@ -474,6 +512,13 @@ public class SysDictController {
 	 */
 	@RequestMapping(value = "/exportXls")
 	public ModelAndView exportXls(SysDict sysDict,HttpServletRequest request) {
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			sysDict.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+		}
+		//------------------------------------------------------------------------------------------------
+		
 		// Step.1 组装查询条件
 		QueryWrapper<SysDict> queryWrapper = QueryGenerator.initQueryWrapper(sysDict, request.getParameterMap());
 		//Step.2 AutoPoi 导出Excel
@@ -509,7 +554,7 @@ public class SysDictController {
 	 * @param
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:importExcel")
 	@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
 	public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
  		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
@@ -639,4 +684,36 @@ public class SysDictController {
 		return Result.error("校验失败,sql解析异常!" + msg);
 	}
 
+	/**
+	 * 根据应用id获取字典列表和详情
+	 * @param request
+	 */
+	@GetMapping("/getDictListByLowAppId")
+	public Result<List<SysDictVo>> getDictListByLowAppId(HttpServletRequest request){
+		String lowAppId = oConvertUtils.getString(TokenUtils.getLowAppIdByRequest(request),"0");
+		List<SysDictVo> list = sysDictService.getDictListByLowAppId(lowAppId);
+		return Result.ok(list);
+	}
+
+	/**
+	 * 添加字典
+	 * @param sysDictVo
+	 * @param request
+	 * @return
+	 */
+	@PostMapping("/addDictByLowAppId")
+	public Result<String> addDictByLowAppId(@RequestBody SysDictVo sysDictVo,HttpServletRequest request){
+		String lowAppId = oConvertUtils.getString(TokenUtils.getLowAppIdByRequest(request),"0");
+		sysDictVo.setLowAppId(lowAppId);
+		sysDictService.addDictByLowAppId(sysDictVo);
+		return Result.ok("添加成功");
+	}
+
+	@PutMapping("/editDictByLowAppId")
+	public Result<String> editDictByLowAppId(@RequestBody SysDictVo sysDictVo,HttpServletRequest request){
+		String lowAppId = oConvertUtils.getString(TokenUtils.getLowAppIdByRequest(request),"0");
+		sysDictVo.setLowAppId(lowAppId);
+		sysDictService.editDictByLowAppId(sysDictVo);
+		return Result.ok("编辑成功");
+	}
 }

+ 5 - 4
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java

@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.apache.commons.lang.StringUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.constant.CacheConstant;
@@ -73,7 +74,7 @@ public class SysDictItemController {
 	 * @功能:新增
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:item:add")
 	@RequestMapping(value = "/add", method = RequestMethod.POST)
 	@CacheEvict(value= {CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries=true)
 	public Result<SysDictItem> add(@RequestBody SysDictItem sysDictItem) {
@@ -94,7 +95,7 @@ public class SysDictItemController {
 	 * @param sysDictItem
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:item:edit")
 	@RequestMapping(value = "/edit",  method = { RequestMethod.PUT,RequestMethod.POST })
 	@CacheEvict(value={CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries=true)
 	public Result<SysDictItem> edit(@RequestBody SysDictItem sysDictItem) {
@@ -118,7 +119,7 @@ public class SysDictItemController {
 	 * @param id
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:item:delete")
 	@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 	@CacheEvict(value={CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries=true)
 	public Result<SysDictItem> delete(@RequestParam(name="id",required=true) String id) {
@@ -140,7 +141,7 @@ public class SysDictItemController {
 	 * @param ids
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:dict:item:deleteBatch")
 	@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
 	@CacheEvict(value={CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries=true)
 	public Result<SysDictItem> deleteBatch(@RequestParam(name="ids",required=true) String ids) {

+ 2 - 1
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysGatewayRouteController.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import io.swagger.annotations.Api;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.system.base.controller.JeecgController;
@@ -67,7 +68,7 @@ public class SysGatewayRouteController extends JeecgController<SysGatewayRoute,
      * @param id
      * @return
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:getway:delete")
     @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
     public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
         sysGatewayRouteService.deleteById(id);

+ 15 - 11
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysPermissionController.java

@@ -1,11 +1,14 @@
 package org.jeecg.modules.system.controller;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.constant.CommonConstant;
@@ -79,6 +82,7 @@ public class SysPermissionController {
 	 *
 	 * @return
 	 */
+	//@RequiresPermissions("system:permission:list")
 	@RequestMapping(value = "/list", method = RequestMethod.GET)
 	public Result<List<SysPermissionTree>> list(SysPermission sysPermission, HttpServletRequest req) {
         long start = System.currentTimeMillis();
@@ -360,7 +364,7 @@ public class SysPermissionController {
 	 * @param permission
 	 * @return
 	 */
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:add")
 	@RequestMapping(value = "/add", method = RequestMethod.POST)
 	public Result<SysPermission> add(@RequestBody SysPermission permission) {
 		Result<SysPermission> result = new Result<SysPermission>();
@@ -380,7 +384,7 @@ public class SysPermissionController {
 	 * @param permission
 	 * @return
 	 */
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:edit")
 	@RequestMapping(value = "/edit", method = { RequestMethod.PUT, RequestMethod.POST })
 	public Result<SysPermission> edit(@RequestBody SysPermission permission) {
 		Result<SysPermission> result = new Result<>();
@@ -402,14 +406,14 @@ public class SysPermissionController {
 	 * @return
 	 */
 	@RequestMapping(value = "/checkPermDuplication", method = RequestMethod.GET)
-	public Result<String> checkPermDuplication(@RequestParam(name = "id", required = false) String id, @RequestParam(name = "url") String url, @RequestParam(name = "alwaysShow") Boolean alwaysShow) {
+	public Result<String> checkPermDuplication(@RequestParam(name = "id", required = false) String id,@RequestParam(name = "url") String url,@RequestParam(name = "alwaysShow") Boolean alwaysShow) {
 		Result<String> result = new Result<>();
 		try {
 			boolean check=sysPermissionService.checkPermDuplication(id,url,alwaysShow);
 			if(check){
 				return Result.ok("该值可用!");
 			}
-			return Result.error("该值不可用,系统中已存在!");
+			return Result.error("访问路径不允许重复,请重定义!");
 		} catch (Exception e) {
 			log.error(e.getMessage(), e);
 			result.error500("操作失败");
@@ -422,7 +426,7 @@ public class SysPermissionController {
 	 * @param id
 	 * @return
 	 */
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:delete")
 	@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 	public Result<SysPermission> delete(@RequestParam(name = "id", required = true) String id) {
 		Result<SysPermission> result = new Result<>();
@@ -441,7 +445,7 @@ public class SysPermissionController {
 	 * @param ids
 	 * @return
 	 */
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:deleteBatch")
 	@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
 	public Result<SysPermission> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
 		Result<SysPermission> result = new Result<>();
@@ -548,8 +552,8 @@ public class SysPermissionController {
 	 *
 	 * @return
 	 */
+    @RequiresPermissions("system:permission:saveRole")
 	@RequestMapping(value = "/saveRolePermission", method = RequestMethod.POST)
-	//@RequiresRoles({ "admin" })
 	public Result<String> saveRolePermission(@RequestBody JSONObject json) {
 		long start = System.currentTimeMillis();
 		Result<String> result = new Result<>();
@@ -881,7 +885,7 @@ public class SysPermissionController {
 	 * @param sysPermissionDataRule
 	 * @return
 	 */
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:addRule")
 	@RequestMapping(value = "/addPermissionRule", method = RequestMethod.POST)
 	public Result<SysPermissionDataRule> addPermissionRule(@RequestBody SysPermissionDataRule sysPermissionDataRule) {
 		Result<SysPermissionDataRule> result = new Result<SysPermissionDataRule>();
@@ -896,7 +900,7 @@ public class SysPermissionController {
 		return result;
 	}
 
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:editRule")
 	@RequestMapping(value = "/editPermissionRule", method = { RequestMethod.PUT, RequestMethod.POST })
 	public Result<SysPermissionDataRule> editPermissionRule(@RequestBody SysPermissionDataRule sysPermissionDataRule) {
 		Result<SysPermissionDataRule> result = new Result<SysPermissionDataRule>();
@@ -916,7 +920,7 @@ public class SysPermissionController {
 	 * @param id
 	 * @return
 	 */
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:deleteRule")
 	@RequestMapping(value = "/deletePermissionRule", method = RequestMethod.DELETE)
 	public Result<SysPermissionDataRule> deletePermissionRule(@RequestParam(name = "id", required = true) String id) {
 		Result<SysPermissionDataRule> result = new Result<SysPermissionDataRule>();
@@ -973,7 +977,7 @@ public class SysPermissionController {
 	 * @return
 	 */
 	@RequestMapping(value = "/saveDepartPermission", method = RequestMethod.POST)
-	//@RequiresRoles({ "admin" })
+    @RequiresPermissions("system:permission:saveDepart")
 	public Result<String> saveDepartPermission(@RequestBody JSONObject json) {
 		long start = System.currentTimeMillis();
 		Result<String> result = new Result<>();

+ 27 - 2
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysPositionController.java

@@ -1,5 +1,6 @@
 package org.jeecg.modules.system.controller;
 
+import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -7,18 +8,19 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.ss.formula.functions.T;
 import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.annotation.AutoLog;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.system.vo.LoginUser;
 import org.jeecg.common.util.ImportExcelUtil;
 import org.jeecg.common.util.oConvertUtils;
-import org.jeecg.modules.quartz.service.IQuartzJobService;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.modules.system.entity.SysPosition;
 import org.jeecg.modules.system.service.ISysPositionService;
+import org.jeecg.modules.system.service.ISysUserService;
 import org.jeecgframework.poi.excel.ExcelImportUtil;
 import org.jeecgframework.poi.excel.def.NormalExcelConstants;
 import org.jeecgframework.poi.excel.entity.ExportParams;
@@ -39,6 +41,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * @Description: 职务表
@@ -55,6 +58,9 @@ public class SysPositionController {
     @Autowired
     private ISysPositionService sysPositionService;
 
+    @Autowired
+    private ISysUserService userService;
+
     /**
      * 分页列表查询
      *
@@ -72,6 +78,12 @@ public class SysPositionController {
                                                     @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
                                                     HttpServletRequest req) {
         Result<IPage<SysPosition>> result = new Result<IPage<SysPosition>>();
+        //------------------------------------------------------------------------------------------------
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+            sysPosition.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(),0));
+        }
+        //------------------------------------------------------------------------------------------------
         QueryWrapper<SysPosition> queryWrapper = QueryGenerator.initQueryWrapper(sysPosition, req.getParameterMap());
         Page<SysPosition> page = new Page<SysPosition>(pageNo, pageSize);
         IPage<SysPosition> pageList = sysPositionService.page(page, queryWrapper);
@@ -92,6 +104,13 @@ public class SysPositionController {
     public Result<SysPosition> add(@RequestBody SysPosition sysPosition) {
         Result<SysPosition> result = new Result<SysPosition>();
         try {
+            //update-begin---author:wangshuai ---date:20230313  for:【QQYUN-4558】vue3职位功能调整,去掉编码和级别,可以先隐藏------------
+            //编号是空的,不需要判断多租户隔离了
+            if(oConvertUtils.isEmpty(sysPosition.getCode())){
+                //生成职位编码10位
+                sysPosition.setCode(RandomUtil.randomString(10));
+            }
+            //update-end---author:wangshuai ---date:20230313  for:【QQYUN-4558】vue3职位功能调整,去掉编码和级别,可以先隐藏-------------
             sysPositionService.save(sysPosition);
             result.success("添加成功!");
         } catch (Exception e) {
@@ -201,6 +220,12 @@ public class SysPositionController {
             if (oConvertUtils.isNotEmpty(paramsStr)) {
                 String deString = URLDecoder.decode(paramsStr, "UTF-8");
                 SysPosition sysPosition = JSON.parseObject(deString, SysPosition.class);
+                //------------------------------------------------------------------------------------------------
+                //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+                if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+                    sysPosition.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(),0));
+                }
+                //------------------------------------------------------------------------------------------------
                 queryWrapper = QueryGenerator.initQueryWrapper(sysPosition, request.getParameterMap());
             }
         } catch (UnsupportedEncodingException e) {

+ 87 - 22
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleController.java

@@ -13,29 +13,24 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.shiro.authz.annotation.RequiresRoles;
+import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.jeecg.common.api.vo.Result;
-import org.jeecg.common.constant.CacheConstant;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.query.QueryGenerator;
-import org.jeecg.common.util.PmsUtil;
 import org.jeecg.common.util.oConvertUtils;
-import org.jeecg.modules.system.entity.SysPermission;
-import org.jeecg.modules.system.entity.SysPermissionDataRule;
-import org.jeecg.modules.system.entity.SysRole;
-import org.jeecg.modules.system.entity.SysRolePermission;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
+import org.jeecg.modules.system.entity.*;
 import org.jeecg.modules.system.model.TreeModel;
-import org.jeecg.modules.system.service.ISysPermissionDataRuleService;
-import org.jeecg.modules.system.service.ISysPermissionService;
-import org.jeecg.modules.system.service.ISysRolePermissionService;
-import org.jeecg.modules.system.service.ISysRoleService;
-import org.jeecgframework.poi.excel.ExcelImportUtil;
+import org.jeecg.modules.system.service.*;
+import org.jeecg.modules.system.vo.SysUserRoleCountVo;
 import org.jeecgframework.poi.excel.def.NormalExcelConstants;
 import org.jeecgframework.poi.excel.entity.ExportParams;
 import org.jeecgframework.poi.excel.entity.ImportParams;
 import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -81,14 +76,18 @@ public class SysRoleController {
 	@Autowired
 	private ISysPermissionService sysPermissionService;
 
+    @Autowired
+    private ISysUserRoleService sysUserRoleService;
+
 	/**
-	  * 分页列表查询
+	  * 分页列表查询 【系统角色,不做租户隔离】
 	 * @param role
 	 * @param pageNo
 	 * @param pageSize
 	 * @param req
 	 * @return
 	 */
+	//@RequiresPermissions("system:role:list")
 	@RequestMapping(value = "/list", method = RequestMethod.GET)
 	public Result<IPage<SysRole>> queryPageList(SysRole role,
 									  @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@@ -103,13 +102,41 @@ public class SysRoleController {
 		return result;
 	}
 	
+	/**
+	 * 分页列表查询【租户角色,做租户隔离】
+	 * @param role
+	 * @param pageNo
+	 * @param pageSize
+	 * @param req
+	 * @return
+	 */
+	@RequestMapping(value = "/listByTenant", method = RequestMethod.GET)
+	public Result<IPage<SysRole>> listByTenant(SysRole role,
+												@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+												@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+												HttpServletRequest req) {
+		Result<IPage<SysRole>> result = new Result<IPage<SysRole>>();
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			role.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(),0));
+		}
+		//------------------------------------------------------------------------------------------------
+		QueryWrapper<SysRole> queryWrapper = QueryGenerator.initQueryWrapper(role, req.getParameterMap());
+		Page<SysRole> page = new Page<SysRole>(pageNo, pageSize);
+		IPage<SysRole> pageList = sysRoleService.page(page, queryWrapper);
+		result.setSuccess(true);
+		result.setResult(pageList);
+		return result;
+	}
+	
 	/**
 	  *   添加
 	 * @param role
 	 * @return
 	 */
 	@RequestMapping(value = "/add", method = RequestMethod.POST)
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:role:add")
 	public Result<SysRole> add(@RequestBody SysRole role) {
 		Result<SysRole> result = new Result<SysRole>();
 		try {
@@ -128,7 +155,7 @@ public class SysRoleController {
 	 * @param role
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:role:edit")
 	@RequestMapping(value = "/edit",method = {RequestMethod.PUT,RequestMethod.POST})
 	public Result<SysRole> edit(@RequestBody SysRole role) {
 		Result<SysRole> result = new Result<SysRole>();
@@ -152,7 +179,7 @@ public class SysRoleController {
 	 * @param id
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:role:delete")
 	@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 	public Result<?> delete(@RequestParam(name="id",required=true) String id) {
 		sysRoleService.deleteRole(id);
@@ -164,7 +191,7 @@ public class SysRoleController {
 	 * @param ids
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:role:deleteBatch")
 	@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
 	public Result<SysRole> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
 		Result<SysRole> result = new Result<SysRole>();
@@ -194,11 +221,43 @@ public class SysRoleController {
 		}
 		return result;
 	}
-	
+
+	/**
+	 * 查询全部角色(参与租户隔离)
+	 * 
+	 * @return
+	 */
 	@RequestMapping(value = "/queryall", method = RequestMethod.GET)
 	public Result<List<SysRole>> queryall() {
 		Result<List<SysRole>> result = new Result<>();
-		List<SysRole> list = sysRoleService.list();
+		LambdaQueryWrapper<SysRole> query = new LambdaQueryWrapper<SysRole>();
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			query.eq(SysRole::getTenantId, oConvertUtils.getInt(TenantContext.getTenant(), 0));
+		}
+		//------------------------------------------------------------------------------------------------
+		List<SysRole> list = sysRoleService.list(query);
+		if(list==null||list.size()<=0) {
+			result.error500("未找到角色信息");
+		}else {
+			result.setResult(list);
+			result.setSuccess(true);
+		}
+		return result;
+	}
+
+	/**
+	 * 查询全部系统角色(不做租户隔离)
+	 *
+	 * @return
+	 */
+	@RequiresPermissions("system:role:queryallNoByTenant")
+	@RequestMapping(value = "/queryallNoByTenant", method = RequestMethod.GET)
+	public Result<List<SysRole>> queryallNoByTenant() {
+		Result<List<SysRole>> result = new Result<>();
+		LambdaQueryWrapper<SysRole> query = new LambdaQueryWrapper<SysRole>();
+		List<SysRole> list = sysRoleService.list(query);
 		if(list==null||list.size()<=0) {
 			result.error500("未找到角色信息");
 		}else {
@@ -253,6 +312,13 @@ public class SysRoleController {
 	 */
 	@RequestMapping(value = "/exportXls")
 	public ModelAndView exportXls(SysRole sysRole,HttpServletRequest request) {
+		//------------------------------------------------------------------------------------------------
+		//是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+		if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+			sysRole.setTenantId(oConvertUtils.getInt(TenantContext.getTenant(), 0));
+		}
+		//------------------------------------------------------------------------------------------------
+		
 		// Step.1 组装查询条件
 		QueryWrapper<SysRole> queryWrapper = QueryGenerator.initQueryWrapper(sysRole, request.getParameterMap());
 		//Step.2 AutoPoi 导出Excel
@@ -408,6 +474,5 @@ public class SysRoleController {
 			
 		}
 	}
-	
-	
+
 }

+ 4 - 0
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java

@@ -5,6 +5,8 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.aspect.annotation.AutoLog;
@@ -65,6 +67,7 @@ public class SysRoleIndexController extends JeecgController<SysRoleIndex, ISysRo
      * @param sysRoleIndex
      * @return
      */
+    @RequiresPermissions("system:roleindex:add")
     @AutoLog(value = "角色首页配置-添加")
     @ApiOperation(value = "角色首页配置-添加", notes = "角色首页配置-添加")
     @PostMapping(value = "/add")
@@ -80,6 +83,7 @@ public class SysRoleIndexController extends JeecgController<SysRoleIndex, ISysRo
      * @param sysRoleIndex
      * @return
      */
+    @RequiresPermissions("system:roleindex:edit")
     @AutoLog(value = "角色首页配置-编辑")
     @ApiOperation(value = "角色首页配置-编辑", notes = "角色首页配置-编辑")
     @RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})

+ 627 - 11
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysTenantController.java

@@ -1,6 +1,7 @@
 package org.jeecg.modules.system.controller;
 
 
+import cn.hutool.core.util.RandomUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -9,12 +10,26 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.annotation.PermissionData;
+import org.jeecg.common.config.TenantContext;
+import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.constant.SymbolConstant;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.common.util.PasswordUtil;
+import org.jeecg.common.util.TokenUtils;
 import org.jeecg.common.util.oConvertUtils;
-import org.jeecg.modules.system.entity.SysTenant;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
+import org.jeecg.modules.base.service.BaseCommonService;
+import org.jeecg.modules.system.entity.*;
+import org.jeecg.modules.system.service.ISysTenantPackService;
 import org.jeecg.modules.system.service.ISysTenantService;
+import org.jeecg.modules.system.service.ISysUserService;
+import org.jeecg.modules.system.service.ISysUserTenantService;
+import org.jeecg.modules.system.vo.SysUserTenantVo;
+import org.jeecg.modules.system.vo.tenant.TenantDepartAuthInfo;
+import org.jeecg.modules.system.vo.tenant.TenantPackModel;
+import org.jeecg.modules.system.vo.tenant.TenantPackUser;
+import org.jeecg.modules.system.vo.tenant.TenantPackUserCount;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -33,6 +48,18 @@ public class SysTenantController {
     @Autowired
     private ISysTenantService sysTenantService;
 
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @Autowired
+    private ISysUserTenantService relationService;
+    
+    @Autowired
+    private ISysTenantPackService sysTenantPackService;
+    
+    @Autowired
+    private BaseCommonService baseCommonService;
+
     /**
      * 获取列表数据
      * @param sysTenant
@@ -43,8 +70,8 @@ public class SysTenantController {
      */
     @PermissionData(pageComponent = "system/TenantList")
 	@RequestMapping(value = "/list", method = RequestMethod.GET)
-	public Result<IPage<SysTenant>> queryPageList(SysTenant sysTenant,@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
-									  @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,HttpServletRequest req) {
+	public Result<IPage<SysTenant>> queryPageList(SysTenant sysTenant, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+                                                  @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, HttpServletRequest req) {
 		Result<IPage<SysTenant>> result = new Result<IPage<SysTenant>>();
         //---author:zhangyafei---date:20210916-----for: 租户管理添加日期范围查询---
         Date beginDate=null;
@@ -70,6 +97,25 @@ public class SysTenantController {
 		return result;
 	}
 
+    /**
+     * 获取租户删除的列表
+     * @param sysTenant
+     * @param pageNo
+     * @param pageSize
+     * @param req
+     * @return
+     */
+    @GetMapping("/recycleBinPageList")
+    public Result<IPage<SysTenant>> recycleBinPageList(SysTenant sysTenant, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+                                                       @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, HttpServletRequest req){
+        Result<IPage<SysTenant>> result = new Result<IPage<SysTenant>>();
+        Page<SysTenant> page = new Page<SysTenant>(pageNo, pageSize);
+        IPage<SysTenant> pageList = sysTenantService.getRecycleBinPageList(page, sysTenant);
+        result.setSuccess(true);
+        result.setResult(pageList);
+        return result;
+    }
+    
     /**
      *   添加
      * @param
@@ -82,7 +128,7 @@ public class SysTenantController {
             return result.error500("该编号已存在!");
         }
         try {
-            sysTenantService.save(sysTenant);
+            sysTenantService.saveTenant(sysTenant);
             result.success("添加成功!");
         } catch (Exception e) {
             log.error(e.getMessage(), e);
@@ -103,6 +149,9 @@ public class SysTenantController {
         if(sysTenant==null) {
            return result.error500("未找到对应实体");
         }
+        if(oConvertUtils.isEmpty(sysTenant.getHouseNumber())){
+            tenant.setHouseNumber(RandomUtil.randomStringUpper(6));
+        }
         boolean ok = sysTenantService.updateById(tenant);
         if(ok) {
             result.success("修改成功!");
@@ -117,6 +166,22 @@ public class SysTenantController {
      */
     @RequestMapping(value = "/delete", method ={RequestMethod.DELETE, RequestMethod.POST})
     public Result<?> delete(@RequestParam(name="id",required=true) String id) {
+        //------------------------------------------------------------------
+        //如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
+        if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
+            //获取当前用户
+            LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+            SysTenant sysTenant = sysTenantService.getById(id);
+
+            String username = "admin";
+            String createdBy = sysUser.getUsername();
+            if (!sysTenant.getCreateBy().equals(createdBy) && !username.equals(createdBy)) {
+                baseCommonService.addLog("未经授权,不能删除非自己创建的租户,租户ID:" + id + ",操作人:" + sysUser.getUsername(), CommonConstant.LOG_TYPE_2, CommonConstant.OPERATE_TYPE_3);
+                return Result.error("删除租户失败,当前操作人不是租户的创建人!");
+            }
+        }
+        //------------------------------------------------------------------
+                
         sysTenantService.removeTenantById(id);
         return Result.ok("删除成功");
     }
@@ -136,6 +201,22 @@ public class SysTenantController {
             // 过滤掉已被引用的租户
             List<Integer> idList = new ArrayList<>();
             for (String id : ls) {
+                //------------------------------------------------------------------
+                //如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
+                if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
+                    //获取当前用户
+                    LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+                    SysTenant sysTenant = sysTenantService.getById(id);
+
+                    String username = "admin";
+                    String createdBy = sysUser.getUsername();
+                    if (!sysTenant.getCreateBy().equals(createdBy) && !username.equals(createdBy)) {
+                        baseCommonService.addLog("未经授权,不能删除非自己创建的租户,租户ID:" + id + ",操作人:" + sysUser.getUsername(), CommonConstant.LOG_TYPE_2, CommonConstant.OPERATE_TYPE_3);
+                        return Result.error("删除租户失败,当前操作人不是租户的创建人!");
+                    }
+                }
+                //------------------------------------------------------------------
+                
                 Long userCount = sysTenantService.countUserLinkTenant(id);
                 if (userCount == 0) {
                     idList.add(Integer.parseInt(id));
@@ -163,6 +244,21 @@ public class SysTenantController {
     @RequestMapping(value = "/queryById", method = RequestMethod.GET)
     public Result<SysTenant> queryById(@RequestParam(name="id",required=true) String id) {
         Result<SysTenant> result = new Result<SysTenant>();
+        if(oConvertUtils.isEmpty(id)){
+            result.error500("参数为空!");
+        }
+        //------------------------------------------------------------------------------------------------
+        //获取登录用户信息
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】, admin给特权可以管理所有租户
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && !"admin".equals(sysUser.getUsername())){
+            Integer loginSessionTenant = oConvertUtils.getInt(TenantContext.getTenant());
+            if(loginSessionTenant!=null && !loginSessionTenant.equals(Integer.valueOf(id))){
+                result.error500("无权限访问他人租户!");
+                return result;
+            }
+        }
+        //------------------------------------------------------------------------------------------------
         SysTenant sysTenant = sysTenantService.getById(id);
         if(sysTenant==null) {
             result.error500("未找到对应实体");
@@ -192,8 +288,72 @@ public class SysTenantController {
         result.setResult(ls);
         return result;
     }
+
+    /**
+     * 产品包分页列表查询
+     *
+     * @param sysTenantPack
+     * @param pageNo
+     * @param pageSize
+     * @param req
+     * @return
+     */
+    @GetMapping(value = "/packList")
+    public Result<IPage<SysTenantPack>> queryPackPageList(SysTenantPack sysTenantPack,
+                                                          @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+                                                          @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+                                                          HttpServletRequest req) {
+        QueryWrapper<SysTenantPack> queryWrapper = QueryGenerator.initQueryWrapper(sysTenantPack, req.getParameterMap());
+        Page<SysTenantPack> page = new Page<SysTenantPack>(pageNo, pageSize);
+        IPage<SysTenantPack> pageList = sysTenantPackService.page(page, queryWrapper);
+        List<SysTenantPack> records = pageList.getRecords();
+        if (null != records && records.size() > 0) {
+            pageList.setRecords(sysTenantPackService.setPermissions(records));
+        }
+        return Result.OK(pageList);
+    }
+
+    /**
+     * 创建租户产品包
+     *
+     * @param sysTenantPack
+     * @return
+     */
+    @PostMapping(value = "/addPackPermission")
+    public Result<String> addPackPermission(@RequestBody SysTenantPack sysTenantPack) {
+        sysTenantPackService.addPackPermission(sysTenantPack);
+        return Result.ok("创建租户产品包成功");
+    }
+
+    /**
+     * 创建租户产品包
+     *
+     * @param sysTenantPack
+     * @return
+     */
+    @PutMapping(value = "/editPackPermission")
+    public Result<String> editPackPermission(@RequestBody SysTenantPack sysTenantPack) {
+        sysTenantPackService.editPackPermission(sysTenantPack);
+        return Result.ok("修改租户产品包成功");
+    }
+
     /**
-     *  查询当前用户的所有有效租户 【当前用于vue3版本】
+     * 批量删除用户菜单
+     *
+     * @param ids
+     * @return
+     */
+    @DeleteMapping("/deletePackPermissions")
+    public Result<String> deletePackPermissions(@RequestParam(value = "ids") String ids) {
+        sysTenantPackService.deletePackPermissions(ids);
+        return Result.ok("删除租户产品包成功");
+    }
+    
+
+
+    //===========【低代码应用,前端专用接口 —— 加入限制只能维护和查看自己拥有的租户】==========================================================
+    /**
+     *  查询当前用户的所有有效租户【低代码应用专用接口】
      * @return
      */
     @RequestMapping(value = "/getCurrentUserTenant", method = RequestMethod.GET)
@@ -201,13 +361,11 @@ public class SysTenantController {
         Result<Map<String,Object>> result = new Result<Map<String,Object>>();
         try {
             LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
-            String tenantIds = sysUser.getRelTenantIds();
+            //update-begin---author:wangshuai ---date:20221223  for:[QQYUN-3371]租户逻辑改造,改成关系表------------
+            List<Integer> tenantIdList = relationService.getTenantIdsByUserId(sysUser.getId());
             Map<String,Object> map = new HashMap(5);
-            if (oConvertUtils.isNotEmpty(tenantIds)) {
-                List<Integer> tenantIdList = new ArrayList<>();
-                for(String id: tenantIds.split(SymbolConstant.COMMA)){
-                    tenantIdList.add(Integer.valueOf(id));
-                }
+            if (null!=tenantIdList && tenantIdList.size()>0) {
+            //update-end---author:wangshuai ---date:20221223  for:[QQYUN-3371]租户逻辑改造,改成关系表------------
                 // 该方法仅查询有效的租户,如果返回0个就说明所有的租户均无效。
                 List<SysTenant> tenantList = sysTenantService.queryEffectiveTenant(tenantIdList);
                 map.put("list", tenantList);
@@ -220,4 +378,462 @@ public class SysTenantController {
         }
         return result;
     }
+
+    /**
+     * 邀请用户【低代码应用专用接口】
+     * @param ids
+     * @param phone
+     * @return
+     */
+    @PutMapping("/invitationUserJoin")
+    public Result<String> invitationUserJoin(@RequestParam("ids") String ids, @RequestParam("phone") String phone){
+        sysTenantService.invitationUserJoin(ids,phone);
+        return Result.ok("邀请用户成功");
+    }
+
+    /**
+     * 获取用户列表数据【低代码应用专用接口】
+     * @param user
+     * @param pageNo
+     * @param pageSize
+     * @param req
+     * @return
+     */
+    @RequestMapping(value = "/getTenantUserList", method = RequestMethod.GET)
+    public Result<IPage<SysUser>> getTenantUserList(SysUser user,
+                                                    @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+                                                    @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+                                                    @RequestParam(name="userTenantId") String userTenantId,
+                                                    HttpServletRequest req) {
+        Result<IPage<SysUser>> result = new Result<>();
+        Page<SysUser> page = new Page<>(pageNo, pageSize);
+        Page<SysUser> pageList = relationService.getPageUserList(page,Integer.valueOf(userTenantId),user);
+        result.setSuccess(true);
+        result.setResult(pageList);
+        return result;
+    }
+
+    /**
+     * 请离用户租户【低代码应用专用接口】
+     * @param userIds
+     * @param tenantId
+     * @return
+     */
+    @PutMapping("/leaveTenant")
+    public Result<String> leaveTenant(@RequestParam("userIds") String userIds,
+                                      @RequestParam("tenantId") String tenantId){
+        Result<String> result = new Result<>();
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && !"admin".equals(sysUser.getUsername())){
+            Integer loginSessionTenant = oConvertUtils.getInt(TenantContext.getTenant());
+            if(loginSessionTenant!=null && !loginSessionTenant.equals(Integer.valueOf(tenantId))){
+                result.error500("无权限访问他人租户!");
+                return result;
+            }
+        }
+        sysTenantService.leaveTenant(userIds,tenantId);
+        return Result.ok("请离成功");
+    }
+
+    /**
+     *  编辑(只允许修改自己拥有的租户)【低代码应用专用接口】
+     * @param
+     * @return
+     */
+    @RequestMapping(value = "/editOwnTenant", method ={RequestMethod.PUT, RequestMethod.POST})
+    public Result<SysTenant> editOwnTenant(@RequestBody SysTenant tenant, HttpServletRequest req) {
+        Result<SysTenant> result = new Result();
+        String tenantId = TokenUtils.getTenantIdByRequest(req);
+        if(!tenantId.equals(tenant.getId().toString())){
+            return result.error500("无权修改他人租户!");
+        }
+
+        SysTenant sysTenant = sysTenantService.getById(tenant.getId());
+        if(sysTenant==null) {
+            return result.error500("未找到对应实体");
+        }
+        if(oConvertUtils.isEmpty(sysTenant.getHouseNumber())){
+            tenant.setHouseNumber(RandomUtil.randomStringUpper(6));
+        }
+        boolean ok = sysTenantService.updateById(tenant);
+        if(ok) {
+            result.success("修改成功!");
+        }
+        return result;
+    }
+    
+    /**
+     * 创建租户并且将用户保存到中间表【低代码应用专用接口】
+     * @param sysTenant
+     */
+    @PostMapping("/saveTenantJoinUser")
+    public Result<Integer> saveTenantJoinUser(@RequestBody SysTenant sysTenant){
+        Result<Integer> result = new Result<>();
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        //------------------------------------------------------------------------------------------------
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+            //---author:scott---date:20220111-----for: 限制免费用户只能创建两个租户--
+            Integer count = sysTenantService.countCreateTenantNum(sysUser.getUsername());
+            if (count > 2) {
+                Set<String> roles = sysUserService.getUserRolesSet(sysUser.getUsername());
+                //创建一个付费角色 paymember
+                if (roles==null || (!roles.contains("paymember") && !roles.contains("admin"))) {
+                    return result.error500("免费用户最多创建两个租户!");    
+                }
+            }
+            //---author:scott---date:20220111-----for:  限制免费用户只能创建两个租户--
+        }
+        //------------------------------------------------------------------------------------------------
+        Integer tenantId = sysTenantService.saveTenantJoinUser(sysTenant, sysUser.getId());
+        result.setSuccess(true);
+        result.setMessage("创建成功");
+        result.setResult(tenantId);
+        return result;
+    }
+
+    /**
+     * 加入租户通过门牌号【低代码应用专用接口】
+     * @param sysTenant
+     */
+    @PostMapping("/joinTenantByHouseNumber")
+    public Result<Integer> joinTenantByHouseNumber(@RequestBody SysTenant sysTenant){
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        Integer tenantId = sysTenantService.joinTenantByHouseNumber(sysTenant, sysUser.getId());
+        Result<Integer> result = new Result<>();
+        if(tenantId != 0){
+            result.setMessage("申请租户成功");
+            result.setSuccess(true);
+            result.setResult(tenantId);
+            return result;
+        }else{
+            result.setMessage("该门牌号不存在");
+            result.setSuccess(false);
+            return result;
+        }
+    }
+    
+    //update-begin---author:wangshuai ---date:20230107  for:[QQYUN-3725]申请加入租户,审核中状态增加接口------------
+    /**
+     * 分页获取租户用户数据(vue3用户租户页面)【低代码应用专用接口】
+     *
+     * @param pageNo
+     * @param pageSize
+     * @param userTenantStatus
+     * @param type
+     * @param req
+     * @return
+     */
+    @GetMapping("/getUserTenantPageList")
+    public Result<IPage<SysUserTenantVo>> getUserTenantPageList(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+                                                                @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+                                                                @RequestParam(name = "userTenantStatus") String userTenantStatus,
+                                                                @RequestParam(name = "type", required = false) String type,
+                                                                SysUser user,
+                                                                HttpServletRequest req) {
+        Page<SysUserTenantVo> page = new Page<SysUserTenantVo>(pageNo, pageSize);
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        String tenantId = oConvertUtils.getString(TenantContext.getTenant(), "0");
+        IPage<SysUserTenantVo> list = relationService.getUserTenantPageList(page, Arrays.asList(userTenantStatus.split(SymbolConstant.COMMA)), user, Integer.valueOf(tenantId));
+        return Result.ok(list);
+    }
+
+    /**
+     * 通过用户id获取租户列表【低代码应用专用接口】
+     *
+     * @param userTenantStatus 关系表的状态
+     * @return
+     */
+    @GetMapping("/getTenantListByUserId")
+    public Result<List<SysUserTenantVo>> getTenantListByUserId(@RequestParam(name = "userTenantStatus", required = false) String userTenantStatus) {
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        List<String> list = null;
+        if (oConvertUtils.isNotEmpty(userTenantStatus)) {
+            list = Arrays.asList(userTenantStatus.split(SymbolConstant.COMMA));
+        }
+        //租户状态,用户id,租户用户关系状态
+        List<SysUserTenantVo> sysTenant = relationService.getTenantListByUserId(sysUser.getId(), list);
+        return Result.ok(sysTenant);
+    }
+
+    /**
+     * 更新用户租户关系状态【低代码应用专用接口】
+     */
+    @PutMapping("/updateUserTenantStatus")
+    public Result<String> updateUserTenantStatus(@RequestBody SysUserTenant userTenant) {
+        String tenantId = TenantContext.getTenant();
+        if (oConvertUtils.isEmpty(tenantId)) {
+            return Result.error("未找到当前租户信息"); 
+        }
+        relationService.updateUserTenantStatus(userTenant.getUserId(), tenantId, userTenant.getStatus());
+        return Result.ok("更新用户租户状态成功");
+    }
+
+    /**
+     * 注销租户【低代码应用专用接口】
+     *
+     * @param sysTenant
+     * @return
+     */
+    @PutMapping("/cancelTenant")
+    public Result<String> cancelTenant(@RequestBody SysTenant sysTenant, HttpServletRequest request) {
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        SysTenant tenant = sysTenantService.getById(sysTenant.getId());
+        if (null == tenant) {
+            return Result.error("未找到当前租户信息");
+        }
+        if (!sysUser.getUsername().equals(tenant.getCreateBy())) {
+            return Result.error("无权限,只能注销自己创建的租户!");
+        }
+        SysUser userById = sysUserService.getById(sysUser.getId());
+        String loginPassword = request.getParameter("loginPassword");
+        String passwordEncode = PasswordUtil.encrypt(sysUser.getUsername(),loginPassword, userById.getSalt());
+        if (!passwordEncode.equals(userById.getPassword())) {
+            return Result.error("密码不正确");
+        }
+        sysTenantService.removeById(sysTenant.getId());
+        return Result.ok("注销成功");
+    }
+    //update-end---author:wangshuai ---date:20230107  for:[QQYUN-3725]申请加入租户,审核中状态增加接口------------
+
+    /**
+     * 获取租户用户不同状态下的数量【低代码应用专用接口】
+     * @return
+     */
+    @GetMapping("/getTenantStatusCount")
+    public Result<Long> getTenantStatusCount(@RequestParam(value = "status",defaultValue = "1") String status, HttpServletRequest req){
+        String tenantId = TokenUtils.getTenantIdByRequest(req);
+        if (null == tenantId) {
+            return Result.error("未找到当前租户信息");
+        }
+        LambdaQueryWrapper<SysUserTenant> query = new LambdaQueryWrapper<>();
+        query.eq(SysUserTenant::getTenantId,tenantId);
+        query.eq(SysUserTenant::getStatus,status);
+        long count = relationService.count(query);
+        return Result.ok(count);
+    }
+
+    /**
+     * 用户取消租户申请【低代码应用专用接口】
+     * @param tenantId
+     * @return
+     */
+    @PutMapping("/cancelApplyTenant")
+    public Result<String> cancelApplyTenant(@RequestParam("tenantId") String tenantId){
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        sysTenantService.leaveTenant(sysUser.getId(),tenantId);
+        return Result.ok("取消申请成功");
+    }
+
+    //===========【低代码应用,前端专用接口 —— 加入限制只能维护和查看自己拥有的租户】==========================================================
+
+    /**
+     * 彻底删除租户
+     * @param ids
+     * @return
+     */
+    @DeleteMapping("/deleteLogicDeleted")
+    public Result<String> deleteTenantLogic(@RequestParam("ids") String ids){
+        sysTenantService.deleteTenantLogic(ids);
+        return Result.ok("彻底删除成功");
+    }
+
+    /**
+     * 还原删除的租户
+     * @param ids
+     * @return
+     */
+    @PutMapping("/revertTenantLogic")
+    public Result<String> revertTenantLogic(@RequestParam("ids") String ids){
+        sysTenantService.revertTenantLogic(ids);
+        return Result.ok("还原成功");
+    }
+
+    /**
+     * 退出租户【低代码应用专用接口】
+     * @param sysTenant
+     * @param request
+     * @return
+     */
+    @DeleteMapping("/exitUserTenant")
+    public Result<String> exitUserTenant(@RequestBody SysTenant sysTenant, HttpServletRequest request){
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        //验证用户是否已存在
+        Integer count = relationService.userTenantIzExist(sysUser.getId(),sysTenant.getId());
+        if (count == 0) {
+            return Result.error("此租户下没有当前用户");
+        }
+        //验证密码
+        String loginPassword = request.getParameter("loginPassword");
+        SysUser userById = sysUserService.getById(sysUser.getId());
+        String passwordEncode = PasswordUtil.encrypt(sysUser.getUsername(),loginPassword, userById.getSalt());
+        if (!passwordEncode.equals(userById.getPassword())) {
+            return Result.error("密码不正确");
+        }
+        //退出登录
+        sysTenantService.exitUserTenant(sysUser.getId(),sysUser.getUsername(),String.valueOf(sysTenant.getId()));
+        return Result.ok("退出租户成功");
+    }
+
+    /**
+     * 变更租户拥有者【低代码应用专用接口】
+     * @param userId
+     * @return
+     */
+    @PostMapping("/changeOwenUserTenant")
+    public Result<String> changeOwenUserTenant(@RequestParam("userId") String userId){
+        sysTenantService.changeOwenUserTenant(userId);
+        return Result.ok("退出租户成功");
+    }
+
+    /**
+     * 邀请用户到租户,通过手机号匹配 【低代码应用专用接口】
+     * @param phone
+     * @return
+     */
+    @PostMapping("/invitationUser")
+    public Result<String> invitationUser(@RequestParam(name="phone") String phone){
+        return sysTenantService.invitationUser(phone);
+    }
+
+
+    /**
+     * 获取 租户产品包-3个默认admin的人员数量
+     * @param tenantId
+     * @return
+     */
+    @GetMapping("/loadAdminPackCount")
+    public Result<List<TenantPackUserCount>> loadAdminPackCount(@RequestParam("tenantId") Integer tenantId){
+        List<TenantPackUserCount> list = sysTenantService.queryTenantPackUserCount(tenantId);
+        return Result.ok(list);
+    }
+
+    /**
+     * 查询租户产品包信息
+     * @param packModel
+     * @return
+     */
+    @GetMapping("/getTenantPackInfo")
+    public Result<TenantPackModel> getTenantPackInfo(TenantPackModel packModel){
+        TenantPackModel tenantPackModel = sysTenantService.queryTenantPack(packModel);
+        return Result.ok(tenantPackModel);
+    }
+
+
+    /**
+     * 添加用户和产品包的关系数据
+     * @param sysTenantPackUser
+     * @return
+     */
+    @PostMapping("/addTenantPackUser")
+    public Result<?> addTenantPackUser(@RequestBody SysTenantPackUser sysTenantPackUser){
+        sysTenantService.addBatchTenantPackUser(sysTenantPackUser);
+        return Result.ok("操作成功!");
+    }
+
+    /**
+     * 从产品包移除用户
+     * @param sysTenantPackUser
+     * @return
+     */
+    @PutMapping("/deleteTenantPackUser")
+    public Result<?> deleteTenantPackUser(@RequestBody SysTenantPackUser sysTenantPackUser){
+        sysTenantService.deleteTenantPackUser(sysTenantPackUser);
+        return Result.ok("操作成功!");
+    }
+
+
+    /**
+     * 修改申请状态
+     * @param sysTenant
+     * @return
+     */
+    @PutMapping("/updateApplyStatus")
+    public Result<?> updateApplyStatus(@RequestBody SysTenant sysTenant){
+        SysTenant entity = this.sysTenantService.getById(sysTenant.getId());
+        if(entity==null){
+            return Result.error("租户不存在!");
+        }
+        entity.setApplyStatus(sysTenant.getApplyStatus());
+        sysTenantService.updateById(entity);
+        return Result.ok("");
+    }
+
+
+    /**
+     * 获取产品包人员申请列表
+     * @param tenantId
+     * @return
+     */
+    @GetMapping("/getTenantPackApplyUsers")
+    public Result<?> getTenantPackApplyUsers(@RequestParam("tenantId") Integer tenantId){
+        List<TenantPackUser> list = sysTenantService.getTenantPackApplyUsers(tenantId);
+        return Result.ok(list);
+    }
+
+    /**
+     * 个人 申请成为管理员
+     * @param sysTenantPackUser
+     * @return
+     */
+    @PostMapping("/doApplyTenantPackUser")
+    public Result<?> doApplyTenantPackUser(@RequestBody SysTenantPackUser sysTenantPackUser){
+        sysTenantService.doApplyTenantPackUser(sysTenantPackUser);
+        return Result.ok("申请成功!");
+    }
+
+    /**
+     * 申请通过 成为管理员
+     * @param sysTenantPackUser
+     * @return
+     */
+    @PutMapping("/passApply")
+    public Result<?> passApply(@RequestBody SysTenantPackUser sysTenantPackUser){
+        sysTenantService.passApply(sysTenantPackUser);
+        return Result.ok("操作成功!");
+    }
+
+    /**
+     *  拒绝申请 成为管理员
+     * @param sysTenantPackUser
+     * @return
+     */
+    @PutMapping("/deleteApply")
+    public Result<?> deleteApply(@RequestBody SysTenantPackUser sysTenantPackUser){
+        sysTenantService.deleteApply(sysTenantPackUser);
+        return Result.ok("");
+    }
+
+
+    /**
+     * 进入应用组织页面 查询租户信息及当前用户是否有 管理员的权限--
+     * @param id
+     * @return
+     */
+    @RequestMapping(value = "/queryTenantAuthInfo", method = RequestMethod.GET)
+    public Result<TenantDepartAuthInfo> queryTenantAuthInfo(@RequestParam(name="id",required=true) String id) {
+        TenantDepartAuthInfo info = sysTenantService.getTenantDepartAuthInfo(Integer.parseInt(id));
+        return Result.ok(info);
+    }
+
+    /**
+     * 获取产品包下的用户列表(分页)
+     * @param tenantId
+     * @param packId
+     * @param status
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    @GetMapping("/queryTenantPackUserList")
+    public Result<IPage<TenantPackUser>> queryTenantPackUserList(@RequestParam("tenantId") String tenantId,
+                                                                 @RequestParam("packId") String packId,
+                                                                 @RequestParam("status") Integer status,
+                                                                 @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+                                                                 @RequestParam(name="pageSize", defaultValue="10") Integer pageSize){
+        Page<TenantPackUser> page = new Page<>(pageNo,pageSize);
+        IPage<TenantPackUser> pageList = sysTenantService.queryTenantPackUserList(tenantId,packId,status,page);
+        return Result.ok(pageList);
+    }
 }

+ 353 - 78
system/system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java

@@ -17,9 +17,10 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.shiro.authz.annotation.RequiresRoles;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.annotation.PermissionData;
+import org.jeecg.common.config.TenantContext;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.constant.SymbolConstant;
-import org.jeecg.common.system.api.ISysBaseAPI;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
 import org.jeecg.modules.base.service.BaseCommonService;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.system.util.JwtUtil;
@@ -31,6 +32,8 @@ import org.jeecg.modules.system.model.SysUserSysDepartModel;
 import org.jeecg.modules.system.service.*;
 import org.jeecg.modules.system.vo.SysDepartUsersVO;
 import org.jeecg.modules.system.vo.SysUserRoleVO;
+import org.jeecg.modules.system.vo.lowapp.DepartAndUserInfo;
+import org.jeecg.modules.system.vo.lowapp.UpdateDepartInfo;
 import org.jeecgframework.poi.excel.ExcelImportUtil;
 import org.jeecgframework.poi.excel.def.NormalExcelConstants;
 import org.jeecgframework.poi.excel.entity.ExportParams;
@@ -74,9 +77,6 @@ public class SysUserController {
 	@Autowired
 	private ISysUserDepartService sysUserDepartService;
 
-	@Autowired
-	private ISysUserRoleService userRoleService;
-
     @Autowired
     private ISysDepartRoleUserService departRoleUserService;
 
@@ -92,8 +92,17 @@ public class SysUserController {
     @Autowired
     private BaseCommonService baseCommonService;
 
+    @Autowired
+    private ISysUserAgentService sysUserAgentService;
+
+    @Autowired
+    private ISysPositionService sysPositionService;
+
+    @Autowired
+    private ISysUserTenantService userTenantService;
+
     /**
-     * 获取用户列表数据
+     * 获取租户下用户数据(支持租户隔离)
      * @param user
      * @param pageNo
      * @param pageSize
@@ -104,63 +113,42 @@ public class SysUserController {
 	@RequestMapping(value = "/list", method = RequestMethod.GET)
 	public Result<IPage<SysUser>> queryPageList(SysUser user,@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
 									  @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,HttpServletRequest req) {
-		Result<IPage<SysUser>> result = new Result<IPage<SysUser>>();
 		QueryWrapper<SysUser> queryWrapper = QueryGenerator.initQueryWrapper(user, req.getParameterMap());
-        
-        //update-begin-Author:wangshuai--Date:20211119--for:【vue3】通过部门id查询用户,通过code查询id
-        //部门ID
-        String departId = req.getParameter("departId");
-        if(oConvertUtils.isNotEmpty(departId)){
-            LambdaQueryWrapper<SysUserDepart> query = new LambdaQueryWrapper<>();
-            query.eq(SysUserDepart::getDepId,departId);
-            List<SysUserDepart> list = sysUserDepartService.list(query);
-            List<String> userIds = list.stream().map(SysUserDepart::getUserId).collect(Collectors.toList());
-            //update-begin---author:wangshuai ---date:20220322  for:[issues/I4XTYB]查询用户时,当部门id 下没有分配用户时接口报错------------
-            if(oConvertUtils.listIsNotEmpty(userIds)){
-                queryWrapper.in("id",userIds);
+        //------------------------------------------------------------------------------------------------
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
+            String tenantId = oConvertUtils.getString(TenantContext.getTenant(), "0");
+            //update-begin---author:wangshuai ---date:20221223  for:[QQYUN-3371]租户逻辑改造,改成关系表------------
+            List<String> userIds = userTenantService.getUserIdsByTenantId(Integer.valueOf(tenantId));
+            if (oConvertUtils.listIsNotEmpty(userIds)) {
+                queryWrapper.in("id", userIds);
             }else{
-                return Result.OK();
+                queryWrapper.eq("id", "通过租户查询不到任何用户");
             }
-            //update-end---author:wangshuai ---date:20220322  for:[issues/I4XTYB]查询用户时,当部门id 下没有分配用户时接口报错------------
+            //update-end---author:wangshuai ---date:20221223  for:[QQYUN-3371]租户逻辑改造,改成关系表------------
         }
-        //用户ID
-        String code = req.getParameter("code");
-        if(oConvertUtils.isNotEmpty(code)){
-            queryWrapper.in("id",Arrays.asList(code.split(",")));
-            pageSize = code.split(",").length;
-        }
-        //update-end-Author:wangshuai--Date:20211119--for:【vue3】通过部门id查询用户,通过code查询id
-
-        //update-begin-author:taoyan--date:20220104--for: JTC-372 【用户冻结问题】 online授权、用户组件,选择用户都能看到被冻结的用户
-        String status = req.getParameter("status");
-        if(oConvertUtils.isNotEmpty(status)){
-            queryWrapper.eq("status", Integer.parseInt(status));
-        }
-        //update-end-author:taoyan--date:20220104--for: JTC-372 【用户冻结问题】 online授权、用户组件,选择用户都能看到被冻结的用户
-
-        //TODO 外部模拟登陆临时账号,列表不显示
-        queryWrapper.ne("username","_reserve_user_external");
-		Page<SysUser> page = new Page<SysUser>(pageNo, pageSize);
-		IPage<SysUser> pageList = sysUserService.page(page, queryWrapper);
-
-        //批量查询用户的所属部门
-        //step.1 先拿到全部的 useids
-        //step.2 通过 useids,一次性查询用户的所属部门名字
-        List<String> userIds = pageList.getRecords().stream().map(SysUser::getId).collect(Collectors.toList());
-        if(userIds!=null && userIds.size()>0){
-            Map<String,String>  useDepNames = sysUserService.getDepNamesByUserIds(userIds);
-            pageList.getRecords().forEach(item->{
-                item.setOrgCodeTxt(useDepNames.get(item.getId()));
-            });
-        }
-		result.setSuccess(true);
-		result.setResult(pageList);
-		log.info(pageList.toString());
-		return result;
+        //------------------------------------------------------------------------------------------------
+        return sysUserService.queryPageList(req, queryWrapper, pageSize, pageNo);
 	}
 
-    //@RequiresRoles({"admin"})
-    //@RequiresPermissions("system:user:add")
+    /**
+     * 获取系统用户数据(查询全部用户,不做租户隔离)
+     *
+     * @param user
+     * @param pageNo
+     * @param pageSize
+     * @param req
+     * @return
+     */
+    @RequiresPermissions("system:user:listAll")
+    @RequestMapping(value = "/listAll", method = RequestMethod.GET)
+    public Result<IPage<SysUser>> queryAllPageList(SysUser user, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+                                                   @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
+        QueryWrapper<SysUser> queryWrapper = QueryGenerator.initQueryWrapper(user, req.getParameterMap());
+        return sysUserService.queryPageList(req, queryWrapper, pageSize, pageNo);
+    }
+
+    @RequiresPermissions("system:user:add")
 	@RequestMapping(value = "/add", method = RequestMethod.POST)
 	public Result<SysUser> add(@RequestBody JSONObject jsonObject) {
 		Result<SysUser> result = new Result<SysUser>();
@@ -178,7 +166,9 @@ public class SysUserController {
 			//用户表字段org_code不能在这里设置他的值
             user.setOrgCode(null);
 			// 保存用户走一个service 保证事务
-			sysUserService.saveUser(user, selectedRoles, selectedDeparts);
+            //获取租户ids
+            String relTenantIds = jsonObject.getString("relTenantIds");
+            sysUserService.saveUser(user, selectedRoles, selectedDeparts, relTenantIds);
             baseCommonService.addLog("添加用户,username: " +user.getUsername() ,CommonConstant.LOG_TYPE_2, 2);
 			result.success("添加成功!");
 		} catch (Exception e) {
@@ -188,8 +178,7 @@ public class SysUserController {
 		return result;
 	}
 
-    //@RequiresRoles({"admin"})
-    //@RequiresPermissions("system:user:edit")
+    @RequiresPermissions("system:user:edit")
 	@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
 	public Result<SysUser> edit(@RequestBody JSONObject jsonObject) {
 		Result<SysUser> result = new Result<SysUser>();
@@ -212,7 +201,9 @@ public class SysUserController {
                 //用户表字段org_code不能在这里设置他的值
                 user.setOrgCode(null);
                 // 修改用户走一个service 保证事务
-				sysUserService.editUser(user, roles, departs);
+                //获取租户ids
+                String relTenantIds = jsonObject.getString("relTenantIds");
+				sysUserService.editUser(user, roles, departs, relTenantIds);
 				result.success("修改成功!");
 			}
 		} catch (Exception e) {
@@ -225,7 +216,7 @@ public class SysUserController {
 	/**
 	 * 删除用户
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:delete")
 	@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
 	public Result<?> delete(@RequestParam(name="id",required=true) String id) {
 		baseCommonService.addLog("删除用户,id: " +id ,CommonConstant.LOG_TYPE_2, 3);
@@ -236,7 +227,7 @@ public class SysUserController {
 	/**
 	 * 批量删除用户
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:deleteBatch")
 	@RequestMapping(value = "/deleteBatch", method = RequestMethod.DELETE)
 	public Result<?> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
 		baseCommonService.addLog("批量删除用户, ids: " +ids ,CommonConstant.LOG_TYPE_2, 3);
@@ -249,7 +240,7 @@ public class SysUserController {
 	 * @param jsonObject
 	 * @return
 	 */
-	//@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:frozenBatch")
 	@RequestMapping(value = "/frozenBatch", method = RequestMethod.PUT)
 	public Result<SysUser> frozenBatch(@RequestBody JSONObject jsonObject) {
 		Result<SysUser> result = new Result<SysUser>();
@@ -272,6 +263,7 @@ public class SysUserController {
 
     }
 
+    @RequiresPermissions("system:user:queryById")
     @RequestMapping(value = "/queryById", method = RequestMethod.GET)
     public Result<SysUser> queryById(@RequestParam(name = "id", required = true) String id) {
         Result<SysUser> result = new Result<SysUser>();
@@ -285,6 +277,7 @@ public class SysUserController {
         return result;
     }
 
+    @RequiresPermissions("system:user:queryUserRole")
     @RequestMapping(value = "/queryUserRole", method = RequestMethod.GET)
     public Result<List<String>> queryUserRole(@RequestParam(name = "userid", required = true) String userid) {
         Result<List<String>> result = new Result<>();
@@ -337,7 +330,7 @@ public class SysUserController {
     /**
      * 修改密码
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:changepwd")
     @RequestMapping(value = "/changePassword", method = RequestMethod.PUT)
     public Result<?> changePassword(@RequestBody SysUser sysUser) {
         SysUser u = this.sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, sysUser.getUsername()));
@@ -460,8 +453,7 @@ public class SysUserController {
      * @param request
      * @param sysUser
      */
-    //@RequiresRoles({"admin"})
-    //@RequiresPermissions("system:user:export")
+    @RequiresPermissions("system:user:export")
     @RequestMapping(value = "/exportXls")
     public ModelAndView exportXls(SysUser sysUser,HttpServletRequest request) {
         // Step.1 组装查询条件
@@ -494,8 +486,7 @@ public class SysUserController {
      * @param response
      * @return
      */
-    //@RequiresRoles({"admin"})
-    //@RequiresPermissions("system:user:import")
+    @RequiresPermissions("system:user:import")
     @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
     public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response)throws IOException {
         MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
@@ -588,10 +579,28 @@ public class SysUserController {
 		return result;
 	}
 
+
+    /**
+     * @功能:根据id 批量查询
+     * @param userNames
+     * @return
+     */
+    @RequestMapping(value = "/queryByNames", method = RequestMethod.GET)
+    public Result<Collection<SysUser>> queryByNames(@RequestParam String userNames) {
+        Result<Collection<SysUser>> result = new Result<>();
+        String[] names = userNames.split(",");
+        QueryWrapper<SysUser> queryWrapper=new QueryWrapper();
+        queryWrapper.lambda().in(true,SysUser::getUsername,names);
+        Collection<SysUser> userRole = sysUserService.list(queryWrapper);
+        result.setSuccess(true);
+        result.setResult(userRole);
+        return result;
+    }
+
 	/**
 	 * 首页用户重置密码
 	 */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:updatepwd")
     @RequestMapping(value = "/updatePassword", method = RequestMethod.PUT)
 	public Result<?> updatePassword(@RequestBody JSONObject json) {
 		String username = json.getString("username");
@@ -632,7 +641,7 @@ public class SysUserController {
      * @param
      * @return
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:addUserRole")
     @RequestMapping(value = "/addSysUserRole", method = RequestMethod.POST)
     public Result<String> addSysUserRole(@RequestBody SysUserRoleVO sysUserRoleVO) {
         Result<String> result = new Result<String>();
@@ -663,7 +672,7 @@ public class SysUserController {
      * @param
      * @return
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:deleteRole")
     @RequestMapping(value = "/deleteUserRole", method = RequestMethod.DELETE)
     public Result<SysUserRole> deleteUserRole(@RequestParam(name="roleId") String roleId,
                                                     @RequestParam(name="userId",required=true) String userId
@@ -687,7 +696,7 @@ public class SysUserController {
      * @param
      * @return
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:deleteRoleBatch")
     @RequestMapping(value = "/deleteUserRoleBatch", method = RequestMethod.DELETE)
     public Result<SysUserRole> deleteUserRoleBatch(
             @RequestParam(name="roleId") String roleId,
@@ -740,6 +749,10 @@ public class SysUserController {
                     item.setOrgCode(useDepNames.get(item.getId()));
                 });
             }
+            //update-begin---author:wangshuai ---date:20221223  for:[QQYUN-3371]租户逻辑改造,改成关系表------------
+            //设置租户id
+            page.setRecords(userTenantService.setUserTenantIds(page.getRecords()));
+            //update-end---author:wangshuai ---date:20221223  for:[QQYUN-3371]租户逻辑改造,改成关系表------------
             result.setSuccess(true);
             result.setResult(pageList);
         }else{
@@ -814,7 +827,7 @@ public class SysUserController {
     /**
      * 给指定部门添加对应的用户
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:editDepartWithUser")
     @RequestMapping(value = "/editSysDepartWithUser", method = RequestMethod.POST)
     public Result<String> editSysDepartWithUser(@RequestBody SysDepartUsersVO sysDepartUsersVO) {
         Result<String> result = new Result<String>();
@@ -843,7 +856,7 @@ public class SysUserController {
     /**
      *   删除指定机构的用户关系
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:deleteUserInDepart")
     @RequestMapping(value = "/deleteUserInDepart", method = RequestMethod.DELETE)
     public Result<SysUserDepart> deleteUserInDepart(@RequestParam(name="depId") String depId,
                                                     @RequestParam(name="userId",required=true) String userId
@@ -875,7 +888,7 @@ public class SysUserController {
     /**
      * 批量删除指定机构的用户关系
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:deleteUserInDepartBatch")
     @RequestMapping(value = "/deleteUserInDepartBatch", method = RequestMethod.DELETE)
     public Result<SysUserDepart> deleteUserInDepartBatch(
             @RequestParam(name="depId") String depId,
@@ -982,20 +995,25 @@ public class SysUserController {
 			return result;
 		}
 
+        String realname = jsonObject.getString("realname");
+        if(oConvertUtils.isEmpty(realname)){
+            realname = username;
+        }
+        
 		try {
 			user.setCreateTime(new Date());// 设置创建时间
 			String salt = oConvertUtils.randomGen(8);
 			String passwordEncode = PasswordUtil.encrypt(username, password, salt);
 			user.setSalt(salt);
 			user.setUsername(username);
-			user.setRealname(username);
+			user.setRealname(realname);
 			user.setPassword(passwordEncode);
 			user.setEmail(email);
 			user.setPhone(phone);
 			user.setStatus(CommonConstant.USER_UNFREEZE);
 			user.setDelFlag(CommonConstant.DEL_FLAG_0);
 			user.setActivitiSync(CommonConstant.ACT_SYNC_0);
-			sysUserService.addUserWithRole(user,"ee8626f80f7c2619917b6236f3a7f02b");//默认临时角色 test
+			sysUserService.addUserWithRole(user,null);
 			result.success("注册成功");
 		} catch (Exception e) {
 			result.error500("注册失败");
@@ -1065,6 +1083,12 @@ public class SysUserController {
         SysUser user = sysUserService.getOne(query);
         Map<String,String> map = new HashMap(5);
         map.put("smscode",smscode);
+        if(null == user){
+            //前端根据文字做判断用户是否存在判断,不能修改
+            result.setMessage("用户信息不存在");
+            result.setSuccess(false);
+            return result;
+        }
         map.put("username",user.getUsername());
         result.setResult(map);
 		result.setSuccess(true);
@@ -1239,7 +1263,7 @@ public class SysUserController {
      * @param userIds 被删除的用户ID,多个id用半角逗号分割
      * @return
      */
-    //@RequiresRoles({"admin"})
+    @RequiresPermissions("system:user:deleteRecycleBin")
     @RequestMapping(value = "/deleteRecycleBin", method = RequestMethod.DELETE)
     public Result deleteRecycleBin(@RequestParam("userIds") String userIds) {
         if (StringUtils.isNotBlank(userIds)) {
@@ -1254,6 +1278,7 @@ public class SysUserController {
      * @param jsonObject
      * @return
      */
+    @RequiresRoles({"admin"})
     @RequestMapping(value = "/appEdit", method = {RequestMethod.PUT,RequestMethod.POST})
     public Result<SysUser> appEdit(HttpServletRequest request,@RequestBody JSONObject jsonObject) {
         Result<SysUser> result = new Result<SysUser>();
@@ -1466,5 +1491,255 @@ public class SysUserController {
         }
         return ls;
     }
+    
+    /**
+     * 聊天 创建聊天组件专用  根据用户账号、用户姓名、部门id分页查询
+     * @param departId 部门id
+     * @param keyword 搜索值
+     * @return
+     */
+    @GetMapping(value = "/getUserInformation")
+    public Result<IPage<SysUser>> getUserInformation(
+            @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+            @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+            @RequestParam(name = "departId", required = false) String departId,
+            @RequestParam(name="keyword",required=false) String keyword) {
+        //------------------------------------------------------------------------------------------------
+        Integer tenantId = null;
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+            tenantId = oConvertUtils.getInt(TenantContext.getTenant(),0);
+        }
+        //------------------------------------------------------------------------------------------------
+        IPage<SysUser> pageList = sysUserDepartService.getUserInformation(tenantId,departId, keyword, pageSize, pageNo);
+        return Result.OK(pageList);
+    }
+
+    /**
+     * 简版流程用户选择组件
+     * @param departId 部门id
+     * @param roleId 角色id
+     * @param keyword 搜索值
+     * @return
+     */
+    @GetMapping(value = "/selectUserList")
+    public Result<IPage<SysUser>> selectUserList(
+            @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+            @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+            @RequestParam(name = "departId", required = false) String departId,
+            @RequestParam(name = "roleId", required = false) String roleId,
+            @RequestParam(name="keyword",required=false) String keyword) {
+        //------------------------------------------------------------------------------------------------
+        Integer tenantId = null;
+        //是否开启系统管理模块的多租户数据隔离【SAAS多租户模式】
+        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+            String tenantStr = TenantContext.getTenant();
+            if(oConvertUtils.isNotEmpty(tenantStr)){
+                tenantId = Integer.parseInt(tenantStr);
+            }
+        }
+        //------------------------------------------------------------------------------------------------
+        IPage<SysUser> pageList = sysUserDepartService.getUserInformation(tenantId, departId,roleId, keyword, pageSize, pageNo);
+        return Result.OK(pageList);
+    }
+
+    /**
+     * 用户离职(新增代理人和用户状态变更操作)【低代码应用专用接口】
+     * @param sysUserAgent
+     * @return
+     */
+    @PutMapping("/userQuitAgent")
+    public Result<String> userQuitAgent(@RequestBody SysUserAgent sysUserAgent){
+        //判断id是否为空
+        if(oConvertUtils.isNotEmpty(sysUserAgent.getId())){
+            sysUserAgentService.updateById(sysUserAgent);
+        }else{
+            sysUserAgentService.save(sysUserAgent);
+        }
+        sysUserService.userQuit(sysUserAgent.getUserName());
+        return Result.ok("离职成功");
+    }
+
+    /**
+     * 获取被逻辑删除的用户列表,无分页【低代码应用专用接口】
+     *
+     * @return List<SysUser>
+     */
+    @GetMapping("/getQuitList")
+    public Result<List<SysUser>> getQuitList(HttpServletRequest req) {
+        Integer tenantId = oConvertUtils.getInt(TokenUtils.getTenantIdByRequest(req),0);
+        List<SysUser> quitList = sysUserService.getQuitList(tenantId);
+        if (null != quitList && quitList.size() > 0) {
+            // 批量查询用户的所属部门
+            // step.1 先拿到全部的 userIds
+            List<String> userIds = quitList.stream().map(SysUser::getId).collect(Collectors.toList());
+            // step.2 通过 userIds,一次性查询用户的所属部门名字
+            Map<String, String> useDepNames = sysUserService.getDepNamesByUserIds(userIds);
+            quitList.forEach(item -> item.setOrgCode(useDepNames.get(item.getId())));
+        }
+        return Result.ok(quitList);
+    }
+
+    /**
+     * 更新刪除状态和离职状态【低代码应用专用接口】
+     * @param jsonObject
+     * @return Result<String>
+     */
+    @PutMapping("/putCancelQuit")
+    public Result<String> putCancelQuit(@RequestBody JSONObject jsonObject, HttpServletRequest request){
+        String userIds = jsonObject.getString("userIds");
+        String usernames = jsonObject.getString("usernames");
+        Integer tenantId = oConvertUtils.getInt(TokenUtils.getTenantIdByRequest(request),0);
+        //将状态改成未删除
+        if (StringUtils.isNotBlank(userIds)) {
+            userTenantService.putCancelQuit(Arrays.asList(userIds.split(SymbolConstant.COMMA)),tenantId);
+        }
+        if(StringUtils.isNotEmpty(usernames)){
+            //根据用户名删除代理人
+            LambdaQueryWrapper<SysUserAgent> query = new LambdaQueryWrapper<>();
+            query.in(SysUserAgent::getUserName,Arrays.asList(usernames.split(SymbolConstant.COMMA)));
+            sysUserAgentService.remove(query);
+        }
+        return Result.ok("取消离职成功");
+    }
+
+    /**
+     * 获取用户信息(vue3用户设置专用)【低代码应用专用接口】
+     * @return
+     */
+    @GetMapping("/login/setting/getUserData")
+    public Result<SysUser> getUserData(HttpServletRequest request) {
+        String username = JwtUtil.getUserNameByToken(request);
+        SysUser user = sysUserService.getUserByName(username);
+        if(user==null) {
+            return Result.error("未找到该用户数据");
+        }
+        if(oConvertUtils.isNotEmpty(user.getPost())){
+            String post = user.getPost();
+            LambdaQueryWrapper<SysPosition> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(SysPosition::getCode,Arrays.asList(post.split(SymbolConstant.COMMA)));
+            queryWrapper.select(SysPosition::getName);
+            List<SysPosition> sysPositionList = sysPositionService.list(queryWrapper);
+            StringBuilder nameBuilder = new StringBuilder();
+            String verticalBar = " | ";
+            for (SysPosition sysPosition:sysPositionList){
+                nameBuilder.append(sysPosition.getName()).append(verticalBar);
+            }
+            String names = nameBuilder.toString();
+            if(oConvertUtils.isNotEmpty(names)){
+                names = names.substring(0,names.lastIndexOf(verticalBar));
+                user.setPostText(names);
+            }
+        }
+        return Result.ok(user);
+    }
+
+    /**
+     * 用户编辑(vue3用户设置专用)【低代码应用专用接口】
+     * @param sysUser
+     * @return
+     */
+    @PostMapping("/login/setting/userEdit")
+    @RequiresPermissions("system:user:setting:edit")
+    public Result<String> userEdit(@RequestBody SysUser sysUser, HttpServletRequest request) {
+        String username = JwtUtil.getUserNameByToken(request);
+        SysUser user = sysUserService.getById(sysUser.getId());
+        if(user==null) {
+           return Result.error("未找到该用户数据");
+        }
+        if(!username.equals(user.getUsername())){
+            return Result.error("只能修改自己的数据");
+        }
+        sysUserService.updateById(sysUser);
+        return Result.ok("更新个人信息成功");
+    }
+
+    /**
+     * 批量修改 【low-app】
+     * @param jsonObject
+     * @return
+     */
+    @PutMapping("/batchEditUsers")
+    public Result<SysUser> batchEditUsers(@RequestBody JSONObject jsonObject) {
+        Result<SysUser> result = new Result<SysUser>();
+        try {
+            sysUserService.batchEditUsers(jsonObject);
+            result.setSuccess(true);
+            result.setMessage("操作成功!");
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            result.error500("操作失败");
+        }
+        return result;
+    }
+
+    /**
+     * 根据关键词搜索部门和用户【low-app】
+     * @param keyword
+     * @return
+     */
+    @GetMapping("/searchByKeyword")
+    public Result<DepartAndUserInfo> searchByKeyword(@RequestParam(name="keyword",required=false) String keyword) {
+        DepartAndUserInfo info = sysUserService.searchByKeyword(keyword);
+        return Result.ok(info);
+    }
+
+    /**
+     * 编辑部门前获取部门相关信息 【low-app】
+     * @param id
+     * @return
+     */
+    @GetMapping("/getUpdateDepartInfo")
+    public Result<UpdateDepartInfo> getUpdateDepartInfo(@RequestParam(name="id",required=false) String id) {
+        UpdateDepartInfo info = sysUserService.getUpdateDepartInfo(id);
+        return Result.ok(info);
+    }
 
+    /**
+     * 编辑部门 【low-app】
+     * @param updateDepartInfo
+     * @return
+     */
+    @PutMapping("/doUpdateDepartInfo")
+    public Result<?> doUpdateDepartInfo(@RequestBody UpdateDepartInfo updateDepartInfo) {
+        sysUserService.doUpdateDepartInfo(updateDepartInfo);
+        return Result.ok();
+    }
+
+    /**
+     * 设置负责人 取消负责人
+     * @param json
+     * @return
+     */
+    @PutMapping("/changeDepartChargePerson")
+    public Result<?> changeDepartChargePerson(@RequestBody JSONObject json) {
+        sysUserService.changeDepartChargePerson(json);
+        return Result.ok();
+    }
+
+    /**
+     * 修改租户下的用户【低代码应用专用接口】
+     * @param sysUser
+     * @param req
+     * @return
+     */
+    @RequestMapping(value = "/editTenantUser", method = {RequestMethod.PUT,RequestMethod.POST})
+    public Result<String> editTenantUser(@RequestBody SysUser sysUser,HttpServletRequest req){
+        Result<String> result = new Result<>();
+        String tenantId = TokenUtils.getTenantIdByRequest(req);
+        if(oConvertUtils.isEmpty(tenantId)){
+            return result.error500("无权修改他人信息!");
+        }
+        LambdaQueryWrapper<SysUserTenant> query = new LambdaQueryWrapper<>();
+        query.eq(SysUserTenant::getTenantId,Integer.valueOf(tenantId));
+        query.eq(SysUserTenant::getUserId,sysUser.getId());
+        SysUserTenant one = userTenantService.getOne(query);
+        if(null == one){
+            return result.error500("非当前租户下的用户,不允许修改!");
+        }
+        String departs = req.getParameter("selecteddeparts");
+        String roles = req.getParameter("selectedroles");
+        sysUserService.editTenantUser(sysUser,tenantId,departs,roles);
+        return Result.ok("修改成功");
+    }
 }

+ 15 - 9
system/system-biz/src/main/java/org/jeecg/modules/system/controller/ThirdLoginController.java

@@ -1,5 +1,6 @@
 package org.jeecg.modules.system.controller;
 
+import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.xkcoding.justauth.AuthRequestFactory;
@@ -12,16 +13,14 @@ import me.zhyd.oauth.utils.AuthStateUtils;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.util.JwtUtil;
-import org.jeecg.common.util.PasswordUtil;
-import org.jeecg.common.util.RedisUtil;
-import org.jeecg.common.util.RestUtil;
-import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.common.util.*;
 import org.jeecg.config.thirdapp.ThirdAppConfig;
 import org.jeecg.config.thirdapp.ThirdAppTypeItemVo;
 import org.jeecg.modules.base.service.BaseCommonService;
 import org.jeecg.modules.system.entity.SysThirdAccount;
 import org.jeecg.modules.system.entity.SysUser;
 import org.jeecg.modules.system.model.ThirdLoginModel;
+import org.jeecg.modules.system.service.ISysDictService;
 import org.jeecg.modules.system.service.ISysThirdAccountService;
 import org.jeecg.modules.system.service.ISysUserService;
 import org.jeecg.modules.system.service.impl.ThirdAppDingtalkServiceImpl;
@@ -31,10 +30,12 @@ import org.springframework.stereotype.Controller;
 import org.springframework.ui.ModelMap;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -49,7 +50,8 @@ public class ThirdLoginController {
 	private ISysUserService sysUserService;
 	@Autowired
 	private ISysThirdAccountService sysThirdAccountService;
-
+	@Autowired
+	private ISysDictService sysDictService;
 	@Autowired
 	private BaseCommonService baseCommonService;
 	@Autowired
@@ -91,8 +93,8 @@ public class ThirdLoginController {
         	//判断有没有这个人
 			//update-begin-author:wangshuai date:20201118 for:修改成查询第三方账户表
         	LambdaQueryWrapper<SysThirdAccount> query = new LambdaQueryWrapper<SysThirdAccount>();
-        	query.eq(SysThirdAccount::getThirdUserUuid, uuid);
         	query.eq(SysThirdAccount::getThirdType, source);
+			query.and(q -> q.eq(SysThirdAccount::getThirdUserUuid, uuid).or().eq(SysThirdAccount::getThirdUserId, uuid));
         	List<SysThirdAccount> thridList = sysThirdAccountService.list(query);
 			SysThirdAccount user = null;
         	if(thridList==null || thridList.size()==0) {
@@ -235,8 +237,12 @@ public class ThirdLoginController {
 		}
 		//update-end-author:wangshuai date:20201118 for:如果真实姓名和头像不存在就取第三方登录的
 		JSONObject obj = new JSONObject();
+		//TODO 第三方登确定登录租户和部门逻辑
+		
 		//用户登录信息
 		obj.put("userInfo", sysUser);
+		//获取字典缓存【解决 #jeecg-boot/issues/3998】
+		obj.put("sysAllDictItems", sysDictService.queryAllDictItems());
 		//token 信息
 		obj.put("token", token);
 		result.setResult(obj);
@@ -293,7 +299,7 @@ public class ThirdLoginController {
 	 */
 	@ResponseBody
 	@GetMapping("/oauth2/{source}/login")
-	public String oauth2LoginCallback(@PathVariable("source") String source, @RequestParam("state") String state, HttpServletResponse response) throws Exception {
+	public String oauth2LoginCallback(@PathVariable("source") String source, @RequestParam("state") String state, HttpServletRequest request, HttpServletResponse response) throws Exception {
 		String url;
 		if (ThirdAppConfig.WECHAT_ENTERPRISE.equalsIgnoreCase(source)) {
 			ThirdAppTypeItemVo config = thirdAppConfig.getWechatEnterprise();
@@ -303,7 +309,7 @@ public class ThirdLoginController {
 			// 企业的CorpID
 			builder.append("?appid=").append(config.getClientId());
 			// 授权后重定向的回调链接地址,请使用urlencode对链接进行处理
-			String redirectUri = RestUtil.getBaseUrl() + "/sys/thirdLogin/oauth2/wechat_enterprise/callback";
+			String redirectUri = CommonUtils.getBaseUrl(request)  + "/sys/thirdLogin/oauth2/wechat_enterprise/callback";
 			builder.append("&redirect_uri=").append(URLEncoder.encode(redirectUri, "UTF-8"));
 			// 返回类型,此时固定为:code
 			builder.append("&response_type=code");
@@ -322,7 +328,7 @@ public class ThirdLoginController {
 			builder.append("https://login.dingtalk.com/oauth2/auth");
 			// 授权通过/拒绝后回调地址。
 			// 注意 需要与注册应用时登记的域名保持一致。
-			String redirectUri = RestUtil.getBaseUrl() + "/sys/thirdLogin/oauth2/dingtalk/callback";
+			String redirectUri = CommonUtils.getBaseUrl(request) + "/sys/thirdLogin/oauth2/dingtalk/callback";
 			builder.append("?redirect_uri=").append(URLEncoder.encode(redirectUri, "UTF-8"));
 			// 固定值为code。
 			// 授权通过后返回authCode。

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä