getPermissionList(Object loginId) {
+ return stpLogic.getPermissionList(loginId);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限, 返回true或false
+ * @param permission 权限码
+ * @return 是否含有指定权限
+ */
+ public static boolean hasPermission(String permission) {
+ return stpLogic.hasPermission(permission);
+ }
+
+ /**
+ * 判断:指定账号id是否含有指定权限, 返回true或false
+ * @param loginId 账号id
+ * @param permission 权限码
+ * @return 是否含有指定权限
+ */
+ public static boolean hasPermission(Object loginId, String permission) {
+ return stpLogic.hasPermission(loginId, permission);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限, [指定多个,必须全部具有]
+ * @param permissionArray 权限码数组
+ * @return true 或 false
+ */
+ public static boolean hasPermissionAnd(String... permissionArray) {
+ return stpLogic.hasPermissionAnd(permissionArray);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
+ * @param permissionArray 权限码数组
+ * @return true 或 false
+ */
+ public static boolean hasPermissionOr(String... permissionArray) {
+ return stpLogic.hasPermissionOr(permissionArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
+ * @param permission 权限码
+ */
+ public static void checkPermission(String permission) {
+ stpLogic.checkPermission(permission);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
+ * @param permissionArray 权限码数组
+ */
+ public static void checkPermissionAnd(String... permissionArray) {
+ stpLogic.checkPermissionAnd(permissionArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
+ * @param permissionArray 权限码数组
+ */
+ public static void checkPermissionOr(String... permissionArray) {
+ stpLogic.checkPermissionOr(permissionArray);
+ }
+
+
+ // =================== id 反查token 相关操作 ===================
+
+ /**
+ * 获取指定账号id的tokenValue
+ * 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
+ * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
+ * @param loginId 账号id
+ * @return token值
+ */
+ public static String getTokenValueByLoginId(Object loginId) {
+ return stpLogic.getTokenValueByLoginId(loginId);
+ }
+
+ /**
+ * 获取指定账号id指定设备端的tokenValue
+ *
在配置为允许并发登录时,此方法只会返回队列的最后一个token,
+ * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
+ * @param loginId 账号id
+ * @param device 设备标识
+ * @return token值
+ */
+ public static String getTokenValueByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenValueByLoginId(loginId, device);
+ }
+
+ /**
+ * 获取指定账号id的tokenValue集合
+ * @param loginId 账号id
+ * @return 此loginId的所有相关token
+ */
+ public static List getTokenValueListByLoginId(Object loginId) {
+ return stpLogic.getTokenValueListByLoginId(loginId);
+ }
+
+ /**
+ * 获取指定账号id指定设备端的tokenValue 集合
+ * @param loginId 账号id
+ * @param device 设备标识
+ * @return 此loginId的所有相关token
+ */
+ public static List getTokenValueListByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenValueListByLoginId(loginId, device);
+ }
+
+ /**
+ * 返回当前会话的登录设备
+ * @return 当前令牌的登录设备
+ */
+ public static String getLoginDevice() {
+ return stpLogic.getLoginDevice();
+ }
+
+
+ // =================== 会话管理 ===================
+
+ /**
+ * 根据条件查询Token
+ * @param keyword 关键字
+ * @param start 开始处索引 (-1代表查询所有)
+ * @param size 获取数量
+ * @return token集合
+ */
+ public static List searchTokenValue(String keyword, int start, int size) {
+ return stpLogic.searchTokenValue(keyword, start, size);
+ }
+
+ /**
+ * 根据条件查询SessionId
+ * @param keyword 关键字
+ * @param start 开始处索引 (-1代表查询所有)
+ * @param size 获取数量
+ * @return sessionId集合
+ */
+ public static List searchSessionId(String keyword, int start, int size) {
+ return stpLogic.searchSessionId(keyword, start, size);
+ }
+
+ /**
+ * 根据条件查询Token专属Session的Id
+ * @param keyword 关键字
+ * @param start 开始处索引 (-1代表查询所有)
+ * @param size 获取数量
+ * @return sessionId集合
+ */
+ public static List searchTokenSessionId(String keyword, int start, int size) {
+ return stpLogic.searchTokenSessionId(keyword, start, size);
+ }
+
+
+ // ------------------- 账号封禁 -------------------
+
+ /**
+ * 封禁指定账号
+ * 此方法不会直接将此账号id踢下线,而是在对方再次登录时抛出`DisableLoginException`异常
+ * @param loginId 指定账号id
+ * @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁)
+ */
+ public static void disable(Object loginId, long disableTime) {
+ stpLogic.disable(loginId, disableTime);
+ }
+
+ /**
+ * 指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
+ * @param loginId 账号id
+ * @return see note
+ */
+ public static boolean isDisable(Object loginId) {
+ return stpLogic.isDisable(loginId);
+ }
+
+ /**
+ * 获取指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
+ * @param loginId 账号id
+ * @return see note
+ */
+ public static long getDisableTime(Object loginId) {
+ return stpLogic.getDisableTime(loginId);
+ }
+
+ /**
+ * 解封指定账号
+ * @param loginId 账号id
+ */
+ public static void untieDisable(Object loginId) {
+ stpLogic.untieDisable(loginId);
+ }
+
+
+ // =================== 身份切换 ===================
+
+ /**
+ * 临时切换身份为指定账号id
+ * @param loginId 指定loginId
+ */
+ public static void switchTo(Object loginId) {
+ stpLogic.switchTo(loginId);
+ }
+
+ /**
+ * 结束临时切换身份
+ */
+ public static void endSwitch() {
+ stpLogic.endSwitch();
+ }
+
+ /**
+ * 当前是否正处于[身份临时切换]中
+ * @return 是否正处于[身份临时切换]中
+ */
+ public static boolean isSwitch() {
+ return stpLogic.isSwitch();
+ }
+
+ /**
+ * 在一个代码段里方法内,临时切换身份为指定账号id
+ * @param loginId 指定账号id
+ * @param function 要执行的方法
+ */
+ public static void switchTo(Object loginId, SaFunction function) {
+ stpLogic.switchTo(loginId, function);
+ }
+
+
+ // ------------------- 二级认证 -------------------
+
+ /**
+ * 在当前会话 开启二级认证
+ * @param safeTime 维持时间 (单位: 秒)
+ */
+ public static void openSafe(long safeTime) {
+ stpLogic.openSafe(safeTime);
+ }
+
+ /**
+ * 当前会话 是否处于二级认证时间内
+ * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
+ */
+ public static boolean isSafe() {
+ return stpLogic.isSafe();
+ }
+
+ /**
+ * 检查当前会话是否已通过二级认证,如未通过则抛出异常
+ */
+ public static void checkSafe() {
+ stpLogic.checkSafe();
+ }
+
+ /**
+ * 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
+ * @return 剩余有效时间
+ */
+ public static long getSafeTime() {
+ return stpLogic.getSafeTime();
+ }
+
+ /**
+ * 在当前会话 结束二级认证
+ */
+ public static void closeSafe() {
+ stpLogic.closeSafe();
+ }
+
+
+ // =================== 历史API,兼容旧版本 ===================
+
+ /**
+ *
本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变
+ *
+ * 获取当前StpLogin的loginKey
+ * @return 当前StpLogin的loginKey
+ */
+ @Deprecated
+ public static String getLoginKey() {
+ return stpLogic.getLoginType();
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId) {
+ stpLogic.login(loginId);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id, 并指定登录设备
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ * @param device 设备标识
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId, String device) {
+ stpLogic.login(loginId, device);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id, 并指定登录设备
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ * @param isLastingCookie 是否为持久Cookie
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId, boolean isLastingCookie) {
+ stpLogic.login(loginId, isLastingCookie);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id, 并指定所有登录参数Model
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ * @param loginModel 此次登录的参数Model
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId, SaLoginModel loginModel) {
+ stpLogic.login(loginId, loginModel);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变
+ *
+ * 会话注销,根据账号id (踢人下线)
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
+ * @param loginId 账号id
+ */
+ @Deprecated
+ public static void logoutByLoginId(Object loginId) {
+ stpLogic.kickout(loginId);
+ }
+
+ /**
+ *
本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变
+ *
+ * 会话注销,根据账号id and 设备标识 (踢人下线)
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
+ * @param loginId 账号id
+ * @param device 设备标识 (填null代表所有注销设备)
+ */
+ @Deprecated
+ public static void logoutByLoginId(Object loginId, String device) {
+ stpLogic.kickout(loginId, device);
+ }
+}
diff --git a/snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpLoginUserUtil.java b/snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpLoginUserUtil.java
new file mode 100644
index 00000000..dcdb01dd
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpLoginUserUtil.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.core.util;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
+import vip.xiaonuo.common.util.CommonServletUtil;
+
+import java.util.List;
+
+/**
+ * B端登录用户工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 10:40
+ **/
+public class StpLoginUserUtil {
+
+ /**
+ * 获取当前B端登录用户
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 10:41
+ **/
+ public static SaBaseLoginUser getLoginUser() {
+ return (SaBaseLoginUser) StpUtil.getTokenSession().get("loginUser");
+ }
+
+ /**
+ * 获取当前B端登录用户的当前请求接口的数据范围
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 10:41
+ **/
+ public static List getLoginUserDataScope() {
+ List resultList = CollectionUtil.newArrayList();
+ getLoginUser().getDataScopeList().forEach(dataScope -> {
+ if(dataScope.getApiUrl().equals(CommonServletUtil.getRequest().getServletPath())) {
+ resultList.addAll(dataScope.getDataScope());
+ }
+ });
+ return resultList;
+ }
+}
diff --git a/snowy-plugin-api/snowy-plugin-biz-api/README.md b/snowy-plugin-api/snowy-plugin-biz-api/README.md
new file mode 100644
index 00000000..480d29de
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-biz-api/README.md
@@ -0,0 +1 @@
+# 业务功能插件api接口
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-biz-api/pom.xml b/snowy-plugin-api/snowy-plugin-biz-api/pom.xml
new file mode 100644
index 00000000..77f13209
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-biz-api/pom.xml
@@ -0,0 +1,25 @@
+
+
+ 4.0.0
+
+
+ vip.xiaonuo
+ snowy-plugin-api
+ 2.0.0
+
+
+ snowy-plugin-biz-api
+ jar
+ 业务功能插件api接口
+
+
+
+
+ vip.xiaonuo
+ snowy-common
+ ${project.parent.version}
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-biz-api/src/main/java/vip/xiaonuo/biz/package-info.java b/snowy-plugin-api/snowy-plugin-biz-api/src/main/java/vip/xiaonuo/biz/package-info.java
new file mode 100644
index 00000000..ba79db40
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-biz-api/src/main/java/vip/xiaonuo/biz/package-info.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz;
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-client-api/README.md b/snowy-plugin-api/snowy-plugin-client-api/README.md
new file mode 100644
index 00000000..7353fbe3
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-client-api/README.md
@@ -0,0 +1 @@
+# C端功能插件api接口
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-client-api/pom.xml b/snowy-plugin-api/snowy-plugin-client-api/pom.xml
new file mode 100644
index 00000000..c1a014c1
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-client-api/pom.xml
@@ -0,0 +1,25 @@
+
+
+ 4.0.0
+
+
+ vip.xiaonuo
+ snowy-plugin-api
+ 2.0.0
+
+
+ snowy-plugin-client-api
+ jar
+ C端功能插件api接口
+
+
+
+
+ vip.xiaonuo
+ snowy-common
+ ${project.parent.version}
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-client-api/src/main/java/vip/xiaonuo/client/package-info.java b/snowy-plugin-api/snowy-plugin-client-api/src/main/java/vip/xiaonuo/client/package-info.java
new file mode 100644
index 00000000..423f9e67
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-client-api/src/main/java/vip/xiaonuo/client/package-info.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.client;
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/README.md b/snowy-plugin-api/snowy-plugin-dev-api/README.md
new file mode 100644
index 00000000..2d3e03e9
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/README.md
@@ -0,0 +1 @@
+# 开发工具插件api接口
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/pom.xml b/snowy-plugin-api/snowy-plugin-dev-api/pom.xml
new file mode 100644
index 00000000..e9036cbf
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/pom.xml
@@ -0,0 +1,101 @@
+
+
+ 4.0.0
+
+
+ vip.xiaonuo
+ snowy-plugin-api
+ 2.0.0
+
+
+ snowy-plugin-dev-api
+ jar
+ 开发工具插件api接口
+
+
+ 5.6.68
+ 3.1.455
+ 3.1.455
+ 3.14.0
+ 3.0.12
+ 1.6.2
+ 3.3.1
+ 3.1.0
+ 2.0.9
+ 6.2.2
+
+
+
+
+
+ vip.xiaonuo
+ snowy-common
+ ${project.parent.version}
+
+
+
+
+ com.qcloud
+ cos_api
+ ${ten.cos.version}
+
+
+
+
+ com.aliyun.oss
+ aliyun-sdk-oss
+ ${ali.oss.version}
+
+
+
+
+ io.minio
+ minio
+ ${minio.version}
+
+
+
+
+ com.sun.mail
+ javax.mail
+ ${javax.mail.version}
+
+
+
+
+ com.aliyun
+ aliyun-java-sdk-dm
+ ${aliyun.sdk.dm.version}
+
+
+
+
+ com.tencentcloudapi
+ tencentcloud-sdk-java-ses
+ ${ten.sdk.ses.version}
+
+
+
+
+ com.aliyun
+ dysmsapi20170525
+ ${aliyun.sdk.dysmsapi.version}
+
+
+
+
+ com.tencentcloudapi
+ tencentcloud-sdk-java-sms
+ ${ten.sdk.sms.version}
+
+
+
+
+ com.github.oshi
+ oshi-core
+ ${oshi.core.version}
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevConfigApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevConfigApi.java
new file mode 100644
index 00000000..5ffc5a2a
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevConfigApi.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+/**
+ * 配置APi接口
+ *
+ * @author xuyuxiang
+ * @date 2022/6/17 10:37
+ **/
+public interface DevConfigApi {
+
+ /**
+ * 根据键获取值
+ *
+ * @author xuyuxiang
+ * @date 2022/6/17 11:11
+ **/
+ String getValueByKey(String key);
+}
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevDictApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevDictApi.java
new file mode 100644
index 00000000..36a9d904
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevDictApi.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+/**
+ * 字典API
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 15:58
+ */
+public interface DevDictApi {
+}
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevEmailApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevEmailApi.java
new file mode 100644
index 00000000..0704bb7d
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevEmailApi.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 邮件API接口
+ *
+ * @author xuyuxiang
+ * @date 2022/6/22 15:21
+ **/
+public interface DevEmailApi {
+
+ /* =========本地邮件========= */
+
+ /**
+ * 发送纯文本邮件
+ *
+ * @param tos 收件人邮箱,逗号拼接
+ * @param subject 邮件主题
+ * @param content 邮件内容
+ * @param files 附件列表
+ * @author xuyuxiang
+ * @date 2022/2/7 22:29
+ */
+ void sendTextEmailLocal(String tos, String subject, String content, List files);
+
+ /**
+ * 发送HTML邮件
+ *
+ * @param tos 收件人邮箱,逗号拼接
+ * @param subject 邮件主题
+ * @param content 邮件内容
+ * @param imageMap – 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+ * @param files 附件列表
+ * @author xuyuxiang
+ * @date 2022/2/7 22:29
+ */
+ void sendHtmlEmailLocal(String tos, String subject, String content, Map imageMap, List files);
+
+ /* =========阿里云邮件========= */
+
+ /**
+ * 发送纯文本邮件(不使用模板,频率限制100 QPS)
+ *
+ * @param from 管理控制台中配置的发信地址,必传且必须正确
+ * @param user 发信人昵称,长度小于15个字符,可不传
+ * @param tos 目标地址,多个 email 地址可以用逗号分隔,最多100个地址,必传且必须正确
+ * @param subject 邮件主题,必传
+ * @param content 邮件 txt 正文,限制28K,必传
+ * @author xuyuxiang
+ * @date 2022/2/23 14:24
+ **/
+ void sendTextEmailAliyun(String from, String user, String tos, String subject, String content);
+
+ /**
+ * 发送HTML邮件(不使用模板,频率限制100 QPS)
+ *
+ * @param from 管理控制台中配置的发信地址,必传且必须正确
+ * @param user 发信人昵称,长度小于15个字符,可不传
+ * @param tos 目标地址,多个 email 地址可以用逗号分隔,最多100个地址,必传且必须正确
+ * @param subject 邮件主题,必传
+ * @param content 邮件 html 正文,限制28K,必传
+ * @author xuyuxiang
+ * @date 2022/2/23 14:24
+ **/
+ void sendHtmlEmailAliyun(String from, String user, String tos, String subject, String content);
+
+ /**
+ * 使用模板发送邮件,国内频率限制是20/min;海外频率限制是10/min。
+ *
+ * @param from 管理控制台中配置的发信地址,必传且必须正确
+ * @param tagName 控制台创建的邮件标签,可不传
+ * @param toName 预先创建且上传了收件人的收件人列表名称,必传且必须正确
+ * @param templateName 预先创建且通过审核的模板名称,必传且必须正确
+ * @author xuyuxiang
+ * @date 2022/2/23 14:24
+ **/
+ void sendEmailWithTemplateAliyun(String from, String tagName, String toName, String templateName);
+
+ /* =========腾讯云邮件========= */
+
+ /**
+ * 发送纯文本邮件(不使用模板,默认接口请求频率限制:20次/秒。)
+ *
+ * @param from 管理控制台中配置的发信地址,必传且必须正确
+ * @param user 发信人昵称,可不传
+ * @param tos 目标地址,多个 email 地址可以用逗号分隔,最多50个地址,必传且必须正确,非群发邮件请多次调用API发送
+ * @param subject 邮件主题,必传
+ * @param content 邮件 txt 正文,必传,注意:腾讯云api目前要求请求包大小不得超过8 MB。
+ * @param attachmentList 需要发送附件时,填写附件相关参数,格式:[{"FileName": "xxxx", "Content": "xxx"}]
+ * 支持的格式与说明见:https://cloud.tencent.com/document/api/1288/51053#Attachment
+ * @author xuyuxiang
+ * @date 2022/2/23 14:24
+ **/
+ void sendTextEmailTencent(String from, String user, String tos, String subject, String content, List attachmentList);
+
+ /**
+ * 发送HTML邮件(不使用模板,默认接口请求频率限制:20次/秒。)
+ *
+ * @param from 管理控制台中配置的发信地址,必传且必须正确
+ * @param user 发信人昵称,可不传
+ * @param tos 目标地址,多个 email 地址可以用逗号分隔,最多50个地址,必传且必须正确,非群发邮件请多次调用API发送
+ * @param subject 邮件主题,必传
+ * @param content 邮件 txt 正文,必传,注意:腾讯云api目前要求请求包大小不得超过8 MB。
+ * @param attachmentList 需要发送附件时,填写附件相关参数,格式:[{"FileName": "xxxx", "Content": "xxx"}]
+ * 支持的格式与说明见:https://cloud.tencent.com/document/api/1288/51053#Attachment
+ * @author xuyuxiang
+ * @date 2022/2/23 14:24
+ **/
+ void sendHtmlEmailTencent(String from, String user, String tos, String subject, String content, List attachmentList);
+
+ /**
+ * 使用模板发送邮件,默认接口请求频率限制:20次/秒。
+ *
+ * @param from 管理控制台中配置的发信地址,必传且必须正确
+ * @param user 发信人昵称,可不传
+ * @param toId 预先创建且上传了收件人的收件人列表id,必传且必须正确
+ * @param templateId 预先创建且通过审核的模板Id,必传且必须正确
+ * @param templateParam 预先创建且通过审核的模板的参数json,格式{"name":"张三"},可不传
+ * @param subject 邮件主题,必传
+ * @author xuyuxiang
+ * @date 2022/2/23 14:24
+ **/
+ void sendEmailWithTemplateTencent(String from, String user, String toId, String templateId, String templateParam, String subject, List attachmentList);
+}
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevFileApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevFileApi.java
new file mode 100644
index 00000000..06a2b6c9
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevFileApi.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 文件API接口,可参考vip.xiaonuo.dev.core.util.file包下的工具类扩展更多需要的方法
+ *
+ * @author xuyuxiang
+ * @date 2022/6/22 15:21
+ **/
+public interface DevFileApi {
+
+ /* =========本地文件========= */
+
+ /**
+ * 上传文件返回Url
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnUrlLocal(MultipartFile file);
+
+ /**
+ * 上传文件返回Id
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnIdLocal(MultipartFile file);
+
+ /* =========阿里云文件========= */
+
+ /**
+ * 上传文件返回Url
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnUrlAliyun(MultipartFile file);
+
+ /**
+ * 上传文件返回Id
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnIdAliyun(MultipartFile file);
+
+ /* =========腾讯云件========= */
+
+ /**
+ * 上传文件返回Url
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnUrlTencent(MultipartFile file);
+
+ /**
+ * 上传文件返回Id
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnIdTencent(MultipartFile file);
+
+ /* =========MINIO件========= */
+
+ /**
+ * 上传文件返回Url
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnUrlMinio(MultipartFile file);
+
+ /**
+ * 上传文件返回Id
+ *
+ * @param file 文件
+ * @author xuyuxiang
+ * @date 2022/6/22 17:44
+ **/
+ String storageFileWithReturnIdMinio(MultipartFile file);
+}
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevJobApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevJobApi.java
new file mode 100644
index 00000000..b1f85818
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevJobApi.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+/**
+ * 定时任务API
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 15:59
+ */
+public interface DevJobApi {
+}
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevLogApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevLogApi.java
new file mode 100644
index 00000000..2dc7e450
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevLogApi.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 日志API
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 15:59
+ */
+public interface DevLogApi {
+
+ /**
+ * 记录登录日志
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 16:03
+ */
+ void executeLoginLog(String userName);
+
+ /**
+ * 记录登出日志
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 16:03
+ */
+ void executeLogoutLog(String userName);
+
+ /**
+ * 获取当前用户的访问日志列表
+ *
+ * @author xuyuxiang
+ * @date 2022/9/4 15:12
+ */
+ List currentUserVisLogList();
+
+ /**
+ * 获取当前用户的操作日志列表
+ *
+ * @author xuyuxiang
+ * @date 2022/9/4 15:12
+ */
+ List currentUserOpLogList();
+}
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevMessageApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevMessageApi.java
new file mode 100644
index 00000000..2ff5b6de
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevMessageApi.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.List;
+
+/**
+ * 站内信API接口
+ *
+ * @author xuyuxiang
+ * @date 2022/6/22 17:33
+ **/
+public interface DevMessageApi {
+
+ /**
+ * 发送站内信,默认:分类系统通知
+ *
+ * @param receiverIdList 接收的用户id集合
+ * @param subject 主题
+ * @author xuyuxiang
+ * @date 2022/6/22 17:35
+ **/
+ void sendMessage(List receiverIdList, String subject);
+
+ /**
+ * 发送站内信指定分类
+ *
+ * @param receiverIdList 接收的用户id集合
+ * @param subject 主题
+ * @author xuyuxiang
+ * @date 2022/6/22 17:35
+ **/
+ void sendMessage(List receiverIdList, String category, String subject);
+
+ /**
+ * 发送站内信带内容,默认:分类系统通知
+ *
+ * @param receiverIdList 接收的用户id集合
+ * @param subject 主题
+ * @param content 站内信内容
+ * @author xuyuxiang
+ * @date 2022/6/22 17:35
+ **/
+ void sendMessageWithContent(List receiverIdList, String subject, String content);
+
+ /**
+ * 发送站内信带内容,指定分类
+ *
+ * @param receiverIdList 接收的用户id集合
+ * @param subject 主题
+ * @param content 站内信内容
+ * @author xuyuxiang
+ * @date 2022/6/22 17:35
+ **/
+ void sendMessageWithContent(List receiverIdList, String category, String subject, String content);
+
+ /**
+ * 获取未读站内信列表
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 11:48
+ */
+ List list(List receiverIdList, Integer limit);
+
+ /**
+ * 获取站内信分页
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 11:48
+ */
+ Page page(List receiverIdList, String category);
+
+ /**
+ * 获取站内信详情
+ *
+ * @param id 站内信id
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ JSONObject detail(String id);
+
+}
diff --git a/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevSmsApi.java b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevSmsApi.java
new file mode 100644
index 00000000..29c22a0d
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevSmsApi.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+/**
+ * 短信API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/22 15:22
+ **/
+public interface DevSmsApi {
+
+ /* =========阿里云邮件========= */
+
+ /**
+ * 发送短信
+ *
+ * @param phoneNumbers 手机号码,支持对多个手机号码发送短信,手机号码之间以半角逗号(,)分隔。
+ * 上限为1000个手机号码。批量调用相对于单条调用及时性稍有延迟。
+ * @param signName 短信服务控制台配置且审核通过的短信签名
+ * @param templateCode 短信服务控制台配置且审核通过的模板编码
+ * @param templateParam 短信模板变量对应的实际值,JSON格式。支持传入多个参数,示例:{"name":"张三","number":"15038****76"}
+ * @author xuyuxiang
+ * @date 2022/2/24 13:42
+ **/
+ void sendSmsAliyun(String phoneNumbers, String signName, String templateCode, String templateParam);
+
+ /* =========腾讯云邮件========= */
+
+ /**
+ * 发送短信
+ *
+ * @param sdkAppId 短信 SdkAppId,在 短信控制台 添加应用后生成的实际 SdkAppId,示例如1400006666。
+ * 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
+ * @param phoneNumbers 手机号码,支持对多个手机号码发送短信,手机号码之间以半角逗号(,)分隔。
+ * 上限为1000个手机号码。批量调用相对于单条调用及时性稍有延迟。
+ * @param signName 短信服务控制台配置且审核通过的短信签名
+ * @param templateCode 短信服务控制台配置且审核通过的模板编码
+ * @param templateParam 短信模板变量对应的顺序。支持传入多个参数,逗号拼接,示例:"张三,15038****76,进行中"}
+ * @author xuyuxiang
+ * @date 2022/2/24 13:42
+ **/
+ void sendSmsTencent(String sdkAppId, String phoneNumbers, String signName, String templateCode, String templateParam);
+}
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/README.md b/snowy-plugin-api/snowy-plugin-sys-api/README.md
new file mode 100644
index 00000000..f8a5b6e2
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/README.md
@@ -0,0 +1 @@
+# 系统功能插件api接口
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/pom.xml b/snowy-plugin-api/snowy-plugin-sys-api/pom.xml
new file mode 100644
index 00000000..d0a3801a
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/pom.xml
@@ -0,0 +1,25 @@
+
+
+ 4.0.0
+
+
+ vip.xiaonuo
+ snowy-plugin-api
+ 2.0.0
+
+
+ snowy-plugin-sys-api
+ jar
+ 系统功能插件api接口
+
+
+
+
+ vip.xiaonuo
+ snowy-common
+ ${project.parent.version}
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/README.md b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/README.md
new file mode 100644
index 00000000..2f4270be
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/README.md
@@ -0,0 +1 @@
+#系统API接口
\ No newline at end of file
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysOrgApi.java b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysOrgApi.java
new file mode 100644
index 00000000..f745427d
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysOrgApi.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 组织API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:35
+ **/
+public interface SysOrgApi {
+
+ /**
+ * 根据id获取名称
+ *
+ * @author xuyuxiang
+ * @date 2022/8/4 10:12
+ **/
+ String getNameById(String orgId);
+
+ /**
+ * 根据组织id获取部门主管id
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 14:50
+ **/
+ String getSupervisorIdByOrgId(String orgId);
+
+ /**
+ * 获取组织树选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/7/22 14:46
+ **/
+ List> orgTreeSelector();
+
+ /**
+ * 获取组织列表选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/7/22 14:45
+ **/
+ List orgListSelector(String parentId);
+}
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysPositionApi.java b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysPositionApi.java
new file mode 100644
index 00000000..ecd5e7d8
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysPositionApi.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 职位API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:35
+ **/
+public interface SysPositionApi {
+
+ /**
+ * 根据id获取名称
+ *
+ * @author xuyuxiang
+ * @date 2022/8/4 10:13
+ **/
+ String getNameById(String positionId);
+
+ /**
+ * 获取职位选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/7/22 14:47
+ **/
+ List positionSelector(String orgId, String searchKey);
+}
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRelationApi.java b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRelationApi.java
new file mode 100644
index 00000000..1df91d96
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRelationApi.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import java.util.List;
+
+/**
+ * 关系API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:41
+ **/
+public interface SysRelationApi {
+
+ /**
+ * 根据角色id集合获取角色下用户id集合
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:43
+ **/
+ List getUserIdListByRoleIdList(List roleIdList);
+}
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRoleApi.java b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRoleApi.java
new file mode 100644
index 00000000..5e0e4ed4
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRoleApi.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 角色API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:36
+ **/
+public interface SysRoleApi {
+
+ /**
+ * 判断组织下是否存在角色
+ *
+ * @author xuyuxiang
+ * @date 2022/8/2 11:16
+ */
+ boolean orgHasRole(List orgIdList);
+
+ /**
+ * 获取角色选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/7/22 14:49
+ **/
+ List roleSelector(String orgId, String category, String searchKey);
+}
diff --git a/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysUserApi.java b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysUserApi.java
new file mode 100644
index 00000000..85d60196
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysUserApi.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 用户Api
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:33
+ **/
+public interface SysUserApi {
+
+ /**
+ * 根据用户id获取用户对象,没有则返回null
+ *
+ * @author xuyuxiang
+ * @date 2022/6/20 18:19
+ **/
+ JSONObject getUserByIdWithoutException(String userId);
+
+ /**
+ * 根据用户id获取用户对象列表,没有的则为空,都没有则返回空集合
+ *
+ * @author xuyuxiang
+ * @date 2022/6/20 18:19
+ **/
+ List getUserListByIdListWithoutException(List userIdList);
+
+ /**
+ * 根据用户id获取用户对象,没有则抛出异常
+ *
+ * @author xuyuxiang
+ * @date 2022/6/20 18:19
+ **/
+ JSONObject getUserByIdWithException(String userId);
+
+ /**
+ * 根据用户id获取用户对象列表,只要有一个没有则抛出异常
+ *
+ * @author xuyuxiang
+ * @date 2022/6/20 18:19
+ **/
+ List getUserListByIdWithException(List userIdList);
+
+ /**
+ * 获取用户拥有角色
+ *
+ * @author xuyuxiang
+ * @date 2022/5/13 21:00
+ */
+ List ownRole(String userId);
+
+ /**
+ * 给用户授权角色
+ *
+ * @author xuyuxiang
+ * @date 2022/8/1 18:28
+ */
+ void grantRole(String userId, List roleIdList);
+
+ /**
+ * 根据组织id集合获取组织下用户id集合
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:40
+ **/
+ List getUserIdListByOrgIdList(List orgIdList);
+
+ /**
+ * 根据职位id集合获取职位下用户id集合
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:44
+ **/
+ List getUserIdListByPositionIdList(List positionIdList);
+
+ /**
+ * 根据用户id和组织id和职位id获取上级主管id
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 14:50
+ **/
+ String getSupervisorIdByUserIdAndOrgIdAndPositionId(String userId, String orgId, String positionId);
+
+ /**
+ * 获取用户选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ List userSelector(String orgId, String searchKey);
+}
diff --git a/snowy-plugin/README.md b/snowy-plugin/README.md
new file mode 100644
index 00000000..1d9cb4bd
--- /dev/null
+++ b/snowy-plugin/README.md
@@ -0,0 +1,11 @@
+# 插件模块
+
+####登录鉴权插件: snowy-plugin-auth
+
+####业务功能插件: snowy-plugin-biz
+
+####C端功能插件: snowy-plugin-client
+
+####开发工具插件: snowy-plugin-dev
+
+####系统功能插件: snowy-plugin-sys
\ No newline at end of file
diff --git a/snowy-plugin/pom.xml b/snowy-plugin/pom.xml
new file mode 100644
index 00000000..92d9db4a
--- /dev/null
+++ b/snowy-plugin/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+
+ vip.xiaonuo
+ snowy
+ 2.0.0
+
+
+ snowy-plugin
+ pom
+ 插件模块
+
+
+
+ snowy-plugin-auth
+
+
+ snowy-plugin-biz
+
+
+ snowy-plugin-client
+
+
+ snowy-plugin-dev
+
+
+ snowy-plugin-sys
+
+
diff --git a/snowy-plugin/snowy-plugin-auth/README.md b/snowy-plugin/snowy-plugin-auth/README.md
new file mode 100644
index 00000000..676168d8
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/README.md
@@ -0,0 +1 @@
+# 登录鉴权插件
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-auth/pom.xml b/snowy-plugin/snowy-plugin-auth/pom.xml
new file mode 100644
index 00000000..f47fe9db
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+
+ vip.xiaonuo
+ snowy-plugin
+ 2.0.0
+
+
+ snowy-plugin-auth
+ jar
+ 登录鉴权插件
+
+
+ 1.30.0
+ 1.16.5
+
+
+
+
+
+
+ vip.xiaonuo
+ snowy-plugin-auth-api
+ ${project.parent.version}
+
+
+
+
+ vip.xiaonuo
+ snowy-plugin-dev-api
+ ${project.parent.version}
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot-starter
+ ${sa.token.version}
+
+
+
+
+ cn.dev33
+ sa-token-dao-redis-jackson
+ ${sa.token.version}
+
+
+
+
+ cn.dev33
+ sa-token-alone-redis
+ ${sa.token.version}
+
+
+
+
+ cn.dev33
+ sa-token-sso
+ ${sa.token.version}
+
+
+
+
+ me.zhyd.oauth
+ JustAuth
+ ${just.auth.version}
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/config/AuthConfigure.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/config/AuthConfigure.java
new file mode 100644
index 00000000..532ba60e
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/config/AuthConfigure.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.core.config;
+
+import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
+import cn.dev33.satoken.stp.StpInterface;
+import cn.dev33.satoken.stp.StpLogic;
+import cn.dev33.satoken.strategy.SaStrategy;
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
+import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
+import vip.xiaonuo.common.pojo.CommonResult;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * SaToken鉴权配置
+ *
+ * @author xuyuxiang
+ * @date 2021/10/9 14:24
+ **/
+@Configuration
+public class AuthConfigure implements WebMvcConfigurer {
+
+ @Resource
+ private OpenApiExtensionResolver openApiExtensionResolver;
+
+ /**
+ * 注册Sa-Token的注解拦截器,打开注解式鉴权功能
+ *
+ * 注解的方式有以下几中,注解既可以加在接口方法上,也可加在Controller类上:
+ * 1.@SaCheckLogin: 登录认证 —— 只有登录之后才能进入该方法(常用)
+ * 2.@SaCheckRole("admin"): 角色认证 —— 必须具有指定角色标识才能进入该方法(常用)
+ * 3.@SaCheckPermission("user:add"): 权限认证 —— 必须具有指定权限才能进入该方法(常用)
+ * 4.@SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法
+ * 5.@SaCheckBasic: HttpBasic认证 —— 只有通过 Basic 认证后才能进入该方法
+ *
+ * 在Controller中创建一个接口,默认不需要登录也不需要任何权限都可以访问的,只有加了上述注解才会校验
+ **/
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关,只是说明哪些接口不需要被拦截器拦截,此处都拦截)
+ registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
+ }
+
+ @Bean("stpLogic")
+ public StpLogic getStpLogic() {
+ // 重写Sa-Token的StpLogic,默认客户端类型为B
+ return new StpLogic(SaClientTypeEnum.B.getValue());
+ }
+
+ @Bean("stpClientLogic")
+ public StpLogic getStpClientLogic() {
+ // 重写Sa-Token的StpLogic,默认客户端类型为C
+ return new StpLogic(SaClientTypeEnum.C.getValue());
+ }
+
+ @Bean
+ public void rewriteSaStrategy() {
+ // 重写Sa-Token的注解处理器,增加注解合并功能
+ SaStrategy.me.getAnnotation = AnnotatedElementUtils::getMergedAnnotation;
+ }
+
+ /**
+ * 权限认证接口实现类,集成权限认证功能
+ *
+ * @author xuyuxiang
+ * @date 2022/7/7 16:16
+ **/
+ @Component
+ public static class StpInterfaceImpl implements StpInterface {
+
+ /**
+ * 返回一个账号所拥有的权限码集合
+ */
+ @Override
+ public List getPermissionList(Object loginId, String loginType) {
+ return StpLoginUserUtil.getLoginUser().getPermissionCodeList();
+ }
+
+ /**
+ * 返回一个账号所拥有的角色标识集合
+ */
+ @Override
+ public List getRoleList(Object loginId, String loginType) {
+ return StpLoginUserUtil.getLoginUser().getRoleCodeList();
+ }
+ }
+
+ /**
+ * API文档分组配置
+ *
+ * @author xuyuxiang
+ * @date 2022/7/7 16:18
+ **/
+ @Bean(value = "authDocApi")
+ public Docket authDocApi() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(new ApiInfoBuilder()
+ .title("登录鉴权AUTH")
+ .description("登录鉴权AUTH")
+ .termsOfServiceUrl("https://www.xiaonuo.vip")
+ .contact(new Contact("SNOWY_TEAM","https://www.xiaonuo.vip", "xuyuxiang29@foxmail.com"))
+ .version("2.0.0")
+ .build())
+ .globalResponseMessage(RequestMethod.GET, CommonResult.responseList())
+ .globalResponseMessage(RequestMethod.POST, CommonResult.responseList())
+ .groupName("登录鉴权AUTH")
+ .select()
+ .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+ .apis(RequestHandlerSelectors.basePackage("vip.xiaonuo.auth"))
+ .paths(PathSelectors.any())
+ .build().extensions(openApiExtensionResolver.buildExtensions("登录鉴权AUTH"));
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/util/AuthExceptionUtil.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/util/AuthExceptionUtil.java
new file mode 100644
index 00000000..29dfadcc
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/util/AuthExceptionUtil.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.core.util;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.exception.*;
+import cn.hutool.http.HttpStatus;
+import lombok.extern.slf4j.Slf4j;
+import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.common.util.CommonServletUtil;
+
+@Slf4j
+public class AuthExceptionUtil {
+
+ /**
+ * 根据错误类型获取对应的CommonResult(只处理SaToken相关异常)
+ *
+ * @author xuyuxiang
+ * @date 2021/10/11 15:52
+ **/
+ public static CommonResult getCommonResult(Exception e) {
+ CommonResult commonResult;
+ if (e instanceof NotLoginException) {
+
+ // 如果是未登录异常 401
+ NotLoginException notLoginException = (NotLoginException) e;
+ commonResult = CommonResult.get(HttpStatus.HTTP_UNAUTHORIZED, notLoginException.getMessage(), null);
+ } else if (e instanceof NotRoleException) {
+
+ // 如果是角色异常 403
+ NotRoleException notRoleException = (NotRoleException) e;
+ commonResult = CommonResult.get(HttpStatus.HTTP_FORBIDDEN, "无此角色:" + notRoleException.getRole() +
+ ",接口地址:" + CommonServletUtil.getRequest().getServletPath(), null);
+ } else if (e instanceof NotPermissionException) {
+
+ // 如果是权限异常 403
+ NotPermissionException notPermissionException = (NotPermissionException) e;
+ commonResult = CommonResult.get(HttpStatus.HTTP_FORBIDDEN, "无此权限:" + notPermissionException.getPermission(), null);
+ } else if (e instanceof DisableLoginException) {
+
+ // 如果是被封禁异常 403
+ DisableLoginException disableLoginException = (DisableLoginException) e;
+ commonResult = CommonResult.get(HttpStatus.HTTP_FORBIDDEN, "账号被封禁:" + disableLoginException.getDisableTime() + "秒后解封", null);
+ } else if (e instanceof SaTokenException) {
+
+ // 如果是SaToken异常 直接返回
+ SaTokenException saTokenException = (SaTokenException) e;
+ commonResult = CommonResult.error(saTokenException.getMessage());
+ } else {
+ // 未知异常才打印
+ e.printStackTrace();
+ // 未知异常返回服务器异常(此处不可能执行进入,因为本方法处理的一定是SaToken的异常,此处仅为安全性考虑)
+ commonResult = CommonResult.error("服务器异常");
+ }
+ log.error(">>> {},请求地址:{}", commonResult.getMsg(), SaHolder.getRequest().getUrl());
+ return commonResult;
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthClientController.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthClientController.java
new file mode 100644
index 00000000..2684f989
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthClientController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.controller;
+
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.auth.core.annotation.SaClientCheckLogin;
+import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
+import vip.xiaonuo.auth.core.pojo.SaBaseClientLoginUser;
+import vip.xiaonuo.auth.core.util.StpClientUtil;
+import vip.xiaonuo.auth.modular.login.param.AuthAccountPasswordLoginParam;
+import vip.xiaonuo.auth.modular.login.param.AuthGetPhoneValidCodeParam;
+import vip.xiaonuo.auth.modular.login.param.AuthPhoneValidCodeLoginParam;
+import vip.xiaonuo.auth.modular.login.result.AuthPicValidCodeResult;
+import vip.xiaonuo.auth.modular.login.service.AuthService;
+import vip.xiaonuo.common.pojo.CommonResult;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+/**
+ * C端登录控制器
+ *
+ * @author xuyuxiang
+ * @date 2021/12/23 21:50
+ */
+@Api(tags = "C端登录控制器")
+@ApiSupport(author = "SNOWY_TEAM", order = 1)
+@RestController
+@Validated
+public class AuthClientController {
+
+ @Resource
+ private AuthService authService;
+
+ /**
+ * C端获取图片验证码
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 9:26
+ **/
+ @ApiOperationSupport(order = 1)
+ @ApiOperation("C端获取图片验证码")
+ @GetMapping("/auth/c/getPicCaptcha")
+ public CommonResult getPicCaptcha() {
+ return CommonResult.data(authService.getPicCaptcha(SaClientTypeEnum.B.getValue()));
+ }
+
+ /**
+ * C端获取手机验证码
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 9:26
+ **/
+ @ApiOperationSupport(order = 2)
+ @ApiOperation("C端获取手机验证码")
+ @GetMapping("/auth/c/getPhoneValidCode")
+ public CommonResult getPhoneValidCode(@Valid AuthGetPhoneValidCodeParam authGetPhoneValidCodeParam) {
+ return CommonResult.data(authService.getPhoneValidCode(authGetPhoneValidCodeParam, SaClientTypeEnum.C.getValue()));
+ }
+
+ /**
+ * C端账号密码登录
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 3)
+ @ApiOperation("C端账号密码登录")
+ @PostMapping("/auth/c/doLogin")
+ public CommonResult doLogin(@RequestBody @Valid AuthAccountPasswordLoginParam authAccountPasswordLoginParam) {
+ return CommonResult.data(authService.doLogin(authAccountPasswordLoginParam, SaClientTypeEnum.C.getValue()));
+ }
+
+ /**
+ * C端手机验证码登录
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 4)
+ @ApiOperation("C端手机验证码登录")
+ @PostMapping("/auth/c/doLoginByPhone")
+ public CommonResult doLoginByPhone(@RequestBody @Valid AuthPhoneValidCodeLoginParam authPhoneValidCodeLoginParam) {
+ return CommonResult.data(authService.doLoginByPhone(authPhoneValidCodeLoginParam, SaClientTypeEnum.C.getValue()));
+ }
+
+ /**
+ * C端退出
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 5)
+ @ApiOperation("C端退出")
+ @SaClientCheckLogin
+ @GetMapping("/auth/c/doLogout")
+ public CommonResult doLogout() {
+ StpClientUtil.logout();
+ return CommonResult.ok();
+ }
+
+ /**
+ * C端获取用户信息
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 6)
+ @ApiOperation("C端获取用户信息")
+ @SaClientCheckLogin
+ @GetMapping("/auth/c/getLoginUser")
+ public CommonResult getLoginUser() {
+ return CommonResult.data(authService.getClientLoginUser());
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthController.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthController.java
new file mode 100644
index 00000000..f7d6be62
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.controller;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.stp.StpUtil;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
+import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
+import vip.xiaonuo.auth.modular.login.param.AuthAccountPasswordLoginParam;
+import vip.xiaonuo.auth.modular.login.param.AuthGetPhoneValidCodeParam;
+import vip.xiaonuo.auth.modular.login.param.AuthPhoneValidCodeLoginParam;
+import vip.xiaonuo.auth.modular.login.result.AuthPicValidCodeResult;
+import vip.xiaonuo.auth.modular.login.service.AuthService;
+import vip.xiaonuo.common.pojo.CommonResult;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+/**
+ * B端登录控制器
+ *
+ * @author xuyuxiang
+ * @date 2021/12/23 21:50
+ */
+@Api(tags = "B端登录控制器")
+@ApiSupport(author = "SNOWY_TEAM", order = 2)
+@RestController
+@Validated
+public class AuthController {
+
+ @Resource
+ private AuthService authService;
+
+ /**
+ * B端获取图片验证码
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 9:26
+ **/
+ @ApiOperationSupport(order = 1)
+ @ApiOperation("B端获取图片验证码")
+ @GetMapping("/auth/b/getPicCaptcha")
+ public CommonResult getPicCaptcha() {
+ return CommonResult.data(authService.getPicCaptcha(SaClientTypeEnum.B.getValue()));
+ }
+
+ /**
+ * B端获取手机验证码
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 9:26
+ **/
+ @ApiOperationSupport(order = 2)
+ @ApiOperation("B端获取手机验证码")
+ @GetMapping("/auth/b/getPhoneValidCode")
+ public CommonResult getPhoneValidCode(@Valid AuthGetPhoneValidCodeParam authGetPhoneValidCodeParam) {
+ return CommonResult.data(authService.getPhoneValidCode(authGetPhoneValidCodeParam, SaClientTypeEnum.B.getValue()));
+ }
+
+ /**
+ * B端账号密码登录
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 3)
+ @ApiOperation("B端账号密码登录")
+ @PostMapping("/auth/b/doLogin")
+ public CommonResult doLogin(@RequestBody @Valid AuthAccountPasswordLoginParam authAccountPasswordLoginParam) {
+ return CommonResult.data(authService.doLogin(authAccountPasswordLoginParam, SaClientTypeEnum.B.getValue()));
+ }
+
+ /**
+ * B端手机验证码登录
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 4)
+ @ApiOperation("B端手机验证码登录")
+ @PostMapping("/auth/b/doLoginByPhone")
+ public CommonResult doLoginByPhone(@RequestBody @Valid AuthPhoneValidCodeLoginParam authPhoneValidCodeLoginParam) {
+ return CommonResult.data(authService.doLoginByPhone(authPhoneValidCodeLoginParam, SaClientTypeEnum.B.getValue()));
+ }
+
+ /**
+ * B端退出
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 5)
+ @ApiOperation("B端退出")
+ @SaCheckLogin
+ @GetMapping("/auth/b/doLogout")
+ public CommonResult doLogout() {
+ StpUtil.logout();
+ return CommonResult.ok();
+ }
+
+ /**
+ * B端获取用户信息
+ *
+ * @author xuyuxiang
+ * @date 2021/10/15 13:12
+ **/
+ @ApiOperationSupport(order = 6)
+ @ApiOperation("B端获取用户信息")
+ @SaCheckLogin
+ @GetMapping("/auth/b/getLoginUser")
+ public CommonResult getLoginUser() {
+ return CommonResult.data(authService.getLoginUser());
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthDeviceTypeEnum.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthDeviceTypeEnum.java
new file mode 100644
index 00000000..0815ff2a
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthDeviceTypeEnum.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.enums;
+
+import lombok.Getter;
+import vip.xiaonuo.common.exception.CommonException;
+
+/**
+ * 登录设备类型枚举
+ *
+ * @author xuyuxiang
+ * @date 2021/10/11 14:02
+ **/
+@Getter
+public enum AuthDeviceTypeEnum {
+
+ /**
+ * PC端
+ */
+ PC("PC"),
+
+ /**
+ * 移动端
+ */
+ APP("APP"),
+
+ /**
+ * 小程序端
+ */
+ MINI("MINI");
+
+ private final String value;
+
+ AuthDeviceTypeEnum(String value) {
+ this.value = value;
+ }
+
+ public static void validate(String value) {
+ boolean flag = PC.getValue().equals(value) || APP.getValue().equals(value) || MINI.getValue().equals(value);
+ if(!flag) {
+ throw new CommonException("不支持的登录设备类型:{}", value);
+ }
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthExceptionEnum.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthExceptionEnum.java
new file mode 100644
index 00000000..dfbfc913
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthExceptionEnum.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.enums;
+
+import lombok.Getter;
+
+/**
+ * 登录异常提示语枚举
+ *
+ * @author xuyuxiang
+ * @date 2021/10/11 14:02
+ **/
+@Getter
+public enum AuthExceptionEnum {
+
+ /**
+ * 验证码不能为空
+ */
+ VALID_CODE_EMPTY("验证码不能为空"),
+
+ /**
+ * 验证码请求号不能为空
+ */
+ VALID_CODE_REQ_NO_EMPTY("验证码请求号不能为空"),
+
+ /**
+ * 验证码错误
+ */
+ VALID_CODE_ERROR("验证码错误"),
+
+ /**
+ * 账号错误
+ */
+ ACCOUNT_ERROR("账号错误"),
+
+ /**
+ * 账号已停用
+ */
+ ACCOUNT_DISABLED("账号已停用"),
+
+ /**
+ * 密码错误
+ */
+ PWD_ERROR("密码错误"),
+
+ /**
+ * 手机号格式错误
+ */
+ PHONE_FORMAT_ERROR("手机号格式错误"),
+
+ /**
+ * 手机号不存在
+ */
+ PHONE_ERROR("手机号不存在"),
+
+ /**
+ * 客户端类型不能为空
+ */
+ CLIENT_TYPE_EMPTY("客户端类型不能为空"),
+
+ /**
+ * 客户端类型错误
+ */
+ CLIENT_TYPE_ERROR("客户端类型错误"),
+
+ /**
+ * 密码解密失败,请检查前端公钥
+ */
+ PWD_DECRYPT_ERROR("密码解密失败,请检查前端公钥");
+
+ private final String value;
+
+ AuthExceptionEnum(String value) {
+ this.value = value;
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/listener/AuthListener.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/listener/AuthListener.java
new file mode 100644
index 00000000..d1480b2d
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/listener/AuthListener.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.listener;
+
+import cn.dev33.satoken.listener.SaTokenListener;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import org.springframework.stereotype.Component;
+import vip.xiaonuo.auth.api.SaBaseLoginUserApi;
+import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
+import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
+import vip.xiaonuo.dev.api.DevLogApi;
+
+import javax.annotation.Resource;
+
+/**
+ * 自定义登录监听器
+ *
+ * @author xuyuxiang
+ * @date 2021/12/28 11:35
+ **/
+@Component
+public class AuthListener implements SaTokenListener {
+
+ @Resource(name = "loginUserApi")
+ private SaBaseLoginUserApi loginUserApi;
+
+ @Resource(name = "clientLoginUserApi")
+ private SaBaseLoginUserApi clientLoginUserApi;
+
+ @Resource
+ private DevLogApi devLogApi;
+
+ /** 每次登录时触发 */
+ @Override
+ public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
+ // 更新用户的登录时间和登录ip等信息
+ if(SaClientTypeEnum.B.getValue().equals(loginType)) {
+ loginUserApi.updateUserLoginInfo(Convert.toStr(loginId), loginModel.getDevice());
+ // 记录B端登录日志
+ SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserById(Convert.toStr(loginId));
+ if(ObjectUtil.isNotEmpty(saBaseLoginUser)) {
+ devLogApi.executeLoginLog(saBaseLoginUser.getName());
+ } else {
+ devLogApi.executeLoginLog(null);
+ }
+ } else {
+ clientLoginUserApi.updateUserLoginInfo(Convert.toStr(loginId), loginModel.getDevice());
+ }
+ }
+
+ /** 每次注销时触发 */
+ @Override
+ public void doLogout(String loginType, Object loginId, String tokenValue) {
+ if(SaClientTypeEnum.B.getValue().equals(loginType)) {
+ // 记录B端登出日志
+ SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserById(Convert.toStr(loginId));
+ if(ObjectUtil.isNotEmpty(saBaseLoginUser)) {
+ devLogApi.executeLogoutLog(saBaseLoginUser.getName());
+ } else {
+ devLogApi.executeLogoutLog(null);
+ }
+ }
+ }
+
+ /** 每次被踢下线时触发 */
+ @Override
+ public void doKickout(String loginType, Object loginId, String tokenValue) {
+ // ...
+ }
+
+ /** 每次被顶下线时触发 */
+ @Override
+ public void doReplaced(String loginType, Object loginId, String tokenValue) {
+ // ...
+ }
+
+ /** 每次被封禁时触发 */
+ @Override
+ public void doDisable(String loginType, Object loginId, long disableTime) {
+ // ...
+ }
+
+ /** 每次被解封时触发 */
+ @Override
+ public void doUntieDisable(String loginType, Object loginId) {
+ // ...
+ }
+
+ /** 每次创建Session时触发 */
+ @Override
+ public void doCreateSession(String id) {
+ // ...
+ }
+
+ /** 每次注销Session时触发 */
+ @Override
+ public void doLogoutSession(String id) {
+ // ...
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthAccountPasswordLoginParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthAccountPasswordLoginParam.java
new file mode 100644
index 00000000..8a486f9d
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthAccountPasswordLoginParam.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 账号密码登录参数
+ *
+ * @author xuyuxiang
+ * @date 2022/7/7 16:46
+ **/
+@Getter
+@Setter
+public class AuthAccountPasswordLoginParam {
+
+ /** 账号 */
+ @ApiModelProperty(value = "账号", required = true, position = 1)
+ @NotBlank(message = "账号不能为空")
+ private String account;
+
+ /** 密码 */
+ @ApiModelProperty(value = "密码", required = true, position = 2)
+ @NotBlank(message = "密码不能为空")
+ private String password;
+
+ /** 设备 */
+ @ApiModelProperty(value = "设备", position = 3)
+ private String device;
+
+ /** 验证码 */
+ @ApiModelProperty(value = "验证码", position = 4)
+ private String validCode;
+
+ /** 验证码请求号 */
+ @ApiModelProperty(value = "验证码请求号", position = 5)
+ private String validCodeReqNo;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthGetPhoneValidCodeParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthGetPhoneValidCodeParam.java
new file mode 100644
index 00000000..d1dab9ae
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthGetPhoneValidCodeParam.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 获取手机验证码参数
+ *
+ * @author xuyuxiang
+ * @date 2022/8/25 13:45
+ **/
+@Getter
+@Setter
+public class AuthGetPhoneValidCodeParam {
+
+ /** 手机号 */
+ @ApiModelProperty(value = "手机号", required = true, position = 1)
+ @NotBlank(message = "手机号不能为空")
+ private String phone;
+
+ /** 验证码 */
+ @ApiModelProperty(value = "验证码", required = true, position = 2)
+ @NotBlank(message = "验证码不能为空")
+ private String validCode;
+
+ /** 验证码请求号 */
+ @ApiModelProperty(value = "验证码请求号", required = true, position = 3)
+ @NotBlank(message = "验证码请求号不能为空")
+ private String validCodeReqNo;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthPhoneValidCodeLoginParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthPhoneValidCodeLoginParam.java
new file mode 100644
index 00000000..51932974
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthPhoneValidCodeLoginParam.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 手机验证码登录参数
+ *
+ * @author xuyuxiang
+ * @date 2022/7/7 16:46
+ **/
+@Getter
+@Setter
+public class AuthPhoneValidCodeLoginParam {
+
+ /** 手机号 */
+ @ApiModelProperty(value = "手机号", required = true, position = 1)
+ @NotBlank(message = "手机号不能为空")
+ private String phone;
+
+ /** 验证码 */
+ @ApiModelProperty(value = "验证码", required = true, position = 2)
+ @NotBlank(message = "验证码不能为空")
+ private String validCode;
+
+ /** 验证码请求号 */
+ @ApiModelProperty(value = "验证码请求号", required = true, position = 3)
+ @NotBlank(message = "验证码请求号不能为空")
+ private String validCodeReqNo;
+
+ /** 设备 */
+ @ApiModelProperty(value = "设备", position = 4)
+ private String device;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/result/AuthPicValidCodeResult.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/result/AuthPicValidCodeResult.java
new file mode 100644
index 00000000..28c917ef
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/result/AuthPicValidCodeResult.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.result;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 图片验证码结果
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 9:28
+ **/
+@Getter
+@Setter
+public class AuthPicValidCodeResult {
+
+ /** 验证码图片,Base64 */
+ @ApiModelProperty(value = "验证码图片,Base64", position = 1)
+ private String validCodeBase64;
+
+ /** 验证码请求号 */
+ @ApiModelProperty(value = "验证码请求号", position = 2)
+ private String validCodeReqNo;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/service/AuthService.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/service/AuthService.java
new file mode 100644
index 00000000..4a6d7ac6
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/service/AuthService.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.service;
+
+import vip.xiaonuo.auth.core.pojo.SaBaseClientLoginUser;
+import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
+import vip.xiaonuo.auth.modular.login.param.AuthAccountPasswordLoginParam;
+import vip.xiaonuo.auth.modular.login.param.AuthGetPhoneValidCodeParam;
+import vip.xiaonuo.auth.modular.login.param.AuthPhoneValidCodeLoginParam;
+import vip.xiaonuo.auth.modular.login.result.AuthPicValidCodeResult;
+
+/**
+ * 登录Service接口
+ *
+ * @author xuyuxiang
+ * @date 2021/12/23 21:51
+ */
+public interface AuthService {
+
+ /**
+ * 获取图片验证码
+ *
+ * @author xuyuxiang
+ * @date 2021/12/28 14:46
+ **/
+ AuthPicValidCodeResult getPicCaptcha(String type);
+
+ /**
+ * 获取手机验证码
+ *
+ * @author xuyuxiang
+ * @date 2021/12/28 14:46
+ **/
+ String getPhoneValidCode(AuthGetPhoneValidCodeParam authGetPhoneValidCodeParam, String type);
+
+ /**
+ * 账号密码登录
+ *
+ * @author xuyuxiang
+ * @date 2021/12/28 14:46
+ **/
+ String doLogin(AuthAccountPasswordLoginParam authAccountPasswordLoginParam, String type);
+
+ /**
+ * 手机验证码登录
+ *
+ * @author xuyuxiang
+ * @date 2021/12/28 14:46
+ **/
+ String doLoginByPhone(AuthPhoneValidCodeLoginParam authPhoneValidCodeLoginParam, String type);
+
+ /**
+ * 获取B端登录用户信息
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 15:59
+ **/
+ SaBaseLoginUser getLoginUser();
+
+ /**
+ * 获取C端登录用户信息
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 15:59
+ **/
+ SaBaseClientLoginUser getClientLoginUser();
+
+ /**
+ * 根据用户id和客户端类型登录,用于第三方登录
+ *
+ * @author xuyuxiang
+ * @date 2022/7/9 14:44
+ */
+ String doLoginById(String userId, String device, String type);
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/service/impl/AuthServiceImpl.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/service/impl/AuthServiceImpl.java
new file mode 100644
index 00000000..231c372d
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/service/impl/AuthServiceImpl.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.login.service.impl;
+
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.captcha.CaptchaUtil;
+import cn.hutool.captcha.CircleCaptcha;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.PhoneUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import org.springframework.stereotype.Service;
+import vip.xiaonuo.auth.api.SaBaseLoginUserApi;
+import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
+import vip.xiaonuo.auth.core.pojo.SaBaseClientLoginUser;
+import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
+import vip.xiaonuo.auth.core.util.StpClientLoginUserUtil;
+import vip.xiaonuo.auth.core.util.StpClientUtil;
+import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
+import vip.xiaonuo.auth.modular.login.enums.AuthDeviceTypeEnum;
+import vip.xiaonuo.auth.modular.login.enums.AuthExceptionEnum;
+import vip.xiaonuo.auth.modular.login.param.AuthAccountPasswordLoginParam;
+import vip.xiaonuo.auth.modular.login.param.AuthGetPhoneValidCodeParam;
+import vip.xiaonuo.auth.modular.login.param.AuthPhoneValidCodeLoginParam;
+import vip.xiaonuo.auth.modular.login.result.AuthPicValidCodeResult;
+import vip.xiaonuo.auth.modular.login.service.AuthService;
+import vip.xiaonuo.common.cache.CommonCacheOperator;
+import vip.xiaonuo.common.exception.CommonException;
+import vip.xiaonuo.common.util.CommonCryptogramUtil;
+import vip.xiaonuo.dev.api.DevConfigApi;
+import vip.xiaonuo.dev.api.DevSmsApi;
+
+import javax.annotation.Resource;
+import java.util.stream.Collectors;
+
+/**
+ * 登录Service接口实现类
+ *
+ * @author xuyuxiang
+ * @date 2021/12/23 21:52
+ */
+@Service
+public class AuthServiceImpl implements AuthService {
+
+ private static final String SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_KEY = "SNOWY_SYS_DEFAULT_CAPTCHA_OPEN";
+
+ private static final String AUTH_CACHE_KEY = "auth-validCode:";
+
+ @Resource(name = "loginUserApi")
+ private SaBaseLoginUserApi loginUserApi;
+
+ @Resource(name = "clientLoginUserApi")
+ private SaBaseLoginUserApi clientLoginUserApi;
+
+ @Resource
+ private DevConfigApi devConfigApi;
+
+ @Resource
+ private DevSmsApi devSmsApi;
+
+ @Resource
+ private CommonCacheOperator commonCacheOperator;
+
+ @Override
+ public AuthPicValidCodeResult getPicCaptcha(String type) {
+ // 生成验证码,随机4位字符
+ CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(100, 38, 4, 10);
+ // 定义返回结果
+ AuthPicValidCodeResult authPicValidCodeResult = new AuthPicValidCodeResult();
+ // 获取验证码的值
+ String validCode = circleCaptcha.getCode();
+ // 获取验证码的base64
+ String validCodeBase64 = circleCaptcha.getImageBase64Data();
+ // 生成请求号
+ String validCodeReqNo = IdWorker.getIdStr();
+ // 将base64返回前端
+ authPicValidCodeResult.setValidCodeBase64(validCodeBase64);
+ // 将请求号返回前端
+ authPicValidCodeResult.setValidCodeReqNo(validCodeReqNo);
+ // 将请求号作为key,验证码的值作为value放到redis,用于校验,5分钟有效
+ commonCacheOperator.put(AUTH_CACHE_KEY + validCodeReqNo, validCode, 5 * 60);
+ return authPicValidCodeResult;
+ }
+
+ @Override
+ public String getPhoneValidCode(AuthGetPhoneValidCodeParam authGetPhoneValidCodeParam, String type) {
+ // 手机号
+ String phone = authGetPhoneValidCodeParam.getPhone();
+ // 验证码
+ String validCode = authGetPhoneValidCodeParam.getValidCode();
+ // 验证码请求号
+ String validCodeReqNo = authGetPhoneValidCodeParam.getValidCodeReqNo();
+ // 校验参数
+ validPhoneValidCodeParam(phone, validCode, validCodeReqNo, type);
+ // 生成手机验证码的值,随机6为数字
+ String phoneValidCode = RandomUtil.randomNumbers(6);
+ // 生成手机验证码的请求号
+ String phoneValidCodeReqNo = IdWorker.getIdStr();
+
+ // TODO 使用阿里云执行发送验证码,将验证码作为短信内容的参数变量放入,
+ // TODO 签名不传则使用系统默认配置的签名,支持传入多个参数,示例:{"name":"张三","number":"15038****76"}
+ //devSmsApi.sendSmsAliyun(phone, null, "验证码模板号", JSONUtil.toJsonStr(JSONUtil.createObj().set("validCode", phoneValidCode)));
+
+ // TODO 使用腾讯云执行发送验证码,将验证码作为短信内容的参数变量放入,
+ // TODO sdkAppId和签名不传则使用系统默认配置的sdkAppId和签名,支持传入多个参数,逗号拼接,示例:"张三,15038****76,进行中"
+ devSmsApi.sendSmsTencent("1400522364", phone, "小诺开源技术", "1502357", phoneValidCode);
+
+ // 将请求号作为key,验证码的值作为value放到redis,用于校验,5分钟有效
+ commonCacheOperator.put(AUTH_CACHE_KEY + phoneValidCodeReqNo, phoneValidCode, 5 * 60);
+ // 返回请求号
+ return phoneValidCodeReqNo;
+ }
+
+ /**
+ * 校验验证码方法
+ *
+ * @author xuyuxiang
+ * @date 2022/8/25 15:26
+ **/
+ private void validValidCode(String validCode, String validCodeReqNo) {
+ // 依据请求号,取出缓存中的验证码进行校验
+ Object existValidCode = commonCacheOperator.get(AUTH_CACHE_KEY + validCodeReqNo);
+ // 为空则直接验证码错误
+ if(ObjectUtil.isEmpty(existValidCode)) {
+ throw new CommonException(AuthExceptionEnum.VALID_CODE_ERROR.getValue());
+ }
+ // 不一致则直接验证码错误
+ if(!validCode.equals(Convert.toStr(existValidCode))) {
+ // 移除该验证码
+ commonCacheOperator.remove(AUTH_CACHE_KEY + validCodeReqNo);
+ throw new CommonException(AuthExceptionEnum.VALID_CODE_ERROR.getValue());
+ }
+ // 移除该验证码
+ commonCacheOperator.remove(AUTH_CACHE_KEY + validCodeReqNo);
+ }
+
+ /**
+ * 校验手机号与验证码等参数
+ *
+ * @author xuyuxiang
+ * @date 2022/8/25 14:29
+ **/
+ private void validPhoneValidCodeParam(String phone, String validCode, String validCodeReqNo, String type) {
+ // 验证码正确则校验手机号格式
+ if(!PhoneUtil.isMobile(phone)) {
+ throw new CommonException(AuthExceptionEnum.PHONE_FORMAT_ERROR.getValue());
+ }
+ // 执行校验验证码
+ validValidCode(validCode, validCodeReqNo);
+ // 根据手机号获取用户信息,判断用户是否存在,根据B端或C端判断
+ if(SaClientTypeEnum.B.getValue().equals(type)) {
+ if(ObjectUtil.isEmpty(loginUserApi.getUserByPhone(phone))) {
+ throw new CommonException(AuthExceptionEnum.PHONE_ERROR.getValue());
+ }
+ } else {
+ if(ObjectUtil.isEmpty(clientLoginUserApi.getClientUserByPhone(phone))) {
+ throw new CommonException(AuthExceptionEnum.PHONE_ERROR.getValue());
+ }
+ }
+ }
+
+ @Override
+ public String doLogin(AuthAccountPasswordLoginParam authAccountPasswordLoginParam, String type) {
+ // 获取账号
+ String account = authAccountPasswordLoginParam.getAccount();
+ // 获取密码
+ String password = authAccountPasswordLoginParam.getPassword();
+ // 获取设备
+ String device = authAccountPasswordLoginParam.getDevice();
+ // 默认指定为PC,如在小程序跟移动端的情况下,自行指定即可
+ if(ObjectUtil.isEmpty(device)) {
+ device = AuthDeviceTypeEnum.PC.getValue();
+ } else {
+ AuthDeviceTypeEnum.validate(device);
+ }
+ // 校验验证码
+ String defaultCaptchaOpen = devConfigApi.getValueByKey(SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_KEY);
+ if(ObjectUtil.isNotEmpty(defaultCaptchaOpen)) {
+ if(Convert.toBool(defaultCaptchaOpen)) {
+ // 获取验证码
+ String validCode = authAccountPasswordLoginParam.getValidCode();
+ // 获取验证码请求号
+ String validCodeReqNo = authAccountPasswordLoginParam.getValidCodeReqNo();
+ // 开启验证码则必须传入验证码
+ if(ObjectUtil.isEmpty(validCode)) {
+ throw new CommonException(AuthExceptionEnum.VALID_CODE_EMPTY.getValue());
+ }
+ // 开启验证码则必须传入验证码请求号
+ if(ObjectUtil.isEmpty(validCodeReqNo)) {
+ throw new CommonException(AuthExceptionEnum.VALID_CODE_REQ_NO_EMPTY.getValue());
+ }
+ // 执行校验验证码
+ validValidCode(validCode, validCodeReqNo);
+ }
+ }
+ // SM2解密并获得前端传来的密码哈希值
+ String passwordHash;
+ try {
+ // 解密,并做哈希值
+ passwordHash = CommonCryptogramUtil.doHashValue(CommonCryptogramUtil.doSm2Decrypt(password));
+ } catch (Exception e) {
+ throw new CommonException(AuthExceptionEnum.PWD_DECRYPT_ERROR.getValue());
+ }
+ // 根据账号获取用户信息,根据B端或C端判断
+ if(SaClientTypeEnum.B.getValue().equals(type)) {
+ SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserByAccount(account);
+ if(ObjectUtil.isEmpty(saBaseLoginUser)) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
+ }
+ if (!saBaseLoginUser.getPassword().equals(passwordHash)) {
+ throw new CommonException(AuthExceptionEnum.PWD_ERROR.getValue());
+ }
+ // 执行B端登录
+ return execLoginB(saBaseLoginUser, device);
+ } else {
+ SaBaseClientLoginUser saBaseClientLoginUser = clientLoginUserApi.getClientUserByAccount(account);
+ if(ObjectUtil.isEmpty(saBaseClientLoginUser)) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
+ }
+ if (!saBaseClientLoginUser.getPassword().equals(passwordHash)) {
+ throw new CommonException(AuthExceptionEnum.PWD_ERROR.getValue());
+ }
+ // 执行C端登录
+ return execLoginC(saBaseClientLoginUser, device);
+ }
+ }
+
+ @Override
+ public String doLoginByPhone(AuthPhoneValidCodeLoginParam authPhoneValidCodeLoginParam, String type) {
+ // 手机号
+ String phone = authPhoneValidCodeLoginParam.getPhone();
+ // 校验参数
+ validPhoneValidCodeParam(phone, authPhoneValidCodeLoginParam.getValidCode(), authPhoneValidCodeLoginParam.getValidCodeReqNo(), type);
+ // 设备
+ String device = authPhoneValidCodeLoginParam.getDevice();
+ // 默认指定为PC,如在小程序跟移动端的情况下,自行指定即可
+ if(ObjectUtil.isEmpty(device)) {
+ device = AuthDeviceTypeEnum.PC.getValue();
+ } else {
+ AuthDeviceTypeEnum.validate(device);
+ }
+ // 根据手机号获取用户信息,根据B端或C端判断
+ if(SaClientTypeEnum.B.getValue().equals(type)) {
+ SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserByPhone(phone);
+ if(ObjectUtil.isEmpty(saBaseLoginUser)) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
+ }
+ // 执行B端登录
+ return execLoginB(saBaseLoginUser, device);
+ } else {
+ SaBaseClientLoginUser saBaseClientLoginUser = clientLoginUserApi.getClientUserByPhone(phone);
+ if(ObjectUtil.isEmpty(saBaseClientLoginUser)) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
+ }
+ // 执行C端登录
+ return execLoginC(saBaseClientLoginUser, device);
+ }
+ }
+
+ /**
+ * 执行B端登录
+ *
+ * @author xuyuxiang
+ * @date 2022/8/25 14:36
+ **/
+ private String execLoginB(SaBaseLoginUser saBaseLoginUser, String device) {
+ // 校验状态
+ if(!saBaseLoginUser.getEnabled()) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_DISABLED.getValue());
+ }
+ // 执行登录
+ StpUtil.login(saBaseLoginUser.getId(), new SaLoginModel().setDevice(device));
+ // 获取按钮码
+ saBaseLoginUser.setButtonCodeList(loginUserApi.getButtonCodeListListByUserId(saBaseLoginUser.getId()));
+ // 获取数据范围
+ saBaseLoginUser.setDataScopeList(Convert.toList(SaBaseLoginUser.DataScope.class,
+ loginUserApi.getPermissionListByUserId(saBaseLoginUser.getId(), saBaseLoginUser.getOrgId())));
+ // 获取权限码
+ saBaseLoginUser.setPermissionCodeList(saBaseLoginUser.getDataScopeList().stream()
+ .map(SaBaseLoginUser.DataScope::getApiUrl).collect(Collectors.toList()));
+ // 获取角色码
+ saBaseLoginUser.setRoleCodeList(loginUserApi.getRoleCodeListByUserId(saBaseLoginUser.getId()));
+ // 缓存用户信息,此处使用TokenSession为了指定时间内无操作则自动下线
+ StpUtil.getTokenSession().set("loginUser", saBaseLoginUser);
+ // 返回token
+ return StpUtil.getTokenInfo().tokenValue;
+ }
+
+ /**
+ * 执行C端登录
+ *
+ * @author xuyuxiang
+ * @date 2022/8/25 14:37
+ **/
+ private String execLoginC(SaBaseClientLoginUser saBaseClientLoginUser, String device) {
+ // 校验状态
+ if(!saBaseClientLoginUser.getEnabled()) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_DISABLED.getValue());
+ }
+ // 执行登录
+ StpClientUtil.login(saBaseClientLoginUser.getId(), new SaLoginModel().setDevice(device));
+ // 获取按钮码
+ saBaseClientLoginUser.setButtonCodeList(clientLoginUserApi.getButtonCodeListListByUserId(saBaseClientLoginUser.getId()));
+ // 获取数据范围
+ saBaseClientLoginUser.setDataScopeList(Convert.toList(SaBaseClientLoginUser.DataScope.class,
+ loginUserApi.getPermissionListByUserId(saBaseClientLoginUser.getId(), null)));
+ // 获取权限码
+ saBaseClientLoginUser.setPermissionCodeList(saBaseClientLoginUser.getDataScopeList().stream()
+ .map(SaBaseClientLoginUser.DataScope::getApiUrl).collect(Collectors.toList()));
+ // 获取角色码
+ saBaseClientLoginUser.setRoleCodeList(clientLoginUserApi.getRoleCodeListByUserId(saBaseClientLoginUser.getId()));
+ // 缓存用户信息,此处使用TokenSession为了指定时间内无操作则自动下线
+ StpClientUtil.getTokenSession().set("loginUser", saBaseClientLoginUser);
+ // 返回token
+ return StpClientUtil.getTokenInfo().tokenValue;
+ }
+
+ /**
+ * 获取B端登录用户信息
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 15:59
+ **/
+ @Override
+ public SaBaseLoginUser getLoginUser() {
+ SaBaseLoginUser saBaseLoginUser = StpLoginUserUtil.getLoginUser();
+ saBaseLoginUser.setPassword(null);
+ saBaseLoginUser.setPermissionCodeList(null);
+ saBaseLoginUser.setDataScopeList(null);
+ return saBaseLoginUser;
+ }
+
+ /**
+ * 获取C端登录用户信息
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 15:59
+ **/
+ @Override
+ public SaBaseClientLoginUser getClientLoginUser() {
+ SaBaseClientLoginUser saBaseClientLoginUser = StpClientLoginUserUtil.getClientLoginUser();
+ saBaseClientLoginUser.setPassword(null);
+ saBaseClientLoginUser.setPermissionCodeList(null);
+ saBaseClientLoginUser.setDataScopeList(null);
+ return StpClientLoginUserUtil.getClientLoginUser();
+ }
+
+ @Override
+ public String doLoginById(String userId, String device, String type) {
+ // 根据id获取用户信息,根据B端或C端判断
+ if(SaClientTypeEnum.B.getValue().equals(type)) {
+ SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserById(userId);
+ if (ObjectUtil.isEmpty(saBaseLoginUser)) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
+ }
+ // 执行B端登录
+ return execLoginB(saBaseLoginUser, device);
+ } else {
+ SaBaseClientLoginUser saBaseClientLoginUser = clientLoginUserApi.getClientUserById(userId);
+ if (ObjectUtil.isEmpty(saBaseClientLoginUser)) {
+ throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
+ }
+ // 执行C端登录
+ return execLoginC(saBaseClientLoginUser, device);
+ }
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/controller/AuthSessionController.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/controller/AuthSessionController.java
new file mode 100644
index 00000000..f723ada9
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/controller/AuthSessionController.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.auth.modular.monitor.param.AuthExitSessionParam;
+import vip.xiaonuo.auth.modular.monitor.param.AuthExitTokenParam;
+import vip.xiaonuo.auth.modular.monitor.param.AuthSessionPageParam;
+import vip.xiaonuo.auth.modular.monitor.result.AuthSessionAnalysisResult;
+import vip.xiaonuo.auth.modular.monitor.result.AuthSessionPageResult;
+import vip.xiaonuo.auth.modular.monitor.service.AuthSessionService;
+import vip.xiaonuo.common.annotation.CommonLog;
+import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.common.pojo.CommonValidList;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * 会话治理控制器
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 15:20
+ **/
+@Api(tags = "会话治理控制器")
+@ApiSupport(author = "SNOWY_TEAM", order = 3)
+@RestController
+@Validated
+public class AuthSessionController {
+
+ @Resource
+ private AuthSessionService authSessionService;
+
+ /**
+ * 会话统计
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:28
+ */
+ @ApiOperationSupport(order = 1)
+ @ApiOperation("会话统计")
+ @GetMapping("/auth/session/analysis")
+ public CommonResult analysis() {
+ return CommonResult.data(authSessionService.analysis());
+ }
+
+ /**
+ * 查询B端会话
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:28
+ */
+ @ApiOperationSupport(order = 2)
+ @ApiOperation("查询B端会话")
+ @GetMapping("/auth/session/b/page")
+ public CommonResult> pageForB(AuthSessionPageParam authSessionPageParam) {
+ return CommonResult.data(authSessionService.pageForB(authSessionPageParam));
+ }
+
+ /**
+ * 查询C端会话
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:28
+ */
+ @ApiOperationSupport(order = 3)
+ @ApiOperation("查询C端会话")
+ @GetMapping("/auth/session/c/page")
+ public CommonResult> pageForC(AuthSessionPageParam authSessionPageParam) {
+ return CommonResult.data(authSessionService.pageForC(authSessionPageParam));
+ }
+
+ /**
+ * 强退B端会话
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 10:25
+ **/
+ @ApiOperationSupport(order = 4)
+ @ApiOperation("强退B端会话")
+ @CommonLog("强退B端会话")
+ @PostMapping("/auth/session/b/exit")
+ public CommonResult exitSessionForB(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
+ CommonValidList authExitSessionParamList) {
+ authSessionService.exitSessionForB(authExitSessionParamList);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 强退C端会话
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 10:25
+ **/
+ @ApiOperationSupport(order = 5)
+ @ApiOperation("强退C端会话")
+ @CommonLog("强退C端会话")
+ @PostMapping("/auth/session/c/exit")
+ public CommonResult exitSessionForC(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
+ CommonValidList authExitSessionParamList) {
+ authSessionService.exitSessionForC(authExitSessionParamList);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 强退B端token
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 10:25
+ **/
+ @ApiOperationSupport(order = 6)
+ @ApiOperation("强退B端token")
+ @CommonLog("强退B端token")
+ @PostMapping("/auth/token/b/exit")
+ public CommonResult exitTokenForB(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
+ CommonValidList authExitTokenParamList) {
+ authSessionService.exitTokenForB(authExitTokenParamList);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 强退C端token
+ *
+ * @author xuyuxiang
+ * @date 2021/10/12 10:25
+ **/
+ @ApiOperationSupport(order = 7)
+ @ApiOperation("强退C端token")
+ @CommonLog("强退C端token")
+ @PostMapping("/auth/token/c/exit")
+ public CommonResult exitTokenForC(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
+ CommonValidList authExitTokenParamList) {
+ authSessionService.exitTokenForC(authExitTokenParamList);
+ return CommonResult.ok();
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthExitSessionParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthExitSessionParam.java
new file mode 100644
index 00000000..0d55a40e
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthExitSessionParam.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * Session退出参数
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:28
+ */
+@Getter
+@Setter
+public class AuthExitSessionParam {
+
+ /** 用户id */
+ @ApiModelProperty(value = "用户id", required = true)
+ @NotBlank(message = "userId不能为空")
+ private String userId;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthExitTokenParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthExitTokenParam.java
new file mode 100644
index 00000000..2796103f
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthExitTokenParam.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * Token退出参数
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:28
+ */
+@Getter
+@Setter
+public class AuthExitTokenParam {
+
+ /** token值 */
+ @ApiModelProperty(value = "token值", required = true)
+ @NotBlank(message = "tokenValue不能为空")
+ private String tokenValue;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthSessionPageParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthSessionPageParam.java
new file mode 100644
index 00000000..606b6210
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/param/AuthSessionPageParam.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 会话查询参数
+ *
+ * @author xuyuxiang
+ * @date 2022/7/28 14:48
+ **/
+@Getter
+@Setter
+public class AuthSessionPageParam {
+
+ /** 用户id */
+ @ApiModelProperty(value = "用户id")
+ private String userId;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/result/AuthSessionAnalysisResult.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/result/AuthSessionAnalysisResult.java
new file mode 100644
index 00000000..55de7c02
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/result/AuthSessionAnalysisResult.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.result;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 会话统计结果
+ *
+ * @author xuyuxiang
+ * @date 2022/7/19 9:29
+ **/
+@Getter
+@Setter
+public class AuthSessionAnalysisResult {
+
+ /** 当前会话总数量 */
+ @ApiModelProperty(value = "当前会话总数量", position = 1)
+ private String currentSessionTotalCount;
+
+ /** 最大签发令牌数 */
+ @ApiModelProperty(value = "最大签发令牌数", position = 2)
+ private String maxTokenCount;
+
+ /** 最近1小时会话数 */
+ @ApiModelProperty(value = "最近1小时会话数", position = 3)
+ private String oneHourNewlyAdded;
+
+ /** BC端会话比例 */
+ @ApiModelProperty(value = "BC端会话比例", position = 4)
+ private String proportionOfBAndC;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/result/AuthSessionPageResult.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/result/AuthSessionPageResult.java
new file mode 100644
index 00000000..68374f83
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/result/AuthSessionPageResult.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.result;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * B端会话结果集
+ *
+ * @author xuyuxiang
+ * @date 2022/7/28 14:46
+ **/
+@Getter
+@Setter
+public class AuthSessionPageResult {
+
+ /** id */
+ @ApiModelProperty(value = "id", position = 1)
+ private String id;
+
+ /** 头像 */
+ @ApiModelProperty(value = "头像", position = 2)
+ private String avatar;
+
+ /** 账号 */
+ @ApiModelProperty(value = "账号", position = 3)
+ private String account;
+
+ /** 姓名 */
+ @ApiModelProperty(value = "姓名", position = 4)
+ private String name;
+
+ /** 上次登录ip */
+ @ApiModelProperty(value = "上次登录ip", position = 5)
+ private String lastLoginIp;
+
+ /** 上次登录地点 */
+ @ApiModelProperty(value = "上次登录地点", position = 6)
+ private String lastLoginAddress;
+
+ /** 上次登录时间 */
+ @ApiModelProperty(value = "上次登录时间", position = 7)
+ private Date lastLoginTime;
+
+ /** 上次登录设备 */
+ @ApiModelProperty(value = "上次登录设备", position = 8)
+ private String lastLoginDevice;
+
+ /** 最新登录ip */
+ @ApiModelProperty(value = "最新登录ip", position = 9)
+ private String latestLoginIp;
+
+ /** 最新登录地点 */
+ @ApiModelProperty(value = "最新登录地点", position = 10)
+ private String latestLoginAddress;
+
+ /** 最新登录时间 */
+ @ApiModelProperty(value = "最新登录时间", position = 11)
+ private Date latestLoginTime;
+
+ /** 最新登录设备 */
+ @ApiModelProperty(value = "最新登录设备", position = 12)
+ private String latestLoginDevice;
+
+ /** 会话id */
+ @ApiModelProperty(value = "会话id", position = 13)
+ private String sessionId;
+
+ /** 会话创建时间 */
+ @ApiModelProperty(value = "会话创建时间", position = 14)
+ private Date sessionCreateTime;
+
+ /** 会话剩余有效期 */
+ @ApiModelProperty(value = "会话剩余有效期", position = 15)
+ private String sessionTimeout;
+
+ /** 令牌数量 */
+ @ApiModelProperty(value = "令牌数量", position = 16)
+ private Integer tokenCount;
+
+ /** 令牌信息集合 */
+ @ApiModelProperty(value = "令牌信息集合", position = 17)
+ private List tokenSignList;
+
+ /**
+ * 令牌信息类
+ *
+ * @author xuyuxiang
+ * @date 2022/7/28 15:04
+ **/
+ @Getter
+ @Setter
+ public static class TokenSignInfo {
+
+ /** token值 */
+ @ApiModelProperty(value = "token值", position = 1)
+ private String tokenValue;
+
+ /** 登录设备 */
+ @ApiModelProperty(value = "登录设备", position = 2)
+ private String tokenDevice;
+
+ /** token剩余有效期 */
+ @ApiModelProperty(value = "token剩余有效期", position = 3)
+ private String tokenTimeout;
+
+ /** token剩余有效期百分比 */
+ @ApiModelProperty(value = "token剩余有效期百分比", position = 4)
+ private Double tokenTimeoutPercent;
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/service/AuthSessionService.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/service/AuthSessionService.java
new file mode 100644
index 00000000..5d9fccde
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/service/AuthSessionService.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import vip.xiaonuo.auth.modular.monitor.param.AuthExitSessionParam;
+import vip.xiaonuo.auth.modular.monitor.param.AuthExitTokenParam;
+import vip.xiaonuo.auth.modular.monitor.param.AuthSessionPageParam;
+import vip.xiaonuo.auth.modular.monitor.result.AuthSessionAnalysisResult;
+import vip.xiaonuo.auth.modular.monitor.result.AuthSessionPageResult;
+
+import java.util.List;
+
+/**
+ * 会话治理Service接口
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 15:46S
+ **/
+public interface AuthSessionService {
+
+ /**
+ * 会话统计
+ *
+ * @author xuyuxiang
+ * @date 2022/7/19 9:33
+ **/
+ AuthSessionAnalysisResult analysis();
+
+ /**
+ * 查询B端会话
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:30
+ */
+ Page pageForB(AuthSessionPageParam authSessionPageParam);
+
+ /**
+ * 查询C端会话
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:30
+ */
+ Page pageForC(AuthSessionPageParam authSessionPageParam);
+
+ /**
+ * 强退B端会话
+ *
+ * @author xuyuxiang
+ * @date 2022/6/29 21:47
+ */
+ void exitSessionForB(List authExitSessionParamList);
+
+ /**
+ * 强退C端会话
+ *
+ * @author xuyuxiang
+ * @date 2022/6/29 21:47
+ */
+ void exitSessionForC(List authExitSessionParamList);
+
+ /**
+ * 强退B端token
+ *
+ * @author xuyuxiang
+ * @date 2022/6/29 21:47
+ */
+ void exitTokenForB(List authExitTokenParamList);
+
+ /**
+ * 强退C端token
+ *
+ * @author xuyuxiang
+ * @date 2022/6/29 21:47
+ */
+ void exitTokenForC(List authExitTokenParamList);
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/service/impl/AuthSessionServiceImpl.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/service/impl/AuthSessionServiceImpl.java
new file mode 100644
index 00000000..4f40262d
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/monitor/service/impl/AuthSessionServiceImpl.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.monitor.service.impl;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.DateField;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.stereotype.Service;
+import vip.xiaonuo.auth.api.SaBaseLoginUserApi;
+import vip.xiaonuo.auth.core.util.StpClientUtil;
+import vip.xiaonuo.auth.modular.monitor.param.AuthExitSessionParam;
+import vip.xiaonuo.auth.modular.monitor.param.AuthExitTokenParam;
+import vip.xiaonuo.auth.modular.monitor.param.AuthSessionPageParam;
+import vip.xiaonuo.auth.modular.monitor.result.AuthSessionAnalysisResult;
+import vip.xiaonuo.auth.modular.monitor.result.AuthSessionPageResult;
+import vip.xiaonuo.auth.modular.monitor.service.AuthSessionService;
+import vip.xiaonuo.common.page.CommonPageRequest;
+import vip.xiaonuo.common.util.CommonTimeFormatUtil;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 会话治理Service接口实现类
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 22:25
+ */
+@Service
+public class AuthSessionServiceImpl implements AuthSessionService {
+
+ @Resource(name = "loginUserApi")
+ private SaBaseLoginUserApi loginUserApi;
+
+ @Resource(name = "clientLoginUserApi")
+ private SaBaseLoginUserApi clientLoginUserApi;
+
+ @Override
+ public AuthSessionAnalysisResult analysis() {
+ AuthSessionAnalysisResult authSessionAnalysisResult = new AuthSessionAnalysisResult();
+ List sessionListB = StpUtil.searchSessionId("", -1, -1).stream().map(sessionId -> {
+ JSONObject jsonObject = JSONUtil.createObj();
+ String userId = StrUtil.split(sessionId, StrUtil.COLON).get(3);
+ SaSession saSession = StpUtil.getSessionByLoginId(userId, false);
+ int tokenCount = saSession.getTokenSignList().size();
+ long createTime = saSession.getCreateTime();
+ jsonObject.set("userId", userId);
+ jsonObject.set("tokenCount", tokenCount);
+ jsonObject.set("createTime", DateTime.of(createTime));
+ return jsonObject;
+ }).collect(Collectors.toList());
+
+ List sessionListC = StpClientUtil.searchSessionId("", -1, -1).stream().map(sessionId -> {
+ JSONObject jsonObject = JSONUtil.createObj();
+ String userId = StrUtil.split(sessionId, StrUtil.COLON).get(3);
+ SaSession saSession = StpClientUtil.getSessionByLoginId(userId, false);
+ int tokenCount = saSession.getTokenSignList().size();
+ long createTime = saSession.getCreateTime();
+ jsonObject.set("userId", userId);
+ jsonObject.set("tokenCount", tokenCount);
+ jsonObject.set("createTime", DateTime.of(createTime));
+ return jsonObject;
+ }).collect(Collectors.toList());
+
+ List tokenCountList = CollectionUtil.newArrayList();
+ tokenCountList.addAll(sessionListB.stream().map(jsonObject -> jsonObject.getInt("tokenCount")).collect(Collectors.toList()));
+ tokenCountList.addAll(sessionListC.stream().map(jsonObject -> jsonObject.getInt("tokenCount")).collect(Collectors.toList()));
+ CollectionUtil.sort(tokenCountList, Comparator.comparingInt(Integer::intValue));
+ int currentSessionTotalCount = sessionListB.size() + sessionListC.size();
+ authSessionAnalysisResult.setCurrentSessionTotalCount(Convert.toStr(currentSessionTotalCount));
+ authSessionAnalysisResult.setMaxTokenCount(Convert.toStr(tokenCountList.get(tokenCountList.size() - 1)));
+ List sessionCreateTimeList = CollectionUtil.newArrayList();
+ sessionCreateTimeList.addAll(sessionListB.stream().map(jsonObject -> jsonObject.getDate("createTime")).collect(Collectors.toList()));
+ sessionCreateTimeList.addAll(sessionListC.stream().map(jsonObject -> jsonObject.getDate("createTime")).collect(Collectors.toList()));
+ System.out.println(sessionCreateTimeList);
+ DateTime oneHourAgo = DateUtil.offset(DateTime.now(), DateField.HOUR, -1);
+ authSessionAnalysisResult.setOneHourNewlyAdded(Convert.toStr(sessionCreateTimeList.stream().filter(date -> DateUtil.compare(oneHourAgo, date) <= 0).count()));
+ authSessionAnalysisResult.setProportionOfBAndC(sessionListB.size() + StrUtil.SLASH + sessionListC.size());
+ return authSessionAnalysisResult;
+ }
+
+ @Override
+ public Page pageForB(AuthSessionPageParam authSessionPageParam) {
+ Page defaultPage = CommonPageRequest.defaultPage();
+ long current = defaultPage.getCurrent();
+ int total = StpUtil.searchSessionId("", -1, Convert.toInt(defaultPage.getSize())).size();
+ if(ObjectUtil.isNotEmpty(total)) {
+ defaultPage = new Page<>(current, defaultPage.getSize(), total);
+ String keyword = "";
+ if(ObjectUtil.isNotEmpty(authSessionPageParam.getUserId())) {
+ keyword = authSessionPageParam.getUserId();
+ }
+ List userIdList = StpUtil.searchSessionId(keyword,
+ Convert.toInt((current - 1) * defaultPage.getSize()),
+ Convert.toInt(defaultPage.getSize())).stream().map(sessionId ->
+ StrUtil.split(sessionId, StrUtil.COLON).get(3)).collect(Collectors.toList());
+ if (ObjectUtil.isNotEmpty(userIdList)) {
+ List authSessionPageResultList = loginUserApi.listUserByUserIdList(userIdList).stream().map(userJsonObject -> {
+ SaSession saSession = StpUtil.getSessionByLoginId(userJsonObject.getStr("id"), false);
+ AuthSessionPageResult authSessionPageResult = JSONUtil.toBean(userJsonObject, AuthSessionPageResult.class);
+ authSessionPageResult.setSessionId(saSession.getId());
+ authSessionPageResult.setSessionCreateTime(DateTime.of(saSession.getCreateTime()));
+ long sessionTimeOut = saSession.getTimeout();
+ if(sessionTimeOut == -1) {
+ authSessionPageResult.setSessionTimeout("永久");
+ } else {
+ authSessionPageResult.setSessionTimeout(CommonTimeFormatUtil.formatSeconds(saSession.getTimeout()));
+ }
+ List tokenInfoList = saSession.getTokenSignList().stream().map(tokenSign -> {
+ AuthSessionPageResult.TokenSignInfo tokenSignInfo = new AuthSessionPageResult.TokenSignInfo();
+ tokenSignInfo.setTokenValue(tokenSign.getValue());
+ tokenSignInfo.setTokenDevice(tokenSign.getDevice());
+ long tokenTimeout = SaManager.getSaTokenDao().getTimeout(StpUtil.stpLogic.splicingKeyTokenValue(tokenSign.getValue()));
+ long tokenTimeoutConfig = StpUtil.stpLogic.getConfig().getTimeout();
+ if(tokenTimeout == -1) {
+ tokenSignInfo.setTokenTimeout("永久");
+ tokenSignInfo.setTokenTimeoutPercent(100d);
+ } else {
+ tokenSignInfo.setTokenTimeout(CommonTimeFormatUtil.formatSeconds(SaManager.getSaTokenDao()
+ .getTimeout(StpUtil.stpLogic.splicingKeyTokenValue(tokenSign.getValue()))));
+ if(tokenTimeoutConfig == -1) {
+ tokenSignInfo.setTokenTimeoutPercent(0d);
+ } else {
+ tokenSignInfo.setTokenTimeoutPercent(NumberUtil.div(tokenTimeout, tokenTimeoutConfig));
+ }
+ }
+ return tokenSignInfo;
+ }).collect(Collectors.toList());
+ authSessionPageResult.setTokenCount(tokenInfoList.size());
+ authSessionPageResult.setTokenSignList(tokenInfoList);
+ return authSessionPageResult;
+ }).collect(Collectors.toList());
+ defaultPage.setRecords(authSessionPageResultList);
+ }
+ }
+ return defaultPage;
+ }
+
+ @Override
+ public Page pageForC(AuthSessionPageParam authSessionPageParam) {
+ Page defaultPage = CommonPageRequest.defaultPage();
+ long current = defaultPage.getCurrent();
+ int total = StpClientUtil.searchSessionId("", -1, Convert.toInt(defaultPage.getSize())).size();
+ if(ObjectUtil.isNotEmpty(total)) {
+ defaultPage = new Page<>(current, defaultPage.getSize(), total);
+ String keyword = "";
+ if(ObjectUtil.isNotEmpty(authSessionPageParam.getUserId())) {
+ keyword = authSessionPageParam.getUserId();
+ }
+ List userIdList = StpClientUtil.searchSessionId(keyword,
+ Convert.toInt((current - 1) * defaultPage.getSize()),
+ Convert.toInt(defaultPage.getSize())).stream().map(sessionId ->
+ StrUtil.split(sessionId, StrUtil.COLON).get(3)).collect(Collectors.toList());
+ if (ObjectUtil.isNotEmpty(userIdList)) {
+ List authSessionPageResultList = loginUserApi.listUserByUserIdList(userIdList).stream().map(userJsonObject -> {
+ SaSession saSession = StpClientUtil.getSessionByLoginId(userJsonObject.getStr("id"), false);
+ AuthSessionPageResult authSessionPageResult = JSONUtil.toBean(userJsonObject, AuthSessionPageResult.class);
+ authSessionPageResult.setSessionId(saSession.getId());
+ authSessionPageResult.setSessionCreateTime(DateTime.of(saSession.getCreateTime()));
+ long sessionTimeOut = saSession.getTimeout();
+ if(sessionTimeOut == -1) {
+ authSessionPageResult.setSessionTimeout("永久");
+ } else {
+ authSessionPageResult.setSessionTimeout(CommonTimeFormatUtil.formatSeconds(saSession.getTimeout()));
+ }
+ List tokenInfoList = saSession.getTokenSignList().stream().map(tokenSign -> {
+ AuthSessionPageResult.TokenSignInfo tokenSignInfo = new AuthSessionPageResult.TokenSignInfo();
+ tokenSignInfo.setTokenValue(tokenSign.getValue());
+ tokenSignInfo.setTokenDevice(tokenSign.getDevice());
+ long tokenTimeout = SaManager.getSaTokenDao().getTimeout(StpClientUtil.stpLogic.splicingKeyTokenValue(tokenSign.getValue()));
+ long tokenTimeoutConfig = StpClientUtil.stpLogic.getConfig().getTimeout();
+ if(tokenTimeout == -1) {
+ tokenSignInfo.setTokenTimeout("永久");
+ tokenSignInfo.setTokenTimeoutPercent(100d);
+ } else {
+ tokenSignInfo.setTokenTimeout(CommonTimeFormatUtil.formatSeconds(SaManager.getSaTokenDao()
+ .getTimeout(StpClientUtil.stpLogic.splicingKeyTokenValue(tokenSign.getValue()))));
+ if(tokenTimeoutConfig == -1) {
+ tokenSignInfo.setTokenTimeoutPercent(0d);
+ } else {
+ tokenSignInfo.setTokenTimeoutPercent(NumberUtil.div(tokenTimeout, tokenTimeoutConfig));
+ }
+ }
+ return tokenSignInfo;
+ }).collect(Collectors.toList());
+ authSessionPageResult.setTokenCount(tokenInfoList.size());
+ authSessionPageResult.setTokenSignList(tokenInfoList);
+ return authSessionPageResult;
+ }).collect(Collectors.toList());
+ defaultPage.setRecords(authSessionPageResultList);
+ }
+ }
+ return defaultPage;
+ }
+
+ @Override
+ public void exitSessionForB(List authExitSessionParamList) {
+ authExitSessionParamList.forEach(authExitSessionParam -> StpUtil.logout(authExitSessionParam.getUserId()));
+ }
+
+ @Override
+ public void exitSessionForC(List authExitSessionParamList) {
+ authExitSessionParamList.forEach(authExitSessionParam -> StpClientUtil.logout(authExitSessionParam.getUserId()));
+ }
+
+ @Override
+ public void exitTokenForB(List authExitTokenParamList) {
+ authExitTokenParamList.forEach(authExitTokenParam -> StpUtil.logoutByTokenValue(authExitTokenParam.getTokenValue()));
+ }
+
+ @Override
+ public void exitTokenForC(List authExitTokenParamList) {
+ authExitTokenParamList.forEach(authExitTokenParam -> StpClientUtil.logoutByTokenValue(authExitTokenParam.getTokenValue()));
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/controller/AuthThirdController.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/controller/AuthThirdController.java
new file mode 100644
index 00000000..2ac83200
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/controller/AuthThirdController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import me.zhyd.oauth.model.AuthCallback;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.auth.modular.third.entity.AuthThirdUser;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdCallbackParam;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdRenderParam;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdUserPageParam;
+import vip.xiaonuo.auth.modular.third.result.AuthThirdRenderResult;
+import vip.xiaonuo.auth.modular.third.service.AuthThirdService;
+import vip.xiaonuo.common.pojo.CommonResult;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+/**
+ * 第三方登录控制器
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:18
+ **/
+@Api(tags = "三方登录控制器")
+@ApiSupport(author = "SNOWY_TEAM", order = 5)
+@RestController
+@Validated
+public class AuthThirdController {
+
+ @Resource
+ private AuthThirdService authThirdService;
+
+ /**
+ * 第三方登录页面渲染
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:19
+ **/
+ @ApiOperationSupport(order = 1)
+ @ApiOperation("第三方登录页面渲染")
+ @GetMapping("/auth/third/render")
+ public CommonResult render(@Valid AuthThirdRenderParam authThirdRenderParam) {
+ return CommonResult.data(authThirdService.render(authThirdRenderParam));
+ }
+
+ /**
+ * 第三方登录授权回调
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:42
+ **/
+ @ApiOperationSupport(order = 2)
+ @ApiOperation("第三方登录授权回调")
+ @GetMapping("/auth/third/callback")
+ public CommonResult callback(@Valid AuthThirdCallbackParam authThirdCallbackParam, AuthCallback authCallback) {
+ return CommonResult.data(authThirdService.callback(authThirdCallbackParam, authCallback));
+ }
+
+ /**
+ * 获取三方用户分页
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 3)
+ @ApiOperation("获取三方用户分页")
+ @GetMapping("/auth/third/page")
+ public CommonResult> page(AuthThirdUserPageParam authThirdUserPageParam) {
+ return CommonResult.data(authThirdService.page(authThirdUserPageParam));
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/entity/AuthThirdUser.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/entity/AuthThirdUser.java
new file mode 100644
index 00000000..1300a35b
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/entity/AuthThirdUser.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import vip.xiaonuo.common.pojo.CommonEntity;
+
+/**
+ * 第三放登录实体
+ *
+ * @author xuyuxiang
+ * @date 2022/7/9 14:29
+ */
+@Getter
+@Setter
+@TableName("AUTH_THIRD_USER")
+public class AuthThirdUser extends CommonEntity {
+
+ /** id */
+ @ApiModelProperty(value = "id", position = 1)
+ private String id;
+
+ /** 三方用户id */
+ @ApiModelProperty(value = "三方用户id", position = 2)
+ private String thirdId;
+
+ /** 系统用户id */
+ @ApiModelProperty(value = "系统用户id", position = 3)
+ private String userId;
+
+ /** 头像 */
+ @ApiModelProperty(value = "头像", position = 4)
+ @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
+ private String avatar;
+
+ /** 姓名 */
+ @ApiModelProperty(value = "姓名", position = 5)
+ private String name;
+
+ /** 昵称 */
+ @ApiModelProperty(value = "昵称", position = 6)
+ @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
+ private String nickname;
+
+ /** 性别 */
+ @ApiModelProperty(value = "性别", position = 7)
+ @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
+ private String gender;
+
+ /** 分类 */
+ @ApiModelProperty(value = "分类", position = 8)
+ private String category;
+
+ /** 扩展信息 */
+ @ApiModelProperty(value = "扩展信息", position = 9)
+ @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
+ private String extJson;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java
new file mode 100644
index 00000000..8d5b609a
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.enums;
+
+import lombok.Getter;
+import vip.xiaonuo.common.exception.CommonException;
+
+/**
+ * 第三方登录平台枚举
+ *
+ * @author xuyuxiang
+ * @date 2021/10/11 14:02
+ **/
+@Getter
+public enum AuthThirdPlatformEnum {
+
+ /**
+ * GITEE
+ */
+ GITEE("GITEE"),
+
+ /**
+ * WECHAT
+ */
+ WECHAT("WECHAT");
+
+ private final String value;
+
+ AuthThirdPlatformEnum(String value) {
+ this.value = value;
+ }
+
+ public static void validate(String value) {
+ boolean flag = GITEE.getValue().equals(value) || WECHAT.getValue().equals(value);
+ if(!flag) {
+ throw new CommonException("不支持的第三方平台:{}", value);
+ }
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/mapper/AuthThirdMapper.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/mapper/AuthThirdMapper.java
new file mode 100644
index 00000000..e0ccb4dd
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/mapper/AuthThirdMapper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import vip.xiaonuo.auth.modular.third.entity.AuthThirdUser;
+
+/**
+ * 第三方登录Mapper接口
+ *
+ * @author xuyuxiang
+ * @date 2022/7/9 14:35
+ */
+public interface AuthThirdMapper extends BaseMapper {
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/mapper/mapping/AuthThirdMapper.xml b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/mapper/mapping/AuthThirdMapper.xml
new file mode 100644
index 00000000..f2716d5a
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/mapper/mapping/AuthThirdMapper.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdCallbackParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdCallbackParam.java
new file mode 100644
index 00000000..9503cf4e
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdCallbackParam.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 第三方登录回调参数
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 20:38
+ */
+@Getter
+@Setter
+public class AuthThirdCallbackParam {
+
+ /** 第三方平台标识 */
+ @ApiModelProperty(value = "第三方平台标识", required = true, position = 1)
+ @NotBlank(message = "platform不能为空")
+ private String platform;
+
+ /** 第三方回调code */
+ @ApiModelProperty(value = "第三方回调code", required = true, position = 2)
+ @NotBlank(message = "code不能为空")
+ private String code;
+
+ /** 第三方回调state */
+ @ApiModelProperty(value = "第三方回调state", required = true, position = 3)
+ @NotBlank(message = "state不能为空")
+ private String state;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdRenderParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdRenderParam.java
new file mode 100644
index 00000000..be833212
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdRenderParam.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 第三方登录授权参数
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 20:38
+ */
+@Getter
+@Setter
+public class AuthThirdRenderParam {
+
+ /** 第三方平台标识 */
+ @ApiModelProperty(value = "第三方平台标识", required = true)
+ @NotBlank(message = "platform不能为空")
+ private String platform;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdUserPageParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdUserPageParam.java
new file mode 100644
index 00000000..1178ac60
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdUserPageParam.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 三方用户查询参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class AuthThirdUserPageParam {
+
+ /** 当前页 */
+ @ApiModelProperty(value = "当前页码")
+ private Integer current;
+
+ /** 每页条数 */
+ @ApiModelProperty(value = "每页条数")
+ private Integer size;
+
+ /** 排序字段 */
+ @ApiModelProperty(value = "排序字段,字段驼峰名称,如:userName")
+ private String sortField;
+
+ /** 排序方式 */
+ @ApiModelProperty(value = "排序方式,升序:ASCEND;降序:DESCEND")
+ private String sortOrder;
+
+ /** 三方用户分类 */
+ @ApiModelProperty(value = "分类")
+ private String category;
+
+ /** 用户名或昵称关键词 */
+ @ApiModelProperty(value = "用户名或昵称关键词")
+ private String searchKey;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/result/AuthThirdRenderResult.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/result/AuthThirdRenderResult.java
new file mode 100644
index 00000000..3a479e2a
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/result/AuthThirdRenderResult.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.result;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 第三方登录授权结果
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 20:39
+ */
+@Getter
+@Setter
+public class AuthThirdRenderResult {
+
+ /** 授权地址 */
+ @ApiModelProperty(value = "授权地址")
+ private String authorizeUrl;
+
+ /** 状态码 */
+ @ApiModelProperty(value = "状态码")
+ private String state;
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/AuthThirdService.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/AuthThirdService.java
new file mode 100644
index 00000000..c53f6660
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/AuthThirdService.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import me.zhyd.oauth.model.AuthCallback;
+import vip.xiaonuo.auth.modular.third.entity.AuthThirdUser;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdCallbackParam;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdRenderParam;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdUserPageParam;
+import vip.xiaonuo.auth.modular.third.result.AuthThirdRenderResult;
+
+/**
+ * 第三方登录Service接口
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:20
+ **/
+public interface AuthThirdService extends IService {
+
+ /**
+ * 第三方登录页面渲染,返回授权结果
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:37
+ **/
+ AuthThirdRenderResult render(AuthThirdRenderParam authThirdRenderParam);
+
+ /**
+ * 第三方登录授权回调,返回登录token
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:42
+ **/
+ String callback(AuthThirdCallbackParam authThirdCallbackParam, AuthCallback authCallback);
+
+ /**
+ * 获取三方用户分页
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ Page page(AuthThirdUserPageParam authThirdUserPageParam);
+}
diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java
new file mode 100644
index 00000000..e43b1668
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.modular.third.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xkcoding.http.HttpUtil;
+import com.xkcoding.http.support.hutool.HutoolImpl;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthGiteeRequest;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.request.AuthWeChatOpenRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import vip.xiaonuo.auth.api.SaBaseLoginUserApi;
+import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
+import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
+import vip.xiaonuo.auth.modular.login.enums.AuthDeviceTypeEnum;
+import vip.xiaonuo.auth.modular.login.service.AuthService;
+import vip.xiaonuo.auth.modular.third.entity.AuthThirdUser;
+import vip.xiaonuo.auth.modular.third.enums.AuthThirdPlatformEnum;
+import vip.xiaonuo.auth.modular.third.mapper.AuthThirdMapper;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdCallbackParam;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdRenderParam;
+import vip.xiaonuo.auth.modular.third.param.AuthThirdUserPageParam;
+import vip.xiaonuo.auth.modular.third.result.AuthThirdRenderResult;
+import vip.xiaonuo.auth.modular.third.service.AuthThirdService;
+import vip.xiaonuo.common.enums.CommonSortOrderEnum;
+import vip.xiaonuo.common.exception.CommonException;
+import vip.xiaonuo.common.page.CommonPageRequest;
+import vip.xiaonuo.dev.api.DevConfigApi;
+
+import javax.annotation.Resource;
+
+/**
+ * 第三方登录Service接口实现类
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:20
+ **/
+@Service
+public class AuthThirdServiceImpl extends ServiceImpl implements AuthThirdService {
+
+ private static final String SNOWY_THIRD_GITEE_CLIENT_ID_KEY = "SNOWY_THIRD_GITEE_CLIENT_ID";
+ private static final String SNOWY_THIRD_GITEE_CLIENT_SECRET_KEY = "SNOWY_THIRD_GITEE_CLIENT_SECRET";
+ private static final String SNOWY_THIRD_GITEE_REDIRECT_URL_KEY = "SNOWY_THIRD_GITEE_REDIRECT_URL";
+
+ private static final String SNOWY_THIRD_WECHAT_CLIENT_ID_KEY = "SNOWY_THIRD_WECHAT_CLIENT_ID";
+ private static final String SNOWY_THIRD_WECHAT_CLIENT_SECRET_KEY = "SNOWY_THIRD_WECHAT_CLIENT_SECRET";
+ private static final String SNOWY_THIRD_WECHAT_REDIRECT_URL_KEY = "SNOWY_THIRD_WECHAT_REDIRECT_URL";
+
+ @Resource
+ private DevConfigApi devConfigApi;
+
+ @Resource
+ private AuthService authService;
+
+ @Resource(name = "loginUserApi")
+ private SaBaseLoginUserApi loginUserApi;
+
+ @Resource(name = "clientLoginUserApi")
+ private SaBaseLoginUserApi clientLoginUserApi;
+
+ @Override
+ public AuthThirdRenderResult render(AuthThirdRenderParam authThirdRenderParam) {
+
+ // 获取请求
+ AuthRequest authRequest = this.getAuthRequest(authThirdRenderParam.getPlatform());
+
+ // 获取状态
+ String state = AuthStateUtils.createState();
+
+ // 构造授权地址
+ String authorizeUrl = authRequest.authorize(state);
+
+ // 构造结果
+ AuthThirdRenderResult authThirdRenderResult = new AuthThirdRenderResult();
+
+ // 返回授权地址
+ authThirdRenderResult.setAuthorizeUrl(authorizeUrl);
+
+ // 返回状态码
+ authThirdRenderResult.setState(state);
+ return authThirdRenderResult;
+ }
+
+ @SuppressWarnings("ALL")
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public String callback(AuthThirdCallbackParam authThirdCallbackParam, AuthCallback authCallback) {
+
+ // 获取请求
+ AuthRequest authRequest = this.getAuthRequest(authThirdCallbackParam.getPlatform());
+
+ // 执行请求
+ AuthResponse authResponse = authRequest.login(authCallback);
+ if (authResponse.ok()) {
+
+ // 授权的用户信息
+ AuthUser authUser = authResponse.getData();
+
+ // 获取第三方用户id
+ String uuid = authUser.getUuid();
+
+ // 获取第三方用户来源
+ String source = authUser.getSource();
+
+ // 根据第三方用户id和用户来源获取用户信息
+ AuthThirdUser authThirdUser = this.getOne(new LambdaQueryWrapper().eq(AuthThirdUser::getThirdId, uuid)
+ .eq(AuthThirdUser::getCategory, source));
+
+ // 定义系统用户id
+ String userId;
+ if(ObjectUtil.isEmpty(authThirdUser)) {
+
+ // 如果用户不存在,则绑定用户并登录
+ userId = this.bindUser(authUser);
+ } else {
+ // 否则直接获取用户id登录
+ userId = authThirdUser.getUserId();
+ }
+ // TODO 此处使用PC端执行B端登录,返回token
+ return authService.doLoginById(userId, AuthDeviceTypeEnum.PC.getValue(), SaClientTypeEnum.B.getValue());
+ } else {
+ throw new CommonException("第三方登录授权回调失败,原因:{}", authResponse.getMsg());
+ }
+ }
+
+ @Override
+ public Page page(AuthThirdUserPageParam authThirdUserPageParam) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if(ObjectUtil.isNotEmpty(authThirdUserPageParam.getCategory())) {
+ queryWrapper.lambda().eq(AuthThirdUser::getCategory, authThirdUserPageParam.getCategory());
+ }
+ if(ObjectUtil.isNotEmpty(authThirdUserPageParam.getSearchKey())) {
+ queryWrapper.and(q -> q.lambda().like(AuthThirdUser::getName, authThirdUserPageParam.getSearchKey())
+ .or().like(AuthThirdUser::getNickname, authThirdUserPageParam.getSearchKey()));
+ }
+ if(ObjectUtil.isAllNotEmpty(authThirdUserPageParam.getSortField(), authThirdUserPageParam.getSortOrder())) {
+ CommonSortOrderEnum.validate(authThirdUserPageParam.getSortOrder());
+ queryWrapper.orderBy(true, authThirdUserPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
+ StrUtil.toUnderlineCase(authThirdUserPageParam.getSortField()));
+ } else {
+ queryWrapper.lambda().orderByDesc(AuthThirdUser::getCreateTime);
+ }
+ return this.page(CommonPageRequest.defaultPage(), queryWrapper);
+ }
+
+ /**
+ * 绑定用户并返回用户id
+ *
+ * @author xuyuxiang
+ * @date 2022/7/9 14:58
+ */
+ private String bindUser(AuthUser authUser) {
+ // TODO 此处固定绑定超管
+ SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserByAccount("admin");
+ if(ObjectUtil.isEmpty(saBaseLoginUser)) {
+ throw new CommonException("第三方登录失败,无法绑定账号admin,原因:账户admin不存在");
+ }
+ AuthThirdUser authThirdUser = new AuthThirdUser();
+ authThirdUser.setThirdId(authUser.getUuid());
+ authThirdUser.setUserId(saBaseLoginUser.getId());
+ authThirdUser.setAvatar(authUser.getAvatar());
+ authThirdUser.setName(authUser.getUsername());
+ authThirdUser.setNickname(authUser.getNickname());
+ authThirdUser.setGender(authUser.getGender().getDesc());
+ authThirdUser.setCategory(authUser.getSource());
+ authThirdUser.setExtJson(JSONUtil.toJsonStr(authUser.getRawUserInfo()));
+ this.save(authThirdUser);
+ return authThirdUser.getUserId();
+ }
+
+ /**
+ * 创建授权请求
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 16:48
+ **/
+ private AuthRequest getAuthRequest(String source) {
+ AuthRequest authRequest = null;
+ source = source.toUpperCase();
+ HttpUtil.setHttp(new HutoolImpl());
+ AuthThirdPlatformEnum.validate(source);
+ if (source.equals(AuthThirdPlatformEnum.GITEE.getValue())) {
+ // GITEE登录
+ authRequest = new AuthGiteeRequest(AuthConfig.builder()
+ .clientId(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_CLIENT_ID_KEY))
+ .clientSecret(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_CLIENT_SECRET_KEY))
+ .redirectUri(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_REDIRECT_URL_KEY))
+ .build());
+ }
+ if(source.equals(AuthThirdPlatformEnum.WECHAT.getValue())){
+ // 微信登录
+ authRequest = new AuthWeChatOpenRequest(AuthConfig.builder()
+ .clientId(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_CLIENT_ID_KEY))
+ .clientSecret(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_CLIENT_SECRET_KEY))
+ .redirectUri(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_REDIRECT_URL_KEY))
+ .build());
+ }
+ return authRequest;
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/README.md b/snowy-plugin/snowy-plugin-biz/README.md
new file mode 100644
index 00000000..a6f09021
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/README.md
@@ -0,0 +1 @@
+# 业务功能插件
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-biz/pom.xml b/snowy-plugin/snowy-plugin-biz/pom.xml
new file mode 100644
index 00000000..30b9adce
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+
+ vip.xiaonuo
+ snowy-plugin
+ 2.0.0
+
+
+ snowy-plugin-biz
+ jar
+ 业务功能插件
+
+
+
+
+ vip.xiaonuo
+ snowy-plugin-biz-api
+ ${project.parent.version}
+
+
+
+
+ vip.xiaonuo
+ snowy-plugin-auth-api
+ ${project.parent.version}
+
+
+
+
+ vip.xiaonuo
+ snowy-plugin-sys-api
+ ${project.parent.version}
+
+
+
+
+ vip.xiaonuo
+ snowy-plugin-dev-api
+ ${project.parent.version}
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/core/config/BizConfigure.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/core/config/BizConfigure.java
new file mode 100644
index 00000000..778f2660
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/core/config/BizConfigure.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.core.config;
+
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.bind.annotation.RequestMethod;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import vip.xiaonuo.common.pojo.CommonResult;
+
+import javax.annotation.Resource;
+
+/**
+ * 业务相关配置
+ *
+ * @author xuyuxiang
+ * @date 2022/7/7 16:18
+ **/
+@Configuration
+public class BizConfigure {
+
+ @Resource
+ private OpenApiExtensionResolver openApiExtensionResolver;
+
+ /**
+ * API文档分组配置
+ *
+ * @author xuyuxiang
+ * @date 2022/7/7 16:18
+ **/
+ @Bean(value = "bizDocApi")
+ public Docket bizDocApi() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(new ApiInfoBuilder()
+ .title("业务功能BIZ")
+ .description("业务功能BIZ")
+ .termsOfServiceUrl("https://www.xiaonuo.vip")
+ .contact(new Contact("SNOWY_TEAM","https://www.xiaonuo.vip", "xuyuxiang29@foxmail.com"))
+ .version("2.0.0")
+ .build())
+ .globalResponseMessage(RequestMethod.GET, CommonResult.responseList())
+ .globalResponseMessage(RequestMethod.POST, CommonResult.responseList())
+ .groupName("业务功能BIZ")
+ .select()
+ .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+ .apis(RequestHandlerSelectors.basePackage("vip.xiaonuo.biz"))
+ .paths(PathSelectors.any())
+ .build().extensions(openApiExtensionResolver.buildExtensions("业务功能BIZ"));
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/core/enums/BizBuildInEnum.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/core/enums/BizBuildInEnum.java
new file mode 100644
index 00000000..363c1039
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/core/enums/BizBuildInEnum.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.core.enums;
+
+import lombok.Getter;
+
+/**
+ * 系统内置的不可删除的标识枚举
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 19:56
+ **/
+@Getter
+public enum BizBuildInEnum {
+
+ /** 超管用户账号 */
+ BUILD_IN_USER_ACCOUNT("superAdmin", "超管");
+
+ private final String value;
+
+ private final String name;
+
+ BizBuildInEnum(String value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/controller/BizOrgController.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/controller/BizOrgController.java
new file mode 100644
index 00000000..eda10fbd
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/controller/BizOrgController.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.biz.modular.org.entity.BizOrg;
+import vip.xiaonuo.biz.modular.org.param.*;
+import vip.xiaonuo.biz.modular.org.service.BizOrgService;
+import vip.xiaonuo.biz.modular.user.entity.BizUser;
+import vip.xiaonuo.common.annotation.CommonLog;
+import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.common.pojo.CommonValidList;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 机构控制器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 19:55
+ */
+@Api(tags = "机构控制器")
+@ApiSupport(author = "SNOWY_TEAM", order = 1)
+@RestController
+@Validated
+public class BizOrgController {
+
+ @Resource
+ private BizOrgService bizOrgService;
+
+ /**
+ * 获取机构分页
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 1)
+ @ApiOperation("获取机构分页")
+ @SaCheckPermission("/biz/org/page")
+ @GetMapping("/biz/org/page")
+ public CommonResult> page(BizOrgPageParam bizOrgPageParam) {
+ return CommonResult.data(bizOrgService.page(bizOrgPageParam));
+ }
+
+ /**
+ * 获取机构树
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 2)
+ @ApiOperation("获取机构树")
+ @SaCheckPermission("/biz/org/tree")
+ @GetMapping("/biz/org/tree")
+ public CommonResult>> tree() {
+ return CommonResult.data(bizOrgService.tree());
+ }
+
+ /**
+ * 添加机构
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:47
+ */
+ @ApiOperationSupport(order = 3)
+ @ApiOperation("添加机构")
+ @CommonLog("添加机构")
+ @SaCheckPermission("/biz/org/add")
+ @PostMapping("/biz/org/add")
+ public CommonResult add(@RequestBody @Valid BizOrgAddParam bizOrgAddParam) {
+ bizOrgService.add(bizOrgAddParam);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 编辑机构
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:47
+ */
+ @ApiOperationSupport(order = 4)
+ @ApiOperation("编辑机构")
+ @CommonLog("编辑机构")
+ @SaCheckPermission("/biz/org/edit")
+ @PostMapping("/biz/org/edit")
+ public CommonResult edit(@RequestBody @Valid BizOrgEditParam bizOrgEditParam) {
+ bizOrgService.edit(bizOrgEditParam);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 删除机构
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 5)
+ @ApiOperation("删除机构")
+ @CommonLog("删除机构")
+ @SaCheckPermission("/biz/org/delete")
+ @PostMapping("/biz/org/delete")
+ public CommonResult delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
+ CommonValidList bizOrgIdParamList) {
+ bizOrgService.delete(bizOrgIdParamList);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 获取机构详情
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 6)
+ @ApiOperation("获取机构详情")
+ @SaCheckPermission("/biz/org/detail")
+ @GetMapping("/biz/org/detail")
+ public CommonResult detail(@Valid BizOrgIdParam bizOrgIdParam) {
+ return CommonResult.data(bizOrgService.detail(bizOrgIdParam));
+ }
+
+ /* ====机构部分所需要用到的选择器==== */
+
+ /**
+ * 获取机构树选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 7)
+ @ApiOperation("获取机构树选择器")
+ @SaCheckPermission("/biz/org/orgTreeSelector")
+ @GetMapping("/biz/org/orgTreeSelector")
+ public CommonResult>> orgTreeSelector() {
+ return CommonResult.data(bizOrgService.orgTreeSelector());
+ }
+
+ /**
+ * 获取人员选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 8)
+ @ApiOperation("获取人员选择器")
+ @SaCheckPermission("/biz/org/userSelector")
+ @GetMapping("/biz/org/userSelector")
+ public CommonResult> userSelector(BizOrgSelectorUserParam bizOrgSelectorUserParam) {
+ return CommonResult.data(bizOrgService.userSelector(bizOrgSelectorUserParam));
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/entity/BizOrg.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/entity/BizOrg.java
new file mode 100644
index 00000000..c1d27269
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/entity/BizOrg.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import vip.xiaonuo.common.pojo.CommonEntity;
+
+/**
+ * 机构实体
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+@TableName("SYS_ORG")
+public class BizOrg extends CommonEntity {
+
+ /** id */
+ @ApiModelProperty(value = "id", position = 1)
+ private String id;
+
+ /** 父id */
+ @ApiModelProperty(value = "父id", position = 2)
+ private String parentId;
+
+ /** 主管id */
+ @ApiModelProperty(value = "主管id", position = 3)
+ @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
+ private String directorId;
+
+ /** 名称 */
+ @ApiModelProperty(value = "名称", position = 4)
+ private String name;
+
+ /** 编码 */
+ @ApiModelProperty(value = "编码", position = 5)
+ private String code;
+
+ /** 分类 */
+ @ApiModelProperty(value = "分类", position = 6)
+ private String category;
+
+ /** 排序码 */
+ @ApiModelProperty(value = "排序码", position = 7)
+ private Integer sortCode;
+
+ /** 扩展信息 */
+ @ApiModelProperty(value = "扩展信息", position = 8)
+ @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
+ private String extJson;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/enums/BizOrgCategoryEnum.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/enums/BizOrgCategoryEnum.java
new file mode 100644
index 00000000..3fb56b40
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/enums/BizOrgCategoryEnum.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.enums;
+
+import lombok.Getter;
+import vip.xiaonuo.common.exception.CommonException;
+
+/**
+ * 机构分类枚举
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 19:56
+ **/
+@Getter
+public enum BizOrgCategoryEnum {
+
+ /** 公司 */
+ COMPANY("COMPANY"),
+
+ /** 部门 */
+ DEPT("DEPT");
+
+ private final String value;
+
+ BizOrgCategoryEnum(String value) {
+ this.value = value;
+ }
+
+ public static void validate(String value) {
+ boolean flag = COMPANY.getValue().equals(value) || DEPT.getValue().equals(value);
+ if(!flag) {
+ throw new CommonException("不支持的机构分类:{}", value);
+ }
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/mapper/BizOrgMapper.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/mapper/BizOrgMapper.java
new file mode 100644
index 00000000..d5b15288
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/mapper/BizOrgMapper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import vip.xiaonuo.biz.modular.org.entity.BizOrg;
+
+/**
+ * 机构Mapper接口
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 18:37
+ **/
+public interface BizOrgMapper extends BaseMapper {
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/mapper/mapping/BizOrgMapper.xml b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/mapper/mapping/BizOrgMapper.xml
new file mode 100644
index 00000000..ed3f8a5c
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/mapper/mapping/BizOrgMapper.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgAddParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgAddParam.java
new file mode 100644
index 00000000..7fecbb1a
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgAddParam.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 机构添加参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizOrgAddParam {
+
+ /** 父id */
+ @ApiModelProperty(value = "父id", required = true, position = 1)
+ @NotBlank(message = "parentId不能为空")
+ private String parentId;
+
+ /** 名称 */
+ @ApiModelProperty(value = "名称", required = true, position = 2)
+ @NotBlank(message = "name不能为空")
+ private String name;
+
+ /** 分类 */
+ @ApiModelProperty(value = "分类", required = true, position = 3)
+ @NotBlank(message = "category不能为空")
+ private String category;
+
+ /** 排序码 */
+ @ApiModelProperty(value = "排序码", required = true, position = 4)
+ @NotNull(message = "sortCode不能为空")
+ private Integer sortCode;
+
+ /** 主管id */
+ @ApiModelProperty(value = "主管id", position = 5)
+ private String directorId;
+
+ /** 扩展JSON */
+ @ApiModelProperty(value = "扩展JSON", position = 6)
+ private String extJson;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgEditParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgEditParam.java
new file mode 100644
index 00000000..06abed6e
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgEditParam.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 机构编辑参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizOrgEditParam {
+
+ /** id */
+ @ApiModelProperty(value = "id", required = true, position = 1)
+ @NotBlank(message = "id不能为空")
+ private String id;
+
+ /** 父id */
+ @ApiModelProperty(value = "父id", required = true, position = 2)
+ @NotBlank(message = "parentId不能为空")
+ private String parentId;
+
+ /** 名称 */
+ @ApiModelProperty(value = "名称", required = true, position = 3)
+ @NotBlank(message = "name不能为空")
+ private String name;
+
+ /** 分类 */
+ @ApiModelProperty(value = "分类", required = true, position = 4)
+ @NotBlank(message = "category不能为空")
+ private String category;
+
+ /** 排序码 */
+ @ApiModelProperty(value = "排序码", required = true, position = 5)
+ @NotNull(message = "sortCode不能为空")
+ private Integer sortCode;
+
+ /** 主管id */
+ @ApiModelProperty(value = "主管id", position = 6)
+ private String directorId;
+
+ /** 扩展JSON */
+ @ApiModelProperty(value = "扩展JSON", position = 7)
+ private String extJson;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgIdParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgIdParam.java
new file mode 100644
index 00000000..e22cb459
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgIdParam.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 机构Id参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizOrgIdParam {
+
+ /** id */
+ @ApiModelProperty(value = "id", required = true)
+ @NotBlank(message = "id不能为空")
+ private String id;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgPageParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgPageParam.java
new file mode 100644
index 00000000..ae6c09e1
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgPageParam.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 机构查询参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizOrgPageParam {
+
+ /** 当前页 */
+ @ApiModelProperty(value = "当前页码")
+ private Integer current;
+
+ /** 每页条数 */
+ @ApiModelProperty(value = "每页条数")
+ private Integer size;
+
+ /** 排序字段 */
+ @ApiModelProperty(value = "排序字段,字段驼峰名称,如:userName")
+ private String sortField;
+
+ /** 排序方式 */
+ @ApiModelProperty(value = "排序方式,升序:ASCEND;降序:DESCEND")
+ private String sortOrder;
+
+ /** 父id */
+ @ApiModelProperty(value = "父id")
+ private String parentId;
+
+ /** 名称关键词 */
+ @ApiModelProperty(value = "名称关键词")
+ private String searchKey;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgSelectorOrgListParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgSelectorOrgListParam.java
new file mode 100644
index 00000000..7494f7ba
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgSelectorOrgListParam.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 机构列表选择器参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizOrgSelectorOrgListParam {
+
+ /** 父id */
+ @ApiModelProperty(value = "父id")
+ private String parentId;
+
+ /** 名称关键词 */
+ @ApiModelProperty(value = "名称关键词")
+ private String searchKey;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgSelectorUserParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgSelectorUserParam.java
new file mode 100644
index 00000000..0207633f
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/param/BizOrgSelectorUserParam.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 人员选择器参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizOrgSelectorUserParam {
+
+ /** 机构id */
+ @ApiModelProperty(value = "机构id")
+ private String orgId;
+
+ /** 姓名关键词 */
+ @ApiModelProperty(value = "姓名关键词")
+ private String searchKey;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/service/BizOrgService.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/service/BizOrgService.java
new file mode 100644
index 00000000..853e0c17
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/service/BizOrgService.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import vip.xiaonuo.biz.modular.org.entity.BizOrg;
+import vip.xiaonuo.biz.modular.org.param.*;
+import vip.xiaonuo.biz.modular.user.entity.BizUser;
+
+import java.util.List;
+
+/**
+ * 机构Service接口
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 18:35
+ **/
+public interface BizOrgService extends IService {
+
+ /**
+ * 获取机构分页
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ Page page(BizOrgPageParam bizOrgPageParam);
+
+ /**
+ * 获取机构树
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ List> tree();
+
+ /**
+ * 添加机构
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:48
+ */
+ void add(BizOrgAddParam bizOrgAddParam);
+
+ /**
+ * 编辑机构
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 21:13
+ */
+ void edit(BizOrgEditParam bizOrgEditParam);
+
+ /**
+ * 删除机构
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 21:18
+ */
+ void delete(List bizOrgIdParamList);
+
+ /**
+ * 获取机构详情
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 21:18
+ */
+ BizOrg detail(BizOrgIdParam bizOrgIdParam);
+
+ /**
+ * 获取机构详情
+ *
+ * @author xuyuxiang
+ * @date 2022/7/25 19:42
+ **/
+ BizOrg queryEntity(String id);
+
+ /**
+ * 获取机构树选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ List> orgTreeSelector();
+
+ /**
+ * 获取机构列表选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/7/22 13:34
+ **/
+ List orgListSelector(BizOrgSelectorOrgListParam bizOrgSelectorOrgListParam);
+
+ /**
+ * 获取人员选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ List userSelector(BizOrgSelectorUserParam bizOrgSelectorUserParam);
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/service/impl/BizOrgServiceImpl.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/service/impl/BizOrgServiceImpl.java
new file mode 100644
index 00000000..48136779
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/org/service/impl/BizOrgServiceImpl.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.org.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollStreamUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNode;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
+import vip.xiaonuo.biz.modular.org.entity.BizOrg;
+import vip.xiaonuo.biz.modular.org.enums.BizOrgCategoryEnum;
+import vip.xiaonuo.biz.modular.org.mapper.BizOrgMapper;
+import vip.xiaonuo.biz.modular.org.param.*;
+import vip.xiaonuo.biz.modular.org.service.BizOrgService;
+import vip.xiaonuo.biz.modular.position.entity.BizPosition;
+import vip.xiaonuo.biz.modular.position.service.BizPositionService;
+import vip.xiaonuo.biz.modular.user.entity.BizUser;
+import vip.xiaonuo.biz.modular.user.service.BizUserService;
+import vip.xiaonuo.common.enums.CommonSortOrderEnum;
+import vip.xiaonuo.common.exception.CommonException;
+import vip.xiaonuo.common.page.CommonPageRequest;
+import vip.xiaonuo.sys.api.SysRoleApi;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 机构Service接口实现类
+ *
+ * @author xuyuxiang
+ * @date 2022/2/23 18:43
+ **/
+@Service
+public class BizOrgServiceImpl extends ServiceImpl implements BizOrgService {
+
+ @Resource
+ private SysRoleApi sysRoleApi;
+
+ @Resource
+ private BizPositionService bizPositionService;
+
+ @Resource
+ private BizUserService bizUserService;
+
+ @Override
+ public Page page(BizOrgPageParam bizOrgPageParam) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ // 查询部分字段
+ queryWrapper.lambda().select(BizOrg::getId, BizOrg::getParentId, BizOrg::getName,
+ BizOrg::getCategory, BizOrg::getSortCode);
+ if(ObjectUtil.isNotEmpty(bizOrgPageParam.getParentId())) {
+ queryWrapper.lambda().eq(BizOrg::getParentId, bizOrgPageParam.getParentId());
+ }
+ if(ObjectUtil.isNotEmpty(bizOrgPageParam.getSearchKey())) {
+ queryWrapper.lambda().like(BizOrg::getName, bizOrgPageParam.getSearchKey());
+ }
+ if(ObjectUtil.isAllNotEmpty(bizOrgPageParam.getSortField(), bizOrgPageParam.getSortOrder())) {
+ CommonSortOrderEnum.validate(bizOrgPageParam.getSortOrder());
+ queryWrapper.orderBy(true, bizOrgPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
+ StrUtil.toUnderlineCase(bizOrgPageParam.getSortField()));
+ } else {
+ queryWrapper.lambda().orderByAsc(BizOrg::getSortCode);
+ }
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ queryWrapper.lambda().in(BizOrg::getId, loginUserDataScope);
+ } else {
+ return new Page<>();
+ }
+ return this.page(CommonPageRequest.defaultPage(), queryWrapper);
+ }
+
+ @Override
+ public List> tree() {
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ lambdaQueryWrapper.in(BizOrg::getId, loginUserDataScope);
+ } else {
+ return CollectionUtil.newArrayList();
+ }
+ lambdaQueryWrapper.orderByAsc(BizOrg::getSortCode);
+ List bizOrgList = this.list(lambdaQueryWrapper);
+ List> treeNodeList = bizOrgList.stream().map(bizOrg ->
+ new TreeNode<>(bizOrg.getId(), bizOrg.getParentId(),
+ bizOrg.getName(), bizOrg.getSortCode()).setExtra(JSONUtil.parseObj(bizOrg)))
+ .collect(Collectors.toList());
+ return TreeUtil.build(treeNodeList, "0");
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void add(BizOrgAddParam bizOrgAddParam) {
+ BizOrgCategoryEnum.validate(bizOrgAddParam.getCategory());
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ if(!loginUserDataScope.contains(bizOrgAddParam.getParentId())) {
+ throw new CommonException("您没有权限在该机构下增加机构,机构id:{}", bizOrgAddParam.getParentId());
+ }
+ } else {
+ throw new CommonException("您没有权限增加机构");
+ }
+ BizOrg bizOrg = BeanUtil.toBean(bizOrgAddParam, BizOrg.class);
+
+ // 重复名称
+ boolean repeatName = this.count(new LambdaQueryWrapper().eq(BizOrg::getParentId, bizOrg.getParentId())
+ .eq(BizOrg::getName, bizOrg.getName())) > 0;
+ if(repeatName) {
+ throw new CommonException("存在重复的同级机构,名称为:{}", bizOrg.getName());
+ }
+ bizOrg.setCode(RandomUtil.randomString(10));
+ this.save(bizOrg);
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void edit(BizOrgEditParam bizOrgEditParam) {
+ BizOrgCategoryEnum.validate(bizOrgEditParam.getCategory());
+ BizOrg bizOrg = this.queryEntity(bizOrgEditParam.getId());
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ if(!loginUserDataScope.contains(bizOrg.getId())) {
+ throw new CommonException("您没有权限编辑该机构,机构id:{}", bizOrg.getId());
+ }
+ if(!loginUserDataScope.contains(bizOrg.getParentId())) {
+ throw new CommonException("您没有权限编辑该机构下的机构,机构id:{}", bizOrg.getParentId());
+ }
+ } else {
+ throw new CommonException("您没有权限编辑该机构,机构id:{}", bizOrg.getId());
+ }
+ BeanUtil.copyProperties(bizOrgEditParam, bizOrg);
+ boolean repeatName = this.count(new LambdaQueryWrapper().eq(BizOrg::getParentId, bizOrg.getParentId())
+ .eq(BizOrg::getName, bizOrg.getName()).ne(BizOrg::getId, bizOrg.getId())) > 0;
+ if(repeatName) {
+ throw new CommonException("存在重复的同级机构,名称为:{}", bizOrg.getName());
+ }
+ List originDataList = this.list();
+ boolean errorLevel = this.getChildListById(originDataList, bizOrg.getId(), true).stream()
+ .map(BizOrg::getId).collect(Collectors.toList()).contains(bizOrg.getParentId());
+ if(errorLevel) {
+ throw new CommonException("不可选择上级机构:{}", this.getById(originDataList, bizOrg.getParentId()).getName());
+ }
+ this.updateById(bizOrg);
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void delete(List bizOrgIdParamList) {
+ List orgIdList = CollStreamUtil.toList(bizOrgIdParamList, BizOrgIdParam::getId);
+ if(ObjectUtil.isNotEmpty(orgIdList)) {
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ if(!loginUserDataScope.containsAll(orgIdList)) {
+ throw new CommonException("您没有权限删除这些机构,机构id:{}", orgIdList);
+ }
+ } else {
+ throw new CommonException("您没有权限删除这些机构,机构id:{}", orgIdList);
+ }
+ List allOrgList = this.list();
+ // 获取所有子组织
+ List toDeleteOrgIdList = CollectionUtil.newArrayList();
+ orgIdList.forEach(orgId -> toDeleteOrgIdList.addAll(this.getChildListById(allOrgList, orgId, true).stream()
+ .map(BizOrg::getId).collect(Collectors.toList())));
+ // 机构下有人不能删除(直属机构)
+ boolean hasOrgUser = bizUserService.count(new LambdaQueryWrapper().in(BizUser::getOrgId, toDeleteOrgIdList)) > 0;
+ if(hasOrgUser) {
+ throw new CommonException("请先删除机构下的人员");
+ }
+ // 机构下有人不能删除(兼任机构)
+ List positionJsonList = bizUserService.list(new LambdaQueryWrapper()
+ .isNotNull(BizUser::getPositionJson)).stream().map(BizUser::getPositionJson).collect(Collectors.toList());
+ if(ObjectUtil.isNotEmpty(positionJsonList)) {
+ List positionOrgIdList = CollectionUtil.newArrayList();
+ positionJsonList.forEach(positionJson -> JSONUtil.toList(JSONUtil.parseArray(positionJson), JSONObject.class)
+ .forEach(jsonObject -> positionOrgIdList.add(jsonObject.getStr("orgId"))));
+ boolean hasPositionUser = CollectionUtil.intersectionDistinct(toDeleteOrgIdList, CollectionUtil.removeNull(positionOrgIdList)).size() > 0;
+ if(hasPositionUser) {
+ throw new CommonException("请先删除机构下的人员");
+ }
+ }
+ // 机构下有角色不能删除
+ boolean hasRole = sysRoleApi.orgHasRole(toDeleteOrgIdList);
+ if(hasRole) {
+ throw new CommonException("请先删除机构下的角色");
+ }
+ // 机构下有岗位不能删除
+ boolean hasPosition = bizPositionService.count(new LambdaQueryWrapper().in(BizPosition::getOrgId, toDeleteOrgIdList)) > 0;
+ if(hasPosition) {
+ throw new CommonException("请先删除机构下的岗位");
+ }
+ // 执行删除
+ this.removeBatchByIds(toDeleteOrgIdList);
+ }
+ }
+
+ @Override
+ public BizOrg detail(BizOrgIdParam bizOrgIdParam) {
+ return this.queryEntity(bizOrgIdParam.getId());
+ }
+
+ @Override
+ public BizOrg queryEntity(String id) {
+ BizOrg bizOrg = this.getById(id);
+ if(ObjectUtil.isEmpty(bizOrg)) {
+ throw new CommonException("机构不存在,id值为:{}", id);
+ }
+ return bizOrg;
+ }
+
+ /* ====机构部分所需要用到的选择器==== */
+
+ @Override
+ public List> orgTreeSelector() {
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ lambdaQueryWrapper.in(BizOrg::getId, loginUserDataScope);
+ } else {
+ return CollectionUtil.newArrayList();
+ }
+ lambdaQueryWrapper.orderByAsc(BizOrg::getSortCode);
+ List bizOrgList = this.list(lambdaQueryWrapper);
+ List> treeNodeList = bizOrgList.stream().map(bizOrg ->
+ new TreeNode<>(bizOrg.getId(), bizOrg.getParentId(), bizOrg.getName(), bizOrg.getSortCode()))
+ .collect(Collectors.toList());
+ return TreeUtil.build(treeNodeList, "0");
+ }
+
+ @Override
+ public List orgListSelector(BizOrgSelectorOrgListParam bizOrgSelectorOrgListParam) {
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ lambdaQueryWrapper.in(BizOrg::getId, loginUserDataScope);
+ } else {
+ return CollectionUtil.newArrayList();
+ }
+ // 查询部分字段
+ lambdaQueryWrapper.select(BizOrg::getId, BizOrg::getParentId, BizOrg::getName,
+ BizOrg::getCategory, BizOrg::getSortCode);
+ if(ObjectUtil.isNotEmpty(bizOrgSelectorOrgListParam.getParentId())) {
+ lambdaQueryWrapper.eq(BizOrg::getParentId, bizOrgSelectorOrgListParam.getParentId());
+ }
+ if(ObjectUtil.isNotEmpty(bizOrgSelectorOrgListParam.getSearchKey())) {
+ lambdaQueryWrapper.like(BizOrg::getName, bizOrgSelectorOrgListParam.getSearchKey());
+ }
+ lambdaQueryWrapper.orderByAsc(BizOrg::getSortCode);
+ return this.list(lambdaQueryWrapper);
+ }
+
+ @Override
+ public List userSelector(BizOrgSelectorUserParam bizOrgSelectorUserParam) {
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ // 校验数据范围
+ List loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
+ if(ObjectUtil.isNotEmpty(loginUserDataScope)) {
+ lambdaQueryWrapper.in(BizUser::getOrgId, loginUserDataScope);
+ } else {
+ return CollectionUtil.newArrayList();
+ }
+ // 只查询部分字段
+ lambdaQueryWrapper.select(BizUser::getId, BizUser::getOrgId, BizUser::getAccount, BizUser::getName);
+ if(ObjectUtil.isNotEmpty(bizOrgSelectorUserParam.getOrgId())) {
+ lambdaQueryWrapper.eq(BizUser::getOrgId, bizOrgSelectorUserParam.getOrgId());
+ }
+ if(ObjectUtil.isNotEmpty(bizOrgSelectorUserParam.getSearchKey())) {
+ lambdaQueryWrapper.like(BizUser::getName, bizOrgSelectorUserParam.getSearchKey());
+ }
+ lambdaQueryWrapper.orderByAsc(BizUser::getSortCode);
+ return bizUserService.list(lambdaQueryWrapper);
+ }
+
+ /* ====以下为各种递归方法==== */
+
+ public List getChildListById(List originDataList, String id, boolean includeSelf) {
+ List resultList = CollectionUtil.newArrayList();
+ execRecursionFindChild(originDataList, id, resultList);
+ if(includeSelf) {
+ BizOrg self = this.getById(originDataList, id);
+ if(ObjectUtil.isNotEmpty(self)) {
+ resultList.add(self);
+ }
+ }
+ return resultList;
+ }
+
+ public List getParentListById(List originDataList, String id, boolean includeSelf) {
+ List resultList = CollectionUtil.newArrayList();
+ execRecursionFindParent(originDataList, id, resultList);
+ if(includeSelf) {
+ BizOrg self = this.getById(originDataList, id);
+ if(ObjectUtil.isNotEmpty(self)) {
+ resultList.add(self);
+ }
+ }
+ return resultList;
+ }
+
+ public void execRecursionFindChild(List originDataList, String id, List resultList) {
+ originDataList.forEach(item -> {
+ if(item.getParentId().equals(id)) {
+ resultList.add(item);
+ execRecursionFindChild(originDataList, item.getId(), resultList);
+ }
+ });
+ }
+
+ public void execRecursionFindParent(List originDataList, String id, List resultList) {
+ originDataList.forEach(item -> {
+ if(item.getId().equals(id)) {
+ BizOrg parent = this.getById(originDataList, item.getParentId());
+ if(ObjectUtil.isNotEmpty(parent)) {
+ resultList.add(parent);
+ }
+ execRecursionFindParent(originDataList, item.getParentId(), resultList);
+ }
+ });
+ }
+
+ public BizOrg getById(List originDataList, String id) {
+ int index = CollStreamUtil.toList(originDataList, BizOrg::getId).indexOf(id);
+ return index == -1?null:originDataList.get(index);
+ }
+
+ public BizOrg getParentById(List originDataList, String id) {
+ BizOrg self = this.getById(originDataList, id);
+ return ObjectUtil.isNotEmpty(self)?self:this.getById(originDataList, self.getParentId());
+ }
+
+ public BizOrg getChildById(List originDataList, String id) {
+ int index = CollStreamUtil.toList(originDataList, BizOrg::getParentId).indexOf(id);
+ return index == -1?null:originDataList.get(index);
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/controller/BizPositionController.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/controller/BizPositionController.java
new file mode 100644
index 00000000..ec3b6889
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/controller/BizPositionController.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.biz.modular.position.entity.BizPosition;
+import vip.xiaonuo.biz.modular.position.param.*;
+import vip.xiaonuo.biz.modular.position.service.BizPositionService;
+import vip.xiaonuo.common.annotation.CommonLog;
+import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.common.pojo.CommonValidList;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 岗位控制器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/25 20:40
+ */
+@Api(tags = "岗位控制器")
+@ApiSupport(author = "SNOWY_TEAM", order = 2)
+@RestController
+@Validated
+public class BizPositionController {
+
+ @Resource
+ private BizPositionService bizPositionService;
+
+ /**
+ * 获取岗位分页
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 1)
+ @ApiOperation("获取岗位分页")
+ @SaCheckPermission("/biz/position/page")
+ @GetMapping("/biz/position/page")
+ public CommonResult> page(BizPositionPageParam bizPositionPageParam) {
+ return CommonResult.data(bizPositionService.page(bizPositionPageParam));
+ }
+
+ /**
+ * 添加岗位
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:47
+ */
+ @ApiOperationSupport(order = 2)
+ @ApiOperation("添加岗位")
+ @CommonLog("添加岗位")
+ @SaCheckPermission("/biz/position/add")
+ @PostMapping("/biz/position/add")
+ public CommonResult add(@RequestBody @Valid BizPositionAddParam bizPositionAddParam) {
+ bizPositionService.add(bizPositionAddParam);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 编辑岗位
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:47
+ */
+ @ApiOperationSupport(order = 3)
+ @ApiOperation("编辑岗位")
+ @CommonLog("编辑岗位")
+ @SaCheckPermission("/biz/position/edit")
+ @PostMapping("/biz/position/edit")
+ public CommonResult edit(@RequestBody @Valid BizPositionEditParam bizPositionEditParam) {
+ bizPositionService.edit(bizPositionEditParam);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 删除岗位
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 4)
+ @ApiOperation("删除岗位")
+ @CommonLog("删除岗位")
+ @SaCheckPermission("/biz/position/delete")
+ @PostMapping("/biz/position/delete")
+ public CommonResult delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
+ CommonValidList bizPositionIdParamList) {
+ bizPositionService.delete(bizPositionIdParamList);
+ return CommonResult.ok();
+ }
+
+ /**
+ * 获取岗位详情
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 5)
+ @ApiOperation("获取岗位详情")
+ @SaCheckPermission("/biz/position/detail")
+ @GetMapping("/biz/position/detail")
+ public CommonResult detail(@Valid BizPositionIdParam bizPositionIdParam) {
+ return CommonResult.data(bizPositionService.detail(bizPositionIdParam));
+ }
+
+ /* ====岗位部分所需要用到的选择器==== */
+
+ /**
+ * 获取组织树选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 6)
+ @ApiOperation("获取组织树选择器")
+ @SaCheckPermission("/biz/position/orgTreeSelector")
+ @GetMapping("/biz/position/orgTreeSelector")
+ public CommonResult>> orgTreeSelector() {
+ return CommonResult.data(bizPositionService.orgTreeSelector());
+ }
+
+ /**
+ * 获取岗位选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:00
+ */
+ @ApiOperationSupport(order = 7)
+ @ApiOperation("获取岗位选择器")
+ @SaCheckPermission("/biz/position/positionSelector")
+ @GetMapping("/biz/position/positionSelector")
+ public CommonResult> positionSelector(BizPositionSelectorPositionParam bizPositionSelectorPositionParam) {
+ return CommonResult.data(bizPositionService.positionSelector(bizPositionSelectorPositionParam));
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/entity/BizPosition.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/entity/BizPosition.java
new file mode 100644
index 00000000..0eafc87b
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/entity/BizPosition.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import vip.xiaonuo.common.pojo.CommonEntity;
+
+/**
+ * 岗位实体
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+@TableName("SYS_POSITION")
+public class BizPosition extends CommonEntity {
+
+ /** id */
+ @ApiModelProperty(value = "id", position = 1)
+ private String id;
+
+ /** 机构id */
+ @ApiModelProperty(value = "机构id", position = 2)
+ private String orgId;
+
+ /** 名称 */
+ @ApiModelProperty(value = "名称", position = 3)
+ private String name;
+
+ /** 编码 */
+ @ApiModelProperty(value = "编码", position = 4)
+ private String code;
+
+ /** 分类 */
+ @ApiModelProperty(value = "分类", position = 5)
+ private String category;
+
+ /** 排序码 */
+ @ApiModelProperty(value = "排序码", position = 6)
+ private Integer sortCode;
+
+ /** 扩展信息 */
+ @ApiModelProperty(value = "扩展信息", position = 7)
+ @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
+ private String extJson;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/enums/BizPositionCategoryEnum.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/enums/BizPositionCategoryEnum.java
new file mode 100644
index 00000000..8758bafc
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/enums/BizPositionCategoryEnum.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.enums;
+
+import lombok.Getter;
+import vip.xiaonuo.common.exception.CommonException;
+
+/**
+ * 岗位分类枚举
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 19:56
+ **/
+@Getter
+public enum BizPositionCategoryEnum {
+
+ /** 高层 */
+ COMPANY("HIGH"),
+
+ /** 中层 */
+ DEPT("MIDDLE"),
+
+ /** 基层 */
+ LOW("LOW");
+
+ private final String value;
+
+ BizPositionCategoryEnum(String value) {
+ this.value = value;
+ }
+
+ public static void validate(String value) {
+ boolean flag = COMPANY.getValue().equals(value) || DEPT.getValue().equals(value);
+ if(!flag) {
+ throw new CommonException("不支持的岗位分类:{}", value);
+ }
+ }
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/mapper/BizPositionMapper.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/mapper/BizPositionMapper.java
new file mode 100644
index 00000000..d0cf6d1d
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/mapper/BizPositionMapper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import vip.xiaonuo.biz.modular.position.entity.BizPosition;
+
+/**
+ * 岗位Mapper接口
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 18:37
+ **/
+public interface BizPositionMapper extends BaseMapper {
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/mapper/mapping/BizPositionMapper.xml b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/mapper/mapping/BizPositionMapper.xml
new file mode 100644
index 00000000..c27d5bb5
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/mapper/mapping/BizPositionMapper.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionAddParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionAddParam.java
new file mode 100644
index 00000000..69d2f882
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionAddParam.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 岗位添加参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizPositionAddParam {
+
+ /** 机构id */
+ @ApiModelProperty(value = "机构id", required = true, position = 1)
+ @NotBlank(message = "orgId不能为空")
+ private String orgId;
+
+ /** 名称 */
+ @ApiModelProperty(value = "名称", required = true, position = 2)
+ @NotBlank(message = "name不能为空")
+ private String name;
+
+ /** 分类 */
+ @ApiModelProperty(value = "分类", required = true, position = 3)
+ @NotBlank(message = "category不能为空")
+ private String category;
+
+ /** 排序码 */
+ @ApiModelProperty(value = "排序码", required = true, position = 4)
+ @NotNull(message = "sortCode不能为空")
+ private Integer sortCode;
+
+ /** 扩展JSON */
+ @ApiModelProperty(value = "扩展JSON", position = 5)
+ private String extJson;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionEditParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionEditParam.java
new file mode 100644
index 00000000..3c1df7fe
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionEditParam.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 岗位编辑参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizPositionEditParam {
+
+ /** id */
+ @ApiModelProperty(value = "id", required = true, position = 1)
+ @NotBlank(message = "id不能为空")
+ private String id;
+
+ /** 机构id */
+ @ApiModelProperty(value = "机构id", required = true, position = 2)
+ @NotBlank(message = "orgId不能为空")
+ private String orgId;
+
+ /** 名称 */
+ @ApiModelProperty(value = "名称", required = true, position = 3)
+ @NotBlank(message = "name不能为空")
+ private String name;
+
+ /** 分类 */
+ @ApiModelProperty(value = "分类", required = true, position = 4)
+ @NotBlank(message = "category不能为空")
+ private String category;
+
+ /** 排序码 */
+ @ApiModelProperty(value = "排序码", required = true, position = 5)
+ @NotNull(message = "sortCode不能为空")
+ private Integer sortCode;
+
+ /** 扩展JSON */
+ @ApiModelProperty(value = "扩展JSON", position = 6)
+ private String extJson;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionIdParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionIdParam.java
new file mode 100644
index 00000000..032dbb45
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionIdParam.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 岗位Id参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizPositionIdParam {
+
+ /** id */
+ @ApiModelProperty(value = "id", required = true)
+ @NotBlank(message = "id不能为空")
+ private String id;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionPageParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionPageParam.java
new file mode 100644
index 00000000..68b9b500
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionPageParam.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 岗位查询参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizPositionPageParam {
+
+ /** 当前页 */
+ @ApiModelProperty(value = "当前页码")
+ private Integer current;
+
+ /** 每页条数 */
+ @ApiModelProperty(value = "每页条数")
+ private Integer size;
+
+ /** 排序字段 */
+ @ApiModelProperty(value = "排序字段,字段驼峰名称,如:userName")
+ private String sortField;
+
+ /** 排序方式 */
+ @ApiModelProperty(value = "排序方式,升序:ASCEND;降序:DESCEND")
+ private String sortOrder;
+
+ /** 机构id */
+ @ApiModelProperty(value = "机构id")
+ private String orgId;
+
+ /** 岗位分类 */
+ @ApiModelProperty(value = "岗位分类")
+ private String category;
+
+ /** 名称关键词 */
+ @ApiModelProperty(value = "名称关键词")
+ private String searchKey;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionSelectorPositionParam.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionSelectorPositionParam.java
new file mode 100644
index 00000000..bbee748c
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/param/BizPositionSelectorPositionParam.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 岗位选择器参数
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 16:13
+ **/
+@Getter
+@Setter
+public class BizPositionSelectorPositionParam {
+
+ /** 机构id */
+ @ApiModelProperty(value = "机构id")
+ private String orgId;
+
+ /** 名称关键词 */
+ @ApiModelProperty(value = "名称关键词")
+ private String searchKey;
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/service/BizPositionService.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/service/BizPositionService.java
new file mode 100644
index 00000000..06e7d76d
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/service/BizPositionService.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import vip.xiaonuo.biz.modular.position.entity.BizPosition;
+import vip.xiaonuo.biz.modular.position.param.*;
+
+import java.util.List;
+
+/**
+ * 岗位Service接口
+ *
+ * @author xuyuxiang
+ * @date 2022/4/21 18:35
+ **/
+public interface BizPositionService extends IService {
+
+ /**
+ * 获取岗位分页
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ Page page(BizPositionPageParam bizPositionPageParam);
+
+ /**
+ * 添加岗位
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:48
+ */
+ void add(BizPositionAddParam bizPositionAddParam);
+
+ /**
+ * 编辑岗位
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 21:13
+ */
+ void edit(BizPositionEditParam bizPositionEditParam);
+
+ /**
+ * 删除岗位
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 21:18
+ */
+ void delete(List bizPositionIdParamList);
+
+ /**
+ * 获取岗位详情
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 21:18
+ */
+ BizPosition detail(BizPositionIdParam bizPositionIdParam);
+
+ /**
+ * 获取岗位详情
+ *
+ * @author xuyuxiang
+ * @date 2022/7/25 19:42
+ **/
+ BizPosition queryEntity(String id);
+
+ /* ====岗位部分所需要用到的选择器==== */
+
+ /**
+ * 获取机构树选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ List> orgTreeSelector();
+
+ /**
+ * 获取岗位选择器
+ *
+ * @author xuyuxiang
+ * @date 2022/4/24 20:08
+ */
+ List positionSelector(BizPositionSelectorPositionParam bizPositionSelectorPositionParam);
+}
diff --git a/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/service/impl/BizPositionServiceImpl.java b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/service/impl/BizPositionServiceImpl.java
new file mode 100644
index 00000000..029cb815
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/position/service/impl/BizPositionServiceImpl.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.position.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollStreamUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNode;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
+import vip.xiaonuo.biz.modular.org.entity.BizOrg;
+import vip.xiaonuo.biz.modular.org.service.BizOrgService;
+import vip.xiaonuo.biz.modular.position.entity.BizPosition;
+import vip.xiaonuo.biz.modular.position.enums.BizPositionCategoryEnum;
+import vip.xiaonuo.biz.modular.position.mapper.BizPositionMapper;
+import vip.xiaonuo.biz.modular.position.param.*;
+import vip.xiaonuo.biz.modular.position.service.BizPositionService;
+import vip.xiaonuo.biz.modular.user.entity.BizUser;
+import vip.xiaonuo.biz.modular.user.service.BizUserService;
+import vip.xiaonuo.common.enums.CommonSortOrderEnum;
+import vip.xiaonuo.common.exception.CommonException;
+import vip.xiaonuo.common.page.CommonPageRequest;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 岗位Service接口实现类
+ *
+ * @author xuyuxiang
+ * @date 2022/2/23 18:43
+ **/
+@Service
+public class BizPositionServiceImpl extends ServiceImpl implements BizPositionService {
+
+ @Resource
+ private BizOrgService bizOrgService;
+
+ @Resource
+ private BizUserService bizUserService;
+
+ @Override
+ public Page page(BizPositionPageParam bizPositionPageParam) {
+ QueryWrapper