瀏覽代碼

first commit

gufj 5 月之前
當前提交
c1e8969dca
共有 100 個文件被更改,包括 6750 次插入0 次删除
  1. 25 0
      .gitignore
  2. 0 0
      LICENSE
  3. 0 0
      README.md
  4. 35 0
      bound-link-api/.gitignore
  5. 27 0
      bound-link-api/blink-admin/pom.xml
  6. 39 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/config/MvcConfig.java
  7. 25 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/config/OperateLogAspectConfig.java
  8. 56 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/constant/AdminCacheConst.java
  9. 16 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/constant/AdminRedisKeyConst.java
  10. 56 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/constant/AdminSwaggerTagConst.java
  11. 186 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/interceptor/AdminInterceptor.java
  12. 22 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/listener/AdminStartupRunner.java
  13. 49 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/DataScope.java
  14. 37 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/DataScopeController.java
  15. 185 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/MyBatisPlugin.java
  16. 55 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/constant/DataScopeTypeEnum.java
  17. 64 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/constant/DataScopeViewTypeEnum.java
  18. 50 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/constant/DataScopeWhereInTypeEnum.java
  19. 34 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeAndViewTypeVO.java
  20. 31 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeDTO.java
  21. 40 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeSqlConfig.java
  22. 27 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeViewTypeVO.java
  23. 70 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/service/DataScopeService.java
  24. 156 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/service/DataScopeSqlConfigService.java
  25. 146 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/service/DataScopeViewService.java
  26. 22 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/strategy/AbstractDataScopeStrategy.java
  27. 69 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/controller/DepartmentController.java
  28. 36 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/dao/DepartmentDao.java
  29. 74 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/entity/DepartmentEntity.java
  30. 37 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/form/DepartmentAddForm.java
  31. 23 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/form/DepartmentUpdateForm.java
  32. 26 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/vo/DepartmentEmployeeTreeVO.java
  33. 31 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/vo/DepartmentTreeVO.java
  34. 46 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/vo/DepartmentVO.java
  35. 92 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/flowSub/FlowDept.java
  36. 94 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/flowSub/TreeNode.java
  37. 241 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/manager/DepartmentCacheManager.java
  38. 144 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/service/DepartmentService.java
  39. 129 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/controller/EmployeeController.java
  40. 120 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/dao/EmployeeDao.java
  41. 95 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java
  42. 67 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java
  43. 30 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeBatchUpdateDepartmentForm.java
  44. 39 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeQueryForm.java
  45. 25 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdateAvatarForm.java
  46. 22 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdateForm.java
  47. 29 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java
  48. 29 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdateRoleForm.java
  49. 67 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/vo/EmployeeVO.java
  50. 68 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/job/InitSystemAccountTask.java
  51. 85 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/manager/EmployeeManager.java
  52. 389 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/service/EmployeeService.java
  53. 100 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/controller/LoginController.java
  54. 40 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/domain/LoginForm.java
  55. 40 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/domain/LoginResultVO.java
  56. 96 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/domain/RequestEmployee.java
  57. 39 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/manager/factory/LoginStrategyFactory.java
  58. 682 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/service/LoginService.java
  59. 23 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/strategy/LoginStrategy.java
  60. 92 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/strategy/impl/DingTalkScanLoginStrategy.java
  61. 26 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/strategy/impl/UsernamePasswordLoginStrategy.java
  62. 39 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/constant/MenuPermsTypeEnum.java
  63. 45 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/constant/MenuTypeEnum.java
  64. 80 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/controller/MenuController.java
  65. 94 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/dao/MenuDao.java
  66. 133 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/entity/MenuEntity.java
  67. 17 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuAddForm.java
  68. 79 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuBaseForm.java
  69. 40 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuPointsOperateForm.java
  70. 23 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuUpdateForm.java
  71. 34 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/vo/MenuSimpleTreeVO.java
  72. 19 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/vo/MenuTreeVO.java
  73. 32 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/vo/MenuVO.java
  74. 229 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/service/MenuService.java
  75. 75 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/controller/PositionController.java
  76. 42 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/dao/PositionDao.java
  77. 61 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/entity/PositionEntity.java
  78. 34 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/form/PositionAddForm.java
  79. 23 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/form/PositionQueryForm.java
  80. 24 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/form/PositionUpdateForm.java
  81. 40 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/vo/PositionVO.java
  82. 20 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/manager/PositionManager.java
  83. 105 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/service/PositionService.java
  84. 64 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleController.java
  85. 44 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleDataScopeController.java
  86. 71 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleEmployeeController.java
  87. 41 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleMenuController.java
  88. 28 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleDao.java
  89. 37 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleDataScopeDao.java
  90. 85 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleEmployeeDao.java
  91. 45 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleMenuDao.java
  92. 50 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleDataScopeEntity.java
  93. 38 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleEmployeeEntity.java
  94. 54 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleEntity.java
  95. 46 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleMenuEntity.java
  96. 41 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleAddForm.java
  97. 40 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleDataScopeUpdateForm.java
  98. 21 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleEmployeeQueryForm.java
  99. 27 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleEmployeeUpdateForm.java
  100. 32 0
      bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleMenuUpdateForm.java

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### front ###
+**/dist
+**/node_modules
+**/.vscode
+

+ 0 - 0
LICENSE


+ 0 - 0
README.md


+ 35 - 0
bound-link-api/.gitignore

@@ -0,0 +1,35 @@
+HELP.md
+target/
+
+velocity.log
+
+!.mvn/wrapper/maven-wrapper.jar
+
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+.DS_Store
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/

+ 27 - 0
bound-link-api/blink-admin/pom.xml

@@ -0,0 +1,27 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         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.cloud</groupId>
+        <artifactId>blink-parent</artifactId>
+        <version>3.0.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>blink-admin</artifactId>
+    <version>3.0.0</version>
+    <packaging>jar</packaging>
+
+    <name>blink-admin</name>
+    <description>blink-admin project</description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.cloud</groupId>
+            <artifactId>blink-base</artifactId>
+            <version>3.0.0</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 39 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/config/MvcConfig.java

@@ -0,0 +1,39 @@
+package com.cloud.sa.admin.config;
+
+import com.cloud.sa.admin.interceptor.AdminInterceptor;
+import com.cloud.sa.base.config.SwaggerConfig;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+
+/**
+ * web相关配置
+ *
+ * @Author 畅联云: admin
+ * @Date 2021-09-02 20:21:10
+ */
+@Configuration
+public class MvcConfig implements WebMvcConfigurer {
+
+    @Resource
+    private AdminInterceptor adminInterceptor;
+
+
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(adminInterceptor)
+                .excludePathPatterns(SwaggerConfig.SWAGGER_WHITELIST)
+                .addPathPatterns("/**");
+    }
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
+        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+    }
+
+}

+ 25 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/config/OperateLogAspectConfig.java

@@ -0,0 +1,25 @@
+package com.cloud.sa.admin.config;
+
+import com.cloud.sa.base.module.support.operatelog.core.OperateLogAspect;
+import com.cloud.sa.base.module.support.operatelog.core.OperateLogConfig;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 操作日志切面 配置
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-05-30 21:22:12
+ */
+@Configuration
+public class OperateLogAspectConfig extends OperateLogAspect{
+
+    /**
+     * 配置信息
+     */
+    @Override
+    public OperateLogConfig getOperateLogConfig() {
+        return OperateLogConfig.builder().corePoolSize(1).queueCapacity(10000).build();
+    }
+
+
+}

+ 56 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/constant/AdminCacheConst.java

@@ -0,0 +1,56 @@
+package com.cloud.sa.admin.constant;
+
+import com.cloud.sa.base.constant.CacheKeyConst;
+
+/**
+ * 缓存 key
+ *
+ * @Author 云畅联:admin
+ * @Date 2022-01-07 18:59:22
+
+ * 
+ */
+public class AdminCacheConst extends CacheKeyConst {
+
+    public static class Department {
+
+        /**
+         * 部门列表
+         */
+        public static final String DEPARTMENT_LIST_CACHE = "department_list_cache";
+
+        /**
+         * 部门map
+         */
+        public static final String DEPARTMENT_MAP_CACHE = "department_map_cache";
+
+        /**
+         * 部门树
+         */
+        public static final String DEPARTMENT_TREE_CACHE = "department_tree_cache";
+
+        /**
+         * 某个部门以及下级的id列表
+         */
+        public static final String DEPARTMENT_SELF_CHILDREN_CACHE = "department_self_children_cache";
+
+        /**
+         * 部门路径 缓存
+         */
+        public static final String DEPARTMENT_PATH_CACHE = "department_path_cache";
+
+    }
+
+    /**
+     * 分类相关缓存
+     */
+    public static class Category {
+
+        public static final String CATEGORY_ENTITY = "category_cache";
+
+        public static final String CATEGORY_SUB = "category_sub_cache";
+
+        public static final String CATEGORY_TREE = "category_tree_cache";
+    }
+
+}

+ 16 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/constant/AdminRedisKeyConst.java

@@ -0,0 +1,16 @@
+package com.cloud.sa.admin.constant;
+
+import com.cloud.sa.base.constant.RedisKeyConst;
+
+/**
+ * redis key 常量类
+ *
+ * @Author 云畅联:admin
+ * @Date 2022-01-07 18:59:22
+
+ *
+ */
+public class AdminRedisKeyConst extends RedisKeyConst {
+
+
+}

+ 56 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/constant/AdminSwaggerTagConst.java

@@ -0,0 +1,56 @@
+package com.cloud.sa.admin.constant;
+
+import com.cloud.sa.base.constant.SwaggerTagConst;
+
+/**
+ * swagger
+ *
+ * @Author 云畅联:admin
+ * @Date 2022-01-07 18:59:22
+
+ *
+ */
+public class AdminSwaggerTagConst extends SwaggerTagConst {
+
+    public static class Business {
+        public static final String MANAGER_CATEGORY = "ERP进销存-分类管理";
+
+        public static final String MANAGER_GOODS = "ERP进销存-商品管理";
+
+        public static final String OA_BANK = "OA办公-银行卡信息";
+
+        public static final String OA_ENTERPRISE = "OA办公-企业";
+
+        public static final String OA_INVOICE = "OA办公-发票信息";
+
+        public static final String OA_NOTICE = "OA办公-通知公告";
+
+    }
+
+
+    public static class System {
+
+        public static final String SYSTEM_LOGIN = "系统-员工登录";
+
+        public static final String SYSTEM_EMPLOYEE = "系统-员工管理";
+
+        public static final String SYSTEM_DEPARTMENT = "系统-部门管理";
+
+        public static final String SYSTEM_MENU = "系统-菜单";
+
+        public static final String SYSTEM_DATA_SCOPE = "系统-系统-数据范围";
+
+        public static final String SYSTEM_ROLE = "系统-角色";
+
+        public static final String SYSTEM_ROLE_DATA_SCOPE = "系统-角色-数据范围";
+
+        public static final String SYSTEM_ROLE_EMPLOYEE = "系统-角色-员工";
+
+        public static final String SYSTEM_ROLE_MENU = "系统-角色-菜单";
+
+        public static final String SYSTEM_POSITION = "系统-职务管理";
+
+    }
+
+
+}

+ 186 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/interceptor/AdminInterceptor.java

@@ -0,0 +1,186 @@
+package com.cloud.sa.admin.interceptor;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.dev33.satoken.exception.SaTokenException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.strategy.SaStrategy;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+import com.cloud.sa.admin.module.system.login.domain.RequestEmployee;
+import com.cloud.sa.admin.module.system.login.service.LoginService;
+import com.cloud.sa.base.common.annoation.NoNeedLogin;
+import com.cloud.sa.base.common.code.SystemErrorCode;
+import com.cloud.sa.base.common.code.UserErrorCode;
+import com.cloud.sa.base.common.constant.StringConst;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.domain.SystemEnvironment;
+import com.cloud.sa.base.common.enumeration.SystemEnvironmentEnum;
+import com.cloud.sa.base.common.enumeration.UserTypeEnum;
+import com.cloud.sa.base.common.util.BlinkRequestUtil;
+import com.cloud.sa.base.common.util.BlinkResponseUtil;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+/**
+ * admin 拦截器
+ *
+ * @Author 云畅联:admin
+ * @Date 2023/7/26 20:20:33
+
+ * Since 2012
+ */
+
+@Component
+@Slf4j
+public class AdminInterceptor implements HandlerInterceptor {
+
+    @Resource
+    private LoginService loginService;
+
+    @Resource
+    private SystemEnvironment systemEnvironment;
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+
+        // OPTIONS请求直接return
+        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
+            response.setStatus(HttpStatus.NO_CONTENT.value());
+            return false;
+        }
+
+        boolean isHandler = handler instanceof HandlerMethod;
+        if (!isHandler) {
+            return true;
+        }
+
+        try {
+            // --------------- 第一步: 根据token 获取用户 ---------------
+
+            String tokenValue = StpUtil.getTokenValue();
+            boolean debugNumberTokenFlag = isDevDebugNumberToken(tokenValue);
+
+            String loginId = null;
+            if (debugNumberTokenFlag) {
+                //开发、测试环境,且为数字的话,则表明为 调试临时用户,即需要调用 sa-token switch
+                loginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + tokenValue;
+                StpUtil.switchTo(loginId);
+            } else {
+                loginId = (String) StpUtil.getLoginIdByToken(tokenValue);
+            }
+
+            RequestEmployee requestEmployee = loginService.getLoginEmployee(loginId, request);
+
+            // --------------- 第二步: 校验 登录 ---------------
+
+            Method method = ((HandlerMethod) handler).getMethod();
+            NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
+            if (noNeedLogin != null) {
+                checkActiveTimeout(requestEmployee,debugNumberTokenFlag);
+                return true;
+            }
+
+            if (requestEmployee == null) {
+                BlinkResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID));
+                return false;
+            }
+
+            // 检测token 活跃频率
+            checkActiveTimeout(requestEmployee,debugNumberTokenFlag);
+
+
+            // --------------- 第三步: 校验 权限 ---------------
+
+            BlinkRequestUtil.setRequestUser(requestEmployee);
+            if (SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {
+                return true;
+            }
+
+            // 如果是超级管理员的话,不需要校验权限
+            if(requestEmployee.getAdministratorFlag()){
+               return true;
+            }
+
+            SaStrategy.instance.checkMethodAnnotation.accept(method);
+
+        } catch (SaTokenException e) {
+            /*
+             * sa-token 异常状态码
+             * 具体请看: https://sa-token.cc/doc.html#/fun/exception-code
+             */
+            int code = e.getCode();
+            if (code == 11041 || code == 11051) {
+                BlinkResponseUtil.write(response, ResponseDTO.error(UserErrorCode.NO_PERMISSION));
+            } else if (code == 11016) {
+                BlinkResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_ACTIVE_TIMEOUT));
+            } else if (code >= 11011 && code <= 11015) {
+                BlinkResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID));
+            } else {
+                BlinkResponseUtil.write(response, ResponseDTO.error(UserErrorCode.PARAM_ERROR));
+            }
+            return false;
+        } catch (Throwable e) {
+            BlinkResponseUtil.write(response, ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR));
+            log.error(e.getMessage(), e);
+            return false;
+        }
+
+        // 通过验证
+        return true;
+    }
+
+
+    /**
+     * 检测:token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结
+     */
+    private void checkActiveTimeout(RequestEmployee requestEmployee, boolean debugNumberTokenFlag) {
+
+        // 对于开发环境的 数字 debug token ,不需要检测活跃有效期
+        if (debugNumberTokenFlag) {
+            return;
+        }
+
+        // 用户不在线,也不用检测
+        if (requestEmployee == null) {
+            return;
+        }
+
+        StpUtil.checkActiveTimeout();
+        StpUtil.updateLastActiveToNow();
+    }
+
+
+    /**
+     * 是否为开发使用的 debug token
+     *
+     * @param token
+     * @return
+     */
+    private boolean isDevDebugNumberToken(String token) {
+        if (!StrUtil.isNumeric(token)) {
+            return false;
+        }
+        return systemEnvironment.getCurrentEnvironment() == SystemEnvironmentEnum.DEV
+                || systemEnvironment.getCurrentEnvironment() == SystemEnvironmentEnum.TEST;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        // 清除上下文
+        BlinkRequestUtil.remove();
+        // 开发环境,关闭 sa token 的临时切换用户
+        if (systemEnvironment.getCurrentEnvironment() == SystemEnvironmentEnum.DEV) {
+            StpUtil.endSwitch();
+        }
+    }
+
+
+}

+ 22 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/listener/AdminStartupRunner.java

@@ -0,0 +1,22 @@
+package com.cloud.sa.admin.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * admin 应用启动加载
+ *
+ * @Author 云畅联:admin
+ * @Date 2021-08-26 18:46:32
+
+ */
+@Slf4j
+@Component
+public class AdminStartupRunner implements CommandLineRunner {
+
+
+    @Override
+    public void run(String... args) {
+    }
+}

+ 49 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/DataScope.java

@@ -0,0 +1,49 @@
+package com.cloud.sa.admin.module.system.datascope;
+
+
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeWhereInTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.strategy.AbstractDataScopeStrategy;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 数据范围
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-18 20:59:17
+
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface DataScope {
+
+    DataScopeTypeEnum dataScopeType() ;
+
+    DataScopeWhereInTypeEnum whereInType() default DataScopeWhereInTypeEnum.EMPLOYEE;
+
+    /**
+     * DataScopeWhereInTypeEnum.CUSTOM_STRATEGY类型 才可使用joinSqlImplClazz属性
+     */
+    Class<? extends AbstractDataScopeStrategy> joinSqlImplClazz()  default AbstractDataScopeStrategy.class;
+
+    /**
+     * 多个参数已逗号分隔,本属性主要用于joinSqlImplClazz 实现类跟进参数进行不同的范围控制,如不使用CUSTOM_STRATEGY,可不做配置
+     */
+    String paramName() default "";
+    /**
+     *
+     * 第几个where 条件 从0开始
+     */
+    int whereIndex() default 0;
+
+    /**
+     * DataScopeWhereInTypeEnum为CUSTOM_STRATEGY类型时,此属性无效
+     */
+    String joinSql() default "";
+
+}

+ 37 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/DataScopeController.java

@@ -0,0 +1,37 @@
+package com.cloud.sa.admin.module.system.datascope;
+
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import com.cloud.sa.admin.module.system.datascope.domain.DataScopeAndViewTypeVO;
+import com.cloud.sa.admin.module.system.datascope.service.DataScopeService;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 查询支持的数据范围类型
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-18 20:59:17
+
+ * 
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_DATA_SCOPE)
+public class DataScopeController {
+
+    @Resource
+    private DataScopeService dataScopeService;
+
+    @Operation(summary = "获取当前系统所配置的所有数据范围 @author admin")
+    @GetMapping("/dataScope/list")
+    public ResponseDTO<List<DataScopeAndViewTypeVO>> dataScopeList() {
+        return dataScopeService.dataScopeList();
+    }
+
+
+}

+ 185 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/MyBatisPlugin.java

@@ -0,0 +1,185 @@
+package com.cloud.sa.admin.module.system.datascope;
+
+import cn.hutool.core.util.StrUtil;
+import com.cloud.sa.admin.module.system.datascope.domain.DataScopeSqlConfig;
+import com.cloud.sa.admin.module.system.datascope.service.DataScopeSqlConfigService;
+import com.google.common.collect.Maps;
+import com.cloud.sa.base.common.domain.DataScopePlugin;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.mapping.*;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Plugin;
+import org.apache.ibatis.plugin.Signature;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * mybaits sql 拦截
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-18 20:59:17
+
+ *
+ */
+@Intercepts({@Signature(type = org.apache.ibatis.executor.Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
+@Component
+public class MyBatisPlugin extends DataScopePlugin {
+
+    @Resource
+    private ApplicationContext applicationContext;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+
+        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
+        Object parameter = invocation.getArgs()[1];
+
+        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
+        String originalSql = boundSql.getSql().trim();
+        String id = mappedStatement.getId();
+        List<String> methodStrList = StrUtil.split(id, ".");
+        String path = methodStrList.get(methodStrList.size() - 2) + "." + methodStrList.get(methodStrList.size() - 1);
+        DataScopeSqlConfigService dataScopeSqlConfigService = this.dataScopeSqlConfigService();
+        if (dataScopeSqlConfigService == null) {
+            return invocation.proceed();
+        }
+        DataScopeSqlConfig sqlConfigDTO = dataScopeSqlConfigService.getSqlConfig(path);
+        if (sqlConfigDTO != null) {
+            Map<String, Object> paramMap = this.getParamList(sqlConfigDTO.getParamName(), parameter);
+            BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, this.joinSql(originalSql, paramMap, sqlConfigDTO));
+            ParameterMap map = mappedStatement.getParameterMap();
+            MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql), map);
+            invocation.getArgs()[0] = newMs;
+        }
+
+        Object obj = invocation.proceed();
+        return obj;
+    }
+
+
+    private Map<String, Object> getParamList(String paramName, Object parameter) {
+        Map<String, Object> paramMap = Maps.newHashMap();
+        if (StringUtils.isEmpty(paramName)) {
+            return paramMap;
+        }
+        if (parameter == null) {
+            return paramMap;
+        }
+        if (parameter instanceof Map) {
+            String[] paramNameArray = paramName.split(",");
+            Map<?, ?> parameterMap = (Map) parameter;
+            for (String param : paramNameArray) {
+                if(parameterMap.containsKey(param)){
+                    paramMap.put(param, parameterMap.get(param));
+                }
+            }
+        }
+        return paramMap;
+    }
+
+    private String joinSql(String sql, Map<String, Object> paramMap, DataScopeSqlConfig sqlConfigDTO) {
+        if (null == sqlConfigDTO) {
+            return sql;
+        }
+        String appendSql = this.dataScopeSqlConfigService().getJoinSql(paramMap, sqlConfigDTO);
+        if (StringUtils.isEmpty(appendSql)) {
+            return sql;
+        }
+        Integer appendSqlWhereIndex = sqlConfigDTO.getWhereIndex();
+        String where = "where";
+        String order = "order by";
+        String group = "group by";
+        int whereIndex = StringUtils.ordinalIndexOf(sql.toLowerCase(), where, appendSqlWhereIndex + 1);
+        int orderIndex = sql.toLowerCase().indexOf(order);
+        int groupIndex = sql.toLowerCase().indexOf(group);
+        if (whereIndex > -1) {
+            String subSql = sql.substring(0, whereIndex + where.length() + 1);
+            subSql = subSql + " " + appendSql + " AND " + sql.substring(whereIndex + where.length() + 1);
+            return subSql;
+        }
+
+        if (groupIndex > -1) {
+            String subSql = sql.substring(0, groupIndex);
+            subSql = subSql + " where " + appendSql + " " + sql.substring(groupIndex);
+            return subSql;
+        }
+        if (orderIndex > -1) {
+            String subSql = sql.substring(0, orderIndex);
+            subSql = subSql + " where " + appendSql + " " + sql.substring(orderIndex);
+            return subSql;
+        }
+        sql += " where " + appendSql;
+        return sql;
+    }
+
+    public DataScopeSqlConfigService dataScopeSqlConfigService() {
+        return (DataScopeSqlConfigService) applicationContext.getBean("dataScopeSqlConfigService");
+    }
+
+    public class BoundSqlSqlSource implements SqlSource {
+
+        BoundSql boundSql;
+
+        public BoundSqlSqlSource(BoundSql boundSql) {
+            this.boundSql = boundSql;
+        }
+
+        @Override
+        public BoundSql getBoundSql(Object parameterObject) {
+            return boundSql;
+        }
+    }
+
+    /**
+     * 复制MappedStatement对象
+     */
+    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource, ParameterMap parameterMap) {
+
+        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
+        builder.resource(ms.getResource());
+        builder.fetchSize(ms.getFetchSize());
+        builder.statementType(ms.getStatementType());
+        builder.keyGenerator(ms.getKeyGenerator());
+        builder.timeout(ms.getTimeout());
+        builder.parameterMap(parameterMap);
+        builder.resultMaps(ms.getResultMaps());
+        builder.resultSetType(ms.getResultSetType());
+        builder.cache(ms.getCache());
+        builder.flushCacheRequired(ms.isFlushCacheRequired());
+        builder.useCache(ms.isUseCache());
+        return builder.build();
+    }
+
+    /**
+     * 复制BoundSql对象
+     */
+    private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) {
+        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
+        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
+            String prop = mapping.getProperty();
+            if (boundSql.hasAdditionalParameter(prop)) {
+                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
+            }
+        }
+        return newBoundSql;
+    }
+
+    @Override
+    public Object plugin(Object arg0) {
+        return Plugin.wrap(arg0, this);
+    }
+
+    @Override
+    public void setProperties(Properties arg0) {
+
+    }
+
+}

+ 55 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/constant/DataScopeTypeEnum.java

@@ -0,0 +1,55 @@
+package com.cloud.sa.admin.module.system.datascope.constant;
+
+import com.cloud.sa.base.common.enumeration.BaseEnum;
+
+/**
+ * 数据范围 类型
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ *
+ */
+public enum DataScopeTypeEnum implements BaseEnum {
+
+    /**
+     * 系统通知
+     */
+    NOTICE(1, 20, "系统通知", "系统通知数据范围"),
+    ;
+
+    private final Integer value;
+
+    private final Integer sort;
+
+    private final String name;
+
+    private final String desc;
+
+    DataScopeTypeEnum(Integer value, Integer sort, String name, String desc) {
+        this.value = value;
+        this.sort = sort;
+        this.name = name;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    public Integer getSort() {
+        return sort;
+    }
+
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+
+}

+ 64 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/constant/DataScopeViewTypeEnum.java

@@ -0,0 +1,64 @@
+package com.cloud.sa.admin.module.system.datascope.constant;
+
+
+import com.cloud.sa.base.common.enumeration.BaseEnum;
+
+
+/**
+ * 数据范围 种类
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ *
+ */
+public enum DataScopeViewTypeEnum implements BaseEnum {
+
+    /**
+     * 本人
+     */
+    ME(0, 0, "本人"),
+
+    /**
+     * 部门
+     */
+    DEPARTMENT(1, 5, "本部门"),
+
+    /**
+     * 本部门及下属子部门
+     */
+    DEPARTMENT_AND_SUB(2, 10, "本部门及下属子部门"),
+
+    /**
+     * 全部
+     */
+    ALL(10, 100, "全部");
+
+
+
+    private final Integer value;
+    private final Integer level;
+    private final String desc;
+
+    DataScopeViewTypeEnum(Integer value, Integer level, String desc) {
+        this.value = value;
+        this.level = level;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    public Integer getLevel() {
+        return level;
+    }
+
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+
+
+}

+ 50 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/constant/DataScopeWhereInTypeEnum.java

@@ -0,0 +1,50 @@
+package com.cloud.sa.admin.module.system.datascope.constant;
+
+
+import com.cloud.sa.base.common.enumeration.BaseEnum;
+
+/**
+ * 数据范围 sql where
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ *
+ */
+public enum DataScopeWhereInTypeEnum implements BaseEnum {
+
+    /**
+     * 以员工IN
+     */
+    EMPLOYEE(0, "以员工IN"),
+
+    /**
+     * 以部门IN
+     */
+    DEPARTMENT(1, "以部门IN"),
+
+    /**
+     * 自定义策略
+     */
+    CUSTOM_STRATEGY(2, "自定义策略");
+
+    private final Integer value;
+    private final String desc;
+
+    DataScopeWhereInTypeEnum(Integer value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+
+
+}

+ 34 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeAndViewTypeVO.java

@@ -0,0 +1,34 @@
+package com.cloud.sa.admin.module.system.datascope.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 数据范围
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ * 
+ */
+@Data
+public class DataScopeAndViewTypeVO {
+
+    @Schema(description = "数据范围类型")
+    private Integer dataScopeType;
+
+    @Schema(description = "数据范围名称")
+    private String dataScopeTypeName;
+
+    @Schema(description = "描述")
+    private String dataScopeTypeDesc;
+
+    @Schema(description = "顺序")
+    private Integer dataScopeTypeSort;
+
+    @Schema(description = "可见范围列表")
+    private List<DataScopeViewTypeVO> viewTypeList;
+
+}

+ 31 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeDTO.java

@@ -0,0 +1,31 @@
+package com.cloud.sa.admin.module.system.datascope.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 数据范围
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ *
+ */
+@Data
+@Builder
+public class DataScopeDTO {
+
+    @Schema(description = "数据范围类型")
+    private Integer dataScopeType;
+
+    @Schema(description = "数据范围名称")
+    private String dataScopeTypeName;
+
+    @Schema(description = "描述")
+    private String dataScopeTypeDesc;
+
+    @Schema(description = "顺序")
+    private Integer dataScopeTypeSort;
+
+}

+ 40 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeSqlConfig.java

@@ -0,0 +1,40 @@
+package com.cloud.sa.admin.module.system.datascope.domain;
+
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeWhereInTypeEnum;
+import lombok.Data;
+
+/**
+ * 数据范围
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ *
+ */
+@Data
+public class DataScopeSqlConfig {
+
+    /**
+     * 数据范围类型
+     * {@link DataScopeTypeEnum}
+     */
+    private DataScopeTypeEnum dataScopeType;
+
+    /**
+     * join sql 具体实现类
+     */
+    private Class<?> joinSqlImplClazz;
+
+    private String joinSql;
+
+    private Integer whereIndex;
+
+    private String paramName;
+
+    /**
+     * whereIn类型
+     * {@link DataScopeWhereInTypeEnum}
+     */
+    private DataScopeWhereInTypeEnum dataScopeWhereInType;
+}

+ 27 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/domain/DataScopeViewTypeVO.java

@@ -0,0 +1,27 @@
+package com.cloud.sa.admin.module.system.datascope.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 数据范围
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ * 
+ */
+@Data
+@Builder
+public class DataScopeViewTypeVO {
+
+    @Schema(description = "可见范围")
+    private Integer viewType;
+
+    @Schema(description = "可见范围名称")
+    private String viewTypeName;
+
+    @Schema(description = "级别,用于表示范围大小")
+    private Integer viewTypeLevel;
+}

+ 70 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/service/DataScopeService.java

@@ -0,0 +1,70 @@
+package com.cloud.sa.admin.module.system.datascope.service;
+
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.domain.DataScopeAndViewTypeVO;
+import com.cloud.sa.admin.module.system.datascope.domain.DataScopeDTO;
+import com.cloud.sa.admin.module.system.datascope.domain.DataScopeViewTypeVO;
+import com.google.common.collect.Lists;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.util.BlinkBeanUtil;
+import org.springframework.stereotype.Service;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * 数据范围 保存
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ *
+ */
+@Service
+public class DataScopeService {
+
+    /**
+     * 获取所有可以进行数据范围配置的信息
+     */
+    public ResponseDTO<List<DataScopeAndViewTypeVO>> dataScopeList() {
+        List<DataScopeDTO> dataScopeList = this.getDataScopeType();
+        List<DataScopeAndViewTypeVO> dataScopeAndTypeList = BlinkBeanUtil.copyList(dataScopeList, DataScopeAndViewTypeVO.class);
+        List<DataScopeViewTypeVO> typeList = this.getViewType();
+        dataScopeAndTypeList.forEach(e -> {
+            e.setViewTypeList(typeList);
+        });
+        return ResponseDTO.ok(dataScopeAndTypeList);
+    }
+
+    /**
+     * 获取当前系统存在的数据可见范围
+     */
+    public List<DataScopeViewTypeVO> getViewType() {
+        List<DataScopeViewTypeVO> viewTypeList = Lists.newArrayList();
+        DataScopeViewTypeEnum[] enums = DataScopeViewTypeEnum.class.getEnumConstants();
+        DataScopeViewTypeVO dataScopeViewTypeDTO;
+        for (DataScopeViewTypeEnum viewTypeEnum : enums) {
+            dataScopeViewTypeDTO = DataScopeViewTypeVO.builder().viewType(viewTypeEnum.getValue()).viewTypeLevel(viewTypeEnum.getLevel()).viewTypeName(viewTypeEnum.getDesc()).build();
+            viewTypeList.add(dataScopeViewTypeDTO);
+        }
+        Comparator<DataScopeViewTypeVO> comparator = (h1, h2) -> h1.getViewTypeLevel().compareTo(h2.getViewTypeLevel());
+        viewTypeList.sort(comparator);
+        return viewTypeList;
+    }
+
+    public List<DataScopeDTO> getDataScopeType() {
+        List<DataScopeDTO> dataScopeTypeList = Lists.newArrayList();
+        DataScopeTypeEnum[] enums = DataScopeTypeEnum.class.getEnumConstants();
+        DataScopeDTO dataScopeDTO;
+        for (DataScopeTypeEnum typeEnum : enums) {
+            dataScopeDTO =
+                    DataScopeDTO.builder().dataScopeType(typeEnum.getValue()).dataScopeTypeDesc(typeEnum.getDesc()).dataScopeTypeName(typeEnum.getName()).dataScopeTypeSort(typeEnum.getSort()).build();
+            dataScopeTypeList.add(dataScopeDTO);
+        }
+        Comparator<DataScopeDTO> comparator = (h1, h2) -> h1.getDataScopeTypeSort().compareTo(h2.getDataScopeTypeSort());
+        dataScopeTypeList.sort(comparator);
+        return dataScopeTypeList;
+    }
+
+}

+ 156 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/service/DataScopeSqlConfigService.java

@@ -0,0 +1,156 @@
+package com.cloud.sa.admin.module.system.datascope.service;
+
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeWhereInTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.domain.DataScopeSqlConfig;
+import com.cloud.sa.admin.module.system.datascope.strategy.AbstractDataScopeStrategy;
+import lombok.extern.slf4j.Slf4j;
+import com.cloud.sa.admin.module.system.datascope.DataScope;
+import com.cloud.sa.base.common.util.BlinkRequestUtil;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.reflections.Reflections;
+import org.reflections.scanners.MethodAnnotationsScanner;
+import org.reflections.util.ClasspathHelper;
+import org.reflections.util.ConfigurationBuilder;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * sql配置
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ *
+ */
+@Slf4j
+@Service
+public class DataScopeSqlConfigService {
+
+    /**
+     * 注解joinsql 参数
+     */
+    private static final String EMPLOYEE_PARAM = "#employeeIds";
+
+    private static final String DEPARTMENT_PARAM = "#departmentIds";
+
+    /**
+     * 用于拼接查看本人数据范围的 SQL
+     */
+    private static final String CREATE_USER_ID_EQUALS = "create_user_id = ";
+
+
+    private final ConcurrentHashMap<String, DataScopeSqlConfig> dataScopeMethodMap = new ConcurrentHashMap<>();
+
+    @Resource
+    private DataScopeViewService dataScopeViewService;
+
+    @Resource
+    private ApplicationContext applicationContext;
+
+
+    @PostConstruct
+    private void initDataScopeMethodMap() {
+        this.refreshDataScopeMethodMap();
+    }
+
+    /**
+     * 刷新 所有添加数据范围注解的接口方法配置<class.method,DataScopeSqlConfigDTO></>
+     */
+    private Map<String, DataScopeSqlConfig> refreshDataScopeMethodMap() {
+        Reflections reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("com.cloud.sa.*")).setScanners(new MethodAnnotationsScanner()));
+        Set<Method> methods = reflections.getMethodsAnnotatedWith(DataScope.class);
+        for (Method method : methods) {
+            DataScope dataScopeAnnotation = method.getAnnotation(DataScope.class);
+            if (dataScopeAnnotation != null) {
+                DataScopeSqlConfig configDTO = new DataScopeSqlConfig();
+                configDTO.setDataScopeType(dataScopeAnnotation.dataScopeType());
+                configDTO.setJoinSql(dataScopeAnnotation.joinSql());
+                configDTO.setWhereIndex(dataScopeAnnotation.whereIndex());
+                configDTO.setDataScopeWhereInType(dataScopeAnnotation.whereInType());
+                configDTO.setParamName(dataScopeAnnotation.paramName());
+                configDTO.setJoinSqlImplClazz(dataScopeAnnotation.joinSqlImplClazz());
+                dataScopeMethodMap.put(method.getDeclaringClass().getSimpleName() + "." + method.getName(), configDTO);
+            }
+        }
+        return dataScopeMethodMap;
+    }
+
+    /**
+     * 根据调用的方法获取,此方法的配置信息
+     *
+     */
+    public DataScopeSqlConfig getSqlConfig(String method) {
+        return this.dataScopeMethodMap.get(method);
+    }
+
+    /**
+     * 组装需要拼接的sql
+     */
+    public String getJoinSql(Map<String, Object> paramMap, DataScopeSqlConfig sqlConfigDTO) {
+//        DataScopeTypeEnum dataScopeTypeEnum = sqlConfigDTO.getDataScopeType();
+//        String joinSql = sqlConfigDTO.getJoinSql();
+        Long employeeId = BlinkRequestUtil.getRequestUserId();
+        if (employeeId == null) {
+            return "";
+        }
+
+        DataScopeTypeEnum dataScopeTypeEnum = sqlConfigDTO.getDataScopeType();
+        DataScopeViewTypeEnum viewTypeEnum = dataScopeViewService.getEmployeeDataScopeViewType(dataScopeTypeEnum, employeeId);
+
+        // 数据权限设置为仅本人可见时 直接返回 create_user_id = employeeId
+        if (DataScopeViewTypeEnum.ME == viewTypeEnum) {
+            return CREATE_USER_ID_EQUALS + employeeId;
+        }
+
+        String joinSql = sqlConfigDTO.getJoinSql();
+
+        if (DataScopeWhereInTypeEnum.CUSTOM_STRATEGY == sqlConfigDTO.getDataScopeWhereInType()) {
+            //Class strategyClass = sqlConfigDTO.getJoinSqlImplClazz();
+            Class<?> strategyClass = sqlConfigDTO.getJoinSqlImplClazz();
+            if (strategyClass == null) {
+                log.warn("data scope custom strategy class is null");
+                return "";
+            }
+            AbstractDataScopeStrategy powerStrategy = (AbstractDataScopeStrategy) applicationContext.getBean(sqlConfigDTO.getJoinSqlImplClazz());
+            if (powerStrategy == null) {
+                log.warn("data scope custom strategy class:{} ,bean is null", sqlConfigDTO.getJoinSqlImplClazz());
+                return "";
+            }
+//            DataScopeViewTypeEnum viewTypeEnum = dataScopeViewService.getEmployeeDataScopeViewType(dataScopeTypeEnum, employeeId);
+//            return powerStrategy.getCondition(viewTypeEnum,paramMap, sqlConfigDTO);
+            return powerStrategy.getCondition(viewTypeEnum, paramMap, sqlConfigDTO);
+        }
+        if (DataScopeWhereInTypeEnum.EMPLOYEE == sqlConfigDTO.getDataScopeWhereInType()) {
+            //List<Long> canViewEmployeeIds = dataScopeViewService.getCanViewEmployeeId(dataScopeTypeEnum, employeeId);
+            List<Long> canViewEmployeeIds = dataScopeViewService.getCanViewEmployeeId(viewTypeEnum, employeeId);
+            if (CollectionUtils.isEmpty(canViewEmployeeIds)) {
+                return "";
+            }
+            String employeeIds = StringUtils.join(canViewEmployeeIds, ",");
+            String sql = joinSql.replaceAll(EMPLOYEE_PARAM, employeeIds);
+            return sql;
+        }
+        if (DataScopeWhereInTypeEnum.DEPARTMENT == sqlConfigDTO.getDataScopeWhereInType()) {
+            //List<Long> canViewDepartmentIds = dataScopeViewService.getCanViewDepartmentId(dataScopeTypeEnum, employeeId);
+            List<Long> canViewDepartmentIds = dataScopeViewService.getCanViewDepartmentId(viewTypeEnum, employeeId);
+            if (CollectionUtils.isEmpty(canViewDepartmentIds)) {
+                return "";
+            }
+            String departmentIds = StringUtils.join(canViewDepartmentIds, ",");
+            String sql = joinSql.replaceAll(DEPARTMENT_PARAM, departmentIds);
+            return sql;
+        }
+        return "";
+    }
+}

+ 146 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/service/DataScopeViewService.java

@@ -0,0 +1,146 @@
+package com.cloud.sa.admin.module.system.datascope.service;
+
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum;
+import com.cloud.sa.admin.module.system.department.service.DepartmentService;
+import com.cloud.sa.admin.module.system.employee.dao.EmployeeDao;
+import com.cloud.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
+import com.cloud.sa.admin.module.system.role.dao.RoleDataScopeDao;
+import com.cloud.sa.admin.module.system.role.dao.RoleEmployeeDao;
+import com.cloud.sa.admin.module.system.role.domain.entity.RoleDataScopeEntity;
+import com.cloud.sa.base.common.util.BlinkEnumUtil;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 数据范围
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+ */
+@Service
+public class DataScopeViewService {
+
+    @Resource
+    private RoleEmployeeDao roleEmployeeDao;
+
+    @Resource
+    private RoleDataScopeDao roleDataScopeDao;
+
+    @Resource
+    private EmployeeDao employeeDao;
+
+    @Resource
+    private DepartmentService departmentService;
+
+    /**
+     * 获取某人可以查看的所有人员信息
+     */
+//    public List<Long> getCanViewEmployeeId(DataScopeTypeEnum dataScopeTypeEnum, Long employeeId) {
+//        DataScopeViewTypeEnum viewType = this.getEmployeeDataScopeViewType(dataScopeTypeEnum, employeeId);
+    public List<Long> getCanViewEmployeeId(DataScopeViewTypeEnum viewType, Long employeeId) {
+        if (DataScopeViewTypeEnum.ME == viewType) {
+            return this.getMeEmployeeIdList(employeeId);
+        }
+        if (DataScopeViewTypeEnum.DEPARTMENT == viewType) {
+            return this.getDepartmentEmployeeIdList(employeeId);
+        }
+        if (DataScopeViewTypeEnum.DEPARTMENT_AND_SUB == viewType) {
+            return this.getDepartmentAndSubEmployeeIdList(employeeId);
+        }
+        return Lists.newArrayList();
+    }
+
+    /**
+     * 获取某人可以查看的所有部门信息
+     */
+//    public List<Long> getCanViewDepartmentId(DataScopeTypeEnum dataScopeTypeEnum, Long employeeId) {
+//        DataScopeViewTypeEnum viewType = this.getEmployeeDataScopeViewType(dataScopeTypeEnum, employeeId);
+    public List<Long> getCanViewDepartmentId(DataScopeViewTypeEnum viewType, Long employeeId) {
+        if (DataScopeViewTypeEnum.ME == viewType) {
+            //return this.getMeDepartmentIdList(employeeId);
+            // 数据可见范围类型为本人时 不可以查看任何部门数据
+            return Lists.newArrayList(0L);
+        }
+        if (DataScopeViewTypeEnum.DEPARTMENT == viewType) {
+            return this.getMeDepartmentIdList(employeeId);
+        }
+        if (DataScopeViewTypeEnum.DEPARTMENT_AND_SUB == viewType) {
+            return this.getDepartmentAndSubIdList(employeeId);
+        }
+        return Lists.newArrayList();
+    }
+
+    public List<Long> getMeDepartmentIdList(Long employeeId) {
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        return Lists.newArrayList(employeeEntity.getDepartmentId());
+    }
+
+    public List<Long> getDepartmentAndSubIdList(Long employeeId) {
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        return departmentService.selfAndChildrenIdList(employeeEntity.getDepartmentId());
+    }
+
+    /**
+     * 根据员工id 获取各数据范围最大的可见范围 map<dataScopeType,viewType></>
+     */
+    public DataScopeViewTypeEnum getEmployeeDataScopeViewType(DataScopeTypeEnum dataScopeTypeEnum, Long employeeId) {
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        if (employeeEntity == null || employeeEntity.getEmployeeId() == null) {
+//        if (employeeId == null) {
+            return DataScopeViewTypeEnum.ME;
+        }
+
+        List<Long> roleIdList = roleEmployeeDao.selectRoleIdByEmployeeId(employeeId);
+        //未设置角色 默认本人
+        if (CollectionUtils.isEmpty(roleIdList)) {
+            return DataScopeViewTypeEnum.ME;
+        }
+        // 如果是超级管理员 则可查看全部
+        if (employeeEntity.getAdministratorFlag()) {
+            return DataScopeViewTypeEnum.ALL;
+        }
+        //未设置角色数据范围 默认本人
+        List<RoleDataScopeEntity> dataScopeRoleList = roleDataScopeDao.listByRoleIdList(roleIdList);
+        if (CollectionUtils.isEmpty(dataScopeRoleList)) {
+            return DataScopeViewTypeEnum.ME;
+        }
+        Map<Integer, List<RoleDataScopeEntity>> listMap = dataScopeRoleList.stream().collect(Collectors.groupingBy(RoleDataScopeEntity::getDataScopeType));
+        List<RoleDataScopeEntity> viewLevelList = listMap.getOrDefault(dataScopeTypeEnum.getValue(), Lists.newArrayList());
+        if (CollectionUtils.isEmpty(viewLevelList)) {
+            return DataScopeViewTypeEnum.ME;
+        }
+        RoleDataScopeEntity maxLevel = viewLevelList.stream().max(Comparator.comparing(e -> BlinkEnumUtil.getEnumByValue(e.getViewType(), DataScopeViewTypeEnum.class).getLevel())).get();
+        return BlinkEnumUtil.getEnumByValue(maxLevel.getViewType(), DataScopeViewTypeEnum.class);
+    }
+
+    /**
+     * 获取本人相关 可查看员工id
+     */
+    private List<Long> getMeEmployeeIdList(Long employeeId) {
+        return Lists.newArrayList(employeeId);
+    }
+
+    /**
+     * 获取本部门相关 可查看员工id
+     */
+    private List<Long> getDepartmentEmployeeIdList(Long employeeId) {
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        return employeeDao.getEmployeeIdByDepartmentId(employeeEntity.getDepartmentId(), false);
+    }
+
+    /**
+     * 获取本部门及下属子部门相关 可查看员工id
+     */
+    private List<Long> getDepartmentAndSubEmployeeIdList(Long employeeId) {
+        List<Long> allDepartmentIds = getDepartmentAndSubIdList(employeeId);
+        return employeeDao.getEmployeeIdByDepartmentIdList(allDepartmentIds, false);
+    }
+}

+ 22 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/datascope/strategy/AbstractDataScopeStrategy.java

@@ -0,0 +1,22 @@
+package com.cloud.sa.admin.module.system.datascope.strategy;
+
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.domain.DataScopeSqlConfig;
+
+import java.util.Map;
+
+/**
+ * 数据范围策略 ,使用DataScopeWhereInTypeEnum.CUSTOM_STRATEGY类型,DataScope注解的joinSql属性无用
+ *
+ * @Author 云畅联: admin
+ * @Date 2020/11/28  20:59:17
+
+ * 
+ */
+public abstract class AbstractDataScopeStrategy {
+
+    /**
+     * 获取joinsql 字符串
+     */
+    public abstract String getCondition(DataScopeViewTypeEnum viewTypeEnum, Map<String, Object> paramMap, DataScopeSqlConfig sqlConfigDTO);
+}

+ 69 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/controller/DepartmentController.java

@@ -0,0 +1,69 @@
+package com.cloud.sa.admin.module.system.department.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import com.cloud.sa.admin.module.system.department.domain.form.DepartmentAddForm;
+import com.cloud.sa.admin.module.system.department.flowSub.TreeNode;
+import com.cloud.sa.admin.module.system.department.service.DepartmentService;
+import com.cloud.sa.base.common.annoation.NoNeedLogin;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.module.system.department.domain.form.DepartmentUpdateForm;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentTreeVO;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentVO;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * 部门
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_DEPARTMENT)
+public class DepartmentController {
+
+    @Resource
+    private DepartmentService departmentService;
+
+    @Operation(summary = "查询部门树形列表 @author admin")
+    @GetMapping("/department/treeList")
+    public ResponseDTO<List<DepartmentTreeVO>> departmentTree() {
+        return departmentService.departmentTree();
+    }
+
+    @Operation(summary = "添加部门 @author admin")
+    @PostMapping("/department/add")
+    @SaCheckPermission("system:department:add")
+    public ResponseDTO<String> addDepartment(@Valid @RequestBody DepartmentAddForm createDTO) {
+        return departmentService.addDepartment(createDTO);
+    }
+
+    @Operation(summary = "更新部门 @author admin")
+    @PostMapping("/department/update")
+    @SaCheckPermission("system:department:update")
+    public ResponseDTO<String> updateDepartment(@Valid @RequestBody DepartmentUpdateForm updateDTO) {
+        return departmentService.updateDepartment(updateDTO);
+    }
+
+    @Operation(summary = "删除部门 @author admin")
+    @GetMapping("/department/delete/{departmentId}")
+    @SaCheckPermission("system:department:delete")
+    public ResponseDTO<String> deleteDepartment(@PathVariable Long departmentId) {
+        return departmentService.deleteDepartment(departmentId);
+    }
+
+    @Operation(summary = "查询部门列表 @author admin")
+    @GetMapping("/department/listAll")
+    public ResponseDTO<List<DepartmentVO>> listAll() {
+        return ResponseDTO.ok(departmentService.listAll());
+    }
+
+}

+ 36 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/dao/DepartmentDao.java

@@ -0,0 +1,36 @@
+package com.cloud.sa.admin.module.system.department.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cloud.sa.admin.module.system.department.domain.entity.DepartmentEntity;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentVO;
+import com.cloud.sa.admin.module.system.department.flowSub.FlowDept;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 部门
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ *
+ */
+@Component
+@Mapper
+public interface DepartmentDao extends BaseMapper<DepartmentEntity> {
+
+    /**
+     * 根据部门id,查询此部门直接子部门的数量
+     *
+     */
+    Integer countSubDepartment(@Param("departmentId") Long departmentId);
+
+    /**
+     * 获取全部部门列表
+     */
+    List<DepartmentVO> listAll();
+
+}

+ 74 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/entity/DepartmentEntity.java

@@ -0,0 +1,74 @@
+package com.cloud.sa.admin.module.system.department.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 部门实体类
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ *
+ */
+@Data
+@TableName(value = "mate_department")
+public class DepartmentEntity {
+
+    /**
+     * 主键id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long departmentId;
+
+    /**
+     * 部门名称
+     */
+    private String name;
+
+    /**
+     * 负责人员工 id
+     */
+    private Long managerId;
+
+    /**
+     * 部门父级id
+     */
+    private Long parentId;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+
+    /**
+     * 部门编码
+     */
+    private String code;
+
+    /**
+     *000010/000530/000010
+     */
+    private String treeSorts;
+
+    /**
+     * 100/f9cecda6f4ea45c3a4280d4066f3e827/370d9cdd8f894b339e7cf78b9b8e1e5e
+     */
+    private String parentIds;
+}

+ 37 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/form/DepartmentAddForm.java

@@ -0,0 +1,37 @@
+package com.cloud.sa.admin.module.system.department.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 部门 添加表单
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ *
+ */
+@Data
+public class DepartmentAddForm {
+
+    @Schema(description = "部门名称")
+    @Length(min = 1, max = 50, message = "请输入正确的部门名称(1-50个字符)")
+    @NotNull(message = "请输入正确的部门名称(1-50个字符)")
+    private String name;
+
+    @Schema(description = "部门编码")
+    private String code;
+    @Schema(description = "排序")
+    @NotNull(message = "排序值")
+    private Integer sort;
+
+    @Schema(description = "部门负责人id")
+    private Long managerId;
+
+    @Schema(description = "上级部门id (可选)")
+    private Long parentId;
+
+}

+ 23 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/form/DepartmentUpdateForm.java

@@ -0,0 +1,23 @@
+package com.cloud.sa.admin.module.system.department.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 部门 更新表单
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ *
+ */
+@Data
+public class DepartmentUpdateForm extends DepartmentAddForm {
+
+    @Schema(description = "部门id")
+    @NotNull(message = "部门id不能为空")
+    private Long departmentId;
+
+}

+ 26 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/vo/DepartmentEmployeeTreeVO.java

@@ -0,0 +1,26 @@
+package com.cloud.sa.admin.module.system.department.domain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.admin.module.system.employee.domain.vo.EmployeeVO;
+
+import java.util.List;
+
+/**
+ * 部门
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ *
+ */
+@Data
+public class DepartmentEmployeeTreeVO extends DepartmentVO {
+
+    @Schema(description = "部门员工列表")
+    private List<EmployeeVO> employees;
+
+    @Schema(description = "子部门")
+    private List<DepartmentEmployeeTreeVO> children;
+
+}

+ 31 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/vo/DepartmentTreeVO.java

@@ -0,0 +1,31 @@
+package com.cloud.sa.admin.module.system.department.domain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 部门
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ * 
+ */
+@Data
+public class DepartmentTreeVO extends DepartmentVO {
+
+    @Schema(description = "同级上一个元素id")
+    private Long preId;
+
+    @Schema(description = "同级下一个元素id")
+    private Long nextId;
+
+    @Schema(description = "子部门")
+    private List<DepartmentTreeVO> children;
+
+    @Schema(description = "自己和所有递归子部门的id集合")
+    private List<Long> selfAndAllChildrenIdList;
+
+}

+ 46 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/domain/vo/DepartmentVO.java

@@ -0,0 +1,46 @@
+package com.cloud.sa.admin.module.system.department.domain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 部门
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ *
+ */
+@Data
+public class DepartmentVO {
+
+    @Schema(description = "部门id")
+    private Long departmentId;
+
+    @Schema(description = "部门名称")
+    private String name;
+
+    @Schema(description = "部门编码")
+    private String code;
+
+    @Schema(description = "部门负责人姓名")
+    private String managerName;
+
+    @Schema(description = "部门负责人id")
+    private Long managerId;
+
+    @Schema(description = "父级部门id")
+    private Long parentId;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+}

+ 92 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/flowSub/FlowDept.java

@@ -0,0 +1,92 @@
+package com.cloud.sa.admin.module.system.department.flowSub;
+import lombok.Data;
+
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 部门表 sys_dept
+ *
+ * @author uoyi
+ */
+@Data
+public class FlowDept
+{
+    private static final long serialVersionUID = 1L;
+    /**
+     * 主键ID
+     */
+    private String id;
+
+    /** 部门编号 */
+    @NotBlank(message = "部门编码不能为空")
+    @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符")
+    private String deptCode;
+
+    /** 部门名称 */
+    @NotBlank(message = "部门名称不能为空")
+    @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符")
+    private String deptName;
+
+    /** 负责人 */
+    private String leader;
+    private String code;
+
+    /** 联系电话 */
+    @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符")
+    private String phone;
+
+    /** 邮箱 */
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    private String email;
+
+    /** 部门状态:0正常,1停用 */
+    private String status;
+
+    @Size(min = 0, max = 200, message = "部门全称长度不能超过200个字符")
+    private String deptFullName;
+    /**部门类型*/
+    private String deptType;
+    /**联系地址*/
+    private String address;
+    /**邮编*/
+    private String zipCode;
+
+    private String deptPinyin;
+
+    private String subtitle;
+    /**
+     * 检索字符串,支持名称,编码,拼音
+     */
+    private String searchText;
+
+    private String parentName;
+
+    private String parentDeptType;
+
+    /** 父ID */
+    private String parentId;
+
+    /** 所有父ID */
+    private String parentIds;
+
+    /** 本级排序号 */
+    private Integer treeSort;
+
+    /** 本级排序号和所有父级排序号 */
+    private String treeSorts;
+
+    /** 树层级 */
+    private Integer treeLevel;
+
+    /** 是否叶子节点 */
+    private String treeLeaf;
+
+    /** 子部门 */
+    private List<?> children = new ArrayList<>();
+}

+ 94 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/flowSub/TreeNode.java

@@ -0,0 +1,94 @@
+package com.cloud.sa.admin.module.system.department.flowSub;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Treeselect树结构实体类
+ *
+ * @author ruoyi
+ */
+@Data
+public class TreeNode implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 节点ID */
+    private String id;
+    private String code;
+
+    /** 节点ID */
+    private String key;
+
+    /** 节点名称 */
+    private String label;
+    /** 节点名称 */
+    private String title;
+
+    /** 节点类型:备用 */
+    private String type;
+
+    /** 是否可选择 */
+    private Boolean selectable;
+
+    /** 是否禁用 */
+    private Boolean disabled;
+
+    /** 复选框是否禁用 */
+    private Boolean disableCheckbox;
+
+    /** 是否叶子节点 */
+    private Boolean isLeaf;
+
+    /** 树层级 */
+    private Boolean treeLevel;
+
+    /** 树图标 */
+    private String treeIcon;
+
+    /** 父节点ID */
+    private String parentIds;
+
+    /** 父节点ID */
+    private String parentId;
+
+    /** 子节点 */
+    private List<TreeNode> children;
+
+    private HashMap<String,Object> attributes;
+
+    public JSONObject getSlots() {
+        return slots;
+    }
+
+    public void setSlots(JSONObject slots) {
+        this.slots = slots;
+    }
+
+    /**
+     * 使用 treeNodes 时,可以通过该属性
+     */
+    private JSONObject slots;
+    /**
+     * 使用 columns 时,可以通过该属性
+     */
+    private JSONObject scopedSlots;
+
+    public JSONObject getScopedSlots() {
+        return scopedSlots;
+    }
+
+    public void setScopedSlots(JSONObject scopedSlots) {
+        this.scopedSlots = scopedSlots;
+    }
+
+    public TreeNode()
+    {
+
+    }
+
+}

+ 241 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/manager/DepartmentCacheManager.java

@@ -0,0 +1,241 @@
+package com.cloud.sa.admin.module.system.department.manager;
+
+import com.cloud.sa.admin.constant.AdminCacheConst;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import lombok.extern.slf4j.Slf4j;
+import com.cloud.sa.admin.module.system.department.dao.DepartmentDao;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentTreeVO;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentVO;
+import com.cloud.sa.base.common.util.BlinkBeanUtil;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 部门 缓存相关
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+
+ * 
+ */
+@Slf4j
+@Service
+public class DepartmentCacheManager {
+
+    @Resource
+    private DepartmentDao departmentDao;
+
+    private void logClearInfo(String cache) {
+        log.info("clear " + cache);
+    }
+
+    @CacheEvict(value = {AdminCacheConst.Department.DEPARTMENT_LIST_CACHE, AdminCacheConst.Department.DEPARTMENT_MAP_CACHE, AdminCacheConst.Department.DEPARTMENT_SELF_CHILDREN_CACHE, AdminCacheConst.Department.DEPARTMENT_TREE_CACHE, AdminCacheConst.Department.DEPARTMENT_PATH_CACHE,}, allEntries = true)
+    public void clearCache() {
+        logClearInfo(AdminCacheConst.Department.DEPARTMENT_LIST_CACHE);
+    }
+
+
+    /**
+     * 部门列表
+     */
+    @Cacheable(AdminCacheConst.Department.DEPARTMENT_LIST_CACHE)
+    public List<DepartmentVO> getDepartmentList() {
+        return departmentDao.listAll();
+    }
+
+    /**
+     * 部门map
+     *
+     */
+    @Cacheable(AdminCacheConst.Department.DEPARTMENT_MAP_CACHE)
+    public Map<Long, DepartmentVO> getDepartmentMap() {
+        return departmentDao.listAll().stream().collect(Collectors.toMap(DepartmentVO::getDepartmentId, Function.identity()));
+    }
+
+
+    /**
+     * 缓存部门树结构
+     *
+     */
+    @Cacheable(AdminCacheConst.Department.DEPARTMENT_TREE_CACHE)
+    public List<DepartmentTreeVO> getDepartmentTree() {
+        List<DepartmentVO> departmentVOList = departmentDao.listAll();
+        return this.buildTree(departmentVOList);
+    }
+
+    /**
+     * 缓存某个部门的下级id列表
+     *
+     */
+    @Cacheable(AdminCacheConst.Department.DEPARTMENT_SELF_CHILDREN_CACHE)
+    public List<Long> getDepartmentSelfAndChildren(Long departmentId) {
+        List<DepartmentVO> departmentVOList = departmentDao.listAll();
+        return this.selfAndChildrenIdList(departmentId, departmentVOList);
+    }
+
+
+    /**
+     * 部门的路径名称
+     *
+     */
+    @Cacheable(AdminCacheConst.Department.DEPARTMENT_PATH_CACHE)
+    public Map<Long, String> getDepartmentPathMap() {
+        List<DepartmentVO> departmentVOList = departmentDao.listAll();
+        Map<Long, DepartmentVO> departmentMap = departmentVOList.stream().collect(Collectors.toMap(DepartmentVO::getDepartmentId, Function.identity()));
+
+        Map<Long, String> pathNameMap = Maps.newHashMap();
+        for (DepartmentVO departmentVO : departmentVOList) {
+            String pathName = this.buildDepartmentPath(departmentVO, departmentMap);
+            pathNameMap.put(departmentVO.getDepartmentId(), pathName);
+        }
+
+        return pathNameMap;
+    }
+
+    /**
+     * 构建父级考点路径
+     */
+    private String buildDepartmentPath(DepartmentVO departmentVO, Map<Long, DepartmentVO> departmentMap) {
+        if (Objects.equals(departmentVO.getParentId(), NumberUtils.LONG_ZERO)) {
+            return departmentVO.getName();
+        }
+        //父节点
+        DepartmentVO parentDepartment = departmentMap.get(departmentVO.getParentId());
+        if (parentDepartment == null) {
+            return departmentVO.getName();
+        }
+        String pathName = buildDepartmentPath(parentDepartment, departmentMap);
+        return pathName + "/" + departmentVO.getName();
+
+    }
+    // ---------------------- 构造树的一些方法 ------------------------------
+
+    /**
+     * 构建部门树结构
+     *
+     */
+    public List<DepartmentTreeVO> buildTree(List<DepartmentVO> voList) {
+        if (CollectionUtils.isEmpty(voList)) {
+            return Lists.newArrayList();
+        }
+        List<DepartmentVO> rootList = voList.stream().filter(e -> e.getParentId() == null || Objects.equals(e.getParentId(), NumberUtils.LONG_ZERO)).collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(rootList)) {
+            return Lists.newArrayList();
+        }
+        List<DepartmentTreeVO> treeVOList = BlinkBeanUtil.copyList(rootList, DepartmentTreeVO.class);
+        this.recursiveBuildTree(treeVOList, voList);
+        return treeVOList;
+    }
+
+    /**
+     * 构建所有根节点的下级树形结构
+     *
+     */
+    private List<Long> recursiveBuildTree(List<DepartmentTreeVO> nodeList, List<DepartmentVO> allDepartmentList) {
+        int nodeSize = nodeList.size();
+        List<Long> childIdList = new ArrayList<>();
+        for(int i = 0; i < nodeSize; i++) {
+            int preIndex = i - 1;
+            int nextIndex = i + 1;
+            DepartmentTreeVO node = nodeList.get(i);
+            if (preIndex > -1) {
+                node.setPreId(nodeList.get(preIndex).getDepartmentId());
+            }
+            if (nextIndex < nodeSize) {
+                node.setNextId(nodeList.get(nextIndex).getDepartmentId());
+            }
+
+//            ArrayList<Long> selfAndAllChildrenIdList = Lists.newArrayList();
+//            selfAndAllChildrenIdList.add(node.getDepartmentId());
+//            node.setSelfAndAllChildrenIdList(selfAndAllChildrenIdList);
+
+
+            List<DepartmentTreeVO> children = getChildren(node.getDepartmentId(), allDepartmentList);
+            List<Long> tempChildIdList = new ArrayList<>();
+            if (CollectionUtils.isNotEmpty(children)) {
+                node.setChildren(children);
+                //this.recursiveBuildTree(children, allDepartmentList);
+                tempChildIdList = this.recursiveBuildTree(children, allDepartmentList);
+            }
+            if(CollectionUtils.isEmpty(node.getSelfAndAllChildrenIdList())) {
+                node.setSelfAndAllChildrenIdList(
+                        new ArrayList<>()
+                );
+            }
+            node.getSelfAndAllChildrenIdList().add(node.getDepartmentId());
+
+            if(CollectionUtils.isNotEmpty(tempChildIdList)) {
+                node.getSelfAndAllChildrenIdList().addAll(tempChildIdList);
+                childIdList.addAll(tempChildIdList);
+            }
+        }
+        // 保证本层遍历顺序
+        for(int i = nodeSize - 1; i >= 0; i--) {
+            childIdList.add(0, nodeList.get(i).getDepartmentId());
+        }
+        return childIdList;
+    }
+
+
+    /**
+     * 获取子元素
+     *
+     */
+    private List<DepartmentTreeVO> getChildren(Long departmentId, List<DepartmentVO> voList) {
+        List<DepartmentVO> childrenEntityList = voList.stream().filter(e -> departmentId.equals(e.getParentId())).collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(childrenEntityList)) {
+            return Lists.newArrayList();
+        }
+        return BlinkBeanUtil.copyList(childrenEntityList, DepartmentTreeVO.class);
+    }
+
+
+    /**
+     * 通过部门id,获取当前以及下属部门
+     *
+     */
+    public List<Long> selfAndChildrenIdList(Long departmentId, List<DepartmentVO> voList) {
+        List<Long> selfAndChildrenIdList = Lists.newArrayList();
+        if (CollectionUtils.isEmpty(voList)) {
+            return selfAndChildrenIdList;
+        }
+        selfAndChildrenIdList.add(departmentId);
+        List<DepartmentTreeVO> children = this.getChildren(departmentId, voList);
+        if (CollectionUtils.isEmpty(children)) {
+            return selfAndChildrenIdList;
+        }
+        List<Long> childrenIdList = children.stream().map(DepartmentTreeVO::getDepartmentId).collect(Collectors.toList());
+        selfAndChildrenIdList.addAll(childrenIdList);
+        for (Long childId : childrenIdList) {
+            this.selfAndChildrenRecursion(selfAndChildrenIdList, childId, voList);
+        }
+        return selfAndChildrenIdList;
+    }
+
+    /**
+     * 递归查询
+     */
+    public void selfAndChildrenRecursion(List<Long> selfAndChildrenIdList, Long departmentId, List<DepartmentVO> voList) {
+        List<DepartmentTreeVO> children = this.getChildren(departmentId, voList);
+        if (CollectionUtils.isEmpty(children)) {
+            return;
+        }
+        List<Long> childrenIdList = children.stream().map(DepartmentTreeVO::getDepartmentId).collect(Collectors.toList());
+        selfAndChildrenIdList.addAll(childrenIdList);
+        for (Long childId : childrenIdList) {
+            this.selfAndChildrenRecursion(selfAndChildrenIdList, childId, voList);
+        }
+    }
+}

+ 144 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/department/service/DepartmentService.java

@@ -0,0 +1,144 @@
+package com.cloud.sa.admin.module.system.department.service;
+
+import com.cloud.sa.admin.module.system.department.dao.DepartmentDao;
+import com.cloud.sa.admin.module.system.department.domain.entity.DepartmentEntity;
+import com.cloud.sa.admin.module.system.department.domain.form.DepartmentAddForm;
+import com.cloud.sa.admin.module.system.department.domain.form.DepartmentUpdateForm;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentTreeVO;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentVO;
+import com.cloud.sa.admin.module.system.department.manager.DepartmentCacheManager;
+import com.cloud.sa.admin.module.system.employee.dao.EmployeeDao;
+import com.cloud.sa.base.common.code.UserErrorCode;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.util.BlinkBeanUtil;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 部门 service
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-01-12 20:37:48
+ */
+@Service
+public class DepartmentService {
+
+    @Resource
+    private DepartmentDao departmentDao;
+
+    @Resource
+    private EmployeeDao employeeDao;
+
+    @Resource
+    private DepartmentCacheManager departmentCacheManager;
+
+    // ---------------------------- 增加、修改、删除 ----------------------------
+
+    /**
+     * 新增添加部门
+     */
+
+    public ResponseDTO<String> addDepartment(DepartmentAddForm departmentAddForm) {
+        DepartmentEntity departmentEntity = BlinkBeanUtil.copy(departmentAddForm, DepartmentEntity.class);
+        departmentDao.insert(departmentEntity);
+        this.clearCache();
+        return ResponseDTO.ok();
+    }
+
+
+    /**
+     * 更新部门信息
+     */
+    public ResponseDTO<String> updateDepartment(DepartmentUpdateForm updateDTO) {
+        if (updateDTO.getParentId() == null) {
+            return ResponseDTO.userErrorParam("父级部门id不能为空");
+        }
+        DepartmentEntity entity = departmentDao.selectById(updateDTO.getDepartmentId());
+        if (entity == null) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+        DepartmentEntity departmentEntity = BlinkBeanUtil.copy(updateDTO, DepartmentEntity.class);
+        departmentEntity.setSort(updateDTO.getSort());
+        departmentDao.updateById(departmentEntity);
+        this.clearCache();
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 根据id删除部门
+     * 1、需要判断当前部门是否有子部门,有子部门则不允许删除
+     * 2、需要判断当前部门是否有员工,有员工则不能删除
+     */
+    public ResponseDTO<String> deleteDepartment(Long departmentId) {
+        DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
+        if (null == departmentEntity) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+        // 是否有子级部门
+        int subDepartmentNum = departmentDao.countSubDepartment(departmentId);
+        if (subDepartmentNum > 0) {
+            return ResponseDTO.userErrorParam("请先删除子级部门");
+        }
+
+        // 是否有未删除员工
+        int employeeNum = employeeDao.countByDepartmentId(departmentId, Boolean.FALSE);
+        if (employeeNum > 0) {
+            return ResponseDTO.userErrorParam("请先删除部门员工");
+        }
+        departmentDao.deleteById(departmentId);
+        // 清除缓存
+        this.clearCache();
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 清除自身以及下级的id列表缓存
+     */
+    private void clearCache() {
+        departmentCacheManager.clearCache();
+    }
+
+    // ---------------------------- 查询 ----------------------------
+
+    /**
+     * 获取部门树形结构
+     */
+    public ResponseDTO<List<DepartmentTreeVO>> departmentTree() {
+        List<DepartmentTreeVO> treeVOList = departmentCacheManager.getDepartmentTree();
+        return ResponseDTO.ok(treeVOList);
+    }
+
+
+    /**
+     * 自身以及所有下级的部门id列表
+     */
+    public List<Long> selfAndChildrenIdList(Long departmentId) {
+        return departmentCacheManager.getDepartmentSelfAndChildren(departmentId);
+    }
+
+
+    /**
+     * 获取所有部门
+     */
+    public List<DepartmentVO> listAll() {
+        return departmentCacheManager.getDepartmentList();
+    }
+
+
+    /**
+     * 获取部门
+     */
+    public DepartmentVO getDepartmentById(Long departmentId) {
+        return departmentCacheManager.getDepartmentMap().get(departmentId);
+    }
+
+    /**
+     * 获取部门路径:/公司/研发部/产品组
+     */
+    public String getDepartmentPath(Long departmentId) {
+        return departmentCacheManager.getDepartmentPathMap().get(departmentId);
+    }
+}

+ 129 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/controller/EmployeeController.java

@@ -0,0 +1,129 @@
+package com.cloud.sa.admin.module.system.employee.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import com.cloud.sa.admin.module.system.employee.domain.form.*;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.module.system.employee.domain.vo.EmployeeVO;
+import com.cloud.sa.admin.module.system.employee.service.EmployeeService;
+import com.cloud.sa.base.common.domain.PageResult;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.util.BlinkRequestUtil;
+import com.cloud.sa.base.module.support.apiencrypt.annotation.ApiDecrypt;
+import com.cloud.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 员工
+ *
+ * @Author 畅联云: admin
+ * @Date 2021-12-09 22:57:49
+
+ * 
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_EMPLOYEE)
+public class EmployeeController {
+
+    @Resource
+    private EmployeeService employeeService;
+
+    @Resource
+    private Level3ProtectConfigService level3ProtectConfigService;
+
+
+    @PostMapping("/employee/query")
+    @Operation(summary = "员工管理查询 @author admin")
+    public ResponseDTO<PageResult<EmployeeVO>> query(@Valid @RequestBody EmployeeQueryForm query) {
+        return employeeService.queryEmployee(query);
+    }
+
+    @Operation(summary = "添加员工(返回添加员工的密码) @author admin")
+    @PostMapping("/employee/add")
+    @SaCheckPermission("system:employee:add")
+    public ResponseDTO<String> addEmployee(@Valid @RequestBody EmployeeAddForm employeeAddForm) {
+        return employeeService.addEmployee(employeeAddForm);
+    }
+
+    @Operation(summary = "更新员工 @author admin")
+    @PostMapping("/employee/update")
+    @SaCheckPermission("system:employee:update")
+    public ResponseDTO<String> updateEmployee(@Valid @RequestBody EmployeeUpdateForm employeeUpdateForm) {
+        return employeeService.updateEmployee(employeeUpdateForm);
+    }
+
+    @Operation(summary = "更新登录人信息 @author admin")
+    @PostMapping("/employee/update/login")
+    public ResponseDTO<String> updateByLogin(@Valid @RequestBody EmployeeUpdateForm employeeUpdateForm) {
+        employeeUpdateForm.setEmployeeId(BlinkRequestUtil.getRequestUserId());
+        return employeeService.updateEmployee(employeeUpdateForm);
+    }
+
+    @Operation(summary = "更新登录人头像 @author admin")
+    @PostMapping("/employee/update/avatar")
+    public ResponseDTO<String> updateAvatar(@Valid @RequestBody EmployeeUpdateAvatarForm employeeUpdateAvatarForm) {
+        employeeUpdateAvatarForm.setEmployeeId(BlinkRequestUtil.getRequestUserId());
+        return employeeService.updateAvatar(employeeUpdateAvatarForm);
+    }
+
+    @Operation(summary = "更新员工禁用/启用状态 @author admin")
+    @GetMapping("/employee/update/disabled/{employeeId}")
+    @SaCheckPermission("system:employee:disabled")
+    public ResponseDTO<String> updateDisableFlag(@PathVariable Long employeeId) {
+        return employeeService.updateDisableFlag(employeeId);
+    }
+
+    @Operation(summary = "批量删除员工 @author admin")
+    @PostMapping("/employee/update/batch/delete")
+    @SaCheckPermission("system:employee:delete")
+    public ResponseDTO<String> batchUpdateDeleteFlag(@RequestBody List<Long> employeeIdList) {
+        return employeeService.batchUpdateDeleteFlag(employeeIdList);
+    }
+
+    @Operation(summary = "批量调整员工部门 @author admin")
+    @PostMapping("/employee/update/batch/department")
+    @SaCheckPermission("system:employee:department:update")
+    public ResponseDTO<String> batchUpdateDepartment(@Valid @RequestBody EmployeeBatchUpdateDepartmentForm batchUpdateDepartmentForm) {
+        return employeeService.batchUpdateDepartment(batchUpdateDepartmentForm);
+    }
+
+    @Operation(summary = "修改密码 @author admin")
+    @PostMapping("/employee/update/password")
+    @ApiDecrypt
+    public ResponseDTO<String> updatePassword(@Valid @RequestBody EmployeeUpdatePasswordForm updatePasswordForm) {
+        updatePasswordForm.setEmployeeId(BlinkRequestUtil.getRequestUserId());
+        return employeeService.updatePassword(BlinkRequestUtil.getRequestUser(), updatePasswordForm);
+    }
+
+    @Operation(summary = "获取密码复杂度 @author admin")
+    @GetMapping("/employee/getPasswordComplexityEnabled")
+    @ApiDecrypt
+    public ResponseDTO<Boolean> getPasswordComplexityEnabled() {
+        return ResponseDTO.ok(level3ProtectConfigService.isPasswordComplexityEnabled());
+    }
+
+    @Operation(summary = "重置员工密码 @author admin")
+    @GetMapping("/employee/update/password/reset/{employeeId}")
+    @SaCheckPermission("system:employee:password:reset")
+    public ResponseDTO<String> resetPassword(@PathVariable Long employeeId) {
+        return employeeService.resetPassword(employeeId);
+    }
+
+    @Operation(summary = "查询员工-根据部门id @author admin")
+    @GetMapping("/employee/getAllEmployeeByDepartmentId/{departmentId}")
+    public ResponseDTO<List<EmployeeVO>> getAllEmployeeByDepartmentId(@PathVariable Long departmentId) {
+        return employeeService.getAllEmployeeByDepartmentId(departmentId, Boolean.FALSE);
+    }
+
+    @Operation(summary = "查询所有员工 @author admin")
+    @GetMapping("/employee/queryAll")
+    public ResponseDTO<List<EmployeeVO>> queryAllEmployee(@RequestParam(value = "disabledFlag", required = false) Boolean disabledFlag) {
+        return employeeService.queryAllEmployee(disabledFlag);
+    }
+
+}

+ 120 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/dao/EmployeeDao.java

@@ -0,0 +1,120 @@
+package com.cloud.sa.admin.module.system.employee.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.cloud.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
+import com.cloud.sa.admin.module.system.employee.domain.form.EmployeeQueryForm;
+import com.cloud.sa.admin.module.system.employee.domain.vo.EmployeeVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 员工 dao
+ *
+ * @Author 畅联云: admin
+ * @Date 2021-12-09 22:57:49
+
+ */
+@Mapper
+@Component
+public interface EmployeeDao extends BaseMapper<EmployeeEntity> {
+    /**
+     * 查询员工列表
+     */
+    List<EmployeeVO> queryEmployee(Page page, @Param("queryForm") EmployeeQueryForm queryForm, @Param("departmentIdList") List<Long> departmentIdList);
+
+    /**
+     * 查询员工
+     */
+    List<EmployeeVO> selectEmployeeByDisabledAndDeleted(@Param("disabledFlag") Boolean disabledFlag, @Param("deletedFlag") Boolean deletedFlag);
+
+
+    /**
+     * 更新单个
+     */
+    void updateDisableFlag(@Param("employeeId") Long employeeId, @Param("disabledFlag") Boolean disabledFlag);
+
+
+    /**
+     * 通过登录名查询
+     */
+    EmployeeEntity getByLoginName(@Param("loginName") String loginName,
+                                  @Param("disabledFlag") Boolean disabledFlag);
+
+
+    /**
+     * 通过姓名查询
+     */
+    EmployeeEntity getByActualName(@Param("actualName") String actualName,
+                                   @Param("disabledFlag") Boolean disabledFlag
+    );
+
+    /**
+     * 通过手机号查询
+     */
+    EmployeeEntity getByPhone(@Param("phone") String phone, @Param("disabledFlag") Boolean disabledFlag);
+
+    /**
+     * 获取所有员工
+     */
+    List<EmployeeVO> listAll();
+
+    /**
+     * 获取某个部门员工数
+     */
+    Integer countByDepartmentId(@Param("departmentId") Long departmentId, @Param("deletedFlag") Boolean deletedFlag);
+
+    /**
+     * 获取一批员工
+     */
+    List<EmployeeVO> getEmployeeByIds(@Param("employeeIds") Collection<Long> employeeIds);
+
+
+    /**
+     * 查询单个员工信息
+     */
+    EmployeeVO getEmployeeById(@Param("employeeId") Long employeeId);
+
+    /**
+     * 根据登录账号查询员工信息
+     */
+    EmployeeVO getEmployeeByLoginName(@Param("loginName") String loginName);
+
+
+    /**
+     * 获取某个部门的员工
+     */
+    List<EmployeeEntity> selectByDepartmentId(@Param("departmentId") Long departmentId, @Param("disabledFlag") Boolean disabledFlag);
+
+
+    /**
+     * 查询某些部门下用户名是xxx的员工
+     */
+    List<EmployeeEntity> selectByActualName(@Param("departmentIdList") List<Long> departmentIdList, @Param("actualName") String actualName, @Param("disabledFlag") Boolean disabledFlag);
+
+
+    /**
+     * 获取某批部门的员工Id
+     */
+    List<Long> getEmployeeIdByDepartmentIdList(@Param("departmentIds") List<Long> departmentIds, @Param("disabledFlag") Boolean disabledFlag);
+
+    /**
+     * 获取所有
+     */
+    List<Long> getEmployeeId(@Param("leaveFlag") Boolean leaveFlag, @Param("disabledFlag") Boolean disabledFlag);
+
+    /**
+     * 获取某个部门的员工Id
+     */
+    List<Long> getEmployeeIdByDepartmentId(@Param("departmentId") Long departmentId, @Param("disabledFlag") Boolean disabledFlag);
+
+    /**
+     * 员工重置密码
+     */
+    Integer updatePassword(@Param("employeeId") Long employeeId, @Param("password") String password);
+
+}

+ 95 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java

@@ -0,0 +1,95 @@
+package com.cloud.sa.admin.module.system.employee.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 员工 实体表
+ *
+ * @Author 畅联云: admin
+ * @Date 2021-12-09 22:57:49
+
+ *
+ */
+@Data
+@TableName("mate_employee")
+public class EmployeeEntity {
+
+    @TableId(type = IdType.AUTO)
+    private Long employeeId;
+
+    /**
+     * 登录账号
+     */
+    private String loginName;
+
+    /**
+     * 登录密码
+     */
+    private String loginPwd;
+
+    /**
+     * 员工名称
+     */
+    private String actualName;
+
+    /**
+     * 头像
+     */
+    private String avatar;
+
+    /**
+     * 性别
+     */
+    private Integer gender;
+
+    /**
+     * 手机号码
+     */
+    private String phone;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+
+    /**
+     * 部门id
+     */
+    private Long departmentId;
+
+    /**
+     * 职务级别ID
+     */
+    private Long positionId;
+
+    /**
+     * 是否为超级管理员: 0 不是,1是
+     */
+    private Boolean administratorFlag;
+
+    /**
+     * 是否被禁用 0否1是
+     */
+    private Boolean disabledFlag;
+
+    /**
+     * 是否删除0否 1是
+     */
+    private Boolean deletedFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    private LocalDateTime updateTime;
+
+    private LocalDateTime createTime;
+
+
+}

+ 67 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java

@@ -0,0 +1,67 @@
+package com.cloud.sa.admin.module.system.employee.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.base.common.enumeration.GenderEnum;
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+import com.cloud.sa.base.common.validator.enumeration.CheckEnum;
+import org.hibernate.validator.constraints.Length;
+import com.cloud.sa.base.common.util.BlinkVerificationUtil;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.util.List;
+
+/**
+ * 添加员工
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-20 21:06:49
+
+ * 
+ */
+@Data
+public class EmployeeAddForm {
+
+    @Schema(description = "姓名")
+    @NotNull(message = "姓名不能为空")
+    @Length(max = 30, message = "姓名最多30字符")
+    private String actualName;
+
+    @Schema(description = "登录账号")
+    @NotNull(message = "登录账号不能为空")
+    @Length(max = 30, message = "登录账号最多30字符")
+    private String loginName;
+
+    @SchemaEnum(GenderEnum.class)
+    @CheckEnum(value = GenderEnum.class, message = "性别错误")
+    private Integer gender;
+
+    @Schema(description = "部门id")
+    @NotNull(message = "部门id不能为空")
+    private Long departmentId;
+
+    @Schema(description = "是否启用")
+    @NotNull(message = "是否被禁用不能为空")
+    private Boolean disabledFlag;
+
+    @Schema(description = "手机号")
+    @NotNull(message = "手机号不能为空")
+    @Pattern(regexp = BlinkVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确")
+    private String phone;
+
+    @Schema(description = "邮箱")
+    private String email;
+
+
+    @Schema(description = "角色列表")
+    private List<Long> roleIdList;
+
+    @Schema(description = "备注")
+    @Length(max = 30, message = "备注最多200字符")
+    private String remark;
+
+    @Schema(description = "职务级别ID")
+    private Long positionId;
+
+}

+ 30 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeBatchUpdateDepartmentForm.java

@@ -0,0 +1,30 @@
+package com.cloud.sa.admin.module.system.employee.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+/**
+ * 员工更新部门
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-20 21:06:49
+
+ * 
+ */
+@Data
+public class EmployeeBatchUpdateDepartmentForm {
+
+    @Schema(description = "员工id")
+    @NotEmpty(message = "员工id不能为空")
+    @Size(max = 99, message = "一次最多调整99个员工")
+    private List<Long> employeeIdList;
+
+    @Schema(description = "部门ID")
+    @NotNull(message = "部门ID不能为空")
+    private Long departmentId;
+}

+ 39 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeQueryForm.java

@@ -0,0 +1,39 @@
+package com.cloud.sa.admin.module.system.employee.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.base.common.domain.PageParam;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.Size;
+import java.util.List;
+
+/**
+ * 员工列表
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-20 21:06:49
+
+ * 
+ */
+@Data
+public class EmployeeQueryForm extends PageParam {
+
+    @Schema(description = "搜索词")
+    @Length(max = 20, message = "搜索词最多20字符")
+    private String keyword;
+
+    @Schema(description = "部门id")
+    private Long departmentId;
+
+    @Schema(description = "是否禁用")
+    private Boolean disabledFlag;
+
+    @Schema(description = "员工id集合")
+    @Size(max = 99, message = "最多查询99个员工")
+    private List<Long> employeeIdList;
+
+    @Schema(description = "删除标识", hidden = true)
+    private Boolean deletedFlag;
+
+}

+ 25 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdateAvatarForm.java

@@ -0,0 +1,25 @@
+package com.cloud.sa.admin.module.system.employee.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 修改登录人头像
+ *
+ * @Author 云畅联: admin
+ * @Date 2024年6月30日00:26:35
+
+ *
+ */
+@Data
+public class EmployeeUpdateAvatarForm {
+
+    @Schema(hidden = true)
+    private Long employeeId;
+
+    @Schema(description = "头像")
+    @NotBlank(message = "头像不能为空哦")
+    private String avatar;
+}

+ 22 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdateForm.java

@@ -0,0 +1,22 @@
+package com.cloud.sa.admin.module.system.employee.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 更新员工
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-20 21:06:49
+
+ *
+ */
+@Data
+public class EmployeeUpdateForm extends EmployeeAddForm {
+
+    @Schema(description = "员工id")
+    @NotNull(message = "员工id不能为空")
+    private Long employeeId;
+}

+ 29 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java

@@ -0,0 +1,29 @@
+package com.cloud.sa.admin.module.system.employee.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 修改密码所需参数
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-20 21:06:49
+
+ *
+ */
+@Data
+public class EmployeeUpdatePasswordForm {
+
+    @Schema(hidden = true)
+    private Long employeeId;
+
+    @Schema(description = "原密码")
+    @NotBlank(message = "原密码不能为空哦")
+    private String oldPassword;
+
+    @Schema(description = "新密码")
+    @NotBlank(message = "新密码不能为空哦")
+    private String newPassword;
+}

+ 29 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/form/EmployeeUpdateRoleForm.java

@@ -0,0 +1,29 @@
+package com.cloud.sa.admin.module.system.employee.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+/**
+ * 员工更新角色
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-20 20:55:13
+
+ * 
+ */
+@Data
+public class EmployeeUpdateRoleForm {
+
+    @Schema(description = "员工id")
+    @NotNull(message = "员工id不能为空")
+    private Long employeeId;
+
+    @Schema(description = "角色ids")
+    @Size(max = 99, message = "角色最多99")
+    private List<Long> roleIdList;
+
+}

+ 67 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/domain/vo/EmployeeVO.java

@@ -0,0 +1,67 @@
+package com.cloud.sa.admin.module.system.employee.domain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.base.common.enumeration.GenderEnum;
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 员工信息
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-21 23:05:56
+
+ *
+ */
+@Data
+public class EmployeeVO {
+
+    @Schema(description = "主键id")
+    private Long employeeId;
+
+    @Schema(description = "登录账号")
+    private String loginName;
+
+    @SchemaEnum(GenderEnum.class)
+    private Integer gender;
+
+    @Schema(description = "员工名称")
+    private String actualName;
+
+    @Schema(description = "手机号码")
+    private String phone;
+
+    @Schema(description = "部门id")
+    private Long departmentId;
+
+    @Schema(description = "是否被禁用")
+    private Boolean disabledFlag;
+
+    @Schema(description = "是否 超级管理员")
+    private Boolean administratorFlag;
+
+    @Schema(description = "部门名称")
+    private String departmentName;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "角色列表")
+    private List<Long> roleIdList;
+
+    @Schema(description = "角色名称列表")
+    private List<String> roleNameList;
+
+    @Schema(description = "职务ID")
+    private Long positionId;
+
+    @Schema(description = "职务名称")
+    private String positionName;
+
+    @Schema(description = "邮箱")
+    private String email;
+
+}

+ 68 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/job/InitSystemAccountTask.java

@@ -0,0 +1,68 @@
+package com.cloud.sa.admin.module.system.employee.job;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.cloud.sa.admin.module.system.employee.dao.EmployeeDao;
+import com.cloud.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
+import com.cloud.sa.admin.module.system.employee.domain.vo.EmployeeVO;
+import com.cloud.sa.base.module.support.job.core.SmartJob;
+import com.cloud.sa.base.module.support.mail.EMailUtils;
+import com.cloud.sa.base.module.support.securityprotect.service.SecurityPasswordService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+/**
+ * 初始化系统账号同时发邮件给各位员工
+ */
+@Slf4j
+@Service
+public class InitSystemAccountTask implements SmartJob {
+    @Resource
+    private EMailUtils eMailUtils;
+
+    @Resource
+    private JavaMailSender mailSender;
+
+    @Resource
+    private EmployeeDao employeeDao;
+
+    @Override
+    public String run(String param) {
+        String title = "【Evosynth项目管理平台】启用通知";
+        String templateName = "systemNotice.html";
+
+        //String newPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getNewPassword());
+        List<EmployeeVO> employees = employeeDao.selectEmployeeByDisabledAndDeleted(Boolean.FALSE, Boolean.FALSE);
+        if (CollectionUtil.isNotEmpty(employees)) {
+            employees = employees.stream().filter(employee -> !employee.getLoginName().equals("admin") && !employee.getLoginName().equals("tangsan"))
+                    .collect(Collectors.toList());
+            for (int i = 0; i < employees.size(); i++) {
+                EmployeeVO employee = employees.get(i);
+                Map<String, Object> model = new HashMap<>();
+                model.put("nickName", employee.getActualName());
+                model.put("loginName", employee.getLoginName());
+                String password = RandomStringUtils.randomAlphabetic(3).toUpperCase()
+                        + RandomStringUtils.randomNumeric(2)
+                        + RandomStringUtils.randomAlphabetic(2).toLowerCase()
+                        + (ThreadLocalRandom.current().nextBoolean() ? "#" : "@");
+                model.put("passWord", password);
+                String[] targetMails = new String[]{employee.getEmail()};
+                eMailUtils.sendHtmlTemplate(title, templateName, model, targetMails);
+                //更新员工的密码
+                EmployeeEntity employeeEntity = new EmployeeEntity();
+                employeeEntity.setEmployeeId(employee.getEmployeeId());
+                employeeEntity.setLoginPwd(SecurityPasswordService.getEncryptPwd(password));
+                employeeDao.updateById(employeeEntity);
+            }
+        }
+        return "初始化系统账号同时发邮件给各位员工";
+    }
+}

+ 85 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/manager/EmployeeManager.java

@@ -0,0 +1,85 @@
+package com.cloud.sa.admin.module.system.employee.manager;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cloud.sa.admin.module.system.employee.dao.EmployeeDao;
+import com.cloud.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
+import com.cloud.sa.admin.module.system.role.dao.RoleEmployeeDao;
+import com.cloud.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity;
+import com.cloud.sa.admin.module.system.role.service.RoleEmployeeService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 员工 manager
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-29 21:52:46
+
+ *
+ */
+@Service
+public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
+
+    @Resource
+    private EmployeeDao employeeDao;
+
+    @Resource
+    private RoleEmployeeService roleEmployeeService;
+
+    @Resource
+    private RoleEmployeeDao roleEmployeeDao;
+
+    /**
+     * 保存员工
+     *
+     */
+    @Transactional(rollbackFor = Throwable.class)
+    public void saveEmployee(EmployeeEntity employee, List<Long> roleIdList) {
+        // 保存员工 获得id
+        employeeDao.insert(employee);
+
+        if (CollectionUtils.isNotEmpty(roleIdList)) {
+            List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
+            roleEmployeeService.batchInsert(roleEmployeeList);
+        }
+    }
+
+    /**
+     * 更新员工
+     *
+     */
+    @Transactional(rollbackFor = Throwable.class)
+    public void updateEmployee(EmployeeEntity employee, List<Long> roleIdList) {
+        // 保存员工 获得id
+        employeeDao.updateById(employee);
+
+        // 若为空,则删除所有角色
+        if (CollectionUtils.isEmpty(roleIdList)) {
+            //roleEmployeeDao.deleteByEmployeeId(employee.getEmployeeId());
+            return;
+        }
+
+        List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
+        this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList);
+    }
+
+    /**
+     * 更新员工角色
+     */
+    @Transactional(rollbackFor = Throwable.class)
+    public void updateEmployeeRole(Long employeeId, List<RoleEmployeeEntity> roleEmployeeList) {
+
+        roleEmployeeDao.deleteByEmployeeId(employeeId);
+
+        if (CollectionUtils.isNotEmpty(roleEmployeeList)) {
+            roleEmployeeService.batchInsert(roleEmployeeList);
+        }
+    }
+
+}

+ 389 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/employee/service/EmployeeService.java

@@ -0,0 +1,389 @@
+package com.cloud.sa.admin.module.system.employee.service;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.cloud.sa.admin.module.system.department.dao.DepartmentDao;
+import com.cloud.sa.admin.module.system.department.domain.entity.DepartmentEntity;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentVO;
+import com.cloud.sa.admin.module.system.department.service.DepartmentService;
+import com.cloud.sa.admin.module.system.employee.domain.form.*;
+import com.cloud.sa.admin.module.system.position.dao.PositionDao;
+import com.cloud.sa.admin.module.system.position.domain.entity.PositionEntity;
+import com.google.common.collect.Lists;
+import com.cloud.sa.admin.module.system.employee.dao.EmployeeDao;
+import com.cloud.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
+import com.cloud.sa.admin.module.system.employee.domain.vo.EmployeeVO;
+import com.cloud.sa.admin.module.system.employee.manager.EmployeeManager;
+import com.cloud.sa.admin.module.system.login.service.LoginService;
+import com.cloud.sa.admin.module.system.role.dao.RoleEmployeeDao;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleEmployeeVO;
+import com.cloud.sa.base.common.code.UserErrorCode;
+import com.cloud.sa.base.common.constant.StringConst;
+import com.cloud.sa.base.common.domain.PageResult;
+import com.cloud.sa.base.common.domain.RequestUser;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.enumeration.UserTypeEnum;
+import com.cloud.sa.base.common.util.BlinkBeanUtil;
+import com.cloud.sa.base.common.util.BlinkPageUtil;
+import com.cloud.sa.base.module.support.securityprotect.service.SecurityPasswordService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 员工 service
+ *
+ * @Author 畅联云: admin
+ * @Date 2021-12-29 21:52:46
+
+ * 
+ */
+@Service
+public class EmployeeService {
+
+    @Resource
+    private EmployeeDao employeeDao;
+
+    @Resource
+    private DepartmentDao departmentDao;
+
+    @Resource
+    private EmployeeManager employeeManager;
+
+    @Resource
+    private RoleEmployeeDao roleEmployeeDao;
+
+    @Resource
+    private DepartmentService departmentService;
+
+    @Resource
+    private SecurityPasswordService securityPasswordService;
+
+    @Resource
+    @Lazy
+    private LoginService loginService;
+
+    @Resource
+    private PositionDao positionDao;
+
+    public EmployeeEntity getById(Long employeeId) {
+        return employeeDao.selectById(employeeId);
+    }
+
+
+    /**
+     * 查询员工列表
+     */
+    public ResponseDTO<PageResult<EmployeeVO>> queryEmployee(EmployeeQueryForm employeeQueryForm) {
+        employeeQueryForm.setDeletedFlag(false);
+        Page pageParam = BlinkPageUtil.convert2PageQuery(employeeQueryForm);
+
+        List<Long> departmentIdList = new ArrayList<>();
+        if (employeeQueryForm.getDepartmentId() != null) {
+            departmentIdList.addAll(departmentService.selfAndChildrenIdList(employeeQueryForm.getDepartmentId()));
+        }
+
+        List<EmployeeVO> employeeList = employeeDao.queryEmployee(pageParam, employeeQueryForm, departmentIdList);
+        if (CollectionUtils.isEmpty(employeeList)) {
+            PageResult<EmployeeVO> pageResult = BlinkPageUtil.convert2PageResult(pageParam, employeeList);
+            return ResponseDTO.ok(pageResult);
+        }
+
+        // 查询员工角色
+        List<Long> employeeIdList = employeeList.stream().map(EmployeeVO::getEmployeeId).collect(Collectors.toList());
+        List<RoleEmployeeVO> roleEmployeeEntityList = employeeIdList.isEmpty() ? Collections.emptyList() : roleEmployeeDao.selectRoleByEmployeeIdList(employeeIdList);
+        Map<Long, List<Long>> employeeRoleIdListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleId, Collectors.toList())));
+        Map<Long, List<String>> employeeRoleNameListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleName, Collectors.toList())));
+
+        // 查询员工职位
+        List<Long> positionIdList = employeeList.stream().map(EmployeeVO::getPositionId).filter(Objects::nonNull).collect(Collectors.toList());
+        List<PositionEntity> positionEntityList = positionIdList.isEmpty() ? Collections.emptyList() : positionDao.selectBatchIds(positionIdList);
+        Map<Long, String> positionNameMap = positionEntityList.stream().collect(Collectors.toMap(PositionEntity::getPositionId, PositionEntity::getPositionName));
+
+        employeeList.forEach(e -> {
+            e.setRoleIdList(employeeRoleIdListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList()));
+            e.setRoleNameList(employeeRoleNameListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList()));
+            e.setDepartmentName(departmentService.getDepartmentPath(e.getDepartmentId()));
+            e.setPositionName(positionNameMap.get(e.getPositionId()));
+        });
+        PageResult<EmployeeVO> pageResult = BlinkPageUtil.convert2PageResult(pageParam, employeeList);
+        return ResponseDTO.ok(pageResult);
+    }
+
+    /**
+     * 新增员工
+     */
+    public synchronized ResponseDTO<String> addEmployee(EmployeeAddForm employeeAddForm) {
+        // 校验登录名是否重复
+        EmployeeEntity employeeEntity = employeeDao.getByLoginName(employeeAddForm.getLoginName(), null);
+        if (null != employeeEntity) {
+            return ResponseDTO.userErrorParam("登录名重复");
+        }
+        // 校验电话是否存在
+        employeeEntity = employeeDao.getByPhone(employeeAddForm.getPhone(), null);
+        if (null != employeeEntity) {
+            return ResponseDTO.userErrorParam("手机号已存在");
+        }
+        // 部门是否存在
+        Long departmentId = employeeAddForm.getDepartmentId();
+        DepartmentEntity department = departmentDao.selectById(departmentId);
+        if (department == null) {
+            return ResponseDTO.userErrorParam("部门不存在");
+        }
+
+        EmployeeEntity entity = BlinkBeanUtil.copy(employeeAddForm, EmployeeEntity.class);
+
+        // 设置密码 默认密码
+        String password = securityPasswordService.randomPassword();
+        entity.setLoginPwd(SecurityPasswordService.getEncryptPwd(password));
+
+        // 保存数据
+        entity.setDeletedFlag(Boolean.FALSE);
+        employeeManager.saveEmployee(entity, employeeAddForm.getRoleIdList());
+
+        return ResponseDTO.ok(password);
+    }
+
+    /**
+     * 更新员工
+     */
+    public synchronized ResponseDTO<String> updateEmployee(EmployeeUpdateForm employeeUpdateForm) {
+
+        Long employeeId = employeeUpdateForm.getEmployeeId();
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        if (null == employeeEntity) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+
+        // 部门是否存在
+        Long departmentId = employeeUpdateForm.getDepartmentId();
+        DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
+        if (departmentEntity == null) {
+            return ResponseDTO.userErrorParam("部门不存在");
+        }
+
+
+        EmployeeEntity existEntity = employeeDao.getByLoginName(employeeUpdateForm.getLoginName(), null);
+        if (null != existEntity && !Objects.equals(existEntity.getEmployeeId(), employeeId)) {
+            return ResponseDTO.userErrorParam("登录名重复");
+        }
+
+        existEntity = employeeDao.getByPhone(employeeUpdateForm.getPhone(), null);
+        if (null != existEntity && !Objects.equals(existEntity.getEmployeeId(), employeeId)) {
+            return ResponseDTO.userErrorParam("手机号已存在");
+        }
+
+
+        // 不更新密码
+        EmployeeEntity entity = BlinkBeanUtil.copy(employeeUpdateForm, EmployeeEntity.class);
+        entity.setLoginPwd(null);
+
+        // 更新数据
+        employeeManager.updateEmployee(entity, employeeUpdateForm.getRoleIdList());
+
+        // 清除员工缓存
+        loginService.clearLoginEmployeeCache(employeeId);
+
+        return ResponseDTO.ok();
+    }
+
+
+    /**
+     * 更新登录人头像
+     *
+     * @param employeeUpdateAvatarForm
+     * @return
+     */
+    public ResponseDTO<String> updateAvatar(EmployeeUpdateAvatarForm employeeUpdateAvatarForm) {
+        Long employeeId = employeeUpdateAvatarForm.getEmployeeId();
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        if (employeeEntity == null) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+        // 更新头像
+        EmployeeEntity updateEntity = new EmployeeEntity();
+        updateEntity.setEmployeeId(employeeId);
+        updateEntity.setAvatar(employeeUpdateAvatarForm.getAvatar());
+        employeeDao.updateById(updateEntity);
+
+        // 清除员工缓存
+        loginService.clearLoginEmployeeCache(employeeId);
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 更新禁用/启用状态
+     */
+    public ResponseDTO<String> updateDisableFlag(Long employeeId) {
+        if (null == employeeId) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        if (null == employeeEntity) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+        employeeDao.updateDisableFlag(employeeId, !employeeEntity.getDisabledFlag());
+
+        if (employeeEntity.getDisabledFlag()) {
+            // 强制退出登录
+            StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId);
+        }
+
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 批量删除员工
+     */
+    public ResponseDTO<String> batchUpdateDeleteFlag(List<Long> employeeIdList) {
+        if (CollectionUtils.isEmpty(employeeIdList)) {
+            return ResponseDTO.ok();
+        }
+        List<EmployeeEntity> employeeEntityList = employeeManager.listByIds(employeeIdList);
+        if (CollectionUtils.isEmpty(employeeEntityList)) {
+            return ResponseDTO.ok();
+        }
+        // 更新删除
+        List<EmployeeEntity> deleteList = employeeIdList.stream().map(e -> {
+            EmployeeEntity updateEmployee = new EmployeeEntity();
+            updateEmployee.setEmployeeId(e);
+            updateEmployee.setDeletedFlag(true);
+            return updateEmployee;
+        }).collect(Collectors.toList());
+        employeeManager.updateBatchById(deleteList);
+
+        for (Long employeeId : employeeIdList) {
+            // 强制退出登录
+            StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId);
+        }
+        return ResponseDTO.ok();
+    }
+
+
+    /**
+     * 批量更新部门
+     */
+    public ResponseDTO<String> batchUpdateDepartment(EmployeeBatchUpdateDepartmentForm batchUpdateDepartmentForm) {
+        List<Long> employeeIdList = batchUpdateDepartmentForm.getEmployeeIdList();
+        List<EmployeeEntity> employeeEntityList = employeeDao.selectBatchIds(employeeIdList);
+        if (employeeIdList.size() != employeeEntityList.size()) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+        // 更新
+        List<EmployeeEntity> updateList = employeeIdList.stream().map(e -> {
+            EmployeeEntity updateEmployee = new EmployeeEntity();
+            updateEmployee.setEmployeeId(e);
+            updateEmployee.setDepartmentId(batchUpdateDepartmentForm.getDepartmentId());
+            return updateEmployee;
+        }).collect(Collectors.toList());
+        employeeManager.updateBatchById(updateList);
+
+        return ResponseDTO.ok();
+    }
+
+
+    /**
+     * 更新密码
+     */
+    @Transactional(rollbackFor = Throwable.class)
+    public ResponseDTO<String> updatePassword(RequestUser requestUser, EmployeeUpdatePasswordForm updatePasswordForm) {
+        Long employeeId = updatePasswordForm.getEmployeeId();
+        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
+        if (employeeEntity == null) {
+            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
+        }
+        // 校验原始密码
+        String oldPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getOldPassword());
+        if (!Objects.equals(oldPassword, employeeEntity.getLoginPwd())) {
+            return ResponseDTO.userErrorParam("原密码有误,请重新输入");
+        }
+        // 校验密码复杂度
+        ResponseDTO<String> validatePassComplexity = securityPasswordService.validatePasswordComplexity(updatePasswordForm.getNewPassword());
+        if (!validatePassComplexity.getOk()) {
+            return validatePassComplexity;
+        }
+
+        // 新旧密码相同
+        String newPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getNewPassword());
+        if (Objects.equals(oldPassword, newPassword)) {
+            return ResponseDTO.userErrorParam("新密码与原始密码相同,请重新输入");
+        }
+
+        // 校验密码复杂度
+        // 根据三级等保规则,校验密码是否重复
+        ResponseDTO<String> passwordRepeatTimes = securityPasswordService.validatePasswordRepeatTimes(requestUser, updatePasswordForm.getNewPassword());
+        if (!passwordRepeatTimes.getOk()) {
+            return ResponseDTO.error(passwordRepeatTimes);
+        }
+
+        // 更新密码
+        EmployeeEntity updateEntity = new EmployeeEntity();
+        updateEntity.setEmployeeId(employeeId);
+        updateEntity.setLoginPwd(newPassword);
+        employeeDao.updateById(updateEntity);
+
+        // 保存修改密码密码记录
+        securityPasswordService.saveUserChangePasswordLog(requestUser, newPassword, oldPassword);
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 获取某个部门的员工信息
+     */
+    public ResponseDTO<List<EmployeeVO>> getAllEmployeeByDepartmentId(Long departmentId, Boolean disabledFlag) {
+        List<EmployeeEntity> employeeEntityList = employeeDao.selectByDepartmentId(departmentId, disabledFlag);
+        if (disabledFlag != null) {
+            employeeEntityList = employeeEntityList.stream().filter(e -> e.getDisabledFlag().equals(disabledFlag)).collect(Collectors.toList());
+        }
+
+        if (CollectionUtils.isEmpty(employeeEntityList)) {
+            return ResponseDTO.ok(Collections.emptyList());
+        }
+
+        DepartmentVO department = departmentService.getDepartmentById(departmentId);
+
+        List<EmployeeVO> voList = employeeEntityList.stream().map(e -> {
+            EmployeeVO employeeVO = BlinkBeanUtil.copy(e, EmployeeVO.class);
+            if (department != null) {
+                employeeVO.setDepartmentName(department.getName());
+            }
+            return employeeVO;
+        }).collect(Collectors.toList());
+        return ResponseDTO.ok(voList);
+    }
+
+
+    /**
+     * 重置密码
+     */
+    public ResponseDTO<String> resetPassword(Long employeeId) {
+        String password = securityPasswordService.randomPassword();
+        employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
+        return ResponseDTO.ok(password);
+    }
+
+
+    /**
+     * 查询全部员工
+     */
+    public ResponseDTO<List<EmployeeVO>> queryAllEmployee(Boolean disabledFlag) {
+        List<EmployeeVO> employeeList = employeeDao.selectEmployeeByDisabledAndDeleted(disabledFlag, Boolean.FALSE);
+        return ResponseDTO.ok(employeeList);
+    }
+
+    /**
+     * 根据登录名获取员工
+     */
+    public EmployeeEntity getByLoginName(String loginName) {
+        return employeeDao.getByLoginName(loginName, null);
+    }
+
+    public EmployeeEntity getByPhone(String mobile) {
+        return employeeDao.getByPhone(mobile, null);
+    }
+}

+ 100 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/controller/LoginController.java

@@ -0,0 +1,100 @@
+package com.cloud.sa.admin.module.system.login.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import com.cloud.sa.admin.module.system.login.domain.LoginForm;
+import com.cloud.sa.admin.module.system.login.domain.LoginResultVO;
+import com.cloud.sa.admin.module.system.login.manager.factory.LoginStrategyFactory;
+import com.cloud.sa.admin.module.system.login.service.LoginService;
+import com.cloud.sa.admin.module.system.login.strategy.LoginStrategy;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.util.AdminRequestUtil;
+import com.cloud.sa.base.common.annoation.NoNeedLogin;
+import com.cloud.sa.base.common.constant.RequestHeaderConst;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.util.BlinkRequestUtil;
+import com.cloud.sa.base.module.support.captcha.domain.CaptchaVO;
+import org.springframework.web.bind.annotation.*;
+import com.cloud.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+/**
+ * 员工登录
+ *
+ * @Author 云畅联:admin
+ * @Date 2021-12-15 21:05:46
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_LOGIN)
+public class LoginController {
+
+    @Resource
+    private LoginService loginService;
+    @Resource
+    private LoginStrategyFactory loginStrategyFactory;
+    @Resource
+    private Level3ProtectConfigService level3ProtectConfigService;
+
+    @NoNeedLogin
+    @PostMapping("/login")
+    @Operation(summary = "登录 @author admin")
+    public ResponseDTO<LoginResultVO> login(@Valid @RequestBody LoginForm loginForm, HttpServletRequest request) {
+        LoginStrategy loginStrategy = loginStrategyFactory.getLoginStrategy(loginForm.getLoginType());
+        return loginStrategy.login(loginForm, request);
+    }
+
+    @NoNeedLogin
+    @GetMapping("/wechat/login")
+    @Operation(summary = "企业微信登录-测试Controller")
+    public ResponseDTO<LoginResultVO> wechatLogin(String code, HttpServletRequest request) {
+        LoginForm loginForm = new LoginForm();
+        loginForm.setAuthCode(code);
+        loginForm.setLoginType("WECHAT_SCAN");
+        LoginStrategy loginStrategy = loginStrategyFactory.getLoginStrategy(loginForm.getLoginType());
+        return loginStrategy.login(loginForm, request);
+    }
+
+    @GetMapping("/login/getLoginInfo")
+    @Operation(summary = "获取登录结果信息  @author admin")
+    public ResponseDTO<LoginResultVO> getLoginInfo() {
+        String tokenValue = StpUtil.getTokenValue();
+        LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), tokenValue);
+        loginResult.setToken(tokenValue);
+        return ResponseDTO.ok(loginResult);
+    }
+
+    @Operation(summary = "退出登陆  @author admin")
+    @GetMapping("/login/logout")
+    public ResponseDTO<String> logout(@RequestHeader(value = RequestHeaderConst.TOKEN, required = false) String token) {
+        return loginService.logout(token, BlinkRequestUtil.getRequestUser());
+    }
+
+    @Operation(summary = "获取验证码  @author admin")
+    @GetMapping("/login/getCaptcha")
+    @NoNeedLogin
+    public ResponseDTO<CaptchaVO> getCaptcha() {
+        return loginService.getCaptcha();
+    }
+
+    @NoNeedLogin
+    @GetMapping("/login/sendEmailCode/{loginName}")
+    @Operation(summary = "获取邮箱登录验证码 @author admin")
+    public ResponseDTO<String> sendEmailCode(@PathVariable String loginName) {
+        return loginService.sendEmailCode(loginName);
+    }
+
+
+    @NoNeedLogin
+    @GetMapping("/login/getTwoFactorLoginFlag")
+    @Operation(summary = "获取双因子登录标识 @author admin")
+    public ResponseDTO<Boolean> getTwoFactorLoginFlag() {
+        // 双因子登录
+        boolean twoFactorLoginEnabled = level3ProtectConfigService.isTwoFactorLoginEnabled();
+        return ResponseDTO.ok(twoFactorLoginEnabled);
+    }
+
+}

+ 40 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/domain/LoginForm.java

@@ -0,0 +1,40 @@
+package com.cloud.sa.admin.module.system.login.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+import com.cloud.sa.base.common.validator.enumeration.CheckEnum;
+import com.cloud.sa.base.module.support.captcha.domain.CaptchaForm;
+import com.cloud.sa.base.constant.LoginDeviceEnum;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 员工登录
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-19 11:49:45
+ */
+@Data
+public class LoginForm extends CaptchaForm {
+
+    @Schema(description = "登录账号")
+    @Length(max = 30, message = "登录账号最多30字符")
+    private String loginName;
+
+    @Schema(description = "密码")
+    private String password;
+
+    @SchemaEnum(desc = "登录终端", value = LoginDeviceEnum.class)
+    private Integer loginDevice;
+
+    @Schema(description = "邮箱验证码")
+    private String emailCode;
+
+    @Schema(description = "登录方式")
+    private String loginType;
+
+    @Schema(description = "authCode")
+    private String authCode;
+}

+ 40 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/domain/LoginResultVO.java

@@ -0,0 +1,40 @@
+package com.cloud.sa.admin.module.system.login.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuVO;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 登录结果信息
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-19 11:49:45
+ */
+@Data
+public class LoginResultVO extends RequestEmployee {
+
+    @Schema(description = "token")
+    private String token;
+
+    @Schema(description = "菜单列表")
+    private List<MenuVO> menuList;
+
+    @Schema(description = "是否需要修改密码")
+    private Boolean needUpdatePwdFlag;
+
+    @Schema(description = "上次登录ip")
+    private String lastLoginIp;
+
+    @Schema(description = "上次登录ip地区")
+    private String lastLoginIpRegion;
+
+    @Schema(description = "上次登录user-agent")
+    private String lastLoginUserAgent;
+
+    @Schema(description = "上次登录时间")
+    private LocalDateTime lastLoginTime;
+
+}

+ 96 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/domain/RequestEmployee.java

@@ -0,0 +1,96 @@
+package com.cloud.sa.admin.module.system.login.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.base.common.domain.RequestUser;
+import com.cloud.sa.base.common.enumeration.GenderEnum;
+import com.cloud.sa.base.common.enumeration.UserTypeEnum;
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+
+/**
+ * 请求员工登录信息
+ *
+ * @Author 云畅联: admin
+ * @Date 2021/8/4 21:15
+ */
+@Data
+public class RequestEmployee implements RequestUser {
+
+    @Schema(description = "员工id")
+    private Long employeeId;
+
+    @SchemaEnum(UserTypeEnum.class)
+    private UserTypeEnum userType;
+
+    @Schema(description = "登录账号")
+    private String loginName;
+
+    @Schema(description = "员工名称")
+    private String actualName;
+
+    @Schema(description = "头像")
+    private String avatar;
+
+    @SchemaEnum(GenderEnum.class)
+    private Integer gender;
+
+    @Schema(description = "手机号码")
+    private String phone;
+
+    @Schema(description = "部门id")
+    private Long departmentId;
+
+    @Schema(description = "部门名称")
+    private String departmentName;
+
+    @Schema(description = "是否禁用")
+    private Boolean disabledFlag;
+
+    @Schema(description = "是否为超管")
+    private Boolean administratorFlag;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "请求ip")
+    private String ip;
+
+    @Schema(description = "请求user-agent")
+    private String userAgent;
+
+    @Override
+    public Long getUserId() {
+        return employeeId;
+    }
+
+    @Override
+    public String getUserCode() {
+        return loginName;
+    }
+
+    @Override
+    public String getUserName() {
+        return actualName;
+    }
+
+    //流程专用start
+    public String getUsername() {
+        return loginName;
+    }
+
+    public String getIpaddr() {
+        return ip;
+    }
+
+    public String getOs() {
+        return userAgent;
+    }
+
+    public String getUserNickName() {
+        return actualName;
+    }
+
+    //流程专用end
+
+
+}

+ 39 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/manager/factory/LoginStrategyFactory.java

@@ -0,0 +1,39 @@
+package com.cloud.sa.admin.module.system.login.manager.factory;
+
+
+import com.cloud.sa.admin.module.system.login.strategy.LoginStrategy;
+import com.cloud.sa.admin.module.system.login.strategy.impl.DingTalkScanLoginStrategy;
+import com.cloud.sa.admin.module.system.login.strategy.impl.UsernamePasswordLoginStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+/**
+ * 工厂类,根据不同登录方式创建对应的登录策略实例
+ */
+@Component
+public class LoginStrategyFactory {
+    private final ApplicationContext applicationContext;
+
+    @Autowired
+    public LoginStrategyFactory(ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    public LoginStrategy getLoginStrategy(String loginType) {
+        if (StringUtils.isEmpty(loginType)) {
+            loginType = "USERNAME_PASSWORD";
+        }
+        switch (loginType) {
+            case "USERNAME_PASSWORD":
+                return applicationContext.getBean(UsernamePasswordLoginStrategy.class);
+            case "DINGTALK_SCAN":
+                return applicationContext.getBean(DingTalkScanLoginStrategy.class);
+            case "WECHAT_SCAN":
+                //return applicationContext.getBean(WeChatScanLoginStrategy.class);
+            default:
+                throw new IllegalArgumentException("Unsupported login type: " + loginType);
+        }
+    }
+}

+ 682 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/service/LoginService.java

@@ -0,0 +1,682 @@
+package com.cloud.sa.admin.module.system.login.service;
+
+import cn.dev33.satoken.stp.StpInterface;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.extra.servlet.ServletUtil;
+import com.cloud.sa.admin.module.system.department.domain.vo.DepartmentVO;
+import com.cloud.sa.admin.module.system.department.service.DepartmentService;
+import com.cloud.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
+import com.cloud.sa.admin.module.system.employee.service.EmployeeService;
+import com.cloud.sa.admin.module.system.login.domain.LoginForm;
+import com.cloud.sa.admin.module.system.login.domain.LoginResultVO;
+import com.cloud.sa.admin.module.system.login.domain.RequestEmployee;
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuVO;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleVO;
+import com.cloud.sa.admin.module.system.role.service.RoleEmployeeService;
+import com.cloud.sa.admin.module.system.role.service.RoleMenuService;
+import com.cloud.sa.base.common.code.UserErrorCode;
+import com.cloud.sa.base.common.constant.RequestHeaderConst;
+import com.cloud.sa.base.common.constant.StringConst;
+import com.cloud.sa.base.common.domain.RequestUser;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.domain.UserPermission;
+import com.cloud.sa.base.common.enumeration.UserTypeEnum;
+import com.cloud.sa.base.common.util.BlinkBeanUtil;
+import com.cloud.sa.base.common.util.BlinkEnumUtil;
+import com.cloud.sa.base.common.util.BlinkIpUtil;
+import com.cloud.sa.base.common.util.BlinkStringUtil;
+import com.cloud.sa.base.constant.LoginDeviceEnum;
+import com.cloud.sa.base.constant.RedisKeyConst;
+import com.cloud.sa.base.module.support.apiencrypt.service.ApiEncryptService;
+import com.cloud.sa.base.module.support.captcha.CaptchaService;
+import com.cloud.sa.base.module.support.captcha.domain.CaptchaVO;
+import com.cloud.sa.base.module.support.config.ConfigKeyEnum;
+import com.cloud.sa.base.module.support.config.ConfigService;
+import com.cloud.sa.base.module.support.file.service.IFileStorageService;
+import com.cloud.sa.base.module.support.loginlog.LoginLogResultEnum;
+import com.cloud.sa.base.module.support.loginlog.LoginLogService;
+import com.cloud.sa.base.module.support.loginlog.domain.LoginLogEntity;
+import com.cloud.sa.base.module.support.loginlog.domain.LoginLogVO;
+import com.cloud.sa.base.module.support.mail.MailService;
+import com.cloud.sa.base.module.support.mail.constant.MailTemplateCodeEnum;
+import com.cloud.sa.base.module.support.redis.RedisService;
+import com.cloud.sa.base.module.support.securityprotect.domain.LoginFailEntity;
+import com.cloud.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
+import com.cloud.sa.base.module.support.securityprotect.service.SecurityLoginService;
+import com.cloud.sa.base.module.support.securityprotect.service.SecurityPasswordService;
+import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+/**
+ * 登录
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-01 22:56:34
+ */
+@Slf4j
+@Service
+public class LoginService implements StpInterface {
+
+    /**
+     * 万能密码的 sa token loginId 前缀
+     */
+    private static final String SUPER_PASSWORD_LOGIN_ID_PREFIX = "S";
+
+    /**
+     * 最大在线缓存人数
+     */
+    private static final long CACHE_MAX_ONLINE_PERSON_COUNT = 1000L;
+
+    /**
+     * 登录信息二级缓存
+     */
+    private final ConcurrentMap<Long, RequestEmployee> loginEmployeeCache = new ConcurrentLinkedHashMap.Builder<Long, RequestEmployee>().maximumWeightedCapacity(CACHE_MAX_ONLINE_PERSON_COUNT).build();
+
+
+    /**
+     * 权限 缓存
+     */
+    private final ConcurrentMap<Long, UserPermission> permissionCache = new ConcurrentLinkedHashMap.Builder<Long, UserPermission>().maximumWeightedCapacity(CACHE_MAX_ONLINE_PERSON_COUNT).build();
+
+    @Resource
+    private EmployeeService employeeService;
+
+    @Resource
+    private DepartmentService departmentService;
+
+    @Resource
+    private CaptchaService captchaService;
+
+    @Resource
+    private ConfigService configService;
+
+    @Resource
+    private LoginLogService loginLogService;
+
+    @Resource
+    private RoleEmployeeService roleEmployeeService;
+
+    @Resource
+    private RoleMenuService roleMenuService;
+
+    @Resource
+    private SecurityLoginService securityLoginService;
+
+    @Resource
+    private SecurityPasswordService protectPasswordService;
+
+    @Resource
+    private IFileStorageService fileStorageService;
+
+    @Resource
+    private ApiEncryptService apiEncryptService;
+
+    @Resource
+    private Level3ProtectConfigService level3ProtectConfigService;
+
+    @Resource
+    private MailService mailService;
+
+    @Resource
+    private RedisService redisService;
+
+
+    /**
+     * 获取验证码
+     */
+    public ResponseDTO<CaptchaVO> getCaptcha() {
+        return ResponseDTO.ok(captchaService.generateCaptcha());
+    }
+
+    /**
+     * 员工登陆
+     *
+     * @return 返回用户登录信息
+     */
+    public ResponseDTO<LoginResultVO> login(LoginForm loginForm, String ip, String userAgent) {
+
+        LoginDeviceEnum loginDeviceEnum = BlinkEnumUtil.getEnumByValue(loginForm.getLoginDevice(), LoginDeviceEnum.class);
+        if (loginDeviceEnum == null) {
+            return ResponseDTO.userErrorParam("登录设备暂不支持!");
+        }
+
+        // 校验 图形验证码
+        ResponseDTO<String> checkCaptcha = captchaService.checkCaptcha(loginForm);
+        if (!checkCaptcha.getOk()) {
+            return ResponseDTO.error(UserErrorCode.PARAM_ERROR, checkCaptcha.getMsg());
+        }
+
+        // 验证登录名
+        EmployeeEntity employeeEntity = employeeService.getByLoginName(loginForm.getLoginName());
+        if (null == employeeEntity) {
+            return ResponseDTO.userErrorParam("登录名或密码错误!");
+        }
+
+        // 验证账号状态
+        if (employeeEntity.getDeletedFlag()) {
+            saveLoginLog(employeeEntity, ip, userAgent, "账号已删除", LoginLogResultEnum.LOGIN_FAIL);
+            return ResponseDTO.userErrorParam("您的账号已被删除,请联系工作人员!");
+        }
+
+        // 验证账号状态
+        if (employeeEntity.getDisabledFlag()) {
+            saveLoginLog(employeeEntity, ip, userAgent, "账号已禁用", LoginLogResultEnum.LOGIN_FAIL);
+            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!");
+        }
+
+        // 解密前端加密的密码
+        String requestPassword = apiEncryptService.decrypt(loginForm.getPassword());
+
+        // 验证密码 是否为万能密码
+        String superPassword = configService.getConfigValue(ConfigKeyEnum.SUPER_PASSWORD);
+        boolean superPasswordFlag = superPassword.equals(requestPassword);
+
+        // 校验双因子登录
+        ResponseDTO<String> validateEmailCode = validateEmailCode(loginForm, employeeEntity, superPasswordFlag);
+        if (!validateEmailCode.getOk()) {
+            return ResponseDTO.error(validateEmailCode);
+        }
+
+        // 万能密码特殊操作
+        if (superPasswordFlag) {
+
+            // 对于万能密码:受限制sa token 要求loginId唯一,万能密码只能插入一段uuid
+            String saTokenLoginId = SUPER_PASSWORD_LOGIN_ID_PREFIX + StringConst.COLON + UUID.randomUUID().toString().replace("-", "") + StringConst.COLON + employeeEntity.getEmployeeId();
+            // 万能密码登录只能登录30分钟
+            StpUtil.login(saTokenLoginId, 1800);
+
+        } else {
+
+            // 按照等保登录要求,进行登录失败次数校验
+            ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = securityLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
+            if (!loginFailEntityResponseDTO.getOk()) {
+                return ResponseDTO.error(loginFailEntityResponseDTO);
+            }
+
+            // 密码错误
+            if (!employeeEntity.getLoginPwd().equals(SecurityPasswordService.getEncryptPwd(requestPassword))) {
+                // 记录登录失败
+                saveLoginLog(employeeEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL);
+                // 记录等级保护次数
+                String msg = securityLoginService.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData());
+                return msg == null ? ResponseDTO.userErrorParam("登录名或密码错误!") : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg);
+            }
+
+            String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
+            // 登录
+            StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
+
+            // 移除邮箱验证码
+            deleteEmailCode(employeeEntity.getEmployeeId());
+        }
+
+        // 获取员工信息
+        RequestEmployee requestEmployee = loadLoginInfo(employeeEntity);
+
+        // 放入缓存
+        loginEmployeeCache.put(employeeEntity.getEmployeeId(), requestEmployee);
+
+        // 移除登录失败
+        securityLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
+
+        // 获取登录结果信息
+        String token = StpUtil.getTokenValue();
+        LoginResultVO loginResultVO = getLoginResult(requestEmployee, token);
+        //保存登录记录
+        saveLoginLog(employeeEntity, ip, userAgent, superPasswordFlag ? "万能密码登录" : loginDeviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);
+
+        // 设置 token
+        loginResultVO.setToken(token);
+        // 清除权限缓存
+        permissionCache.remove(employeeEntity.getEmployeeId());
+
+        return ResponseDTO.ok(loginResultVO);
+    }
+
+    /**
+     * 员工登陆
+     *
+     * @return 返回用户登录信息
+     */
+    public ResponseDTO<LoginResultVO> dingtalkLogin(String mobile, String ip, String userAgent) {
+        LoginDeviceEnum loginDeviceEnum = BlinkEnumUtil.getEnumByValue(6, LoginDeviceEnum.class);
+        // 验证手机号
+        EmployeeEntity employeeEntity = employeeService.getByPhone(mobile);
+        if (null == employeeEntity) {
+            //return ResponseDTO.userErrorParam("登录名不存在!");
+            return ResponseDTO.ok();
+        }
+
+        // 验证账号状态
+        if (employeeEntity.getDisabledFlag()) {
+            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!");
+        }
+        String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
+        // 登录
+        StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
+        // 获取员工信息
+        RequestEmployee requestEmployee = loadLoginInfo(employeeEntity);
+
+        // 放入缓存
+        loginEmployeeCache.put(employeeEntity.getEmployeeId(), requestEmployee);
+
+        // 移除登录失败
+        securityLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
+
+        // 获取登录结果信息
+        // 获取登录结果信息
+        String token = StpUtil.getTokenValue();
+        LoginResultVO loginResultVO = getLoginResult(requestEmployee, token);
+
+        //保存登录记录
+        saveLoginLog(employeeEntity, ip, userAgent, loginDeviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);
+
+        // 设置 token
+        loginResultVO.setToken(StpUtil.getTokenValue());
+
+        // 清除权限缓存
+        permissionCache.remove(employeeEntity.getEmployeeId());
+
+        return ResponseDTO.ok(loginResultVO);
+
+    }
+
+    /**
+     * 企业微信登录
+     *
+     * @param mobile
+     * @param ip
+     * @param userAgent
+     * @return
+     */
+    public ResponseDTO<LoginResultVO> wechatLogin(String mobile, String userId, String ip, String userAgent) {
+        LoginDeviceEnum deviceEnum = BlinkEnumUtil.getEnumByValue(5, LoginDeviceEnum.class);
+        //验证手机号 todo 企业微信未授权获取不到员工部分信息,比如mobile等
+//        EmployeeEntity employee = employeeService.getByPhone(mobile);
+        EmployeeEntity employee = employeeService.getByLoginName(userId);
+        if (null == employee) {
+            return ResponseDTO.userErrorParam("登录名不存在");
+        }
+        //验证账户状态
+        if (employee.getDisabledFlag()) {
+            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员");
+        }
+        String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employee.getEmployeeId();
+        //登录
+        StpUtil.login(saTokenLoginId, String.valueOf(deviceEnum.getDesc()));
+        //获取员工信息
+        RequestEmployee requestEmployee = loadLoginInfo(employee);
+        //放入缓存
+        loginEmployeeCache.put(employee.getEmployeeId(), requestEmployee);
+        //移出登陆失败
+        securityLoginService.removeLoginFail(employee.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
+        //获取 登录结果信息
+        String token = StpUtil.getTokenValue();
+        LoginResultVO loginResult = getLoginResult(requestEmployee, token);
+        //保存登录记录
+        saveLoginLog(employee, ip, userAgent, deviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);
+        //设置token
+        loginResult.setToken(StpUtil.getTokenValue());
+        //清除权限缓存
+        permissionCache.remove(employee.getEmployeeId());
+        return ResponseDTO.ok(loginResult);
+    }
+
+    // public ResponseDTO<LoginResultVO> login
+
+    /**
+     * 获取登录结果信息
+     */
+    public LoginResultVO getLoginResult(RequestEmployee requestEmployee, String token) {
+
+        // 基础信息
+        LoginResultVO loginResultVO = BlinkBeanUtil.copy(requestEmployee, LoginResultVO.class);
+
+        // 前端菜单和功能点清单
+        List<RoleVO> roleList = roleEmployeeService.getRoleIdList(requestEmployee.getEmployeeId());
+        List<MenuVO> menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), requestEmployee.getAdministratorFlag());
+        loginResultVO.setMenuList(menuAndPointsList);
+
+        // 更新下后端权限缓存
+        UserPermission userPermission = getUserPermission(requestEmployee.getUserId());
+        permissionCache.put(requestEmployee.getUserId(), userPermission);
+
+        // 上次登录信息
+        LoginLogVO loginLogVO = loginLogService.queryLastByUserId(requestEmployee.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, LoginLogResultEnum.LOGIN_SUCCESS);
+        if (loginLogVO != null) {
+            loginResultVO.setLastLoginIp(loginLogVO.getLoginIp());
+            loginResultVO.setLastLoginIpRegion(loginLogVO.getLoginIpRegion());
+            loginResultVO.setLastLoginTime(loginLogVO.getCreateTime());
+            loginResultVO.setLastLoginUserAgent(loginLogVO.getUserAgent());
+        }
+        // 是否需要强制修改密码
+        boolean needChangePasswordFlag = protectPasswordService.checkNeedChangePassword(requestEmployee.getUserType().getValue(), requestEmployee.getUserId());
+        loginResultVO.setNeedUpdatePwdFlag(needChangePasswordFlag);
+
+        // 万能密码登录,则不需要设置强制修改密码
+        String loginIdByToken = (String) StpUtil.getLoginIdByToken(token);
+        if (loginIdByToken != null && loginIdByToken.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
+            loginResultVO.setNeedUpdatePwdFlag(false);
+        }
+
+        return loginResultVO;
+    }
+
+
+    /**
+     * 获取登录的用户信息
+     */
+    private RequestEmployee loadLoginInfo(EmployeeEntity employeeEntity) {
+
+        // 基础信息
+        RequestEmployee requestEmployee = BlinkBeanUtil.copy(employeeEntity, RequestEmployee.class);
+        requestEmployee.setUserType(UserTypeEnum.ADMIN_EMPLOYEE);
+
+        // 部门信息
+        DepartmentVO department = departmentService.getDepartmentById(employeeEntity.getDepartmentId());
+        requestEmployee.setDepartmentName(null == department ? StringConst.EMPTY : department.getName());
+
+        // 头像信息
+        String avatar = employeeEntity.getAvatar();
+        if (StringUtils.isNotBlank(avatar)) {
+            ResponseDTO<String> getFileUrl = fileStorageService.getFileUrl(avatar);
+            if (BooleanUtils.isTrue(getFileUrl.getOk())) {
+                requestEmployee.setAvatar(getFileUrl.getData());
+            }
+        }
+
+        return requestEmployee;
+    }
+
+
+    /**
+     * 根据登陆token 获取员请求工信息
+     */
+    public RequestEmployee getLoginEmployee(String loginId, HttpServletRequest request) {
+        if (loginId == null) {
+            return null;
+        }
+
+        Long requestEmployeeId = getEmployeeIdByLoginId(loginId);
+        if (requestEmployeeId == null) {
+            return null;
+        }
+
+        RequestEmployee requestEmployee = loginEmployeeCache.get(requestEmployeeId);
+        if (requestEmployee == null) {
+            // 员工基本信息
+            EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId);
+            if (employeeEntity == null) {
+                return null;
+            }
+
+            requestEmployee = this.loadLoginInfo(employeeEntity);
+            loginEmployeeCache.put(requestEmployeeId, requestEmployee);
+        }
+
+        // 更新请求ip和user agent
+        requestEmployee.setUserAgent(ServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT));
+        requestEmployee.setIp(ServletUtil.getClientIP(request));
+
+        return requestEmployee;
+    }
+
+    /**
+     * 根据 loginId 获取 员工id
+     */
+    Long getEmployeeIdByLoginId(String loginId) {
+
+        if (loginId == null) {
+            return null;
+        }
+
+        try {
+            // 如果是 万能密码 登录的用户
+            String employeeIdStr = null;
+            if (loginId.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
+                employeeIdStr = loginId.split(StringConst.COLON)[2];
+            } else {
+                employeeIdStr = loginId.substring(2);
+            }
+
+            return Long.parseLong(employeeIdStr);
+        } catch (Exception e) {
+            log.error("loginId parse error , loginId : {}", loginId, e);
+            return null;
+        }
+    }
+
+
+    /**
+     * 退出登录
+     */
+    public ResponseDTO<String> logout(String token, RequestUser requestUser) {
+
+        // sa token 登出
+        StpUtil.logoutByTokenValue(token);
+
+        // 清空登录信息缓存
+        loginEmployeeCache.remove(requestUser.getUserId());
+
+        //保存登出日志
+        LoginLogEntity loginEntity = LoginLogEntity.builder()
+                .userId(requestUser.getUserId())
+                .userType(requestUser.getUserType().getValue())
+                .userName(requestUser.getUserName())
+                .userAgent(requestUser.getUserAgent())
+                .loginIp(requestUser.getIp())
+                .loginIpRegion(BlinkIpUtil.getRegion(requestUser.getIp()))
+                .loginResult(LoginLogResultEnum.LOGIN_OUT.getValue())
+                .createTime(LocalDateTime.now())
+                .build();
+        loginLogService.log(loginEntity);
+
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 清除员工登录缓存
+     *
+     * @param employeeId
+     */
+    public void clearLoginEmployeeCache(Long employeeId) {
+        // 清空登录信息缓存
+        loginEmployeeCache.remove(employeeId);
+    }
+
+    /**
+     * 保存登录日志
+     */
+    private void saveLoginLog(EmployeeEntity employeeEntity, String ip, String userAgent, String remark, LoginLogResultEnum result) {
+        LoginLogEntity loginEntity = LoginLogEntity.builder()
+                .userId(employeeEntity.getEmployeeId())
+                .userType(UserTypeEnum.ADMIN_EMPLOYEE.getValue())
+                .userName(employeeEntity.getActualName())
+                .userAgent(userAgent)
+                .loginIp(ip)
+                .loginIpRegion(BlinkIpUtil.getRegion(ip))
+                .remark(remark)
+                .loginResult(result.getValue())
+                .createTime(LocalDateTime.now())
+                .build();
+        loginLogService.log(loginEntity);
+    }
+
+
+    @Override
+    public List<String> getPermissionList(Object loginId, String loginType) {
+        Long employeeId = this.getEmployeeIdByLoginId((String) loginId);
+        if (employeeId == null) {
+            return Collections.emptyList();
+        }
+
+        UserPermission userPermission = permissionCache.get(employeeId);
+        if (userPermission == null) {
+            userPermission = getUserPermission(employeeId);
+            permissionCache.put(employeeId, userPermission);
+        }
+
+        return userPermission.getPermissionList();
+    }
+
+    @Override
+    public List<String> getRoleList(Object loginId, String loginType) {
+        Long employeeId = this.getEmployeeIdByLoginId((String) loginId);
+        if (employeeId == null) {
+            return Collections.emptyList();
+        }
+
+        UserPermission userPermission = permissionCache.get(employeeId);
+        if (userPermission == null) {
+            userPermission = getUserPermission(employeeId);
+            permissionCache.put(employeeId, userPermission);
+        }
+        return userPermission.getRoleList();
+    }
+
+    /**
+     * 获取用户的权限(包含 角色列表、权限列表)
+     */
+    private UserPermission getUserPermission(Long employeeId) {
+
+        UserPermission userPermission = new UserPermission();
+        userPermission.setPermissionList(new ArrayList<>());
+        userPermission.setRoleList(new ArrayList<>());
+
+        // 角色列表
+        List<RoleVO> roleList = roleEmployeeService.getRoleIdList(employeeId);
+        userPermission.getRoleList().addAll(roleList.stream().map(RoleVO::getRoleCode).collect(Collectors.toSet()));
+
+        // 前端菜单和功能点清单
+        EmployeeEntity employeeEntity = employeeService.getById(employeeId);
+
+        List<MenuVO> menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), employeeEntity.getAdministratorFlag());
+
+        // 权限列表
+        HashSet<String> permissionSet = new HashSet<>();
+        for (MenuVO menu : menuAndPointsList) {
+            if (menu.getPermsType() == null) {
+                continue;
+            }
+
+            String perms = menu.getApiPerms();
+            if (StringUtils.isEmpty(perms)) {
+                continue;
+            }
+            //接口权限
+            String[] split = perms.split(",");
+            permissionSet.addAll(Arrays.asList(split));
+        }
+        userPermission.getPermissionList().addAll(permissionSet);
+
+        return userPermission;
+    }
+
+
+    /**
+     * 发送 邮箱 验证码
+     */
+    public ResponseDTO<String> sendEmailCode(String loginName) {
+
+        // 开启双因子登录
+        if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
+            return ResponseDTO.userErrorParam("无需使用邮箱验证码");
+        }
+
+        // 验证登录名
+        EmployeeEntity employeeEntity = employeeService.getByLoginName(loginName);
+        if (null == employeeEntity) {
+            return ResponseDTO.userErrorParam("登录名不存在!");
+        }
+        // 验证账号状态
+        if (employeeEntity.getDeletedFlag()) {
+            return ResponseDTO.userErrorParam("您的账号已被删除,请联系工作人员!");
+        }
+
+        // 验证账号状态
+        if (employeeEntity.getDisabledFlag()) {
+            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!");
+        }
+
+        String mail = employeeEntity.getEmail();
+        if (BlinkStringUtil.isBlank(mail)) {
+            return ResponseDTO.userErrorParam("您暂未配置邮箱地址,请联系管理员配置邮箱");
+        }
+
+        // 校验验证码发送时间,60秒内不能重复发生
+        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
+        String emailCode = redisService.get(redisVerificationCodeKey);
+        long sendCodeTimeMills = -1;
+        if (!com.cloud.sa.base.common.util.BlinkStringUtil.isEmpty(emailCode)) {
+            sendCodeTimeMills = NumberUtil.parseLong(emailCode.split(StringConst.UNDERLINE)[1]);
+        }
+
+        if (System.currentTimeMillis() - sendCodeTimeMills < 60 * 1000) {
+            return ResponseDTO.userErrorParam("邮箱验证码已发送,一分钟内请勿重复发送");
+        }
+
+        //生成验证码
+        long currentTimeMillis = System.currentTimeMillis();
+        String verificationCode = RandomUtil.randomNumbers(4);
+        redisService.set(redisVerificationCodeKey, verificationCode + StringConst.UNDERLINE + currentTimeMillis, 300);
+
+        // 发送邮件验证码
+        HashMap<String, Object> mailParams = new HashMap<>();
+        mailParams.put("code", verificationCode);
+        return mailService.sendMail(MailTemplateCodeEnum.LOGIN_VERIFICATION_CODE, mailParams, Collections.singletonList(employeeEntity.getEmail()));
+    }
+
+
+    /**
+     * 校验邮箱验证码
+     */
+    private ResponseDTO<String> validateEmailCode(LoginForm loginForm, EmployeeEntity employeeEntity, boolean superPasswordFlag) {
+        // 万能密码则不校验
+        if (superPasswordFlag) {
+            return ResponseDTO.ok();
+        }
+
+        // 未开启双因子登录
+        if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
+            return ResponseDTO.ok();
+        }
+
+        if (com.cloud.sa.base.common.util.BlinkStringUtil.isEmpty(loginForm.getEmailCode())) {
+            return ResponseDTO.userErrorParam("请输入邮箱验证码");
+        }
+
+        // 校验验证码
+        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
+        String emailCode = redisService.get(redisVerificationCodeKey);
+        if (com.cloud.sa.base.common.util.BlinkStringUtil.isEmpty(emailCode)) {
+            return ResponseDTO.userErrorParam("邮箱验证码已失效,请重新发送");
+        }
+
+        if (!emailCode.split(StringConst.UNDERLINE)[0].equals(loginForm.getEmailCode().trim())) {
+            return ResponseDTO.userErrorParam("邮箱验证码错误,请重新填写");
+        }
+
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 移除邮箱验证码
+     */
+    private void deleteEmailCode(Long employeeId) {
+        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeId);
+        redisService.delete(redisVerificationCodeKey);
+    }
+}

+ 23 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/strategy/LoginStrategy.java

@@ -0,0 +1,23 @@
+package com.cloud.sa.admin.module.system.login.strategy;
+
+
+import com.cloud.sa.admin.module.system.login.domain.LoginForm;
+import com.cloud.sa.admin.module.system.login.domain.LoginResultVO;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 定义统一的登录策略接口
+ */
+public interface LoginStrategy {
+
+    /**
+     * 登录
+     *
+     * @param loginForm
+     * @param request
+     * @return
+     */
+    ResponseDTO<LoginResultVO> login(LoginForm loginForm, HttpServletRequest request);
+}

+ 92 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/strategy/impl/DingTalkScanLoginStrategy.java

@@ -0,0 +1,92 @@
+package com.cloud.sa.admin.module.system.login.strategy.impl;
+
+import cn.hutool.extra.servlet.ServletUtil;
+import com.cloud.sa.admin.module.system.login.domain.LoginForm;
+import com.cloud.sa.admin.module.system.login.domain.LoginResultVO;
+import com.cloud.sa.admin.module.system.login.service.LoginService;
+import com.cloud.sa.admin.module.system.login.strategy.LoginStrategy;
+import com.cloud.sa.base.common.constant.RequestHeaderConst;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiGettokenRequest;
+import com.dingtalk.api.request.OapiSnsGetuserinfoBycodeRequest;
+import com.dingtalk.api.request.OapiUserGetbyunionidRequest;
+import com.dingtalk.api.request.OapiV2UserGetRequest;
+import com.dingtalk.api.response.OapiGettokenResponse;
+import com.dingtalk.api.response.OapiSnsGetuserinfoBycodeResponse;
+import com.dingtalk.api.response.OapiUserGetbyunionidResponse;
+import com.dingtalk.api.response.OapiV2UserGetResponse;
+import com.taobao.api.ApiException;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+public class DingTalkScanLoginStrategy implements LoginStrategy {
+    @Resource
+    private LoginService loginService;
+
+    @Override
+    public ResponseDTO<LoginResultVO> login(LoginForm loginForm, HttpServletRequest request) {
+        String ip = ServletUtil.getClientIP(request);
+        String userAgent = ServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT);
+        ResponseDTO<LoginResultVO> responseDTO = null;
+        String access_token = getToken();
+
+        // 通过临时授权码获取授权用户的个人信息
+        DefaultDingTalkClient client2 = new DefaultDingTalkClient("https://oapi.dingtalk.com/sns/getuserinfo_bycode");
+        OapiSnsGetuserinfoBycodeRequest reqBycodeRequest = new OapiSnsGetuserinfoBycodeRequest();
+        // 通过扫描二维码,跳转指定的redirect_uri后,向url中追加的code临时授权码
+        reqBycodeRequest.setTmpAuthCode(loginForm.getAuthCode());
+        OapiSnsGetuserinfoBycodeResponse bycodeResponse = null;
+        try {
+            bycodeResponse = client2.execute(reqBycodeRequest, "dingu35ilmyp0f4vlkfd", "yhDXTxSvDFMMii0obVe47-PGvQ8RAPzb8ugZoBxd38bHvPDJN7UoJWd2deMdLpJs");
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+
+        // 根据unionid获取userid
+        String unionid = bycodeResponse.getUserInfo().getUnionid();
+        DingTalkClient clientDingTalkClient = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
+        OapiUserGetbyunionidRequest reqGetbyunionidRequest = new OapiUserGetbyunionidRequest();
+        reqGetbyunionidRequest.setUnionid(unionid);
+        OapiUserGetbyunionidResponse oapiUserGetbyunionidResponse;
+        try {
+            oapiUserGetbyunionidResponse = clientDingTalkClient.execute(reqGetbyunionidRequest, access_token);
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+
+        // 根据userId获取用户信息
+        String userid = oapiUserGetbyunionidResponse.getResult().getUserid();
+        DingTalkClient clientDingTalkClient2 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
+        OapiV2UserGetRequest reqGetRequest = new OapiV2UserGetRequest();
+        reqGetRequest.setUserid(userid);
+        reqGetRequest.setLanguage("zh_CN");
+        try {
+            OapiV2UserGetResponse rspGetResponse = clientDingTalkClient2.execute(reqGetRequest, access_token);
+            responseDTO = loginService.dingtalkLogin(rspGetResponse.getResult().getMobile(),ip,userAgent);
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+        return responseDTO;
+    }
+
+    public static String getToken() {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
+        OapiGettokenRequest request = new OapiGettokenRequest();
+        request.setAppkey("dingu35ilmyp0f4vlkfd");
+        request.setAppsecret("yhDXTxSvDFMMii0obVe47-PGvQ8RAPzb8ugZoBxd38bHvPDJN7UoJWd2deMdLpJs");
+        request.setHttpMethod("GET");
+        OapiGettokenResponse response = null;
+        try {
+            response = client.execute(request);
+        } catch (ApiException e) {
+            e.printStackTrace();
+        }
+        System.out.println(response.getBody());
+        return response.getAccessToken();
+    }
+}

+ 26 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/login/strategy/impl/UsernamePasswordLoginStrategy.java

@@ -0,0 +1,26 @@
+package com.cloud.sa.admin.module.system.login.strategy.impl;
+
+import cn.hutool.extra.servlet.ServletUtil;
+import com.cloud.sa.admin.module.system.login.domain.LoginForm;
+import com.cloud.sa.admin.module.system.login.domain.LoginResultVO;
+import com.cloud.sa.admin.module.system.login.service.LoginService;
+import com.cloud.sa.admin.module.system.login.strategy.LoginStrategy;
+import com.cloud.sa.base.common.constant.RequestHeaderConst;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+public class UsernamePasswordLoginStrategy implements LoginStrategy {
+    @Resource
+    private LoginService loginService;
+
+    @Override
+    public ResponseDTO<LoginResultVO> login(LoginForm loginForm, HttpServletRequest request) {
+        String ip = ServletUtil.getClientIP(request);
+        String userAgent = ServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT);
+        return loginService.login(loginForm, ip, userAgent);
+    }
+}

+ 39 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/constant/MenuPermsTypeEnum.java

@@ -0,0 +1,39 @@
+package com.cloud.sa.admin.module.system.menu.constant;
+
+
+import com.cloud.sa.base.common.enumeration.BaseEnum;
+
+/**
+ * 权限类型
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+public enum MenuPermsTypeEnum implements BaseEnum {
+    /**
+     * sa-token
+     */
+    SA_TOKEN(1, "Sa-Token模式"),
+
+    ;
+
+    private final Integer value;
+
+    private final String desc;
+
+
+    MenuPermsTypeEnum(Integer value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+}

+ 45 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/constant/MenuTypeEnum.java

@@ -0,0 +1,45 @@
+package com.cloud.sa.admin.module.system.menu.constant;
+
+
+import com.cloud.sa.base.common.enumeration.BaseEnum;
+
+/**
+ * 菜单类型枚举
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+public enum MenuTypeEnum implements BaseEnum {
+    /**
+     * 目录
+     */
+    CATALOG(1, "目录"),
+    /**
+     * 菜单
+     */
+    MENU(2, "菜单"),
+    /**
+     * 功能点
+     */
+    POINTS(3, "功能点");
+
+    private final Integer value;
+
+    private final String desc;
+
+
+    MenuTypeEnum(Integer value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+}

+ 80 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/controller/MenuController.java

@@ -0,0 +1,80 @@
+package com.cloud.sa.admin.module.system.menu.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import com.cloud.sa.admin.module.system.menu.service.MenuService;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.module.system.menu.domain.form.MenuAddForm;
+import com.cloud.sa.admin.module.system.menu.domain.form.MenuUpdateForm;
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuTreeVO;
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuVO;
+import com.cloud.sa.base.common.domain.RequestUrlVO;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.util.BlinkRequestUtil;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 菜单
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_MENU)
+public class MenuController {
+
+    @Resource
+    private MenuService menuService;
+
+    @Operation(summary = "添加菜单 @author admin")
+    @PostMapping("/menu/add")
+    @SaCheckPermission("system:menu:add")
+    public ResponseDTO<String> addMenu(@RequestBody @Valid MenuAddForm menuAddForm) {
+        menuAddForm.setCreateUserId(BlinkRequestUtil.getRequestUserId());
+        return menuService.addMenu(menuAddForm);
+    }
+
+    @Operation(summary = "更新菜单 @author admin")
+    @PostMapping("/menu/update")
+    @SaCheckPermission("system:menu:update")
+    public ResponseDTO<String> updateMenu(@RequestBody @Valid MenuUpdateForm menuUpdateForm) {
+        menuUpdateForm.setUpdateUserId(BlinkRequestUtil.getRequestUserId());
+        return menuService.updateMenu(menuUpdateForm);
+    }
+
+    @Operation(summary = "批量删除菜单 @author admin")
+    @GetMapping("/menu/batchDelete")
+    @SaCheckPermission("system:menu:batchDelete")
+    public ResponseDTO<String> batchDeleteMenu(@RequestParam("menuIdList") List<Long> menuIdList) {
+        return menuService.batchDeleteMenu(menuIdList, BlinkRequestUtil.getRequestUserId());
+    }
+
+    @Operation(summary = "查询菜单列表 @author admin")
+    @GetMapping("/menu/query")
+    public ResponseDTO<List<MenuVO>> queryMenuList() {
+        return ResponseDTO.ok(menuService.queryMenuList(null));
+    }
+
+    @Operation(summary = "查询菜单详情 @author admin")
+    @GetMapping("/menu/detail/{menuId}")
+    public ResponseDTO<MenuVO> getMenuDetail(@PathVariable Long menuId) {
+        return menuService.getMenuDetail(menuId);
+    }
+
+    @Operation(summary = "查询菜单树 @author admin")
+    @GetMapping("/menu/tree")
+    public ResponseDTO<List<MenuTreeVO>> queryMenuTree(@RequestParam("onlyMenu") Boolean onlyMenu) {
+        return menuService.queryMenuTree(onlyMenu);
+    }
+
+    @Operation(summary = "获取所有请求路径 @author admin")
+    @GetMapping("/menu/auth/url")
+    public ResponseDTO<List<RequestUrlVO>> getAuthUrl() {
+        return menuService.getAuthUrl();
+    }
+}

+ 94 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/dao/MenuDao.java

@@ -0,0 +1,94 @@
+package com.cloud.sa.admin.module.system.menu.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cloud.sa.admin.module.system.menu.domain.entity.MenuEntity;
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 菜单 dao
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Mapper
+@Component
+public interface MenuDao extends BaseMapper<MenuEntity> {
+
+    /**
+     * 根据名称查询同一级下的菜单
+     *
+     * @param menuName    菜单名
+     * @param parentId    父级id
+     * @param deletedFlag 是否删除
+     */
+    MenuEntity getByMenuName(@Param("menuName") String menuName, @Param("parentId") Long parentId, @Param("deletedFlag") Boolean deletedFlag);
+
+    /**
+     * 根据前端权限字符串查询菜单
+     *
+     * @param webPerms    前端权限字符串
+     * @param deletedFlag 是否删除
+     */
+    MenuEntity getByWebPerms(@Param("webPerms") String webPerms, @Param("deletedFlag") Boolean deletedFlag);
+
+    /**
+     * 根据菜单ID删除菜单(逻辑删除)
+     *
+     * @param menuIdList   菜单id集合
+     * @param updateUserId 操作人id
+     * @param deletedFlag  是否删除
+     */
+    void deleteByMenuIdList(@Param("menuIdList") List<Long> menuIdList, @Param("updateUserId") Long updateUserId, @Param("deletedFlag") Boolean deletedFlag);
+
+    /**
+     * 查询菜单列表
+     *
+     * @param deletedFlag  是否删除
+     * @param disabledFlag 是否禁用
+     * @param menuTypeList 菜单类型集合
+     */
+    List<MenuVO> queryMenuList(@Param("deletedFlag") Boolean deletedFlag, @Param("disabledFlag") Boolean disabledFlag, @Param("menuTypeList") List<Integer> menuTypeList);
+
+
+    /**
+     * 根据菜单ID 查询功能点列表
+     *
+     * @param menuId      菜单id
+     * @param menuType    菜单类型
+     * @param deletedFlag 删除标记
+     */
+    List<MenuEntity> getPointListByMenuId(@Param("menuId") Long menuId, @Param("menuType") Integer menuType, @Param("deletedFlag") Boolean deletedFlag);
+
+    /**
+     * 根据员工ID查询菜单列表
+     *
+     * @param deletedFlag  是否删除
+     * @param disabledFlag 禁用标识
+     * @param employeeId   员工id
+     */
+    List<MenuVO> queryMenuByEmployeeId(@Param("deletedFlag") Boolean deletedFlag,
+                                       @Param("disabledFlag") Boolean disabledFlag,
+                                       @Param("employeeId") Long employeeId);
+
+    /**
+     * 根据菜单类型查询
+     *
+     * @param menuType     菜单类型
+     * @param deletedFlag  删除
+     * @param disabledFlag 禁用
+     */
+    List<MenuEntity> queryMenuByType(@Param("menuType") Integer menuType,
+                                     @Param("deletedFlag") Boolean deletedFlag,
+                                     @Param("disabledFlag") Boolean disabledFlag);
+
+    /**
+     * 查询孩子id
+     *
+     */
+    List<Long> selectMenuIdByParentIdList(@Param("menuIdList") List<Long> menuIdList);
+}

+ 133 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/entity/MenuEntity.java

@@ -0,0 +1,133 @@
+package com.cloud.sa.admin.module.system.menu.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import com.cloud.sa.admin.module.system.menu.constant.MenuTypeEnum;
+
+import java.time.LocalDateTime;
+
+/**
+ * 菜单 表
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+@TableName(value = "mate_menu")
+public class MenuEntity {
+
+    /**
+     * 菜单ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long menuId;
+
+    /**
+     * 菜单名称
+     */
+    private String menuName;
+
+    /**
+     * 类型
+     *
+     * @see MenuTypeEnum
+     */
+    private Integer menuType;
+
+    /**
+     * 父菜单ID
+     */
+    private Long parentId;
+
+    /**
+     * 显示顺序
+     */
+    private Integer sort;
+
+    /**
+     * 路由地址
+     */
+    private String path;
+
+    /**
+     * 组件路径
+     */
+    private String component;
+
+    /**
+     * 是否为外链
+     */
+    private Boolean frameFlag;
+
+    /**
+     * 外链地址
+     */
+    private String frameUrl;
+
+    /**
+     * 是否缓存
+     */
+    private Boolean cacheFlag;
+
+    /**
+     * 显示状态
+     */
+    private Boolean visibleFlag;
+
+    /**
+     * 禁用状态
+     */
+    private Boolean disabledFlag;
+
+    /**
+     * 后端权限字符串
+     */
+    private String apiPerms;
+
+    /**
+     * 权限类型
+     */
+    private Integer permsType;
+
+    /**
+     * 前端权限字符串
+     */
+    private String webPerms;
+
+    /**
+     * 菜单图标
+     */
+    private String icon;
+
+    /**
+     * 功能点关联菜单ID
+     */
+    private Long contextMenuId;
+
+    /**
+     * 删除状态
+     */
+    private Boolean deletedFlag;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 创建人
+     */
+    private Long createUserId;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 更新人
+     */
+    private Long updateUserId;
+}

+ 17 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuAddForm.java

@@ -0,0 +1,17 @@
+package com.cloud.sa.admin.module.system.menu.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 菜单 添加表单
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+public class MenuAddForm extends MenuBaseForm {
+
+    @Schema(hidden = true)
+    private Long createUserId;
+}

+ 79 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuBaseForm.java

@@ -0,0 +1,79 @@
+package com.cloud.sa.admin.module.system.menu.domain.form;
+
+import com.cloud.sa.admin.module.system.menu.constant.MenuPermsTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+import com.cloud.sa.base.common.validator.enumeration.CheckEnum;
+import org.hibernate.validator.constraints.Length;
+import com.cloud.sa.admin.module.system.menu.constant.MenuTypeEnum;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 菜单基础
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+public class MenuBaseForm {
+
+    @Schema(description = "菜单名称")
+    @NotBlank(message = "菜单名称不能为空")
+    @Length(max = 30, message = "菜单名称最多30个字符")
+    private String menuName;
+
+    @SchemaEnum(value = MenuTypeEnum.class, desc = "类型")
+    @CheckEnum(value = MenuTypeEnum.class, message = "类型错误")
+    private Integer menuType;
+
+    @Schema(description = "父菜单ID 无上级可传0")
+    @NotNull(message = "父菜单ID不能为空")
+    private Long parentId;
+
+    @Schema(description = "显示顺序")
+    private Integer sort;
+
+    @Schema(description = "路由地址")
+    private String path;
+
+    @Schema(description = "组件路径")
+    private String component;
+
+    @Schema(description = "是否为外链")
+    @NotNull(message = "是否为外链不能为空")
+    private Boolean frameFlag;
+
+    @Schema(description = "外链地址")
+    private String frameUrl;
+
+    @Schema(description = "是否缓存")
+    @NotNull(message = "是否缓存不能为空")
+    private Boolean cacheFlag;
+
+    @Schema(description = "显示状态")
+    @NotNull(message = "显示状态不能为空")
+    private Boolean visibleFlag;
+
+    @Schema(description = "禁用状态")
+    @NotNull(message = "禁用状态不能为空")
+    private Boolean disabledFlag;
+
+    @SchemaEnum(value = MenuPermsTypeEnum.class, desc = "权限类型 ")
+    @CheckEnum(value = MenuPermsTypeEnum.class, message = "权限类型")
+    private Integer permsType;
+
+    @Schema(description = "前端权限字符串")
+    private String webPerms;
+
+    @Schema(description = "后端端权限字符串")
+    private String apiPerms;
+
+    @Schema(description = "菜单图标")
+    private String icon;
+
+    @Schema(description = "功能点关联菜单ID")
+    private Long contextMenuId;
+}

+ 40 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuPointsOperateForm.java

@@ -0,0 +1,40 @@
+package com.cloud.sa.admin.module.system.menu.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 菜单功能点操作Form
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+public class MenuPointsOperateForm {
+
+    @Schema(description = "菜单ID")
+    private Long menuId;
+
+    @Schema(description = "功能点名称")
+    @NotBlank(message = "功能点不能为空")
+    @Length(max = 30, message = "功能点最多30个字符")
+    private String menuName;
+
+    @Schema(description = "禁用状态")
+    @NotNull(message = "禁用状态不能为空")
+    private Boolean disabledFlag;
+
+    @Schema(description = "后端接口权限集合")
+    private List<String> apiPermsList;
+
+    @Schema(description = "权限字符串")
+    private String webPerms;
+
+    @Schema(description = "功能点关联菜单ID")
+    private Long contextMenuId;
+}

+ 23 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/form/MenuUpdateForm.java

@@ -0,0 +1,23 @@
+package com.cloud.sa.admin.module.system.menu.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 菜单 更新Form
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+public class MenuUpdateForm extends MenuBaseForm {
+
+    @Schema(description = "菜单ID")
+    @NotNull(message = "菜单ID不能为空")
+    private Long menuId;
+
+    @Schema(hidden = true)
+    private Long updateUserId;
+}

+ 34 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/vo/MenuSimpleTreeVO.java

@@ -0,0 +1,34 @@
+package com.cloud.sa.admin.module.system.menu.domain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 简易的菜单VO
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+public class MenuSimpleTreeVO {
+
+    @Schema(description = "菜单ID")
+    private Long menuId;
+
+    @Schema(description = "菜单名称")
+    private String menuName;
+
+    @Schema(description = "功能点关联菜单ID")
+    private Long contextMenuId;
+
+    @Schema(description = "父级菜单ID")
+    private Long parentId;
+
+    @Schema(description = "菜单类型")
+    private Integer menuType;
+
+    @Schema(description = "子菜单")
+    private List<MenuSimpleTreeVO> children;
+}

+ 19 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/vo/MenuTreeVO.java

@@ -0,0 +1,19 @@
+package com.cloud.sa.admin.module.system.menu.domain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 菜单
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+public class MenuTreeVO extends MenuVO{
+
+    @Schema(description = "菜单子集")
+    private List<MenuTreeVO> children;
+}

+ 32 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/domain/vo/MenuVO.java

@@ -0,0 +1,32 @@
+package com.cloud.sa.admin.module.system.menu.domain.vo;
+
+import com.cloud.sa.admin.module.system.menu.domain.form.MenuBaseForm;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 菜单
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-06 22:04:37
+ */
+@Data
+public class MenuVO extends MenuBaseForm {
+
+    @Schema(description = "菜单ID")
+    private Long menuId;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "创建人")
+    private Long createUserId;
+
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+
+    @Schema(description = "更新人")
+    private Long updateUserId;
+}

+ 229 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/menu/service/MenuService.java

@@ -0,0 +1,229 @@
+package com.cloud.sa.admin.module.system.menu.service;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.google.common.collect.Lists;
+import com.cloud.sa.admin.module.system.menu.constant.MenuTypeEnum;
+import com.cloud.sa.admin.module.system.menu.dao.MenuDao;
+import com.cloud.sa.admin.module.system.menu.domain.entity.MenuEntity;
+import com.cloud.sa.admin.module.system.menu.domain.form.MenuAddForm;
+import com.cloud.sa.admin.module.system.menu.domain.form.MenuBaseForm;
+import com.cloud.sa.admin.module.system.menu.domain.form.MenuUpdateForm;
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuTreeVO;
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuVO;
+import com.cloud.sa.base.common.code.SystemErrorCode;
+import com.cloud.sa.base.common.domain.RequestUrlVO;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.util.BlinkBeanUtil;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 菜单
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-08 22:15:09
+ */
+@Service
+public class MenuService {
+
+    @Resource
+    private MenuDao menuDao;
+
+    @Resource
+    private List<RequestUrlVO> authUrl;
+
+    /**
+     * 添加菜单
+     *
+     */
+    public synchronized ResponseDTO<String> addMenu(MenuAddForm menuAddForm) {
+        // 校验菜单名称
+        if (this.validateMenuName(menuAddForm)) {
+            return ResponseDTO.userErrorParam("菜单名称已存在");
+        }
+        // 校验前端权限字符串
+        if (this.validateWebPerms(menuAddForm)) {
+            return ResponseDTO.userErrorParam("前端权限字符串已存在");
+        }
+        MenuEntity menuEntity = BlinkBeanUtil.copy(menuAddForm, MenuEntity.class);
+        menuDao.insert(menuEntity);
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 更新菜单
+     *
+     */
+    public synchronized ResponseDTO<String> updateMenu(MenuUpdateForm menuUpdateForm) {
+        //校验菜单是否存在
+        MenuEntity selectMenu = menuDao.selectById(menuUpdateForm.getMenuId());
+        if (selectMenu == null) {
+            return ResponseDTO.userErrorParam("菜单不存在");
+        }
+        if (selectMenu.getDeletedFlag()) {
+            return ResponseDTO.userErrorParam("菜单已被删除");
+        }
+        //校验菜单名称
+        if (this.validateMenuName(menuUpdateForm)) {
+            return ResponseDTO.userErrorParam("菜单名称已存在");
+        }
+        // 校验前端权限字符串
+        if (this.validateWebPerms(menuUpdateForm)) {
+            return ResponseDTO.userErrorParam("前端权限字符串已存在");
+        }
+        if (menuUpdateForm.getMenuId().equals(menuUpdateForm.getParentId())) {
+            return ResponseDTO.userErrorParam("上级菜单不能为自己");
+        }
+        MenuEntity menuEntity = BlinkBeanUtil.copy(menuUpdateForm, MenuEntity.class);
+        menuDao.updateById(menuEntity);
+        return ResponseDTO.ok();
+    }
+
+
+    /**
+     * 批量删除菜单
+     *
+     */
+    public synchronized ResponseDTO<String> batchDeleteMenu(List<Long> menuIdList, Long employeeId) {
+        if (CollectionUtils.isEmpty(menuIdList)) {
+            return ResponseDTO.userErrorParam("所选菜单不能为空");
+        }
+        menuDao.deleteByMenuIdList(menuIdList, employeeId, Boolean.TRUE);
+        //孩子节点也需要删除
+        this.recursiveDeleteChildren(menuIdList, employeeId);
+        return ResponseDTO.ok();
+    }
+
+    private void recursiveDeleteChildren(List<Long> menuIdList, Long employeeId) {
+        List<Long> childrenMenuIdList = menuDao.selectMenuIdByParentIdList(menuIdList);
+        if (CollectionUtil.isEmpty(childrenMenuIdList)) {
+            return;
+        }
+        menuDao.deleteByMenuIdList(childrenMenuIdList, employeeId, Boolean.TRUE);
+        recursiveDeleteChildren(childrenMenuIdList, employeeId);
+    }
+
+    /**
+     * 校验菜单名称
+     *
+     */
+    public <T extends MenuBaseForm> Boolean validateMenuName(T menuDTO) {
+        MenuEntity menu = menuDao.getByMenuName(menuDTO.getMenuName(), menuDTO.getParentId(), Boolean.FALSE);
+        if (menuDTO instanceof MenuAddForm) {
+            return menu != null;
+        }
+        if (menuDTO instanceof MenuUpdateForm) {
+            Long menuId = ((MenuUpdateForm) menuDTO).getMenuId();
+            return menu != null && menu.getMenuId().longValue() != menuId.longValue();
+        }
+        return true;
+    }
+
+    /**
+     * 校验前端权限字符串
+     *
+     * @return true 重复 false 未重复
+     */
+    public <T extends MenuBaseForm> Boolean validateWebPerms(T menuDTO) {
+        MenuEntity menu = menuDao.getByWebPerms(menuDTO.getWebPerms(), Boolean.FALSE);
+        if (menuDTO instanceof MenuAddForm) {
+            return menu != null;
+        }
+        if (menuDTO instanceof MenuUpdateForm) {
+            Long menuId = ((MenuUpdateForm) menuDTO).getMenuId();
+            return menu != null && menu.getMenuId().longValue() != menuId.longValue();
+        }
+        return true;
+    }
+
+    /**
+     * 查询菜单列表
+     *
+     */
+    public List<MenuVO> queryMenuList(Boolean disabledFlag) {
+        List<MenuVO> menuVOList = menuDao.queryMenuList(Boolean.FALSE, disabledFlag, null);
+        //根据ParentId进行分组
+        Map<Long, List<MenuVO>> parentMap = menuVOList.stream().collect(Collectors.groupingBy(MenuVO::getParentId, Collectors.toList()));
+        return this.filterNoParentMenu(parentMap, NumberUtils.LONG_ZERO);
+    }
+
+    /**
+     * 过滤没有上级菜单的菜单列表
+     *
+     */
+    private List<MenuVO> filterNoParentMenu(Map<Long, List<MenuVO>> parentMap, Long parentId) {
+        // 获取本级菜单树List
+        List<MenuVO> res = parentMap.getOrDefault(parentId, Lists.newArrayList());
+        List<MenuVO> childMenu = Lists.newArrayList();
+        // 循环遍历下级菜单
+        res.forEach(e -> {
+            List<MenuVO> menuList = this.filterNoParentMenu(parentMap, e.getMenuId());
+            childMenu.addAll(menuList);
+        });
+        res.addAll(childMenu);
+        return res;
+    }
+
+    /**
+     * 查询菜单树
+     *
+     * @param onlyMenu 不查询功能点
+     */
+    public ResponseDTO<List<MenuTreeVO>> queryMenuTree(Boolean onlyMenu) {
+        List<Integer> menuTypeList = Lists.newArrayList();
+        if (onlyMenu) {
+            menuTypeList = Lists.newArrayList(MenuTypeEnum.CATALOG.getValue(), MenuTypeEnum.MENU.getValue());
+        }
+        List<MenuVO> menuVOList = menuDao.queryMenuList(Boolean.FALSE, null, menuTypeList);
+        //根据ParentId进行分组
+        Map<Long, List<MenuVO>> parentMap = menuVOList.stream().collect(Collectors.groupingBy(MenuVO::getParentId, Collectors.toList()));
+        List<MenuTreeVO> menuTreeVOList = this.buildMenuTree(parentMap, NumberUtils.LONG_ZERO);
+        return ResponseDTO.ok(menuTreeVOList);
+    }
+
+    /**
+     * 构建菜单树
+     *
+     */
+    List<MenuTreeVO> buildMenuTree(Map<Long, List<MenuVO>> parentMap, Long parentId) {
+        // 获取本级菜单树List
+        List<MenuTreeVO> res = parentMap.getOrDefault(parentId, Lists.newArrayList()).stream()
+                .map(e -> BlinkBeanUtil.copy(e, MenuTreeVO.class)).collect(Collectors.toList());
+        // 循环遍历下级菜单
+        res.forEach(e -> {
+            e.setChildren(this.buildMenuTree(parentMap, e.getMenuId()));
+        });
+        return res;
+    }
+
+    /**
+     * 查询菜单详情
+     *
+     */
+    public ResponseDTO<MenuVO> getMenuDetail(Long menuId) {
+        //校验菜单是否存在
+        MenuEntity selectMenu = menuDao.selectById(menuId);
+        if (selectMenu == null) {
+            return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "菜单不存在");
+        }
+        if (selectMenu.getDeletedFlag()) {
+            return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "菜单已被删除");
+        }
+        MenuVO menuVO = BlinkBeanUtil.copy(selectMenu, MenuVO.class);
+        return ResponseDTO.ok(menuVO);
+    }
+
+    /**
+     * 获取系统所有请求路径
+     */
+    public ResponseDTO<List<RequestUrlVO>> getAuthUrl() {
+        return ResponseDTO.ok(authUrl);
+    }
+
+}

+ 75 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/controller/PositionController.java

@@ -0,0 +1,75 @@
+package com.cloud.sa.admin.module.system.position.controller;
+
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import com.cloud.sa.admin.module.system.position.domain.form.PositionAddForm;
+import com.cloud.sa.admin.module.system.position.domain.form.PositionQueryForm;
+import com.cloud.sa.admin.module.system.position.domain.form.PositionUpdateForm;
+import com.cloud.sa.admin.module.system.position.domain.vo.PositionVO;
+import com.cloud.sa.admin.module.system.position.service.PositionService;
+import com.cloud.sa.base.common.domain.ValidateList;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.domain.PageResult;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 职务表 Controller
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_POSITION)
+public class PositionController {
+
+    @Resource
+    private PositionService positionService;
+
+    @Operation(summary = "分页查询 @author kaiyun")
+    @PostMapping("/position/queryPage")
+    public ResponseDTO<PageResult<PositionVO>> queryPage(@RequestBody @Valid PositionQueryForm queryForm) {
+        return ResponseDTO.ok(positionService.queryPage(queryForm));
+    }
+
+    @Operation(summary = "添加 @author kaiyun")
+    @PostMapping("/position/add")
+    public ResponseDTO<String> add(@RequestBody @Valid PositionAddForm addForm) {
+        return positionService.add(addForm);
+    }
+
+    @Operation(summary = "更新 @author kaiyun")
+    @PostMapping("/position/update")
+    public ResponseDTO<String> update(@RequestBody @Valid PositionUpdateForm updateForm) {
+        return positionService.update(updateForm);
+    }
+
+    @Operation(summary = "批量删除 @author kaiyun")
+    @PostMapping("/position/batchDelete")
+    public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
+        return positionService.batchDelete(idList);
+    }
+
+    @Operation(summary = "单个删除 @author kaiyun")
+    @GetMapping("/position/delete/{positionId}")
+    public ResponseDTO<String> batchDelete(@PathVariable Long positionId) {
+        return positionService.delete(positionId);
+    }
+
+
+    @Operation(summary = "不分页查询 @author kaiyun")
+    @GetMapping("/position/queryList")
+    public ResponseDTO<List<PositionVO>> queryList() {
+        return ResponseDTO.ok(positionService.queryList());
+    }
+}

+ 42 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/dao/PositionDao.java

@@ -0,0 +1,42 @@
+package com.cloud.sa.admin.module.system.position.dao;
+
+import java.util.List;
+
+import com.cloud.sa.admin.module.system.position.domain.entity.PositionEntity;
+import com.cloud.sa.admin.module.system.position.domain.form.PositionQueryForm;
+import com.cloud.sa.admin.module.system.position.domain.vo.PositionVO;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+/**
+ * 职务表 Dao
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+
+@Mapper
+@Component
+public interface PositionDao extends BaseMapper<PositionEntity> {
+
+    /**
+     * 分页 查询
+     *
+     * @param page
+     * @param queryForm
+     * @return
+     */
+    List<PositionVO> queryPage(Page page, @Param("queryForm") PositionQueryForm queryForm);
+
+
+    /**
+     * 查询
+     * @param deletedFlag
+     * @return
+     */
+    List<PositionVO> queryList(@Param("deletedFlag") Boolean deletedFlag);
+}

+ 61 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/entity/PositionEntity.java

@@ -0,0 +1,61 @@
+package com.cloud.sa.admin.module.system.position.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import java.time.LocalDateTime;
+
+import lombok.Data;
+
+/**
+ * 职务表 实体类
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ *
+ */
+
+@Data
+@TableName("mate_position")
+public class PositionEntity {
+
+    /**
+     * 职务ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long positionId;
+
+    /**
+     * 职务名称
+     */
+    private String positionName;
+
+    /**
+     * 职级
+     */
+    private String level;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    private Boolean deletedFlag;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+}

+ 34 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/form/PositionAddForm.java

@@ -0,0 +1,34 @@
+package com.cloud.sa.admin.module.system.position.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+import lombok.Data;
+
+/**
+ * 职务表 新建表单
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+
+@Data
+public class PositionAddForm {
+
+    @Schema(description = "职务名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "职务名称 不能为空")
+    private String positionName;
+
+    @Schema(description = "职级")
+    private String level;
+
+    @Schema(description = "排序")
+    @NotNull(message = "排序不能为空")
+    private Integer sort;
+
+    @Schema(description = "备注")
+    private String remark;
+
+}

+ 23 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/form/PositionQueryForm.java

@@ -0,0 +1,23 @@
+package com.cloud.sa.admin.module.system.position.domain.form;
+
+import com.cloud.sa.base.common.domain.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 职务表 分页查询表单
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+
+@Data
+public class PositionQueryForm extends PageParam{
+
+    @Schema(description = "关键字查询")
+    private String keywords;
+
+    @Schema(hidden = true)
+    private Boolean deletedFlag;
+}

+ 24 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/form/PositionUpdateForm.java

@@ -0,0 +1,24 @@
+package com.cloud.sa.admin.module.system.position.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.validation.constraints.NotNull;
+
+import lombok.Data;
+
+/**
+ * 职务表 更新表单
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+
+@Data
+public class PositionUpdateForm extends PositionAddForm {
+
+    @Schema(description = "职务ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "职务ID 不能为空")
+    private Long positionId;
+
+}

+ 40 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/domain/vo/PositionVO.java

@@ -0,0 +1,40 @@
+package com.cloud.sa.admin.module.system.position.domain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDateTime;
+import lombok.Data;
+
+/**
+ * 职务表 列表VO
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+
+@Data
+public class PositionVO {
+
+
+    @Schema(description = "职务ID")
+    private Long positionId;
+
+    @Schema(description = "职务名称")
+    private String positionName;
+
+    @Schema(description = "职级")
+    private String level;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+
+}

+ 20 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/manager/PositionManager.java

@@ -0,0 +1,20 @@
+package com.cloud.sa.admin.module.system.position.manager;
+
+import com.cloud.sa.admin.module.system.position.dao.PositionDao;
+import com.cloud.sa.admin.module.system.position.domain.entity.PositionEntity;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * 职务表  Manager
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+@Service
+public class PositionManager extends ServiceImpl<PositionDao, PositionEntity> {
+
+
+}

+ 105 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/position/service/PositionService.java

@@ -0,0 +1,105 @@
+package com.cloud.sa.admin.module.system.position.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.cloud.sa.admin.module.system.position.domain.form.PositionQueryForm;
+import com.cloud.sa.admin.module.system.position.domain.form.PositionUpdateForm;
+import com.cloud.sa.admin.module.system.position.domain.vo.PositionVO;
+import com.cloud.sa.admin.module.system.position.dao.PositionDao;
+import com.cloud.sa.admin.module.system.position.domain.entity.PositionEntity;
+import com.cloud.sa.admin.module.system.position.domain.form.PositionAddForm;
+import com.cloud.sa.base.common.domain.PageResult;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import com.cloud.sa.base.common.util.BlinkBeanUtil;
+import com.cloud.sa.base.common.util.BlinkPageUtil;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 职务表 Service
+ *
+ * @Author kaiyun
+ * @Date 2024-06-23 23:31:38
+ * 
+ */
+
+@Service
+public class PositionService {
+
+    @Resource
+    private PositionDao positionDao;
+
+    /**
+     * 分页查询
+     *
+     * @param queryForm
+     * @return
+     */
+    public PageResult<PositionVO> queryPage(PositionQueryForm queryForm) {
+        queryForm.setDeletedFlag(Boolean.FALSE);
+        Page<?> page = BlinkPageUtil.convert2PageQuery(queryForm);
+        List<PositionVO> list = positionDao.queryPage(page, queryForm);
+        PageResult<PositionVO> pageResult = BlinkPageUtil.convert2PageResult(page, list);
+        return pageResult;
+    }
+
+    /**
+     * 添加
+     */
+    public ResponseDTO<String> add(PositionAddForm addForm) {
+        PositionEntity positionEntity = BlinkBeanUtil.copy(addForm, PositionEntity.class);
+        positionDao.insert(positionEntity);
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 更新
+     *
+     * @param updateForm
+     * @return
+     */
+    public ResponseDTO<String> update(PositionUpdateForm updateForm) {
+        PositionEntity positionEntity = BlinkBeanUtil.copy(updateForm, PositionEntity.class);
+        positionDao.updateById(positionEntity);
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 批量删除
+     *
+     * @param idList
+     * @return
+     */
+    public ResponseDTO<String> batchDelete(List<Long> idList) {
+        if (CollectionUtils.isEmpty(idList)){
+            return ResponseDTO.ok();
+        }
+
+        positionDao.deleteBatchIds(idList);
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 单个删除
+     */
+    public ResponseDTO<String> delete(Long positionId) {
+        if (null == positionId){
+            return ResponseDTO.ok();
+        }
+
+        positionDao.deleteById(positionId);
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 分页查询
+     *
+     * @return
+     */
+    public List<PositionVO> queryList() {
+        List<PositionVO> list = positionDao.queryList(Boolean.FALSE);
+        return list;
+    }
+}

+ 64 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleController.java

@@ -0,0 +1,64 @@
+package com.cloud.sa.admin.module.system.role.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.module.system.role.domain.form.RoleAddForm;
+import com.cloud.sa.admin.module.system.role.domain.form.RoleUpdateForm;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleVO;
+import com.cloud.sa.admin.module.system.role.service.RoleService;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 角色
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-14 19:40:28
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE)
+public class RoleController {
+
+    @Resource
+    private RoleService roleService;
+
+    @Operation(summary = "添加角色 @author admin")
+    @PostMapping("/role/add")
+    @SaCheckPermission("system:role:add")
+    public ResponseDTO<String> addRole(@Valid @RequestBody RoleAddForm roleAddForm) {
+        return roleService.addRole(roleAddForm);
+    }
+
+    @Operation(summary = "删除角色 @author admin")
+    @GetMapping("/role/delete/{roleId}")
+    @SaCheckPermission("system:role:delete")
+    public ResponseDTO<String> deleteRole(@PathVariable Long roleId) {
+        return roleService.deleteRole(roleId);
+    }
+
+    @Operation(summary = "更新角色 @author admin")
+    @PostMapping("/role/update")
+    @SaCheckPermission("system:role:update")
+    public ResponseDTO<String> updateRole(@Valid @RequestBody RoleUpdateForm roleUpdateDTO) {
+        return roleService.updateRole(roleUpdateDTO);
+    }
+
+    @Operation(summary = "获取角色数据 @author admin")
+    @GetMapping("/role/get/{roleId}")
+    public ResponseDTO<RoleVO> getRole(@PathVariable("roleId") Long roleId) {
+        return roleService.getRoleById(roleId);
+    }
+
+    @Operation(summary = "获取所有角色 @author admin")
+    @GetMapping("/role/getAll")
+    public ResponseDTO<List<RoleVO>> getAllRole() {
+        return roleService.getAllRole();
+    }
+
+}

+ 44 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleDataScopeController.java

@@ -0,0 +1,44 @@
+package com.cloud.sa.admin.module.system.role.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.module.system.role.domain.form.RoleDataScopeUpdateForm;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleDataScopeVO;
+import com.cloud.sa.admin.module.system.role.service.RoleDataScopeService;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 角色的数据权限配置
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-02-26 22:09:59
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE_DATA_SCOPE)
+public class RoleDataScopeController {
+
+    @Resource
+    private RoleDataScopeService roleDataScopeService;
+
+    @Operation(summary = "获取某角色所设置的数据范围 @author admin")
+    @GetMapping("/role/dataScope/getRoleDataScopeList/{roleId}")
+    public ResponseDTO<List<RoleDataScopeVO>> dataScopeListByRole(@PathVariable Long roleId) {
+        return roleDataScopeService.getRoleDataScopeList(roleId);
+    }
+
+    @Operation(summary = "批量设置某角色数据范围 @author admin")
+    @PostMapping("/role/dataScope/updateRoleDataScopeList")
+    @SaCheckPermission("system:role:dataScope:update")
+    public ResponseDTO<String> updateRoleDataScopeList(@RequestBody @Valid RoleDataScopeUpdateForm roleDataScopeUpdateForm) {
+        return roleDataScopeService.updateRoleDataScopeList(roleDataScopeUpdateForm);
+    }
+
+
+}

+ 71 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleEmployeeController.java

@@ -0,0 +1,71 @@
+package com.cloud.sa.admin.module.system.role.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import com.cloud.sa.admin.module.system.employee.domain.vo.EmployeeVO;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.module.system.role.domain.form.RoleEmployeeQueryForm;
+import com.cloud.sa.admin.module.system.role.domain.form.RoleEmployeeUpdateForm;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleSelectedVO;
+import com.cloud.sa.admin.module.system.role.service.RoleEmployeeService;
+import com.cloud.sa.base.common.domain.PageResult;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 角色的员工
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-02-26 22:09:59
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE_EMPLOYEE)
+public class RoleEmployeeController {
+
+    @Resource
+    private RoleEmployeeService roleEmployeeService;
+
+    @Operation(summary = "查询某个角色下的员工列表  @author admin")
+    @PostMapping("/role/employee/queryEmployee")
+    public ResponseDTO<PageResult<EmployeeVO>> queryEmployee(@Valid @RequestBody RoleEmployeeQueryForm roleEmployeeQueryForm) {
+        return roleEmployeeService.queryEmployee(roleEmployeeQueryForm);
+    }
+
+    @Operation(summary = "获取某个角色下的所有员工列表(无分页)  @author admin")
+    @GetMapping("/role/employee/getAllEmployeeByRoleId/{roleId}")
+    public ResponseDTO<List<EmployeeVO>> listAllEmployeeRoleId(@PathVariable Long roleId) {
+        return ResponseDTO.ok(roleEmployeeService.getAllEmployeeByRoleId(roleId));
+    }
+
+    @Operation(summary = "从角色成员列表中移除员工 @author admin")
+    @GetMapping("/role/employee/removeEmployee")
+    @SaCheckPermission("system:role:employee:delete")
+    public ResponseDTO<String> removeEmployee(Long employeeId, Long roleId) {
+        return roleEmployeeService.removeRoleEmployee(employeeId, roleId);
+    }
+
+    @Operation(summary = "从角色成员列表中批量移除员工 @author admin")
+    @PostMapping("/role/employee/batchRemoveRoleEmployee")
+    @SaCheckPermission("system:role:employee:batch:delete")
+    public ResponseDTO<String> batchRemoveEmployee(@Valid @RequestBody RoleEmployeeUpdateForm updateForm) {
+        return roleEmployeeService.batchRemoveRoleEmployee(updateForm);
+    }
+
+    @Operation(summary = "角色成员列表中批量添加员工 @author admin")
+    @PostMapping("/role/employee/batchAddRoleEmployee")
+    @SaCheckPermission("system:role:employee:add")
+    public ResponseDTO<String> addEmployeeList(@Valid @RequestBody RoleEmployeeUpdateForm addForm) {
+        return roleEmployeeService.batchAddRoleEmployee(addForm);
+    }
+
+    @Operation(summary = "获取员工所有选中的角色和所有角色 @author admin")
+    @GetMapping("/role/employee/getRoles/{employeeId}")
+    public ResponseDTO<List<RoleSelectedVO>> getRoleByEmployeeId(@PathVariable Long employeeId) {
+        return ResponseDTO.ok(roleEmployeeService.getRoleInfoListByEmployeeId(employeeId));
+    }
+}

+ 41 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/controller/RoleMenuController.java

@@ -0,0 +1,41 @@
+package com.cloud.sa.admin.module.system.role.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cloud.sa.admin.constant.AdminSwaggerTagConst;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import com.cloud.sa.admin.module.system.role.domain.form.RoleMenuUpdateForm;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleMenuTreeVO;
+import com.cloud.sa.admin.module.system.role.service.RoleMenuService;
+import com.cloud.sa.base.common.domain.ResponseDTO;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+/**
+ * 角色的菜单
+ *
+ * @Author 畅联云: admin
+ * @Date 2022-02-26 21:34:01
+ */
+@RestController
+@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE_MENU)
+public class RoleMenuController {
+
+    @Resource
+    private RoleMenuService roleMenuService;
+
+    @Operation(summary = "更新角色权限 @author admin")
+    @PostMapping("/role/menu/updateRoleMenu")
+    @SaCheckPermission("system:role:menu:update")
+    public ResponseDTO<String> updateRoleMenu(@Valid @RequestBody RoleMenuUpdateForm updateDTO) {
+        return roleMenuService.updateRoleMenu(updateDTO);
+    }
+
+    @Operation(summary = "获取角色关联菜单权限 @author admin")
+    @GetMapping("/role/menu/getRoleSelectedMenu/{roleId}")
+    public ResponseDTO<RoleMenuTreeVO> getRoleSelectedMenu(@PathVariable Long roleId) {
+        return roleMenuService.getRoleSelectedMenu(roleId);
+    }
+}

+ 28 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleDao.java

@@ -0,0 +1,28 @@
+package com.cloud.sa.admin.module.system.role.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cloud.sa.admin.module.system.role.domain.entity.RoleEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+/**
+ * 角色 dao
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-02-26 21:34:01
+ */
+@Mapper
+@Component
+public interface RoleDao extends BaseMapper<RoleEntity> {
+
+    /**
+     * 根据角色名称查询
+     */
+    RoleEntity getByRoleName(@Param("roleName") String roleName);
+
+    /**
+     * 根据角色编码
+     */
+    RoleEntity getByRoleCode(@Param("roleCode") String roleCode);
+}

+ 37 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleDataScopeDao.java

@@ -0,0 +1,37 @@
+package com.cloud.sa.admin.module.system.role.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cloud.sa.admin.module.system.role.domain.entity.RoleDataScopeEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+
+/**
+ * 角色 数据权限 dao
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-02-26 21:34:01
+ */
+@Mapper
+@Component
+public interface RoleDataScopeDao extends BaseMapper<RoleDataScopeEntity> {
+
+    /**
+     * 获取某个角色的设置信息
+     */
+    List<RoleDataScopeEntity> listByRoleId(@Param("roleId") Long roleId);
+
+    /**
+     * 获取某批角色的所有数据范围配置信息
+     */
+    List<RoleDataScopeEntity> listByRoleIdList(@Param("roleIdList") List<Long> roleIdList);
+
+    /**
+     * 删除某个角色的设置信息
+     */
+    void deleteByRoleId(@Param("roleId") Long roleId);
+
+}

+ 85 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleEmployeeDao.java

@@ -0,0 +1,85 @@
+package com.cloud.sa.admin.module.system.role.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.cloud.sa.admin.module.system.employee.domain.vo.EmployeeVO;
+import com.cloud.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity;
+import com.cloud.sa.admin.module.system.role.domain.form.RoleEmployeeQueryForm;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleEmployeeVO;
+import com.cloud.sa.admin.module.system.role.domain.vo.RoleVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 角色 员工 dao
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-07 18:54:42
+ */
+@Mapper
+@Component
+public interface RoleEmployeeDao extends BaseMapper<RoleEmployeeEntity> {
+
+    /**
+     * 根据员工id 查询所有的角色
+     */
+    List<RoleVO> selectRoleByEmployeeId(@Param("employeeId") Long employeeId);
+
+    /**
+     * 根据员工id 查询所有的角色io集合
+     */
+    List<Long> selectRoleIdByEmployeeId(@Param("employeeId") Long employeeId);
+
+    /**
+     * 根据员工id 查询所有的角色
+     */
+    List<RoleEmployeeEntity> selectRoleIdByEmployeeIdList(@Param("employeeIdList") List<Long> employeeIdList);
+
+    /**
+     * 根据员工id 查询所有的角色
+     */
+    List<RoleEmployeeVO> selectRoleByEmployeeIdList(@Param("employeeIdList") List<Long> employeeIdList);
+
+    /**
+     * 查询角色下的人员id
+     */
+    Set<Long> selectEmployeeIdByRoleIdList(@Param("roleIdList") List<Long> roleIdList);
+
+    /**
+     *
+     */
+    List<EmployeeVO> selectRoleEmployeeByName(Page page, @Param("queryForm") RoleEmployeeQueryForm roleEmployeeQueryForm);
+
+    /**
+     *
+     */
+    List<EmployeeVO> selectEmployeeByRoleId(@Param("roleId") Long roleId);
+    /**
+     * 根据员工信息删除
+     */
+    void deleteByEmployeeId(@Param("employeeId") Long employeeId);
+
+    /**
+     * 删除某个角色的所有关系
+     */
+    void deleteByRoleId(@Param("roleId")Long roleId);
+
+    /**
+     * 根据员工和 角色删除关系
+     */
+    void deleteByEmployeeIdRoleId(@Param("employeeId") Long employeeId,@Param("roleId")Long roleId);
+
+    /**
+     * 批量删除某个角色下的某批用户的关联关系
+     */
+    void batchDeleteEmployeeRole(@Param("roleId") Long roleId, @Param("employeeIds") Set<Long> employeeIds);
+
+    /**
+     * 判断某个角色下是否存在用户
+     */
+    Integer existsByRoleId(@Param("roleId") Long roleId);
+}

+ 45 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/dao/RoleMenuDao.java

@@ -0,0 +1,45 @@
+package com.cloud.sa.admin.module.system.role.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cloud.sa.admin.module.system.role.domain.entity.RoleMenuEntity;
+import com.cloud.sa.admin.module.system.menu.domain.entity.MenuEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 角色 菜单 dao
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-07 18:54:42
+ */
+@Mapper
+@Component
+public interface RoleMenuDao extends BaseMapper<RoleMenuEntity> {
+
+    /**
+     * 根据角色ID删除菜单权限
+     *
+     */
+    void deleteByRoleId(@Param("roleId") Long roleId);
+
+    /**
+     * 根据角色ID查询选择的菜单权限
+     *
+     */
+    List<Long> queryMenuIdByRoleId(@Param("roleId") Long roleId);
+
+    /**
+     * 根据角色ID集合查询选择的菜单权限
+     *
+     */
+    List<MenuEntity> selectMenuListByRoleIdList(@Param("roleIdList") List<Long> roleIdList, @Param("deletedFlag") Boolean deletedFlag);
+
+    /**
+     * 查询所有的菜单角色
+     *
+     */
+    List<RoleMenuEntity> queryAllRoleMenu();
+}

+ 50 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleDataScopeEntity.java

@@ -0,0 +1,50 @@
+package com.cloud.sa.admin.module.system.role.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeTypeEnum;
+import com.cloud.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 数据范围与角色关系
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-07 18:54:42
+ */
+@Data
+@TableName("mate_role_data_scope")
+public class RoleDataScopeEntity {
+    /**
+     * 主键id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 数据范围id
+     * {@link DataScopeTypeEnum}
+     */
+    private Integer dataScopeType;
+    /**
+     * 数据范围类型
+     * {@link DataScopeViewTypeEnum}
+     */
+    private Integer viewType;
+    /**
+     * 角色id
+     */
+    private Long roleId;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 38 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleEmployeeEntity.java

@@ -0,0 +1,38 @@
+package com.cloud.sa.admin.module.system.role.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 角色 员工关系
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-07 18:54:42
+ */
+@Data
+@TableName("mate_role_employee")
+public class RoleEmployeeEntity {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long roleId;
+
+    private Long employeeId;
+
+    private LocalDateTime updateTime;
+
+    private LocalDateTime createTime;
+
+    public RoleEmployeeEntity() {
+    }
+
+    public RoleEmployeeEntity(Long roleId, Long employeeId) {
+        this.roleId = roleId;
+        this.employeeId = employeeId;
+    }
+}

+ 54 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleEntity.java

@@ -0,0 +1,54 @@
+package com.cloud.sa.admin.module.system.role.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 角色
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-07 18:54:42
+ */
+@Data
+@TableName("mate_role")
+public class RoleEntity {
+    /**
+     * 主键id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    private String roleName;
+
+    /**
+     * 角色编码
+     */
+    private String roleCode;
+
+    /**
+     * 角色备注
+     */
+    private String remark;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 所属部门
+     */
+    private long departmentId;
+}

+ 46 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/entity/RoleMenuEntity.java

@@ -0,0 +1,46 @@
+package com.cloud.sa.admin.module.system.role.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 角色 菜单
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-03-16 23:00:57
+ */
+@Data
+@TableName("mate_role_menu")
+public class RoleMenuEntity {
+
+    /**
+     * 主键id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long roleMenuId;
+
+    /**
+     * 角色 id
+     */
+    private Long roleId;
+
+    /**
+     * 菜单 id
+     */
+    private Long menuId;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+}

+ 41 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleAddForm.java

@@ -0,0 +1,41 @@
+package com.cloud.sa.admin.module.system.role.domain.form;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 角色 添加表单
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-02-26 19:09:42
+ */
+@Data
+public class RoleAddForm {
+
+    @Schema(description = "所属部门ID")
+    @NotNull(message = "所属部门不能为空")
+    private Long departmentId;
+    /**
+     * 角色名称
+     */
+    @Schema(description = "角色名称")
+    @NotNull(message = "角色名称不能为空")
+    @Length(min = 1, max = 20, message = "角色名称(1-20)个字符")
+    private String roleName;
+
+    @Schema(description = "角色编码")
+    @NotNull(message = "角色编码 不能为空")
+    @Length(min = 1, max = 20, message = "角色编码(1-20)个字符")
+    private String roleCode;
+
+    /**
+     * 角色描述
+     */
+    @Schema(description = "角色描述")
+    @Length(max = 255, message = "角色描述最多255个字符")
+    private String remark;
+
+
+}

+ 40 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleDataScopeUpdateForm.java

@@ -0,0 +1,40 @@
+package com.cloud.sa.admin.module.system.role.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 角色的数据范围更新
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-04-08 21:53:04
+ */
+@Data
+public class RoleDataScopeUpdateForm {
+
+    @Schema(description = "角色id")
+    @NotNull(message = "角色id不能为空")
+    private Long roleId;
+
+    @Schema(description = "设置信息")
+    @Valid
+    private List<RoleUpdateDataScopeListFormItem> dataScopeItemList;
+
+
+    @Data
+    public static class RoleUpdateDataScopeListFormItem {
+
+        @Schema(description = "数据范围类型")
+        @NotNull(message = "数据范围类型不能为空")
+        private Integer dataScopeType;
+
+        @Schema(description = "可见范围")
+        @NotNull(message = "可见范围不能为空")
+        private Integer viewType;
+    }
+
+}

+ 21 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleEmployeeQueryForm.java

@@ -0,0 +1,21 @@
+package com.cloud.sa.admin.module.system.role.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.cloud.sa.base.common.domain.PageParam;
+
+/**
+ * 角色的员工查询
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-04-08 21:53:04
+ */
+@Data
+public class RoleEmployeeQueryForm extends PageParam {
+
+    @Schema(description = "关键字")
+    private String keywords;
+
+    @Schema(description = "角色id")
+    private String roleId;
+}

+ 27 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleEmployeeUpdateForm.java

@@ -0,0 +1,27 @@
+package com.cloud.sa.admin.module.system.role.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Set;
+/**
+ * 角色的员工更新
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-04-08 21:53:04
+ */
+@Data
+public class RoleEmployeeUpdateForm {
+
+    @Schema(description = "角色id")
+    @NotNull(message = "角色id不能为空")
+    protected Long roleId;
+
+    @Schema(description = "员工id集合")
+    @NotEmpty(message = "员工id不能为空")
+    protected Set<Long> employeeIdList;
+
+}

+ 32 - 0
bound-link-api/blink-admin/src/main/java/com/cloud/sa/admin/module/system/role/domain/form/RoleMenuUpdateForm.java

@@ -0,0 +1,32 @@
+package com.cloud.sa.admin.module.system.role.domain.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 角色的菜单更新
+ *
+ * @Author 云畅联: admin
+ * @Date 2022-04-08 21:53:04
+ */
+@Data
+public class RoleMenuUpdateForm {
+
+    /**
+     * 角色id
+     */
+    @Schema(description = "角色id")
+    @NotNull(message = "角色id不能为空")
+    private Long roleId;
+
+    /**
+     * 菜单ID 集合
+     */
+    @Schema(description = "菜单ID集合")
+    @NotNull(message = "菜单ID不能为空")
+    private List<Long> menuIdList;
+
+}

部分文件因文件數量過多而無法顯示