2 Commits 6cd865fac3 ... d5021f3b97

Autore SHA1 Messaggio Data
  1811872455@163.com d5021f3b97 Merge remote-tracking branch 'origin/master' 2 settimane fa
  1811872455@163.com ee34547491 框架结构修改 2 settimane fa
71 ha cambiato i file con 6479 aggiunte e 302 eliminazioni
  1. 6 0
      java/storlead-api/pom.xml
  2. 0 189
      java/storlead-api/src/main/java/com/storlead/login/LoginApiController.java
  3. 244 0
      java/storlead-api/src/main/java/com/storlead/login/TenantLoginApiController.java
  4. 88 0
      java/storlead-api/src/main/java/com/storlead/system/controller/CompanyApiController.java
  5. 65 0
      java/storlead-api/src/main/java/com/storlead/system/controller/CompanyCustomSettingsController.java
  6. 252 0
      java/storlead-api/src/main/java/com/storlead/system/controller/CorpWeChatController.java
  7. 657 0
      java/storlead-api/src/main/java/com/storlead/system/controller/DeptApiController.java
  8. 169 0
      java/storlead-api/src/main/java/com/storlead/system/controller/DeptController.java
  9. 19 0
      java/storlead-api/src/main/java/com/storlead/system/controller/DeptRoleController.java
  10. 359 0
      java/storlead-api/src/main/java/com/storlead/system/controller/FileResourceController.java
  11. 109 0
      java/storlead-api/src/main/java/com/storlead/system/controller/JobApiController.java
  12. 53 0
      java/storlead-api/src/main/java/com/storlead/system/controller/JobController.java
  13. 522 0
      java/storlead-api/src/main/java/com/storlead/system/controller/LoginApiController.java
  14. 152 0
      java/storlead-api/src/main/java/com/storlead/system/controller/LoginController.java
  15. 505 0
      java/storlead-api/src/main/java/com/storlead/system/controller/MenuApiController.java
  16. 310 0
      java/storlead-api/src/main/java/com/storlead/system/controller/OrganizDataSyncApiController.java
  17. 165 0
      java/storlead-api/src/main/java/com/storlead/system/controller/PermissionResApiController.java
  18. 163 0
      java/storlead-api/src/main/java/com/storlead/system/controller/QuickMenuApiController.java
  19. 294 0
      java/storlead-api/src/main/java/com/storlead/system/controller/RoleApiController.java
  20. 161 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SubCompanyApiController.java
  21. 81 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SubCompanyController.java
  22. 174 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SysDictApiController.java
  23. 60 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SystemConfigItemController.java
  24. 50 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SystemOssConfigApiController.java
  25. 52 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SystemPersonalizeSettingApiController.java
  26. 50 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SystemSettingApiController.java
  27. 41 0
      java/storlead-api/src/main/java/com/storlead/system/controller/SystemWechatConfigApiController.java
  28. 607 0
      java/storlead-api/src/main/java/com/storlead/system/controller/UserApiController.java
  29. 7 6
      java/storlead-api/src/main/java/com/storlead/system/controller/UserController.java
  30. 14 0
      java/storlead-api/src/main/java/com/storlead/system/dto/CompanyDTO.java
  31. 5 1
      java/storlead-api/src/main/resources/application.yml
  32. 2 0
      java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java
  33. 17 0
      java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/MenuPermis.java
  34. 1 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/properties/UrlChainDefinitionPorperties.java
  35. 232 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/PingyinUtil.java
  36. 144 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/CryptoZipUtil.java
  37. 62 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/HttpSecurityCheckUtil.java
  38. 55 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/menu/TreeVo.java
  39. 0 4
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/entity/SysBaseField.java
  40. 27 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/entity/TenantBaseField.java
  41. 15 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/mapper/MyBaseMapper.java
  42. 3 2
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/query/QueryBaseEntity.java
  43. 56 42
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/service/MyBaseService.java
  44. 263 17
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/service/impl/MyBaseServiceImpl.java
  45. 2 1
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerAnalysisResultEntity.java
  46. 1 1
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/impl/CustomerAiAnalysisServiceImpl.java
  47. 0 4
      java/storlead-system/storlead-system-api/src/main/java/com/storlead/system/api/package-info.java
  48. 1 7
      java/storlead-system/storlead-system-biz/src/main/java/com/storlead/system/assemble/DataScope.java
  49. 2 1
      java/storlead-thirdparty/pom.xml
  50. 33 0
      java/storlead-thirdparty/storlead-thirdparty-biz/pom.xml
  51. 28 0
      java/storlead-thirdparty/storlead-thirdparty-biz/src/main/java/com/storlead/thirdparty/biz/config/ThirdPartyBizAutoConfiguration.java
  52. 6 6
      java/storlead-thirdparty/storlead-thirdparty-biz/src/main/java/com/storlead/thirdparty/biz/service/DefaultThirdPartyService.java
  53. 2 0
      java/storlead-thirdparty/storlead-thirdparty-biz/src/main/resources/META-INF/spring.factories
  54. 1 1
      java/storlead-thirdparty/storlead-thirdparty-dingtalk/pom.xml
  55. 3 6
      java/storlead-thirdparty/storlead-thirdparty-spi/pom.xml
  56. 1 1
      java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/ThirdPartyDirectorySyncClient.java
  57. 1 1
      java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/ThirdPartyMessageClient.java
  58. 1 1
      java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/ThirdPartyUserSyncClient.java
  59. 4 0
      java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/package-info.java
  60. 1 1
      java/storlead-thirdparty/storlead-thirdparty-wecom/pom.xml
  61. 4 0
      java/storlead-user/storlead-user-biz/pom.xml
  62. 14 0
      java/storlead-user/storlead-user-biz/src/main/java/com/storlead/user/service/impl/TenantEnterpriseServiceImpl.java
  63. 2 0
      java/storlead-user/storlead-user-biz/src/main/java/com/storlead/user/service/impl/TenantUserServiceImpl.java
  64. 33 0
      java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/dto/TenantLoginDTO.java
  65. 1 1
      java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/dto/UserParam.java
  66. 2 1
      java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/entity/TenantEnterpriseEntity.java
  67. 4 2
      java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/entity/TenantUserEntity.java
  68. 5 0
      java/storlead-user/storlead-user-spi/src/main/java/com/storlead/user/service/ITenantEnterpriseService.java
  69. 4 1
      java/storlead-user/storlead-user-spi/src/main/java/com/storlead/user/service/ITenantUserService.java
  70. 6 4
      java/项目模块结构说明.md
  71. 6 1
      pom.xml

+ 6 - 0
java/storlead-api/pom.xml

@@ -56,6 +56,12 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-system-biz</artifactId>
         </dependency>
+
+        <!-- UserServiceImpl 等依赖 ThirdPartyUserSyncService,默认实现见 storlead-thirdparty-biz -->
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-thirdparty-biz</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 0 - 189
java/storlead-api/src/main/java/com/storlead/login/LoginApiController.java

@@ -1,189 +0,0 @@
-package com.storlead.login;
-
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.crypto.SecureUtil;
-import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.storlead.framework.auth.jwt.JwtUtil;
-import com.storlead.framework.auth.vo.LoginUser;
-import com.storlead.framework.common.constant.CommonConstant;
-import com.storlead.framework.common.constant.RedisKeySaltConstant;
-import com.storlead.framework.common.ecode.BCryptPasswordEncoder;
-import com.storlead.framework.common.util.RsaUtils;
-import com.storlead.framework.redis.RedisService;
-import com.storlead.framework.common.result.Result;
-import com.storlead.user.pojo.dto.UserLoginDTO;
-import com.storlead.user.pojo.entity.DeptEntity;
-import com.storlead.user.pojo.entity.JobEntity;
-import com.storlead.user.pojo.entity.UserEntity;
-import com.storlead.user.service.IDepartService;
-import com.storlead.user.service.IJobService;
-import com.storlead.user.service.IUserService;
-import com.storlead.system.service.IMenuService;
-import com.storlead.system.service.IUserRoleService;
-import com.storlead.system.util.SystemConfigItemCacheUtil;
-import com.storlead.sms.spi.SmsCaptchaScene;
-import com.storlead.sms.spi.SmsCaptchaService;
-import com.storlead.wecom.service.CorpWeChatService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-
-@RestController
-@RequestMapping("/sys/auth")
-@Api(tags="System -> 登录模块")
-@Slf4j
-public class LoginApiController {
-
-    @Resource
-    private RedisService redisService;
-
-    @Resource
-    private IUserService userService;
-
-    @Resource
-    private IMenuService menuService;
-
-    @Resource
-    private IDepartService departService;
-
-    @Resource
-    private CorpWeChatService corpWeChatService;
-
-    @Resource
-    private SmsCaptchaService smsCaptchaService;
-
-    @Value("${environment}")
-    private  String environment;
-
-    @Resource
-    private IUserRoleService userRoleService;
-
-    @Resource
-    private IJobService jobService;
-
-    @RequestMapping(value = "/login", method = RequestMethod.POST)
-    @ApiOperation(value = "用户登录接口", notes = "用户登录接口")
-    public Result login(@RequestBody UserLoginDTO loginDTO) {
-
-        if (Objects.isNull(loginDTO)) {
-            return Result.error("参数错误");
-        }
-        UserEntity userInfo;
-        JSONObject obj = new JSONObject();
-        if (StrUtil.isNotBlank(loginDTO.getWxCode())) {
-            String code = corpWeChatService.login(loginDTO.getWxCode());
-            userInfo = userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getXworkUserId,code));
-        } else if (StrUtil.isNotBlank(loginDTO.getSmsCode())) {
-            // 验证码登录:仅在 /login 内完成验证码校验(consume=true 防止重复使用),再查用户
-            String mobile = StrUtil.trimToEmpty(loginDTO.getAccount());
-            String smsCode = StrUtil.trimToEmpty(loginDTO.getSmsCode());
-            if (StrUtil.isBlank(mobile)) {
-                return Result.error("请输入手机号");
-            }
-            if (StrUtil.isBlank(smsCode)) {
-                return Result.error("请输入验证码");
-            }
-            Result<?> vr = smsCaptchaService.verifyCaptcha(mobile, smsCode, SmsCaptchaScene.LOGIN, true);
-            if (!vr.isSuccess()) {
-                return Result.error(vr.getCode(), vr.getMessage());
-            }
-            userInfo = userService.getOne(new LambdaQueryWrapper<UserEntity>()
-                    .eq(UserEntity::getMobile, mobile)
-                    .eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0));
-            if (userInfo == null) {
-                return Result.error("该手机号未绑定用户或账号已删除");
-            }
-        } else {
-            if (StrUtil.isBlank(loginDTO.getAccount()) || StrUtil.isBlank(loginDTO.getPassword())) {
-                return Result.error("用户名或密码为空");
-            }
-            userInfo = userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,loginDTO.getAccount()).eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0));
-            if (Objects.isNull(userInfo)) {
-                return Result.error("账号不存在");
-            }
-
-            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
-            String password = "";
-            if((!environment.equals("prod") && !loginDTO.getPassword().equals("qq123456")) || environment.equals("prod")){
-                try {
-                    password = RsaUtils.decryptByPrivateKey(RsaUtils.PRIVATE_KEY, loginDTO.getPassword());
-                }catch (Exception e) {
-                    return Result.error("用户名或密码错误");
-                }
-            }
-            if(environment.equals("prod") || (environment.equals("test")
-                    && !loginDTO.getPassword().equals("qq123456") &&
-                    !password.equals("qq123456"))) {
-                if (!bCryptPasswordEncoder.matches(password, userInfo.getPassword())) {
-                    return Result.error("用户名或密码错误");
-                }
-            }
-            String regex = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{8,}$";
-            String defaultPassWord = SystemConfigItemCacheUtil.getDefaultPassWord();
-
-            if(password !=null && (!password.matches(regex) || password.equals(defaultPassWord))){
-                obj.put("status",1);
-            }else {
-                obj.put("status",2);
-            }
-
-        }
-        if (Objects.isNull(userInfo)) {
-            return Result.error("登录失败,未获取到用户信息");
-        }
-        if (!userInfo.getEnabled()) {
-            return Result.error("该账号用户暂无登录权限");
-        }
-
-        Integer possessMenuCount = menuService.checkUserMenuAccess(userInfo.getId());
-        if (possessMenuCount == 0) {
-            return Result.error("暂未获取到菜单权限,请联系管理员分配权限!");
-        }
-
-        LoginUser loginUser = new LoginUser();
-        BeanUtils.copyProperties(userInfo,loginUser);
-
-        if (Objects.nonNull(userInfo.getJobId())) {
-            JobEntity job = jobService.getById(userInfo.getJobId());
-            if (Objects.nonNull(job)) {
-                loginUser.setJobName(job.getName());
-            }
-        }
-
-        loginUser.setRoleIds(userRoleService.selectUserRoleByUserId(userInfo.getId()));
-        if (Objects.nonNull(userInfo.getDeptId())) {
-            DeptEntity dept = departService.getById(userInfo.getDeptId());
-            if (Objects.nonNull(dept)) {
-                loginUser.setDeptName(dept.getName());
-                loginUser.setDeptJobDes(dept.getDeptJobDes());
-            }
-        }
-
-        //用户登录信息
-        String jwtToken = JwtUtil.createJWT(JSONObject.toJSONString(loginUser),loginUser.getMobile());
-        String token = SecureUtil.md5(jwtToken);
-
-        String json = JSONObject.toJSONString(loginUser);
-        redisService.setCacheObject(token, json, 60 * 60 * 24L * 7, TimeUnit.SECONDS);
-
-        Map<String, String> apiMap = new HashMap();
-        redisService.setCacheObject(RedisKeySaltConstant.API_CODE_REDIS + loginUser.getMobile(), apiMap);
-
-        obj.put("token", token);
-        return Result.ok(obj);
-    }
-}

+ 244 - 0
java/storlead-api/src/main/java/com/storlead/login/TenantLoginApiController.java

@@ -0,0 +1,244 @@
+package com.storlead.login;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.auth.jwt.JwtUtil;
+import com.storlead.framework.auth.vo.LoginUser;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.common.constant.RedisKeySaltConstant;
+import com.storlead.framework.common.ecode.BCryptPasswordEncoder;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.common.util.RsaUtils;
+import com.storlead.framework.core.tenant.TenantContext;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.sms.spi.SmsCaptchaScene;
+import com.storlead.sms.spi.SmsCaptchaService;
+import com.storlead.system.service.IMenuService;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.system.util.SystemConfigItemCacheUtil;
+import com.storlead.user.pojo.dto.TenantLoginDTO;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.JobEntity;
+import com.storlead.user.pojo.entity.TenantEnterpriseEntity;
+import com.storlead.user.pojo.entity.TenantUserEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.IJobService;
+import com.storlead.user.service.ITenantEnterpriseService;
+import com.storlead.user.service.ITenantUserService;
+import com.storlead.user.service.IUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 租户空间登录:先用 {@code user} 表完成账号校验,再以表 {@code tenant_user} 判定是否属于目标租户,并写入 {@link LoginUser#setTenantId}。
+ */
+@RestController
+@RequestMapping("/sys/auth")
+@Api(tags = "System -> 租户登录")
+@Slf4j
+public class TenantLoginApiController {
+
+    @Resource
+    private RedisService redisService;
+    @Resource
+    private IUserService userService;
+    @Resource
+    private IMenuService menuService;
+    @Resource
+    private IDepartService departService;
+    @Resource
+    private SmsCaptchaService smsCaptchaService;
+    @Value("${environment}")
+    private String environment;
+    @Resource
+    private IUserRoleService userRoleService;
+    @Resource
+    private IJobService jobService;
+    @Resource
+    private ITenantEnterpriseService tenantEnterpriseService;
+    @Resource
+    private ITenantUserService tenantUserService;
+
+    @RequestMapping(value = "/tenant/login", method = RequestMethod.POST)
+    @ApiOperation(value = "租户登录", notes = "仅支持短信验证码或密码;需指定 tenantId 或 enterpriseCode;通过后必须在 tenant_user 存在对应关系")
+    public Result<?> tenantLogin(@RequestBody TenantLoginDTO loginDTO) {
+        if (loginDTO == null) {
+            return Result.error("参数错误");
+        }
+        if (loginDTO.getTenantId() == null && StrUtil.isBlank(loginDTO.getEnterpriseCode())) {
+            return Result.error("请指定 tenantId 或 enterpriseCode");
+        }
+
+        TenantContext.setIgnoreTenantLine(true);
+        try {
+            TenantEnterpriseEntity enterprise;
+            if (loginDTO.getTenantId() != null) {
+                enterprise = tenantEnterpriseService.getByTenantId(loginDTO.getTenantId());
+            } else {
+                enterprise = tenantEnterpriseService.getByEnterpriseCode(loginDTO.getEnterpriseCode());
+            }
+            if (enterprise == null) {
+                return Result.error("租户不存在或已删除");
+            }
+            if (Boolean.FALSE.equals(enterprise.getEnabled())) {
+                return Result.error("租户已禁用");
+            }
+            if (enterprise.getExpireAt() != null && enterprise.getExpireAt().before(new Date())) {
+                return Result.error("租户已过期");
+            }
+
+            JSONObject obj = new JSONObject();
+            UserEntity userInfo = authenticateAccount(loginDTO, obj);
+            if (userInfo == null) {
+                return Result.error(obj.getString("_err"));
+            }
+
+            if (!userInfo.getEnabled()) {
+                return Result.error("该账号用户暂无登录权限");
+            }
+
+            // 租户成员以 tenant_user 为准:无记录则不允许进入该租户空间
+            TenantUserEntity tenantUser = tenantUserService.getByTenantIdAndUserId(enterprise.getTenantId(), userInfo.getId());
+            if (tenantUser == null) {
+                return Result.error("该账号未在 tenant_user 中绑定当前租户");
+            }
+            if (tenantUser.getEnterpriseId() != null && !tenantUser.getEnterpriseId().equals(enterprise.getId())) {
+                return Result.error("租户成员数据不一致,请联系管理员检查 tenant_user");
+            }
+
+            Integer possessMenuCount = menuService.checkUserMenuAccess(userInfo.getId());
+            if (possessMenuCount == 0) {
+                return Result.error("暂未获取到菜单权限,请联系管理员分配权限!");
+            }
+
+            LoginUser loginUser = new LoginUser();
+            BeanUtils.copyProperties(userInfo, loginUser);
+            loginUser.setTenantId(enterprise.getTenantId());
+            loginUser.setIsAdmin(tenantUser.getIsAdmin() != null && tenantUser.getIsAdmin() == 1);
+
+            if (Objects.nonNull(userInfo.getJobId())) {
+                JobEntity job = jobService.getById(userInfo.getJobId());
+                if (Objects.nonNull(job)) {
+                    loginUser.setJobName(job.getName());
+                }
+            }
+            loginUser.setRoleIds(userRoleService.selectUserRoleByUserId(userInfo.getId()));
+            if (Objects.nonNull(userInfo.getDeptId())) {
+                DeptEntity dept = departService.getById(userInfo.getDeptId());
+                if (Objects.nonNull(dept)) {
+                    loginUser.setDeptName(dept.getName());
+                    loginUser.setDeptJobDes(dept.getDeptJobDes());
+                }
+            }
+
+            String jwtToken = JwtUtil.createJWT(JSONObject.toJSONString(loginUser), loginUser.getMobile());
+            String token = SecureUtil.md5(jwtToken);
+            String json = JSONObject.toJSONString(loginUser);
+            redisService.setCacheObject(token, json, 60 * 60 * 24L * 7, TimeUnit.SECONDS);
+
+            Map<String, String> apiMap = new HashMap<>();
+            redisService.setCacheObject(RedisKeySaltConstant.API_CODE_REDIS + loginUser.getMobile(), apiMap);
+
+            obj.remove("_err");
+            obj.put("token", token);
+            obj.put("tenantId", enterprise.getTenantId());
+            obj.put("enterpriseId", enterprise.getId());
+            @SuppressWarnings("unchecked")
+            Map<String, Object> body = JSONObject.parseObject(obj.toJSONString(), Map.class);
+            return Result.ok(body);
+        } finally {
+            TenantContext.setIgnoreTenantLine(false);
+        }
+    }
+
+    /**
+     * @param out 用于回传密码强度状态或错误信息(_err)
+     * @return 已认证用户;失败时返回 null
+     */
+    private UserEntity authenticateAccount(TenantLoginDTO loginDTO, JSONObject out) {
+        try {
+            if (StrUtil.isNotBlank(loginDTO.getSmsCode())) {
+                String mobile = StrUtil.trimToEmpty(loginDTO.getAccount());
+                String smsCode = StrUtil.trimToEmpty(loginDTO.getSmsCode());
+                if (StrUtil.isBlank(mobile)) {
+                    out.put("_err", "请输入手机号");
+                    return null;
+                }
+                if (StrUtil.isBlank(smsCode)) {
+                    out.put("_err", "请输入验证码");
+                    return null;
+                }
+                Result<?> vr = smsCaptchaService.verifyCaptcha(mobile, smsCode, SmsCaptchaScene.LOGIN, true);
+                if (!vr.isSuccess()) {
+                    out.put("_err", vr.getMessage());
+                    return null;
+                }
+                UserEntity u = userService.getOne(new LambdaQueryWrapper<UserEntity>()
+                        .eq(UserEntity::getMobile, mobile)
+                        .eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0));
+                if (u == null) {
+                    out.put("_err", "该手机号未绑定用户或账号已删除");
+                }
+                return u;
+            }
+            if (StrUtil.isBlank(loginDTO.getAccount()) || StrUtil.isBlank(loginDTO.getPassword())) {
+                out.put("_err", "请使用短信验证码登录(填写 account + smsCode)或密码登录(填写 account + password)");
+                return null;
+            }
+            UserEntity userInfo = userService.getOne(new LambdaQueryWrapper<UserEntity>()
+                    .eq(UserEntity::getMobile, loginDTO.getAccount())
+                    .eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0));
+            if (userInfo == null) {
+                out.put("_err", "账号不存在");
+                return null;
+            }
+            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+            String password = "";
+            if ((!environment.equals("prod") && !loginDTO.getPassword().equals("qq123456")) || environment.equals("prod")) {
+                try {
+                    password = RsaUtils.decryptByPrivateKey(RsaUtils.PRIVATE_KEY, loginDTO.getPassword());
+                } catch (Exception e) {
+                    out.put("_err", "用户名或密码错误");
+                    return null;
+                }
+            }
+            if (environment.equals("prod") || (environment.equals("test")
+                    && !loginDTO.getPassword().equals("qq123456")
+                    && !password.equals("qq123456"))) {
+                if (!encoder.matches(password, userInfo.getPassword())) {
+                    out.put("_err", "用户名或密码错误");
+                    return null;
+                }
+            }
+            String regex = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{8,}$";
+            String defaultPassWord = SystemConfigItemCacheUtil.getDefaultPassWord();
+            if (password != null && (!password.matches(regex) || password.equals(defaultPassWord))) {
+                out.put("status", 1);
+            } else {
+                out.put("status", 2);
+            }
+            return userInfo;
+        } catch (Exception e) {
+            log.warn("tenant login authenticate failed", e);
+            out.put("_err", "登录失败");
+            return null;
+        }
+    }
+}

+ 88 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/CompanyApiController.java

@@ -0,0 +1,88 @@
+package com.storlead.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.dto.CompanyDTO;
+import com.storlead.system.util.SystemSettingCacheCheckUtil;
+import com.storlead.user.pojo.entity.CompanyEntity;
+import com.storlead.user.pojo.entity.SubCompanyEntity;
+import com.storlead.user.service.ICompanyService;
+import com.storlead.user.service.ISubCompanyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-06-29 17:33
+ */
+
+@Api(tags = "系统: 公司数据维护")
+@RestController
+@RequestMapping("/sys/company")
+public class CompanyApiController {
+
+    @Resource
+    private ICompanyService companyService;
+
+    @Resource
+    private ISubCompanyService subCompanyService;
+
+    @ApiOperation("新增公司")
+    @PostMapping("save")
+    public Result save(CompanyEntity company) {
+        Result r = SystemSettingCacheCheckUtil.checkAddDataMode();
+        if (!r.isSuccess()) {
+            return r;
+        }
+        if(Objects.isNull(company.getId())) {
+            company.setDataSource(1);
+            LambdaQueryWrapper<CompanyEntity> lm = new LambdaQueryWrapper();
+            lm.eq(CompanyEntity::getDataSource,1);
+            lm.orderByDesc(CompanyEntity::getRouteCode);
+            lm.last("limit 1");
+            CompanyEntity top = companyService.getOne(lm);
+            if (Objects.nonNull(top)) {
+                Integer code = Integer.valueOf(top.getRouteCode());
+                company.setRouteCode(String.valueOf(code+1));
+            }else {
+                company.setRouteCode("150");
+            }
+        }
+        companyService.saveOrUpdate(company);
+        return Result.ok();
+    }
+
+    @ApiOperation("分页查询公司")
+    @PostMapping("pagelist")
+    public Result save(CompanyDTO param) {
+        IPage<CompanyEntity> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        IPage<CompanyEntity> companys = companyService.page(page);
+        return Result.ok(companys);
+    }
+
+    @ApiOperation("删除总公司信息")
+    @PostMapping("delete")
+    public Result delete(Long [] ids) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        Integer exist = subCompanyService.count(new LambdaQueryWrapper<SubCompanyEntity>().in(SubCompanyEntity::getCompanyId, Arrays.asList(ids)));
+        if (!Objects.equals(exist,Integer.valueOf(0))) {
+            return Result.error("公司下有关联子公司,无法删除!");
+        }
+        companyService.removeByIds(Arrays.asList(ids));
+        return Result.ok();
+    }
+}

+ 65 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/CompanyCustomSettingsController.java

@@ -0,0 +1,65 @@
+package com.storlead.system.controller;
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.system.constant.ReidsCacheConstant;
+import com.storlead.system.pojo.dto.CompanyCustomSettingsEntityDTO;
+import com.storlead.system.pojo.entity.CompanyCustomSettingsEntity;
+import com.storlead.system.pojo.entity.SystemPersonalizeSettingEntity;
+import com.storlead.system.service.CompanyCustomSettingsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Objects;
+
+/**
+ * <p>
+ * 公司配置信息 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2022-08-23
+ */
+@RestController
+@RequestMapping("/sys/custom/settings")
+@Api(tags = "设置: 企业个性化设置")
+public class CompanyCustomSettingsController {
+
+
+    @Resource
+    private CompanyCustomSettingsService customSettingsService;
+
+    @Resource
+    private RedisService redisService;
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "配置系统个性设置", notes = "配置系统个性设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result saveSetting(CompanyCustomSettingsEntityDTO entity) {
+        CompanyCustomSettingsEntity setting = customSettingsService.getOne(null);
+        if (Objects.nonNull(setting)) {
+            entity.setSettingsId(setting.getSettingsId());
+        }
+        redisService.setCacheObject(ReidsCacheConstant.SYS_CACHE_ORG_SYNC_MODE,setting.getSyncMode());
+        customSettingsService.saveOrUpdate((CompanyCustomSettingsEntity) entity);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/get")
+    @ApiOperation(value = "获取系统个性设置", notes = "获取系统个性设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = CompanyCustomSettingsEntityDTO.class)
+    })
+    public Result<?> getSetting() {
+        CompanyCustomSettingsEntity setting = customSettingsService.getOne(null);
+        return Result.ok(setting);
+    }
+}

+ 252 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/CorpWeChatController.java

@@ -0,0 +1,252 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.auth.jwt.JwtUtil;
+import com.storlead.framework.auth.vo.LoginUser;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.system.service.IMenuService;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.JobEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.IJobService;
+import com.storlead.user.service.IUserService;
+import com.storlead.wecom.pojo.vo.CorpWeChatUserVO;
+import com.storlead.wecom.pojo.vo.WechatToken;
+import com.storlead.wecom.properties.CorpWeChatConstants;
+import com.storlead.wecom.properties.CorpWeChatProperties;
+import com.storlead.wecom.service.CorpWeChatService;
+import com.storlead.wecom.service.WxDataSyncService;
+import com.storlead.wecom.util.CorpWechatUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.BeanUtils;
+import javax.annotation.Resource;
+import org.springframework.core.env.Environment;
+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 javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 企业微信管理
+ *
+ * @author blank
+ * @date 2021-3-8 下午 12:06
+ */
+@Api(tags = "系统: 企业微信管理")
+@RestController
+@RequestMapping("/xwork")
+@Log4j2
+public class CorpWeChatController {
+
+    @Resource
+    private CorpWeChatService corpWeChatService;
+
+    @Resource
+    private IUserService sysUserService;
+
+    @Resource
+    private IJobService jobService;
+
+    @Resource
+    private IDepartService departService;
+
+    @Resource
+    private RedisService redisService;
+
+    @Resource
+    private IUserRoleService userRoleService;
+
+    @Resource
+    private WxDataSyncService wxDataSyncService;
+
+    @Resource
+    private Environment environment;
+
+    @Resource
+    private IMenuService menuService;
+
+    @ApiOperation("获取AccessToken")
+    @GetMapping("/get_access_token")
+    public String getAccessToken() {
+        return corpWeChatService.getAccessToken().getToken();
+    }
+
+    @ApiOperation("获取JS Ticket")
+    @GetMapping("/get_js_api_ticket")
+    public String getJsApiTicket() {
+        return corpWeChatService.getJSApiTicket().getToken();
+    }
+
+    @ApiOperation("返回用于企业微信登陆所需要的参数")
+    @GetMapping("/get_x_work_login_param")
+    public Result<JSONObject> getCorpWechatLoginParam() {
+        Result<JSONObject> result = new Result<>();
+
+        result.setResult(corpWeChatService.getCorpWechatLoginParam());
+        return result;
+    }
+
+    @ApiOperation("根据code登录")
+    @GetMapping("/login")
+    public Result login(String code) {
+        String wxCode = corpWeChatService.login(code);
+        String corpId = CorpWeChatProperties.getCorpId();
+        String corpAddressSecret = CorpWeChatProperties.getCorpAddressSecret();
+        WechatToken t = CorpWechatUtil.getAccessToken(CorpWeChatConstants.GET_ACCESS_TOKEN_URL, corpId, corpAddressSecret);
+        CorpWeChatUserVO vo =  wxDataSyncService.getWxLoginInfo(t,code);
+
+        if (StrUtil.isBlank(wxCode)) {
+            return Result.error("微信登录错误,未获取到用户信息");
+        }
+        UserEntity userInfo = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getXworkUserId,wxCode));
+        if (Objects.isNull(userInfo)) {
+            return Result.error("微信登录错误,未获取到用户信息");
+        }
+
+//        Integer possessMenuCount = menuService.checkUserMenuAccess(userInfo.getId());
+//        if (possessMenuCount == 0) {
+//            return Result.error("暂未获取到菜单权限,请联系管理员分配权限!");
+//        }
+
+        com.alibaba.fastjson.JSONObject obj = new com.alibaba.fastjson.JSONObject();
+        LoginUser loginUser = new LoginUser();
+        BeanUtils.copyProperties(userInfo,loginUser);
+
+        if (Objects.nonNull(userInfo.getJobId())) {
+            JobEntity job = jobService.getById(userInfo.getJobId());
+            if (Objects.nonNull(job)) {
+                loginUser.setJobName(job.getName());
+            }
+        }
+
+        if (Objects.nonNull(userInfo.getDeptId())) {
+            DeptEntity dept = departService.getById(userInfo.getDeptId());
+            if (Objects.nonNull(dept)) {
+                loginUser.setDeptJobDes(dept.getDeptJobDes());
+            }
+        }
+
+        //用户登录信息
+        String jwtToken = JwtUtil.createJWT(com.alibaba.fastjson.JSONObject.toJSONString(loginUser),loginUser.getMobile());
+        String token = SecureUtil.md5(jwtToken);
+
+        String json = com.alibaba.fastjson.JSONObject.toJSONString(loginUser);
+        redisService.setCacheObject(token, json, 60 * 60 * 24L, TimeUnit.SECONDS);
+//        Map<String, String> apiMap = new HashMap();
+//        redisService.setCacheObject(RedisKeySaltConstant.API_CODE_REDIS + loginUser.getMobile(), apiMap);
+        obj.put("token", token);
+        return Result.ok(obj);
+    }
+
+
+//    https://open.weixin.qq.com/connect/oauth2/authorize?appid=ww5323bd8ab4394132&redirect_uri=https%3A%2F%2Fsales.test.storlead.com%2Findex&response_type=code&scope=snsapi_base&agentid=1000033&state=/fromQYwx#wechat_redirect
+
+    @ApiOperation("根据code登录")
+    @GetMapping("/code-login")
+    public Result apilogin(@RequestParam String code,
+                           HttpServletResponse response, HttpServletRequest request)  throws IOException {
+        log.error("code-login-------code="+code+"----state--"+"redirect_uri");
+        String redirect_uri ="";
+        log.error("requestURL----  "+request.getRequestURI());
+        String wxCode = corpWeChatService.login(code);
+        String corpId = CorpWeChatProperties.getCorpId();
+        String corpAddressSecret = CorpWeChatProperties.getCorpAddressSecret();
+        WechatToken t = CorpWechatUtil.getAccessToken(CorpWeChatConstants.GET_ACCESS_TOKEN_URL, corpId, corpAddressSecret);
+        CorpWeChatUserVO vo =  wxDataSyncService.getWxLoginInfo(t,code);
+
+        if (StrUtil.isBlank(wxCode)) {
+            return Result.error("微信登录错误,未获取到用户信息");
+        }
+        UserEntity userInfo = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getXworkUserId,wxCode));
+        if (Objects.isNull(userInfo)) {
+            return Result.error("微信登录错误,未获取到用户信息");
+        }
+
+//        Integer possessMenuCount = menuService.checkUserMenuAccess(userInfo.getId());
+//        if (possessMenuCount == 0) {
+//            return Result.error("暂未获取到菜单权限,请联系管理员分配权限!");
+//        }
+
+        com.alibaba.fastjson.JSONObject obj = new com.alibaba.fastjson.JSONObject();
+        LoginUser loginUser = new LoginUser();
+        BeanUtils.copyProperties(userInfo,loginUser);
+
+        if (Objects.nonNull(userInfo.getJobId())) {
+            JobEntity job = jobService.getById(userInfo.getJobId());
+            if (Objects.nonNull(job)) {
+                loginUser.setJobName(job.getName());
+            }
+        }
+
+        if (Objects.nonNull(userInfo.getDeptId())) {
+            DeptEntity dept = departService.getById(userInfo.getDeptId());
+            if (Objects.nonNull(dept)) {
+                loginUser.setDeptJobDes(dept.getDeptJobDes());
+            }
+        }
+
+        //用户登录信息
+        String jwtToken = JwtUtil.createJWT(com.alibaba.fastjson.JSONObject.toJSONString(loginUser),loginUser.getMobile());
+        String token = SecureUtil.md5(jwtToken);
+
+        String json = com.alibaba.fastjson.JSONObject.toJSONString(loginUser);
+        redisService.setCacheObject(token, json, 60 * 60 * 24L, TimeUnit.SECONDS);
+//        Map<String, String> apiMap = new HashMap();
+//        redisService.setCacheObject(RedisKeySaltConstant.API_CODE_REDIS + loginUser.getMobile(), apiMap);
+        obj.put("token", token);
+
+        String mobileUrl = "";
+        if (isMobile(request)) {
+            mobileUrl ="/H5";
+        }
+
+        String active = "";
+        if(environment.equals("prod")) {
+            active = "";
+        } else if (environment.equals("test")){
+            active = "test.";
+        }
+
+        String redirectUrl = "https://sales."+active+"storlead.com"+mobileUrl+"/index?token=" + token;
+        log.error("===redirectUrl-----"+redirectUrl);
+        response.sendRedirect(redirectUrl);
+
+        return Result.ok(obj);
+    }
+
+    public boolean isMobile(HttpServletRequest request) {
+        String userAgent = request.getHeader("User-Agent");
+        if (userAgent == null) {
+            return false;
+        }
+        // 常见移动设备关键字
+        String[] mobileKeywords = {
+                "Mobile", "Android", "iPhone", "iPad", "iPod",
+                "BlackBerry", "Windows Phone", "Opera Mini", "IEMobile"
+        };
+
+        userAgent = userAgent.toLowerCase();
+        for (String keyword : mobileKeywords) {
+            if (userAgent.contains(keyword.toLowerCase())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 657 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/DeptApiController.java

@@ -0,0 +1,657 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.dto.CompanyDTO;
+import com.storlead.system.util.SystemSettingCacheCheckUtil;
+import com.storlead.user.pojo.dto.DepartmentParam;
+import com.storlead.user.pojo.entity.CompanyEntity;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.SubCompanyEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.pojo.vo.DepartmentTree;
+import com.storlead.user.service.ICompanyService;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.ISubCompanyService;
+import com.storlead.user.service.IUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import javax.annotation.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-06-29 17:46
+ */
+@Api(tags = "系统: 公司部门数据维护")
+@RestController
+@RequestMapping("/sys/dept")
+@Log4j2
+public class DeptApiController {
+
+    @Resource
+    private IDepartService departService;
+
+    @Resource
+    private IUserService userService;
+
+    @Resource
+    private ICompanyService companyService;
+
+    @Resource
+    private ISubCompanyService subCompanyService;
+//    @ApiOperation("获取部门组织列")
+//    @PostMapping("listPage")
+//    public Result listPage(@RequestBody DepartmentParam p
+//    aram) {
+//        IPage page = new Page(param.getPageIndex(),param.getPageSize());
+//        LambdaQueryWrapper<Dept> query = new LambdaQueryWrapper();
+//        if (param.getOrgTypes() != null) {
+//            query.in(Department::getOrgType,param.getOrgTypes());
+//        }
+//        if (param.getParentId() != null) {
+//            Department depart = departmentService.getById(param.getParentId());
+//            if (Objects.nonNull(depart)) {
+//                query.likeLeft(Department::getOrgCode,depart.getOrgCode());
+//            }
+//        }
+//        IPage<Department> depts = departmentService.page(page,query);
+//        return Result.success(depts);
+//    }
+    @ApiOperation("通过公司id返回部门List")
+    @PostMapping("getListBySub")
+    public Result getListBySub(Long subCompanyId) {
+        LambdaQueryWrapper<DeptEntity> deptQquery = new LambdaQueryWrapper<>();
+        deptQquery.eq(DeptEntity::getIsDelete,0);
+        deptQquery.eq(DeptEntity::getSubCompanyId,subCompanyId);
+        List<DeptEntity> deptList = departService.list(deptQquery);
+        return Result.ok(deptList);
+    }
+
+    @ApiOperation("获取组织树")
+    @PostMapping("scopelistTree")
+    public Result scopelistTree(@RequestBody DepartmentParam param) {
+        List<CompanyEntity> companys = new ArrayList<>();
+        List<SubCompanyEntity> subCompanies = new ArrayList<>();
+        List<DeptEntity> depts = new ArrayList<>();
+        if (Objects.isNull(param.getOrgTypes())) {
+            return Result.error("参数错误");
+        }
+
+        LambdaQueryWrapper<UserEntity> userQquery = new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getIsDelete,0);
+        userQquery.eq(Objects.nonNull(param.getEnabled()),UserEntity::getEnabled,param.getEnabled());
+
+        List<UserEntity> users = userService.listWithScope(userQquery);
+        Set<Long> deptIds = new HashSet<>();
+        Set<Long> subCompanyIds = new HashSet<>();
+        Set<Long> companyIds = new HashSet<>();
+        if (!CollectionUtils.isEmpty(users)) {
+
+             deptIds = users.stream().map(UserEntity::getDeptId).collect(Collectors.toSet());
+             subCompanyIds = users.stream().map(UserEntity::getSubCompanyId).collect(Collectors.toSet());
+             companyIds = users.stream().map(UserEntity::getCompanyId).collect(Collectors.toSet());
+        }
+
+        Map<Long,String> usMap = userService.list().stream().collect(Collectors.toMap(UserEntity::getId,UserEntity::getRealName));
+        List<DepartmentTree> pidls = Collections.EMPTY_LIST;
+        List<DepartmentTree> trees = new ArrayList<>();
+        //类型:10 集团,20:公司(分公司),30:部门,40:小组,50:雇员
+        if (param.getOrgTypes().contains(Integer.valueOf(10)) && !CollectionUtils.isEmpty(companyIds)) {
+            LambdaQueryWrapper<CompanyEntity> cuwp = new LambdaQueryWrapper<CompanyEntity>();
+            cuwp.in(CompanyEntity::getId,companyIds);
+            companys = companyService.list(cuwp);
+            List<DepartmentTree> ls =  DepartmentTree.companyListToDepartmentTreeVoList(companys);
+            if (!CollectionUtils.isEmpty(ls)) {
+                pidls = ls;
+                trees.addAll(ls);
+            }
+        }
+        if (param.getOrgTypes().contains(Integer.valueOf(20)) && !CollectionUtils.isEmpty(subCompanyIds)) {
+            LambdaQueryWrapper<SubCompanyEntity> subCompanyQquery = new LambdaQueryWrapper<SubCompanyEntity>().eq(SubCompanyEntity::getIsDelete,0);
+            subCompanyQquery.eq(Objects.nonNull(param.getEnabled()),SubCompanyEntity::getEnabled,param.getEnabled());
+            if (!CollectionUtils.isEmpty(subCompanyIds)) {
+                subCompanyQquery.in(SubCompanyEntity::getId,subCompanyIds);
+            }
+            subCompanies = subCompanyService.list(subCompanyQquery);
+            if (CollectionUtils.isEmpty(subCompanies)) {
+                subCompanies.forEach(sc -> {
+                    String realName = usMap.get(sc.getLeaderId());
+                    sc.setLeaderName(StrUtil.isEmpty(realName) ? "" : realName);
+                });
+            }
+            List<DepartmentTree> ls =  DepartmentTree.subCompanyListToDepartmentTreeVoList(subCompanies);
+            if (!CollectionUtils.isEmpty(ls)) {
+                if (CollectionUtils.isEmpty(pidls)) {
+                    pidls = ls;
+                }
+                trees.addAll(ls);
+            }
+        }
+
+        if (param.getOrgTypes().contains(Integer.valueOf(30)) && !CollectionUtils.isEmpty(deptIds)) {
+            LambdaQueryWrapper<DeptEntity> dmap1= new LambdaQueryWrapper<>();
+            dmap1.eq(Objects.nonNull(param.getEnabled()),DeptEntity::getEnabled,param.getEnabled());
+
+            List<DeptEntity> allDepts = departService.list(dmap1);
+            if (!CollectionUtils.isEmpty(allDepts)) {
+                Map<Long, DeptEntity> allDeptMaps =  allDepts.stream().collect(Collectors.toMap(DeptEntity::getId, a -> a,(existing, replacement) -> existing));
+                Set<Long> allParentIds = getParentDeptsForMultiple(deptIds, allDeptMaps);
+                if (!CollectionUtils.isEmpty(allParentIds)) {
+                    deptIds.addAll(allParentIds);
+                }
+            }
+            LambdaQueryWrapper<DeptEntity> dmap= new LambdaQueryWrapper<>();
+            dmap.in(DeptEntity::getId,deptIds);
+            dmap1.eq(Objects.nonNull(param.getEnabled()),DeptEntity::getEnabled,param.getEnabled());
+
+            depts = departService.list(dmap);
+            if (CollectionUtils.isEmpty(depts)) {
+                depts.forEach(d -> {
+                    String realName = usMap.get(d.getManagerId());
+                    d.setLeaderName(StrUtil.isEmpty(realName) ? "" : realName);
+                });
+            }
+            List<DepartmentTree> ls =  DepartmentTree.deptListToDepartmentTreeVoList(depts);
+            if (!CollectionUtils.isEmpty(ls)) {
+                if (CollectionUtils.isEmpty(pidls)) {
+                    pidls = ls;
+                }
+                trees.addAll(ls);
+            }
+        }
+
+        Map<Long, List<UserEntity>> groupByGrade = new HashMap<>();
+        if (!CollectionUtils.isEmpty(param.getOrgTypes()) && param.getOrgTypes().contains(99) && !CollectionUtils.isEmpty(depts)) {
+            if (!CollectionUtils.isEmpty(users)) {
+                groupByGrade = users.stream().collect(Collectors.groupingBy(UserEntity::getDeptId));
+            }
+        }
+        List<DepartmentTree> newTrees = new ArrayList<>();
+        for (DepartmentTree d : trees) {
+            if (Integer.valueOf(30).equals(d.getType())) {
+                d.setChildren(getEmployeeTree(groupByGrade.get(d.getId()),param.getCheckNodes()));
+            }
+            if (Objects.nonNull(param.getCheckNodes()) && param.getCheckNodes().contains(d.getType())) {
+                d.setDisabled(false);
+                d.setDisableCheckbox(false);
+            }
+            newTrees.add(d);
+        }
+        List<DepartmentTree> result =  createTreels(newTrees,pidls);;
+        return Result.ok(result);
+    }
+
+
+    @ApiOperation("获取组织树")
+    @PostMapping("orgTree")
+    public Result orgTree(@RequestBody DepartmentParam param) {
+        param.setEnabled(null);
+        return listTree(param);
+    }
+
+    @ApiOperation("获取组织树")
+    @PostMapping("listTree")
+    public Result listTree(@RequestBody DepartmentParam param) {
+        if (StrUtil.isNotBlank(param.getScopeMenuId()) && !Integer.valueOf(1).equals(param.getReviewTag())) {
+            return scopelistTree(param);
+        }
+        List<CompanyEntity> companys = Collections.EMPTY_LIST;
+        List<SubCompanyEntity> subCompanies = Collections.EMPTY_LIST;
+        List<DeptEntity> depts = Collections.EMPTY_LIST;
+        if (Objects.isNull(param.getOrgTypes())) {
+            return Result.error("参数错误");
+        }
+        Map<Long,String> usMap = userService.list().stream().collect(Collectors.toMap(UserEntity::getId,UserEntity::getRealName));
+        List<DepartmentTree> pidls = Collections.EMPTY_LIST;
+        List<DepartmentTree> trees = new ArrayList<>();
+        //类型:10 集团,20:公司(分公司),30:部门,40:小组,50:雇员
+        if (param.getOrgTypes().contains(Integer.valueOf(10))) {
+            companys = companyService.list();
+            List<DepartmentTree> ls =  DepartmentTree.companyListToDepartmentTreeVoList(companys);
+            if (!CollectionUtils.isEmpty(ls)) {
+                pidls = ls;
+                trees.addAll(ls);
+            }
+        }
+        if (param.getOrgTypes().contains(Integer.valueOf(20))) {
+            LambdaQueryWrapper<SubCompanyEntity> subCompanyQquery = new LambdaQueryWrapper<SubCompanyEntity>().eq(SubCompanyEntity::getIsDelete,0);
+            subCompanyQquery.eq(Objects.nonNull(param.getEnabled()),SubCompanyEntity::getEnabled,param.getEnabled());
+            if (Objects.nonNull(param.getSubCompanyId())) {
+                subCompanyQquery.eq(SubCompanyEntity::getId,param.getSubCompanyId());
+            }
+            subCompanies = subCompanyService.list(subCompanyQquery);
+            if (CollectionUtils.isEmpty(subCompanies)) {
+                subCompanies.forEach(sc -> {
+                    String realName = usMap.get(sc.getLeaderId());
+                    sc.setLeaderName(StrUtil.isEmpty(realName) ? "" : realName);
+                });
+            }
+            List<DepartmentTree> ls =  DepartmentTree.subCompanyListToDepartmentTreeVoList(subCompanies);
+            if (!CollectionUtils.isEmpty(ls)) {
+                if (CollectionUtils.isEmpty(pidls)) {
+                    pidls = ls;
+                }
+                trees.addAll(ls);
+            }
+        }
+
+        if (param.getOrgTypes().contains(Integer.valueOf(30))) {
+            LambdaQueryWrapper<DeptEntity> deptQquery = new LambdaQueryWrapper<DeptEntity>().eq(DeptEntity::getIsDelete,0);
+            deptQquery.eq(Objects.nonNull(param.getEnabled()),DeptEntity::getEnabled,param.getEnabled());
+            if (Objects.nonNull(param.getDeptId())) {
+                DeptEntity d = departService.getById(param.getDeptId());
+                if (Objects.nonNull(d)) {
+                    deptQquery.apply(" LOCATE(route_code,'"+d.getRouteCode()+"') = 1 ");
+                }
+            }
+            depts = departService.list(deptQquery);
+            if (CollectionUtils.isEmpty(depts)) {
+                depts.forEach(d -> {
+                    String realName = usMap.get(d.getManagerId());
+                    d.setLeaderName(StrUtil.isEmpty(realName) ? "" : realName);
+                });
+            }
+            List<DepartmentTree> ls =  DepartmentTree.deptListToDepartmentTreeVoList(depts);
+            if (!CollectionUtils.isEmpty(ls)) {
+                if (CollectionUtils.isEmpty(pidls)) {
+                    pidls = ls;
+                }
+                trees.addAll(ls);
+            }
+        }
+
+        Map<Long, List<UserEntity>> groupByGrade = new HashMap<>();
+        if (!CollectionUtils.isEmpty(param.getOrgTypes()) && param.getOrgTypes().contains(99) && !CollectionUtils.isEmpty(depts)) {
+            LambdaQueryWrapper<UserEntity> userQquery = new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getIsDelete,0);
+            userQquery.eq(Objects.nonNull(param.getEnabled()),UserEntity::getEnabled,param.getEnabled());
+
+            List<UserEntity> users = userService.list(userQquery);
+            if (!CollectionUtils.isEmpty(users)) {
+                groupByGrade = users.stream().collect(Collectors.groupingBy(UserEntity::getDeptId));
+            }
+        }
+        List<DepartmentTree> newTrees = new ArrayList<>();
+        for (DepartmentTree d : trees) {
+            if (Integer.valueOf(30).equals(d.getType())) {
+                d.setChildren(getEmployeeTree(groupByGrade.get(d.getId()),param.getCheckNodes()));
+            }
+            if (Objects.nonNull(param.getCheckNodes()) && param.getCheckNodes().contains(d.getType())) {
+                d.setDisabled(false);
+                d.setDisableCheckbox(false);
+            }
+            newTrees.add(d);
+        }
+        List<DepartmentTree> result =  createTreels(newTrees,pidls);;
+        return Result.ok(result);
+    }
+
+    public Set<Long> getParentDeptsForMultiple(Set<Long> deptIds, Map<Long, DeptEntity> allDepts) {
+        Set<Long> allParentIds = new HashSet<>();
+        Set<Long> currentLevel = new HashSet<>(deptIds);
+
+        while (!currentLevel.isEmpty()) {
+            Set<Long> nextLevel = new HashSet<>();
+            for (Long deptId : currentLevel) {
+                DeptEntity dept = allDepts.get(deptId);
+                if (dept != null && dept.getPid() != null && dept.getPid() != 0) {
+                    if (!allParentIds.contains(dept.getPid())) {
+                        allParentIds.add(dept.getPid());
+                        nextLevel.add(dept.getPid());
+                    }
+                }
+            }
+            currentLevel = nextLevel;
+        }
+        return allParentIds;
+    }
+
+    private List<DepartmentTree> createTreeVo(List<DepartmentTree> treels, DepartmentTree treeVo,Long topId) {
+        List<DepartmentTree> trees = new ArrayList<>();
+        for (DepartmentTree tree : treels) {
+
+            /**
+             * 处理公司,部门,用户id冲突无法满足组装通用树结构得问题 begin
+             */
+            if (Objects.nonNull(treeVo)
+                    && (Integer.valueOf(10).equals(treeVo.getType())
+                    && (!Integer.valueOf(20).equals(tree.getType()) && !Integer.valueOf(10).equals(tree.getType())))) {
+                continue;
+            }
+            if (Objects.nonNull(treeVo)
+                    && (Integer.valueOf(20).equals(treeVo.getType())
+                    && !Integer.valueOf(30).equals(tree.getType()))) {
+                continue;
+            }
+            if (Objects.nonNull(treeVo)
+                    && (Integer.valueOf(30).equals(treeVo.getType())
+                    && (!Integer.valueOf(30).equals(tree.getType())))) {
+                continue;
+            }
+
+            Long pid = Objects.nonNull(topId) ? treeVo.getId() : Long.valueOf(0);
+            Long parentId = tree.getParentId();
+
+            if (Objects.nonNull(treeVo) && Integer.valueOf(20).equals(treeVo.getType()) && Integer.valueOf(30).equals(tree.getType()) ) {
+                DeptEntity dept = (DeptEntity)tree.getArg();
+                if (!Long.valueOf(0).equals(dept.getPid())) {
+                    continue;
+                }
+            }
+            else if (Objects.nonNull(treeVo) && Integer.valueOf(30).equals(treeVo.getType()) && Integer.valueOf(30).equals(tree.getType()) ) {
+                DeptEntity deptp = (DeptEntity)treeVo.getArg();
+                DeptEntity dept = (DeptEntity)tree.getArg();
+
+                if (Long.valueOf(0).equals(dept.getPid())) {
+                    continue;
+                }
+//                if (Long.valueOf(0).equals(deptp.getPid()) && Long.valueOf(0).equals(dept.getPid())) {
+//                    continue;
+//                }
+            }
+            if (pid.equals(parentId)) {
+                trees.add(tree);
+                List<DepartmentTree> childs =  createTreeVo(treels,tree,tree.getParentId());
+                if (!CollectionUtils.isEmpty(childs)) {
+                    if (tree.getChildren() != null) {
+                        childs.addAll(tree.getChildren());
+                    }
+                    tree.setChildren(childs);
+                }
+            }
+        }
+        return trees;
+    }
+
+    private List<DepartmentTree> createTreels(List<DepartmentTree> treels, List<DepartmentTree>  pTrees) {
+        List<DepartmentTree> trees = new ArrayList<>();
+        trees.addAll(createTreeVo(treels,null,null));
+        return trees;
+    }
+
+
+    public List<DepartmentTree> getEmployeeTree(List<UserEntity> users, List<Integer> checkNodes){
+        if (CollectionUtils.isEmpty(users)) {
+            return  null;
+        }
+        List<DepartmentTree> trees = new ArrayList<>();
+        for (UserEntity d : users) {
+            DepartmentTree departmentTree = new DepartmentTree();
+            departmentTree.setId(d.getId());
+            departmentTree.setParentId(d.getDeptId());
+            departmentTree.setTitle(d.getRealName());
+            departmentTree.setArg(d);
+            departmentTree.setPhoto(d.getAvatar());
+            departmentTree.setAvatar(d.getAvatar());
+            departmentTree.setType(99);
+            departmentTree.setExpand(false);
+            if (Objects.nonNull(checkNodes) && checkNodes.contains(Integer.valueOf(99))) {
+                departmentTree.setDisabled(false);
+                departmentTree.setDisableCheckbox(false);
+            }
+            trees.add(departmentTree);
+        }
+        return trees;
+    }
+
+
+    @ApiOperation("新增部门")
+    @PostMapping("save")
+    public Result save(DeptEntity dept) {
+        Result r = SystemSettingCacheCheckUtil.checkAddDataMode();
+        if (!r.isSuccess()) {
+            return r;
+        }
+        if (Objects.isNull(dept.getPid())) {
+            dept.setPid(Long.valueOf(0));
+        }
+        if(Objects.isNull(dept.getId())) {
+            dept.setDataSource(1);
+            String topCode = "";
+            LambdaQueryWrapper<DeptEntity> subQWrapper = new LambdaQueryWrapper();
+            if (Long.valueOf(0).equals(dept.getPid())) {
+                LambdaQueryWrapper<SubCompanyEntity> lm = new LambdaQueryWrapper();
+                lm.eq(SubCompanyEntity::getId,dept.getSubCompanyId());
+                lm.last("limit 1");
+                SubCompanyEntity subCompany = subCompanyService.getOne(lm);
+                if (Objects.isNull(subCompany)) {
+                    return Result.error("部门需要挂载在在公司下面,请绑定部门公司");
+                }
+                dept.setCompanyId(subCompany.getCompanyId());
+                subQWrapper.eq(DeptEntity::getSubCompanyId,subCompany.getId());
+                topCode = subCompany.getRouteCode();
+            } else {
+                LambdaQueryWrapper<DeptEntity> lm = new LambdaQueryWrapper();
+                lm.eq(DeptEntity::getId,dept.getPid());
+                lm.last("limit 1");
+                DeptEntity dept1 = departService.getOne(lm);
+                if (Objects.isNull(dept1)) {
+                    return Result.error("参数错误,未查到上级部门");
+                }
+                dept.setSubCompanyId(dept1.getSubCompanyId());
+                dept.setCompanyId(dept1.getCompanyId());
+                subQWrapper.eq(DeptEntity::getId,dept1.getId());
+                topCode = dept1.getRouteCode();
+            }
+            subQWrapper.orderByDesc(DeptEntity::getRouteCode);
+            subQWrapper.last("limit 1");
+            DeptEntity dept1 = departService.getOne(subQWrapper);
+            if (Objects.isNull(dept1)) {
+                dept.setRouteCode(topCode+",100");
+            }else {
+                String [] routeCodes = dept1.getRouteCode().split(",");
+                String lastCode = routeCodes[routeCodes.length-1];
+                lastCode = String.valueOf(Integer.valueOf(lastCode)+1);
+                routeCodes[routeCodes.length-1] = lastCode;
+                String routeCode = StringUtils.join(routeCodes,",");
+                dept.setRouteCode(routeCode);
+            }
+        }
+
+        departService.saveOrUpdate(dept);
+        return Result.ok();
+    }
+
+    @ApiOperation("分页查询部门")
+    @PostMapping("pagelist")
+    public Result save(CompanyDTO param) {
+        IPage<DeptEntity> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        IPage<DeptEntity> depts = departService.page(page);
+        return Result.ok(depts);
+    }
+
+    @ApiOperation("删除部门数据")
+    @PostMapping("delete")
+    public Result delete(Long [] ids) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        Integer exist = userService.count(new LambdaQueryWrapper<UserEntity>().in(UserEntity::getDeptId, Arrays.asList(ids)));
+        if (!Objects.equals(exist,Integer.valueOf(0))) {
+            return Result.error("部门下有关联的员工,无法删除!");
+        }
+        departService.removeByIds(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+    @ApiOperation("设置部门领导")
+    @PostMapping("setDepartManager")
+    public Result delete(Long deptId ,Long managerId) {
+        if (Objects.isNull(deptId)) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<DeptEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.eq(DeptEntity::getId,deptId);
+        UserEntity user = userService.getById(managerId);
+        if (Objects.nonNull(user)) {
+            updateWrapper.set(DeptEntity::getManagerId,managerId);
+            updateWrapper.set(DeptEntity::getLeaderName,user.getRealName());
+        } else {
+            updateWrapper.set(DeptEntity::getManagerId,null);
+            updateWrapper.set(DeptEntity::getLeaderName,"");
+        }
+        DeptEntity deptMar = departService.getById(deptId);
+        Boolean b =  departService.update(updateWrapper);
+        if (b) {
+            if (Objects.nonNull(deptMar)) {
+                if (!managerId.equals(deptMar.getManagerId())) {
+                    UserEntity oldUser = userService.getById(deptMar.getManagerId());
+                    if (Objects.nonNull(oldUser) && !Integer.valueOf(1).equals(oldUser.getIsLeader()))  {
+                        LambdaQueryWrapper userDeptLq =  new LambdaQueryWrapper<DeptEntity>().eq(DeptEntity::getManagerId,deptMar.getManagerId()).ne(DeptEntity::getId,deptId);
+                        Integer c = departService.count(userDeptLq);
+                        LambdaUpdateWrapper<UserEntity> rmLeaderUpdate = new LambdaUpdateWrapper();
+                        if (c > 0) {
+                            rmLeaderUpdate.set(UserEntity::getIsLeader,Integer.valueOf(2));
+                        } else {
+                            rmLeaderUpdate.set(UserEntity::getIsLeader,Integer.valueOf(0));
+                        }
+                        rmLeaderUpdate.eq(UserEntity::getId,deptMar.getManagerId());
+                        userService.update(rmLeaderUpdate);
+                    }
+                }
+            }
+            if (Integer.valueOf(1).equals(user.getIsLeader()) || Integer.valueOf(2).equals(user.getIsLeader())) {
+                return Result.ok();
+            }
+            LambdaUpdateWrapper<UserEntity> userUpdate = new LambdaUpdateWrapper();
+            userUpdate.set(UserEntity::getIsLeader,Integer.valueOf(2));
+            userUpdate.eq(UserEntity::getId,managerId);
+            userService.update(userUpdate);
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation("设置部门分管领导")
+    @PostMapping("setDepartSubManager")
+    public Result delete(Long deptId,String subManagerIds) {
+        if (Objects.isNull(deptId)) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<DeptEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.eq(DeptEntity::getId,deptId);
+        updateWrapper.set(DeptEntity::getSubManagerIds,subManagerIds);
+        departService.update(updateWrapper);
+        return Result.ok();
+    }
+
+    @ApiOperation("矫正路由")
+    @PostMapping("correctDeptRoute")
+    public Result correctDeptRoute() {
+        List<DeptEntity> depts = departService.list();
+        List<DeptEntity> topDepts = depts.stream().filter(e -> Long.valueOf(0).equals(e.getPid())).collect(Collectors.toList());
+        int code = 100;
+        List<DeptEntity> deptEntities = new ArrayList<>();
+        for (DeptEntity d:topDepts) {
+            SubCompanyEntity subCompany = subCompanyService.getById(d.getSubCompanyId());
+            String topRoute = subCompany.getRouteCode();
+            String topRoute1 = topRoute+","+String.valueOf(code);
+            d.setRouteCode(topRoute1);
+            deptEntities.add(d);
+            List<DeptEntity> ns = markRoute(d,depts);
+            if (!CollectionUtils.isEmpty(ns)) {
+                deptEntities.addAll(ns);
+            }
+            code ++;
+        }
+        departService.updateBatchById(deptEntities);
+        Map<Long, String> map3 = deptEntities.stream().collect(Collectors.toMap(DeptEntity::getId, DeptEntity::getRouteCode, (u1, u2) -> u1));
+        List<UserEntity> users = userService.list();
+        for (UserEntity u:users) {
+            u.setOrgRouteCode(map3.get(u.getDeptId()));
+        }
+        userService.updateBatchById(users);
+        return Result.ok();
+    }
+
+    @ApiOperation("启用禁用部门")
+    @PostMapping("enable")
+    public Result setEnable(Long id,Boolean enable,Integer type) {
+        if (id == null) {
+            return Result.error("参数错误");
+        }
+        if (type == null) {
+            return Result.error("参数错误");
+        }
+        if (type == 10) {
+            return Result.error("无法禁用总公司");
+        }
+        if (type == 20) {
+
+            LambdaUpdateWrapper<SubCompanyEntity> update = new LambdaUpdateWrapper();
+            update.eq(SubCompanyEntity::getId,id);
+            update.set(SubCompanyEntity::getEnabled,enable);
+            Boolean re = subCompanyService.update(update);
+            if (re) {
+                // 下级部门同步禁用启用,用户同步
+                LambdaUpdateWrapper<DeptEntity> update1 = new LambdaUpdateWrapper();
+                update1.eq(DeptEntity::getSubCompanyId,id);
+                update1.set(DeptEntity::getEnabled,enable);
+                departService.update(update1);
+                return Result.ok();
+            } else {
+                return Result.error("操作失败");
+            }
+
+        } else if (type == 30) {
+            DeptEntity dept = departService.getById(id);
+            if (Objects.isNull(dept)) {
+                return Result.error("参数错误");
+            }
+            LambdaQueryWrapper<DeptEntity> query = new LambdaQueryWrapper();
+            query.likeRight(DeptEntity::getRouteCode,dept.getRouteCode());
+            query.eq(DeptEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+            List<DeptEntity> deptls = departService.list(query);
+            if (CollectionUtils.isEmpty(deptls)) {
+                return Result.error("参数错误");
+            }
+            List<Long>  deptIds = deptls.stream().map(DeptEntity::getId).collect(Collectors.toList());
+            // 下级部门同步禁用启用,用户同步
+            LambdaUpdateWrapper<DeptEntity> update = new LambdaUpdateWrapper();
+            update.in(DeptEntity::getId,deptIds);
+            update.set(DeptEntity::getEnabled,enable);
+            Boolean re =  departService.update(update);
+            if (re) {
+                return Result.ok();
+            } else {
+                return Result.error("操作失败");
+            }
+        } else {
+            return Result.error("操作失败,参数错误");
+        }
+    }
+
+    public List<DeptEntity> markRoute(DeptEntity d,List<DeptEntity> depts){
+        List<DeptEntity> deptEntities = new ArrayList<>();
+        int code = 100;
+        List<DeptEntity> topDepts = depts.stream().filter(e -> e.getPid().equals(d.getId()) && e.getSubCompanyId().equals(d.getSubCompanyId())).collect(Collectors.toList());
+        for (DeptEntity d1:topDepts) {
+            String topRoute = d.getRouteCode();
+            String topRoute1 = topRoute+","+String.valueOf(code);
+            d1.setRouteCode(topRoute1);
+            deptEntities.add(d1);
+            List<DeptEntity> ns = markRoute(d1,depts);
+            if (!CollectionUtils.isEmpty(ns)) {
+                deptEntities.addAll(ns);
+            }
+            code++;
+        }
+        return deptEntities;
+    }
+}

+ 169 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/DeptController.java

@@ -0,0 +1,169 @@
+package com.storlead.system.controller;
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.user.model.DepartIdModel;
+import com.storlead.user.model.DeptTreeModel;
+import com.storlead.user.service.IDepartService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/lingcun/dept")
+@Slf4j
+@Api(tags="5.公共接口")
+public class DeptController {
+
+	@Resource
+	private IDepartService departService;
+
+	/**
+	 * 查询数据 查出所有部门,并以树结构数据格式响应给前端
+	 *
+	 * @return
+	 */
+	@RequestMapping(value = "/queryTreeList", method = RequestMethod.GET)
+	@ApiOperation(value = "公共接口-部门-tree数据", notes = "公共接口-部门-tree数据")
+	@ApiResponses({
+			@ApiResponse(code = 200, message = "", response = DeptTreeModel.class)
+	})
+	public Result<List<DeptTreeModel>> queryTreeList() {
+		Result<List<DeptTreeModel>> result = new Result<>();
+		try {
+			// 从内存中读取
+//			List<SysDepartTreeModel> list =FindsDepartsChildrenUtil.getSysDepartTreeList();
+//			if (CollectionUtils.isEmpty(list)) {
+//				list = sysDepartService.queryTreeList();
+//			}
+			List<DeptTreeModel> list = departService.queryTreeList();
+			result.setResult(list);
+			result.setSuccess(true);
+		} catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+		return result;
+	}
+
+	/**
+	 * 查询数据 查出分公司下所有部门
+	 *
+	 * @return
+	 */
+	@RequestMapping(value = "/queryDeptBySub", method = RequestMethod.GET)
+	@ApiOperation(value = "公共接口-公司部门-根据分公司ID查询部门tree数据", notes = "根据分公司ID查询部门tree数据")
+	@ApiResponses({
+			@ApiResponse(code = 200, message = "", response = DeptTreeModel.class)
+	})
+	public Result<List<DeptTreeModel>> queryDeptBySub(Integer subCompanyId) {
+		Result<List<DeptTreeModel>> result = new Result<>();
+		try {
+			List<DeptTreeModel> list = departService.queryDeptBySubList(subCompanyId);
+			result.setResult(list);
+			result.setSuccess(true);
+		} catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+		return result;
+	}
+
+	/**
+	 * 查询数据 查出所有部门,并以树结构数据格式响应给前端
+	 *
+	 * @return
+	 */
+	@RequestMapping(value = "/queryTreeListWithSubCompanyId", method = RequestMethod.GET)
+	@ApiOperation(value = "公共接口-公司部门-tree数据", notes = "根据分公司ID查询部门tree数据")
+	@ApiResponses({
+			@ApiResponse(code = 200, message = "", response = DeptTreeModel.class)
+	})
+	public Result<List<DeptTreeModel>> queryTreeListWithSubCompanyId(Integer subCompanyId) {
+		Result<List<DeptTreeModel>> result = new Result<>();
+		try {
+			// 从内存中读取
+//			List<SysDepartTreeModel> list =FindsDepartsChildrenUtil.getSysDepartTreeList();
+//			if (CollectionUtils.isEmpty(list)) {
+//				list = sysDepartService.queryTreeList();
+//			}
+			List<DeptTreeModel> list = departService.queryTreeListWithSubCompanyId(subCompanyId);
+			result.setResult(list);
+			result.setSuccess(true);
+		} catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+		return result;
+	}
+
+	/**
+	 * 查询数据 添加或编辑页面对该方法发起请求,以树结构形式加载所有部门的名称,方便用户的操作
+	 *
+	 * @return
+	 */
+	@RequestMapping(value = "/queryIdTree", method = RequestMethod.GET)
+	public Result<List<DepartIdModel>> queryIdTree() {
+//		Result<List<DepartIdModel>> result = new Result<List<DepartIdModel>>();
+//		List<DepartIdModel> idList;
+//		try {
+//			idList = FindsDepartsChildrenUtil.wrapDepartIdModel();
+//			if (idList != null && idList.size() > 0) {
+//				result.setResult(idList);
+//				result.setSuccess(true);
+//			} else {
+//				sysDepartService.queryTreeList();
+//				idList = FindsDepartsChildrenUtil.wrapDepartIdModel();
+//				result.setResult(idList);
+//				result.setSuccess(true);
+//			}
+//			return result;
+//		} catch (Exception e) {
+//			log.error(e.getMessage(),e);
+//			result.setSuccess(false);
+//			return result;
+//		}
+		Result<List<DepartIdModel>> result = new Result<>();
+		try {
+			List<DepartIdModel> list = departService.queryDepartIdTreeList();
+			result.setResult(list);
+			result.setSuccess(true);
+		} catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+		return result;
+	}
+
+	/**
+	 * <p>
+	 * 部门搜索功能方法,根据关键字模糊搜索相关部门
+	 * </p>
+	 *
+	 * @param keyWord
+	 * @return
+	 */
+	@RequestMapping(value = "/searchBy", method = RequestMethod.GET)
+	public Result<List<DeptTreeModel>> searchBy(@RequestParam(name = "keyWord") String keyWord) {
+		Result<List<DeptTreeModel>> result = new Result<List<DeptTreeModel>>();
+		try {
+			List<DeptTreeModel> treeList = this.departService.searhBy(keyWord);
+			if (treeList.size() == 0 || treeList == null) {
+				throw new Exception();
+			}
+			result.setSuccess(true);
+			result.setResult(treeList);
+			return result;
+		} catch (Exception e) {
+			e.fillInStackTrace();
+			result.setSuccess(false);
+			result.setMessage("查询失败或没有您想要的任何数据!");
+			return result;
+		}
+	}
+
+}

+ 19 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/DeptRoleController.java

@@ -0,0 +1,19 @@
+package com.storlead.system.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 部门角色权限 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-02
+ */
+@RestController
+@RequestMapping("/sys/dept/role")
+public class DeptRoleController {
+
+}

+ 359 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/FileResourceController.java

@@ -0,0 +1,359 @@
+package com.storlead.system.controller;
+
+
+import com.alibaba.fastjson.JSONObject;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.common.util.OssBootUtil;
+import com.storlead.framework.common.util.generate.CodeGenerate;
+import com.storlead.system.pojo.entity.FileResourceEntity;
+import com.storlead.system.properties.FileProperties;
+import com.storlead.system.service.FileResourceService;
+import com.storlead.system.service.IOSSFileService;
+import com.storlead.system.util.FileUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.log4j.Log4j2;
+import javax.annotation.Resource;
+import org.springframework.core.env.Environment;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.net.URLEncoder;
+import java.util.Objects;
+
+/**
+ * <p>
+ * 文件系统 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2022-08-22
+ */
+@Log4j2
+@RestController
+@RequestMapping("/oss/file")
+@Api(tags="051.公共接口")
+public class FileResourceController {
+
+    @Resource
+    private FileProperties fileProperties;
+
+    @Resource
+    private Environment environment;
+
+    @Resource
+    private IOSSFileService ossFileService;
+
+    @Resource
+    private FileResourceService fileResourceService;
+
+
+
+    @ResponseBody
+    @PostMapping("/upload")
+    @ApiOperation(value = "公共接口-oss上传", notes = "公共接口-oss上传")
+    public Result upload(@ApiParam(value = "上传文件", required = true)@RequestParam("file") MultipartFile file) {
+
+        Result result = new Result();
+        try {
+            FileUtil.checkSize(fileProperties.getMaxSize(), file.getSize());
+            String suffix = FileUtil.getExtensionName(file.getOriginalFilename());
+//            if (!documents.contains(suffix)) {
+//                return Result.error("请上传pdf、doc、docx、xls、xlsx、ppt、pptx和zip格式!");
+//            }
+            String typeName = FileUtil.getFileTypeName(suffix);
+            String code = CodeGenerate.GenerateFullCode();
+
+            String name = FileUtil.getFileNameNoEx(file.getOriginalFilename());
+            FileResourceEntity fileResourceEntity =new FileResourceEntity();
+            fileResourceEntity.setFileName(name);
+            fileResourceEntity.setFileCode(code);
+            fileResourceEntity.setFileSuffix(suffix);
+            fileResourceEntity.setFileTypeName(typeName);
+            fileResourceEntity.setFileSize(Integer.valueOf((int) (file.getSize() / FileUtil.KB)));
+            JSONObject jsonObject = new JSONObject();
+            if (Integer.valueOf(0).equals(fileProperties.getMode())) {
+//                StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
+//                if(Objects.isNull(storePath)){
+//                    return  Result.error("上传错误");
+//                }
+//                fileResourceEntity.setFilePath(fileProperties.getFilePath()+File.separator+storePath.getFullPath());
+//                fileResourceService.save(fileResourceEntity);
+//                String url = fileProperties.getFilePath()+"/"+storePath.getFullPath();
+//                jsonObject.put("url", url);
+            } else  {
+                String url = ossFileService.upload(file);
+                jsonObject.put("url", url);
+            }
+            jsonObject.put("fileShowName", file.getOriginalFilename());
+            jsonObject.put("fileName", file.getName());
+            jsonObject.put("fileSize", getFileSize(file.getSize()));
+            jsonObject.put("fileExt", suffix);
+
+            result.setResult(jsonObject);
+            result.success("上传成功!");
+
+        }catch (Exception e) {
+            log.error("--upload -- error =",e);
+            return  Result.error("上传错误");
+        }
+        return result;
+    }
+
+    @ResponseBody
+    @PostMapping("/documentUpload")
+    @ApiOperation(value = "documentUpload 公共接口-oss上传", notes = " documentUpload 公共接口-oss上传")
+    public Result documentUpload(@ApiParam(value = "上传文件", required = true)@RequestParam("file") MultipartFile file) {
+
+        Result result = new Result();
+        String documents = "pdf doc docx xls xlsx ppt pptx zip";
+        try {
+            FileUtil.checkSize(fileProperties.getMaxSize(), file.getSize());
+            String suffix = FileUtil.getExtensionName(file.getOriginalFilename());
+            if (!documents.contains(suffix)) {
+                return Result.error("请上传pdf、doc、docx、xls、xlsx、ppt、pptx和zip格式!");
+            }
+            String typeName = FileUtil.getFileTypeName(suffix);
+            String code = CodeGenerate.GenerateFullCode();
+
+            String name = FileUtil.getFileNameNoEx(file.getOriginalFilename());
+            FileResourceEntity fileResourceEntity =new FileResourceEntity();
+            fileResourceEntity.setFileName(name);
+            fileResourceEntity.setFileCode(code);
+            fileResourceEntity.setFileSuffix(suffix);
+            fileResourceEntity.setFileTypeName(typeName);
+            fileResourceEntity.setFileSize(Integer.valueOf((int) (file.getSize() / FileUtil.KB)));
+            JSONObject jsonObject = new JSONObject();
+            if (Integer.valueOf(0).equals(fileProperties.getMode())) {
+//                StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
+//                if(Objects.isNull(storePath)){
+//                    return  Result.error("上传错误");
+//                }
+//                fileResourceEntity.setFilePath(fileProperties.getFilePath()+File.separator+storePath.getFullPath());
+//                fileResourceService.save(fileResourceEntity);
+//                String url = fileProperties.getFilePath()+"/"+storePath.getFullPath();
+//                jsonObject.put("url", url);
+            } else  {
+                String url = ossFileService.upload(file);
+                jsonObject.put("url", url);
+            }
+            jsonObject.put("fileShowName", file.getOriginalFilename());
+            jsonObject.put("fileName", file.getName());
+            jsonObject.put("fileSize", getFileSize(file.getSize()));
+            jsonObject.put("fileExt", suffix);
+
+            result.setResult(jsonObject);
+            result.success("上传成功!");
+
+        }catch (Exception e) {
+            log.error("--upload -- error =",e);
+            return  Result.error("上传错误");
+        }
+        return result;
+    }
+
+
+    @GetMapping(value = "/download")
+    @ApiOperation(value = "下载", notes = "下载")
+    public void download(Long fileId, HttpServletResponse response) throws IOException {
+        if (Objects.isNull(fileId)) {
+            return;
+        }
+        FileResourceEntity resource = fileResourceService.getById(fileId);
+        if (resource == null) {
+            return;
+        }
+        String url =  fileProperties.getPath().getPath() + File.separator + resource.getFilePath();
+        InputStream inputStream ;
+//        if (Integer.valueOf(0).equals(resource.getUploadMode())) {
+////            String fileUrl = resource.getFilePath();
+////            fileUrl = fileUrl.replace(fileProperties.getFilePath()+"/","");
+////            String group = fileUrl.substring(0, fileUrl.indexOf("/"));
+////            String path = fileUrl.substring(fileUrl.indexOf("/") + 1);
+////            byte[] bytes = fastFileStorageClient.downloadFile(group, path, new DownloadByteArray());
+//            inputStream = new ByteArrayInputStream(bytes);
+//        }else {
+//            inputStream = OssBootUtil.downloadObjFileToInputStream(url);
+//        }
+        inputStream = OssBootUtil.downloadObjFileToInputStream(url);
+        response.reset();
+        response.setContentType("application/octet-stream;charset=utf-8");
+        /*
+         * 跨域配置
+         * */
+        response.addHeader("Access-Control-Allow-Origin", "*");
+        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
+        response.addHeader("Access-Control-Allow-Headers", "Content-Type");
+
+        String filename = resource.getFileName()+"."+resource.getFileSuffix();
+        filename = filename.replace(" ","");
+        response.addHeader("content-disposition",
+                "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")
+                        + ";filename*=utf-8''" + URLEncoder.encode(filename, "UTF-8"));
+
+        ServletOutputStream outputStream = response.getOutputStream();
+        byte[] b = new byte[1024];
+        int len;
+//从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
+        while ((len = inputStream.read(b)) > 0) {
+            outputStream.write(b, 0, len);
+        }
+        inputStream.close();
+        outputStream.close();
+    }
+
+//    @GetMapping(value = "/download-url")
+//    @ApiOperation(value = "下载", notes = "下载")
+//    public Result downloadUrl(Long fileId) throws IOException {
+//        if (Objects.isNull(fileId)) {
+//            return Result.error("参数错误");
+//        }
+//        FileResourceEntity resource = fileResourceService.getById(fileId);
+//        if (resource == null) {
+//            return Result.error("附未找到");
+//        }
+//        String url =  fileProperties.getPath().getPath() + File.separator + resource.getFilePath();
+//
+//        String active = environment.getProperty("spring.profiles.active");
+//        String port =  environment.getProperty("server.port");
+//        String domainUrl = "";
+//        if(active.equals("prod")) {
+//            domainUrl = "https://sales.storlead.com/file"+url;
+//        } else if (active.equals("test")){
+//            domainUrl = "https://sales.test.storlead.com/file"+url;
+//        } else if (active.equals("dev")){
+//            domainUrl = "http://localhost:"+port+url;
+//        }
+//        return Result.error(domainUrl);
+//    }
+
+//    @GetMapping(value = "/downloadOSS")
+//    @ApiOperation(value = "下载", notes = "下载")
+//    public void downloadOSS(Long resourceId, HttpServletResponse response) throws IOException {
+//        if (resourceId == null) {
+//            return;
+//        }
+//        TargetResource resource = targetResourceService.getById(resourceId);
+//        if (resource == null) {
+//            return;
+//        }
+//        String url =  resource.getResourceUrl().substring(resource.getResourceUrl().indexOf(".com/")+5);
+//        InputStream inputStream ;
+//        if (Integer.valueOf(0).equals(fileProperties.getMode())) {
+//            String fileUrl = resource.getResourceUrl();
+//            fileUrl = fileUrl.replace(fileProperties.getFilePath()+"/","");
+//            String group = fileUrl.substring(0, fileUrl.indexOf("/"));
+//            String path = fileUrl.substring(fileUrl.indexOf("/") + 1);
+//            byte[] bytes = fastFileStorageClient.downloadFile(group, path, new DownloadByteArray());
+//            inputStream = new ByteArrayInputStream(bytes);
+//        }else {
+//            inputStream = OssBootUtil.downloadObjFileToInputStream(url);
+//        }
+//
+//        response.reset();
+//        response.setContentType("application/octet-stream;charset=utf-8");
+//        /*
+//         * 跨域配置
+//         * */
+//        response.addHeader("Access-Control-Allow-Origin", "*");
+//        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
+//        response.addHeader("Access-Control-Allow-Headers", "Content-Type");
+////        String filename = "测试.docx";
+//        String filename = resource.getResourceName();
+//        response.addHeader("content-disposition",
+//                "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")
+//                        + ";filename*=utf-8''" + URLEncoder.encode(filename, "UTF-8"));
+////		response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
+//        ServletOutputStream outputStream = response.getOutputStream();
+//        byte[] b = new byte[1024];
+//        int len;
+////从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
+//        while ((len = inputStream.read(b)) > 0) {
+//            outputStream.write(b, 0, len);
+//        }
+//        inputStream.close();
+//        outputStream.close();
+//    }
+
+    /**
+     * 获取文件大小
+     *
+     * @author timo
+     * @date 2018/9/3 14:03
+     */
+    public static String getFileSize(Long size) {
+        BigDecimal fileSize = BigDecimal.valueOf(size);
+        long B_SIZE = 1024;
+        long KB_SIZE = B_SIZE;
+        long MB_SIZE = KB_SIZE * B_SIZE;
+        long GB_SIZE = MB_SIZE * B_SIZE;
+
+        if (size / GB_SIZE > 0) {
+            // GB
+            return fileSize.divide(BigDecimal.valueOf(GB_SIZE), 2, RoundingMode.HALF_UP) + "GB";
+        }
+        if (size / MB_SIZE > 0) {
+            // GB
+            return fileSize.divide(BigDecimal.valueOf(MB_SIZE), 2, RoundingMode.HALF_UP) + "MB";
+        }
+        if (size / KB_SIZE > 0) {
+            // GB
+            return fileSize.divide(BigDecimal.valueOf(KB_SIZE), 2, RoundingMode.HALF_UP) + "KB";
+        }
+        if (size < B_SIZE) {
+            return size + "B";
+        }
+        return 0 + "B";
+    }
+
+
+    //headers必须是form-data否则无法上传文件
+//    @RequestMapping(value = "/uploadFile", headers = "content-type=multipart/form-data", method = RequestMethod.POST)
+//    @ApiOperation(value = "文件上传测试", notes = "文件上传测试")
+//    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
+//        String result;
+//        try {
+//            String path = fileResourceService.upload(file);
+//            if (!StringUtils.isEmpty(path)) {
+//                result = path;
+//            } else {
+//                result = "上传失败";
+//            }
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//            result = "服务异常";
+//        }
+//        return ResponseEntity.ok(result);
+//    }
+//
+//    @RequestMapping(value = "/download-test", method = RequestMethod.GET)
+//    @ApiOperation(value = "文件下载测试", notes = "文件下载测试")
+//    public void downloadTest(String fileUrl, HttpServletResponse response, HttpServletRequest request) throws Exception {
+////        "group1/M00/00/00/wKhAA2MF40eAX-w3AAMUCjmMJr4994.jpg"
+//
+//        byte[] data = fileResourceService.download(fileUrl);
+//        response.setCharacterEncoding("UTF-8");
+//        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode("下载文件名.jpg", "UTF-8"));
+//        // 写出
+//        ServletOutputStream outputStream = response.getOutputStream();
+//        IOUtils.write(data, outputStream);
+//    }
+//
+//    @RequestMapping(value = "/deleteByPath", method = RequestMethod.GET)
+//    @ApiOperation(value = "文件删除测试", notes = "文件删除测试")
+//    public Result deleteByPath(String filePathName) {
+//        // String filePathName = "group1/M00/00/00/wKhjZF3WEDmAPSglAABSZAhj0eU111.jpg" ;
+//        fileResourceService.deleteFile(filePathName);
+//        return Result.ok("删除成功");
+//    }
+
+}

+ 109 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/JobApiController.java

@@ -0,0 +1,109 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.util.SystemSettingCacheCheckUtil;
+import com.storlead.user.pojo.dto.JobDTO;
+import com.storlead.user.pojo.entity.JobEntity;
+import com.storlead.user.service.IJobService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2022-05-24 11:03
+ */
+@Api(tags = "系统: 角色管理")
+@RestController
+@RequestMapping("/sys/job/manage")
+public class JobApiController {
+
+    @Resource
+    private IJobService jobService;
+
+    @ApiOperation("岗位分页查询")
+    @PostMapping("/listpage")
+    public Result<?> listpage(@RequestBody JobDTO param) {
+        IPage<JobEntity> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        LambdaQueryWrapper<JobEntity> queryWrapper = new LambdaQueryWrapper();
+        if (StrUtil.isNotBlank(param.getJobName())) {
+            queryWrapper.like(JobEntity::getName,param.getJobName());
+        }
+        if (Objects.nonNull(param.getId())) {
+            queryWrapper.eq(JobEntity::getId,param.getId());
+        }
+        queryWrapper.eq(JobEntity::getIsDelete,0);
+        queryWrapper.eq(JobEntity::getEnabled,1);
+        queryWrapper.orderByAsc(JobEntity::getSort);
+        IPage<JobEntity> jobs = jobService.page(page,queryWrapper);
+        return Result.ok(jobs);
+    }
+
+    @ApiOperation("岗位查询")
+    @PostMapping("/listquery")
+    public Result<?> listquery(@RequestBody JobDTO param) {
+        LambdaQueryWrapper<JobEntity> queryWrapper = new LambdaQueryWrapper();
+        if (StrUtil.isNotBlank(param.getJobName())) {
+            queryWrapper.like(JobEntity::getName,param.getJobName());
+        }
+        queryWrapper.eq(JobEntity::getIsDelete,0);
+        queryWrapper.eq(JobEntity::getEnabled,1);
+        queryWrapper.orderByAsc(JobEntity::getSort);
+        List<JobEntity> jobs = jobService.list(queryWrapper);
+        return Result.ok(jobs);
+    }
+
+    @ApiOperation("保存岗位")
+    @PostMapping("/save")
+    public Result create(@RequestBody JobEntity info) {
+        Result r = SystemSettingCacheCheckUtil.checkAddDataMode();
+        if (!r.isSuccess()) {
+            return r;
+        }
+        if (Objects.isNull(info) || StrUtil.isBlank(info.getName())) {
+            return Result.error("参数错误");
+        }
+        if(Objects.isNull(info.getId())) {
+            info.setDataSource(1);
+        }
+        if (Objects.isNull(info.getId())) {
+            Integer exit = jobService.count(new LambdaQueryWrapper<JobEntity>().eq(JobEntity::getName,info.getName()));
+            if (exit > 0) {
+                return Result.error("岗位'"+ info.getName() +"'已存在!");
+            }
+        } else {
+            JobEntity oldAccount = jobService.getOne(new LambdaQueryWrapper<JobEntity>().eq(JobEntity::getName,info.getName()));
+            if (Objects.nonNull(oldAccount)) {
+                if (!oldAccount.getId().equals(info.getId())) {
+                    return Result.error("岗位'"+ info.getName() +"'已存在,无法修改!");
+                }
+            }
+        }
+        jobService.saveOrUpdate(info);
+        return Result.ok();
+    }
+
+    @ApiOperation("删除岗位")
+    @PostMapping("/delete")
+    public Result<?> remove(Long [] ids) {
+        if (Objects.isNull(ids) || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        jobService.removeByIds(Arrays.asList(ids));
+        return Result.ok();
+    }
+}

+ 53 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/JobController.java

@@ -0,0 +1,53 @@
+package com.storlead.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.common.result.Result;
+import com.storlead.user.pojo.entity.JobEntity;
+import com.storlead.user.service.IJobService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/lingcun/job")
+@Slf4j
+@Api(tags="5.公共接口")
+public class JobController {
+
+	@Resource
+	private IJobService jobService;
+
+	/**
+	 * 查询数据 查出所有部门,并以树结构数据格式响应给前端
+	 *
+	 * @return
+	 */
+	@RequestMapping(value = "/list", method = RequestMethod.GET)
+	@ApiOperation(value = "公共接口-职位-列表", notes = "公共接口-职位-列表")
+	@ApiResponses({
+			@ApiResponse(code = 200, message = "", response = JobEntity.class)
+	})
+	public Result<List<JobEntity>> queryTreeList() {
+		Result<List<JobEntity>> result = new Result<>();
+		try {
+			LambdaQueryWrapper<JobEntity> queryWrapper = new LambdaQueryWrapper();
+			queryWrapper.select(JobEntity::getDeptId, JobEntity::getName,JobEntity::getSort,JobEntity::getId);
+			List<JobEntity> list = jobService.list(queryWrapper);
+			result.setResult(list);
+			result.setSuccess(true);
+		} catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+		return result;
+	}
+
+
+}

+ 522 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/LoginApiController.java

@@ -0,0 +1,522 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.auth.jwt.JwtUtil;
+import com.storlead.framework.auth.vo.LoginUser;
+import com.storlead.framework.auth.vo.MenuPermis;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.common.constant.RedisKeySaltConstant;
+import com.storlead.framework.common.ecode.BCryptPasswordEncoder;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.common.util.MD5Util;
+import com.storlead.framework.common.util.RsaUtils;
+import com.storlead.framework.common.util.sso.CryptoZipUtil;
+import com.storlead.framework.common.util.sso.HttpSecurityCheckUtil;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.sms.pojo.entity.SmsLogEntity;
+import com.storlead.sms.spi.SmsCaptchaScene;
+import com.storlead.sms.spi.SmsCaptchaService;
+import com.storlead.sms.spi.SmsLogService;
+import com.storlead.system.pojo.entity.MenuEntity;
+import com.storlead.system.service.IMenuService;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.system.util.SystemConfigItemCacheUtil;
+import com.storlead.user.pojo.dto.UserLoginDTO;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.JobEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.IJobService;
+import com.storlead.user.service.IUserService;
+import com.storlead.wecom.service.CorpWeChatService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-07-15 10:11
+ */
+@RestController
+@RequestMapping("/sys/auth")
+@Api(tags="5.认证接口")
+@Slf4j
+public class LoginApiController {
+
+    @Resource
+    private IUserService sysUserService;
+
+    @Resource
+    private RedisService redisService;
+
+    @Resource
+    private SmsLogService smsLogService;
+
+    @Resource
+    private SmsCaptchaService smsCaptchaService;
+
+    @Resource
+    private IDepartService departService;
+
+    @Resource
+    private IMenuService menuService;
+
+    @Value("${environment}")
+    private  String environment;
+
+    @Resource
+    private IUserRoleService userRoleService;
+
+    @Resource
+    private IJobService jobService;
+
+    @Resource
+    private CorpWeChatService corpWeChatService;
+
+    @RequestMapping(value = "/redirect")
+    @ResponseBody
+    public Object redirect(HttpServletRequest request, HttpServletResponse response)  throws IOException {
+        response.sendRedirect("http://www.baidu.com");
+        return Result.ok("");
+    }
+
+    @RequestMapping(value = "/login", method = RequestMethod.POST)
+    @ApiOperation(value = "用户登录接口", notes = "用户登录接口")
+    public Result login(@RequestBody UserLoginDTO loginDTO) {
+
+        if (Objects.isNull(loginDTO)) {
+            return Result.error("参数错误");
+        }
+        UserEntity userInfo;
+        JSONObject obj = new JSONObject();
+        if (StrUtil.isNotBlank(loginDTO.getWxCode())) {
+            String code = corpWeChatService.login(loginDTO.getWxCode());
+            userInfo = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getXworkUserId,code));
+        } else if (StrUtil.isNotBlank(loginDTO.getSmsCode())) {
+            String mobile = StrUtil.trimToEmpty(loginDTO.getAccount());
+            String smsCode = StrUtil.trimToEmpty(loginDTO.getSmsCode());
+            if (StrUtil.isBlank(mobile)) {
+                return Result.error("请输入手机号");
+            }
+            if (StrUtil.isBlank(smsCode)) {
+                return Result.error("请输入验证码");
+            }
+            Result<?> vr = smsCaptchaService.verifyCaptcha(mobile, smsCode, SmsCaptchaScene.LOGIN, true);
+            if (!vr.isSuccess()) {
+                return Result.error(vr.getCode(), vr.getMessage());
+            }
+            userInfo = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>()
+                    .eq(UserEntity::getMobile, mobile)
+                    .eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0));
+            if (userInfo == null) {
+                return Result.error("该手机号未绑定用户或账号已删除");
+            }
+        } else {
+            if (StrUtil.isBlank(loginDTO.getAccount()) || StrUtil.isBlank(loginDTO.getPassword())) {
+                return Result.error("用户名或密码为空");
+            }
+            userInfo = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,loginDTO.getAccount()).eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0));
+            if (Objects.isNull(userInfo)) {
+                return Result.error("账号不存在");
+            }
+
+            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
+            String password = "";
+            if((!environment.equals("prod") && !loginDTO.getPassword().equals("qq123456")) || environment.equals("prod")){
+                try {
+                    password = RsaUtils.decryptByPrivateKey(RsaUtils.PRIVATE_KEY, loginDTO.getPassword());
+                }catch (Exception e) {
+                    return Result.error("用户名或密码错误");
+                }
+            }
+            if(environment.equals("prod") || (environment.equals("test")
+                    && !loginDTO.getPassword().equals("qq123456") &&
+                    !password.equals("qq123456"))) {
+                if (!bCryptPasswordEncoder.matches(password, userInfo.getPassword())) {
+                    return Result.error("用户名或密码错误");
+                }
+            }
+            String regex = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{8,}$";
+            String defaultPassWord = SystemConfigItemCacheUtil.getDefaultPassWord();
+
+            if(password !=null && (!password.matches(regex) || password.equals(defaultPassWord))){
+                obj.put("status",1);
+            }else {
+                obj.put("status",2);
+            }
+
+        }
+        if (Objects.isNull(userInfo)) {
+            return Result.error("登录失败,未获取到用户信息");
+        }
+        if (!userInfo.getEnabled()) {
+            return Result.error("该账号用户暂无登录权限");
+        }
+
+        Integer possessMenuCount = menuService.checkUserMenuAccess(userInfo.getId());
+        if (possessMenuCount == 0) {
+            return Result.error("暂未获取到菜单权限,请联系管理员分配权限!");
+        }
+
+        LoginUser loginUser = new LoginUser();
+        BeanUtils.copyProperties(userInfo,loginUser);
+
+        if (Objects.nonNull(userInfo.getJobId())) {
+            JobEntity job = jobService.getById(userInfo.getJobId());
+            if (Objects.nonNull(job)) {
+                loginUser.setJobName(job.getName());
+            }
+        }
+
+        loginUser.setRoleIds(userRoleService.selectUserRoleByUserId(userInfo.getId()));
+        if (Objects.nonNull(userInfo.getDeptId())) {
+            DeptEntity dept = departService.getById(userInfo.getDeptId());
+            if (Objects.nonNull(dept)) {
+                loginUser.setDeptName(dept.getName());
+                loginUser.setDeptJobDes(dept.getDeptJobDes());
+            }
+        }
+
+        //用户登录信息
+        String jwtToken = JwtUtil.createJWT(JSONObject.toJSONString(loginUser),loginUser.getMobile());
+        String token = SecureUtil.md5(jwtToken);
+
+        String json = JSONObject.toJSONString(loginUser);
+        redisService.setCacheObject(token, json, 60 * 60 * 24L * 7, TimeUnit.SECONDS);
+
+        Map<String, String> apiMap = new HashMap();
+        redisService.setCacheObject(RedisKeySaltConstant.API_CODE_REDIS + loginUser.getMobile(), apiMap);
+
+        obj.put("token", token);
+        return Result.ok(obj);
+    }
+
+    @RequestMapping(value = "/ticket_login", method = RequestMethod.POST)
+    @ApiOperation(value = "授权登录", notes = "授权登录")
+    public Result ticketLogin(String ticket) {
+
+        //解析加密
+        if (StrUtil.isBlank(ticket)) {
+            return Result.error("非法进入!");
+        }
+        JSONObject obj = new JSONObject();
+        // 解密
+        Map<String, String> argMap = CryptoZipUtil.decryptParams(ticket, CryptoZipUtil.SALT_KEY);
+        argMap.put("appSecret","K7C2R5WJYfN6V9XbZqH4A8eLTMsPDmU3");
+
+        // 解析参数
+        String checkSign = MD5Util.MD5Encode(HttpSecurityCheckUtil.makeSign(argMap),"UTF-8").toLowerCase();
+        String sign = argMap.get("sign").toString();
+        if (!checkSign.equals(sign) || StrUtil.isBlank(checkSign)) {
+            return Result.error("非法进入!");
+        }
+        long dt = Long.parseLong(argMap.get("dt"));
+        long currentTime = (System.currentTimeMillis() /1000) % HttpSecurityCheckUtil.divisor;
+        long difference = Math.abs(currentTime - dt);
+        if (difference > 30) {
+            return Result.error("连接已失效,请重新进入!");
+        }
+        String redirect = argMap.get("redirect").toString();
+        if (StrUtil.isBlank(redirect)) {
+            return Result.error("参数错误!");
+        }
+        obj.put("redirect",redirect);
+        String token = argMap.get("token").toString();
+        if (StrUtil.isNotBlank(token)) {
+            Object info = redisService.getCacheObject(token);
+            if (Objects.nonNull(info)) {
+                obj.put("token",token);
+                return Result.result(obj);
+            }
+        }
+        String account = argMap.get("account").toString();
+        if (StrUtil.isBlank(account)) {
+            return Result.error("参数错误!");
+        }
+        UserEntity userInfo = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,account));
+        if (Objects.isNull(userInfo)) {
+            return Result.error("授权失败,未找到用户信息!");
+        }
+
+        if (!userInfo.getEnabled()) {
+            return Result.error("授权失败,无权限访问!");
+        }
+
+        LoginUser loginUser = new LoginUser();
+        BeanUtils.copyProperties(userInfo,loginUser);
+
+        if (Objects.nonNull(userInfo.getJobId())) {
+            JobEntity job = jobService.getById(userInfo.getJobId());
+            if (Objects.nonNull(job)) {
+                loginUser.setJobName(job.getName());
+            }
+        }
+
+        loginUser.setRoleIds(userRoleService.selectUserRoleByUserId(userInfo.getId()));
+        if (Objects.nonNull(userInfo.getDeptId())) {
+            DeptEntity dept = departService.getById(userInfo.getDeptId());
+            if (Objects.nonNull(dept)) {
+                loginUser.setDeptName(dept.getName());
+                loginUser.setDeptJobDes(dept.getDeptJobDes());
+            }
+        }
+
+        //用户登录信息
+        String jwtToken = JwtUtil.createJWT(JSONObject.toJSONString(loginUser),loginUser.getMobile());
+        token = SecureUtil.md5(jwtToken);
+
+        String json = JSONObject.toJSONString(loginUser);
+        redisService.setCacheObject(token, json, 60 * 60 * 24L * 7, TimeUnit.SECONDS);
+
+        Map<String, String> apiMap = new HashMap();
+        redisService.setCacheObject(RedisKeySaltConstant.API_CODE_REDIS + loginUser.getMobile(), apiMap);
+
+        obj.put("token", token);
+        return Result.ok(obj);
+    }
+
+    @RequestMapping(value = "/external_login", method = RequestMethod.POST)
+    @ApiOperation(value = "用户登录接口", notes = "用户登录接口")
+    public Result externalLogin(@RequestBody UserLoginDTO loginDTO) {
+
+        if (Objects.isNull(loginDTO)) {
+            return Result.error("参数错误");
+        }
+        UserEntity userInfo;
+        JSONObject obj = new JSONObject();
+
+        if (StrUtil.isBlank(loginDTO.getAccount()) || StrUtil.isBlank(loginDTO.getPassword())) {
+            return Result.error("用户名或密码为空");
+        }
+        userInfo = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,loginDTO.getAccount()));
+        if (Objects.isNull(userInfo)) {
+            return Result.error("账号不存在");
+        }
+
+        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
+        String password = "";
+        if((!environment.equals("prod") && !loginDTO.getPassword().equals("qq123456")) || environment.equals("prod")){
+            try {
+                password = RsaUtils.decryptByPrivateKey(RsaUtils.PRIVATE_KEY, loginDTO.getPassword());
+            }catch (Exception e) {
+                return Result.error("用户名或密码错误");
+            }
+        }
+        if(environment.equals("prod") || (environment.equals("test")
+                && !loginDTO.getPassword().equals("qq123456") &&
+                !password.equals("qq123456"))) {
+            if (!bCryptPasswordEncoder.matches(password, userInfo.getPassword())) {
+                return Result.error("用户名或密码错误");
+            }
+        }
+        String regex = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{8,}$";
+        String defaultPassWord = SystemConfigItemCacheUtil.getDefaultPassWord();
+
+        if(password !=null && (!password.matches(regex) || password.equals(defaultPassWord))){
+            obj.put("status",1);
+        }else {
+            obj.put("status",2);
+        }
+
+        if (Objects.isNull(userInfo)) {
+            return Result.error("登录失败,未获取到用户信息");
+        }
+        if (!Integer.valueOf(9).equals(userInfo.getExternalUserType())) {
+            return Result.error("该账号暂无登录该系统的权限");
+        }
+
+        LoginUser loginUser = new LoginUser();
+        BeanUtils.copyProperties(userInfo,loginUser);
+
+        //用户登录信息
+        String jwtToken = JwtUtil.createJWT(JSONObject.toJSONString(loginUser),loginUser.getMobile());
+        String token = SecureUtil.md5(jwtToken);
+
+        String json = JSONObject.toJSONString(loginUser);
+        redisService.setCacheObject(token, json, 60 * 60 * 24L * 7, TimeUnit.SECONDS);
+
+        Map<String, String> apiMap = new HashMap();
+        redisService.setCacheObject(RedisKeySaltConstant.API_CODE_REDIS + loginUser.getMobile(), apiMap);
+
+        obj.put("token", token);
+        return Result.ok(obj);
+    }
+
+    @ApiOperation("获取当前用户信息")
+    @PostMapping("/getCurrentUserInfo")
+    public Object getCurrentUserInfo() {
+        LoginUser loginUser = LoginUserUtil.getLoginUser();
+        if (Objects.isNull(loginUser)) {
+            return Result.error("获取失败,请重新登录");
+        }
+        DeptEntity dept = departService.getById(loginUser.getDeptId());
+        UserEntity sysUser = sysUserService.getById(loginUser.getId());
+        loginUser.setJobDes(sysUser.getJobDes())
+                 .setBirthday(sysUser.getBirthday())
+                 .setEmail(sysUser.getEmail())
+                 .setMobile(sysUser.getMobile())
+                 .setSex(sysUser.getSex())
+                 .setUserName(sysUser.getUserName())
+                 .setNickName(sysUser.getNickName());
+        if (Objects.nonNull(dept)) {
+            loginUser.setDeptJobDes(dept.getDeptJobDes());
+        }
+        if (Objects.nonNull(dept)) {
+            if (StrUtil.isNotBlank(dept.getRouteCode())) {
+                List<String> codes = Arrays.asList(dept.getRouteCode().split(","));
+                loginUser.setSubCompanyCode(codes.get(1));
+                loginUser.setCompanyCode(codes.get(0));
+            }
+            loginUser.setDeptCode(dept.getRouteCode());
+            loginUser.setDeptName(dept.getName());
+        }
+
+        List<MenuEntity> userMenuList = menuService.getUserMenuList(loginUser.getId(),Arrays.asList(0),Arrays.asList(3));
+        if (!CollectionUtils.isEmpty(userMenuList)) {
+            loginUser.setBtnRes(userMenuList.stream().map(MenuEntity::getPerms).collect(Collectors.toSet()));
+        }
+        return Result.ok(loginUser);
+    }
+
+    @ApiOperation("获取当前用户信息")
+    @PostMapping("/getH5CurrentUserInfo")
+    public Object getH5CurrentUserInfo() {
+        LoginUser loginUser = LoginUserUtil.getLoginUser();
+        if (Objects.isNull(loginUser)) {
+            return Result.error("获取失败,请重新登录");
+        }
+        DeptEntity dept = departService.getById(loginUser.getDeptId());
+        UserEntity sysUser = sysUserService.getById(loginUser.getId());
+        loginUser.setJobDes(sysUser.getJobDes())
+                .setBirthday(sysUser.getBirthday())
+                .setEmail(sysUser.getEmail())
+                .setMobile(sysUser.getMobile())
+                .setSex(sysUser.getSex())
+                .setUserName(sysUser.getUserName())
+                .setNickName(sysUser.getNickName());
+        if (Objects.nonNull(dept)) {
+            loginUser.setDeptJobDes(dept.getDeptJobDes());
+        }
+        if (Objects.nonNull(dept)) {
+            if (StrUtil.isNotBlank(dept.getRouteCode())) {
+                List<String> codes = Arrays.asList(dept.getRouteCode().split(","));
+                loginUser.setSubCompanyCode(codes.get(1));
+                loginUser.setCompanyCode(codes.get(0));
+            }
+            loginUser.setDeptCode(dept.getRouteCode());
+            loginUser.setDeptName(dept.getName());
+        }
+
+        List<MenuEntity> userMenuList = menuService.getUserMenuList(loginUser.getId(),Arrays.asList(1),Arrays.asList(1,2,3));
+        if (!CollectionUtils.isEmpty(userMenuList)) {
+            List<MenuPermis> permis = userMenuList.stream()
+                    .map(menu -> {
+                        MenuPermis mp = new MenuPermis();
+                        mp.setId(menu.getId());
+                        mp.setPerm(menu.getPerms());
+                        mp.setType(menu.getMenuType());
+                        return mp;
+                    })
+                    .collect(Collectors.toList());
+            loginUser.setPermis(permis);
+        }
+        return Result.ok(loginUser);
+    }
+
+    @ApiOperation("获取当前用户信息")
+    @RequestMapping("/getCurrentUser")
+    public Object getCurrentUser() {
+        LoginUser loginUser = LoginUserUtil.getLoginUser();
+        if (Objects.isNull(loginUser)) {
+            return Result.error("获取失败,请重新登录");
+        }
+//        DeptEntity dept = departService.getById(loginUser.getDeptId());
+        UserEntity sysUser = sysUserService.getById(loginUser.getId());
+        loginUser.setJobDes(sysUser.getJobDes())
+                .setBirthday(sysUser.getBirthday())
+                .setEmail(sysUser.getEmail())
+                .setMobile(sysUser.getMobile())
+                .setSex(sysUser.getSex())
+                .setUserName(sysUser.getUserName())
+                .setNickName(sysUser.getNickName());
+//        if (Objects.nonNull(dept)) {
+//            loginUser.setDeptJobDes(dept.getDeptJobDes());
+//        }
+//        if (Objects.nonNull(dept)) {
+//            if (StrUtil.isNotBlank(dept.getRouteCode())) {
+//                List<String> codes = Arrays.asList(dept.getRouteCode().split(","));
+//                loginUser.setSubCompanyCode(codes.get(1));
+//                loginUser.setCompanyCode(codes.get(0));
+//            }
+//            loginUser.setDeptCode(dept.getRouteCode());
+//            loginUser.setDeptName(dept.getName());
+//        }
+        return Result.ok(loginUser);
+    }
+
+    @ApiOperation("获取AccessToken")
+    @PostMapping("/sendVerifyCode")
+    public Result sendLoginVerifyCode(String mobile) {
+        if (StrUtil.isBlank(mobile)) {
+            return Result.error("参数错误");
+        }
+        UserEntity userAccount = sysUserService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,mobile));
+        if (userAccount == null) {
+            return Result.error("手机号错误,该手机号用户不存在");
+        }
+        try {
+            return  smsLogService.sendLoginSmsCode(mobile);
+        }catch (Exception e) {
+            return  Result.error(e.getMessage());
+        }
+    }
+
+
+
+//    public static void main(String[] args) {
+//
+//        String baseUrl = "https://openapi.liblibai.cloud";
+//
+//        String secretKey = "MyJwnqGd1h_W__ENlx_ywK-RpkHTeJd7";
+//        // 请求API接口的uri地址
+//        String uri = "/api/generate/webui/status";
+//        // 当前毫秒时间戳
+//        Long timestamp = System.currentTimeMillis();
+//        // 随机字符串
+//        String signatureNonce = RandomStringUtils.randomAlphanumeric(10);
+//        // 拼接请求数据
+//        String content = uri + "&" + timestamp + "&" + signatureNonce;
+//
+//        try {
+//            // 生成签名
+//            SecretKeySpec secret = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
+//            Mac mac = Mac.getInstance("HmacSHA1");
+//            mac.init(secret);
+//            String signature =  Base64.encodeBase64URLSafeString(mac.doFinal(content.getBytes()));
+//            String url = baseUrl+uri+"?AccessKey=kaB5IBReZKiU9LiIJPbl6g&Signature="+signature+"&Timestamp="+timestamp+"&SignatureNonce="+signatureNonce;
+//            System.out.println("url :"+url);
+//        } catch (NoSuchAlgorithmException e) {
+//           log.error("NoSuchAlgorithmException -- error--",e);
+//        } catch (InvalidKeyException e) {
+//            log.error("InvalidKeyException -- error--",e);
+//        }
+//    }
+}

+ 152 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/LoginController.java

@@ -0,0 +1,152 @@
+package com.storlead.system.controller;
+
+import cn.hutool.crypto.SecureUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.auth.jwt.JwtUtil;
+import com.storlead.framework.auth.vo.LoginUser;
+import com.storlead.framework.common.constant.DefContants;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.common.util.ConvertUtils;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.JobEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.pojo.entity.UserForm;
+import com.storlead.user.pojo.vo.WeChatPhoneInfo;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.IJobService;
+import com.storlead.user.service.IUserService;
+import com.storlead.wecom.util.WechatPhoneUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import javax.annotation.Resource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Author scott
+ * @since 2018-12-17
+ */
+@RestController
+@RequestMapping("/lingcun")
+@Api(tags="5.公共接口")
+@Slf4j
+public class LoginController {
+	@Resource
+	private IUserService sysUserService;
+
+	@Resource
+    private IDepartService departService;
+
+	@Resource
+	private RedisService redisService;
+
+	@ApiOperation("修改密码")
+	@PostMapping("/modifyPass")
+	public Object updateUserPass(@RequestBody @Validated UserForm param) {
+		return sysUserService.updateUserPass(param);
+	}
+
+
+	/**
+	 * 退出登录
+	 * @param request
+	 * @param
+	 * @return
+	 */
+	@RequestMapping(value = "/logout")
+	@ApiOperation(value = "用户退出登录接口", notes = "用户退出登录接口")
+	public Result<Object> logout(HttpServletRequest request) {
+		//用户退出逻辑
+		String token = request.getHeader(DefContants.ACCESS_TOKEN);
+		if(ConvertUtils.isEmpty(token)) {
+			return Result.error("退出登录失败!");
+		}
+		Boolean del = redisService.deleteObject(token);
+		if (del) {
+			return Result.ok("退出登录成功!");
+		} else {
+			Object obj = redisService.getCacheObject(token);
+			if (obj == null) {
+				return Result.error("退出登录成功!");
+			}
+			return Result.error("退出登录失败!");
+		}
+	}
+
+
+	/**
+	 * 用户信息
+	 *
+	 * @param request
+	 * @return
+	 */
+	@GetMapping("/getUserBaseInfo")
+	@ApiOperation(value = "根据token获取用户基本信息", notes = "根据token获取用户基本信息(header中传token)")
+	public Result<JSONObject> getUserBaseInfo(HttpServletRequest request) {
+
+		LoginUser loginUser =  LoginUserUtil.getLoginUser();
+		// 生成token
+		Result<JSONObject> result = new Result<JSONObject>();
+		//用户退出逻辑
+		String token = request.getHeader(DefContants.ACCESS_TOKEN);
+		UserEntity sysUser = sysUserService.getUserByName(loginUser.getUserName());
+		// 获取用户部门信息
+		JSONObject obj = new JSONObject();
+		DeptEntity dept = departService.getById(sysUser.getDeptId());
+		obj.put("dept", dept);
+		obj.put("userInfo", sysUser);
+		result.setResult(obj);
+		result.success("登录成功");
+		return result;
+	}
+
+//	/**
+//	 * 获取校验码
+//	 */
+//	@GetMapping(value = "/getCheckCode")
+//	public Result<Map<String,String>> getCheckCode(){
+//		Result<Map<String,String>> result = new Result<Map<String,String>>();
+//		Map<String,String> map = new HashMap<String,String>();
+//		try {
+//			String code = RandomUtil.randomString(BASE_CHECK_CODES,4);
+//			String key = MD5Util.MD5Encode(code+System.currentTimeMillis(), "utf-8");
+//			ehCacheService.put(CommonConstant.CAPCHA_CACHE,key, code, 60);
+//			map.put("key", key);
+//			map.put("code",code);
+//			result.setResult(map);
+//			result.setSuccess(true);
+//		} catch (Exception e) {
+//			e.printStackTrace();
+//			result.setSuccess(false);
+//		}
+//		return result;
+//	}
+
+	/**
+	 * 获取手机验证码
+	 * @return
+	 */
+//	@GetMapping(value = "/getPhoneCode")
+//	@ApiOperation(value = "获取手机验证码", notes = "获取手机验证码")
+//	public Result getPhoneCode(@RequestParam("mobile") @ApiParam(value = "手机号") String mobile){
+//		try {
+//			int code = RandomCodeUtil.RandomCheckCode();
+//			return smsService.sendSms(mobile,code+"", SmsTypeEnum.Login_SMS.getCode(),null);
+//		} catch (Exception e) {
+//			e.printStackTrace();
+//		}
+//		return Result.error("发送手机验证码失败");
+//	}
+}

+ 505 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/MenuApiController.java

@@ -0,0 +1,505 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.common.util.PingyinUtil;
+import com.storlead.framework.common.vo.menu.TreeVo;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.system.entity.UserWebH5MenuShowConfigEntity;
+import com.storlead.system.pojo.dto.MenuParamDTO;
+import com.storlead.system.pojo.entity.MenuEntity;
+import com.storlead.system.pojo.vo.CustomMenuDTO;
+import com.storlead.system.pojo.vo.RouterVo;
+import com.storlead.system.pojo.vo.UserMenuVO;
+import com.storlead.system.service.IMenuService;
+import com.storlead.system.service.SysResourceMenuService;
+import com.storlead.system.service.UserWebH5MenuShowConfigService;
+import com.storlead.user.pojo.dto.MenuParam;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.log4j.Log4j2;
+import net.sourceforge.pinyin4j.PinyinHelper;
+import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
+import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
+import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
+import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
+import org.apache.commons.lang3.StringUtils;
+import javax.annotation.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2022-05-20 10:37
+ */
+@Api(tags = "系统: 菜单管理")
+@Log4j2
+@RestController
+@RequestMapping("sys/menu")
+public class MenuApiController {
+
+    @Resource
+    private IMenuService menuService;
+
+    @Resource
+    private UserWebH5MenuShowConfigService webH5MenuShowConfigService;
+
+    @Resource
+    private SysResourceMenuService resourceMenuService;
+
+    public static Integer FIRST_CODE = 100;
+
+    @ApiOperation("查询菜单Tree")
+    @PostMapping("listTree")
+    public Result listTree(@RequestBody MenuParamDTO dto) {
+        try {
+            LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper();
+            queryWrapper.eq(MenuEntity::getIsDelete,0);
+            queryWrapper.eq(MenuEntity::getEnabled,1);
+            if (Objects.isNull(dto)) {
+                queryWrapper.eq(MenuEntity::getServiceModeType,0);
+            } else {
+                queryWrapper.eq(MenuEntity::getServiceModeType,dto.getServiceModeType());
+            }
+            queryWrapper.orderByAsc(MenuEntity::getSort);
+            List<MenuEntity> menus = menuService.list(queryWrapper);
+            if(CollectionUtils.isEmpty(menus)) {
+                return Result.ok(Collections.EMPTY_LIST);
+            }
+            List<UserMenuVO> res = createTreeMenus(Long.valueOf(0), UserMenuVO.menuListToTargetMenuTreeVoList(menus));
+            //res.stream().sorted(Comparator.comparing(UserMenuVO::getSort).reversed());
+            return Result.ok(res);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询菜单所有菜单")
+    @PostMapping("listAllTree")
+    public Result listAllTree(@RequestBody MenuParamDTO dto) {
+        try {
+            LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper();
+            queryWrapper.eq(MenuEntity::getIsDelete,0);
+            queryWrapper.eq(MenuEntity::getEnabled,1);
+            queryWrapper.orderByAsc(MenuEntity::getSort);
+            if (Objects.isNull(dto)) {
+                queryWrapper.eq(MenuEntity::getServiceModeType,0);
+            } else {
+                queryWrapper.eq(MenuEntity::getServiceModeType,dto.getServiceModeType());
+            }
+            List<MenuEntity> menus = menuService.list(queryWrapper);
+            List<RouterVo> routerVos = menuToRouter(menus);
+            return Result.ok(routerVos);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+
+    private List<RouterVo> menuToRouter(List<MenuEntity> menus){
+        List<RouterVo> routerVos = new ArrayList<>();
+        for (MenuEntity menu : menus) {
+            RouterVo routerVo = new RouterVo() ;
+            routerVo.setMenuType(menu.getMenuType()+"");
+            routerVo.setName(menu.getMenuName());
+            routerVo.setParentId(menu.getPid());
+            routerVo.setId(menu.getId());
+            routerVos.add(routerVo);
+        }
+        return routerVos;
+    }
+
+
+    @ApiOperation("查询菜单Tree")
+    @PostMapping("sort")
+    public Result sort(Long [] ids) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        Integer i = 0;
+        for (Long id : ids) {
+            LambdaUpdateWrapper<MenuEntity> update = new LambdaUpdateWrapper();
+            update.eq(MenuEntity::getId,id);
+            update.set(MenuEntity::getSort,i);
+            menuService.update(update);
+            i++;
+        }
+
+        return Result.ok();
+    }
+
+
+    @ApiOperation("查询菜单Tree")
+    @PostMapping("listResTree")
+    public Result listResTree() {
+        try {
+            LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper();
+            queryWrapper.eq(MenuEntity::getIsDelete,0);
+            queryWrapper.eq(MenuEntity::getEnabled,1);
+            queryWrapper.orderByAsc(MenuEntity::getSort);
+            queryWrapper.eq(MenuEntity::getServiceModeType,0);
+
+            List<MenuEntity> menus = menuService.list(queryWrapper);
+            if(CollectionUtils.isEmpty(menus)) {
+                return Result.ok(Collections.EMPTY_LIST);
+            }
+            List<TreeVo> resls = new ArrayList<>();
+            Map<Long, List<TreeVo>> resMap = new HashMap<>();
+            List<TreeVo> menuResls = new ArrayList<>();
+            if(!CollectionUtils.isEmpty(menus)) {
+                for(MenuEntity res : menus) {
+                    TreeVo treeVo= new TreeVo();
+                    treeVo.setId(res.getId());
+                    treeVo.setMenuId(res.getId());
+                    treeVo.setParentId(res.getPid());
+                    treeVo.setTitle(res.getMenuName());
+                    treeVo.setType(20);
+                    treeVo.setArg(res);
+                    treeVo.setExpand(false);
+                    treeVo.setDisabled(false);
+                    treeVo.setDisableCheckbox(false);
+                    menuResls.add(treeVo);
+                }
+            }
+            List<TreeVo> res = createResTrees(Long.valueOf(0),menuResls);
+            return Result.ok(res);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+
+
+    @ApiOperation("查询菜单Tree(H5和移动端共用)")
+    @PostMapping("list_web_h5_res_tree")
+    public Result listWebH5ResTree(@RequestBody MenuParamDTO dto) {
+        try {
+            LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper();
+            queryWrapper.eq(MenuEntity::getIsDelete,0);
+            queryWrapper.eq(MenuEntity::getEnabled,1);
+            queryWrapper.orderByAsc(MenuEntity::getSort);
+            if (Objects.isNull(dto)) {
+                queryWrapper.eq(MenuEntity::getServiceModeType,0);
+            } else {
+                queryWrapper.eq(MenuEntity::getServiceModeType,dto.getServiceModeType());
+            }
+            List<MenuEntity> menus = menuService.list(queryWrapper);
+            if(CollectionUtils.isEmpty(menus)) {
+                return Result.ok(Collections.EMPTY_LIST);
+            }
+            List<TreeVo> resls = new ArrayList<>();
+            Map<Long, List<TreeVo>> resMap = new HashMap<>();
+
+            List<TreeVo> menuResls = new ArrayList<>();
+            if(!CollectionUtils.isEmpty(menus)) {
+                for(MenuEntity res : menus) {
+                    TreeVo treeVo= new TreeVo();
+                    treeVo.setId(res.getId());
+                    treeVo.setMenuId(res.getId());
+                    treeVo.setParentId(res.getPid());
+                    treeVo.setTitle(res.getMenuName());
+                    treeVo.setType(20);
+                    treeVo.setArg(res);
+                    treeVo.setExpand(false);
+                    treeVo.setDisabled(false);
+                    treeVo.setDisableCheckbox(false);
+                    menuResls.add(treeVo);
+                }
+            }
+            List<TreeVo> res = createResTrees(Long.valueOf(0),menuResls);
+            return Result.ok(res);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+
+
+    @ApiOperation("查询菜单列表")
+    @PostMapping("pagelist")
+    public Result pagelist(@RequestBody MenuParam dto) {
+        try {
+            IPage<MenuEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+            LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper();
+            if (Objects.nonNull(dto)) {
+                queryWrapper.eq(MenuEntity::getServiceModeType,dto.getServiceModeType());
+            }
+            IPage<MenuEntity> menus = menuService.page(page,queryWrapper);
+            return Result.ok(menus);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+
+    @ApiOperation("保存菜单")
+    @PostMapping("save")
+    public Result save(@RequestBody MenuEntity menu) {
+        if (Objects.isNull(menu.getPid())) {
+            menu.setPid(0L);
+        }
+        convertMenuCode(menu);
+        return Result.ok(menuService.saveOrUpdate(menu));
+    }
+
+    @ApiOperation("删除菜单")
+    @PostMapping("delete")
+    public Result remove(@NotNull @NotEmpty Long[] ids) {
+        List<MenuEntity> menus = menuService.list(new LambdaQueryWrapper<MenuEntity>().in(MenuEntity::getPid,ids));
+        if (!CollectionUtils.isEmpty(menus)) {
+            List idls = Arrays.asList(ids);
+            for (MenuEntity menu : menus) {
+                if (!idls.contains(menu.getId())){
+                    return Result.error("请先删除子节点");
+                }
+            }
+        }
+        menuService.removeByIds(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+
+
+    /**
+     * 递归生成菜单树
+     */
+    private List<TreeVo> createResTrees(Long pid, List<TreeVo> menus) {
+        List<TreeVo> treeMenu = new ArrayList<>();
+        for (TreeVo menu : menus) {
+            if (pid.equals(menu.getParentId())) {
+                treeMenu.add(menu);
+                List<TreeVo> childs =  createResTrees(menu.getId(), menus);
+                if (childs != null && childs.size() > 0) {
+                    menu.setChildren(childs);
+                }
+            }
+        }
+        return treeMenu;
+    }
+
+    private void convertMenuCode(MenuEntity info) {
+
+        if (StrUtil.isBlank(info.getMenuCode())) {
+            String routeCode = getInitials(info.getMenuName());
+            MenuEntity menu = menuService.getRootLastNodeInfo(info.getPid());
+            String lastCode;
+            if (menu == null) {
+                info.setPid(Long.valueOf(0));
+                info.setMenuCode(routeCode);
+            } else {
+                lastCode = getInitials(menu.getMenuName());
+                info.setMenuCode(lastCode+"_"+routeCode);
+            }
+        }
+    }
+
+    @ApiOperation("处理历史数据code")
+    @PostMapping("batch_generate_code")
+    public Result initializeCode() {
+        List<MenuEntity> menuList = menuService.list(new LambdaQueryWrapper<MenuEntity>().in(MenuEntity::getMenuType,Arrays.asList(1,2)));
+        Map<Long,MenuEntity> mapMenu = menuList.stream().collect(Collectors.toMap(MenuEntity::getId, a ->a));
+        for (MenuEntity info : menuList) {
+            String routeCode = getInitials(info.getMenuName());
+            String code = recursionMenu(mapMenu,info,routeCode);
+            LambdaUpdateWrapper<MenuEntity> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(MenuEntity::getId,info.getId());
+            updateWrapper.set(MenuEntity::getMenuCode,code);
+            menuService.update(updateWrapper);
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation("处理历史数据code")
+    @PostMapping("update_menu_icon")
+    public Result updateMenuIcon() {
+        List<MenuEntity> menuList = menuService.list(new LambdaQueryWrapper<MenuEntity>().eq(MenuEntity::getMenuType,2));
+        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
+        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+        for (MenuEntity info : menuList) {
+            try {
+                String iconName = PinyinHelper.toHanyuPinyinString(info.getMenuName(), format, "");
+                LambdaUpdateWrapper<MenuEntity> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(MenuEntity::getId,info.getId());
+                updateWrapper.set(MenuEntity::getIcon,iconName);
+                menuService.update(updateWrapper);
+            }catch (Exception e) {
+                log.error("---updateMenuIcon",e);
+            }
+        }
+        return Result.ok();
+    }
+    public String recursionMenu(Map<Long,MenuEntity> mapMenu,MenuEntity info,String routeCode) {
+
+        if (Integer.valueOf(0).equals(info.getPid())) {
+            return routeCode;
+        }
+        MenuEntity pMenu = mapMenu.get(info.getPid());
+        if (Objects.isNull(pMenu)) {
+            return routeCode;
+        }
+        String pCode = getInitials(pMenu.getMenuName());
+        if (StrUtil.isNotBlank(pCode)) {
+            routeCode = pCode+"_"+routeCode;
+           return recursionMenu(mapMenu,pMenu,routeCode);
+        }
+        return routeCode;
+    }
+
+    public static String getInitials(String chinese) {
+        StringBuilder initials = new StringBuilder();
+        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
+        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+
+        for (char ch : chinese.toCharArray()) {
+            if (Character.toString(ch).matches("[\u4E00-\u9FA5]")) { // 判断是否为汉字
+                try {
+                    String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
+                    if (pinyinArray != null && pinyinArray.length > 0) {
+                        initials.append(pinyinArray[0].charAt(0));
+                    }
+                } catch (BadHanyuPinyinOutputFormatCombination e) {
+                   log.error("hanyuPinyinOutputFormatCombination---error-----",e);
+                }
+            } else {
+                initials.append(ch); // 非汉字直接附加
+            }
+        }
+
+        return initials.toString();
+    }
+
+    public static void main(String[] args) {
+        // 创建输出格式对象
+        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
+        // 设置拼音为全小写
+        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+        // 设置拼音不带音调
+        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+
+        // 你可以使用 PinyinHelper 来转换汉字为拼音
+        try {
+            String pinyin = PinyinHelper.toHanyuPinyinString("汉字", format, "");
+            System.out.println(pinyin);  // 输出:han zi
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 递归生成菜单树
+     */
+    private List<UserMenuVO> createTreeMenus(Long pid, List<UserMenuVO> menus) {
+        List<UserMenuVO> treeMenu = new ArrayList<>();
+        for (UserMenuVO menu : menus) {
+            if (pid.equals(menu.getPid())) {
+                treeMenu.add(menu);
+                List<UserMenuVO> childs =  createTreeMenus(menu.getId(), menus);
+                if (childs != null && childs.size() > 0) {
+                    menu.setChildren(childs);
+                }
+            }
+        }
+        return treeMenu;
+    }
+
+    @ApiOperation("获取自定义菜单")
+    @PostMapping("get_custom_menu_list")
+    public Result getCustomMenuList(Integer position) {
+
+        LambdaQueryWrapper<UserWebH5MenuShowConfigEntity> queryH5Config = new LambdaQueryWrapper();
+        queryH5Config.eq(UserWebH5MenuShowConfigEntity::getOwnerBy, LoginUserUtil.getCurrentUserId());
+        queryH5Config.eq(UserWebH5MenuShowConfigEntity::getMenuShowPosition,position);
+        queryH5Config.last("limit 1");
+        UserWebH5MenuShowConfigEntity webH5MenuShowConfig =  webH5MenuShowConfigService.getOne(queryH5Config);
+        if (Objects.nonNull(webH5MenuShowConfig)) {
+            String menuJson = webH5MenuShowConfig.getMenuJson();
+            return Result.result(menuJson);
+        }
+//        LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper();
+//        queryWrapper.eq(MenuEntity::getIsDelete,0);
+//        queryWrapper.eq(MenuEntity::getEnabled,1);
+//        queryWrapper.eq(MenuEntity::getServiceModeType, 1);
+//        queryWrapper.eq(MenuEntity::getMenuShowPosition, position);
+//        queryWrapper.orderByAsc(MenuEntity::getSort);
+//        List<MenuEntity> menus = menuService.list(queryWrapper);
+//        if (!CollectionUtils.isEmpty(menus)) {
+//            String menuJson = JSON.toJSONString(menus);
+//            return Result.ok(JSONArray.parseArray(menuJson, UserH5MenuVO.class));
+//        }
+        return Result.ok();
+    }
+
+    @ApiOperation("自定义菜单设置")
+    @PostMapping("set_custom_menu")
+    public Result customMenuSet(@RequestBody CustomMenuDTO dto) {
+
+        LambdaQueryWrapper<UserWebH5MenuShowConfigEntity> queryH5Config = new LambdaQueryWrapper();
+        queryH5Config.eq(UserWebH5MenuShowConfigEntity::getOwnerBy, LoginUserUtil.getCurrentUserId());
+        queryH5Config.eq(UserWebH5MenuShowConfigEntity::getMenuShowPosition,dto.getPosition());
+        queryH5Config.last("limit 1");
+        UserWebH5MenuShowConfigEntity webH5MenuShowConfig =  webH5MenuShowConfigService.getOne(queryH5Config);
+        if (Objects.isNull(webH5MenuShowConfig)) {
+            webH5MenuShowConfig = new UserWebH5MenuShowConfigEntity();
+            webH5MenuShowConfig.setMenuJson(dto.getJsonMenu());
+            webH5MenuShowConfig.setMenuShowPosition(dto.getPosition());
+            webH5MenuShowConfig.setOwnerBy(LoginUserUtil.getCurrentUserId());
+        } else {
+            webH5MenuShowConfig.setMenuJson(dto.getJsonMenu());
+            webH5MenuShowConfig.setMenuShowPosition(dto.getPosition());
+        }
+        webH5MenuShowConfigService.saveOrUpdate(webH5MenuShowConfig);
+        return Result.ok();
+    }
+
+
+    @ApiOperation("查询菜单所有菜单")
+    @PostMapping("allMenulist")
+    public Result allMenulist(String menuName, Integer[] types) {
+        if(types == null) {
+            types = new Integer[]{1, 2};
+        }
+        try {
+            LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper();
+            queryWrapper.eq(MenuEntity::getIsDelete,0);
+            queryWrapper.eq(MenuEntity::getEnabled,1);
+            queryWrapper.in(MenuEntity::getMenuType, Arrays.asList(types));
+            queryWrapper.likeLeft(StringUtils.isNotEmpty(menuName), MenuEntity::getMenuName, menuName);
+            queryWrapper.likeRight(StringUtils.isNotEmpty(menuName), MenuEntity::getMenuName, menuName);
+            queryWrapper.orderByAsc(MenuEntity::getSort);
+            List<MenuEntity> menus = menuService.list(queryWrapper);
+            if (!CollectionUtils.isEmpty(menus)) {
+                menus.forEach(e -> {
+                    if (StrUtil.isNotBlank(e.getMenuName())) {
+                        String pyIcon = PingyinUtil.getLowFullSpell(e.getMenuName());
+                        e.setIcon(pyIcon);
+                    }
+                });
+            }
+//            List<RouterVo> routerVos = menuToRouter(menus);
+            return Result.ok(menus);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+
+}

+ 310 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/OrganizDataSyncApiController.java

@@ -0,0 +1,310 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.storlead.framework.common.ecode.PasswordEncoder;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.system.util.SystemConfigItemCacheUtil;
+import com.storlead.user.pojo.entity.*;
+import com.storlead.user.pojo.vo.WxExcelOrgVo;
+import com.storlead.user.service.*;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import javax.annotation.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.InputStream;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-07-15 15:55
+ */
+
+@Api(tags = "系统: 角色管理")
+@RestController
+@RequestMapping("/sys/data/sync")
+public class OrganizDataSyncApiController {
+
+    @Resource
+    private ICompanyService companyService;
+
+    @Resource
+    private ISubCompanyService subCompanyService;
+
+    @Resource
+    private IDepartService departService;
+
+    @Resource
+    private IUserService userService;
+
+    @Resource
+    private PasswordEncoder passwordEncoder;
+
+    @Resource
+    private IUserRoleService userRoleService;
+
+    @Resource
+    private IJobService jobService;
+
+    @ApiOperation("导入企业微信数据")
+    @RequestMapping(value = "/importWxData",method = {RequestMethod.POST},headers = "content-type=multipart/form-data")
+    public Result importWxData(HttpServletRequest request, @ApiParam(value="文件",required=true)@RequestPart("file") MultipartFile file) throws Exception {
+        if (file == null ) {
+            return Result.error("请上传附件!");
+        }
+        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
+        // MultipartFile files = multipartRequest.getFile("filename");
+        InputStream inputStream = file.getInputStream();
+        List<WxExcelOrgVo> vols = excelWxExcelOrg(inputStream, file.getOriginalFilename());
+        Result result = markData(vols);
+        return result;
+    }
+
+    public List<WxExcelOrgVo> excelWxExcelOrg(InputStream in, String fileName) {
+        List list = new ArrayList<>();
+        try{
+            //创建Excel工作薄
+            Workbook work = this.getWorkbook(in, fileName);
+            if (null == work) {
+                return null;
+            }
+            //获得第一个shell
+            Sheet sheet=work.getSheetAt(0);
+            //获得Excel的行数
+            int totalRows=sheet.getPhysicalNumberOfRows();
+            // 获取总列数
+            // int totalCells=sheet.getRow(0).getPhysicalNumberOfCells();
+            // Cell cell = null;
+            List<WxExcelOrgVo> orgVos = new ArrayList<>();
+            for (int j = 10; j < totalRows; j++) {
+                Row row = sheet.getRow(j);
+                WxExcelOrgVo orgVo = new WxExcelOrgVo();
+                if(row.getCell(0) != null) {
+                    orgVo.setRealName(row.getCell(0).toString());
+                }
+                if(row.getCell(1) != null) {
+                    orgVo.setXworkUserId(row.getCell(1).toString());
+                }
+                if(row.getCell(2) != null) {
+                    orgVo.setNickName(row.getCell(2).toString());
+                }
+                if(row.getCell(3) != null) {
+                    orgVo.setJobName(row.getCell(3).toString());
+                }
+                if(row.getCell(4) != null) {
+                    orgVo.setDeptName(row.getCell(4).toString());
+                }
+                if(row.getCell(5) != null) {
+                    orgVo.setSex(row.getCell(5).toString());
+                }
+
+                if(row.getCell(6) != null) {
+                    orgVo.setMobile(row.getCell(6).toString());
+                }
+                if(row.getCell(10) != null) {
+                    orgVo.setEmail(row.getCell(10).toString());
+                }
+                if(row.getCell(12) != null) {
+                    String  enabledStr = row.getCell(12).toString();
+                    orgVo.setEnabled("已激活".equals(enabledStr) ? true:false);
+                }
+                orgVos.add(orgVo);
+            }
+            work.close();
+            return orgVos;
+        }catch (Exception e){
+            return null;
+        }
+    }
+
+    /**
+     * 判断文件格式
+     *
+     * @param inStr
+     * @param fileName
+     * @return
+     * @throws Exception
+     */
+    public Workbook getWorkbook(InputStream inStr, String fileName) throws Exception {
+        Workbook workbook = null;
+        String fileType = fileName.substring(fileName.lastIndexOf("."));
+        if (".xls".equals(fileType)) {
+            workbook = new HSSFWorkbook(inStr);
+        } else if (".xlsx".equals(fileType)) {
+            workbook = new XSSFWorkbook(inStr);
+        } else {
+            return null;
+        }
+        return workbook;
+    }
+
+    public Result markData(List<WxExcelOrgVo> vos) {
+
+        /**
+         * 岗位处理
+         */
+        Map<String, Long> jobMap = new HashMap<>();
+        List<JobEntity> oldJob = jobService.list(new LambdaUpdateWrapper<JobEntity>().eq(JobEntity::getIsDelete,0));
+        if (!CollectionUtils.isEmpty(oldJob)) {
+            jobMap = oldJob.stream().collect(Collectors.toMap(JobEntity::getName, JobEntity::getId));
+        }
+
+        List<JobEntity> jobs = new ArrayList<>();
+        for (WxExcelOrgVo vo : vos) {
+            JobEntity job = oldJob.stream().filter(e ->vo.getJobName().equals(e.getName())).findFirst().orElse(null);
+            if (Objects.isNull(job)) {
+                job = new JobEntity();
+                job.setName(vo.getJobName());
+                jobs.add(job);
+                if (StrUtil.isNotBlank(vo.getJobName())) {
+                    jobService.saveOrUpdate(job);
+                    oldJob.add(job);
+                    jobMap.put(job.getName(),job.getId());
+                }
+            }
+        }
+
+
+        /**
+         * 人员处理
+         */
+        List<DeptEntity> oldDeptls = departService.list(new LambdaUpdateWrapper<DeptEntity>().eq(DeptEntity::getIsDelete,0));
+
+        Map<String,DeptEntity> deptMap = new HashMap();
+        if (!CollectionUtils.isEmpty(oldDeptls)) {
+            deptMap = oldDeptls.stream().collect(Collectors.toMap(DeptEntity::getDeptNameRoute, a -> a));
+        }
+        CompanyEntity company = new CompanyEntity();
+        SubCompanyEntity subCompany = new SubCompanyEntity();
+
+        /**
+         * 处理组织数据
+         */
+        int i = 1;
+        for (WxExcelOrgVo vo : vos) {
+            String[] ds = vo.getDeptName().split(";");
+            String[] deptstr = ds[0].split("/");
+            if (i == 1) {
+                company.setName(deptstr[0]);
+                company.setRouteCode("100");
+                company.setId(Long.valueOf(1));
+                subCompany.setName(deptstr[0]);
+                subCompany.setCompanyId(company.getId());
+                subCompany.setRouteCode("100,100");
+                subCompany.setId(Long.valueOf(1));
+                companyService.truncateTable();
+                companyService.save(company);
+
+                subCompanyService.truncateTable();
+                subCompanyService.save(subCompany);
+                i++;
+            }
+
+            String routeStr = deptstr[0];
+            for (int j = 1; j < deptstr.length; j++) {
+                routeStr = routeStr + "/" + deptstr[j];
+                String finalRouteStr = routeStr;
+                DeptEntity dept = oldDeptls.stream().filter(e -> e.getDeptNameRoute().equals(finalRouteStr)).findFirst().orElse(null);
+                if (Objects.isNull(dept)) {
+                    String topRoute = finalRouteStr.substring(0, StrUtil.ordinalIndexOf(finalRouteStr, "/", j));
+                    // 上一级
+                    DeptEntity topDept = oldDeptls.stream().filter(e -> e.getDeptNameRoute().equals(topRoute)).findFirst().orElse(null);
+                    DeptEntity newDept = new DeptEntity();
+                    if (Objects.nonNull(topDept)) {
+                        DeptEntity nextD = oldDeptls.stream().filter(e -> e.getDeptNameRoute().contains(topRoute) && !e.getDeptNameRoute().equals(topRoute)).collect(Collectors.toList()).stream().max(Comparator.comparing(s -> s.getRouteCode())).orElse(null);
+                        if (Objects.isNull(nextD)) {
+                            newDept.setRouteCode(topDept.getRouteCode() + ",100");
+                        } else {
+                            String[] routeCodes = nextD.getRouteCode().split(",");
+                            String lastCode = routeCodes[routeCodes.length - 1];
+                            lastCode = String.valueOf(Integer.valueOf(lastCode) + 1);
+                            routeCodes[routeCodes.length - 1] = lastCode;
+                            String routeCode = StringUtils.join(routeCodes, ",");
+                            newDept.setRouteCode(routeCode);
+                        }
+                        newDept.setCompanyId(topDept.getCompanyId());
+                        newDept.setSubCompanyId(topDept.getSubCompanyId());
+                        newDept.setDeptNameRoute(routeStr);
+                        newDept.setPid(topDept.getId());
+                        newDept.setName(deptstr[j]);
+                        departService.saveOrUpdate(newDept);
+                        deptMap.put(newDept.getDeptNameRoute(), newDept);
+                        oldDeptls.add(newDept);
+                    } else {
+                        newDept.setPid(Long.valueOf(0));
+                        newDept.setRouteCode(subCompany.getRouteCode() + ",100");
+                        newDept.setDeptNameRoute(subCompany.getName() + "/"+deptstr[j]);
+                        newDept.setCompanyId(company.getId());
+                        newDept.setSubCompanyId(subCompany.getId());
+                        newDept.setName(deptstr[j]);
+                        departService.saveOrUpdate(newDept);
+                        deptMap.put(newDept.getDeptNameRoute(), newDept);
+                        oldDeptls.add(newDept);
+
+                    }
+
+                }
+            }
+        }
+
+
+
+        List<UserEntity> oldUsers = userService.list(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getIsDelete,0));
+        List<UserEntity> users = new ArrayList<>();
+        String defaultPassWord = SystemConfigItemCacheUtil.getDefaultPassWord();
+        for (WxExcelOrgVo vo : vos) {
+            UserEntity oldInfo = oldUsers.stream().filter(e ->e.getMobile().equals(vo.getMobile())).findFirst().orElse(null);
+            if (Objects.nonNull(oldInfo)) {
+                continue;
+            }
+            UserEntity user = new UserEntity();
+            user.setUserName(vo.getRealName());
+            user.setSex(vo.getSex());
+            user.setNickName(vo.getNickName());
+            user.setRealName(vo.getRealName());
+            user.setXworkUserId(vo.getXworkUserId());
+            user.setEmail(vo.getEmail());
+            user.setPassword(passwordEncoder.encode(defaultPassWord));
+            user.setJobId(jobMap.get(vo.getJobName()));
+            user.setId(Long.valueOf(i));
+            user.setStatus("1");
+            String[] ds = vo.getDeptName().split(";");
+            String deptRouteName = ds[0];
+            DeptEntity wxVo = deptMap.get(deptRouteName);
+            if (Objects.nonNull(wxVo)) {
+                DeptEntity d = wxVo;
+                user.setSubCompanyId(d.getSubCompanyId());
+                user.setCompanyId(d.getCompanyId());
+                user.setDeptId(d.getId());
+                user.setOrgRouteCode(d.getRouteCode());
+            }
+            user.setMobile(vo.getMobile());
+            users.add(user);
+        }
+        userService.saveBatch(users);
+        userRoleService.initUserRole();
+
+        return Result.ok();
+    }
+}

+ 165 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/PermissionResApiController.java

@@ -0,0 +1,165 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.common.util.PingyinUtil;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.system.pojo.entity.MenuEntity;
+import com.storlead.system.pojo.vo.MetaVo;
+import com.storlead.system.pojo.vo.RouterVo;
+import com.storlead.system.pojo.vo.UserMenuVO;
+import com.storlead.system.service.IMenuService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import javax.annotation.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.*;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-09 10:02
+ */
+
+@Api(tags = "系统: 权限菜单")
+@Log4j2
+@RestController
+@RequestMapping("/sys/permission")
+public class PermissionResApiController {
+
+    @Resource
+    private IMenuService menuService;
+    private static final String PARENT_VIEW = "ParentView";
+    private static final String LAYOUT = "Layout";
+
+    @ApiOperation("获取用户拥有的全部菜单,权限")
+    @PostMapping("getUserAccessResource")
+    public Result getUserMenuList() {
+        try {
+            Map<String,Object> map = new HashMap();
+            Long currentUserId = LoginUserUtil.getCurrentUserId();
+            List<MenuEntity> userMenuList = menuService.getUserMenuList(currentUserId,Arrays.asList(0),Arrays.asList(1,2));
+            List<UserMenuVO> menuls = new ArrayList();
+//            List<MenuEntity> menuBtnls = new ArrayList();
+//            List<MenuEntity> quickMenuls = new ArrayList();
+//            List<Long> qmIds = quickMenuService.listByMap(Map.of("user_id",currentUserId)).stream().map(QuickMenuEntity::getMenuId).collect(Collectors.toList());
+
+            userMenuList.forEach(menu -> {
+                UserMenuVO menuVo = new UserMenuVO();
+                BeanUtils.copyProperties(menu,menuVo);
+                menuls.add(menuVo);
+            });
+            if (!CollectionUtils.isEmpty(menuls)) {
+                menuls.forEach(e -> {
+                    if (StrUtil.isNotBlank(e.getMenuName())) {
+                        String pyIcon = PingyinUtil.getLowFullSpell(e.getMenuName());
+                        e.setIcon(pyIcon);
+                    }
+                });
+            }
+            List<UserMenuVO>  menuRes = createTreeMenus(Long.valueOf(0),menuls);
+            List<RouterVo> treeRouter = new ArrayList<>();
+            menuToRouter(menuRes, null, treeRouter);
+            map.put("routerVo",treeRouter);
+            return Result.ok(map);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+
+
+    @ApiOperation("获取用户拥有的全部菜单,权限")
+    @PostMapping("getUserAccessResourceTest")
+    public Result getUserMenuListTest() {
+        try {
+            Map<String,Object> map = new HashMap();
+
+            List<MenuEntity> userMenuList = menuService.getAllMenuCreateTree();
+            List<UserMenuVO> menuls = new ArrayList();
+//            List<MenuEntity> menuBtnls = new ArrayList();
+//            List<MenuEntity> quickMenuls = new ArrayList();
+//            List<Long> qmIds = quickMenuService.listByMap(Map.of("user_id",currentUserId)).stream().map(QuickMenuEntity::getMenuId).collect(Collectors.toList());
+
+            userMenuList.forEach(menu -> {
+                UserMenuVO menuVo = new UserMenuVO();
+                BeanUtils.copyProperties(menu,menuVo);
+                menuls.add(menuVo);
+            });
+            if (!CollectionUtils.isEmpty(menuls)) {
+                menuls.forEach(e -> {
+                    if (StrUtil.isNotBlank(e.getMenuName())) {
+                        String pyIcon = PingyinUtil.getLowFullSpell(e.getMenuName());
+                        e.setIcon(pyIcon);
+                    }
+                });
+            }
+            List<UserMenuVO>  menuRes = createTreeMenus(Long.valueOf(0),menuls);
+            return Result.ok(menuRes);
+        }catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取菜单信息错误:"+e.getMessage());
+        }
+    }
+    /**
+     * 是否为parent_view组件
+     */
+
+    private void menuToRouter(List<UserMenuVO> menus, RouterVo parent,  List<RouterVo> treeRouter){
+
+        for (UserMenuVO menu : menus) {
+            RouterVo routerVo = new RouterVo() ;
+            MetaVo metaVo = new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getIsCache() == 0 ? true : false, menu.getId());
+            if(menu.getPid() != 0L && menu.getMenuType() == 1 && StringUtils.isBlank(menu.getComponent()) ) {
+                routerVo.setComponent(PARENT_VIEW);
+                routerVo.setAlwaysShow(true);
+            } else if((menu.getPid() == null || menu.getPid() == 0L) && menu.getMenuType() == 1 && StringUtils.isBlank(menu.getComponent()) ){
+                routerVo.setComponent(LAYOUT);
+                routerVo.setAlwaysShow(true);
+            } else {
+                routerVo.setComponent(menu.getComponent());
+            }
+
+            routerVo.setHidden(menu.getVisible());
+            routerVo.setMeta(metaVo);
+            routerVo.setTitle(menu.getMenuName());
+            routerVo.setName(menu.getRouterName());
+            routerVo.setPath(menu.getPath());
+            if(parent != null) {
+                parent.getChildren().add(routerVo);
+            } else {
+                routerVo.setAlwaysShow(true);
+                treeRouter.add(routerVo);
+            }
+            if(menu.getChildren()!=null && menu.getChildren().size() > 0) {
+                List<RouterVo> treeRouterChild = new ArrayList<>();
+                routerVo.setChildren(treeRouterChild);
+                menuToRouter(menu.getChildren(), routerVo, treeRouterChild);
+            }
+        }
+    }
+    /**
+     * 递归生成菜单树
+     */
+    private List<UserMenuVO> createTreeMenus(Long pid, List<UserMenuVO> menus) {
+        List<UserMenuVO> treeMenu = new ArrayList<>();
+        for (UserMenuVO menu : menus) {
+            if (pid.equals(menu.getPid())) {
+                treeMenu.add(menu);
+                List<UserMenuVO> childs =  createTreeMenus(menu.getId(), menus);
+                if (childs != null && childs.size() > 0) {
+                    menu.setChildren(childs);
+                }
+            }
+        }
+        return treeMenu;
+    }
+}

+ 163 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/QuickMenuApiController.java

@@ -0,0 +1,163 @@
+package com.storlead.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.system.pojo.dto.QuickMenuDTO;
+import com.storlead.system.pojo.entity.MenuEntity;
+import com.storlead.system.pojo.entity.QuickMenuEntity;
+import com.storlead.system.pojo.vo.UserMenuVO;
+import com.storlead.system.service.IMenuService;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.system.service.QuickMenuService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanUtils;
+import javax.annotation.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-08 17:14
+ */
+@Api(tags = "系统-快捷菜单: 快捷菜单设置")
+@RestController
+@RequestMapping("/sys/quick/menu")
+public class QuickMenuApiController {
+
+    @Resource
+    private QuickMenuService quickMenuService;
+
+    @Resource
+    private IMenuService menuService;
+
+    @Resource
+    private IUserRoleService userRoleService;
+
+    @ApiOperation("保存角色")
+    @PostMapping("/save")
+    public Result create(@RequestBody QuickMenuDTO dto) {
+
+        if (CollectionUtils.isEmpty(dto.getMenuIdls())) {
+            return Result.error("参数错误");
+        }
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        quickMenuService.removeByMap(Map.of("user_id",currentUserId));
+        List<QuickMenuEntity> quickMenuls = new ArrayList<>();
+        dto.getMenuIdls().forEach(mId -> {
+            QuickMenuEntity quickMenu = new QuickMenuEntity();
+            quickMenu.setMenuId(mId);
+            quickMenu.setUserId(currentUserId);
+            quickMenu.setRangeType(dto.getRangeType());
+            quickMenuls.add(quickMenu);
+        });
+        quickMenuService.saveBatch(quickMenuls);
+        return Result.ok();
+    }
+
+    @ApiOperation("获取权限资源")
+    @PostMapping("/listMenus")
+    public Result listMenus() {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        List<MenuEntity> userMenuList = menuService.getUserMenuList(currentUserId,Arrays.asList(0,1), Arrays.asList(1,2));
+
+        LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.likeRight(MenuEntity::getMenuCode,"KJCD");
+        List<MenuEntity> args = menuService.list(queryWrapper);
+        if (!CollectionUtils.isEmpty(args)) {
+            userMenuList.addAll(args);
+        }
+        List<UserMenuVO> menuls = new ArrayList();
+        userMenuList.forEach(menu -> {
+            UserMenuVO menuVo = new UserMenuVO();
+            BeanUtils.copyProperties(menu,menuVo);
+            menuls.add(menuVo);
+        });
+        List<UserMenuVO>  menuRes = createTreeMenus(Long.valueOf(0),menuls);
+        return Result.ok(menuRes);
+    }
+
+    private List<UserMenuVO> createTreeMenus(Long pid, List<UserMenuVO> menus) {
+        List<UserMenuVO> treeMenu = new ArrayList<>();
+        for (UserMenuVO menu : menus) {
+            if (pid.equals(menu.getPid())) {
+                treeMenu.add(menu);
+                List<UserMenuVO> childs =  createTreeMenus(menu.getId(), menus);
+                if (childs != null && childs.size() > 0) {
+                    menu.setChildren(childs);
+                }
+            }
+        }
+        return treeMenu;
+    }
+
+    @ApiOperation("查询快捷方式")
+    @PostMapping("/quickMenus")
+    public Result quickMenus() {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        /**
+         * 根据用户id查询出当前用户的快捷菜单,并且转换成List对象集合
+         */
+        List<Long> alertMenuList;
+        LambdaQueryWrapper<MenuEntity> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.likeRight(MenuEntity::getMenuCode,"KJCD");
+        List<MenuEntity> args = menuService.list(queryWrapper);
+        if (!CollectionUtils.isEmpty(args)) {
+            alertMenuList = args.stream().map(MenuEntity::getId).collect(Collectors.toList());
+        } else {
+            alertMenuList = new ArrayList<>();
+        }
+
+        List<MenuEntity> allMenuList = menuService.list();
+        Map<Long, String> menuPathMap = allMenuList.stream()
+                .collect(Collectors.toMap(MenuEntity::getId, MenuEntity::getPath));
+
+        Map<Long, Long> menuPidMap = allMenuList.stream()
+                .collect(Collectors.toMap(MenuEntity::getId, MenuEntity::getPid));
+
+        List<QuickMenuEntity> quickMenuls = quickMenuService.list(new LambdaQueryWrapper<QuickMenuEntity>().eq(QuickMenuEntity::getUserId,currentUserId));
+        if(!CollectionUtils.isEmpty(quickMenuls)) {
+            List<Long> menuIds = quickMenuls.stream().map(QuickMenuEntity::getMenuId).collect(Collectors.toList());
+            List<MenuEntity> userMenuList = menuService.list(new LambdaQueryWrapper<MenuEntity>().in(MenuEntity::getId,menuIds));
+            if (!CollectionUtils.isEmpty(userMenuList)) {
+                List<UserMenuVO> menuls = new ArrayList();
+                List<Long> finalAlertMenuList = alertMenuList;
+                userMenuList.forEach(menu -> {
+                    UserMenuVO menuVo = new UserMenuVO();
+                    if (finalAlertMenuList.contains(menu.getId())) {
+                        menuVo.setOpenType(1);
+                    } else {
+                        menuVo.setOpenType(0);
+                    }
+                    BeanUtils.copyProperties(menu,menuVo);
+                    appendMenuPath(menuVo,menuPathMap,menu.getPid(),menuPidMap);
+                    menuls.add(menuVo);
+                });
+                return Result.result(menuls);
+            }
+        }
+        return Result.result(null);
+    }
+
+    public void appendMenuPath(UserMenuVO menuVO,Map<Long,String> menuPathMap,Long pid,Map<Long,Long> menuPidMap) {
+        if (Objects.nonNull(menuPidMap.get(pid))) {
+            String appendPath = menuPathMap.get(pid);
+            pid = menuPidMap.get(pid);
+            String path = appendPath+"/"+menuVO.getPath();
+            path = path.replaceAll("//", "/");
+            menuVO.setPath(path);
+            appendMenuPath(menuVO,menuPathMap,pid,menuPidMap);
+        }
+    }
+}

+ 294 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/RoleApiController.java

@@ -0,0 +1,294 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.pojo.entity.RoleEntity;
+import com.storlead.system.pojo.entity.RoleMenuEntity;
+import com.storlead.system.pojo.entity.UserRoleEntity;
+import com.storlead.system.pojo.vo.RoleMenuDTO;
+import com.storlead.system.service.DeptRoleService;
+import com.storlead.system.service.IRoleMenuService;
+import com.storlead.system.service.IRoleService;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.user.pojo.dto.RoleParam;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.service.IUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import javax.annotation.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2022-05-20 10:11
+ */
+@Api(tags = "系统: 角色管理")
+@RestController
+@RequestMapping("/sys/role/manage")
+public class RoleApiController {
+    @Resource
+    private IRoleService roleService;
+
+    @Resource
+    private IUserService userService;
+
+    @Resource
+    private IUserRoleService userRoleService;
+
+    @Resource
+    private IRoleMenuService roleMenuService;
+
+    @Resource
+    private DeptRoleService deptRoleService;
+//    @Resource
+//    private SysRoleResourceEngineService roleResourceEngineService;
+
+
+    @ApiOperation("角色分页")
+    @PostMapping("/pagelist")
+    public Result<?> list(@RequestBody RoleParam param) {
+        IPage<RoleEntity> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
+        if (StrUtil.isNotBlank(param.getName())) {
+            queryWrapper.likeRight("name",param.getName());
+        }
+        IPage<RoleEntity> roles = roleService.page(page,queryWrapper);
+        if (!CollectionUtils.isEmpty(roles.getRecords())) {
+            setRoleUserNname(roles.getRecords());
+        }
+        return Result.ok(roles);
+    }
+
+    @ApiOperation("保存角色")
+    @PostMapping("/save")
+    public Result create(@RequestBody RoleParam param) {
+
+        RoleEntity info = new RoleEntity();
+        BeanUtils.copyProperties(param, info);
+
+        if (Objects.isNull(info) || StrUtil.isBlank(info.getName())) {
+            return Result.error("参数错误");
+        }
+        if (Objects.isNull(info.getId())) {
+            Integer exit = roleService.count(new LambdaQueryWrapper<RoleEntity>().eq(RoleEntity::getName,info.getName()));
+            if (exit > 0) {
+                return Result.error("角色'"+ info.getName() +"'已存在!");
+            }
+        } else {
+            RoleEntity oldAccount = roleService.getOne(new LambdaQueryWrapper<RoleEntity>().eq(RoleEntity::getName,info.getName()));
+            if (Objects.nonNull(oldAccount)) {
+                if (!oldAccount.getId().equals(info.getId())) {
+                    return Result.error("角色'"+ info.getName() +"'已存在,无法修改!");
+                }
+            }
+        }
+
+        Boolean b = roleService.saveOrUpdate(info);
+        if (b) {
+            roleMenuService.updateRoleMenu(info.getId(),param.getMenus(),param.getIsApp());
+            deptRoleService.updateDeptRole(info.getId(),param.getDepts());
+        }
+        return Result.ok();
+    }
+    @ApiOperation("启用禁用员工")
+    @PostMapping("enable")
+    public Result setEnable(Long id,Boolean enable) {
+        LambdaUpdateWrapper<RoleEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(RoleEntity::getEnabled,enable);
+        updateWrapper.eq(RoleEntity::getId,id);
+        roleService.update(updateWrapper);
+        return Result.ok();
+    }
+
+    @ApiOperation("删除角色")
+    @PostMapping("/delete")
+    public Result<?> remove(Long [] ids) {
+        if (Objects.isNull(ids) || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        Boolean result = roleService.removeByIds(Arrays.asList(ids));
+        if(result) {
+            userRoleService.remove(new LambdaQueryWrapper<UserRoleEntity>().in(UserRoleEntity::getRoleId, Arrays.asList(ids)));
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation("关联角色用户")
+    @PostMapping("/bindRoleUser")
+    public Result<?> bindRoleUser(Long rid,Long [] uids) {
+        if (Objects.isNull(rid)) {
+            return Result.error("参数错误");
+        }
+        LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<UserRoleEntity>().eq(UserRoleEntity::getRoleId,rid);
+        userRoleService.remove(lambdaQueryWrapper);
+        if (Objects.nonNull(uids)) {
+            List<UserRoleEntity> urs = new ArrayList<>();
+            for (Long uid : Arrays.asList(uids)) {
+                UserRoleEntity userRole = new UserRoleEntity();
+                userRole.setUserId(uid);
+                userRole.setRoleId(rid);
+                urs.add(userRole);
+                if (Long.valueOf(1).equals(rid)) {
+                    UserRoleEntity adminRole = new UserRoleEntity();
+                    adminRole.setUserId(1L);
+                    adminRole.setRoleId(rid);
+                    urs.add(adminRole);
+                }
+            }
+            if (!CollectionUtils.isEmpty(urs)) {
+                userRoleService.saveBatch(urs);
+            }
+        }
+        return Result.ok();
+    }
+
+
+    @ApiOperation("关联角色菜单")
+    @PostMapping("/bind_role_menu")
+    public Result<?> bindRoleMenu(RoleMenuDTO dto) {
+        if (Objects.isNull(dto.getRoleId())) {
+            return Result.error("参数错误");
+        }
+        if (Objects.isNull(dto.getIsApp())) {
+            dto.setIsApp(0);
+        }
+        LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<RoleMenuEntity>().eq(RoleMenuEntity::getRoleId,dto.getRoleId()).eq(RoleMenuEntity::getIsApp,dto.getIsApp());
+        roleMenuService.remove(lambdaQueryWrapper);
+        if (!CollectionUtils.isEmpty(dto.getMenuls())) {
+            List<RoleMenuEntity> urs = new ArrayList<>();
+            for (Long menuId : dto.getMenuls()) {
+                RoleMenuEntity roleMenu = new RoleMenuEntity();
+                roleMenu.setRoleId(dto.getRoleId());
+                roleMenu.setMenuId(menuId);
+                roleMenu.setIsApp(dto.getIsApp());
+                urs.add(roleMenu);
+            }
+            if (!CollectionUtils.isEmpty(urs)) {
+                roleMenuService.saveBatch(urs);
+            }
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation("设置用户角色")
+    @PostMapping("/setUserRole")
+    public Result<?> bindUserRole(Long uid,Long [] rids) {
+        if (Objects.isNull(rids) || Objects.isNull(uid)) {
+            return Result.error("参数错误");
+        }
+        LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<UserRoleEntity>().eq(UserRoleEntity::getUserId,uid);
+        userRoleService.remove(lambdaQueryWrapper);
+
+        List<UserRoleEntity> urs = new ArrayList<>();
+        Arrays.asList(rids).forEach(rid -> {
+            UserRoleEntity userRole = new UserRoleEntity();
+            userRole.setUserId(uid);
+            userRole.setRoleId(rid);
+            urs.add(userRole);
+        });
+
+        if (!CollectionUtils.isEmpty(urs)) {
+            userRoleService.saveBatch(urs);
+        }
+        return Result.ok();
+    }
+
+
+    @ApiOperation("绑定部门")
+    @PostMapping("/bindDeptRole")
+    public Result<?> bindDeptRole(@RequestBody RoleParam param) {
+        deptRoleService.updateDeptRole(param.getId(),param.getDepts());
+        return Result.ok();
+    }
+
+
+    @ApiOperation("关联角色菜单")
+    @PostMapping("/bindMenuRole")
+    public Result<?> bindMenuRole(Long [] rids,Long [] menus) {
+        if (Objects.isNull(rids)) {
+            return Result.error("参数错误");
+        }
+        LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<RoleMenuEntity>().in(RoleMenuEntity::getRoleId,Arrays.asList(rids));
+        roleMenuService.remove(lambdaQueryWrapper);
+        if (Objects.nonNull(rids) && rids.length > 0) {
+            List<RoleMenuEntity> rms = new ArrayList<>();
+            for (Long rid : Arrays.asList(rids)) {
+                Arrays.asList(menus).forEach(m -> {
+                    RoleMenuEntity roleMenu = new RoleMenuEntity();
+                    roleMenu.setRoleId(rid);
+                    roleMenu.setMenuId(m);
+                    rms.add(roleMenu);
+                });
+            }
+            if (!CollectionUtils.isEmpty(rms)) {
+               roleMenuService.saveBatch(rms);
+            }
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation("根据角色查询菜单id")
+    @PostMapping("/getRoleMenuId")
+    public Result<?> getRoleMenuId(Long roleId) {
+        return Result.ok(roleService.getMenuIdsByRoleId(roleId,0));
+    }
+
+    @ApiOperation("根据角色查询菜单id")
+    @PostMapping("/getH5RoleMenuId")
+    public Result<?> getH5RoleMenuId(Long roleId) {
+        return Result.ok(roleService.getMenuIdsByRoleId(roleId,1));
+    }
+
+    /**
+     * 根据角色id查询部门id*
+     * @param roleId
+     */
+    @ApiOperation("根据角色查询组织id")
+    @PostMapping("/getRoleDeptId")
+    public Result<?> getRoleDeptId(Long roleId) {
+        return Result.ok(roleService.getdeptIdsByRoleId(roleId));
+    }
+
+    private void setRoleUserNname(List<RoleEntity> roles){
+        if (!CollectionUtils.isEmpty(roles)) {
+            List<Long> roleIds = new ArrayList<>();
+            roles.forEach(user -> {
+                roleIds.add(user.getId());
+            });
+            Map<Long,List<UserRoleEntity>> userRoles = userRoleService.list(new LambdaQueryWrapper<UserRoleEntity>().in(UserRoleEntity::getRoleId,roleIds)).stream().collect(Collectors.groupingBy(UserRoleEntity::getRoleId));
+            Map<Long,List<Long>> usMap = new HashMap<>();
+            for (Map.Entry<Long,List<UserRoleEntity>> entry : userRoles.entrySet()) {
+                usMap.put(entry.getKey(),entry.getValue().stream().map(UserRoleEntity::getUserId).collect(Collectors.toList()));
+            }
+            List<UserEntity> users = userService.list();
+            if (!CollectionUtils.isEmpty(roles) && Objects.nonNull(usMap)) {
+                roles.forEach(user -> {
+                    List<Long> userIds = usMap.get(user.getId());
+                    if (!CollectionUtils.isEmpty(userIds)) {
+                        List<String> unames = users.stream().filter(s -> userIds.contains(s.getId())).map(UserEntity::getRealName).collect(Collectors.toList());
+                        user.setUserNames(StringUtils.join(unames,","));
+                        user.setUserIds(userIds);
+                    }
+                });
+            }
+
+        }
+    }
+
+}

+ 161 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SubCompanyApiController.java

@@ -0,0 +1,161 @@
+package com.storlead.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.dto.CompanyDTO;
+import com.storlead.system.util.SystemSettingCacheCheckUtil;
+import com.storlead.user.pojo.entity.CompanyEntity;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.SubCompanyEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.service.ICompanyService;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.ISubCompanyService;
+import com.storlead.user.service.IUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-06-29 17:42
+ */
+
+
+@Api(tags = "系统: 子公司数据维护")
+@RestController
+@RequestMapping("/sys/subcompany")
+public class SubCompanyApiController {
+
+    @Resource
+    private ISubCompanyService subCompanyService;
+
+    @Resource
+    private IDepartService departService;
+
+    @Resource
+    private ICompanyService companyService;
+
+    @Resource
+    private IUserService userService;
+
+
+    @ApiOperation("新增子公司")
+    @PostMapping("save")
+    public Result save(SubCompanyEntity subCompany) {
+        Result r = SystemSettingCacheCheckUtil.checkAddDataMode();
+        if (!r.isSuccess()) {
+            return r;
+        }
+        if(Objects.isNull(subCompany.getId())) {
+            subCompany.setDataSource(1);
+
+            LambdaQueryWrapper<CompanyEntity> lm = new LambdaQueryWrapper();
+            lm.eq(CompanyEntity::getId,subCompany.getCompanyId());
+            lm.last("limit 1");
+            CompanyEntity company = companyService.getOne(lm);
+            if (Objects.isNull(company)) {
+                return Result.error("公司需要绑定在总公司下面,请绑定总公司");
+            }
+            LambdaQueryWrapper<SubCompanyEntity> subQWrapper = new LambdaQueryWrapper();
+            subQWrapper.eq(SubCompanyEntity::getCompanyId,subCompany.getCompanyId());
+            subQWrapper.orderByDesc(SubCompanyEntity::getRouteCode);
+            subQWrapper.last("limit 1");
+            SubCompanyEntity subCompany1 = subCompanyService.getOne(subQWrapper);
+            if (Objects.isNull(subCompany1)) {
+                subCompany.setRouteCode(company.getRouteCode()+",100");
+            }else {
+                String [] routeCodes = subCompany1.getRouteCode().split(",");
+                String lastCode = routeCodes[routeCodes.length-1];
+                lastCode = String.valueOf(Integer.valueOf(lastCode)+1);
+                routeCodes[routeCodes.length-1] = lastCode;
+                String routeCode = StringUtils.join(routeCodes,",");
+                subCompany.setRouteCode(routeCode);
+            }
+        }
+        subCompanyService.saveOrUpdate(subCompany);
+        return Result.ok();
+    }
+
+    @ApiOperation("分页查询子公司")
+    @PostMapping("pagelist")
+    public Result save(CompanyDTO param) {
+        IPage<SubCompanyEntity> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        IPage<SubCompanyEntity> companys = subCompanyService.page(page);
+        return Result.ok(companys);
+    }
+
+    @ApiOperation("删除子公司信息")
+    @PostMapping("delete")
+    public Result delete(Long [] ids) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        Integer exist = departService.count(new LambdaQueryWrapper<DeptEntity>().in(DeptEntity::getSubCompanyId, Arrays.asList(ids)));
+        if (!Objects.equals(exist,Integer.valueOf(0))) {
+            return Result.error("公司有关联的部门,无法删除!");
+        }
+        subCompanyService.removeByIds(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+    @ApiOperation("设置子公司负责人")
+    @PostMapping("setLeaderId")
+    public Result setLeaderId(Integer subCompanyId,Long leaderId) {
+        if (Objects.isNull(subCompanyId)) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<SubCompanyEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.eq(SubCompanyEntity::getId,subCompanyId);
+        UserEntity user = userService.getById(leaderId);
+        if (Objects.nonNull(user)) {
+            updateWrapper.set(SubCompanyEntity::getLeaderId,leaderId);
+            updateWrapper.set(SubCompanyEntity::getLeaderName,user.getRealName());
+        } else {
+            updateWrapper.set(SubCompanyEntity::getLeaderId,null);
+            updateWrapper.set(SubCompanyEntity::getLeaderName,"");
+        }
+        SubCompanyEntity subCompany = subCompanyService.getById(subCompanyId);
+        Boolean b =  subCompanyService.update(updateWrapper);
+        if (b) {
+            if (Objects.nonNull(subCompany)) {
+                if (!leaderId.equals(subCompany.getLeaderId())) {
+                    LambdaQueryWrapper subCompanyLq =  new LambdaQueryWrapper<SubCompanyEntity>().eq(SubCompanyEntity::getLeaderId,subCompany.getLeaderId()).ne(SubCompanyEntity::getId,subCompanyId);
+                    Integer scCount = subCompanyService.count(subCompanyLq);
+                    if (scCount == 0) {
+                        LambdaUpdateWrapper<UserEntity> rmLeaderUpdate = new LambdaUpdateWrapper();
+                        LambdaQueryWrapper userDeptLq =  new LambdaQueryWrapper<DeptEntity>().eq(DeptEntity::getManagerId,subCompany.getLeaderId());
+                        Integer c = departService.count(userDeptLq);
+                        if (c > 0) {
+                            rmLeaderUpdate.set(UserEntity::getIsLeader,Integer.valueOf(2));
+                        } else {
+                            rmLeaderUpdate.set(UserEntity::getIsLeader,Integer.valueOf(0));
+                        }
+                        rmLeaderUpdate.eq(UserEntity::getId,subCompany.getLeaderId());
+                        userService.update(rmLeaderUpdate);
+                    }
+                }
+            }
+            LambdaUpdateWrapper<UserEntity> userUpdate = new LambdaUpdateWrapper();
+            userUpdate.set(UserEntity::getIsLeader,Integer.valueOf(1));
+            userUpdate.eq(UserEntity::getId,leaderId);
+            userService.update(userUpdate);
+        }
+        return Result.ok();
+    }
+
+}
+

+ 81 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SubCompanyController.java

@@ -0,0 +1,81 @@
+package com.storlead.system.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.storlead.framework.common.result.Result;
+import com.storlead.user.pojo.entity.SubCompanyEntity;
+import com.storlead.user.service.ISubCompanyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("/lingcun")
+@Api(tags="5.公共接口")
+public class SubCompanyController {
+
+    @Resource
+    private ISubCompanyService subCompanyService;
+
+    @GetMapping(value = "/subCompany/list")
+    @ApiOperation(value = "公共接口-分公司-列表", notes = "公共接口-分公司-列表")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SubCompanyEntity.class)
+    })
+    public Result<?> list() {
+        // Step.1 组装查询条件
+        QueryWrapper<SubCompanyEntity> queryWrapper = new QueryWrapper<SubCompanyEntity>();
+        queryWrapper.select("id,company_id,`desc`,name,sort,enabled");
+        queryWrapper.eq("enabled", 1);
+        queryWrapper.orderByAsc("sort");
+        List<SubCompanyEntity> list = subCompanyService.list(queryWrapper);
+        return Result.ok(list);
+    }
+
+    @GetMapping(value = "/subCompany/listHasAllCompany")
+    @ApiOperation(value = "公共接口-分公司-包含所有公司列表", notes = "公共接口-分公司-包含所有公司列表")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SubCompanyEntity.class)
+    })
+    public Result<?> listHasAllCompany() {
+        List<SubCompanyEntity> list = new ArrayList<>();
+        SubCompanyEntity subCompany = new SubCompanyEntity();
+        subCompany.setId(0L);
+        subCompany.setName("所有公司");
+        subCompany.setDesc("所有公司");
+        list.add(subCompany);
+        // Step.1 组装查询条件
+        QueryWrapper<SubCompanyEntity> queryWrapper = new QueryWrapper<SubCompanyEntity>();
+        queryWrapper.select("id,company_id,`desc`,name,sort,enabled");
+        queryWrapper.eq("enabled", 1);
+        queryWrapper.orderByAsc("sort");
+        List<SubCompanyEntity> subCompanyEntityList = subCompanyService.list(queryWrapper);
+        list.addAll(subCompanyEntityList);
+        return Result.ok(list);
+    }
+    @ApiOperation("启用禁用部门")
+    @PostMapping("/su_company/enable")
+    public Result setEnable(Long id,Boolean enable) {
+        LambdaUpdateWrapper<SubCompanyEntity> update = new LambdaUpdateWrapper();
+        update.in(SubCompanyEntity::getId, Arrays.asList(id));
+        update.set(SubCompanyEntity::getEnabled,enable);
+        Boolean re = subCompanyService.update(update);
+        if (re) {
+            return Result.ok();
+        } else {
+            return Result.error("操作失败");
+        }
+    }
+
+}

+ 174 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SysDictApiController.java

@@ -0,0 +1,174 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.pojo.dto.SysDictDTO;
+import com.storlead.system.pojo.entity.SysDictDetailEntity;
+import com.storlead.system.pojo.entity.SysDictEntity;
+import com.storlead.system.pojo.entity.SystemPersonalizeSettingEntity;
+import com.storlead.system.service.SysDictDetailService;
+import com.storlead.system.service.SysDictService;
+import io.swagger.annotations.*;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+/**
+ * <p>
+ * 字典组 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2022-09-09
+ */
+@RestController
+@RequestMapping("/sys/dict")
+@Api(tags="101:系统数据字典")
+public class SysDictApiController {
+
+    @Resource
+    private SysDictService sysDictService;
+
+    @Resource
+    private SysDictDetailService sysDictDetailService;
+
+    @PostMapping(value = "/saveDict")
+    @ApiOperation(value = "保存字典组", notes = "保存字典组")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result saveDict(SysDictEntity sysDict) {
+        if (StrUtil.isBlank(sysDict.getCode())) {
+            return Result.error("参数错误,字典编码不能为空");
+        }
+        SysDictEntity entity = sysDictService.getOne(new LambdaQueryWrapper<SysDictEntity>().eq(SysDictEntity::getCode,sysDict.getCode()));
+        if (Objects.nonNull(entity)) {
+            if (Objects.isNull(sysDict.getDictId())) {
+                return Result.error("字典"+sysDict.getCode()+"已被占用,请修改编码!");
+            }else {
+                if (!entity.getDictId().equals(sysDict.getDictId())) {
+                    return Result.error("字典"+sysDict.getCode()+"已被占用编码!");
+                }
+            }
+        }
+        sysDictService.saveOrUpdate(sysDict);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/saveDictDetail")
+    @ApiOperation(value = "保存字典详情", notes = "保存字典详情")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "")
+    })
+    public Result saveDictDetail(SysDictDetailEntity Detail) {
+        sysDictDetailService.saveOrUpdate(Detail);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/pageDict")
+    @ApiOperation(value = "查询字典配置数据", notes = "查询字典配置数据")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "")
+    })
+    public Result pageDict(SysDictDTO dto) {
+        Page<SysDictEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<SysDictEntity> queryWrapper = new LambdaQueryWrapper();
+        if (StrUtil.isNotBlank(dto.getCode()) && StrUtil.isNotBlank(dto.getName())) {
+            queryWrapper.eq(SysDictEntity::getCode, dto.getCode()).or().like(SysDictEntity::getName, dto.getName());
+        } else {
+            if (StrUtil.isNotBlank(dto.getCode())) {
+                queryWrapper.eq(SysDictEntity::getCode, dto.getCode());
+            } else if (StrUtil.isNotBlank(dto.getName())) {
+                queryWrapper.like(SysDictEntity::getName, dto.getName());
+            }
+        }
+        if (Objects.nonNull(dto.getDictType())) {
+            queryWrapper.eq(SysDictEntity::getDictType, dto.getDictType());
+        }
+        queryWrapper.like(SysDictEntity::getIsDelete, Integer.valueOf(0));
+        IPage<SysDictEntity> vos = sysDictService.page(page,queryWrapper);
+        return Result.ok(vos);
+    }
+
+    @PostMapping(value = "/listDetialByDictId")
+    @ApiOperation(value = "获取字典详情", notes = "获取指定字典详情")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "")
+    })
+    public Result lisDetialByDictId(SysDictDTO dto) {
+        if (Objects.isNull(dto.getDictId())) {
+            return Result.error("查询参数错误!");
+        }
+        Page<SysDictDetailEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<SysDictDetailEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(SysDictDetailEntity::getDictId,dto.getDictId());
+        queryWrapper.eq(SysDictDetailEntity::getIsDelete,Integer.valueOf(0));
+//      queryWrapper.eq(SysDictDetailEntity::getEnabled,1);
+        IPage<SysDictDetailEntity> vos = sysDictDetailService.page(page,queryWrapper);
+        return Result.ok(vos);
+    }
+
+    @PostMapping(value = "/listDetialByCodes")
+    @ApiOperation(value = "获取字典组", notes = "获取字典组")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SysDictDetailEntity.class)
+    })
+    public Result lisDetialByCodes(SysDictDTO dto) {
+
+        return sysDictService.lisDetialByCodes(dto);
+
+    }
+
+    @PostMapping(value = "/deleteDict")
+    @ApiOperation(value = "删除字典组", notes = "删除字典组")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SysDictDetailEntity.class)
+    })
+    public Result deleteDict(@RequestParam(value = "ids") @ApiParam(value = "主键id") Long [] ids) {
+
+        LambdaUpdateWrapper<SysDictEntity>  updateDictWrapper = new LambdaUpdateWrapper();
+        updateDictWrapper.set(SysDictEntity::getIsDelete,Integer.valueOf(1));
+        updateDictWrapper.in(SysDictEntity::getDictId,ids);
+        sysDictService.update(updateDictWrapper);
+
+        LambdaUpdateWrapper<SysDictDetailEntity> updateDictDetailWrapper = new LambdaUpdateWrapper();
+        updateDictDetailWrapper.set(SysDictDetailEntity::getIsDelete,Integer.valueOf(1));
+        updateDictDetailWrapper.in(SysDictDetailEntity::getDictId,ids);
+
+        sysDictDetailService.update(updateDictDetailWrapper);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/deleteDictDetail")
+    @ApiOperation(value = "删除字典详情", notes = "删除字典详情")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SysDictDetailEntity.class)
+    })
+    public Result deleteDictDetail(@RequestParam(value = "ids") @ApiParam(value = "主键id") Long [] ids) {
+
+        LambdaUpdateWrapper<SysDictDetailEntity> updateDictDetailWrapper = new LambdaUpdateWrapper();
+        updateDictDetailWrapper.set(SysDictDetailEntity::getIsDelete,Integer.valueOf(1));
+        updateDictDetailWrapper.in(SysDictDetailEntity::getDictDetailId,ids);
+
+        sysDictDetailService.update(updateDictDetailWrapper);
+        return Result.ok();
+    }
+
+    @ApiOperation("禁用或启用字典")
+    @PostMapping("enabledDetail")
+    public Result enabledTemplateDetail(Long id,Boolean enable) {
+        LambdaUpdateWrapper<SysDictDetailEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(SysDictDetailEntity::getEnabled,enable);
+        updateWrapper.eq(SysDictDetailEntity::getDictDetailId,id);
+        sysDictDetailService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 60 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SystemConfigItemController.java

@@ -0,0 +1,60 @@
+package com.storlead.system.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.pojo.dto.SystemConfigItemDTO;
+import com.storlead.system.pojo.entity.SystemConfigItemEntity;
+import com.storlead.system.pojo.entity.SystemPersonalizeSettingEntity;
+import com.storlead.system.service.SystemConfigItemService;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * <p>
+ * 系统配置项 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2023-07-26
+ */
+@RestController
+@RequestMapping("/system/config")
+public class SystemConfigItemController {
+
+    @Resource
+    private SystemConfigItemService configItemService;
+
+    @PostMapping(value = "/update")
+    @ApiOperation(value = "修改配置项信息", notes = "配置系统基础设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result update(SystemConfigItemDTO dto) {
+        SystemConfigItemEntity configItem = SystemConfigItemDTO.convertConfigItemEntity(dto);
+        if (null == configItem) {
+            return Result.error("参数错误");
+        }
+        configItemService.saveOrUpdate(configItem);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/enabled")
+    @ApiOperation(value = "修改配置项信息", notes = "配置系统基础设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result enabled(Long itemId,Boolean enable) {
+        LambdaUpdateWrapper<SystemConfigItemEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(SystemConfigItemEntity::getEnabled,enable);
+        updateWrapper.eq(SystemConfigItemEntity::getItemId,itemId);
+        configItemService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 50 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SystemOssConfigApiController.java

@@ -0,0 +1,50 @@
+package com.storlead.system.controller;
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.pojo.entity.SystemOssConfigEntity;
+import com.storlead.system.pojo.entity.SystemPersonalizeSettingEntity;
+import com.storlead.system.service.SystemOssConfigService;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * <p>
+ * OSS  secret 配置 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2022-07-20
+ */
+@RestController
+@RequestMapping("/sys/alioss/config")
+public class SystemOssConfigApiController {
+
+    @Resource
+    private SystemOssConfigService systemOssConfigService;
+
+    @PostMapping(value = "/saveAlioss")
+    @ApiOperation(value = "配置系统基础设置", notes = "配置系统基础设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result saveSetting(SystemOssConfigEntity entity) {
+        systemOssConfigService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/getAlioss")
+    @ApiOperation(value = "获取系统基础设置", notes = "获取系统基础设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result<?> getSetting() {
+        SystemOssConfigEntity alioss = systemOssConfigService.getOne(null);
+        return Result.ok(alioss);
+    }
+}

+ 52 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SystemPersonalizeSettingApiController.java

@@ -0,0 +1,52 @@
+package com.storlead.system.controller;
+
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.system.pojo.entity.SystemPersonalizeSettingEntity;
+import com.storlead.system.service.SystemPersonalizeSettingService;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * <p>
+ * 系统个性化设置 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2022-07-20
+ */
+@RestController
+@RequestMapping("/sys/personalize/setting")
+public class SystemPersonalizeSettingApiController {
+
+    @Resource
+    private SystemPersonalizeSettingService personalizeSettingService;
+
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "配置系统基础设置", notes = "配置系统基础设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result saveSetting(SystemPersonalizeSettingEntity entity) {
+         personalizeSettingService.saveOrUpdate(entity);
+         return Result.ok();
+    }
+
+    @PostMapping(value = "/get")
+    @ApiOperation(value = "获取系统基础设置", notes = "获取系统基础设置")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result<?> getSetting() {
+        SystemPersonalizeSettingEntity setting = personalizeSettingService.getOne(null);
+        return Result.ok(setting);
+    }
+}

+ 50 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SystemSettingApiController.java

@@ -0,0 +1,50 @@
+package com.storlead.system.controller;
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.system.constant.ReidsCacheConstant;
+import com.storlead.system.pojo.entity.SystemPersonalizeSettingEntity;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/sys/set")
+public class SystemSettingApiController {
+
+    @Resource
+    private RedisService redisService;
+
+    @PostMapping(value = "/orgSyncMode")
+    @ApiOperation(value = "组织结构数据同步模式", notes = "组织结构数据同步模式")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result orgSyncMode(Integer mode) {
+        if (mode == null) {
+            mode = 0;
+        }
+        redisService.setCacheObject(ReidsCacheConstant.SYS_CACHE_ORG_SYNC_MODE,mode);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/getOrgSyncMode")
+    @ApiOperation(value = "获取组织结构数据同步模式", notes = "获取组织结构数据同步模式")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SystemPersonalizeSettingEntity.class)
+    })
+    public Result getOrgSyncMode() {
+        Object mode = redisService.getCacheObject(ReidsCacheConstant.SYS_CACHE_ORG_SYNC_MODE);
+        if (Objects.isNull(mode)) {
+            mode = 0;
+        }
+        return Result.ok(mode);
+    }
+}

+ 41 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/SystemWechatConfigApiController.java

@@ -0,0 +1,41 @@
+package com.storlead.system.controller;
+
+import com.storlead.framework.common.result.Result;
+import com.storlead.wecom.pojo.vo.SystemWechatConfigEntity;
+import com.storlead.wecom.service.SystemWechatConfigService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * <p>
+ * 企业微信secret配置 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2022-07-20
+ */
+@RestController
+@RequestMapping("/sys/wechat/config")
+public class SystemWechatConfigApiController {
+
+    @Resource
+    private SystemWechatConfigService systemWechatConfigService;
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存企业微信参数配置", notes = "配置系统基础设置")
+    public Result saveSetting(SystemWechatConfigEntity entity) {
+        systemWechatConfigService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/get")
+    @ApiOperation(value = "保存企业微信参数配置")
+    public Result<?> getSetting() {
+        SystemWechatConfigEntity setting = systemWechatConfigService.getOne(null);
+        return Result.ok(setting);
+    }
+}

+ 607 - 0
java/storlead-api/src/main/java/com/storlead/system/controller/UserApiController.java

@@ -0,0 +1,607 @@
+package com.storlead.system.controller;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.common.ecode.PasswordEncoder;
+import com.storlead.framework.auth.vo.LoginUser;
+import com.storlead.framework.common.result.Result;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.system.pojo.entity.RoleEntity;
+import com.storlead.system.pojo.entity.UserRoleEntity;
+import com.storlead.system.service.IRoleService;
+import com.storlead.system.service.IUserRoleService;
+import com.storlead.system.util.SystemConfigItemCacheUtil;
+import com.storlead.system.util.SystemSettingCacheCheckUtil;
+import com.storlead.user.model.UserInfo;
+import com.storlead.user.model.UserQueryModel;
+import com.storlead.user.pojo.dto.SetUserDetailDTO;
+import com.storlead.user.pojo.dto.UserParam;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.pojo.vo.SetUserDetailVo;
+import com.storlead.user.pojo.vo.UserVo;
+import com.storlead.user.pojo.vo.WxUserVO;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.IUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Resource;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2022-05-20 12:08
+ */
+@Slf4j
+@Api(tags = "用户: 用户管理(含公共列表/拼音/下级/直属领导查询)")
+@RestController
+@RequestMapping("/sys/user")
+public class UserApiController {
+
+    @Resource
+    private IUserService userService;
+
+    @Resource
+    private IUserRoleService userRoleService;
+
+    @Resource
+    private IRoleService roleService;
+
+    @Resource
+    private IDepartService departService;
+
+    @Resource
+    private PasswordEncoder passwordEncoder;
+
+    @ApiOperation("保存员工")
+    @PostMapping("save")
+    public Result save(@RequestBody UserEntity user) {
+        Result r = SystemSettingCacheCheckUtil.checkAddDataMode();
+        if (!r.isSuccess()) {
+            return r;
+        }
+        if (StrUtil.isBlank(user.getMobile())) {
+            return Result.error("手机号不能为空!");
+        }
+        if(Objects.isNull(user.getId())) {
+            String defaultPassWord = SystemConfigItemCacheUtil.getDefaultPassWord();
+            user.setDataSource(1);
+            user.setPassword(passwordEncoder.encode(defaultPassWord));
+        }
+        UserEntity oUser = userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,user.getMobile()).eq(UserEntity::getIsDelete,0).last("limit 1"));
+        if (Objects.nonNull(oUser)) {
+            if (!oUser.getId().equals(user.getId())) {
+                return Result.error("手机'"+user.getMobile()+"'号已被占用!");
+            }
+        }
+
+        if (Objects.nonNull(user.getDeptId())) {
+            DeptEntity dept = departService.getById(user.getDeptId());
+            if (Objects.nonNull(dept)) {
+                user.setOrgRouteCode(dept.getRouteCode());
+                user.setSubCompanyId(dept.getSubCompanyId());
+                user.setCompanyId(dept.getCompanyId());
+                user.setSubCompanyId(dept.getSubCompanyId());
+            }
+        }
+        if (Objects.nonNull(user.getManagerId())) {
+            UserEntity u = userService.getById(user.getManagerId());
+            if (Objects.nonNull(u)) {
+                user.setManagers(u.getManagers()+","+user.getManagerId().toString());
+            } else {
+                user.setManagers(user.getManagerId().toString());
+            }
+        }
+        userService.saveOrUpdate(user);
+        return Result.ok();
+    }
+
+    @ApiOperation("根据部门id查询用户")
+    @PostMapping("listByDeptId")
+    public Result listByDeptId(Long deptId) {
+
+        List<UserEntity> list = userService.list(
+                new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getDeptId,deptId)
+                        .ne(UserEntity::getStatus,Integer.valueOf(5))
+                        .eq(UserEntity::getIsDelete,0));
+        return Result.ok(list);
+    }
+
+    @ApiOperation("分页查询用户")
+    @PostMapping("pageUserlist")
+    public Result pageUserlist(@RequestBody UserParam param) {
+        IPage<UserVo> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        LambdaQueryWrapper<UserEntity> queryWrapper = new LambdaQueryWrapper<UserEntity>(new UserEntity());
+        queryWrapper.eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        if (StrUtil.isNotBlank(param.getBlurry())) {
+            queryWrapper.like(UserEntity::getUserName,param.getBlurry()).or().like(UserEntity::getRealName,param.getBlurry());
+        }
+        if (StrUtil.isNotBlank(param.getOrgRouteCode())) {
+            queryWrapper.likeRight(UserEntity::getOrgRouteCode,param.getOrgRouteCode());
+        }
+//        if (Objects.isNull(param.getStatus())) {
+//            queryWrapper.ne(UserEntity::getStatus,Integer.valueOf(5));
+//        } else {
+//            queryWrapper.ne(UserEntity::getStatus,param.getStatus());
+//        }
+        if (!param.getIsIncludeResign()) {
+            queryWrapper.ne(UserEntity::getStatus,Integer.valueOf(5));
+        }
+        queryWrapper.orderByDesc(UserEntity::getEmail);
+        List<UserVo> us  = userService.listWithScopeVo(page,queryWrapper,param);
+        if (CollectionUtil.isNotEmpty(us)) {
+            List<Long> userIds = us.stream().map(UserVo::getId).collect(Collectors.toList());
+            List<UserRoleEntity> userRolels = userRoleService.selectUserRole(new LambdaQueryWrapper<UserRoleEntity>().in(UserRoleEntity::getUserId,userIds));
+            if (CollectionUtil.isNotEmpty(userRolels)) {
+                List<RoleEntity> rs = roleService.list(new LambdaQueryWrapper<RoleEntity>().in(RoleEntity::getId,userRolels.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toList())));
+                us.forEach(u -> {
+                    List<RoleEntity> roles = rs.stream().filter(r -> userRolels.stream().anyMatch(ur -> ur.getRoleId().equals(r.getId()) && ur.getUserId().equals(u.getId()))).collect(Collectors.toList());
+                    if(CollectionUtil.isNotEmpty(roles)) {
+                        u.setRoleNames(roles.stream().map(RoleEntity::getName).collect(Collectors.toList()));
+                        u.setRoleIds(roles.stream().map(RoleEntity::getId).collect(Collectors.toList()));
+                    }
+                });
+            }
+
+            us.forEach(e -> {
+                e.setIdNum("");
+                e.setPassword("");
+                e.setBankCardNum("");
+                e.setBankName("");
+                e.setEducation("");
+                e.setAddress("");
+                e.setBirthday(null);
+                e.setEmail("");
+            });
+        }
+        page.setRecords(us);
+        return Result.ok(page);
+    }
+
+
+    @ApiOperation("分页查询用户")
+    @PostMapping("pagelist")
+    public Result pagelist(@RequestBody UserParam param) {
+
+//        param.setCommonQueryOrderBy("UserEntity");
+
+        IPage<UserVo> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        LambdaQueryWrapper<UserEntity> queryWrapper = new LambdaQueryWrapper<UserEntity>(new UserEntity());
+        queryWrapper.eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        if (StrUtil.isNotBlank(param.getBlurry())) {
+            queryWrapper.like(UserEntity::getUserName,param.getBlurry()).or().like(UserEntity::getRealName,param.getBlurry());
+        }
+
+
+        LambdaQueryWrapper<DeptEntity> dlp = new LambdaQueryWrapper<>();
+        dlp.eq(DeptEntity::getEnabled,1);
+        dlp.eq(DeptEntity::getIsDelete,CommonConstant.DEL_FLAG_0);
+        List<DeptEntity> deptEntities = departService.list(dlp);
+        if (CollectionUtil.isNotEmpty(deptEntities)) {
+            List<Long> deptIds = deptEntities.stream().map(DeptEntity::getId).collect(Collectors.toList());
+            queryWrapper.in(UserEntity::getDeptId,deptIds);
+        }
+        if (StrUtil.isNotBlank(param.getOrgRouteCode())) {
+            queryWrapper.likeRight(UserEntity::getOrgRouteCode,param.getOrgRouteCode());
+        }
+//        if (Objects.isNull(param.getStatus())) {
+//            queryWrapper.ne(UserEntity::getStatus,Integer.valueOf(5));
+//        } else {
+//            queryWrapper.ne(UserEntity::getStatus,param.getStatus());
+//        }
+        if (!param.getIsIncludeResign()) {
+            // 在职
+            queryWrapper.ne(UserEntity::getStatus,Integer.valueOf(5));
+            queryWrapper.eq(UserEntity::getEnabled,1);
+        } else {
+            queryWrapper.and(wrapper ->
+                    wrapper.eq(UserEntity::getStatus, 5) // 离职
+                            .or(w -> w.ne(UserEntity::getStatus, 5) // 在职
+                                    .eq(UserEntity::getEnabled, 1)) // 并且启用
+            );
+        }
+        queryWrapper.orderByDesc(UserEntity::getEmail);
+        List<UserVo> us  = userService.listWithScopeVo(page,queryWrapper,param);
+        if (CollectionUtil.isNotEmpty(us)) {
+            List<Long> userIds = us.stream().map(UserVo::getId).collect(Collectors.toList());
+            List<UserRoleEntity> userRolels = userRoleService.selectUserRole(new LambdaQueryWrapper<UserRoleEntity>().in(UserRoleEntity::getUserId,userIds));
+            if (CollectionUtil.isNotEmpty(userRolels)) {
+                List<RoleEntity> rs = roleService.list(new LambdaQueryWrapper<RoleEntity>().in(RoleEntity::getId,userRolels.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toList())));
+                us.forEach(u -> {
+                    List<RoleEntity> roles = rs.stream().filter(r -> userRolels.stream().anyMatch(ur -> ur.getRoleId().equals(r.getId()) && ur.getUserId().equals(u.getId()))).collect(Collectors.toList());
+                    if(CollectionUtil.isNotEmpty(roles)) {
+                        u.setRoleNames(roles.stream().map(RoleEntity::getName).collect(Collectors.toList()));
+                        u.setRoleIds(roles.stream().map(RoleEntity::getId).collect(Collectors.toList()));
+                    }
+                });
+            }
+
+            us.forEach(e -> {
+                e.setIdNum("");
+                e.setPassword("");
+                e.setBankCardNum("");
+                e.setBankName("");
+                e.setEducation("");
+                e.setAddress("");
+                e.setBirthday(null);
+                e.setEmail("");
+            });
+        }
+        page.setRecords(us);
+        return Result.ok(page);
+    }
+
+    @ApiOperation("分页查询用户")
+    @PostMapping("pageScopelist")
+    public Result pageScopelist(@RequestBody UserParam param) {
+
+        IPage<UserVo> page = new Page<>(param.getPageIndex(),param.getPageSize());
+        LambdaQueryWrapper<UserEntity> queryWrapper = new LambdaQueryWrapper<UserEntity>(new UserEntity());
+        queryWrapper.eq(UserEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        if (StrUtil.isNotBlank(param.getBlurry())) {
+            queryWrapper.like(UserEntity::getUserName,param.getBlurry()).or().like(UserEntity::getRealName,param.getBlurry());
+        }
+        if (StrUtil.isNotBlank(param.getOrgRouteCode())) {
+            queryWrapper.likeRight(UserEntity::getOrgRouteCode,param.getOrgRouteCode());
+        }
+        if (!param.getIsIncludeResign()) {
+            queryWrapper.ne(UserEntity::getStatus,Integer.valueOf(5));
+        }
+        queryWrapper.orderByDesc(UserEntity::getEmail);
+        List<UserVo> us  = userService.listWithScopeVo(page,queryWrapper,param);
+        if (CollectionUtil.isNotEmpty(us)) {
+            List<Long> userIds = us.stream().map(UserVo::getId).collect(Collectors.toList());
+            List<UserRoleEntity> userRolels = userRoleService.selectUserRole(new LambdaQueryWrapper<UserRoleEntity>().in(UserRoleEntity::getUserId,userIds));
+            if (CollectionUtil.isNotEmpty(userRolels)) {
+                List<RoleEntity> rs = roleService.list(new LambdaQueryWrapper<RoleEntity>().in(RoleEntity::getId,userRolels.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toList())));
+                us.forEach(u -> {
+                    List<RoleEntity> roles = rs.stream().filter(r -> userRolels.stream().anyMatch(ur -> ur.getRoleId().equals(r.getId()) && ur.getUserId().equals(u.getId()))).collect(Collectors.toList());
+                    if(CollectionUtil.isNotEmpty(roles)) {
+                        u.setRoleNames(roles.stream().map(RoleEntity::getName).collect(Collectors.toList()));
+                        u.setRoleIds(roles.stream().map(RoleEntity::getId).collect(Collectors.toList()));
+                    }
+                });
+            }
+
+            us.forEach(e -> {
+                e.setIdNum("");
+                e.setPassword("");
+                e.setBankCardNum("");
+                e.setBankName("");
+                e.setEducation("");
+                e.setAddress("");
+                e.setBirthday(null);
+                e.setEmail("");
+            });
+        }
+        page.setRecords(us);
+        return Result.ok(page);
+    }
+
+    @ApiOperation("删除人员数据")
+    @PostMapping("delete")
+    public Result delete(Long [] ids) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<UserEntity> update = new LambdaUpdateWrapper();
+        update.in(UserEntity::getId,Arrays.asList(ids));
+        update.set(UserEntity::getIsDelete,1);
+        userService.update(update);
+        return Result.ok();
+    }
+
+
+    @ApiOperation("标记冻结")
+    @PostMapping("markFreeze")
+    public Result markFreeze(Long [] ids,Integer isFreeze) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<UserEntity> update = new LambdaUpdateWrapper();
+        update.in(UserEntity::getId,Arrays.asList(ids));
+        update.set(UserEntity::getIsFreeze,isFreeze);
+        userService.update(update);
+        if(Integer.valueOf(0).equals(isFreeze)) {
+            userService.updateLastFollowUpTime(Arrays.asList(ids));
+        }
+        return Result.ok();
+    }
+
+
+    @ApiOperation("删除人员数据")
+    @PostMapping("deleteById")
+    public Result deleteById(Long id) {
+        if (id == null) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<UserEntity> update = new LambdaUpdateWrapper();
+        update.eq(UserEntity::getId,id);
+        update.set(UserEntity::getIsDelete,1);
+        userService.update(update);
+        return Result.ok();
+    }
+
+    @ApiOperation("删除人员数据")
+    @PostMapping("updateRealName")
+    public Result deleteById(Long id,String realName) {
+        if (id == null) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<UserEntity> update = new LambdaUpdateWrapper();
+        update.eq(UserEntity::getId,id);
+        update.set(UserEntity::getRealName,realName);
+        userService.update(update);
+        return Result.ok();
+    }
+
+
+    @ApiOperation("启用禁用员工")
+    @PostMapping("enable")
+    public Result setEnable(Long [] ids,Boolean enable) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<UserEntity> update = new LambdaUpdateWrapper();
+        update.in(UserEntity::getId,Arrays.asList(ids));
+        update.set(UserEntity::getEnabled,enable);
+        Boolean re =  userService.update(update);
+        if (re) {
+            return Result.ok();
+        } else {
+            return Result.error("操作失败");
+        }
+    }
+
+
+    @ApiOperation("根据ids查询用户")
+    @PostMapping("queryByIds")
+    public Result queryByIds(Long [] ids) {
+        if (ids == null || ids.length == 0) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<UserEntity> query = new LambdaUpdateWrapper();
+        query.in(UserEntity::getId,Arrays.asList(ids));
+        List<UserEntity> list  =  userService.list(query);
+        return Result.ok(list);
+    }
+
+
+    @ApiOperation("同步OA数据")
+    @PostMapping("syncOa")
+    public Result syncOa() {
+        Result r = SystemSettingCacheCheckUtil.checkSyncOaDataMode();
+        if (!r.isSuccess()) {
+            return r;
+        }
+       userService.syncOa();
+       return Result.ok();
+    }
+
+    @ApiOperation("重置密码")
+    @PostMapping("resetPassword")
+    public Result resetPassword(Long userId) {
+        if  (Objects.isNull(userId)) {
+            return Result.error("参数错误");
+        }
+        String defaultPassWord = SystemConfigItemCacheUtil.getDefaultPassWord();
+        LambdaUpdateWrapper<UserEntity> update = new LambdaUpdateWrapper<UserEntity>();
+        update.eq(UserEntity::getId,userId);
+        update.set(UserEntity::getPassword,passwordEncoder.encode(defaultPassWord));
+        userService.update(update);
+        return Result.ok();
+    }
+
+    @ApiOperation("修改用户信息")
+    @PostMapping("updateUserInfo")
+    public Result setPhone(SetUserDetailDTO dto) {
+        Long userId = LoginUserUtil.getCurrentUserId();
+        if  (Objects.isNull(userId)) {
+            return Result.error("您未登录");
+        }
+        if  (StrUtil.isBlank(dto.getAvatar())) {
+            return Result.error("参数错误,头像url不能为空");
+        }
+        if  (StrUtil.isBlank(dto.getMobile())) {
+            return Result.error("参数错误,手机号不能为空");
+        }
+        UserEntity entity = userService.getById(userId);
+        if (Objects.isNull(entity)) {
+            return Result.error("未找到当前用户,请重新登录");
+        }
+        UserEntity oUser = userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,dto.getMobile()).eq(UserEntity::getIsDelete,0).last("limit 1"));
+        if (Objects.nonNull(oUser)) {
+            if (!oUser.getId().equals(userId)) {
+                return Result.error("该手机'"+dto.getMobile()+"'号已被占用!");
+            }
+        }
+        if  (StrUtil.isNotBlank(dto.getEmail())) {
+            oUser = userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile,dto.getEmail()).eq(UserEntity::getIsDelete,0).last("limit 1"));
+            if (Objects.nonNull(oUser)) {
+                if (!oUser.getId().equals(userId)) {
+                    return Result.error("该邮箱'"+dto.getEmail()+"'已被占用!");
+                }
+            }
+        }
+        LambdaUpdateWrapper<UserEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(UserEntity::getId, userId);
+
+        update.set(UserEntity::getAvatar, dto.getAvatar())
+              .set(UserEntity::getEmail, dto.getEmail())
+              .set(UserEntity::getNickName, dto.getNickName())
+              .set(UserEntity::getRealName, dto.getRealName())
+              .set(UserEntity::getMobile, dto.getMobile())
+              .set(UserEntity::getSex, dto.getSex())
+              .set(UserEntity::getBirthday, dto.getBirthday());
+
+        if (StrUtil.isNotBlank(dto.getJobDes())) {
+            update.set(UserEntity::getJobDes, dto.getJobDes());
+        }
+
+        try {
+            boolean result = userService.update(update);
+            if (!result) {
+                return Result.error("更新用户信息失败,请重试。");
+            }
+        } catch (Exception e) {
+            log.error("更新用户信息发生错误: {}", e.getMessage());
+            return Result.error("系统错误,更新用户信息失败。");
+        }
+
+        if (StrUtil.isNotBlank(dto.getDeptJobDes()) && Objects.nonNull(entity.getDeptId())) {
+            DeptEntity dept = departService.getById(entity.getDeptId());
+            if (!dto.getDeptJobDes().equals(dept.getDeptJobDes())) {
+                if (Integer.valueOf(0).equals(entity.getIsLeader())) {
+                    return Result.error("只有领导才能设置部门职责");
+                }
+                LambdaUpdateWrapper<DeptEntity> dUpdate = new LambdaUpdateWrapper<DeptEntity>();
+                dUpdate.eq(DeptEntity::getId,entity.getDeptId());
+                dUpdate.set(DeptEntity::getDeptJobDes,dto.getDeptJobDes());
+                departService.update(dUpdate);
+            }
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation("获取用户基础信息")
+    @PostMapping("getSimpleUserInfo")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SetUserDetailVo.class)
+    })
+    public Result getSimpleUserInfo() {
+        Long userId = LoginUserUtil.getCurrentUserId();
+        if  (Objects.isNull(userId)) {
+            return Result.error("您未登录");
+        }
+        UserVo entity = userService.getUserInfoById(userId);
+
+        SetUserDetailVo detailVo = new SetUserDetailVo();
+        detailVo.setDeptId(entity.getDeptId());
+        detailVo.setId(entity.getId());
+        detailVo.setDeptName(entity.getDeptName());
+        detailVo.setAvatar(entity.getAvatar());
+        detailVo.setJobId(entity.getJobId());
+        detailVo.setJobName(entity.getJobName());
+        detailVo.setEmail(entity.getEmail());
+        detailVo.setMobile(entity.getMobile());
+        detailVo.setUserName(entity.getRealName());
+        detailVo.setJobDes(entity.getJobDes());
+        detailVo.setIsLeader(entity.getIsLeader());
+        DeptEntity dept = departService.getById(entity.getDeptId());
+        if (Objects.nonNull(dept)) {
+            detailVo.setDeptJobDes(dept.getDeptJobDes());
+        }
+        return Result.ok(detailVo);
+    }
+
+    @RequestMapping(value = "/list", method = RequestMethod.GET)
+    @ApiOperation(value = "公共接口-用户信息-列表", notes = "公共接口-用户信息-列表")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = UserInfo.class)
+    })
+    public Result<List<UserInfo>> queryTreeList(UserQueryModel userQueryModel) {
+        Result<List<UserInfo>> result = new Result<>();
+        try {
+            List<UserInfo> list = userService.getUserListBySearch(userQueryModel);
+            result.setResult(list);
+            result.setSuccess(true);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return result;
+    }
+
+    @RequestMapping(value = "/user-list-pinyin", method = RequestMethod.GET)
+    @ApiOperation(value = "公共接口-用户信息-列表(按拼音首字母排序)", notes = "公共接口-用户信息-列表(按拼音首字母排序)")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = UserInfo.class)
+    })
+    public Result<List<WxUserVO>> userListPinyin(UserQueryModel userQueryModel) {
+        Result<List<WxUserVO>> result = new Result<>();
+        try {
+            List<WxUserVO> wxUserInfoList = userService.getPingyinUserList(userQueryModel);
+            result.setResult(wxUserInfoList);
+            result.setSuccess(true);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return result;
+    }
+
+    @RequestMapping(value = "/list-subordinate", method = RequestMethod.GET)
+    @ApiOperation(value = "公共接口-下级用户信息-列表", notes = "公共接口-下级用户信息-列表")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = UserInfo.class)
+    })
+    public Result<List<UserInfo>> listSubordinate(UserQueryModel userQueryModel) {
+        Result<List<UserInfo>> result = new Result<>();
+        try {
+            LoginUser loginUser = LoginUserUtil.getLoginUser();
+            List<UserInfo> list = userService.getUserListBySearch(userQueryModel);
+            List<Long> allSubordinate = userService.getAllSubordinate(loginUser.getId());
+            Set<Long> allSubordinateSet = new HashSet<>(allSubordinate);
+            list = list.stream().filter((u) -> allSubordinateSet.contains(u.getId())).collect(Collectors.toList());
+            result.setResult(list);
+            result.setSuccess(true);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return result;
+    }
+
+    @GetMapping(value = "/findManager")
+    @ApiOperation(value = "公共接口-用户信息-查看当前用户领导", notes = "公共接口-用户信息-查看当前用户领导")
+    public Result<UserEntity> findManager() {
+        LoginUser loginUser = LoginUserUtil.getLoginUser();
+        UserEntity user = userService.getById(loginUser.getManagerId());
+        return Result.ok(user);
+    }
+
+    public void updateManagerId(UserEntity user,Long oldManagerId){
+        if (Objects.isNull(user)) {
+            return;
+        }
+        if (Objects.isNull(user.getManagerId())) {
+            return;
+        }
+        List<UserEntity> userEntities = userService.list(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getManagerId,user.getId()));
+        for (UserEntity userEntity : userEntities) {
+            if (StrUtil.isNotBlank(userEntity.getManagers())) {
+                String routeCodeStr = ","+userEntity.getManagers()+",";
+                routeCodeStr = routeCodeStr.replace(","+oldManagerId+",",","+userEntity.getManagerId()+",");
+                StringUtils.startsWithIgnoreCase(routeCodeStr,",");
+                StringUtils.endsWithIgnoreCase(routeCodeStr,",");
+
+                LambdaUpdateWrapper<UserEntity> updateWrapper = new LambdaUpdateWrapper();
+                updateWrapper.eq(UserEntity::getId,userEntity.getId());
+                updateWrapper.set(UserEntity::getManagers,routeCodeStr);
+                userService.update(updateWrapper);
+            }
+        }
+
+    }
+}

+ 7 - 6
java/storlead-user/storlead-user-api/src/main/java/com/storlead/user/api/UserApiController.java → java/storlead-api/src/main/java/com/storlead/system/controller/UserController.java

@@ -1,8 +1,8 @@
-package com.storlead.user.api;
+package com.storlead.system.controller;
 
 import com.storlead.framework.auth.vo.LoginUser;
-import com.storlead.framework.util.LoginUserUtil;
 import com.storlead.framework.common.result.Result;
+import com.storlead.framework.util.LoginUserUtil;
 import com.storlead.user.model.UserInfo;
 import com.storlead.user.model.UserQueryModel;
 import com.storlead.user.pojo.entity.UserEntity;
@@ -13,11 +13,12 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
-import javax.annotation.Resource;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -33,9 +34,9 @@ import java.util.stream.Collectors;
  */
 @Slf4j
 @RestController
-@RequestMapping("/sys/user")
-@Api(tags="System-User ->  公共接口")
-public class UserApiController {
+@RequestMapping("/lingcun/user")
+@Api(tags="5.公共接口")
+public class UserController {
 
 	@Resource
 	private IUserService sysUserService;

+ 14 - 0
java/storlead-api/src/main/java/com/storlead/system/dto/CompanyDTO.java

@@ -0,0 +1,14 @@
+package com.storlead.system.dto;
+
+import com.storlead.framework.common.dto.page.PageDTO;
+import lombok.Data;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-09 17:41
+ */
+@Data
+public class CompanyDTO extends PageDTO {
+}

+ 5 - 1
java/storlead-api/src/main/resources/application.yml

@@ -49,4 +49,8 @@ spring:
         max-size: 100
         queue-capacity: 50
 
-
+# 多租户:无 tenant_id 列的表须列入忽略,否则 SQL 会拼 tenant_id 导致 Unknown column
+storlead:
+  tenant:
+    ignore-tables:
+      - system_wechat_config

+ 2 - 0
java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java

@@ -183,4 +183,6 @@ public class LoginUser implements Serializable {
      */
     private Set<String> btnRes;
 
+    private List<MenuPermis> permis;
+
 }

+ 17 - 0
java/storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/MenuPermis.java

@@ -0,0 +1,17 @@
+package com.storlead.framework.auth.vo;
+
+import lombok.Data;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-11 09:55
+ */
+@Data
+public class MenuPermis {
+    private Long id;
+    private String perm;
+    private Integer type;
+}
+

+ 1 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/properties/UrlChainDefinitionPorperties.java

@@ -48,6 +48,7 @@ public class UrlChainDefinitionPorperties {
 			"/sys/auth/external_login",
 			"/lingcun/phone-login",
 			"/sys/auth/login",
+			"/sys/auth/tenant/login",
 			"/sys/auth/redirect",
 			"/actuator/metrics/**",
 			"/actuator/httptrace/**",

+ 232 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/PingyinUtil.java

@@ -0,0 +1,232 @@
+package com.storlead.framework.common.util;
+
+import net.sourceforge.pinyin4j.PinyinHelper;
+import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
+import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
+import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
+import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
+
+import java.text.Collator;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-09 17:58
+ */
+public class PingyinUtil {
+    /**
+     * 获取汉字串拼音,英文字符不变
+     */
+    public static String getLowFullSpell(String chinese) {
+        chinese = filterSpecialChars(chinese);
+        StringBuffer pybf = new StringBuffer();
+        char[] arr = chinese.toCharArray();
+        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
+        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+        for (int i = 0; i < arr.length; i++) {
+            if (arr[i] > 128) {
+                try {
+                    pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
+                } catch (BadHanyuPinyinOutputFormatCombination e) {
+                    e.printStackTrace();
+                }
+            } else {
+                pybf.append(arr[i]);
+            }
+        }
+        return pybf.toString();
+    }
+
+    /**
+     * 获取汉字串拼音,英文字符不变
+     */
+    public static String getFullSpell(String chinese) {
+        chinese = filterSpecialChars(chinese);
+        StringBuffer pybf = new StringBuffer();
+        char[] arr = chinese.toCharArray();
+        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
+        defaultFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
+        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+        for (int i = 0; i < arr.length; i++) {
+            if (arr[i] > 128) {
+                try {
+                    pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
+                } catch (BadHanyuPinyinOutputFormatCombination e) {
+                    e.printStackTrace();
+                }
+            } else {
+                pybf.append(arr[i]);
+            }
+        }
+        return pybf.toString();
+    }
+
+    /**
+     * 将集合数据按照汉字首字母分组排序
+     */
+    public static Map<String, Object> screenManufacturer(List<String> list) {
+        try {
+            Collator com = Collator.getInstance(Locale.CHINA);
+            // 按字母排序
+            Collections.sort(list, com);
+            // 输出26个字母
+            Map<String, Object> map = new TreeMap<>();
+            for (int i = 1; i < 27; i++) {
+                String word = String.valueOf((char)(96 + i)).toUpperCase();
+                // 循环找出 首字母一样的数据
+                List<String> letter = new ArrayList<>();
+                for (String str : list) {
+//                    String pybf = getFullSpell(str);
+                    String pybf = getFullPinyin(str);
+                    String zm = pybf.substring(0, 1);
+                    if (word.equals(zm)) {
+                        letter.add(str);
+                    }
+                }
+                map.put(word, letter);
+            }
+            return map;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+//    /**
+//     * 将集合数据按照汉字首字母分组排序
+//     */
+//    public static List<WxUserVO> wxUserList(List<UserInfo> userList) {
+//        try {
+//            List<String> userNameList = new ArrayList<>();
+//            Map<String,UserInfo> userInfoMap = new HashMap<>();
+//            for (UserInfo u:userList) {
+//                userNameList.add(u.getRealName()+u.getId());
+//                userInfoMap.put(u.getRealName()+u.getId(),u);
+//            }
+//
+//            Collator com = Collator.getInstance(Locale.CHINA);
+//            // 按字母排序
+//            Collections.sort(userNameList, com);
+//            // 输出26个字母
+//            List<WxUserVO> wxUserVOList = new ArrayList<>();
+//            for (int i = 1; i < 27; i++) {
+//                String word = String.valueOf((char)(96 + i)).toUpperCase();
+//                // 循环找出 首字母一样的数据
+//                List<UserInfo> userInfoList = new ArrayList<>();
+//                for (String str : userNameList) {
+////                    String pybf = getFullSpell(str);
+//                    String pybf = getFullPinyin(str);
+//                    String zm = pybf.substring(0, 1);
+//                    if (word.equals(zm)) {
+//                        userInfoList.add(userInfoMap.get(str));
+//                    }
+//                }
+//                WxUserVO wxUserVO = new WxUserVO();
+//                wxUserVO.setLetter(word);
+//                wxUserVO.setData(userInfoList);
+//                wxUserVOList.add(wxUserVO);
+//            }
+//            return wxUserVOList;
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+//        return null;
+//    }
+
+
+    /***
+     * @Description: 姓氏多音字解决方案
+     * @Param:
+     * @return:
+     * @Author: YPZ
+     * @Date: 2023/3/23 11:26
+     */
+    /**
+     * 多音字拼音
+     */
+    public static Map<String, String> FIRST_NAME_PINYIN = new HashMap<>();
+    static {
+        FIRST_NAME_PINYIN.put("繁", "PO");
+        FIRST_NAME_PINYIN.put("区", "OU");
+        FIRST_NAME_PINYIN.put("仇", "QIU");
+        FIRST_NAME_PINYIN.put("种", "CHONG");
+        FIRST_NAME_PINYIN.put("单", "SHAN");
+        FIRST_NAME_PINYIN.put("解", "XIE");
+        FIRST_NAME_PINYIN.put("查", "ZHA");
+        FIRST_NAME_PINYIN.put("曾", "ZENG");
+        FIRST_NAME_PINYIN.put("秘", "BI");
+        FIRST_NAME_PINYIN.put("乐", "YUE");
+        FIRST_NAME_PINYIN.put("重", "CHONG");
+        FIRST_NAME_PINYIN.put("朴", "PIAO");
+        FIRST_NAME_PINYIN.put("缪", " MIAO");
+        FIRST_NAME_PINYIN.put("冼", " XIAN");
+        FIRST_NAME_PINYIN.put("翟", "ZHAI");
+        FIRST_NAME_PINYIN.put("折", "SHE");
+        FIRST_NAME_PINYIN.put("黑", "HE");
+        FIRST_NAME_PINYIN.put("盖", "GE");
+        FIRST_NAME_PINYIN.put("沈", "SHEN");
+        FIRST_NAME_PINYIN.put("尉迟", "YUCHI");
+        FIRST_NAME_PINYIN.put("万俟", "MOQI");
+    }
+
+    /**
+     * @Description: 过滤掉中文特殊字符
+     * @throws PatternSyntaxException
+     */
+    public static String filterSpecialChars(String target) throws PatternSyntaxException
+    {
+        String regEx = "[\\uac00-\\ud7af\\u3130–\\u318F]*[\\u0800-\\u4e00`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?_]";
+        Pattern pat = Pattern.compile(regEx);
+        Matcher mat = pat.matcher(target);
+        return mat.replaceAll("").trim();
+    }
+    /**
+     * 获得完整拼音
+     * 解决姓多音字问题
+     *
+     * @param keywords
+     * @return
+     */
+    public static String getFullPinyin(String keywords) {
+        if(keywords == null || "".equals(keywords)) {
+            return keywords;
+        }
+        for (Map.Entry<String, String> map : FIRST_NAME_PINYIN.entrySet()) {
+            if (keywords.startsWith(map.getKey())) {
+                String suffix = keywords.substring(map.getKey().length());
+                return map.getValue() + getFullSpell(suffix);
+            }
+        }
+        return getFullSpell(keywords);
+    }
+
+    public static String getInitials(String chinese) {
+        StringBuilder initials = new StringBuilder();
+        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
+        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+
+        for (char ch : chinese.toCharArray()) {
+            if (Character.toString(ch).matches("[\u4E00-\u9FA5]")) { // 判断是否为汉字
+                try {
+                    String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
+                    if (pinyinArray != null && pinyinArray.length > 0) {
+                        initials.append(pinyinArray[0].charAt(0));
+                    }
+                } catch (BadHanyuPinyinOutputFormatCombination e) {
+
+                }
+            } else {
+                initials.append(ch); // 非汉字直接附加
+            }
+        }
+
+        return initials.toString();
+    }
+}

+ 144 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/CryptoZipUtil.java

@@ -0,0 +1,144 @@
+package com.storlead.framework.common.util.sso;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * @program: storlead-centre-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-11-21 16:03
+ */
+public class CryptoZipUtil {
+
+    private static final String AES_CIPHER = "AES/CBC/PKCS5Padding";
+    private static final String IV = "1234567890abcdef"; // 16字节固定 IV
+    public static final String SALT_KEY = "G4YfTqX6Kh8cHQy5";
+
+
+
+    // ====================================
+    // Map → URL 参数 → GZIP → AES → Base64
+    // ====================================
+    public static String encryptParams(Map<String, String> params, String key) {
+        StringBuilder sb = new StringBuilder();
+        params.forEach((k, v) -> {
+            try {
+                sb.append(URLEncoder.encode(k, "UTF-8"))
+                        .append("=")
+                        .append(URLEncoder.encode(v, "UTF-8"))
+                        .append("&");
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        String paramString = sb.substring(0, sb.length() - 1);
+
+        // 1. GZIP 压缩
+        byte[] gzipData = gzip(paramString.getBytes(StandardCharsets.UTF_8));
+
+        // 2. AES 加密
+        byte[] encryptedBytes = aesEncrypt(gzipData, key);
+
+        // 3. Base64 URL 安全编码
+        return Base64.getUrlEncoder().encodeToString(encryptedBytes);
+    }
+
+    // ====================================
+    // Base64 → AES 解密 → GZIP 解压 → Map
+    // ====================================
+    public static Map<String, String> decryptParams(String encrypted, String key) {
+        try {
+            byte[] encryptedBytes = Base64.getUrlDecoder().decode(encrypted);
+
+            // 1. AES 解密
+            byte[] gzipBytes = aesDecrypt(encryptedBytes, key);
+
+            // 2. GZIP 解压
+            String decrypted = new String(gunzip(gzipBytes), StandardCharsets.UTF_8);
+
+            // 3. URL 参数解析
+            Map<String, String> map = new HashMap<>();
+            for (String pair : decrypted.split("&")) {
+                String[] kv = pair.split("=");
+                if (kv.length == 2) {
+                    map.put(
+                            URLDecoder.decode(kv[0], "UTF-8"),
+                            URLDecoder.decode(kv[1], "UTF-8")
+                    );
+                }
+            }
+            return map;
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // GZIP 压缩
+    private static byte[] gzip(byte[] data) {
+        try {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            try (GZIPOutputStream gzip = new GZIPOutputStream(bos)) {
+                gzip.write(data);
+            }
+            return bos.toByteArray();
+        } catch (Exception e) {
+            throw new RuntimeException("GZIP 压缩失败", e);
+        }
+    }
+
+    // GZIP 解压
+    private static byte[] gunzip(byte[] data) {
+        try {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            GZIPInputStream gis = new GZIPInputStream(new java.io.ByteArrayInputStream(data));
+            byte[] buffer = new byte[1024];
+            int len;
+            while ((len = gis.read(buffer)) > 0) {
+                bos.write(buffer, 0, len);
+            }
+            return bos.toByteArray();
+        } catch (Exception e) {
+            throw new RuntimeException("GZIP 解压失败", e);
+        }
+    }
+
+    // AES 加密
+    private static byte[] aesEncrypt(byte[] content, String key) {
+        try {
+            Cipher cipher = Cipher.getInstance(AES_CIPHER);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
+            return cipher.doFinal(content);
+        } catch (Exception e) {
+            throw new RuntimeException("AES 加密失败", e);
+        }
+    }
+
+    // AES 解密
+    private static byte[] aesDecrypt(byte[] encrypted, String key) {
+        try {
+            Cipher cipher = Cipher.getInstance(AES_CIPHER);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
+            return cipher.doFinal(encrypted);
+        } catch (Exception e) {
+            throw new RuntimeException("AES 解密失败", e);
+        }
+    }
+}
+

+ 62 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/HttpSecurityCheckUtil.java

@@ -0,0 +1,62 @@
+package com.storlead.framework.common.util.sso;
+
+import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @program: storlead-centre-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-11-21 15:01
+ */
+public class HttpSecurityCheckUtil {
+
+    private static final String SALT_KEY = "G4YfTqX6Kh8cHQy5";
+    /**
+     * 除数
+     */
+    public static long divisor = 100861;
+
+    public static String makeSign(Map<String, String> sginArgMap) {
+        List<Map.Entry<String, String>> entryList = new ArrayList<>(sginArgMap.entrySet());
+        entryList.sort(Map.Entry.comparingByKey());
+        /**
+         * 构建字符串
+         */
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : entryList) {
+            if ("sign".equals(entry.getKey())) {
+                continue;
+            }
+            if (sb.length() > 0) {
+                sb.append("&");
+            }
+            sb.append(entry.getKey()).append("=").append(entry.getValue());
+        }
+        String dataToEncrypt = sb.toString();
+        String sign = AccessKeyEncryptor.getAccessKeyEncryptor(SALT_KEY).encrypt(dataToEncrypt);
+        return  sign;
+    }
+
+    public static String buildUrlParams(Map<String, String> paramMap) {
+        if (paramMap == null || paramMap.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+            if (entry.getValue() == null) continue; // 忽略 null
+            if (sb.length() > 0) sb.append("&");
+
+            sb.append(entry.getKey())
+                    .append("=")
+                    .append(entry.getValue());
+        }
+
+        return sb.toString();
+    }
+}

+ 55 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/menu/TreeVo.java

@@ -0,0 +1,55 @@
+package com.storlead.framework.common.vo.menu;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-11 10:17
+ */
+@Data
+public class TreeVo {
+    private Long id;
+    private String treeId = UUID.randomUUID().toString();
+    private Long parentId;
+    private Long menuId;
+    private String title;
+    /**
+     * 存放当前对象,1:菜单资源,2:接口按钮资源
+     */
+    private Object arg;
+    /**
+     * 存放规则引擎:只有type = 2 才可能会有
+     */
+    private Object engine;
+    private Integer rank;
+    private Boolean checked;
+
+    private Boolean _showChildren = false;
+    /**
+     * 存放当前对象,1:菜单资源,2:接口按钮资源
+     */
+    private Integer type;
+
+    /**
+     * 是否展开
+     */
+    private Boolean expand = false;
+
+    /**
+     *
+     */
+    private Boolean disableCheckbox = false;
+
+    /**
+     *  是否可以选择
+     */
+    private Boolean disabled = true;
+
+    private List<TreeVo> children;
+}
+

+ 0 - 4
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/entity/SysBaseField.java

@@ -28,10 +28,6 @@ public class SysBaseField implements Serializable {
     @TableField(fill = FieldFill.INSERT)
     private Long ownerBy;
 
-    @ApiModelProperty(value = "租户(企业)ID")
-    @TableField(value = "tenant_id", fill = FieldFill.INSERT)
-    private Long tenantId;
-
     @TableField(fill = FieldFill.INSERT)
     private Long createBy;
 

+ 27 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/entity/TenantBaseField.java

@@ -0,0 +1,27 @@
+package com.storlead.framework.mybatis.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-09 11:15
+ */
+@EqualsAndHashCode(callSuper = false)
+@Log4j2
+@Accessors(chain = true)
+@Data
+public class TenantBaseField extends SysBaseField {
+
+    @ApiModelProperty(value = "租户(企业)ID")
+    @TableField(value = "tenant_id", fill = FieldFill.INSERT)
+    private Long tenantId;
+
+}

+ 15 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/mapper/MyBaseMapper.java

@@ -1,7 +1,22 @@
 package com.storlead.framework.mybatis.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import com.storlead.framework.mybatis.query.QueryBaseEntity;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 public interface MyBaseMapper<T> extends BaseMapper<T> {
 
+    <R extends SysBaseField, E extends QueryBaseEntity> List<R> list(IPage page,E entity);
+
+    <R extends SysBaseField> List<R> list(IPage page,@Param(Constants.WRAPPER) LambdaQueryWrapper<T> entity);
+
+    <R extends SysBaseField, E extends QueryBaseEntity> List<R> list(E entity);
+
+    <R extends SysBaseField> List<R> list(@Param(Constants.WRAPPER) LambdaQueryWrapper<T> entity);
 }

+ 3 - 2
java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/assemble/QueryBaseEntity.java → java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/query/QueryBaseEntity.java

@@ -1,6 +1,7 @@
-package com.storlead.framework.web.assemble;
+package com.storlead.framework.mybatis.query;
 
 import com.baomidou.mybatisplus.annotation.TableField;
+import com.storlead.framework.mybatis.page.Page;
 import com.storlead.framework.mybatis.query.page.PageQuery;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
@@ -18,7 +19,7 @@ import lombok.extern.log4j.Log4j2;
 @AllArgsConstructor
 @Data
 @Log4j2
-public class QueryBaseEntity extends PageQuery {
+public class QueryBaseEntity extends Page {
 
     @TableField(exist = false)
     @ApiModelProperty(value = "搜索参数")

+ 56 - 42
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/service/MyBaseService.java

@@ -1,58 +1,72 @@
 package com.storlead.framework.mybatis.service;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import com.storlead.framework.mybatis.query.QueryBaseEntity;
 
+import java.io.Serializable;
 import java.util.List;
 
 public interface MyBaseService <T> extends IService<T> {
 
-    /**
-     * 删除
-     * @param id
-     * @return
-     */
+    <R extends SysBaseField, E extends QueryBaseEntity> List<R> listWithScopeVo(IPage page, E e);
+
+    List<T> listWith(IPage page, LambdaQueryWrapper<T> wrapper);
+
+    <R extends SysBaseField, E extends QueryBaseEntity> List<R> listWithScopeVo(IPage page, LambdaQueryWrapper<T> wrapper,E e);
+
+    <R extends SysBaseField, E extends QueryBaseEntity> List<R> pageWithScopelist(IPage page, LambdaQueryWrapper<T> wrapper);
+
+    <R extends SysBaseField, E extends QueryBaseEntity> List<R> pageWithScopelist(IPage page, LambdaQueryWrapper<T> wrapper, String mergeSql);
+
+    <R extends SysBaseField> List<R> listWithScopeVo(LambdaQueryWrapper<T> wrapper);
+
+    List<T> listWithScope(LambdaQueryWrapper<T> wrapper);
+    List<T> listWithScope(LambdaQueryWrapper<T> wrapper,String mergeSql);
+
+    <R extends SysBaseField> IPage<R> listWithScopeVo(IPage page, LambdaQueryWrapper<T> wrapper);
+
+    IPage<T> pageWithScope(IPage page, LambdaQueryWrapper<T> wrapper);
+
+    IPage<T> pageWithScope(IPage page, LambdaQueryWrapper<T> wrapper,String mergeSql);
+
+    <E extends QueryBaseEntity> IPage<T> pageWithScope(IPage page, LambdaQueryWrapper<T> wrapper,E e,String mergeSql);
+
+    <E extends QueryBaseEntity> List<T> listWithScope(LambdaQueryWrapper<T> wrapper,E e,String mergeSql);
+
+    Integer selectCount(LambdaQueryWrapper<T> wrapper,String mergeSql);
+
+    <E extends QueryBaseEntity> Integer selectCount(LambdaQueryWrapper<T> wrapper,E e,String mergeSql);
+
     Boolean lgDelete(Long id);
 
+    Boolean lgCustomerDelete(Long customerId);
+
+    Boolean lgCustomerListDelete(List<Long> customerIdList);
 
-    /**
-     * 删除
-     * @param ids
-     * @return
-     */
     Boolean lgDelete(List<Long> ids);
 
-    /**
-     * 查询
-     * @param ids
-     * @return
-     */
     List<T> listByIds(List<Long> ids);
 
-    /**
-     * 启用
-     * @param ids
-     * @return
-     */
-    Boolean enable(List<Long> ids);
-
-    /**
-     * 启用
-     * @param id
-     * @return
-     */
-    Boolean enable(Long id);
-
-    /**
-     * 禁用
-     * @param ids
-     * @return
-     */
-    Boolean disable(List<Long> ids);
-
-    /**
-     * 禁用
-     * @param id
-     * @return
-     */
-    Boolean disable(Long id);
+    Boolean changeCustomerId(Long fromId, Long customerId);
+
+    T getNextById(Serializable currentId, LambdaQueryWrapper<T> queryWrapper);
+
+    T getPreviousById(Serializable currentId, LambdaQueryWrapper<T> queryWrapper);
+
+//    T getOldEntityById(Class<T> clazz, Long id);
+
+    Class<T> getEntityClass();
+
+    boolean changeResourceTypeId(String resourceType, Long fromId, Long id);
+
+    boolean deleteByResourceId(String resourceType, Long resourceTypeId);
+
+    boolean deleteByResourceIds(String resourceType, List<Long> resourceTypeIds);
+
+    List<T> listByResourceId(String resourceType, Long resourceTypeId);
+
+    List<T> listByResourceIds(String resourceType, List<Long> resourceTypeIds);
 }

+ 263 - 17
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/service/impl/MyBaseServiceImpl.java

@@ -6,11 +6,17 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.common.util.DateUtils;
+import com.storlead.framework.common.util.SpringContextUtils;
 import com.storlead.framework.mybatis.entity.SysBaseField;
 import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.framework.mybatis.query.QueryBaseEntity;
 import com.storlead.framework.mybatis.service.MyBaseService;
 import org.springframework.util.CollectionUtils;
 
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -23,6 +29,96 @@ import java.util.Objects;
  * @create: 2024-04-02 15:31
  */
 public class MyBaseServiceImpl<M extends MyBaseMapper<T>, T extends SysBaseField> extends ServiceImpl<M, T> implements MyBaseService<T> {
+    @Override
+    public <R extends SysBaseField, E extends QueryBaseEntity> List<R> listWithScopeVo(IPage page, E e) {
+        assemblyDataSource(e);
+        return this.baseMapper.list(page,e);
+    }
+
+    @Override
+    public List<T> listWith(IPage page, LambdaQueryWrapper<T> wrapper) {
+        return this.baseMapper.list(page,wrapper);
+    }
+
+    @Override
+    public <R extends SysBaseField, E extends QueryBaseEntity> List<R> listWithScopeVo(IPage page, LambdaQueryWrapper<T> wrapper,E e) {
+        assemblyDataSource(wrapper,e,"");
+        return this.baseMapper.list(page,wrapper);
+    }
+
+    @Override
+    public <R extends SysBaseField, E extends QueryBaseEntity> List<R> pageWithScopelist(IPage page, LambdaQueryWrapper<T> wrapper) {
+        assemblyDataOnlySource(wrapper,"");
+        return this.baseMapper.list(page,wrapper);
+    }
+
+    @Override
+    public <R extends SysBaseField, E extends QueryBaseEntity> List<R> pageWithScopelist(IPage page, LambdaQueryWrapper<T> wrapper,String mergeSql) {
+        assemblyDataOnlySource(wrapper,mergeSql);
+        return this.baseMapper.list(page,wrapper);
+    }
+
+    @Override
+    public <R extends SysBaseField> List<R> listWithScopeVo(LambdaQueryWrapper<T> wrapper) {
+        assemblyDataOnlySource(wrapper,"");
+        return this.baseMapper.list(wrapper);
+    }
+
+
+
+    @Override
+    public  List<T> listWithScope(LambdaQueryWrapper<T> wrapper) {
+        assemblyDataOnlySource(wrapper,"");
+        return this.baseMapper.selectList(wrapper);
+    }
+    @Override
+    public  List<T> listWithScope(LambdaQueryWrapper<T> wrapper,String mergeSql) {
+        assemblyDataOnlySource(wrapper,mergeSql);
+        return this.baseMapper.selectList(wrapper);
+    }
+
+    @Override
+    public  <R extends SysBaseField> IPage<R> listWithScopeVo(IPage page, LambdaQueryWrapper<T> wrapper) {
+        assemblyDataOnlySource(wrapper,"");
+        return this.baseMapper.selectPage(page,wrapper);
+    }
+
+    @Override
+    public  IPage<T> pageWithScope(IPage page, LambdaQueryWrapper<T> wrapper) {
+        assemblyDataOnlySource(wrapper,"");
+        return this.baseMapper.selectPage(page,wrapper);
+    }
+
+    @Override
+    public  IPage pageWithScope(IPage page, LambdaQueryWrapper<T> wrapper,String mergeSql) {
+        assemblyDataOnlySource(wrapper,mergeSql);
+        return this.baseMapper.selectPage(page,wrapper);
+    }
+
+    @Override
+    public  <E extends QueryBaseEntity> IPage<T> pageWithScope(IPage page, LambdaQueryWrapper<T> wrapper, E e, String mergeSql) {
+        assemblyDataSource(wrapper, e,mergeSql);
+        return this.baseMapper.selectPage(page,wrapper);
+    }
+
+    @Override
+    public <E extends QueryBaseEntity> List<T> listWithScope(LambdaQueryWrapper<T> wrapper,E e,String mergeSql) {
+        assemblyDataSource(wrapper,e,mergeSql);
+        return this.baseMapper.selectList(wrapper);
+    }
+
+    @Override
+    public  <E extends QueryBaseEntity> Integer selectCount(LambdaQueryWrapper<T> wrapper,E e,String mergeSql) {
+        assemblyDataSource(wrapper, e,mergeSql);
+        return this.baseMapper.selectCount(wrapper);
+    }
+
+    @Override
+    public  Integer selectCount(LambdaQueryWrapper<T> wrapper,String mergeSql) {
+        assemblyDataOnlySource(wrapper,mergeSql);
+        return this.baseMapper.selectCount(wrapper);
+    }
+
     @Override
     public Boolean lgDelete(Long id) {
         if (Objects.isNull(id)) {
@@ -31,6 +127,31 @@ public class MyBaseServiceImpl<M extends MyBaseMapper<T>, T extends SysBaseField
         UpdateWrapper updateWrapper = new UpdateWrapper<>();
         updateWrapper.eq("id",id);
         updateWrapper.set("is_delete", CommonConstant.DEL_FLAG_1);
+        updateWrapper.set("update_time", DateUtils.now());
+        return this.update(updateWrapper);
+    }
+
+    @Override
+    public Boolean lgCustomerDelete(Long customerId) {
+        if (Objects.isNull(customerId)) {
+            return false;
+        }
+        UpdateWrapper updateWrapper = new UpdateWrapper<>();
+        updateWrapper.eq("customer_id",customerId);
+        updateWrapper.set("is_delete",CommonConstant.DEL_FLAG_1);
+        updateWrapper.set("update_time", DateUtils.now());
+        return this.update(updateWrapper);
+    }
+
+    @Override
+    public Boolean lgCustomerListDelete(List<Long> customerIdList) {
+        if (CollectionUtils.isEmpty(customerIdList)) {
+            return false;
+        }
+        UpdateWrapper updateWrapper = new UpdateWrapper<>();
+        updateWrapper.in("customer_id",customerIdList);
+        updateWrapper.set("is_delete",CommonConstant.DEL_FLAG_1);
+        updateWrapper.set("update_time",DateUtils.now());
         return this.update(updateWrapper);
     }
 
@@ -41,7 +162,7 @@ public class MyBaseServiceImpl<M extends MyBaseMapper<T>, T extends SysBaseField
         }
         UpdateWrapper updateWrapper = new UpdateWrapper<>();
         updateWrapper.in("id",ids);
-        updateWrapper.set("is_delete", CommonConstant.DEL_FLAG_1);
+        updateWrapper.set("is_delete",CommonConstant.DEL_FLAG_1);
         return this.update(updateWrapper);
     }
 
@@ -56,46 +177,171 @@ public class MyBaseServiceImpl<M extends MyBaseMapper<T>, T extends SysBaseField
     }
 
     @Override
-    public Boolean enable(List<Long> ids) {
-        if (CollectionUtils.isEmpty(ids)) {
+    public Boolean changeCustomerId(Long fromId, Long customerId) {
+        if (Objects.isNull(customerId) || Objects.isNull(fromId)) {
             return false;
         }
         UpdateWrapper updateWrapper = new UpdateWrapper<>();
-        updateWrapper.in("id",ids);
-        updateWrapper.set("enable", CommonConstant.DEL_FLAG_1);
+        updateWrapper.eq("customer_id",fromId);
+        updateWrapper.set("customer_id",customerId);
         return this.update(updateWrapper);
     }
 
     @Override
-    public Boolean enable(Long id) {
-        if (Objects.isNull(id)) {
+    public boolean changeResourceTypeId(String resourceType, Long fromId, Long id) {
+        if (Objects.isNull(id) || Objects.isNull(fromId)) {
             return false;
         }
         UpdateWrapper updateWrapper = new UpdateWrapper<>();
-        updateWrapper.eq("id",id);
-        updateWrapper.set("enable", CommonConstant.DEL_FLAG_1);
+        updateWrapper.eq("resource_type",resourceType);
+        updateWrapper.eq("resource_type_id",fromId);
+        updateWrapper.set("resource_type_id",id);
         return this.update(updateWrapper);
     }
 
     @Override
-    public Boolean disable(List<Long> ids) {
-        if (CollectionUtils.isEmpty(ids)) {
+    public boolean deleteByResourceId(String resourceType, Long resourceTypeId) {
+        if (Objects.isNull(resourceTypeId)) {
             return false;
         }
         UpdateWrapper updateWrapper = new UpdateWrapper<>();
-        updateWrapper.eq("id",ids);
-        updateWrapper.set("enable", CommonConstant.DEL_FLAG_0);
+        updateWrapper.eq("resource_type",resourceType);
+        updateWrapper.eq("resource_type_id",resourceTypeId);
+        updateWrapper.set("is_delete",CommonConstant.DEL_FLAG_1);
         return this.update(updateWrapper);
     }
 
     @Override
-    public Boolean disable(Long id) {
-        if (Objects.isNull(id)) {
+    public boolean deleteByResourceIds(String resourceType, List<Long> resourceTypeIds) {
+        if (CollectionUtils.isEmpty(resourceTypeIds)) {
             return false;
         }
         UpdateWrapper updateWrapper = new UpdateWrapper<>();
-        updateWrapper.eq("id",id);
-        updateWrapper.set("enable", CommonConstant.DEL_FLAG_0);
+        updateWrapper.eq("resource_type",resourceType);
+        updateWrapper.in("resource_type_id",resourceTypeIds);
+        updateWrapper.set("is_delete",CommonConstant.DEL_FLAG_1);
         return this.update(updateWrapper);
     }
+
+    @Override
+    public List<T> listByResourceId(String resourceType, Long resourceTypeId) {
+        QueryWrapper wrapper = new QueryWrapper();
+        wrapper.eq("resource_type",resourceType);
+        wrapper.eq("resource_type_id",resourceTypeId);
+        return this.baseMapper.selectList(wrapper);
+    }
+
+    @Override
+    public List<T> listByResourceIds(String resourceType, List<Long> resourceTypeIds) {
+        QueryWrapper wrapper = new QueryWrapper();
+        wrapper.eq("resource_type",resourceType);
+        wrapper.in("resource_type_id",resourceTypeIds);
+        return this.baseMapper.selectList(wrapper);
+    }
+
+    private <E extends QueryBaseEntity> void assemblyDataSource(LambdaQueryWrapper<T> wrapper,E e,String mergeSql) {
+        try {
+            log.error("进入assemblyDataSource---LambdaQueryWrapper");
+            Object myClassInstance = SpringContextUtils.getBean("dataScope");
+            Class<?> clazz = myClassInstance.getClass();
+            Method method = clazz.getMethod("assemblyDataWrapperPurview", LambdaQueryWrapper.class,QueryBaseEntity.class,String.class);
+            // 调用未编写的方法
+            method.invoke(myClassInstance,wrapper,e,mergeSql);
+        } catch (Exception e1) {
+            log.error("方法不存在",e1);
+        }
+    }
+
+    private <E extends QueryBaseEntity> void assemblyDataOnlySource(LambdaQueryWrapper<T> wrapper,String mergeSql) {
+        try {
+            log.error("进入assemblyDataSource---LambdaQueryWrapper");
+            Object myClassInstance = SpringContextUtils.getBean("dataScope");
+            Class<?> clazz = myClassInstance.getClass();
+            Method method = clazz.getMethod("assemblyDataOnlyWrapper", LambdaQueryWrapper.class,String.class);
+            // 调用未编写的方法
+            method.invoke(myClassInstance,wrapper,mergeSql);
+        } catch (Exception e1) {
+            log.error("方法不存在",e1);
+        }
+    }
+
+    private <E extends QueryBaseEntity> void assemblyDataSource(E e) {
+        try {
+            log.error("assemblyDataSource---QueryBaseEntity");
+
+            // 假设我们有一个类名为"MyClass"但没有编写该类的实例
+            Class<?> clazz = Class.forName("DataScope");
+            // 使用反射创建一个实例
+            Object myClassInstance = clazz.newInstance();
+            // 使用反射获取方法
+            Method method = clazz.getMethod("assemblyDataEntityPurview", QueryBaseEntity.class);
+            // 调用未编写的方法
+            method.invoke(myClassInstance, e);
+        } catch (Exception e1) {
+            log.error("方法不存在",e1);
+        }
+    }
+
+    @Override
+    public T getNextById(Serializable currentId, LambdaQueryWrapper<T> queryWrapper) {
+        return findAdjacentById(currentId, queryWrapper, true);
+    }
+
+    @Override
+    public T getPreviousById(Serializable currentId, LambdaQueryWrapper<T> queryWrapper) {
+        return findAdjacentById(currentId, queryWrapper, false);
+    }
+
+    private T findAdjacentById(Serializable currentId, LambdaQueryWrapper<T> queryWrapper, boolean isNext) {
+        // 确保ID字段被正确排序
+//        LambdaQueryWrapper.orderByAsc("id");
+
+        try {
+            // 获取所有满足条件的数据
+            List<T> allData = listWithScope(queryWrapper);
+
+            // 查找目标ID的索引
+//            int index = allData.indexOf(allData.stream().filter(record -> currentId.equals(getIdFromRecord(record))).findFirst().orElseThrow());
+//
+//            // 检查索引是否有效
+//            if (index < 0 || index >= allData.size()) {
+//                return null;
+//            }
+            if(CollectionUtils.isEmpty(allData) || allData.size()==1){
+                return null;
+            }
+            return allData.get(1);
+
+            // 获取前一个和后一个元素(如果存在)
+//            if (isNext && index < allData.size() - 1){
+//                return allData.get(index + 1);
+//            }else if (!isNext && index > 0){
+//                return allData.get(index - 1);
+//            }
+//            return null;
+
+        }catch (Exception e){
+            return null;
+//            throw new IllegalStateException("Unexpected condition encountered while finding adjacent ID.");
+        }
+
+    }
+
+    // 假设所有实体类都有一个名为"id"的属性来获取ID,这里简化处理
+    private Serializable getIdFromRecord(T record) {
+        try {
+            return (Serializable) record.getClass().getMethod("getId").invoke(record);
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to get ID from record", e);
+        }
+    }
+
+//    public T getOldEntityById(Class<T> clazz, Long id) {
+//        return getById(id);
+//    }
+
+    public Class<T> getEntityClass(){
+        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
+
+    }
 }

+ 2 - 1
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerAnalysisResultEntity.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.storlead.framework.mybatis.entity.SysBaseField;
+import com.storlead.framework.mybatis.entity.TenantBaseField;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -15,7 +16,7 @@ import java.util.Date;
 @EqualsAndHashCode(callSuper = true)
 @TableName("customer_analysis_result")
 @ApiModel(value = "CustomerAnalysisResultEntity", description = "客户分析结果")
-public class CustomerAnalysisResultEntity extends SysBaseField {
+public class CustomerAnalysisResultEntity extends TenantBaseField {
     @TableId(type = IdType.AUTO)
     @ApiModelProperty(value = "主键ID")
     private Long id;

+ 1 - 1
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/impl/CustomerAiAnalysisServiceImpl.java

@@ -59,7 +59,7 @@ public class CustomerAiAnalysisServiceImpl implements CustomerAiAnalysisService
 
         CustomerAnalysisResultEntity entity = new CustomerAnalysisResultEntity();
         entity.setCustomerId(customer.getId());
-        entity.setTenantId(customer.getTenantId());
+//        entity.setTenantId(customer.getTenantId());
         entity.setOwnerBy(customer.getOwnerBy());
         entity.setAnalysisBatchNo(UUID.randomUUID().toString().replace("-", ""));
         entity.setAnalysisScene(scene);

+ 0 - 4
java/storlead-system/storlead-system-api/src/main/java/com/storlead/system/api/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 系统域 HTTP 接口:{@code RestController} 等放置于此包。
- */
-package com.storlead.system.api;

+ 1 - 7
java/storlead-system/storlead-system-biz/src/main/java/com/storlead/system/assemble/DataScope.java

@@ -7,7 +7,7 @@ import com.storlead.framework.auth.vo.LoginUser;
 import com.storlead.framework.common.util.ConvertUtils;
 import com.storlead.framework.mybatis.entity.SysBaseField;
 import com.storlead.framework.util.LoginUserUtil;
-import com.storlead.framework.web.assemble.QueryBaseEntity;
+import com.storlead.framework.mybatis.query.QueryBaseEntity;
 import com.storlead.system.pojo.entity.*;
 import com.storlead.system.service.*;
 import com.storlead.user.pojo.entity.DeptEntity;
@@ -15,16 +15,10 @@ import com.storlead.user.pojo.entity.UserEntity;
 import com.storlead.user.service.IDepartService;
 import com.storlead.user.service.IUserService;
 import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang3.reflect.FieldUtils;
-import org.apache.poi.ss.formula.functions.T;
 import org.springframework.stereotype.Component;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
-import java.lang.reflect.Field;
-import java.lang.reflect.Proxy;
 import java.util.*;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**

+ 2 - 1
java/storlead-thirdparty/pom.xml

@@ -17,7 +17,8 @@
 
     <modules>
         <module>storlead-thirdparty-api</module>
-        <module>storlead-thirdparty-core</module>
+        <module>storlead-thirdparty-spi</module>
+        <module>storlead-thirdparty-biz</module>
         <module>storlead-thirdparty-dingtalk</module>
     </modules>
 </project>

+ 33 - 0
java/storlead-thirdparty/storlead-thirdparty-biz/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-thirdparty</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-thirdparty-biz</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-thirdparty-biz</name>
+    <description>第三方默认实现:按渠道聚合 SPI 客户端,对外暴露 api 中的门面服务。</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-thirdparty-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-thirdparty-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 28 - 0
java/storlead-thirdparty/storlead-thirdparty-biz/src/main/java/com/storlead/thirdparty/biz/config/ThirdPartyBizAutoConfiguration.java

@@ -0,0 +1,28 @@
+package com.storlead.thirdparty.biz.config;
+
+import com.storlead.thirdparty.api.service.ThirdPartyUserSyncService;
+import com.storlead.thirdparty.biz.service.DefaultThirdPartyService;
+import com.storlead.thirdparty.spi.ThirdPartyDirectorySyncClient;
+import com.storlead.thirdparty.spi.ThirdPartyMessageClient;
+import com.storlead.thirdparty.spi.ThirdPartyUserSyncClient;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+/**
+ * 注册 {@link DefaultThirdPartyService},避免仅依赖组件扫描时漏扫。
+ */
+@Configuration(proxyBeanMethods = false)
+public class ThirdPartyBizAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(ThirdPartyUserSyncService.class)
+    public DefaultThirdPartyService defaultThirdPartyService(
+            List<ThirdPartyMessageClient> messageClientList,
+            List<ThirdPartyDirectorySyncClient> syncClientList,
+            List<ThirdPartyUserSyncClient> userSyncClientList) {
+        return new DefaultThirdPartyService(messageClientList, syncClientList, userSyncClientList);
+    }
+}

+ 6 - 6
java/storlead-thirdparty/storlead-thirdparty-core/src/main/java/com/storlead/thirdparty/core/service/DefaultThirdPartyService.java → java/storlead-thirdparty/storlead-thirdparty-biz/src/main/java/com/storlead/thirdparty/biz/service/DefaultThirdPartyService.java

@@ -1,4 +1,4 @@
-package com.storlead.thirdparty.core.service;
+package com.storlead.thirdparty.biz.service;
 
 import com.storlead.thirdparty.api.dto.ThirdPartyTextMessageDTO;
 import com.storlead.thirdparty.api.dto.ThirdPartyUserSyncDTO;
@@ -6,16 +6,16 @@ import com.storlead.thirdparty.api.enums.ThirdPartyChannel;
 import com.storlead.thirdparty.api.service.ThirdPartyDirectorySyncService;
 import com.storlead.thirdparty.api.service.ThirdPartyNotificationService;
 import com.storlead.thirdparty.api.service.ThirdPartyUserSyncService;
-import com.storlead.thirdparty.core.spi.ThirdPartyDirectorySyncClient;
-import com.storlead.thirdparty.core.spi.ThirdPartyMessageClient;
-import com.storlead.thirdparty.core.spi.ThirdPartyUserSyncClient;
-import org.springframework.stereotype.Service;
+import com.storlead.thirdparty.biz.config.ThirdPartyBizAutoConfiguration;
+import com.storlead.thirdparty.spi.ThirdPartyDirectorySyncClient;
+import com.storlead.thirdparty.spi.ThirdPartyMessageClient;
+import com.storlead.thirdparty.spi.ThirdPartyUserSyncClient;
 
 import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 
-@Service
+/** 由 {@link ThirdPartyBizAutoConfiguration} 注册为 Spring Bean */
 public class DefaultThirdPartyService implements ThirdPartyNotificationService, ThirdPartyDirectorySyncService, ThirdPartyUserSyncService {
     private final Map<ThirdPartyChannel, ThirdPartyMessageClient> messageClients = new EnumMap<>(ThirdPartyChannel.class);
     private final Map<ThirdPartyChannel, ThirdPartyDirectorySyncClient> syncClients = new EnumMap<>(ThirdPartyChannel.class);

+ 2 - 0
java/storlead-thirdparty/storlead-thirdparty-biz/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.storlead.thirdparty.biz.config.ThirdPartyBizAutoConfiguration

+ 1 - 1
java/storlead-thirdparty/storlead-thirdparty-dingtalk/pom.xml

@@ -18,7 +18,7 @@
     <dependencies>
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-thirdparty-core</artifactId>
+            <artifactId>storlead-thirdparty-spi</artifactId>
         </dependency>
     </dependencies>
 </project>

+ 3 - 6
java/storlead-thirdparty/storlead-thirdparty-core/pom.xml → java/storlead-thirdparty/storlead-thirdparty-spi/pom.xml

@@ -11,18 +11,15 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>storlead-thirdparty-core</artifactId>
+    <artifactId>storlead-thirdparty-spi</artifactId>
     <packaging>jar</packaging>
-    <name>storlead-thirdparty-core</name>
+    <name>storlead-thirdparty-spi</name>
+    <description>第三方通道扩展契约(SPI):各渠道实现消息、通讯录、用户同步等客户端接口。</description>
 
     <dependencies>
         <dependency>
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-thirdparty-api</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter</artifactId>
-        </dependency>
     </dependencies>
 </project>

+ 1 - 1
java/storlead-thirdparty/storlead-thirdparty-core/src/main/java/com/storlead/thirdparty/core/spi/ThirdPartyDirectorySyncClient.java → java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/ThirdPartyDirectorySyncClient.java

@@ -1,4 +1,4 @@
-package com.storlead.thirdparty.core.spi;
+package com.storlead.thirdparty.spi;
 
 import com.storlead.thirdparty.api.enums.ThirdPartyChannel;
 

+ 1 - 1
java/storlead-thirdparty/storlead-thirdparty-core/src/main/java/com/storlead/thirdparty/core/spi/ThirdPartyMessageClient.java → java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/ThirdPartyMessageClient.java

@@ -1,4 +1,4 @@
-package com.storlead.thirdparty.core.spi;
+package com.storlead.thirdparty.spi;
 
 import com.storlead.thirdparty.api.dto.ThirdPartyTextMessageDTO;
 import com.storlead.thirdparty.api.enums.ThirdPartyChannel;

+ 1 - 1
java/storlead-thirdparty/storlead-thirdparty-core/src/main/java/com/storlead/thirdparty/core/spi/ThirdPartyUserSyncClient.java → java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/ThirdPartyUserSyncClient.java

@@ -1,4 +1,4 @@
-package com.storlead.thirdparty.core.spi;
+package com.storlead.thirdparty.spi;
 
 import com.storlead.thirdparty.api.dto.ThirdPartyUserSyncDTO;
 import com.storlead.thirdparty.api.enums.ThirdPartyChannel;

+ 4 - 0
java/storlead-thirdparty/storlead-thirdparty-spi/src/main/java/com/storlead/thirdparty/spi/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 第三方通道 SPI:由各具体渠道模块(如企微、钉钉)实现,由 {@code storlead-thirdparty-biz} 聚合路由。
+ */
+package com.storlead.thirdparty.spi;

+ 1 - 1
java/storlead-thirdparty/storlead-thirdparty-wecom/pom.xml

@@ -39,7 +39,7 @@
 
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-thirdparty-core</artifactId>
+            <artifactId>storlead-thirdparty-spi</artifactId>
         </dependency>
 
         <dependency>

+ 4 - 0
java/storlead-user/storlead-user-biz/pom.xml

@@ -37,6 +37,10 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-thirdparty-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-thirdparty-biz</artifactId>
+        </dependency>
         <dependency>
             <groupId>cn.stylefeng.roses</groupId>
             <artifactId>pinyin-sdk-pinyin4j</artifactId>

+ 14 - 0
java/storlead-user/storlead-user-biz/src/main/java/com/storlead/user/service/impl/TenantEnterpriseServiceImpl.java

@@ -1,6 +1,8 @@
 package com.storlead.user.service.impl;
 
+import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.storlead.framework.common.constant.CommonConstant;
 import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
 import com.storlead.user.mapper.TenantEnterpriseMapper;
 import com.storlead.user.pojo.entity.TenantEnterpriseEntity;
@@ -18,6 +20,18 @@ public class TenantEnterpriseServiceImpl extends MyBaseServiceImpl<TenantEnterpr
         }
         return getOne(Wrappers.<TenantEnterpriseEntity>lambdaQuery()
                 .eq(TenantEnterpriseEntity::getTenantId, tenantId)
+                .eq(TenantEnterpriseEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
+                .last("LIMIT 1"));
+    }
+
+    @Override
+    public TenantEnterpriseEntity getByEnterpriseCode(String enterpriseCode) {
+        if (StrUtil.isBlank(enterpriseCode)) {
+            return null;
+        }
+        return getOne(Wrappers.<TenantEnterpriseEntity>lambdaQuery()
+                .eq(TenantEnterpriseEntity::getEnterpriseCode, StrUtil.trim(enterpriseCode))
+                .eq(TenantEnterpriseEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
                 .last("LIMIT 1"));
     }
 }

+ 2 - 0
java/storlead-user/storlead-user-biz/src/main/java/com/storlead/user/service/impl/TenantUserServiceImpl.java

@@ -1,6 +1,7 @@
 package com.storlead.user.service.impl;
 
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.storlead.framework.common.constant.CommonConstant;
 import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
 import com.storlead.user.mapper.TenantUserMapper;
 import com.storlead.user.pojo.entity.TenantUserEntity;
@@ -22,6 +23,7 @@ public class TenantUserServiceImpl extends MyBaseServiceImpl<TenantUserMapper, T
         return getOne(Wrappers.<TenantUserEntity>lambdaQuery()
                 .eq(TenantUserEntity::getTenantId, tenantId)
                 .eq(TenantUserEntity::getUserId, userId)
+                .eq(TenantUserEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
                 .last("LIMIT 1"));
     }
 

+ 33 - 0
java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/dto/TenantLoginDTO.java

@@ -0,0 +1,33 @@
+package com.storlead.user.pojo.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 租户空间登录参数。
+ * <p>
+ * 是否在「某个租户」下可用,<b>唯一依据</b>是表 {@code tenant_user}:先用 {@code user} 表完成<b>短信验证码或密码</b>登录校验,
+ * 再通过 {@code tenant_user}(tenant_id + user_id)判定是否属于该租户。
+ */
+@Data
+@ApiModel("租户登录参数")
+public class TenantLoginDTO implements Serializable {
+
+    @ApiModelProperty(value = "租户数据行 ID(tenant_enterprise.tenant_id),与 enterpriseCode 二选一必填")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "企业编码(tenant_enterprise.enterprise_code),与 tenantId 二选一必填")
+    private String enterpriseCode;
+
+    @ApiModelProperty(value = "登录账号,一般为手机号")
+    private String account;
+
+    @ApiModelProperty(value = "密码(与短信二选一)")
+    private String password;
+
+    @ApiModelProperty(value = "短信验证码(与密码二选一,需配合 account 手机号)")
+    private String smsCode;
+}

+ 1 - 1
java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/dto/UserParam.java

@@ -1,6 +1,6 @@
 package com.storlead.user.pojo.dto;
 
-import com.storlead.framework.web.assemble.QueryBaseEntity;
+import com.storlead.framework.mybatis.query.QueryBaseEntity;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;

+ 2 - 1
java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/entity/TenantEnterpriseEntity.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.storlead.framework.mybatis.entity.SysBaseField;
+import com.storlead.framework.mybatis.entity.TenantBaseField;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -22,7 +23,7 @@ import java.util.Date;
 @Accessors(chain = true)
 @ApiModel(value = "TenantEnterpriseEntity", description = "企业租户主表")
 @TableName("tenant_enterprise")
-public class TenantEnterpriseEntity extends SysBaseField {
+public class TenantEnterpriseEntity extends TenantBaseField {
 
     private static final long serialVersionUID = 1L;
 

+ 4 - 2
java/storlead-user/storlead-user-core/src/main/java/com/storlead/user/pojo/entity/TenantUserEntity.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.storlead.framework.mybatis.entity.SysBaseField;
+import com.storlead.framework.mybatis.entity.TenantBaseField;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -15,14 +16,15 @@ import org.springframework.format.annotation.DateTimeFormat;
 import java.util.Date;
 
 /**
- * 企业-账号关系表(租户用户)
+ * 租户用户表 {@code tenant_user}:表示某平台用户({@code userId})在某个租户({@code tenantId} / {@code enterpriseId})下的成员关系,
+ * 租户登录、授权范围应以此表为准。
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
 @Accessors(chain = true)
 @ApiModel(value = "TenantUserEntity", description = "企业-账号关系表")
 @TableName("tenant_user")
-public class TenantUserEntity extends SysBaseField {
+public class TenantUserEntity extends TenantBaseField {
 
     private static final long serialVersionUID = 1L;
 

+ 5 - 0
java/storlead-user/storlead-user-spi/src/main/java/com/storlead/user/service/ITenantEnterpriseService.java

@@ -12,4 +12,9 @@ public interface ITenantEnterpriseService extends MyBaseService<TenantEnterprise
      * 按租户ID查询(uk_tenant_id 下最多一条)
      */
     TenantEnterpriseEntity getByTenantId(Long tenantId);
+
+    /**
+     * 按企业编码查询(未删除、启用由调用方或 SQL 条件保证)
+     */
+    TenantEnterpriseEntity getByEnterpriseCode(String enterpriseCode);
 }

+ 4 - 1
java/storlead-user/storlead-user-spi/src/main/java/com/storlead/user/service/ITenantUserService.java

@@ -6,10 +6,13 @@ import com.storlead.user.pojo.entity.TenantUserEntity;
 import java.util.List;
 
 /**
- * 租户用户(企业-账号关系)
+ * 租户侧用户成员:<b>以表 {@code tenant_user} 为准</b>(平台账号在 {@code user} 表,二者通过 user_id 关联)。
  */
 public interface ITenantUserService extends MyBaseService<TenantUserEntity> {
 
+    /**
+     * 按租户行 tenant_id + 平台用户 id 查询 {@code tenant_user}。
+     */
     TenantUserEntity getByTenantIdAndUserId(Long tenantId, Long userId);
 
     List<TenantUserEntity> listByEnterpriseId(Long enterpriseId);

+ 6 - 4
java/项目模块结构说明.md

@@ -55,7 +55,8 @@ storlead-saas-platform
 │  └─ storlead-knowledge-api
 ├─ storlead-thirdparty
 │  ├─ storlead-thirdparty-api
-│  ├─ storlead-thirdparty-core
+│  ├─ storlead-thirdparty-spi
+│  ├─ storlead-thirdparty-biz
 │  └─ storlead-thirdparty-dingtalk
 ├─ storlead-account
 │  ├─ storlead-account-api
@@ -116,9 +117,10 @@ storlead-saas-platform
 
 ### 3.3 第三方主链
 
-- `storlead-thirdparty-core` -> `storlead-thirdparty-api`
-- `storlead-thirdparty-dingtalk` -> `storlead-thirdparty-core`
-- `storlead-thirdparty-wecom`(目录存在)-> `storlead-thirdparty-core`、`storlead-common`、`storlead-web`、`storlead-redis`、`storlead-mybatis`
+- `storlead-thirdparty-spi` -> `storlead-thirdparty-api`(对外扩展契约:`com.storlead.thirdparty.spi`)
+- `storlead-thirdparty-biz` -> `storlead-thirdparty-api`、`storlead-thirdparty-spi`(默认聚合实现与自动配置)
+- `storlead-thirdparty-dingtalk` -> `storlead-thirdparty-spi`
+- `storlead-thirdparty-wecom`(目录存在)-> `storlead-thirdparty-spi`、`storlead-common`、`storlead-web`、`storlead-redis`、`storlead-mybatis`
 
 ## 4. 补充说明
 

+ 6 - 1
pom.xml

@@ -290,7 +290,12 @@
 
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-thirdparty-core</artifactId>
+            <artifactId>storlead-thirdparty-spi</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-thirdparty-biz</artifactId>
             <version>${revision}</version>
         </dependency>