523096025 пре 1 година
комит
825ec99f72
100 измењених фајлова са 7641 додато и 0 уклоњено
  1. 34 0
      .gitignore
  2. 53 0
      LICENSE
  3. 706 0
      pom.xml
  4. 1 0
      snowy-common/READM.md
  5. 176 0
      snowy-common/pom.xml
  6. 32 0
      snowy-common/src/main/java/vip/xiaonuo/common/annotation/CommonLog.java
  7. 32 0
      snowy-common/src/main/java/vip/xiaonuo/common/annotation/CommonNoRepeat.java
  8. 35 0
      snowy-common/src/main/java/vip/xiaonuo/common/annotation/CommonWrapper.java
  9. 86 0
      snowy-common/src/main/java/vip/xiaonuo/common/cache/CommonCacheOperator.java
  10. 31 0
      snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonDeleteFlagEnum.java
  11. 42 0
      snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonExceptionEnum.java
  12. 45 0
      snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonSortOrderEnum.java
  13. 112 0
      snowy-common/src/main/java/vip/xiaonuo/common/excel/CommonExcelCustomMergeStrategy.java
  14. 50 0
      snowy-common/src/main/java/vip/xiaonuo/common/exception/CommonException.java
  15. 61 0
      snowy-common/src/main/java/vip/xiaonuo/common/handler/CommonSm4CbcTypeHandler.java
  16. 175 0
      snowy-common/src/main/java/vip/xiaonuo/common/listener/CommonDataChangeEventCenter.java
  17. 76 0
      snowy-common/src/main/java/vip/xiaonuo/common/listener/CommonDataChangeListener.java
  18. 79 0
      snowy-common/src/main/java/vip/xiaonuo/common/page/CommonPageRequest.java
  19. 63 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonEntity.java
  20. 160 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonResult.java
  21. 146 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonValidList.java
  22. 32 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonWrapperInterface.java
  23. 37 0
      snowy-common/src/main/java/vip/xiaonuo/common/prop/CommonProperties.java
  24. 31 0
      snowy-common/src/main/java/vip/xiaonuo/common/sse/CommonSseParam.java
  25. 30 0
      snowy-common/src/main/java/vip/xiaonuo/common/timer/CommonTimerTaskRunner.java
  26. 126 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonAvatarUtil.java
  27. 142 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonCryptogramUtil.java
  28. 63 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonDownloadUtil.java
  29. 50 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonEmailUtil.java
  30. 40 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonFilterExceptionUtil.java
  31. 108 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonIpAddressUtil.java
  32. 72 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonJoinPointUtil.java
  33. 125 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonNetWorkInfoUtil.java
  34. 65 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonResponseUtil.java
  35. 99 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonServletUtil.java
  36. 183 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonTimeFormatUtil.java
  37. 80 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonUaUtil.java
  38. BIN
      snowy-common/src/main/resources/ip2region.xdb
  39. 15 0
      snowy-plugin-api/README.md
  40. 39 0
      snowy-plugin-api/pom.xml
  41. 1 0
      snowy-plugin-api/snowy-plugin-auth-api/README.md
  42. 30 0
      snowy-plugin-api/snowy-plugin-auth-api/pom.xml
  43. 124 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/api/SaBaseLoginUserApi.java
  44. 35 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckLogin.java
  45. 51 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckPermission.java
  46. 52 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckRole.java
  47. 49 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/enums/SaClientTypeEnum.java
  48. 233 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseClientLoginUser.java
  49. 269 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseLoginUser.java
  50. 47 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpClientLoginUserUtil.java
  51. 936 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpClientUtil.java
  52. 55 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpLoginUserUtil.java
  53. 1 0
      snowy-plugin-api/snowy-plugin-biz-api/README.md
  54. 24 0
      snowy-plugin-api/snowy-plugin-biz-api/pom.xml
  55. 13 0
      snowy-plugin-api/snowy-plugin-biz-api/src/main/java/vip/xiaonuo/biz/package-info.java
  56. 1 0
      snowy-plugin-api/snowy-plugin-client-api/README.md
  57. 24 0
      snowy-plugin-api/snowy-plugin-client-api/pom.xml
  58. 13 0
      snowy-plugin-api/snowy-plugin-client-api/src/main/java/vip/xiaonuo/client/package-info.java
  59. 1 0
      snowy-plugin-api/snowy-plugin-dev-api/README.md
  60. 108 0
      snowy-plugin-api/snowy-plugin-dev-api/pom.xml
  61. 30 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevConfigApi.java
  62. 22 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevDictApi.java
  63. 142 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevEmailApi.java
  64. 104 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevFileApi.java
  65. 22 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevJobApi.java
  66. 58 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevLogApi.java
  67. 111 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevMessageApi.java
  68. 54 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevSmsApi.java
  69. 68 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevSseApi.java
  70. 1 0
      snowy-plugin-api/snowy-plugin-gen-api/README.md
  71. 24 0
      snowy-plugin-api/snowy-plugin-gen-api/pom.xml
  72. 13 0
      snowy-plugin-api/snowy-plugin-gen-api/src/main/java/vip/xiaonuo/gen/package-info.java
  73. 1 0
      snowy-plugin-api/snowy-plugin-mobile-api/README.md
  74. 24 0
      snowy-plugin-api/snowy-plugin-mobile-api/pom.xml
  75. 1 0
      snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/README.md
  76. 32 0
      snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileButtonApi.java
  77. 43 0
      snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileMenuApi.java
  78. 33 0
      snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileModuleApi.java
  79. 1 0
      snowy-plugin-api/snowy-plugin-sys-api/README.md
  80. 24 0
      snowy-plugin-api/snowy-plugin-sys-api/pom.xml
  81. 1 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/README.md
  82. 30 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysButtonApi.java
  83. 30 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysMenuApi.java
  84. 60 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysOrgApi.java
  85. 43 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysPositionApi.java
  86. 48 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRelationApi.java
  87. 51 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRoleApi.java
  88. 115 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysUserApi.java
  89. 15 0
      snowy-plugin/README.md
  90. 39 0
      snowy-plugin/pom.xml
  91. 1 0
      snowy-plugin/snowy-plugin-auth/README.md
  92. 61 0
      snowy-plugin/snowy-plugin-auth/pom.xml
  93. 149 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/config/AuthConfigure.java
  94. 67 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/util/AuthExceptionUtil.java
  95. 133 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthClientController.java
  96. 133 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthController.java
  97. 54 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthDeviceTypeEnum.java
  98. 86 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthExceptionEnum.java
  99. 118 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/listener/AuthListener.java
  100. 0 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthAccountPasswordLoginParam.java

+ 34 - 0
.gitignore

@@ -0,0 +1,34 @@
+*.class
+
+# Package Files #
+*.jar
+*.war
+*.ear
+target/
+
+# eclipse
+.settings/
+.classpath
+.project
+logs/
+
+# idea
+.idea/
+*.iml
+.murphy.yml
+
+*velocity.log*
+
+### STS ###
+.apt_generated
+.factorypath
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.ipr
+*.log
+tmp/
+!DmJdbcDriver18.jar
+!kingbase8-8.6.0.jar

Разлика између датотеке није приказан због своје велике величине
+ 53 - 0
LICENSE


+ 706 - 0
pom.xml

@@ -0,0 +1,706 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>vip.xiaonuo</groupId>
+    <artifactId>snowy</artifactId>
+    <name>snowy</name>
+    <version>2.0.0</version>
+    <description>snowy快速开发平台</description>
+    <packaging>pom</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.5.12</version>
+    </parent>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <snowy.version>2.0.0</snowy.version>
+        <spring-framework.version>5.3.26</spring-framework.version>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <!-- 锁定依赖版本号 -->
+        <ali.oss.version>3.14.0</ali.oss.version>
+        <aliyun.sdk.dm.version>3.3.1</aliyun.sdk.dm.version>
+        <aliyun.sdk.dysmsapi.version>2.0.9</aliyun.sdk.dysmsapi.version>
+        <aliyun.sdk.ecs.version>3.1.0</aliyun.sdk.ecs.version>
+        <bcprov.jdk15on.version>1.70</bcprov.jdk15on.version>
+        <beetl.version>1.2.40.Beetl.RELEASE</beetl.version>
+        <checker.qual.version>3.31.0</checker.qual.version>
+        <commons.beanutils.version>1.9.4</commons.beanutils.version>
+        <commons.compress.version>1.22</commons.compress.version>
+        <commons.pool2.version>2.11.1</commons.pool2.version>
+        <druid.version>1.2.9</druid.version>
+        <dynamic.datasource.version>3.5.1</dynamic.datasource.version>
+        <easy.trans.version>2.1.7</easy.trans.version>
+        <easyexcel.version>3.2.1</easyexcel.version>
+        <easypoi.version>4.3.0</easypoi.version>
+        <fastjson.version>2.0.24</fastjson.version>
+        <gson.version>2.8.9</gson.version>
+        <guava.version>31.1-jre</guava.version>
+        <hutool.version>5.8.12</hutool.version>
+        <ip2region.version>2.6.3</ip2region.version>
+        <jackson.annotations.version>2.14.2</jackson.annotations.version>
+        <jackson.core.version>2.14.2</jackson.core.version>
+        <jackson.databind.version>2.14.2</jackson.databind.version>
+        <jackson.datatype.jdk8.version>2.14.2</jackson.datatype.jdk8.version>
+        <jackson.datatype.jsr310.version>2.14.2</jackson.datatype.jsr310.version>
+        <jackson.module.parameter.names.version>2.14.2</jackson.module.parameter.names.version>
+        <javax.mail.version>1.6.2</javax.mail.version>
+        <jettison.version>1.5.4</jettison.version>
+        <junit.version>4.13.2</junit.version>
+        <just.auth.version>1.16.5</just.auth.version>
+        <knife4j.version>2.0.9</knife4j.version>
+        <logback.classic.version>1.2.0</logback.classic.version>
+        <lombok.versin>1.18.22</lombok.versin>
+        <minio.version>8.5.2</minio.version>
+        <mssql.connector.java.version>9.2.1.jre8</mssql.connector.java.version>
+        <mybatis.plus.version>3.5.3.1</mybatis.plus.version>
+        <mybatis.version>3.5.10</mybatis.version>
+        <mysql.connector.java.version>8.0.28</mysql.connector.java.version>
+        <netty.common.version>4.1.89.Final</netty.common.version>
+        <netty.handler.version>4.1.89.Final</netty.handler.version>
+        <okhttp3.version>4.10.0</okhttp3.version>
+        <okio.version>3.3.0</okio.version>
+        <dm.connector.java.version>8.1.2.192</dm.connector.java.version>
+        <kingbase.connector.java.version>8.6.0</kingbase.connector.java.version>
+        <oracle.connector.java.version>21.5.0.0</oracle.connector.java.version>
+        <oracle.nls.orai18n.version>19.7.0.0</oracle.nls.orai18n.version>
+        <oshi.core.version>6.2.2</oshi.core.version>
+        <pinyin.version>2.5.1</pinyin.version>
+        <postgres.connector.java.version>42.2.25</postgres.connector.java.version>
+        <protobuf.java.version>3.21.12</protobuf.java.version>
+        <sa.token.version>1.31.0</sa.token.version>
+        <smcrypto.version>0.3.2</smcrypto.version>
+        <snakeyaml.version>2.0</snakeyaml.version>
+        <spring.context.version>5.3.19</spring.context.version>
+        <spring.security.crypto.version>6.0.2</spring.security.crypto.version>
+        <springfox.swagger2.version>2.10.5</springfox.swagger2.version>
+        <ten.cos.version>5.6.68</ten.cos.version>
+        <ten.sdk.ses.version>3.1.455</ten.sdk.ses.version>
+        <ten.sdk.sms.version>3.1.455</ten.sdk.sms.version>
+        <tomcat.embed.core.version>9.0.72</tomcat.embed.core.version>
+    </properties>
+
+     <modules>
+        <!-- 基础通用规则模块 -->
+        <module>snowy-common</module>
+
+        <!-- 插件模块 -->
+        <module>snowy-plugin</module>
+
+        <!-- 插件API接口模块 -->
+        <module>snowy-plugin-api</module>
+
+        <!-- 主启动模块 -->
+        <module>snowy-web-app</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+
+            <!-- snowy-common -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-common</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-auth-api -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-auth-api</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-biz-api -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-biz-api</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-client-api -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-client-api</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-dev-api -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-dev-api</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-gen-api -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-gen-api</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-mobile-api -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-mobile-api</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-sys-api -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-sys-api</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-auth -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-auth</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-biz -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-biz</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-client -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-client</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-dev -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-dev</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-gen -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-gen</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-mobile -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-mobile</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- snowy-plugin-sys -->
+            <dependency>
+                <groupId>vip.xiaonuo</groupId>
+                <artifactId>snowy-plugin-sys</artifactId>
+                <version>${snowy.version}</version>
+            </dependency>
+
+            <!-- lombok -->
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.versin}</version>
+            </dependency>
+
+            <!-- druid -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+
+            <!-- mybatis -->
+            <dependency>
+                <groupId>org.mybatis</groupId>
+                <artifactId>mybatis</artifactId>
+                <version>${mybatis.version}</version>
+            </dependency>
+
+            <!-- mybatis-plus-core -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-core</artifactId>
+                <version>${mybatis.plus.version}</version>
+            </dependency>
+
+            <!-- mybatis-plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis.plus.version}</version>
+            </dependency>
+
+            <!-- easy-trans -->
+            <dependency>
+                <groupId>com.fhs-opensource</groupId>
+                <artifactId>easy-trans-spring-boot-starter</artifactId>
+                <version>${easy.trans.version}</version>
+            </dependency>
+
+            <!-- easy-trans-mybatis-plus-extend -->
+            <dependency>
+                <groupId>com.fhs-opensource</groupId>
+                <artifactId>easy-trans-mybatis-plus-extend</artifactId>
+                <version>${easy.trans.version}</version>
+            </dependency>
+
+            <!-- redis -->
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-pool2</artifactId>
+                <version>${commons.pool2.version}</version>
+            </dependency>
+
+            <!-- okhttp -->
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp</artifactId>
+                <version>${okhttp3.version}</version>
+            </dependency>
+
+            <!-- okio -->
+            <dependency>
+                <groupId>com.squareup.okio</groupId>
+                <artifactId>okio</artifactId>
+                <version>${okio.version}</version>
+            </dependency>
+
+            <!-- hutool -->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool.version}</version>
+            </dependency>
+
+            <!-- pinyin4j -->
+            <dependency>
+                <groupId>com.belerweb</groupId>
+                <artifactId>pinyin4j</artifactId>
+                <version>${pinyin.version}</version>
+            </dependency>
+
+            <!-- ip2region -->
+            <dependency>
+                <groupId>org.lionsoul</groupId>
+                <artifactId>ip2region</artifactId>
+                <version>${ip2region.version}</version>
+            </dependency>
+
+            <!-- knife4j -->
+            <dependency>
+                <groupId>com.github.xiaoymin</groupId>
+                <artifactId>knife4j-spring-boot-starter</artifactId>
+                <version>${knife4j.version}</version>
+            </dependency>
+
+            <!-- easy-poi -->
+            <dependency>
+                <groupId>cn.afterturn</groupId>
+                <artifactId>easypoi-spring-boot-starter</artifactId>
+                <version>${easypoi.version}</version>
+            </dependency>
+
+            <!-- sm-crypto -->
+            <dependency>
+                <groupId>com.antherd</groupId>
+                <artifactId>sm-crypto</artifactId>
+                <version>${smcrypto.version}</version>
+            </dependency>
+
+            <!-- easyexcel -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>easyexcel</artifactId>
+                <version>${easyexcel.version}</version>
+            </dependency>
+
+            <!-- sa-token-core -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-core</artifactId>
+                <version>${sa.token.version}</version>
+            </dependency>
+
+            <!-- sa-token -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-spring-boot-starter</artifactId>
+                <version>${sa.token.version}</version>
+            </dependency>
+
+            <!-- sa-token 整合 redis (使用jackson序列化方式) -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-dao-redis-jackson</artifactId>
+                <version>${sa.token.version}</version>
+            </dependency>
+
+            <!-- Sa-Token插件:权限缓存与业务缓存分离 -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-alone-redis</artifactId>
+                <version>${sa.token.version}</version>
+            </dependency>
+
+            <!-- Sa-Token 插件:整合SSO -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-sso</artifactId>
+                <version>${sa.token.version}</version>
+            </dependency>
+
+            <!-- JustAuth 第三方登录 -->
+            <dependency>
+                <groupId>me.zhyd.oauth</groupId>
+                <artifactId>JustAuth</artifactId>
+                <version>${just.auth.version}</version>
+            </dependency>
+
+            <!-- beetl模板引擎 -->
+            <dependency>
+                <groupId>com.ibeetl</groupId>
+                <artifactId>beetl-framework-starter</artifactId>
+                <version>${beetl.version}</version>
+            </dependency>
+
+            <!--腾讯云上传文件客户端-->
+            <dependency>
+                <groupId>com.qcloud</groupId>
+                <artifactId>cos_api</artifactId>
+                <version>${ten.cos.version}</version>
+            </dependency>
+
+            <!--阿里云上传文件客户端-->
+            <dependency>
+                <groupId>com.aliyun.oss</groupId>
+                <artifactId>aliyun-sdk-oss</artifactId>
+                <version>${ali.oss.version}</version>
+            </dependency>
+
+            <!--minio上传文件客户端-->
+            <dependency>
+                <groupId>io.minio</groupId>
+                <artifactId>minio</artifactId>
+                <version>${minio.version}</version>
+            </dependency>
+
+            <!--java邮件发送-->
+            <dependency>
+                <groupId>com.sun.mail</groupId>
+                <artifactId>javax.mail</artifactId>
+                <version>${javax.mail.version}</version>
+            </dependency>
+
+            <!--阿里云邮件发送-->
+            <dependency>
+                <groupId>com.aliyun</groupId>
+                <artifactId>aliyun-java-sdk-dm</artifactId>
+                <version>${aliyun.sdk.dm.version}</version>
+            </dependency>
+
+            <!-- 腾讯云邮件发送 -->
+            <dependency>
+                <groupId>com.tencentcloudapi</groupId>
+                <artifactId>tencentcloud-sdk-java-ses</artifactId>
+                <version>${ten.sdk.ses.version}</version>
+            </dependency>
+
+            <!--阿里云短信发送-->
+            <dependency>
+                <groupId>com.aliyun</groupId>
+                <artifactId>dysmsapi20170525</artifactId>
+                <version>${aliyun.sdk.dysmsapi.version}</version>
+            </dependency>
+
+            <!--腾讯云短信发送-->
+            <dependency>
+                <groupId>com.tencentcloudapi</groupId>
+                <artifactId>tencentcloud-sdk-java-sms</artifactId>
+                <version>${ten.sdk.sms.version}</version>
+            </dependency>
+
+            <!--系统硬件信息-->
+            <dependency>
+                <groupId>com.github.oshi</groupId>
+                <artifactId>oshi-core</artifactId>
+                <version>${oshi.core.version}</version>
+            </dependency>
+
+            <!-- junit -->
+            <dependency>
+                <groupId>junit</groupId>
+                <artifactId>junit</artifactId>
+                <version>${junit.version}</version>
+                <scope>test</scope>
+            </dependency>
+
+            <!-- logback-classic -->
+            <dependency>
+                <groupId>ch.qos.logback</groupId>
+                <artifactId>logback-classic</artifactId>
+                <version>${logback.classic.version}</version>
+            </dependency>
+
+            <!-- gson -->
+            <dependency>
+                <groupId>com.google.code.gson</groupId>
+                <artifactId>gson</artifactId>
+                <version>${gson.version}</version>
+            </dependency>
+
+            <!-- guava -->
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
+
+            <!-- netty-common -->
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-common</artifactId>
+                <version>${netty.common.version}</version>
+            </dependency>
+
+            <!-- netty-common -->
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-handler</artifactId>
+                <version>${netty.handler.version}</version>
+            </dependency>
+
+            <!-- jettison -->
+            <dependency>
+                <groupId>org.codehaus.jettison</groupId>
+                <artifactId>jettison</artifactId>
+                <version>${jettison.version}</version>
+            </dependency>
+
+            <!-- snakeyaml -->
+            <dependency>
+                <groupId>org.yaml</groupId>
+                <artifactId>snakeyaml</artifactId>
+                <version>${snakeyaml.version}</version>
+            </dependency>
+
+            <!-- spring-context -->
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-context</artifactId>
+                <version>${spring.context.version}</version>
+            </dependency>
+
+            <!-- spring-security-crypto -->
+            <dependency>
+                <groupId>org.springframework.security</groupId>
+                <artifactId>spring-security-crypto</artifactId>
+                <version>${spring.security.crypto.version}</version>
+            </dependency>
+
+            <!-- springfox-swagger2 -->
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger2</artifactId>
+                <version>${springfox.swagger2.version}</version>
+            </dependency>
+
+            <!-- tomcat-embed-core -->
+            <dependency>
+                <groupId>org.apache.tomcat.embed</groupId>
+                <artifactId>tomcat-embed-core</artifactId>
+                <version>${tomcat.embed.core.version}</version>
+            </dependency>
+
+            <!-- fastjson -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+            <!-- jackson-annotations -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-annotations</artifactId>
+                <version>${jackson.annotations.version}</version>
+            </dependency>
+
+            <!-- jackson-core -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-core</artifactId>
+                <version>${jackson.core.version}</version>
+            </dependency>
+
+            <!-- jackson-databind -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-databind</artifactId>
+                <version>${jackson.databind.version}</version>
+            </dependency>
+
+            <!-- jackson-datatype -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.datatype</groupId>
+                <artifactId>jackson-datatype-jdk8</artifactId>
+                <version>${jackson.datatype.jdk8.version}</version>
+            </dependency>
+
+            <!-- jackson-jsr310 -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.datatype</groupId>
+                <artifactId>jackson-datatype-jsr310</artifactId>
+                <version>${jackson.datatype.jsr310.version}</version>
+            </dependency>
+
+            <!-- jackson-module-parameter-names -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.module</groupId>
+                <artifactId>jackson-module-parameter-names</artifactId>
+                <version>${jackson.module.parameter.names.version}</version>
+            </dependency>
+
+            <!-- commons-beanutils -->
+            <dependency>
+                <groupId>commons-beanutils</groupId>
+                <artifactId>commons-beanutils</artifactId>
+                <version>${commons.beanutils.version}</version>
+            </dependency>
+
+            <!-- commons-compress -->
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-compress</artifactId>
+                <version>${commons.compress.version}</version>
+            </dependency>
+
+            <!-- protobuf-java -->
+            <dependency>
+                <groupId>com.google.protobuf</groupId>
+                <artifactId>protobuf-java</artifactId>
+                <version>${protobuf.java.version}</version>
+            </dependency>
+
+            <!-- checker-qual -->
+            <dependency>
+                <groupId>org.checkerframework</groupId>
+                <artifactId>checker-qual</artifactId>
+                <version>${checker.qual.version}</version>
+            </dependency>
+
+            <!-- bcprov-jdk15on -->
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15on</artifactId>
+                <version>${bcprov.jdk15on.version}</version>
+            </dependency>
+
+            <!-- dynamic-datasource -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+                <version>${dynamic.datasource.version}</version>
+            </dependency>
+
+            <!-- mysql -->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql.connector.java.version}</version>
+            </dependency>
+
+            <!-- postgresql -->
+            <!--<dependency>
+                <groupId>org.postgresql</groupId>
+                <artifactId>postgresql</artifactId>
+                <version>${postgres.connector.java.version}</version>
+            </dependency>-->
+
+            <!-- 达梦数据库 -->
+            <!--<dependency>
+                <groupId>com.dameng</groupId>
+                <artifactId>DmJdbcDriver18</artifactId>
+                <version>${dm.connector.java.version}</version>
+            </dependency>-->
+
+            <!-- 人大金仓数据库 -->
+            <!--<dependency>
+                <groupId>cn.com.kingbase</groupId>
+                <artifactId>kingbase8</artifactId>
+                <version>${kingbase.connector.java.version}</version>
+            </dependency>-->
+
+            <!-- oracle -->
+            <!--<dependency>
+                <groupId>com.oracle.database.jdbc</groupId>
+                <artifactId>ojdbc8</artifactId>
+                <version>${oracle.connector.java.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.oracle.database.nls</groupId>
+                <artifactId>orai18n</artifactId>
+                <version>${oracle.nls.orai18n.version}</version>
+            </dependency>-->
+
+            <!-- mssql -->
+            <!--<dependency>
+                <groupId>com.microsoft.sqlserver</groupId>
+                <artifactId>mssql-jdbc</artifactId>
+                <version>${mssql.connector.java.version}</version>
+            </dependency>-->
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.7.0</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>3.0.1</version>
+                <configuration>
+                    <attach>true</attach>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+    </build>
+</project>

+ 1 - 0
snowy-common/READM.md

@@ -0,0 +1 @@
+# 基础通用模块

+ 176 - 0
snowy-common/pom.xml

@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-common</artifactId>
+    <packaging>jar</packaging>
+    <description>基础通用模块</description>
+
+    <dependencies>
+        <!-- validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!-- web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- aop -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- processor -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+
+        <!-- redis -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!-- lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!-- druid -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- mybatis-plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <!-- easy-trans -->
+        <dependency>
+            <groupId>com.fhs-opensource</groupId>
+            <artifactId>easy-trans-spring-boot-starter</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-plus-annotation</artifactId>
+                    <groupId>com.baomidou</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>springfox-schema</artifactId>
+                    <groupId>io.springfox</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>error_prone_annotations</artifactId>
+                    <groupId>com.google.errorprone</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- easy-trans-mybatis-plus-extend -->
+        <dependency>
+            <groupId>com.fhs-opensource</groupId>
+            <artifactId>easy-trans-mybatis-plus-extend</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-plus-extension</artifactId>
+                    <groupId>com.baomidou</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- redis -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <!-- jackson-core -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+
+        <!-- jackson-databind -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <!-- hutool -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <!-- pinyin4j -->
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+        </dependency>
+
+        <!-- ip2region -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+        </dependency>
+
+        <!-- knife4j -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- easy-poi -->
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-spring-boot-starter</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>javassist</artifactId>
+                    <groupId>org.javassist</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>poi</artifactId>
+                    <groupId>org.apache.poi</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>poi-ooxml</artifactId>
+                    <groupId>org.apache.poi</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>poi-ooxml-schemas</artifactId>
+                    <groupId>org.apache.poi</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- sm-crypto -->
+        <dependency>
+            <groupId>com.antherd</groupId>
+            <artifactId>sm-crypto</artifactId>
+        </dependency>
+
+        <!-- easyexcel -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 32 - 0
snowy-common/src/main/java/vip/xiaonuo/common/annotation/CommonLog.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.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义日志注解
+ *
+ * @author xuyuxiang
+ * @date 2022/6/20 14:25
+ **/
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface CommonLog {
+
+    /**
+     * 日志的名称,例如:"修改菜单"
+     */
+    String value() default "未命名";
+}

+ 32 - 0
snowy-common/src/main/java/vip/xiaonuo/common/annotation/CommonNoRepeat.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.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义节流防抖注解
+ *
+ * @author xuyuxiang
+ * @date 2022/6/20 14:25
+ **/
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface CommonNoRepeat {
+
+    /**
+     * 间隔时间(ms),小于此时间视为重复提交,默认5000ms
+     */
+    int interval() default 5000;
+}

+ 35 - 0
snowy-common/src/main/java/vip/xiaonuo/common/annotation/CommonWrapper.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.common.annotation;
+
+import vip.xiaonuo.common.pojo.CommonWrapperInterface;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义包装注解,对响应结果包装
+ *
+ * @author xuyuxiang
+ * @date 2022/9/15 21:12
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface CommonWrapper {
+
+    /**
+     * 具体包装类
+     */
+    Class<? extends CommonWrapperInterface<?>>[] value();
+}

+ 86 - 0
snowy-common/src/main/java/vip/xiaonuo/common/cache/CommonCacheOperator.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.common.cache;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * 通用Redis缓存操作器
+ *
+ * @author xuyuxiang
+ * @date 2022/6/21 16:00
+ **/
+@Component
+public class CommonCacheOperator {
+
+    /** 所有缓存Key的前缀 */
+    private static final String CACHE_KEY_PREFIX = "Cache:";
+
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
+    public void put(String key, Object value) {
+        redisTemplate.boundValueOps(CACHE_KEY_PREFIX + key).set(value);
+    }
+
+    public void put(String key, Object value, long timeoutSeconds) {
+        redisTemplate.boundValueOps(CACHE_KEY_PREFIX + key).set(value, timeoutSeconds, TimeUnit.SECONDS);
+    }
+
+    public Object get(String key) {
+        return redisTemplate.boundValueOps(CACHE_KEY_PREFIX + key).get();
+    }
+
+    public void remove(String... key) {
+        ArrayList<String> keys = CollectionUtil.toList(key);
+        List<String> withPrefixKeys = keys.stream().map(i -> CACHE_KEY_PREFIX + i).collect(Collectors.toList());
+        redisTemplate.delete(withPrefixKeys);
+    }
+
+    public Collection<String> getAllKeys() {
+        Set<String> keys = redisTemplate.keys(CACHE_KEY_PREFIX + "*");
+        if (keys != null) {
+            // 去掉缓存key的common prefix前缀
+            return keys.stream().map(key -> StrUtil.removePrefix(key, CACHE_KEY_PREFIX)).collect(Collectors.toSet());
+        } else {
+            return CollectionUtil.newHashSet();
+        }
+    }
+
+    public Collection<Object> getAllValues() {
+        Set<String> keys = redisTemplate.keys(CACHE_KEY_PREFIX + "*");
+        if (keys != null) {
+            return redisTemplate.opsForValue().multiGet(keys);
+        } else {
+            return CollectionUtil.newArrayList();
+        }
+    }
+
+    public Map<String, Object> getAllKeyValues() {
+        Collection<String> allKeys = this.getAllKeys();
+        HashMap<String, Object> results = MapUtil.newHashMap();
+        for (String key : allKeys) {
+            results.put(key, this.get(key));
+        }
+        return results;
+    }
+}

+ 31 - 0
snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonDeleteFlagEnum.java

@@ -0,0 +1,31 @@
+/*
+ * 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.common.enums;
+
+import lombok.Getter;
+
+/**
+ * 通用删除标志枚举
+ *
+ * @author xuyuxiang
+ * @date 2021/10/11 14:02
+ **/
+@Getter
+public enum CommonDeleteFlagEnum {
+
+    /** 未删除 */
+    NOT_DELETE,
+
+    /** 已删除 */
+    DELETED
+}

+ 42 - 0
snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonExceptionEnum.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.common.enums;
+
+import lombok.Getter;
+
+/**
+ * 异常码枚举
+ *
+ * @author xuyuxiang
+ * @date 2022/8/15 16:09
+ **/
+@Getter
+public enum CommonExceptionEnum {
+
+    OK200(200, "请求成功"),
+    ERROR401(401, "未登录"),
+    ERROR403(403, "无权限"),
+    ERROR404(404, "路径不存在"),
+    ERROR405(405, "请求方法不正确"),
+    ERROR415(415, "参数传递异常"),
+    ERROR500(500, "业务异常");
+
+    private final Integer code;
+
+    private final String message;
+
+    CommonExceptionEnum(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+}

+ 45 - 0
snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonSortOrderEnum.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.common.enums;
+
+import lombok.Getter;
+import vip.xiaonuo.common.exception.CommonException;
+
+/**
+ * 通用排序方式枚举
+ *
+ * @author xuyuxiang
+ * @date 2022/7/13 17:48
+ **/
+@Getter
+public enum CommonSortOrderEnum {
+
+    /** 升序 */
+    ASC("ASCEND"),
+
+    /** 降序 */
+    DESC("DESCEND");
+
+    private final String value;
+
+    CommonSortOrderEnum(String value) {
+        this.value = value.toUpperCase();
+    }
+
+    public static void validate(String value) {
+        boolean flag = ASC.getValue().toLowerCase().equals(value) || DESC.getValue().toLowerCase().equals(value);
+        if(!flag) {
+            throw new CommonException("不支持该排序方式:{}", value);
+        }
+    }
+}

+ 112 - 0
snowy-common/src/main/java/vip/xiaonuo/common/excel/CommonExcelCustomMergeStrategy.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.common.excel;
+
+import com.alibaba.excel.metadata.Head;
+import com.alibaba.excel.write.merge.AbstractMergeStrategy;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * EasyExcel自定义合并策略 该类继承了AbstractMergeStrategy抽象合并策略,需要重写merge()方法
+ *
+ * @author xuyuxiang
+ * @date 2023/3/6 13:21
+ **/
+public class CommonExcelCustomMergeStrategy extends AbstractMergeStrategy {
+
+    /**
+     * 分组,每几行合并一次
+     */
+    private final List<Integer> exportFieldGroupCountList;
+
+    /**
+     * 目标合并列index
+     */
+    private final Integer targetColumnIndex;
+
+    /**
+     * 需要开始合并单元格的首行index
+     */
+    private Integer rowIndex;
+
+    /**
+     * exportDataList为待合并目标列的值
+     *
+     * @author xuyuxiang
+     * @date 2023/3/6 13:23
+     **/
+    public CommonExcelCustomMergeStrategy(List<String> exportDataList, Integer targetColumnIndex) {
+        this.exportFieldGroupCountList = getGroupCountList(exportDataList);
+        this.targetColumnIndex = targetColumnIndex;
+    }
+
+
+    @Override
+    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
+
+        if (null == rowIndex) {
+            rowIndex = cell.getRowIndex();
+        }
+        // 仅从首行以及目标列的单元格开始合并,忽略其他
+        if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == targetColumnIndex) {
+            mergeGroupColumn(sheet);
+        }
+    }
+
+    private void mergeGroupColumn(Sheet sheet) {
+        int rowCount = rowIndex;
+        for (Integer count : exportFieldGroupCountList) {
+            if(count == 1) {
+                rowCount += count;
+                continue ;
+            }
+            // 合并单元格
+            CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCount, rowCount + count - 1, targetColumnIndex, targetColumnIndex);
+            sheet.addMergedRegionUnsafe(cellRangeAddress);
+            rowCount += count;
+        }
+    }
+
+    /*
+     * 该方法将目标列根据值是否相同连续可合并,存储可合并的行数
+     *
+     * @author xuyuxiang
+     * @date 2023/3/6 13:23
+     **/
+    private List<Integer> getGroupCountList(List<String> exportDataList){
+        if (CollectionUtils.isEmpty(exportDataList)) {
+            return new ArrayList<>();
+        }
+
+        List<Integer> groupCountList = new ArrayList<>();
+        int count = 1;
+
+        for (int i = 1; i < exportDataList.size(); i++) {
+            if (exportDataList.get(i).equals(exportDataList.get(i - 1))) {
+                count++;
+            } else {
+                groupCountList.add(count);
+                count = 1;
+            }
+        }
+        // 处理完最后一条后
+        groupCountList.add(count);
+        return groupCountList;
+    }
+}

+ 50 - 0
snowy-common/src/main/java/vip/xiaonuo/common/exception/CommonException.java

@@ -0,0 +1,50 @@
+/*
+ * 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.common.exception;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 通用异常
+ *
+ * @author xuyuxiang
+ * @date 2020/4/8 15:54
+ */
+@Getter
+@Setter
+public class CommonException extends RuntimeException {
+
+    private Integer code;
+
+    private String msg;
+
+    public CommonException() {
+        super("服务器异常");
+        this.code = 500;
+        this.msg = "服务器异常";
+    }
+
+    public CommonException(String msg, Object... arguments) {
+        super(StrUtil.format(msg, arguments));
+        this.code = 500;
+        this.msg = StrUtil.format(msg, arguments);
+    }
+
+    public CommonException(Integer code, String msg, Object... arguments) {
+        super(StrUtil.format(msg, arguments));
+        this.code = code;
+        this.msg = StrUtil.format(msg, arguments);
+    }
+}

+ 61 - 0
snowy-common/src/main/java/vip/xiaonuo/common/handler/CommonSm4CbcTypeHandler.java

@@ -0,0 +1,61 @@
+/*
+ * 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.common.handler;
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import vip.xiaonuo.common.util.CommonCryptogramUtil;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Sm4Cbc加解密
+ *
+ * @author wanglei
+ * @date 2022/9/30 15:24
+ **/
+@MappedJdbcTypes(JdbcType.VARCHAR)
+public class CommonSm4CbcTypeHandler<T> extends BaseTypeHandler<T> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
+        ps.setString(i, CommonCryptogramUtil.doSm4CbcEncrypt((String)parameter));
+    }
+
+    @SuppressWarnings("ALL")
+    @Override
+    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        String columnValue = rs.getString(columnName);
+        //有一些可能是空字符
+        return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)CommonCryptogramUtil.doSm4CbcDecrypt(columnValue);
+    }
+
+    @SuppressWarnings("ALL")
+    @Override
+    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        String columnValue = rs.getString(columnIndex);
+        return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)CommonCryptogramUtil.doSm4CbcDecrypt(columnValue);
+    }
+
+    @SuppressWarnings("ALL")
+    @Override
+    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String columnValue = cs.getString(columnIndex);
+        return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)CommonCryptogramUtil.doSm4CbcDecrypt(columnValue);
+    }
+}

+ 175 - 0
snowy-common/src/main/java/vip/xiaonuo/common/listener/CommonDataChangeEventCenter.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.common.listener;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONUtil;
+import vip.xiaonuo.common.exception.CommonException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 通用数据变化事件中心 事件发布器
+ *
+ * @author xuyuxiang
+ * @date 2023/3/3 10:14
+ **/
+public class CommonDataChangeEventCenter {
+
+    // --------- 注册侦听器 
+
+    private static List<CommonDataChangeListener> listenerList = new ArrayList<>();
+
+    /**
+     * 获取已注册的所有侦听器
+     * @return / 
+     */
+    public static List<CommonDataChangeListener> getListenerList() {
+        return listenerList;
+    }
+
+    /**
+     * 重置侦听器集合
+     * @param listenerList / 
+     */
+    public static void setListenerList(List<CommonDataChangeListener> listenerList) {
+        if(listenerList == null) {
+            throw new CommonException("重置的侦听器集合不可以为空");
+        }
+        CommonDataChangeEventCenter.listenerList = listenerList;
+    }
+
+    /**
+     * 注册一个侦听器 
+     * @param listener / 
+     */
+    public static void registerListener(CommonDataChangeListener listener) {
+        if(listener == null) {
+            throw new CommonException("注册的侦听器不可以为空");
+        }
+        listenerList.add(listener);
+    }
+
+    /**
+     * 注册一组侦听器 
+     * @param listenerList / 
+     */
+    public static void registerListenerList(List<CommonDataChangeListener> listenerList) {
+        if(listenerList == null) {
+            throw new CommonException("注册的侦听器不可以为空");
+        }
+        for (CommonDataChangeListener listener : listenerList) {
+            if(listener == null) {
+                throw new CommonException("注册的侦听器不可以为空");
+            }
+        }
+        CommonDataChangeEventCenter.listenerList.addAll(listenerList);
+    }
+
+    /**
+     * 移除一个侦听器 
+     * @param listener / 
+     */
+    public static void removeListener(CommonDataChangeListener listener) {
+        listenerList.remove(listener);
+    }
+
+    /**
+     * 移除指定类型的所有侦听器 
+     * @param cls / 
+     */
+    public static void removeListener(Class<? extends CommonDataChangeListener> cls) {
+        ArrayList<CommonDataChangeListener> listenerListCopy = new ArrayList<>(listenerList);
+        for (CommonDataChangeListener listener : listenerListCopy) {
+            if(cls.isAssignableFrom(listener.getClass())) {
+                listenerList.remove(listener);
+            }
+        }
+    }
+
+    /**
+     * 清空所有已注册的侦听器 
+     */
+    public static void clearListener() {
+        listenerList.clear();
+    }
+
+    /**
+     * 判断是否已经注册了指定侦听器 
+     * @param listener / 
+     * @return / 
+     */
+    public static boolean hasListener(CommonDataChangeListener listener) {
+        return listenerList.contains(listener);
+    }
+
+    /**
+     * 判断是否已经注册了指定类型的侦听器 
+     * @param cls / 
+     * @return / 
+     */
+    public static boolean hasListener(Class<? extends CommonDataChangeListener> cls) {
+        for (CommonDataChangeListener listener : listenerList) {
+            if(cls.isAssignableFrom(listener.getClass())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // --------- 事件发布-添加 --------- //
+
+    /**
+     * 执行添加事件发布,数据集合
+     *
+     * @author xuyuxiang
+     * @date 2023/3/3 10:22
+     **/
+    public static void doAddWithData(String dataType, JSONArray jsonArray) {
+        for (CommonDataChangeListener listener : listenerList) {
+            listener.doAddWithDataIdList(dataType, jsonArray.stream().map(o -> JSONUtil.parseObj(o).getStr("id")).collect(Collectors.toList()));
+            listener.doAddWithDataList(dataType, jsonArray);
+        }
+    }
+
+    // --------- 事件发布-更新 --------- //
+
+    /**
+     * 执行更新事件发布,数据集合
+     *
+     * @author xuyuxiang
+     * @date 2023/3/3 10:22
+     **/
+    public static void doUpdateWithData(String dataType, JSONArray jsonArray) {
+        for (CommonDataChangeListener listener : listenerList) {
+            listener.doUpdateWithDataIdList(dataType, jsonArray.stream().map(o -> JSONUtil.parseObj(o).getStr("id")).collect(Collectors.toList()));
+            listener.doUpdateWithDataList(dataType, jsonArray);
+        }
+    }
+
+    // --------- 事件发布-删除 --------- //
+
+    /**
+     * 执行删除事件发布,ID集合
+     *
+     * @author xuyuxiang
+     * @date 2023/3/3 10:22
+     **/
+    public static void doDeleteWithDataId(String dataType, List<String> dataIdList) {
+        for (CommonDataChangeListener listener : listenerList) {
+            listener.doDeleteWithDataIdList(dataType, dataIdList);
+        }
+    }
+}

+ 76 - 0
snowy-common/src/main/java/vip/xiaonuo/common/listener/CommonDataChangeListener.java

@@ -0,0 +1,76 @@
+/*
+ * 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.common.listener;
+
+import cn.hutool.json.JSONArray;
+
+import java.util.List;
+
+/**
+ * 通用数据变化侦听器,你可以实现该侦听器接口,在数据新增、更新、删除时进行一些AOP操作
+ *
+ * @author xuyuxiang
+ * @date 2023/3/3 10:14
+ **/
+public interface CommonDataChangeListener {
+
+    /**
+     * 执行添加,ID集合
+     *
+     * @param dataType 数据类型,如USER、ORG,自行定义
+     * @param dataIdList 被添加的数据ID集合
+     * @author xuyuxiang
+     * @date 2023/3/3 10:24
+     **/
+    void doAddWithDataIdList(String dataType, List<String> dataIdList);
+
+    /**
+     * 执行添加,数据集合
+     *
+     * @param dataType 数据类型,如USER、ORG,自行定义
+     * @param jsonArray 被添加的数据集合
+     * @author xuyuxiang
+     * @date 2023/3/3 10:24
+     **/
+    void doAddWithDataList(String dataType, JSONArray jsonArray);
+
+    /**
+     * 执行更新,ID集合
+     *
+     * @param dataType 数据类型,如USER、ORG,自行定义
+     * @param dataIdList 被更新的数据ID集合
+     * @author xuyuxiang
+     * @date 2023/3/3 10:24
+     **/
+    void doUpdateWithDataIdList(String dataType, List<String> dataIdList);
+
+    /**
+     * 执行更新,数据集合
+     *
+     * @param dataType 数据类型,如USER、ORG,自行定义
+     * @param jsonArray 被更新的数据集合
+     * @author xuyuxiang
+     * @date 2023/3/3 10:24
+     **/
+    void doUpdateWithDataList(String dataType, JSONArray jsonArray);
+
+    /**
+     * 执行删除,ID集合
+     *
+     * @param dataType 数据类型,如USER、ORG,自行定义
+     * @param dataIdList 被删除的数据ID集合
+     * @author xuyuxiang
+     * @date 2023/3/3 10:24
+     **/
+    void doDeleteWithDataIdList(String dataType, List<String> dataIdList);
+}

+ 79 - 0
snowy-common/src/main/java/vip/xiaonuo/common/page/CommonPageRequest.java

@@ -0,0 +1,79 @@
+/*
+ * 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.common.page;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+import vip.xiaonuo.common.util.CommonServletUtil;
+
+import java.util.List;
+
+/**
+ * 通用分页请求
+ *
+ * @author xuyuxiang
+ * @date 2021/12/18 14:43
+ */
+@Slf4j
+public class CommonPageRequest {
+
+    private static final String PAGE_SIZE_PARAM_NAME = "size";
+
+    private static final String PAGE_PARAM_NAME = "current";
+
+    private static final Integer PAGE_SIZE_MAX_VALUE = 100;
+
+    public static <T> Page<T> defaultPage() {
+        return defaultPage(null);
+    }
+
+    public static <T> Page<T> defaultPage(List<OrderItem> orderItemList) {
+
+        int size = 20;
+
+        int page = 1;
+
+        //每页条数
+        String pageSizeString = CommonServletUtil.getParamFromRequest(PAGE_SIZE_PARAM_NAME);
+        if (ObjectUtil.isNotEmpty(pageSizeString)) {
+            try {
+                size = Convert.toInt(pageSizeString);
+                if(size > PAGE_SIZE_MAX_VALUE) {
+                    size = PAGE_SIZE_MAX_VALUE;
+                }
+            } catch (Exception e) {
+                log.error(">>> 分页条数转换异常:", e);
+                size = 20;
+            }
+        }
+
+        //第几页
+        String pageString = CommonServletUtil.getParamFromRequest(PAGE_PARAM_NAME);
+        if (ObjectUtil.isNotEmpty(pageString)) {
+            try {
+                page = Convert.toInt(pageString);
+            } catch (Exception e) {
+                log.error(">>> 分页页数转换异常:", e);
+                page = 1;
+            }
+        }
+        Page<T> objectPage = new Page<>(page, size);
+        if (ObjectUtil.isNotEmpty(orderItemList)) {
+            objectPage.setOrders(orderItemList);
+        }
+        return objectPage;
+    }
+}

+ 63 - 0
snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonEntity.java

@@ -0,0 +1,63 @@
+/*
+ * 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.common.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ *     通用基础字段实体:创建时间、创建人、修改时间、修改人,需要此通用字段的实体可继承此类,
+ *     继承此类要求数据表有对应的字段
+ * </p>
+ *
+ * @author xuyuxiang
+ * @date 2020/3/10 16:02
+ */
+@Getter
+@Setter
+public class CommonEntity implements Serializable {
+
+    /** 删除标志 */
+    @TableLogic
+    @ApiModelProperty(value = "删除标志", position = 999)
+    @TableField(fill = FieldFill.INSERT)
+    private String deleteFlag;
+
+    /** 创建时间 */
+    @ApiModelProperty(value = "创建时间", position = 1000)
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 创建人 */
+    @ApiModelProperty(value = "创建人", position = 1001)
+    @TableField(fill = FieldFill.INSERT)
+    private String createUser;
+
+    /** 更新时间 */
+    @ApiModelProperty(value = "更新时间", position = 1002)
+    @TableField(fill = FieldFill.UPDATE)
+    private Date updateTime;
+
+    /** 更新人 */
+    @ApiModelProperty(value = "更新人", position = 1003)
+    @TableField(fill = FieldFill.UPDATE)
+    private String updateUser;
+}

+ 160 - 0
snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonResult.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.common.pojo;
+
+import io.swagger.annotations.ApiModelProperty;
+import springfox.documentation.builders.ResponseMessageBuilder;
+import springfox.documentation.service.ResponseMessage;
+import vip.xiaonuo.common.enums.CommonExceptionEnum;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 对Ajax请求返回Json格式数据的简易封装
+ *
+ * @author xuyuxiang
+ * @date 2022/8/15 16:08
+ **/
+public class CommonResult<T> implements Serializable{
+    private static final long serialVersionUID = 1L;
+    public static final int CODE_SUCCESS = 200;
+    public static final int CODE_ERROR = 500;
+
+    @ApiModelProperty(value = "状态码")
+    private int code;
+
+    @ApiModelProperty(value = "提示语")
+    private String msg;
+
+    @ApiModelProperty(value = "返回数据")
+    private T data;
+
+    public CommonResult() {
+    }
+
+    public CommonResult(int code, String msg, T data) {
+        this.setCode(code);
+        this.setMsg(msg);
+        this.setData(data);
+    }
+
+    /**
+     * 获取code
+     * @return code
+     */
+    public Integer getCode() {
+        return this.code;
+    }
+
+    /**
+     * 获取msg
+     * @return msg
+     */
+    public String getMsg() {
+        return this.msg;
+    }
+    /**
+     * 获取data
+     * @return data
+     */
+    public T getData() {
+        return this.data;
+    }
+
+    /**
+     * 给code赋值,连缀风格
+     * @param code code
+     * @return 对象自身
+     */
+    public CommonResult<T> setCode(int code) {
+        this.code = code;
+        return this;
+    }
+
+    /**
+     * 给msg赋值,连缀风格
+     * @param msg msg
+     * @return 对象自身
+     */
+    public CommonResult<T> setMsg(String msg) {
+        this.msg = msg;
+        return this;
+    }
+
+    /**
+     * 给data赋值,连缀风格
+     * @param data data
+     * @return 对象自身
+     */
+    public CommonResult<T> setData(T data) {
+        this.data = data;
+        return this;
+    }
+
+
+    // ============================  构建  ==================================
+
+    // 构建成功
+    public static <T> CommonResult<T> ok() {
+        return new CommonResult<>(CODE_SUCCESS, "操作成功", null);
+    }
+    public static <T> CommonResult<T> ok(String msg) {
+        return new CommonResult<>(CODE_SUCCESS, msg, null);
+    }
+    public static <T> CommonResult<T> code(int code) {
+        return new CommonResult<>(code, null, null);
+    }
+    public static <T> CommonResult<T> data(T data) {
+        return new CommonResult<>(CODE_SUCCESS, "操作成功", data);
+    }
+
+    // 构建失败
+    public static <T> CommonResult<T> error() {
+        return new CommonResult<>(CODE_ERROR, "服务器异常", null);
+    }
+    public static <T> CommonResult<T> error(String msg) {
+        return new CommonResult<>(CODE_ERROR, msg, null);
+    }
+
+    // 构建指定状态码
+    public static <T> CommonResult<T> get(int code, String msg, T data) {
+        return new CommonResult<>(code, msg, data);
+    }
+
+    /*
+     * toString()
+     */
+    @Override
+    public String toString() {
+        return "{"
+                + "\"code\": " + this.getCode()
+                + ", \"msg\": \"" + this.getMsg() + "\""
+                + ", \"data\": \"" + this.getData() + "\""
+                + "}";
+    }
+
+    /**
+     * 响应状态码集合
+     *
+     * @author xuyuxiang
+     * @date 2022/7/25 13:36
+     **/
+    public static List<ResponseMessage> responseList() {
+        return Arrays.stream(CommonExceptionEnum.values()).map(commonExceptionEnum -> new ResponseMessageBuilder()
+                .code(commonExceptionEnum.getCode()).message(commonExceptionEnum.getMessage()).build())
+                .collect(Collectors.toList());
+    }
+}

+ 146 - 0
snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonValidList.java

@@ -0,0 +1,146 @@
+/*
+ * 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.common.pojo;
+
+import lombok.Data;
+
+import javax.validation.Valid;
+import java.util.*;
+
+/**
+ * 可被校验的通用List
+ *
+ * @author xuyuxiang
+ * @date 2022/7/28 16:08
+ **/
+@Data
+public class CommonValidList<E> implements List<E> {
+
+    @Valid
+    private List<E> list = new LinkedList<>();
+
+    @Override
+    public int size() {
+        return list.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return list.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return list.contains(o);
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return list.iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return list.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+        return list.toArray(a);
+    }
+
+    @Override
+    public boolean add(E e) {
+        return list.add(e);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        return list.remove(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return list.containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends E> c) {
+        return list.addAll(c);
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends E> c) {
+        return list.addAll(index, c);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        return list.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        return list.retainAll(c);
+    }
+
+    @Override
+    public void clear() {
+        list.clear();
+    }
+
+    @Override
+    public E get(int index) {
+        return list.get(index);
+    }
+
+    @Override
+    public E set(int index, E element) {
+        return list.set(index, element);
+    }
+
+    @Override
+    public void add(int index, E element) {
+        list.add(index, element);
+    }
+
+    @Override
+    public E remove(int index) {
+        return list.remove(index);
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        return list.indexOf(o);
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        return list.lastIndexOf(o);
+    }
+
+    @Override
+    public ListIterator<E> listIterator() {
+        return list.listIterator();
+    }
+
+    @Override
+    public ListIterator<E> listIterator(int index) {
+        return list.listIterator(index);
+    }
+
+    @Override
+    public List<E> subList(int fromIndex, int toIndex) {
+        return list.subList(fromIndex, toIndex);
+    }
+}

+ 32 - 0
snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonWrapperInterface.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.common.pojo;
+
+import cn.hutool.json.JSONObject;
+
+/**
+ * 通用包装接口
+ *
+ * @author xuyuxiang
+ * @date 2022/9/15 21:17
+ */
+public interface CommonWrapperInterface<T> {
+
+    /**
+     * 执行包装
+     *
+     * @author xuyuxiang
+     * @date 2022/9/15 21:17
+     */
+    JSONObject doWrap(T wrapperObject);
+}

+ 37 - 0
snowy-common/src/main/java/vip/xiaonuo/common/prop/CommonProperties.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.common.prop;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 通用基础配置
+ *
+ * @author xuyuxiang
+ * @date 2022/1/2 17:03
+ */
+@Getter
+@Setter
+@Component
+@ConfigurationProperties(prefix = "snowy.config.common")
+public class CommonProperties {
+
+    /** 前端地址 */
+    private String frontUrl;
+
+    /** 后端地址 */
+    private String backendUrl;
+}

+ 31 - 0
snowy-common/src/main/java/vip/xiaonuo/common/sse/CommonSseParam.java

@@ -0,0 +1,31 @@
+/*
+ * 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.common.sse;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 通用SSE参数
+ *
+ * @author diantu
+ * @date 2023/7/10
+ */
+@Getter
+@Setter
+public class CommonSseParam {
+
+    private String clientId;
+
+    private String loginId;
+}

+ 30 - 0
snowy-common/src/main/java/vip/xiaonuo/common/timer/CommonTimerTaskRunner.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.common.timer;
+
+/**
+ * 定时器执行者,定时器都要实现本接口,并需要把实现类加入到spring容器中
+ *
+ * @author xuyuxiang
+ * @date 2022/8/15 16:09
+ **/
+public interface CommonTimerTaskRunner {
+
+    /**
+     * 任务执行的具体内容
+     *
+     * @author xuyuxiang
+     * @date 2022/8/15 16:09
+     **/
+    void action();
+}

+ 126 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonAvatarUtil.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.common.util;
+
+import cn.hutool.core.img.ImgUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 通用头像工具类,生成文字头像
+ *
+ * @author xuyuxiang
+ * @date 2022/7/5 17:36
+ **/
+public class CommonAvatarUtil {
+
+    /**
+     * 绘制字体头像,如果是英文名,只显示首字母大写,
+     * 如果是中文名,只显示最后两个字
+     * 返回图片base64
+     *
+     * @author xuyuxiang
+     * @date 2022/7/5 17:36
+     **/
+    public static String generateImg(String name) {
+        int width = 100;
+        int height = 100;
+        int nameLength = name.length();
+        String nameWritten;
+        // 如果用户输入的姓名少于等于2个字符,不用截取
+        if (nameLength <= 2) {
+            nameWritten = name;
+        } else {
+            // 如果用户输入的姓名大于等于3个字符,截取后面两位
+            String first = StrUtil.sub(name, 0, 1);
+            if (isChinese(first)) {
+                // 截取倒数两位汉字
+                nameWritten = name.substring(nameLength - 2);
+            } else {
+                // 截取前面的两个英文字母
+                nameWritten = StrUtil.sub(name, 0, 1).toUpperCase();
+            }
+        }
+        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
+        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        g2.setBackground(getRandomColor());
+        g2.clearRect(0, 0, width, height);
+        g2.setPaint(Color.WHITE);
+        Font font;
+        // 两个字及以上
+        if(nameWritten.length() >= 2) {
+            font = new Font("微软雅黑", Font.BOLD, 30);
+            g2.setFont(font);
+            String firstWritten = StrUtil.sub(nameWritten, 0, 1);
+            String secondWritten = StrUtil.sub(nameWritten, 0, 2);
+            // 两个中文 如 言曌
+            if (isChinese(firstWritten) && isChinese(secondWritten)) {
+                g2.drawString(nameWritten, 20, 60);
+            }
+            // 首中次英 如 罗Q
+            else if (isChinese(firstWritten) && !isChinese(secondWritten)) {
+                g2.drawString(nameWritten, 27, 60);
+                // 首英 如 AB
+            } else {
+                nameWritten = nameWritten.substring(0,1);
+            }
+        }
+        // 一个字
+        if(nameWritten.length() == 1) {
+            // 中文
+            if(isChinese(nameWritten)) {
+                font = new Font("微软雅黑", Font.PLAIN, 50);
+                g2.setFont(font);
+                g2.drawString(nameWritten, 25, 70);
+            } else {
+                font = new Font("微软雅黑", Font.PLAIN, 55);
+                g2.setFont(font);
+                g2.drawString(nameWritten.toUpperCase(), 33, 67);
+            }
+        }
+        return ImgUtil.toBase64DataUri(bufferedImage, "jpg");
+    }
+
+    /**
+     * 获得随机颜色
+     *
+     * @author xuyuxiang
+     * @date 2022/7/5 17:41
+     **/
+    private static Color getRandomColor() {
+        String[] beautifulColors =
+                new String[]{"114,101,230", "255,191,0", "0,162,174", "245,106,0", "24,144,255", "96,109,128"};
+        String[] color = beautifulColors[RandomUtil.randomInt(beautifulColors.length)].split(StrUtil.COMMA);
+        return new Color(Integer.parseInt(color[0]), Integer.parseInt(color[1]),
+                Integer.parseInt(color[2]));
+    }
+
+    /**
+     * 判断字符串是否为中文
+     *
+     * @author xuyuxiang
+     * @date 2022/7/5 17:41
+     **/
+    private static boolean isChinese(String str) {
+        String regEx = "[\\u4e00-\\u9fa5]+";
+        Pattern p = Pattern.compile(regEx);
+        Matcher m = p.matcher(str);
+        return m.find();
+    }
+}

+ 142 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonCryptogramUtil.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.common.util;
+
+import com.antherd.smcrypto.sm2.Sm2;
+import com.antherd.smcrypto.sm3.Sm3;
+import com.antherd.smcrypto.sm4.Sm4;
+import com.antherd.smcrypto.sm4.Sm4Options;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 加密工具类,本框架目前使用 https://github.com/antherd/sm-crypto 项目中一些加解密方式
+ * 使用小伙伴需要过等保密评相关,请在此处更改为自己的加密方法,或加密机,使用加密机同时需要替换公钥,私钥在内部无法导出,提供加密的方法
+ * 如果不涉及到加密机方面的内容,请更改公私要为自己重新生成的,生成方式请看集成的sm-crypto主页
+ *
+ * @author yubaoshan
+ * @date 2022/9/15 21:51
+ */
+@Slf4j
+public class CommonCryptogramUtil {
+
+    /** 公钥 */
+    private static final String PUBLIC_KEY = "04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54";
+
+    /** 私钥 */
+    private static final String PRIVATE_KEY = "3037723d47292171677ec8bd7dc9af696c7472bc5f251b2cec07e65fdef22e25";
+
+    /** SM4的对称秘钥(生产环境需要改成自己使用的) 16 进制字符串,要求为 128 比特 */
+    private static final String KEY = "0123456789abcdeffedcba9876543210";
+
+    /**
+     * 加密方法(Sm2 的专门针对前后端分离,非对称秘钥对的方式,暴露出去的公钥,对传输过程中的密码加个密)
+     *
+     * @author yubaoshan
+     * @date 2022/9/15 21:51
+     * @param str 待加密数据
+     * @return 加密后的密文
+     */
+    public static String doSm2Encrypt(String str) {
+        return Sm2.doEncrypt(str, PUBLIC_KEY);
+    }
+
+    /**
+     * 解密方法
+     * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可
+     *
+     * @author yubaoshan
+     * @date 2022/9/15 21:51
+     * @param str 密文
+     * @return 解密后的明文
+     */
+    public static String doSm2Decrypt(String str) {
+        // 解密
+        return Sm2.doDecrypt(str, PRIVATE_KEY);
+    }
+
+    /**
+     * 加密方法
+     *
+     * @author yubaoshan
+     * @date 2022/9/15 21:51
+     * @param str 待加密数据
+     * @return 加密后的密文
+     */
+    public static String doSm4CbcEncrypt(String str) {
+        // SM4 加密  cbc模式
+        Sm4Options sm4Options4 = new Sm4Options();
+        sm4Options4.setMode("cbc");
+        sm4Options4.setIv("fedcba98765432100123456789abcdef");
+        return Sm4.encrypt(str, KEY, sm4Options4);
+    }
+
+    /**
+     * 解密方法
+     * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可
+     *
+     * @author yubaoshan
+     * @date 2022/9/15 21:51
+     * @param str 密文
+     * @return 解密后的明文
+     */
+    public static String doSm4CbcDecrypt(String str) {
+        // 解密,cbc 模式,输出 utf8 字符串
+        Sm4Options sm4Options8 = new Sm4Options();
+        sm4Options8.setMode("cbc");
+        sm4Options8.setIv("fedcba98765432100123456789abcdef");
+        String docString =  Sm4.decrypt(str, KEY, sm4Options8);
+        if ("".equals(docString)) {
+            log.warn(">>> 字段解密失败,返回原文值:{}", str);
+            return str;
+        } else {
+            return docString;
+        }
+    }
+
+    /**
+     * 纯签名
+     *
+     * @author yubaoshan
+     * @date 2022/9/15 21:51
+     * @param str 待签名数据
+     * @return 签名结果
+     */
+    public static String doSignature(String str) {
+        return Sm2.doSignature(str, PRIVATE_KEY);
+    }
+
+    /**
+     * 验证签名结果
+     *
+     * @author yubaoshan
+     * @date 2022/9/15 21:51
+     * @param originalStr 签名原文数据
+     * @param str 签名结果
+     * @return 是否通过
+     */
+    public static boolean doVerifySignature(String originalStr, String str) {
+        return Sm2.doVerifySignature(originalStr, str, PUBLIC_KEY);
+    }
+
+    /**
+     * 通过杂凑算法取得hash值,用于做数据完整性保护
+     *
+     * @author yubaoshan
+     * @date 2022/9/15 21:51
+     * @param str 字符串
+     * @return hash 值
+     */
+    public static String doHashValue(String str) {
+        return Sm3.sm3(str);
+    }
+}

+ 63 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonDownloadUtil.java

@@ -0,0 +1,63 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.URLUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 文件下载工具类,使用本类前,对参数校验的异常使用CommonResponseUtil.renderError()方法进行渲染
+ *
+ * @author xuyuxiang
+ * @date 2020/8/5 21:45
+ */
+@Slf4j
+public class CommonDownloadUtil {
+
+    /**
+     * 下载文件
+     *
+     * @param file     要下载的文件
+     * @param response 响应
+     * @author xuyuxiang
+     * @date 2020/8/5 21:46
+     */
+    public static void download(File file, HttpServletResponse response) {
+        download(file.getName(), FileUtil.readBytes(file), response);
+    }
+
+    /**
+     * 下载文件
+     *
+     * @author xuyuxiang
+     * @date 2022/7/31 10:57
+     */
+    public static void download(String fileName, byte[] fileBytes, HttpServletResponse response) {
+        try {
+            response.setHeader("Content-Disposition", "attachment;filename=" + URLUtil.encode(fileName));
+            response.addHeader("Content-Length", "" + fileBytes.length);
+            response.setHeader("Access-Control-Allow-Origin", "*");
+            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+            response.setContentType("application/octet-stream;charset=UTF-8");
+            IoUtil.write(response.getOutputStream(), true, fileBytes);
+        } catch (IOException e) {
+            log.error(">>> 文件下载异常:", e);
+        }
+    }
+}

+ 50 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonEmailUtil.java

@@ -0,0 +1,50 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import vip.xiaonuo.common.exception.CommonException;
+
+/**
+ * 通用邮件工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/8/25 15:10
+ **/
+public class CommonEmailUtil {
+
+    /**
+     * 判断是否邮箱
+     *
+     * @author xuyuxiang
+     * @date 2022/8/15 13:32
+     **/
+    public static boolean isEmail(String email) {
+        return  Validator.isEmail(email);
+    }
+
+    /**
+     * 校验邮箱格式
+     *
+     * @author xuyuxiang
+     * @date 2022/8/15 13:32
+     **/
+    public static void validEmail(String emails) {
+        StrUtil.split(emails, StrUtil.COMMA).forEach(email -> {
+            if(!isEmail(email)) {
+                throw new CommonException("邮件地址:{}格式错误", email);
+            }
+        });
+    }
+}

+ 40 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonFilterExceptionUtil.java

@@ -0,0 +1,40 @@
+/*
+ * 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.common.util;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * 过滤器异常工具类,用于处理过滤器中的异常
+ * 原理:将异常转发到/errorView进行处理
+ *
+ * @author xuyuxiang
+ * @date 2022/7/18 18:59
+ **/
+public class CommonFilterExceptionUtil {
+
+    /**
+     * 处理过滤器中的异常
+     *
+     * @author xuyuxiang
+     * @date 2022/7/18 19:00
+     **/
+    public static void handleFilterException(ServletRequest request, ServletResponse response, Exception e) {
+        try {
+            request.setAttribute("model", e);
+            request.getRequestDispatcher("/errorView").forward(request, response);
+        } catch (Exception ignored) {
+        }
+    }
+}

+ 108 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonIpAddressUtil.java

@@ -0,0 +1,108 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.net.Ipv4Util;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.servlet.ServletUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.lionsoul.ip2region.xdb.Searcher;
+import vip.xiaonuo.common.exception.CommonException;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * 根据ip地址定位工具类,离线方式
+ * 参考地址:https://gitee.com/lionsoul/ip2region/tree/master/binding/java
+ *
+ * @author xuyuxiang
+ * @date 2020/3/16 11:25
+ */
+@Slf4j
+public class CommonIpAddressUtil {
+
+    private static final String LOCAL_REMOTE_HOST = "0:0:0:0:0:0:0:1";
+
+    private static final Searcher searcher;
+
+    static {
+        String fileName = "/ip2region.xdb";
+        File existFile = FileUtil.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
+        if(!FileUtil.exist(existFile)) {
+            InputStream resourceAsStream = CommonIpAddressUtil.class.getResourceAsStream(fileName);
+            if(ObjectUtil.isEmpty(resourceAsStream)) {
+                throw new CommonException("CommonIpAddressUtil初始化失败,原因:IP地址库数据不存在");
+            }
+            FileUtil.writeFromStream(resourceAsStream, existFile);
+        }
+
+        String dbPath = existFile.getPath();
+
+        // 1、从 dbPath 加载整个 xdb 到内存。
+        byte[] cBuff;
+        try {
+            cBuff = Searcher.loadContentFromFile(dbPath);
+        } catch (Exception e) {
+            log.error(">>> CommonIpAddressUtil初始化异常:", e);
+            throw new CommonException("CommonIpAddressUtil初始化异常");
+        }
+
+        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
+        try {
+            searcher = Searcher.newWithBuffer(cBuff);
+        } catch (Exception e) {
+            log.error(">>> CommonIpAddressUtil初始化异常:", e);
+            throw new CommonException("CommonIpAddressUtil初始化异常");
+        }
+    }
+
+    /**
+     * 获取客户端ip
+     *
+     * @author xuyuxiang
+     * @date 2020/3/19 9:32
+     */
+    public static String getIp(HttpServletRequest request) {
+        if (ObjectUtil.isEmpty(request)) {
+            return Ipv4Util.LOCAL_IP;
+        } else {
+            try {
+                String remoteHost = ServletUtil.getClientIP(request);
+                return LOCAL_REMOTE_HOST.equals(remoteHost) ? Ipv4Util.LOCAL_IP : remoteHost;
+            } catch (Exception e) {
+                log.error(">>> 获取客户端ip异常:", e);
+                return Ipv4Util.LOCAL_IP;
+            }
+        }
+    }
+
+    /**
+     * 根据IP地址离线获取城市
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 23:14
+     */
+    public static String getCityInfo(String ip) {
+        try {
+            ip = ip.trim();
+            // 3、执行查询
+            String region = searcher.searchByStr(ip);
+            return region.replace("0|", "").replace("|0", "");
+        } catch (Exception e) {
+            return "未知";
+        }
+    }
+}

+ 72 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonJoinPointUtil.java

@@ -0,0 +1,72 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * Spring切面工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 15:51
+ */
+public class CommonJoinPointUtil {
+
+    /**
+     * 获取切面的参数JSON
+     *
+     * @author xuyuxiang
+     * @date 2022/9/2 15:51
+     */
+    public static String getArgsJsonString(JoinPoint joinPoint) {
+        Signature signature = joinPoint.getSignature();
+        // 参数名数组
+        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
+        // 构造参数组集合
+        Map<String, Object> map = MapUtil.newHashMap();
+        Object[] args = joinPoint.getArgs();
+        for (int i = 0; i < args.length; i++) {
+            if(ObjectUtil.isNotEmpty(args[i]) && isUsefulParam(args[i])) {
+                if(JSONUtil.isTypeJSON(StrUtil.toString(args[i]))) {
+                    map.put(parameterNames[i], JSONUtil.parseObj(args[i]));
+                } else {
+                    map.put(parameterNames[i], JSONUtil.toJsonStr(args[i]));
+                }
+            }
+        }
+        return JSONUtil.toJsonStr(map);
+    }
+
+    /**
+     * 判断是否需要拼接的参数,过滤掉HttpServletRequest,MultipartFile,HttpServletResponse等类型参数
+     *
+     * @author xuyuxiang
+     * @date 2022/9/2 15:51
+     */
+    private static boolean isUsefulParam(Object arg) {
+        return !(arg instanceof MultipartFile) &&
+                !(arg instanceof HttpServletRequest) &&
+                !(arg instanceof HttpServletResponse);
+    }
+}

+ 125 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonNetWorkInfoUtil.java

@@ -0,0 +1,125 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.system.SystemUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.*;
+
+/**
+ * 通用获取当前网速工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/9/1 23:45
+ */
+@Slf4j
+public class CommonNetWorkInfoUtil {
+
+    /**
+     * 网速测速时间2s
+     */
+    private static final int SLEEP_SECONDS = 2;
+
+    /**
+     * 获取网络上下行速率,格式{"UP": "123KB/S, "DOWN": "345KB/S"}
+     *
+     * @author xuyuxiang
+     * @date 2022/9/1 23:51
+     */
+    public static Map<String, String> getNetworkUpRate() {
+        Map<String, String> result = new HashMap<>();
+        Process pro = null;
+        Runtime r = Runtime.getRuntime();
+        BufferedReader input = null;
+        try {
+            boolean isWindows = SystemUtil.getOsInfo().isWindows();
+            String command = isWindows ? "netstat -e" : "ifconfig";
+            pro = r.exec(command);
+            input = new BufferedReader(new InputStreamReader(pro.getInputStream()));
+            long[] result1 = readInLine(input, isWindows);
+            Thread.sleep(SLEEP_SECONDS * 1000);
+            pro.destroy();
+            input.close();
+            pro = r.exec(command);
+            input = new BufferedReader(new InputStreamReader(pro.getInputStream()));
+            long[] result2 = readInLine(input, isWindows);
+            String upSpeed = FileUtil.readableFileSize(Convert.toLong(NumberUtil
+                    .div(NumberUtil.sub(result2[0], result1[0]), SLEEP_SECONDS)));
+            String downSpeed = FileUtil.readableFileSize(Convert.toLong(NumberUtil
+                    .div(NumberUtil.sub(result2[1], result1[1]), SLEEP_SECONDS)));
+            result.put("UP", upSpeed + (upSpeed.endsWith("B")?"/S":"B/S"));
+            result.put("DOWN", downSpeed + (downSpeed.endsWith("B")?"/S":"B/S"));
+        } catch (Exception e) {
+            log.info(">>> 网络测速失败:", e);
+        } finally {
+            if (input != null) {
+                try {
+                    input.close();
+                } catch (IOException e) {
+                    log.info(">>> 网络测速失败:", e);
+                }
+            }
+            Optional.ofNullable(pro).ifPresent(Process::destroy);
+        }
+        return result;
+    }
+
+    private static String formatNumber(double f) {
+        return new Formatter().format("%.2f", f).toString();
+    }
+
+    private static long[] readInLine(BufferedReader input, boolean isWindows) {
+        long[] arr = new long[2];
+        StringTokenizer tokenStat;
+        try {
+            if (isWindows) {
+                // 获取windows环境下的网口上下行速率
+                input.readLine();
+                input.readLine();
+                input.readLine();
+                input.readLine();
+                tokenStat = new StringTokenizer(input.readLine());
+                tokenStat.nextToken();
+                arr[0] = Long.parseLong(tokenStat.nextToken());
+                arr[1] = Long.parseLong(tokenStat.nextToken());
+            } else {
+                // 获取linux环境下的网口上下行速率
+                long rx = 0, tx = 0;
+                String line = null;
+                //RX packets:4171603 errors:0 dropped:0 overruns:0 frame:0
+                //TX packets:4171603 errors:0 dropped:0 overruns:0 carrier:0
+                while ((line = input.readLine()) != null) {
+                    if (line.contains("RX packets")) {
+                        rx += Long.parseLong(line.substring(line.indexOf("RX packets") + 11, line.indexOf(" ",
+                                line.indexOf("RX packets") + 11)));
+                    } else if (line.contains("TX packets")) {
+                        tx += Long.parseLong(line.substring(line.indexOf("TX packets") + 11, line.indexOf(" ",
+                                line.indexOf("TX packets") + 11)));
+                    }
+                }
+                arr[0] = rx;
+                arr[1] = tx;
+            }
+        } catch (Exception e) {
+            log.error(">>> 网络测速异常:", e);
+        }
+        return arr;
+    }
+}

+ 65 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonResponseUtil.java

@@ -0,0 +1,65 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.ContentType;
+import cn.hutool.json.JSONUtil;
+import vip.xiaonuo.common.pojo.CommonResult;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 通用响应工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/8/4 9:40
+ **/
+public class CommonResponseUtil {
+
+    /**
+     * 以流的方式响应错误信息,默认错误消息
+     *
+     * @author xuyuxiang
+     * @date 2022/8/4 9:41
+     **/
+    public static void renderError(HttpServletResponse response) throws IOException {
+        renderError(response, null);
+    }
+
+    /**
+     * 以流的方式响应错误信息,指定错误消息
+     *
+     * @author xuyuxiang
+     * @date 2022/8/4 9:41
+     **/
+    public static void renderError(HttpServletResponse response, String msg) throws IOException {
+        response.setCharacterEncoding(CharsetUtil.UTF_8);
+        response.setContentType(ContentType.JSON.toString());
+        response.getWriter().write(JSONUtil.toJsonStr(ObjectUtil.isNotEmpty(msg)?CommonResult.error(msg):CommonResult.error()));
+    }
+
+    /**
+     * 以流的方式响应错误信息,指定错误码和错误消息
+     *
+     * @author xuyuxiang
+     * @date 2022/8/4 9:41
+     **/
+    public static void renderError(HttpServletResponse response, Integer code, String msg) throws IOException {
+        response.setCharacterEncoding(CharsetUtil.UTF_8);
+        response.setContentType(ContentType.JSON.toString());
+        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.get(code, msg, null)));
+    }
+}

+ 99 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonServletUtil.java

@@ -0,0 +1,99 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import vip.xiaonuo.common.exception.CommonException;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * HttpServlet工具类,获取当前request和response
+ *
+ * @author xuyuxiang
+ * @date 2020/3/30 15:09
+ */
+@Slf4j
+public class CommonServletUtil {
+
+    /**
+     * 从请求中中获取参数
+     *
+     * @author xuyuxiang
+     * @date 2021/10/14 10:44
+     **/
+    public static String getParamFromRequest(String paramName) {
+        HttpServletRequest request = getRequest();
+
+        // 1. 尝试从请求体里面读取
+        String paramValue = request.getParameter(paramName);
+
+        // 2. 尝试从header里读取
+        if (ObjectUtil.isEmpty(paramValue)) {
+            paramValue = request.getHeader(paramName);
+        }
+        // 3. 尝试从cookie里读取
+        if (ObjectUtil.isEmpty(paramValue)) {
+            Cookie[] cookies = request.getCookies();
+            if(ObjectUtil.isNotEmpty(cookies)) {
+                for (Cookie cookie : cookies) {
+                    String cookieName = cookie.getName();
+                    if (cookieName.equals(paramName)) {
+                        return cookie.getValue();
+                    }
+                }
+            }
+        }
+        // 4. 返回
+        return paramValue;
+    }
+
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes servletRequestAttributes;
+        try {
+            servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        } catch (Exception e) {
+            log.error(">>> 非Web上下文无法获取Request:", e);
+            throw new CommonException("非Web上下文无法获取Request");
+        }
+        if (servletRequestAttributes == null) {
+            throw new CommonException("非Web上下文无法获取Request");
+        } else {
+            return servletRequestAttributes.getRequest();
+        }
+    }
+
+    public static HttpServletResponse getResponse() {
+        ServletRequestAttributes servletRequestAttributes;
+        try {
+            servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        } catch (Exception e) {
+            log.error(">>> 非Web上下文无法获取Response:", e);
+            throw new CommonException("非Web上下文无法获取Response");
+        }
+        if (servletRequestAttributes == null) {
+            throw new CommonException("非Web上下文无法获取Response");
+        } else {
+            return servletRequestAttributes.getResponse();
+        }
+    }
+
+    public static boolean isWeb() {
+        return RequestContextHolder.getRequestAttributes() != null;
+    }
+}

+ 183 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonTimeFormatUtil.java

@@ -0,0 +1,183 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+
+import java.util.Date;
+
+/**
+ * 时间格式化工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/6/24 15:28
+ **/
+public class CommonTimeFormatUtil {
+
+    private static final long ONE_MINUTE_SECONDS = 60;
+
+    private static final int BEFORE_DAWN_HOUR = 6;
+
+    private static final int MORNING_END_HOUR = 12;
+
+    private static final int NOON_END_HOUR = 13;
+
+    private static final int AFTERNOON_END_HOUR = 18;
+
+    private static final int NIGHT_END_HOUR = 24;
+
+    /**
+     * 将日期格式化为仿微信的日期
+     *
+     * @author xuyuxiang
+     * @date 2022/6/24 15:28
+     **/
+    public static String formatWxPastTime(Date date) {
+        if (DateUtil.between(date, DateUtil.date(), DateUnit.SECOND, false) < 0) {
+            //今天之后的时间显示年月日时分
+            return DateUtil.format(date, DatePattern.NORM_DATETIME_MINUTE_PATTERN);
+        } else {
+            //如果是今年
+            if (DateUtil.thisYear() == DateUtil.year(date)) {
+                //如果是今天
+                if (DateUtil.isSameDay(date, DateUtil.date())) {
+                    //相差分钟数
+                    long betweenMinute = DateUtil.between(date, DateUtil.date(), DateUnit.MINUTE);
+                    //如果在1小时之内
+                    if (betweenMinute < ONE_MINUTE_SECONDS) {
+                        //一分钟之内,显示刚刚
+                        if (betweenMinute < 1) {
+                            return "刚刚";
+                        } else {
+                            //一分钟之外,显示xx分钟前
+                            return betweenMinute + "分钟前";
+                        }
+                    } else {
+                        //一小时之外,显示时分
+                        return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm");
+                    }
+                } else if (DateUtil.isSameDay(date, DateUtil.yesterday())) {
+                    //如果是昨天,显示昨天时分
+                    return "昨天 " + DateUtil.format(date, "HH:mm");
+                } else if (isThisWeek(date)) {
+                    //如果是本周
+                    String weekday;
+                    //获取是本周的第几天
+                    int dayOfWeek = DateUtil.dayOfWeek(date) - 1;
+                    switch (dayOfWeek) {
+                        case 1:
+                            weekday = "周一";
+                            break;
+                        case 2:
+                            weekday = "周二";
+                            break;
+                        case 3:
+                            weekday = "周三";
+                            break;
+                        case 4:
+                            weekday = "周四";
+                            break;
+                        case 5:
+                            weekday = "周五";
+                            break;
+                        case 6:
+                            weekday = "周六";
+                            break;
+                        default:
+                            weekday = "周日";
+                            break;
+                    }
+                    //显示本周时分
+                    return weekday + " " + DateUtil.format(date, "HH:mm");
+                } else {
+                    //否则显示月日时分
+                    return DateUtil.format(date, "MM-dd HH:mm");
+                }
+            } else {
+                //本年之外显示年月日时分
+                return DateUtil.format(date, DatePattern.NORM_DATETIME_MINUTE_PATTERN);
+            }
+        }
+    }
+
+    /**
+     * 将秒数格式化为天时分秒
+     *
+     * @author xuyuxiang
+     * @date 2022/6/24 15:29
+     **/
+    public static String formatSeconds(long secondsParam) {
+        String result;
+        long days = secondsParam / ( ONE_MINUTE_SECONDS * ONE_MINUTE_SECONDS * NIGHT_END_HOUR);
+        long hours = (secondsParam % ( ONE_MINUTE_SECONDS * ONE_MINUTE_SECONDS * NIGHT_END_HOUR)) / (ONE_MINUTE_SECONDS * ONE_MINUTE_SECONDS);
+        long minutes = (secondsParam % ( ONE_MINUTE_SECONDS * ONE_MINUTE_SECONDS)) /ONE_MINUTE_SECONDS;
+        long seconds = secondsParam % ONE_MINUTE_SECONDS;
+        if(days > 0) {
+            result = days + "天" + hours + "小时" + minutes + "分钟" + seconds + "秒";
+        } else if(hours > 0) {
+            result = hours + "小时" + minutes + "分钟" + seconds + "秒";
+        } else if(minutes > 0) {
+            result = minutes + "分钟" + seconds + "秒";
+        } else {
+            result = seconds + "秒";
+        }
+        return result;
+    }
+
+    /**
+     * 判断日期是否是本周
+     *
+     * @param date 要判断的日期
+     * @return boolean
+     * @author xuyuxiang
+     * @date 2020/8/6 12:10
+     **/
+    private static boolean isThisWeek(Date date) {
+        //获取本周开始时间
+        DateTime beginOfWeek = DateUtil.beginOfWeek(DateUtil.date());
+        //获取与本周开始时间相差的天数
+        long betweenBegin = DateUtil.between(date, beginOfWeek, DateUnit.DAY, false) + 1;
+        //如果是同一天,或相差天数小于0,则是本周
+        return DateUtil.isSameDay(date, beginOfWeek) || betweenBegin < 0;
+    }
+
+    /**
+     * 根据今天日期获取早中晚
+     *
+     * @author xuyuxiang
+     * @date 2020/8/6 14:42
+     **/
+    private static String getTodayHour(Date date) {
+        String result = "";
+        int hour = DateUtil.hour(date, true);
+        if (hour >= 0 && hour <= BEFORE_DAWN_HOUR) {
+            result = "凌晨";
+        }
+        if (hour > BEFORE_DAWN_HOUR && hour < MORNING_END_HOUR) {
+            result = "上午";
+        }
+        if (hour == MORNING_END_HOUR) {
+            result = "中午";
+        }
+        if (hour >= NOON_END_HOUR && hour <= AFTERNOON_END_HOUR) {
+            result = "下午";
+        }
+        if (hour > AFTERNOON_END_HOUR && hour <= NIGHT_END_HOUR) {
+            result = "晚上";
+        }
+        return result;
+    }
+}

+ 80 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonUaUtil.java

@@ -0,0 +1,80 @@
+/*
+ * 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.common.util;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.servlet.ServletUtil;
+import cn.hutool.http.useragent.Browser;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 用户代理工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/9/2 15:34
+ */
+public class CommonUaUtil {
+
+    /**
+     * 获取客户端浏览器
+     *
+     * @author xuyuxiang
+     * @date 2020/3/19 14:53
+     */
+    public static String getBrowser(HttpServletRequest request) {
+        UserAgent userAgent = getUserAgent(request);
+        if (ObjectUtil.isEmpty(userAgent)) {
+            return StrUtil.DASHED;
+        } else {
+            String browser = userAgent.getBrowser().toString();
+            return "Unknown".equals(browser) ? StrUtil.DASHED : browser;
+        }
+    }
+
+    /**
+     * 获取客户端操作系统
+     *
+     * @author xuyuxiang
+     * @date 2022/9/2 15:36
+     */
+    public static String getOs(HttpServletRequest request) {
+        UserAgent userAgent = getUserAgent(request);
+        if (ObjectUtil.isEmpty(userAgent)) {
+            return StrUtil.DASHED;
+        } else {
+            String os = userAgent.getOs().toString();
+            return "Unknown".equals(os) ? StrUtil.DASHED : os;
+        }
+    }
+
+    /**
+     * 获取请求代理头
+     *
+     * @author xuyuxiang
+     * @date 2022/9/2 15:36
+     */
+    private static UserAgent getUserAgent(HttpServletRequest request) {
+        String userAgentStr = ServletUtil.getHeaderIgnoreCase(request, "User-Agent");
+        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
+        if (ObjectUtil.isNotEmpty(userAgentStr)) {
+            if ("Unknown".equals(userAgent.getBrowser().getName())) {
+                userAgent.setBrowser(new Browser(userAgentStr, null, ""));
+            }
+        }
+        return userAgent;
+    }
+}

BIN
snowy-common/src/main/resources/ip2region.xdb


+ 15 - 0
snowy-plugin-api/README.md

@@ -0,0 +1,15 @@
+# 插件API接口模块
+
+####登录鉴权插件api接口: snowy-plugin-auth-api
+
+####业务功能插件api接口: snowy-plugin-biz-api
+
+####C端功能插件api接口: snowy-plugin-client-api
+
+####开发工具插件api接口: snowy-plugin-dev-api
+
+####代码生成api接口: snowy-plugin-gen-api
+
+####移动端管理api接口: snowy-plugin-mobile-api
+
+####系统功能插件api接口: snowy-plugin-sys-api

+ 39 - 0
snowy-plugin-api/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-api</artifactId>
+    <packaging>pom</packaging>
+    <description>插件API接口模块</description>
+
+    <modules>
+        <!-- 登录鉴权插件api接口 -->
+        <module>snowy-plugin-auth-api</module>
+
+        <!-- 业务功能插件api接口 -->
+        <module>snowy-plugin-biz-api</module>
+
+        <!-- C端功能插件api接口 -->
+        <module>snowy-plugin-client-api</module>
+
+        <!-- 开发工具插件api接口 -->
+        <module>snowy-plugin-dev-api</module>
+
+        <!-- 代码生成插件api接口 -->
+        <module>snowy-plugin-gen-api</module>
+
+        <!-- 移动端管理插件api接口 -->
+        <module>snowy-plugin-mobile-api</module>
+
+        <!-- 系统功能插件api接口 -->
+        <module>snowy-plugin-sys-api</module>
+    </modules>
+</project>

+ 1 - 0
snowy-plugin-api/snowy-plugin-auth-api/README.md

@@ -0,0 +1 @@
+# 登录鉴权插件api接口

+ 30 - 0
snowy-plugin-api/snowy-plugin-auth-api/pom.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-auth-api</artifactId>
+    <packaging>jar</packaging>
+    <description>登录鉴权插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+        </dependency>
+
+        <!-- sa-token-core -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-core</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 124 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/api/SaBaseLoginUserApi.java

@@ -0,0 +1,124 @@
+/*
+ * 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.api;
+
+import cn.hutool.json.JSONObject;
+import vip.xiaonuo.auth.core.pojo.SaBaseClientLoginUser;
+import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
+
+import java.util.List;
+
+/**
+ * 登录用户API,由其他模块实现
+ *
+ * @author xuyuxiang
+ * @date 2021/12/23 21:48
+ */
+public interface SaBaseLoginUserApi {
+
+    /**
+     * 根据id获取B端用户信息,查不到则返回null
+     *
+     * @author xuyuxiang
+     * @date 2022/3/10 16:14
+     **/
+    SaBaseLoginUser getUserById(String id);
+
+    /**
+     * 根据id获取C端用户信息,查不到则返回null
+     *
+     * @author xuyuxiang
+     * @date 2022/3/10 16:14
+     **/
+    SaBaseClientLoginUser getClientUserById(String id);
+
+    /**
+     * 根据账号获取B端用户信息,查不到则返回null
+     *
+     * @author xuyuxiang
+     * @date 2022/3/10 16:14
+     **/
+    SaBaseLoginUser getUserByAccount(String account);
+
+    /**
+     * 根据账号获取C端用户信息,查不到则返回null
+     *
+     * @author xuyuxiang
+     * @date 2022/3/10 16:14
+     **/
+    SaBaseClientLoginUser getClientUserByAccount(String account);
+
+    /**
+     * 根据手机号获取B端用户信息,查不到则返回null
+     *
+     * @author xuyuxiang
+     * @date 2022/3/10 16:14
+     **/
+    SaBaseLoginUser getUserByPhone(String phone);
+
+    /**
+     * 根据手机号获取C端用户信息,查不到则返回null
+     *
+     * @author xuyuxiang
+     * @date 2022/3/10 16:14
+     **/
+    SaBaseClientLoginUser getClientUserByPhone(String phone);
+
+    /**
+     * 根据用户id获取用户集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:53
+     */
+    List<JSONObject> listUserByUserIdList(List<String> userIdList);
+
+    /**
+     * 根据用户id获取角色集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:53
+     */
+    List<JSONObject> getRoleListByUserId(String userId);
+
+    /**
+     * 根据角色id和用户id集合获取按钮码集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:54
+     */
+    List<String> getButtonCodeListListByUserAndRoleIdList(List<String> userAndRoleIdList);
+
+    /**
+     * 根据角色id和用户id集合获取移动端按钮码集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:54
+     */
+    List<String> getMobileButtonCodeListListByUserIdAndRoleIdList(List<String> userAndRoleIdList);
+
+    /**
+     * 根据角色id和用户id集合获取权限集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:54
+     */
+    List<JSONObject> getPermissionListByUserIdAndRoleIdList(List<String> userAndRoleIdList, String orgId);
+
+    /**
+     * 更新用户的登录时间和登录ip等信息
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:57
+     */
+    void updateUserLoginInfo(String userId, String device);
+}

+ 35 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckLogin.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.core.annotation;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import vip.xiaonuo.auth.core.util.StpClientUtil;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 登录认证(前台User版):只有登录之后才能进入该方法
+ * 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author xuyuxiang
+ * @date 2022/3/10 10:39
+ **/
+@SaCheckLogin(type = StpClientUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaClientCheckLogin {
+
+}

+ 51 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckPermission.java

@@ -0,0 +1,51 @@
+/*
+ * 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.annotation;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaMode;
+import org.springframework.core.annotation.AliasFor;
+import vip.xiaonuo.auth.core.util.StpClientUtil;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 权限认证(前台User版):必须具有指定权限才能进入该方法
+ * 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author xuyuxiang
+ * @date 2022/3/10 10:40
+ **/
+@SaCheckPermission(type = StpClientUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaClientCheckPermission {
+
+    /**
+     * 需要校验的权限码
+     * @return 需要校验的权限码
+     */
+    @AliasFor(annotation = SaCheckPermission.class)
+    String [] value() default {};
+
+    /**
+     * 验证模式:AND | OR,默认AND
+     * @return 验证模式
+     */
+    @AliasFor(annotation = SaCheckPermission.class)
+    SaMode mode() default SaMode.AND;
+
+}

+ 52 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckRole.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.core.annotation;
+
+
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+import org.springframework.core.annotation.AliasFor;
+import vip.xiaonuo.auth.core.util.StpClientUtil;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 角色认证(前台User版):必须具有指定角色标识才能进入该方法
+ * 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author xuyuxiang
+ * @date 2022/3/10 10:41
+ **/
+@SaCheckRole(type = StpClientUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaClientCheckRole {
+
+    /**
+     * 需要校验的角色标识
+     * @return 需要校验的角色标识
+     */
+    @AliasFor(annotation = SaCheckRole.class)
+    String [] value() default {};
+
+    /**
+     * 验证模式:AND | OR,默认AND
+     * @return 验证模式
+     */
+    @AliasFor(annotation = SaCheckRole.class)
+    SaMode mode() default SaMode.AND;
+
+}

+ 49 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/enums/SaClientTypeEnum.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.core.enums;
+
+import lombok.Getter;
+import vip.xiaonuo.common.exception.CommonException;
+
+/**
+ * 登录端类型枚举
+ *
+ * @author xuyuxiang
+ * @date 2021/10/11 14:02
+ **/
+@Getter
+public enum SaClientTypeEnum {
+
+    /**
+     * B端用户
+     */
+    B("B"),
+
+    /**
+     * C端用户
+     */
+    C("C");
+
+    private final String value;
+
+    SaClientTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public static void validate(String value) {
+        boolean flag = B.getValue().equals(value) || C.getValue().equals(value);
+        if(!flag) {
+            throw new CommonException("不支持的登录端类型:{}", value);
+        }
+    }
+}

+ 233 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseClientLoginUser.java

@@ -0,0 +1,233 @@
+/*
+ * 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.pojo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 基础的C端登录用户对象,可继承此类扩展更多属性
+ *
+ * @author xuyuxiang
+ * @date 2021/12/23 21:49
+ */
+@Getter
+@Setter
+public abstract class SaBaseClientLoginUser {
+
+    /** id */
+    @ApiModelProperty(value = "id", position = 1)
+    private String id;
+
+    /** 头像 */
+    @ApiModelProperty(value = "头像,图片base64", position = 2)
+    private String avatar;
+
+    /** 签名 */
+    @ApiModelProperty(value = "签名,图片base64", position = 3)
+    private String signature;
+
+    /** 账号 */
+    @ApiModelProperty(value = "账号", position = 4)
+    private String account;
+
+    /** 姓名 */
+    @ApiModelProperty(value = "姓名", position = 5)
+    private String name;
+
+    /** 昵称 */
+    @ApiModelProperty(value = "昵称", position = 6)
+    private String nickname;
+
+    /** 性别 */
+    @ApiModelProperty(value = "性别", position = 7)
+    private String gender;
+
+    /** 年龄 */
+    @ApiModelProperty(value = "年龄", position = 8)
+    private String age;
+
+    /** 出生日期 */
+    @ApiModelProperty(value = "出生日期", position = 9)
+    private String birthday;
+
+    /** 民族 */
+    @ApiModelProperty(value = "民族", position = 10)
+    private String nation;
+
+    /** 籍贯 */
+    @ApiModelProperty(value = "籍贯", position = 11)
+    private String nativePlace;
+
+    /** 家庭住址 */
+    @ApiModelProperty(value = "家庭住址", position = 12)
+    private String homeAddress;
+
+    /** 通信地址 */
+    @ApiModelProperty(value = "通信地址", position = 13)
+    private String mailingAddress;
+
+    /** 证件类型 */
+    @ApiModelProperty(value = "证件类型", position = 14)
+    private String idCardType;
+
+    /** 证件号码 */
+    @ApiModelProperty(value = "证件号码", position = 15)
+    private String idCardNumber;
+
+    /** 文化程度 */
+    @ApiModelProperty(value = "文化程度", position = 16)
+    private String cultureLevel;
+
+    /** 政治面貌 */
+    @ApiModelProperty(value = "政治面貌", position = 17)
+    private String politicalOutlook;
+
+    /** 毕业院校 */
+    @ApiModelProperty(value = "毕业院校", position = 18)
+    private String college;
+
+    /** 学历 */
+    @ApiModelProperty(value = "学历", position = 19)
+    private String education;
+
+    /** 学制 */
+    @ApiModelProperty(value = "学制", position = 20)
+    private String eduLength;
+
+    /** 学位 */
+    @ApiModelProperty(value = "学位", position = 21)
+    private String degree;
+
+    /** 手机 */
+    @ApiModelProperty(value = "手机", position = 22)
+    private String phone;
+
+    /** 邮箱 */
+    @ApiModelProperty(value = "邮箱", position = 23)
+    private String email;
+
+    /** 家庭电话 */
+    @ApiModelProperty(value = "家庭电话", position = 24)
+    private String homeTel;
+
+    /** 办公电话 */
+    @ApiModelProperty(value = "办公电话", position = 25)
+    private String officeTel;
+
+    /** 紧急联系人 */
+    @ApiModelProperty(value = "紧急联系人", position = 26)
+    private String emergencyContact;
+
+    /** 紧急联系人电话 */
+    @ApiModelProperty(value = "紧急联系人电话", position = 27)
+    private String emergencyPhone;
+
+    /** 紧急联系人地址 */
+    @ApiModelProperty(value = "紧急联系人地址", position = 28)
+    private String emergencyAddress;
+
+    /** 上次登录ip */
+    @ApiModelProperty(value = "上次登录ip", position = 29)
+    private String lastLoginIp;
+
+    /** 上次登录地点 */
+    @ApiModelProperty(value = "上次登录地点", position = 30)
+    private String lastLoginAddress;
+
+    /** 上次登录时间 */
+    @ApiModelProperty(value = "上次登录时间", position = 31)
+    private Date lastLoginTime;
+
+    /** 上次登录设备 */
+    @ApiModelProperty(value = "上次登录设备", position = 32)
+    private String lastLoginDevice;
+
+    /** 最新登录ip */
+    @ApiModelProperty(value = "最新登录ip", position = 33)
+    private String latestLoginIp;
+
+    /** 最新登录地点 */
+    @ApiModelProperty(value = "最新登录地点", position = 34)
+    private String latestLoginAddress;
+
+    /** 最新登录时间 */
+    @ApiModelProperty(value = "最新登录时间", position = 35)
+    private Date latestLoginTime;
+
+    /** 最新登录设备 */
+    @ApiModelProperty(value = "最新登录设备", position = 36)
+    private String latestLoginDevice;
+
+    /** 用户状态 */
+    @ApiModelProperty(value = "用户状态", position = 37)
+    private String userStatus;
+
+    /** 排序码 */
+    @ApiModelProperty(value = "排序码", position = 38)
+    private Integer sortCode;
+
+    /** 扩展信息 */
+    @ApiModelProperty(value = "扩展信息", position = 39)
+    private String extJson;
+
+    /** 按钮码集合 */
+    @ApiModelProperty(value = "按钮码集合", position = 40)
+    private List<String> buttonCodeList;
+
+    /** 移动端按钮码集合 */
+    @ApiModelProperty(value = "移动端按钮码集合", position = 41)
+    private List<String> mobileButtonCodeList;
+
+    /** 权限码集合 */
+    @ApiModelProperty(value = "权限码集合", position = 42, hidden = true)
+    private List<String> permissionCodeList;
+
+    /** 角色码集合 */
+    @ApiModelProperty(value = "角色码集合", position = 43, hidden = true)
+    private List<String> roleCodeList;
+
+    /** 数据范围集合 */
+    @ApiModelProperty(value = "数据范围集合", position = 44, hidden = true)
+    private List<DataScope> dataScopeList;
+
+    /** 用户密码hash值 */
+    @ApiModelProperty(value = "用户密码hash值", position = 45)
+    private String password;
+
+    /** 是否可登录,由继承类实现 */
+    public abstract Boolean getEnabled();
+
+    /**
+     * 数据范围类
+     *
+     * @author xuyuxiang
+     * @date 2022/8/15 13:57
+     **/
+    @Getter
+    @Setter
+    public static class DataScope {
+
+        /** API接口 */
+        @ApiModelProperty(value = "API接口", position = 1)
+        private String apiUrl;
+
+        /** 数据范围 */
+        @ApiModelProperty(value = "数据范围", position = 2)
+        private List<String> dataScope;
+    }
+}

+ 269 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseLoginUser.java

@@ -0,0 +1,269 @@
+/*
+ * 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.pojo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 基础的B端登录用户对象,可继承此类扩展更多属性
+ *
+ * @author xuyuxiang
+ * @date 2021/12/23 21:49
+ */
+@Getter
+@Setter
+public abstract class SaBaseLoginUser {
+
+    /** id */
+    @ApiModelProperty(value = "id", position = 1)
+    private String id;
+
+    /** 头像 */
+    @ApiModelProperty(value = "头像", position = 3)
+    private String avatar;
+
+    /** 签名 */
+    @ApiModelProperty(value = "签名", position = 4)
+    private String signature;
+
+    /** 账号 */
+    @ApiModelProperty(value = "账号", position = 5)
+    private String account;
+
+    /** 姓名 */
+    @ApiModelProperty(value = "姓名", position = 6)
+    private String name;
+
+    /** 昵称 */
+    @ApiModelProperty(value = "昵称", position = 7)
+    private String nickname;
+
+    /** 性别 */
+    @ApiModelProperty(value = "性别", position = 8)
+    private String gender;
+
+    /** 年龄 */
+    @ApiModelProperty(value = "年龄", position = 9)
+    private String age;
+
+    /** 出生日期 */
+    @ApiModelProperty(value = "出生日期", position = 10)
+    private String birthday;
+
+    /** 民族 */
+    @ApiModelProperty(value = "民族", position = 11)
+    private String nation;
+
+    /** 籍贯 */
+    @ApiModelProperty(value = "籍贯", position = 12)
+    private String nativePlace;
+
+    /** 家庭住址 */
+    @ApiModelProperty(value = "家庭住址", position = 13)
+    private String homeAddress;
+
+    /** 通信地址 */
+    @ApiModelProperty(value = "通信地址", position = 14)
+    private String mailingAddress;
+
+    /** 证件类型 */
+    @ApiModelProperty(value = "证件类型", position = 15)
+    private String idCardType;
+
+    /** 证件号码 */
+    @ApiModelProperty(value = "证件号码", position = 16)
+    private String idCardNumber;
+
+    /** 文化程度 */
+    @ApiModelProperty(value = "文化程度", position = 17)
+    private String cultureLevel;
+
+    /** 政治面貌 */
+    @ApiModelProperty(value = "政治面貌", position = 18)
+    private String politicalOutlook;
+
+    /** 毕业院校 */
+    @ApiModelProperty(value = "毕业院校", position = 19)
+    private String college;
+
+    /** 学历 */
+    @ApiModelProperty(value = "学历", position = 20)
+    private String education;
+
+    /** 学制 */
+    @ApiModelProperty(value = "学制", position = 21)
+    private String eduLength;
+
+    /** 学位 */
+    @ApiModelProperty(value = "学位", position = 22)
+    private String degree;
+
+    /** 手机 */
+    @ApiModelProperty(value = "手机", position = 23)
+    private String phone;
+
+    /** 邮箱 */
+    @ApiModelProperty(value = "邮箱", position = 24)
+    private String email;
+
+    /** 家庭电话 */
+    @ApiModelProperty(value = "家庭电话", position = 25)
+    private String homeTel;
+
+    /** 办公电话 */
+    @ApiModelProperty(value = "办公电话", position = 26)
+    private String officeTel;
+
+    /** 紧急联系人 */
+    @ApiModelProperty(value = "紧急联系人", position = 27)
+    private String emergencyContact;
+
+    /** 紧急联系人电话 */
+    @ApiModelProperty(value = "紧急联系人电话", position = 28)
+    private String emergencyPhone;
+
+    /** 紧急联系人地址 */
+    @ApiModelProperty(value = "紧急联系人地址", position = 29)
+    private String emergencyAddress;
+
+    /** 员工编号 */
+    @ApiModelProperty(value = "员工编号", position = 30)
+    private String empNo;
+
+    /** 入职日期 */
+    @ApiModelProperty(value = "入职日期", position = 31)
+    private String entryDate;
+
+    /** 组织id */
+    @ApiModelProperty(value = "组织id", position = 32)
+    private String orgId;
+
+    /** 组织名称 */
+    @ApiModelProperty(value = "组织名称", position = 33)
+    private String orgName;
+
+    /** 职位id */
+    @ApiModelProperty(value = "职位id", position = 34)
+    private String positionId;
+
+    /** 职位名称 */
+    @ApiModelProperty(value = "职位名称", position = 35)
+    private String positionName;
+
+    /** 职级 */
+    @ApiModelProperty(value = "职级", position = 36)
+    private String positionLevel;
+
+    /** 主管id */
+    @ApiModelProperty(value = "主管id", position = 37)
+    private String directorId;
+
+    /** 兼任信息 */
+    @ApiModelProperty(value = "兼任信息", position = 38)
+    private String positionJson;
+
+    /** 上次登录ip */
+    @ApiModelProperty(value = "上次登录ip", position = 39)
+    private String lastLoginIp;
+
+    /** 上次登录地点 */
+    @ApiModelProperty(value = "上次登录地点", position = 40)
+    private String lastLoginAddress;
+
+    /** 上次登录时间 */
+    @ApiModelProperty(value = "上次登录时间", position = 41)
+    private Date lastLoginTime;
+
+    /** 上次登录设备 */
+    @ApiModelProperty(value = "上次登录设备", position = 42)
+    private String lastLoginDevice;
+
+    /** 最新登录ip */
+    @ApiModelProperty(value = "最新登录ip", position = 43)
+    private String latestLoginIp;
+
+    /** 最新登录地点 */
+    @ApiModelProperty(value = "最新登录地点", position = 44)
+    private String latestLoginAddress;
+
+    /** 最新登录时间 */
+    @ApiModelProperty(value = "最新登录时间", position = 45)
+    private Date latestLoginTime;
+
+    /** 最新登录设备 */
+    @ApiModelProperty(value = "最新登录设备", position = 46)
+    private String latestLoginDevice;
+
+    /** 用户状态 */
+    @ApiModelProperty(value = "用户状态", position = 47)
+    private String userStatus;
+
+    /** 排序码 */
+    @ApiModelProperty(value = "排序码", position = 48)
+    private Integer sortCode;
+
+    /** 扩展信息 */
+    @ApiModelProperty(value = "扩展信息", position = 49)
+    private String extJson;
+
+    /** 按钮码集合 */
+    @ApiModelProperty(value = "按钮码集合", position = 50)
+    private List<String> buttonCodeList;
+
+    /** 移动端按钮码集合 */
+    @ApiModelProperty(value = "移动端按钮码集合", position = 51)
+    private List<String> mobileButtonCodeList;
+
+    /** 权限码集合 */
+    @ApiModelProperty(value = "权限码集合", position = 52, hidden = true)
+    private List<String> permissionCodeList;
+
+    /** 角色码集合 */
+    @ApiModelProperty(value = "角色码集合", position = 53, hidden = true)
+    private List<String> roleCodeList;
+
+    /** 数据范围集合 */
+    @ApiModelProperty(value = "数据范围集合", position = 54, hidden = true)
+    private List<DataScope> dataScopeList;
+
+    /** 用户密码hash值 */
+    @ApiModelProperty(value = "用户密码hash值", position = 55)
+    private String password;
+
+    /** 是否可登录,由继承类实现 */
+    public abstract Boolean getEnabled();
+
+    /**
+     * 数据范围类
+     *
+     * @author xuyuxiang
+     * @date 2022/8/15 13:57
+     **/
+    @Getter
+    @Setter
+    public static class DataScope {
+
+        /** API接口 */
+        @ApiModelProperty(value = "API接口", position = 1)
+        private String apiUrl;
+
+        /** 数据范围 */
+        @ApiModelProperty(value = "数据范围", position = 2)
+        private List<String> dataScope;
+    }
+}

+ 47 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpClientLoginUserUtil.java

@@ -0,0 +1,47 @@
+/*
+ * 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.hutool.core.collection.CollectionUtil;
+import vip.xiaonuo.auth.core.pojo.SaBaseClientLoginUser;
+
+import java.util.List;
+
+/**
+ * C端登录用户工具类
+ *
+ * @author xuyuxiang
+ * @date 2022/7/8 10:40
+ **/
+public class StpClientLoginUserUtil {
+
+    /**
+     * 获取当前C端登录用户
+     *
+     * @author xuyuxiang
+     * @date 2022/7/8 10:41
+     **/
+    public static SaBaseClientLoginUser getClientLoginUser() {
+        return (SaBaseClientLoginUser) StpClientUtil.getTokenSession().get("loginUser");
+    }
+
+    /**
+     * 获取当前C端登录用户的当前请求接口的数据范围(暂无数据范围)
+     *
+     * @author xuyuxiang
+     * @date 2022/7/8 10:41
+     **/
+    public static List<String> getLoginUserDataScope() {
+        return CollectionUtil.newArrayList();
+    }
+}

+ 936 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpClientUtil.java

@@ -0,0 +1,936 @@
+/*
+ * 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.SaManager;
+import cn.dev33.satoken.fun.SaFunction;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.SaTokenInfo;
+import cn.dev33.satoken.stp.StpLogic;
+import cn.dev33.satoken.stp.StpUtil;
+
+import java.util.List;
+
+/**
+ * Sa-Token 权限认证工具类 (C端用户版)
+ *
+ * @author xuyuxiang
+ * @date 2022/3/10 10:43
+ **/
+public class StpClientUtil {
+
+    /**
+     * 账号类型标识
+     */
+    public static final String TYPE = "C";
+
+    /**
+     * 底层的 StpLogic 对象,使用匿名子类 重写`stpLogic对象`的一些方法
+     */
+    public static StpLogic stpLogic = new StpLogic(TYPE) {
+
+        /**
+         * 重写 StpLogic 类下的 `splicingKeyTokenName` 函数,返回一个与 `StpUtil` 相同的token名称
+         */
+        @Override
+        public String splicingKeyTokenName() {
+            return super.splicingKeyTokenName();
+        }
+
+        // 此处可以根据需求重写其他方法
+    };
+
+    /**
+     * 获取当前 StpLogic 的账号类型
+     * @return See Note
+     */
+    public static String getLoginType() {
+        return stpLogic.getLoginType();
+    }
+
+    /**
+     * 重置 StpLogic 对象
+     * @param stpLogic /
+     */
+    public static void setStpLogic(StpLogic stpLogic) {
+        StpUtil.stpLogic = stpLogic;
+        // 防止自定义 stpLogic 被覆盖
+        SaManager.putStpLogic(stpLogic);
+    }
+
+
+    // =================== 获取token 相关 ===================
+
+    /**
+     * 返回token名称
+     * @return 此StpLogic的token名称
+     */
+    public static String getTokenName() {
+        return stpLogic.getTokenName();
+    }
+
+    /**
+     * 在当前会话写入当前TokenValue
+     * @param tokenValue token值
+     */
+    public static void setTokenValue(String tokenValue) {
+        stpLogic.setTokenValue(tokenValue);
+    }
+
+    /**
+     * 在当前会话写入当前TokenValue
+     * @param tokenValue token值
+     * @param cookieTimeout Cookie存活时间(秒)
+     */
+    public static void setTokenValue(String tokenValue, int cookieTimeout) {
+        stpLogic.setTokenValue(tokenValue, cookieTimeout);
+    }
+
+    /**
+     * 获取当前TokenValue
+     * @return 当前tokenValue
+     */
+    public static String getTokenValue() {
+        return stpLogic.getTokenValue();
+    }
+
+    /**
+     * 获取当前TokenValue (不裁剪前缀)
+     * @return /
+     */
+    public static String getTokenValueNotCut() {
+        return stpLogic.getTokenValueNotCut();
+    }
+
+    /**
+     * 获取当前会话的Token信息
+     * @return token信息
+     */
+    public static SaTokenInfo getTokenInfo() {
+        return stpLogic.getTokenInfo();
+    }
+
+
+    // =================== 登录相关操作 ===================
+
+    // --- 登录
+
+    /**
+     * 会话登录
+     * @param id 账号id,建议的类型:(long | int | String)
+     */
+    public static void login(Object id) {
+        stpLogic.login(id);
+    }
+
+    /**
+     * 会话登录,并指定登录设备
+     * @param id 账号id,建议的类型:(long | int | String)
+     * @param device 设备标识
+     */
+    public static void login(Object id, String device) {
+        stpLogic.login(id, device);
+    }
+
+    /**
+     * 会话登录,并指定是否 [记住我]
+     * @param id 账号id,建议的类型:(long | int | String)
+     * @param isLastingCookie 是否为持久Cookie
+     */
+    public static void login(Object id, boolean isLastingCookie) {
+        stpLogic.login(id, isLastingCookie);
+    }
+
+    /**
+     * 会话登录,并指定所有登录参数Model
+     * @param id 登录id,建议的类型:(long | int | String)
+     * @param loginModel 此次登录的参数Model
+     */
+    public static void login(Object id, SaLoginModel loginModel) {
+        stpLogic.login(id, loginModel);
+    }
+
+    /**
+     * 创建指定账号id的登录会话
+     * @param id 登录id,建议的类型:(long | int | String)
+     * @return 返回会话令牌
+     */
+    public static String createLoginSession(Object id) {
+        return stpLogic.createLoginSession(id);
+    }
+
+    /**
+     * 创建指定账号id的登录会话
+     * @param id 登录id,建议的类型:(long | int | String)
+     * @param loginModel 此次登录的参数Model
+     * @return 返回会话令牌
+     */
+    public static String createLoginSession(Object id, SaLoginModel loginModel) {
+        return stpLogic.createLoginSession(id, loginModel);
+    }
+
+    // --- 注销
+
+    /**
+     * 会话注销
+     */
+    public static void logout() {
+        stpLogic.logout();
+    }
+
+    /**
+     * 会话注销,根据账号id
+     * @param loginId 账号id
+     */
+    public static void logout(Object loginId) {
+        stpLogic.logout(loginId);
+    }
+
+    /**
+     * 会话注销,根据账号id 和 设备标识
+     *
+     * @param loginId 账号id
+     * @param device 设备标识 (填null代表所有注销设备)
+     */
+    public static void logout(Object loginId, String device) {
+        stpLogic.logout(loginId, device);
+    }
+
+    /**
+     * 会话注销,根据指定 Token
+     *
+     * @param tokenValue 指定token
+     */
+    public static void logoutByTokenValue(String tokenValue) {
+        stpLogic.logoutByTokenValue(tokenValue);
+    }
+
+    /**
+     * 踢人下线,根据账号id
+     * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
+     *
+     * @param loginId 账号id
+     */
+    public static void kickout(Object loginId) {
+        stpLogic.kickout(loginId);
+    }
+
+    /**
+     * 踢人下线,根据账号id 和 设备标识
+     * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
+     *
+     * @param loginId 账号id
+     * @param device 设备标识 (填null代表踢出所有设备)
+     */
+    public static void kickout(Object loginId, String device) {
+        stpLogic.kickout(loginId, device);
+    }
+
+    /**
+     * 踢人下线,根据指定 Token
+     * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
+     *
+     * @param tokenValue 指定token
+     */
+    public static void kickoutByTokenValue(String tokenValue) {
+        stpLogic.kickoutByTokenValue(tokenValue);
+    }
+
+    /**
+     * 顶人下线,根据账号id 和 设备标识
+     * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
+     *
+     * @param loginId 账号id
+     * @param device 设备标识 (填null代表顶替所有设备)
+     */
+    public static void replaced(Object loginId, String device) {
+        stpLogic.replaced(loginId, device);
+    }
+
+
+    // 查询相关
+
+    /**
+     * 当前会话是否已经登录
+     * @return 是否已登录
+     */
+    public static boolean isLogin() {
+        return stpLogic.isLogin();
+    }
+
+    /**
+     * 检验当前会话是否已经登录,如未登录,则抛出异常
+     */
+    public static void checkLogin() {
+        stpLogic.checkLogin();
+    }
+
+    /**
+     * 获取当前会话账号id, 如果未登录,则抛出异常
+     * @return 账号id
+     */
+    public static Object getLoginId() {
+        return stpLogic.getLoginId();
+    }
+
+    /**
+     * 获取当前会话账号id, 如果未登录,则返回默认值
+     * @param <T> 返回类型
+     * @param defaultValue 默认值
+     * @return 登录id
+     */
+    public static <T> T getLoginId(T defaultValue) {
+        return stpLogic.getLoginId(defaultValue);
+    }
+
+    /**
+     * 获取当前会话账号id, 如果未登录,则返回null
+     * @return 账号id
+     */
+    public static Object getLoginIdDefaultNull() {
+        return stpLogic.getLoginIdDefaultNull();
+    }
+
+    /**
+     * 获取当前会话账号id, 并转换为String类型
+     * @return 账号id
+     */
+    public static String getLoginIdAsString() {
+        return stpLogic.getLoginIdAsString();
+    }
+
+    /**
+     * 获取当前会话账号id, 并转换为int类型
+     * @return 账号id
+     */
+    public static int getLoginIdAsInt() {
+        return stpLogic.getLoginIdAsInt();
+    }
+
+    /**
+     * 获取当前会话账号id, 并转换为long类型
+     * @return 账号id
+     */
+    public static long getLoginIdAsLong() {
+        return stpLogic.getLoginIdAsLong();
+    }
+
+    /**
+     * 获取指定Token对应的账号id,如果未登录,则返回 null
+     * @param tokenValue token
+     * @return 账号id
+     */
+    public static Object getLoginIdByToken(String tokenValue) {
+        return stpLogic.getLoginIdByToken(tokenValue);
+    }
+
+    /**
+     * 获取Token扩展信息(只在jwt模式下有效)
+     * @param key 键值
+     * @return 对应的扩展数据
+     */
+    public static Object getExtra(String key) {
+        return stpLogic.getExtra(key);
+    }
+
+
+    // =================== User-Session 相关 ===================
+
+    /**
+     * 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
+     * @param loginId 账号id
+     * @param isCreate 是否新建
+     * @return Session对象
+     */
+    public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
+        return stpLogic.getSessionByLoginId(loginId, isCreate);
+    }
+
+    /**
+     * 获取指定key的Session, 如果Session尚未创建,则返回null
+     * @param sessionId SessionId
+     * @return Session对象
+     */
+    public static SaSession getSessionBySessionId(String sessionId) {
+        return stpLogic.getSessionBySessionId(sessionId);
+    }
+
+    /**
+     * 获取指定账号id的Session,如果Session尚未创建,则新建并返回
+     * @param loginId 账号id
+     * @return Session对象
+     */
+    public static SaSession getSessionByLoginId(Object loginId) {
+        return stpLogic.getSessionByLoginId(loginId);
+    }
+
+    /**
+     * 获取当前会话的Session, 如果Session尚未创建,isCreate=是否新建并返回
+     * @param isCreate 是否新建
+     * @return Session对象
+     */
+    public static SaSession getSession(boolean isCreate) {
+        return stpLogic.getSession(isCreate);
+    }
+
+    /**
+     * 获取当前会话的Session,如果Session尚未创建,则新建并返回
+     * @return Session对象
+     */
+    public static SaSession getSession() {
+        return stpLogic.getSession();
+    }
+
+
+    // =================== Token-Session 相关 ===================
+
+    /**
+     * 获取指定Token-Session,如果Session尚未创建,则新建并返回
+     * @param tokenValue Token值
+     * @return Session对象
+     */
+    public static SaSession getTokenSessionByToken(String tokenValue) {
+        return stpLogic.getTokenSessionByToken(tokenValue);
+    }
+
+    /**
+     * 获取当前Token-Session,如果Session尚未创建,则新建并返回
+     * @return Session对象
+     */
+    public static SaSession getTokenSession() {
+        return stpLogic.getTokenSession();
+    }
+
+
+    // =================== [临时有效期] 验证相关 ===================
+
+    /**
+     * 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
+     */
+    public static void checkActivityTimeout() {
+        stpLogic.checkActivityTimeout();
+    }
+
+    /**
+     * 续签当前token:(将 [最后操作时间] 更新为当前时间戳)
+     * <h1>请注意: 即时token已经 [临时过期] 也可续签成功,
+     * 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可 </h1>
+     */
+    public static void updateLastActivityToNow() {
+        stpLogic.updateLastActivityToNow();
+    }
+
+
+    // =================== 过期时间相关 ===================
+
+    /**
+     * 获取当前登录者的 token 剩余有效时间 (单位: 秒)
+     * @return token剩余有效时间
+     */
+    public static long getTokenTimeout() {
+        return stpLogic.getTokenTimeout();
+    }
+
+    /**
+     * 获取当前登录者的 User-Session 剩余有效时间 (单位: 秒)
+     * @return token剩余有效时间
+     */
+    public static long getSessionTimeout() {
+        return stpLogic.getSessionTimeout();
+    }
+
+    /**
+     * 获取当前 Token-Session 剩余有效时间 (单位: 秒)
+     * @return token剩余有效时间
+     */
+    public static long getTokenSessionTimeout() {
+        return stpLogic.getTokenSessionTimeout();
+    }
+
+    /**
+     * 获取当前 token [临时过期] 剩余有效时间 (单位: 秒)
+     * @return token [临时过期] 剩余有效时间
+     */
+    public static long getTokenActivityTimeout() {
+        return stpLogic.getTokenActivityTimeout();
+    }
+
+    /**
+     * 对当前 Token 的 timeout 值进行续期
+     * @param timeout 要修改成为的有效时间 (单位: 秒)
+     */
+    public static void renewTimeout(long timeout) {
+        stpLogic.renewTimeout(timeout);
+    }
+
+    /**
+     * 对指定 Token 的 timeout 值进行续期
+     * @param tokenValue 指定token
+     * @param timeout 要修改成为的有效时间 (单位: 秒)
+     */
+    public static void renewTimeout(String tokenValue, long timeout) {
+        stpLogic.renewTimeout(tokenValue, timeout);
+    }
+
+    // =================== 角色验证操作 ===================
+
+    /**
+     * 获取:当前账号的角色集合
+     * @return /
+     */
+    public static List<String> getRoleList() {
+        return stpLogic.getRoleList();
+    }
+
+    /**
+     * 获取:指定账号的角色集合
+     * @param loginId 指定账号id
+     * @return /
+     */
+    public static List<String> getRoleList(Object loginId) {
+        return stpLogic.getRoleList(loginId);
+    }
+
+    /**
+     * 判断:当前账号是否拥有指定角色, 返回true或false
+     * @param role 角色标识
+     * @return 是否含有指定角色标识
+     */
+    public static boolean hasRole(String role) {
+        return stpLogic.hasRole(role);
+    }
+
+    /**
+     * 判断:指定账号是否含有指定角色标识, 返回true或false
+     * @param loginId 账号id
+     * @param role 角色标识
+     * @return 是否含有指定角色标识
+     */
+    public static boolean hasRole(Object loginId, String role) {
+        return stpLogic.hasRole(loginId, role);
+    }
+
+    /**
+     * 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
+     * @param roleArray 角色标识数组
+     * @return true或false
+     */
+    public static boolean hasRoleAnd(String... roleArray) {
+        return stpLogic.hasRoleAnd(roleArray);
+    }
+
+    /**
+     * 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
+     * @param roleArray 角色标识数组
+     * @return true或false
+     */
+    public static boolean hasRoleOr(String... roleArray) {
+        return stpLogic.hasRoleOr(roleArray);
+    }
+
+    /**
+     * 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
+     * @param role 角色标识
+     */
+    public static void checkRole(String role) {
+        stpLogic.checkRole(role);
+    }
+
+    /**
+     * 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
+     * @param roleArray 角色标识数组
+     */
+    public static void checkRoleAnd(String... roleArray) {
+        stpLogic.checkRoleAnd(roleArray);
+    }
+
+    /**
+     * 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
+     * @param roleArray 角色标识数组
+     */
+    public static void checkRoleOr(String... roleArray) {
+        stpLogic.checkRoleOr(roleArray);
+    }
+
+
+    // =================== 权限验证操作 ===================
+
+    /**
+     * 获取:当前账号的权限码集合
+     * @return /
+     */
+    public static List<String> getPermissionList() {
+        return stpLogic.getPermissionList();
+    }
+
+    /**
+     * 获取:指定账号的权限码集合
+     * @param loginId 指定账号id
+     * @return /
+     */
+    public static List<String> 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
+     * <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
+     * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
+     * @param loginId 账号id
+     * @return token值
+     */
+    public static String getTokenValueByLoginId(Object loginId) {
+        return stpLogic.getTokenValueByLoginId(loginId);
+    }
+
+    /**
+     * 获取指定账号id指定设备端的tokenValue
+     * <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个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<String> getTokenValueListByLoginId(Object loginId) {
+        return stpLogic.getTokenValueListByLoginId(loginId);
+    }
+
+    /**
+     * 获取指定账号id指定设备端的tokenValue 集合
+     * @param loginId 账号id
+     * @param device 设备标识
+     * @return 此loginId的所有相关token
+     */
+    public static List<String> 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<String> searchTokenValue(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchTokenValue(keyword, start, size, sortType);
+    }
+
+    /**
+     * 根据条件查询SessionId
+     * @param keyword 关键字
+     * @param start 开始处索引 (-1代表查询所有)
+     * @param size 获取数量
+     * @return sessionId集合
+     */
+    public static List<String> searchSessionId(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchSessionId(keyword, start, size, sortType);
+    }
+
+    /**
+     * 根据条件查询Token专属Session的Id
+     * @param keyword 关键字
+     * @param start 开始处索引 (-1代表查询所有)
+     * @param size 获取数量
+     * @return sessionId集合
+     */
+    public static List<String> searchTokenSessionId(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchTokenSessionId(keyword, start, size, sortType);
+    }
+
+
+    // ------------------- 账号封禁 -------------------
+
+    /**
+     * 封禁指定账号
+     * <p> 此方法不会直接将此账号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,兼容旧版本 ===================
+
+    /**
+     * <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变 </h1>
+     *
+     * 获取当前StpLogin的loginKey
+     * @return 当前StpLogin的loginKey
+     */
+    @Deprecated
+    public static String getLoginKey() {
+        return stpLogic.getLoginType();
+    }
+
+    /**
+     * <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
+     *
+     * 在当前会话上登录id
+     * @param loginId 登录id,建议的类型:(long | int | String)
+     */
+    @Deprecated
+    public static void setLoginId(Object loginId) {
+        stpLogic.login(loginId);
+    }
+
+    /**
+     * <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
+     *
+     * 在当前会话上登录id, 并指定登录设备
+     * @param loginId 登录id,建议的类型:(long | int | String)
+     * @param device 设备标识
+     */
+    @Deprecated
+    public static void setLoginId(Object loginId, String device) {
+        stpLogic.login(loginId, device);
+    }
+
+    /**
+     * <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
+     *
+     * 在当前会话上登录id, 并指定登录设备
+     * @param loginId 登录id,建议的类型:(long | int | String)
+     * @param isLastingCookie 是否为持久Cookie
+     */
+    @Deprecated
+    public static void setLoginId(Object loginId, boolean isLastingCookie) {
+        stpLogic.login(loginId, isLastingCookie);
+    }
+
+    /**
+     * <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
+     *
+     * 在当前会话上登录id, 并指定所有登录参数Model
+     * @param loginId 登录id,建议的类型:(long | int | String)
+     * @param loginModel 此次登录的参数Model
+     */
+    @Deprecated
+    public static void setLoginId(Object loginId, SaLoginModel loginModel) {
+        stpLogic.login(loginId, loginModel);
+    }
+
+    /**
+     * <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
+     *
+     * 会话注销,根据账号id (踢人下线)
+     * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
+     * @param loginId 账号id
+     */
+    @Deprecated
+    public static void logoutByLoginId(Object loginId) {
+        stpLogic.kickout(loginId);
+    }
+
+    /**
+     * <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
+     *
+     * 会话注销,根据账号id and 设备标识 (踢人下线)
+     * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
+     * @param loginId 账号id
+     * @param device 设备标识 (填null代表所有注销设备)
+     */
+    @Deprecated
+    public static void logoutByLoginId(Object loginId, String device) {
+        stpLogic.kickout(loginId, device);
+    }
+}

+ 55 - 0
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<String> getLoginUserDataScope() {
+        List<String> resultList = CollectionUtil.newArrayList();
+        getLoginUser().getDataScopeList().forEach(dataScope -> {
+            if(dataScope.getApiUrl().equals(CommonServletUtil.getRequest().getServletPath())) {
+                resultList.addAll(dataScope.getDataScope());
+            }
+        });
+        return resultList;
+    }
+}

+ 1 - 0
snowy-plugin-api/snowy-plugin-biz-api/README.md

@@ -0,0 +1 @@
+# 业务功能插件api接口

+ 24 - 0
snowy-plugin-api/snowy-plugin-biz-api/pom.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-biz-api</artifactId>
+    <packaging>jar</packaging>
+    <description>业务功能插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 13 - 0
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;

+ 1 - 0
snowy-plugin-api/snowy-plugin-client-api/README.md

@@ -0,0 +1 @@
+# C端功能插件api接口

+ 24 - 0
snowy-plugin-api/snowy-plugin-client-api/pom.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-client-api</artifactId>
+    <packaging>jar</packaging>
+    <description>C端功能插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 13 - 0
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;

+ 1 - 0
snowy-plugin-api/snowy-plugin-dev-api/README.md

@@ -0,0 +1 @@
+# 开发工具插件api接口

+ 108 - 0
snowy-plugin-api/snowy-plugin-dev-api/pom.xml

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-dev-api</artifactId>
+    <packaging>jar</packaging>
+    <description>开发工具插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+        </dependency>
+
+        <!--腾讯云上传文件客户端-->
+        <dependency>
+            <groupId>com.qcloud</groupId>
+            <artifactId>cos_api</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>tencentcloud-sdk-java-common</artifactId>
+                    <groupId>com.tencentcloudapi</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!--阿里云上传文件客户端-->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.codehaus.jettison</groupId>
+                    <artifactId>jettison</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- jettison -->
+        <dependency>
+            <groupId>org.codehaus.jettison</groupId>
+            <artifactId>jettison</artifactId>
+        </dependency>
+
+        <!--minio上传文件客户端-->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+        </dependency>
+
+        <!--java邮件发送-->
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+        </dependency>
+
+        <!--阿里云邮件发送-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-dm</artifactId>
+        </dependency>
+
+        <!-- 腾讯云邮件发送 -->
+        <dependency>
+            <groupId>com.tencentcloudapi</groupId>
+            <artifactId>tencentcloud-sdk-java-ses</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>okio-jvm</artifactId>
+                    <groupId>com.squareup.okio</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!--阿里云短信发送-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>org.jacoco.agent</artifactId>
+                    <groupId>org.jacoco</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!--腾讯云短信发送-->
+        <dependency>
+            <groupId>com.tencentcloudapi</groupId>
+            <artifactId>tencentcloud-sdk-java-sms</artifactId>
+        </dependency>
+
+        <!--系统硬件信息-->
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 30 - 0
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);
+}

+ 22 - 0
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 {
+}

+ 142 - 0
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<File> 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<String, InputStream> imageMap, List<File> 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<JSONObject> 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<JSONObject> 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<JSONObject> attachmentList);
+}

+ 104 - 0
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);
+}

+ 22 - 0
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 {
+}

+ 58 - 0
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<JSONObject> currentUserVisLogList();
+
+    /**
+     * 获取当前用户的操作日志列表
+     *
+     * @author xuyuxiang
+     * @date 2022/9/4 15:12
+     */
+    List<JSONObject> currentUserOpLogList();
+}

+ 111 - 0
snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevMessageApi.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.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<String> receiverIdList, String subject);
+
+    /**
+     * 发送站内信指定分类
+     *
+     * @param receiverIdList 接收的用户id集合
+     * @param subject 主题
+     * @author xuyuxiang
+     * @date 2022/6/22 17:35
+     **/
+    void sendMessage(List<String> receiverIdList, String category, String subject);
+
+    /**
+     * 发送站内信带内容,默认:分类系统通知
+     *
+     * @param receiverIdList 接收的用户id集合
+     * @param subject 主题
+     * @param content 站内信内容
+     * @author xuyuxiang
+     * @date 2022/6/22 17:35
+     **/
+    void sendMessageWithContent(List<String> receiverIdList, String subject, String content);
+
+    /**
+     * 发送站内信带内容,指定分类
+     *
+     * @param receiverIdList 接收的用户id集合
+     * @param subject 主题
+     * @param content 站内信内容
+     * @author xuyuxiang
+     * @date 2022/6/22 17:35
+     **/
+    void sendMessageWithContent(List<String> receiverIdList, String category, String subject, String content);
+
+    /**
+     * 获取未读站内信列表
+     *
+     * @author xuyuxiang
+     * @date 2022/9/2 11:48
+     */
+    List<JSONObject> list(List<String> receiverIdList, Integer limit);
+
+    /**
+     * 获取未读站内信数量
+     *
+     * @author diantu
+     * @date 2023/7/10
+     */
+    Long unreadCount(String loginId);
+
+    /**
+     * 获取站内信分页
+     *
+     * @author xuyuxiang
+     * @date 2022/9/2 11:48
+     */
+    Page<JSONObject> page(List<String> receiverIdList, String category);
+
+    /**
+     * 获取站内信详情
+     *
+     * @param id 站内信id
+     * @author xuyuxiang
+     * @date 2022/4/24 20:08
+     */
+    JSONObject detail(String id);
+
+    /**
+     * 站内信全部标记已读
+     *
+     * @author diantu
+     * @date 2023/7/10
+     */
+    void allMessageMarkRead();
+
+}

+ 54 - 0
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);
+}

+ 68 - 0
snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevSseApi.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.dev.api;
+
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+import vip.xiaonuo.common.sse.CommonSseParam;
+import java.util.function.Consumer;
+
+/**
+ * SSE API接口
+ *
+ * @author diantu
+ * @date 2023/7/5
+ **/
+public interface DevSseApi {
+
+    /**
+     * 创建SSE连接
+     *
+     * @param clientId 客户端id,不传则自动生成
+     * @param setHeartBeat 是否设置心跳定时任务,默认为false(true:设置 false:不设置)
+     * @param defaultHeartbeat 是否使用默认心跳任务
+     * @param consumer 自定义心跳任务,需要自定义实现Consumer接口中的accept方法(setHeartBeat必须为true,defaultHeartbeat为false才有意义)
+     * @return 初次建立连接会推送客户端id,状态码为0
+     * @author diantu
+     * @date 2023/7/5
+     **/
+    public SseEmitter createSseConnect(String clientId, Boolean setHeartBeat, Boolean defaultHeartbeat, Consumer<CommonSseParam> consumer);
+
+    /**
+     * 关闭连接
+     *
+     * @param clientId 客户端id
+     * @author diantu
+     * @date 2023/7/5
+     **/
+    public void closeSseConnect(String clientId);
+
+    /**
+     * 推送消息到所有客户端
+     *
+     * @param msg 推送消息
+     * @author diantu
+     * @date 2023/7/5
+     **/
+    public void sendMessageToAllClient(String msg);
+
+    /**
+     * 根据clientId发送消息给某一客户端
+     *
+     * @param clientId 客户端id
+     * @param msg 推送消息
+     * @author diantu
+     * @date 2023/7/5
+     **/
+    public void sendMessageToOneClient(String clientId, String msg);
+
+}

+ 1 - 0
snowy-plugin-api/snowy-plugin-gen-api/README.md

@@ -0,0 +1 @@
+# 代码生成插件api接口

+ 24 - 0
snowy-plugin-api/snowy-plugin-gen-api/pom.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-gen-api</artifactId>
+    <packaging>jar</packaging>
+    <description>代码生成插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 13 - 0
snowy-plugin-api/snowy-plugin-gen-api/src/main/java/vip/xiaonuo/gen/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.gen;

+ 1 - 0
snowy-plugin-api/snowy-plugin-mobile-api/README.md

@@ -0,0 +1 @@
+# 移动端管理插件api接口

+ 24 - 0
snowy-plugin-api/snowy-plugin-mobile-api/pom.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-mobile-api</artifactId>
+    <packaging>jar</packaging>
+    <description>移动端管理插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 1 - 0
snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/README.md

@@ -0,0 +1 @@
+#移动端API接口

+ 32 - 0
snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileButtonApi.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.mobile.api;
+
+import java.util.List;
+
+/**
+ * 移动端按钮API
+ *
+ * @author xuyuxiang
+ * @date 2023/2/1 9:52
+ **/
+public interface MobileButtonApi {
+
+    /**
+     * 根据按钮id集合获取按钮码列表
+     *
+     * @author 每天一点
+     * @date 2023/2/5 13:26
+     **/
+    List<String> listByIds(List<String> idList);
+}

+ 43 - 0
snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileMenuApi.java

@@ -0,0 +1,43 @@
+/*
+ * 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.mobile.api;
+
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 移动端菜单API
+ *
+ * @author xuyuxiang
+ * @date 2023/1/31 10:09
+ **/
+public interface MobileMenuApi {
+
+    /**
+     * 获取移动端菜单授权树
+     *
+     * @author xuyuxiang
+     * @date 2023/1/31 10:10
+     **/
+    List<JSONObject> mobileMenuTreeSelector();
+
+    /**
+     * 获取移动端登录菜单树
+     *
+     * @author xuyuxiang
+     * @date 2023/1/31 10:29
+     **/
+    List<Tree<String>> loginMobileMenuTree(List<String> menuIdList);
+}

+ 33 - 0
snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileModuleApi.java

@@ -0,0 +1,33 @@
+/*
+ * 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.mobile.api;
+
+import cn.hutool.json.JSONObject;
+import java.util.List;
+
+/**
+ * 移动端菜单API
+ *
+ * @author xuyuxiang
+ * @date 2023/1/31 10:09
+ **/
+public interface MobileModuleApi {
+
+    /**
+     * 获取移动端模块选择器
+     *
+     * @author xuyuxiang
+     * @date 2023/7/15 22:01
+     **/
+    List<JSONObject> mobileModuleSelector();
+}

+ 1 - 0
snowy-plugin-api/snowy-plugin-sys-api/README.md

@@ -0,0 +1 @@
+# 系统功能插件api接口

+ 24 - 0
snowy-plugin-api/snowy-plugin-sys-api/pom.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-sys-api</artifactId>
+    <packaging>jar</packaging>
+    <description>系统功能插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 1 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/README.md

@@ -0,0 +1 @@
+#系统API接口

+ 30 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysButtonApi.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.sys.api;
+
+/**
+ * 按钮API
+ *
+ * @author xuyuxiang
+ * @date 2022/11/1 13:45
+ **/
+public interface SysButtonApi {
+
+    /**
+     * 代码生成按钮插入
+     *
+     * @author xuyuxiang
+     * @date 2022/11/1 13:48
+     **/
+    void addForGenButton(String menuId, String className, String functionName);
+}

+ 30 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysMenuApi.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.sys.api;
+
+/**
+ * 菜单API
+ *
+ * @author xuyuxiang
+ * @date 2022/11/1 13:44
+ **/
+public interface SysMenuApi {
+
+    /**
+     * 代码生成菜单插入
+     *
+     * @author xuyuxiang
+     * @date 2022/11/1 13:48
+     **/
+    String addForGenMenu(String parentId, String busName, String module, String title, String path);
+}

+ 60 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysOrgApi.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.sys.api;
+
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+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<Tree<String>> orgTreeSelector();
+
+    /**
+     * 获取组织列表选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:45
+     **/
+    Page<JSONObject> orgListSelector(String parentId);
+}

+ 43 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysPositionApi.java

@@ -0,0 +1,43 @@
+/*
+ * 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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+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
+     **/
+    Page<JSONObject> positionSelector(String orgId, String searchKey);
+}

+ 48 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRelationApi.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.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<String> getUserIdListByRoleIdList(List<String> roleIdList);
+
+    /**
+     * 根据移动端菜单Id集合移除角色和移动端菜单关系
+     *
+     * @author xuyuxiang
+     * @date 2023/1/31 9:54
+     **/
+    void removeRoleHasMobileMenuRelation(List<String> targetIdList);
+
+    /**
+     * 清除对应的角色与移动端菜单信息中的【授权的移动端按钮信息】
+     *
+     * @author xuyuxiang
+     * @date 2023/1/31 9:54
+     **/
+    void removeRoleHasMobileButtonRelation(List<String> targetIdList, List<String> buttonIdList);
+}

+ 51 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRoleApi.java

@@ -0,0 +1,51 @@
+/*
+ * 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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+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<String> orgIdList);
+
+    /**
+     * 获取角色选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:49
+     **/
+    Page<JSONObject> roleSelector(String orgId, String category, String searchKey, List<String> dataScopeList, boolean excludeSuperAdmin);
+
+    /**
+     * 代码生成菜单按钮授权
+     *
+     * @author xuyuxiang
+     * @date 2022/11/1 15:58
+     **/
+    void grantForGenMenuAndButton(String menuId);
+}

+ 115 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysUserApi.java

@@ -0,0 +1,115 @@
+/*
+ * 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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+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<JSONObject> getUserListByIdListWithoutException(List<String> userIdList);
+
+    /**
+     * 根据用户id获取用户对象,没有则抛出异常
+     *
+     * @author xuyuxiang
+     * @date 2022/6/20 18:19
+     **/
+    JSONObject getUserByIdWithException(String userId);
+
+    /**
+     * 根据用户id获取用户对象列表,只要有一个没有则抛出异常
+     *
+     * @author xuyuxiang
+     * @date 2022/6/20 18:19
+     **/
+    List<JSONObject> getUserListByIdWithException(List<String> userIdList);
+
+    /**
+     * 获取用户拥有角色
+     *
+     * @author xuyuxiang
+     * @date 2022/5/13 21:00
+     */
+    List<String> ownRole(String userId);
+
+    /**
+     * 给用户授权角色
+     *
+     * @author xuyuxiang
+     * @date 2022/8/1 18:28
+     */
+    void grantRole(String userId, List<String> roleIdList);
+
+    /**
+     * 根据组织id集合获取组织下用户id集合
+     *
+     * @author xuyuxiang
+     * @date 2022/6/6 11:40
+     **/
+    List<String> getUserIdListByOrgIdList(List<String> orgIdList);
+
+    /**
+     * 根据职位id集合获取职位下用户id集合
+     *
+     * @author xuyuxiang
+     * @date 2022/6/6 11:44
+     **/
+    List<String> getUserIdListByPositionIdList(List<String> positionIdList);
+
+    /**
+     * 根据用户id和组织id和职位id和主管层级获取上级主管id
+     *
+     * @author xuyuxiang
+     * @date 2022/6/6 14:50
+     **/
+    JSONObject getSupervisorIdBySupervisorLevel(List<String> userIdList, String userId, String orgId, String supervisorLevel);
+
+    /**
+     * 根据用户id和组织id和职位id和终点主管层级获取上级主管id集合
+     *
+     * @author xuyuxiang
+     * @date 2022/6/6 14:50
+     **/
+    List<String> getMulSupervisorIdListByEndLevel(String userId, String orgId, String endLevel);
+
+    /**
+     * 获取用户选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/4/24 20:08
+     */
+    Page<JSONObject> userSelector(String orgId, String searchKey);
+}

+ 15 - 0
snowy-plugin/README.md

@@ -0,0 +1,15 @@
+# 插件模块
+
+####登录鉴权插件: snowy-plugin-auth
+
+####业务功能插件: snowy-plugin-biz
+
+####C端功能插件: snowy-plugin-client
+
+####开发工具插件: snowy-plugin-dev
+
+####代码生成插件: snowy-plugin-gen
+
+####移动端管理插件: snowy-plugin-mobile
+
+####系统功能插件: snowy-plugin-sys

+ 39 - 0
snowy-plugin/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin</artifactId>
+    <packaging>pom</packaging>
+    <description>插件模块</description>
+
+    <modules>
+        <!-- 登录鉴权插件 -->
+        <module>snowy-plugin-auth</module>
+
+        <!-- 业务功能插件 -->
+        <module>snowy-plugin-biz</module>
+
+         <!-- C端功能插件 -->
+        <module>snowy-plugin-client</module>
+
+        <!-- 开发工具插件 -->
+        <module>snowy-plugin-dev</module>
+
+        <!-- 代码生成插件 -->
+        <module>snowy-plugin-gen</module>
+
+        <!-- 移动端管理插件 -->
+        <module>snowy-plugin-mobile</module>
+
+        <!-- 系统功能插件 -->
+        <module>snowy-plugin-sys</module>
+    </modules>
+</project>

+ 1 - 0
snowy-plugin/snowy-plugin-auth/README.md

@@ -0,0 +1 @@
+# 登录鉴权插件

+ 61 - 0
snowy-plugin/snowy-plugin-auth/pom.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-auth</artifactId>
+    <packaging>jar</packaging>
+    <description>登录鉴权插件</description>
+
+    <dependencies>
+
+        <!-- 每个插件都要引入自己的对外接口 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-auth-api</artifactId>
+        </dependency>
+
+        <!-- 引入开发工具接口,用于获取配置 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-dev-api</artifactId>
+        </dependency>
+
+        <!-- sa-token -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- sa-token 整合 redis (使用jackson序列化方式) -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-dao-redis-jackson</artifactId>
+        </dependency>
+
+        <!-- Sa-Token插件:权限缓存与业务缓存分离 -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-alone-redis</artifactId>
+        </dependency>
+
+        <!-- Sa-Token 插件:整合SSO -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-sso</artifactId>
+        </dependency>
+
+        <!-- JustAuth 第三方登录 -->
+        <dependency>
+            <groupId>me.zhyd.oauth</groupId>
+            <artifactId>JustAuth</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 149 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/config/AuthConfigure.java

@@ -0,0 +1,149 @@
+/*
+ * 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.SaInterceptor;
+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.StpClientLoginUserUtil;
+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 SaInterceptor()).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<String> getPermissionList(Object loginId, String loginType) {
+            if (SaClientTypeEnum.B.getValue().equals(loginType)) {
+                return StpLoginUserUtil.getLoginUser().getPermissionCodeList();
+            } else {
+                return StpClientLoginUserUtil.getClientLoginUser().getPermissionCodeList();
+            }
+        }
+
+        /**
+         * 返回一个账号所拥有的角色标识集合
+         */
+        @Override
+        public List<String> getRoleList(Object loginId, String loginType) {
+            if (SaClientTypeEnum.B.getValue().equals(loginType)) {
+                return StpLoginUserUtil.getLoginUser().getRoleCodeList();
+            } else {
+                return StpClientLoginUserUtil.getClientLoginUser().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"));
+    }
+}

+ 67 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/util/AuthExceptionUtil.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.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<String> getCommonResult(Exception e) {
+        CommonResult<String> 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 DisableServiceException) {
+
+            // 如果是被封禁异常 403
+            DisableServiceException disableServiceException = (DisableServiceException) e;
+            commonResult = CommonResult.get(HttpStatus.HTTP_FORBIDDEN, "账号被封禁:" + disableServiceException.getDisableTime() + "秒后解封", null);
+        } else if (e instanceof SaTokenException) {
+
+            // 如果是SaToken异常 直接返回
+            SaTokenException saTokenException = (SaTokenException) e;
+            commonResult = CommonResult.error(saTokenException.getMessage());
+        } else {
+            // 未知异常才打印
+            log.error(">>> 服务器未知异常,请求地址:{},具体信息:", CommonServletUtil.getRequest().getRequestURL(), e);
+            // 未知异常返回服务器异常(此处不可能执行进入,因为本方法处理的一定是SaToken的异常,此处仅为安全性考虑)
+            commonResult = CommonResult.error();
+        }
+        return commonResult;
+    }
+}

+ 133 - 0
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<AuthPicValidCodeResult> getPicCaptcha() {
+        return CommonResult.data(authService.getPicCaptcha(SaClientTypeEnum.C.getValue()));
+    }
+
+    /**
+     * C端获取手机验证码
+     *
+     * @author xuyuxiang
+     * @date 2022/7/8 9:26
+     **/
+    @ApiOperationSupport(order = 2)
+    @ApiOperation("C端获取手机验证码")
+    @GetMapping("/auth/c/getPhoneValidCode")
+    public CommonResult<String> 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<String> 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<String> 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<String> 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<SaBaseClientLoginUser> getLoginUser() {
+        return CommonResult.data(authService.getClientLoginUser());
+    }
+}

+ 133 - 0
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<AuthPicValidCodeResult> 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<String> 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<String> 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<String> 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<String> 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<SaBaseLoginUser> getLoginUser() {
+        return CommonResult.data(authService.getLoginUser());
+    }
+}

+ 54 - 0
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);
+        }
+    }
+}

+ 86 - 0
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;
+    }
+}

+ 118 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/listener/AuthListener.java

@@ -0,0 +1,118 @@
+/*
+ * 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端登录日志
+            Object name = loginModel.getExtra("name");
+            if(ObjectUtil.isNotEmpty(name)) {
+                devLogApi.executeLoginLog(Convert.toStr(name));
+            } 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, String service, int level, long disableTime) {
+        // ...
+    }
+
+    /** 每次被解封时触发 */
+    @Override
+    public void doUntieDisable(String loginType, Object loginId, String service) {
+        // ...
+    }
+
+    /** 每次创建Session时触发 */
+    @Override
+    public void doCreateSession(String id) {
+        // ...
+    }
+
+    /** 每次注销Session时触发 */
+    @Override
+    public void doLogoutSession(String id) {
+        // ...
+    }
+
+    /** 每次Token续期时触发 */
+    @Override
+    public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
+        // ...
+    }
+}

+ 0 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/param/AuthAccountPasswordLoginParam.java


Неке датотеке нису приказане због велике количине промена