浏览代码

严格粮业代码

fanzherong_v 2 月之前
父节点
当前提交
730d8e0356
共有 100 个文件被更改,包括 7817 次插入0 次删除
  1. 33 0
      .gitignore
  2. 53 0
      LICENSE
  3. 79 0
      pom.xml
  4. 1 0
      snowy-common/READM.md
  5. 180 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. 4 0
      snowy-common/src/main/java/vip/xiaonuo/common/cache/BeanCopyUtilCallBack.java
  10. 86 0
      snowy-common/src/main/java/vip/xiaonuo/common/cache/CommonCacheOperator.java
  11. 31 0
      snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonDeleteFlagEnum.java
  12. 42 0
      snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonExceptionEnum.java
  13. 45 0
      snowy-common/src/main/java/vip/xiaonuo/common/enums/CommonSortOrderEnum.java
  14. 50 0
      snowy-common/src/main/java/vip/xiaonuo/common/exception/CommonException.java
  15. 49 0
      snowy-common/src/main/java/vip/xiaonuo/common/handler/CommonSm4CbcTypeHandler.java
  16. 77 0
      snowy-common/src/main/java/vip/xiaonuo/common/page/CommonPageRequest.java
  17. 62 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/BaseEntity.java
  18. 63 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonEntity.java
  19. 160 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonResult.java
  20. 146 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonValidList.java
  21. 32 0
      snowy-common/src/main/java/vip/xiaonuo/common/pojo/CommonWrapperInterface.java
  22. 40 0
      snowy-common/src/main/java/vip/xiaonuo/common/prop/CommonProperties.java
  23. 30 0
      snowy-common/src/main/java/vip/xiaonuo/common/timer/CommonTimerTaskRunner.java
  24. 42 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/BeanCopyUtil.java
  25. 175 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/BigDecimalUtil.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. 61 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. 61 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/CommonJoinPointUtil.java
  33. 126 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. 97 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. 551 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/DatesUtil.java
  39. 108 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/HexUtils.java
  40. 291 0
      snowy-common/src/main/java/vip/xiaonuo/common/util/QRCodeUtil.java
  41. 二进制
      snowy-common/src/main/resources/ip2region.xdb
  42. 11 0
      snowy-plugin-api/README.md
  43. 39 0
      snowy-plugin-api/pom.xml
  44. 1 0
      snowy-plugin-api/snowy-plugin-auth-api/README.md
  45. 36 0
      snowy-plugin-api/snowy-plugin-auth-api/pom.xml
  46. 124 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/api/SaBaseLoginUserApi.java
  47. 35 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckLogin.java
  48. 51 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckPermission.java
  49. 52 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/annotation/SaClientCheckRole.java
  50. 49 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/enums/SaClientTypeEnum.java
  51. 233 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseClientLoginUser.java
  52. 269 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseLoginUser.java
  53. 47 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpClientLoginUserUtil.java
  54. 936 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpClientUtil.java
  55. 65 0
      snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpLoginUserUtil.java
  56. 1 0
      snowy-plugin-api/snowy-plugin-biz-api/README.md
  57. 25 0
      snowy-plugin-api/snowy-plugin-biz-api/pom.xml
  58. 13 0
      snowy-plugin-api/snowy-plugin-biz-api/src/main/java/vip/xiaonuo/biz/package-info.java
  59. 1 0
      snowy-plugin-api/snowy-plugin-client-api/README.md
  60. 25 0
      snowy-plugin-api/snowy-plugin-client-api/pom.xml
  61. 13 0
      snowy-plugin-api/snowy-plugin-client-api/src/main/java/vip/xiaonuo/client/package-info.java
  62. 1 0
      snowy-plugin-api/snowy-plugin-dev-api/README.md
  63. 101 0
      snowy-plugin-api/snowy-plugin-dev-api/pom.xml
  64. 30 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevConfigApi.java
  65. 22 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevDictApi.java
  66. 142 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevEmailApi.java
  67. 131 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevFileApi.java
  68. 22 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevJobApi.java
  69. 58 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevLogApi.java
  70. 95 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevMessageApi.java
  71. 54 0
      snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevSmsApi.java
  72. 1 0
      snowy-plugin-api/snowy-plugin-gen-api/README.md
  73. 25 0
      snowy-plugin-api/snowy-plugin-gen-api/pom.xml
  74. 13 0
      snowy-plugin-api/snowy-plugin-gen-api/src/main/java/vip/xiaonuo/gen/package-info.java
  75. 1 0
      snowy-plugin-api/snowy-plugin-mobile-api/README.md
  76. 25 0
      snowy-plugin-api/snowy-plugin-mobile-api/pom.xml
  77. 1 0
      snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/README.md
  78. 33 0
      snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileButtonApi.java
  79. 43 0
      snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileMenuApi.java
  80. 1 0
      snowy-plugin-api/snowy-plugin-sys-api/README.md
  81. 25 0
      snowy-plugin-api/snowy-plugin-sys-api/pom.xml
  82. 1 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/README.md
  83. 30 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysButtonApi.java
  84. 30 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysMenuApi.java
  85. 75 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysOrgApi.java
  86. 42 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysPositionApi.java
  87. 48 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRelationApi.java
  88. 58 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRoleApi.java
  89. 150 0
      snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysUserApi.java
  90. 11 0
      snowy-plugin/README.md
  91. 39 0
      snowy-plugin/pom.xml
  92. 1 0
      snowy-plugin/snowy-plugin-auth/README.md
  93. 73 0
      snowy-plugin/snowy-plugin-auth/pom.xml
  94. 149 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/config/AuthConfigure.java
  95. 68 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/util/AuthExceptionUtil.java
  96. 133 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthClientController.java
  97. 133 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/controller/AuthController.java
  98. 54 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthDeviceTypeEnum.java
  99. 86 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/enums/AuthExceptionEnum.java
  100. 118 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/login/listener/AuthListener.java

+ 33 - 0
.gitignore

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

+ 53 - 0
LICENSE

@@ -0,0 +1,53 @@
+Apache License
+
+Version 2.0, January 2004
+
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of this License; and
+You must cause any modified files to carry prominent notices stating that You changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
+5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS

+ 79 - 0
pom.xml

@@ -0,0 +1,79 @@
+<?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>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+     <modules>
+        <!-- 基础通用规则模块 -->
+        <module>snowy-common</module>
+
+        <!-- 插件模块 -->
+        <module>snowy-plugin</module>
+
+        <!-- 插件API接口模块 -->
+        <module>snowy-plugin-api</module>
+
+        <!-- 主启动模块 -->
+        <module>snowy-web-app</module>
+    </modules>
+
+    <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 @@
+# 基础通用模块

+ 180 - 0
snowy-common/pom.xml

@@ -0,0 +1,180 @@
+<?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>
+
+    <properties>
+        <lombok.versin>1.18.22</lombok.versin>
+        <druid.version>1.2.9</druid.version>
+        <mybatis.plus.version>3.5.2</mybatis.plus.version>
+        <easy.trans.version>2.0.3</easy.trans.version>
+        <commons.pool2.version>2.11.1</commons.pool2.version>
+        <hutool.version>5.8.8</hutool.version>
+        <pinyin.version>2.5.1</pinyin.version>
+        <ip2region.version>2.6.3</ip2region.version>
+        <knife4j.version>2.0.9</knife4j.version>
+        <easypoi.version>4.3.0</easypoi.version>
+        <smcrypto.version>0.3.2</smcrypto.version>
+    </properties>
+
+    <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>
+            <version>${lombok.versin}</version>
+        </dependency>
+
+        <!-- druid -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+        <!-- mybatis-plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis.plus.version}</version>
+        </dependency>
+
+        <!-- easy-trans 排除自带MP版本使用最新版本 -->
+        <dependency>
+            <groupId>com.fhs-opensource</groupId>
+            <artifactId>easy-trans-spring-boot-starter</artifactId>
+            <version>${easy.trans.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-plus-annotation</artifactId>
+                    <groupId>com.baomidou</groupId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.springfox</groupId>
+                    <artifactId>springfox-schema</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fhs-opensource</groupId>
+            <artifactId>easy-trans-mybatis-plus-extend</artifactId>
+            <version>${easy.trans.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-plus-core</artifactId>
+                    <groupId>com.baomidou</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>mybatis-plus-extension</artifactId>
+                    <groupId>com.baomidou</groupId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.springfox</groupId>
+                    <artifactId>springfox-schema</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- redis -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+            <version>${commons.pool2.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>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.4.0</version>
+        </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();
+}

+ 4 - 0
snowy-common/src/main/java/vip/xiaonuo/common/cache/BeanCopyUtilCallBack.java

@@ -0,0 +1,4 @@
+package vip.xiaonuo.common.cache;
+
+public interface BeanCopyUtilCallBack<S, T> {
+}

+ 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;
+    }
+
+    public static void validate(String value) {
+        boolean flag = ASC.getValue().toLowerCase().equals(value) || DESC.getValue().toLowerCase().equals(value);
+        if(!flag) {
+            throw new CommonException("不支持该排序方式:{}", value);
+        }
+    }
+}

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

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

@@ -0,0 +1,49 @@
+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);
+    }
+}

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

@@ -0,0 +1,77 @@
+/*
+ * 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 vip.xiaonuo.common.util.CommonServletUtil;
+
+import java.util.List;
+
+/**
+ * 通用分页请求
+ *
+ * @author xuyuxiang
+ * @date 2021/12/18 14:43
+ */
+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) {
+                e.printStackTrace();
+                size = 20;
+            }
+        }
+
+        //第几页
+        String pageString = CommonServletUtil.getParamFromRequest(PAGE_PARAM_NAME);
+        if (ObjectUtil.isNotEmpty(pageString)) {
+            try {
+                page = Convert.toInt(pageString);
+            } catch (Exception e) {
+                e.printStackTrace();
+                page = 1;
+            }
+        }
+        Page<T> objectPage = new Page<>(page, size);
+        if (ObjectUtil.isNotEmpty(orderItemList)) {
+            objectPage.setOrders(orderItemList);
+        }
+        return objectPage;
+    }
+}

+ 62 - 0
snowy-common/src/main/java/vip/xiaonuo/common/pojo/BaseEntity.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.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 BaseEntity implements Serializable {
+
+    /** 删除标志:0-未删除;1-已删除 */
+    @TableLogic(value = "0", delval = "1")
+    @ApiModelProperty(value = "删除标志", position = 999)
+    private String delFlag;
+
+    /** 创建时间 */
+    @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;
+}

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

+ 40 - 0
snowy-common/src/main/java/vip/xiaonuo/common/prop/CommonProperties.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.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;
+
+    /** 文件阅读地址 */
+    private String kkFileUrl;
+}

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

+ 42 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/BeanCopyUtil.java

@@ -0,0 +1,42 @@
+package vip.xiaonuo.common.util;
+
+import org.springframework.beans.BeanUtils;
+import vip.xiaonuo.common.cache.BeanCopyUtilCallBack;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class BeanCopyUtil extends BeanUtils {
+
+
+    /**
+     * 集合数据的拷贝
+     *
+     * @param sources: 数据源类
+     * @param target:  目标类::new(eg: UserVO::new)
+     * @return
+     */
+    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> target) {
+        return copyListProperties(sources, target, null);
+    }
+
+
+    /**
+     * 带回调函数的集合数据的拷贝(可自定义字段拷贝规则)
+     *
+     * @param sources:  数据源类
+     * @param target:   目标类::new(eg: UserVO::new)
+     * @param callBack: 回调函数
+     * @return
+     */
+    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> target, BeanCopyUtilCallBack<S, T> callBack) {
+        List<T> list = new ArrayList<>(sources.size());
+        for (S source : sources) {
+            T t = target.get();
+            copyProperties(source, t);
+            list.add(t);
+        }
+        return list;
+    }
+}

+ 175 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/BigDecimalUtil.java

@@ -0,0 +1,175 @@
+package vip.xiaonuo.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigDecimal;
+
+/**
+ * @author xianghao
+ * @date 2023-03-20
+ */
+@Slf4j
+public class BigDecimalUtil {
+    private static final BigDecimal ZERO = BigDecimal.ZERO;
+
+    /*  等于0 */
+    public static boolean isNullOrEqZero(BigDecimal bigDecimal) {
+        return bigDecimal == null || bigDecimal.compareTo(ZERO) == 0;
+    }
+
+    /* 不等于0 */
+    public static boolean isNotNullOrEqZero(BigDecimal bigDecimal) {
+        return !isNullOrEqZero(bigDecimal);
+    }
+
+    /* 小于0 */
+    public static boolean isNullOrLessZero(BigDecimal bigDecimal) {
+        return bigDecimal == null || bigDecimal.compareTo(ZERO) < 0;
+    }
+
+    /* 不大于0 */
+    public static boolean isNullOrLessOrEqZero(BigDecimal bigDecimal) {
+        return bigDecimal == null || bigDecimal.compareTo(ZERO) <= 0;
+    }
+
+    /* 大于0 */
+    public static boolean isGreaterThanZero(BigDecimal bigDecimal) {
+        if (bigDecimal == null) {
+            return false;
+        }
+        return bigDecimal.compareTo(ZERO) > 0;
+    }
+
+    /* 不小于0 */
+    public static boolean isGreaterThanOrEqualToZero(BigDecimal bigDecimal) {
+        if (bigDecimal == null) {
+            return false;
+        }
+        return bigDecimal.compareTo(ZERO) >= 0;
+    }
+
+    /* 将字符串转换成BigDecimal对象 */
+    public static BigDecimal stringToBigDecimal(String stringBigDecimal) {
+        if (StringUtils.isBlank(stringBigDecimal)) {
+            return BigDecimal.ZERO;
+        }
+        try {
+            return new BigDecimal(stringBigDecimal).setScale(2, 4);
+        } catch (Exception e) {
+            log.error("stringToBigDecimal is error");
+        }
+        return BigDecimal.ZERO;
+    }
+
+    /* 将null转换为0 */
+    public static BigDecimal bigDecimalNullToZero(BigDecimal bigDecimal) {
+        if (bigDecimal == null) {
+            return BigDecimal.ZERO;
+        } else {
+            return bigDecimal;
+        }
+    }
+
+    /* 将null或0转换为1 */
+    public static BigDecimal bigDecimalNullToOne(BigDecimal bigDecimal) {
+        if (bigDecimal == null || bigDecimal.doubleValue() == 0) {
+            return BigDecimal.ONE;
+        } else {
+            return bigDecimal;
+        }
+    }
+
+    /**
+     * 获取相除百分比
+     * 指定精度 scale
+     * 除数0时默认1
+     *
+     * @return BigDecimal
+     */
+    public static BigDecimal dividePercentage(int dividend, int divisor, int scale) {
+        return BigDecimalUtil.dividePercentage(new BigDecimal(dividend), new BigDecimal(divisor), scale);
+    }
+
+    /**
+     * 获取相除百分比
+     * 指定精度 scale
+     * 除数0时默认1
+     *
+     * @return BigDecimal
+     */
+    public static BigDecimal dividePercentage(Integer dividend, Integer divisor, int scale) {
+        return BigDecimalUtil.dividePercentage(dividend == null ? 0 : dividend.intValue(), divisor == null ? 0 : divisor.intValue(), scale);
+    }
+
+    /**
+     * 获取相除百分比
+     * 指定精度 scale
+     * 除数0时默认1
+     *
+     * @return BigDecimal
+     */
+    public static BigDecimal dividePercentage(BigDecimal dividend, BigDecimal divisor, int scale) {
+        return BigDecimalUtil.bigDecimalNullToZero(dividend).multiply(new BigDecimal(100)).divide(BigDecimalUtil.bigDecimalNullToOne(divisor), scale, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * 获取相除结果
+     * 指定精度 scale
+     * 除数0时默认1
+     * 四舍五入
+     *
+     * @return BigDecimal
+     */
+    public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor, int scale) {
+        return BigDecimalUtil.bigDecimalNullToZero(dividend).divide(BigDecimalUtil.bigDecimalNullToOne(divisor), scale, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * 获取相除结果
+     * 指定精度 scale
+     * 除数0时默认1
+     *
+     * @return BigDecimal
+     */
+    public static BigDecimal divide(int dividend, int divisor, int scale, int round) {
+        return new BigDecimal(dividend).divide(BigDecimalUtil.bigDecimalNullToOne(new BigDecimal(divisor)), scale, round);
+    }
+
+    /**
+     * 获取相除结果
+     * 指定精度 scale
+     * 除数0时默认1
+     *
+     * @return BigDecimal
+     */
+    public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor, int scale, int round) {
+        return BigDecimalUtil.bigDecimalNullToZero(dividend).divide(BigDecimalUtil.bigDecimalNullToOne(divisor), scale, round);
+    }
+
+    /**
+     * 加一个值
+     * 被加数 augend
+     */
+    public static BigDecimal addVal(BigDecimal val, BigDecimal augend) {
+        if (val == null) val = BigDecimal.ZERO;
+        return isNullOrEqZero(augend) ? val : val.add(augend);
+    }
+
+    /**
+     * 减一个值
+     */
+    public static BigDecimal subVal(BigDecimal val1, BigDecimal val2) {
+        if (val1 == null) val1 = BigDecimal.ZERO;
+        if (val2 == null) val2 = BigDecimal.ZERO;
+        return val1.subtract(val2);
+    }
+
+    /**
+     * 乘一个值
+     */
+    public static BigDecimal multiplyVal(BigDecimal val1, BigDecimal val2) {
+        return isNullOrEqZero(val1) || isNullOrEqZero(val2) ? BigDecimal.ZERO : val1.multiply(val2);
+    }
+
+}

+ 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 (docString.equals("")) {
+            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);
+    }
+}

+ 61 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonDownloadUtil.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.util;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.URLUtil;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 文件下载工具类,使用本类前,对参数校验的异常使用CommonResponseUtil.renderError()方法进行渲染
+ *
+ * @author xuyuxiang
+ * @date 2020/8/5 21:45
+ */
+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) {
+            e.printStackTrace();
+        }
+    }
+}

+ 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.util.ReUtil;
+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 ReUtil.isMatch("^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", email);
+    }
+
+    /**
+     * 校验邮箱格式
+     *
+     * @author xuyuxiang
+     * @date 2022/8/15 13:32
+     **/
+    public static void validEmail(String emails) {
+        StrUtil.split(emails, StrUtil.COMMA).forEach(email -> {
+            if(!ReUtil.isMatch("^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", 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) {
+            e.printStackTrace();
+            throw new CommonException("CommonIpAddressUtil初始化失败,原因:", e.getMessage());
+        }
+
+        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
+        try {
+            searcher = Searcher.newWithBuffer(cBuff);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new CommonException("CommonIpAddressUtil初始化失败,原因:", e.getMessage());
+        }
+    }
+
+    /**
+     * 获取客户端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) {
+                e.printStackTrace();
+                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 "未知";
+        }
+    }
+}

+ 61 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonJoinPointUtil.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.util;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import org.aspectj.lang.JoinPoint;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 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) {
+        StringBuilder argsJson = new StringBuilder();
+        Object[] args = joinPoint.getArgs();
+        for (Object arg : args) {
+            if (!isFilterObject(arg)) {
+                if (ObjectUtil.isNotNull(arg)) {
+                    argsJson.append(JSONUtil.toJsonStr(arg)).append(" ");
+                }
+            }
+        }
+        return argsJson.toString().trim();
+    }
+
+    /**
+     * 判断是否需要拼接参数,过滤掉HttpServletRequest,MultipartFile,HttpServletResponse等类型参数
+     *
+     * @author xuyuxiang
+     * @date 2022/9/2 15:51
+     */
+    private static boolean isFilterObject(Object arg) {
+        return arg instanceof MultipartFile ||
+                arg instanceof HttpServletRequest ||
+                arg instanceof HttpServletResponse;
+    }
+}

+ 126 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/CommonNetWorkInfoUtil.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.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.printStackTrace();
+        } finally {
+            if (input != null) {
+                try {
+                    input.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            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) {
+            e.printStackTrace();
+        }
+        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)));
+    }
+}

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

@@ -0,0 +1,97 @@
+/*
+ * 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 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
+ */
+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) {
+            e.printStackTrace();
+            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) {
+            e.printStackTrace();
+            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;
+    }
+}

+ 551 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/DatesUtil.java

@@ -0,0 +1,551 @@
+package vip.xiaonuo.common.util;
+
+import cn.hutool.core.date.DateUtil;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 日期工具
+ *
+ * @author xiang
+ * @date 2023/03/08 16:49
+ **/
+public class DatesUtil {
+
+    /**
+     * 获取范围开始的时间
+     *
+     * @param type 1、当日;2、本周;3、本月;4、季度;5、年度
+     */
+    public static Date getStartTime(Date date, int type) {
+        Date result = null;
+        if (type == 1)
+            result = DateUtil.beginOfDay(date);
+        else if (type == 2)
+            result = DateUtil.beginOfWeek(date);
+        else if (type == 3)
+            result = DateUtil.beginOfMonth(date);
+        else if (type == 4)
+            result = DateUtil.beginOfQuarter(date);
+        else if (type == 5)
+            result = DateUtil.beginOfYear(date);
+        return result;
+    }
+
+    /**
+     * 获取范围开始的时间
+     *
+     * @param type 1、当日;2、本周;3、本月;4、季度;5、年度
+     */
+    public static String getStartTime(String date, int type) {
+        return DatesUtil.forDateTime(getStartTime(DatesUtil.forDate(date), type));
+    }
+
+    /**
+     * 获取范围结束的时间
+     *
+     * @param type 1、当日;2、本周;3、本月;4、季度;5、年度
+     */
+    public static String getEndTime(String date, int type) {
+        return DatesUtil.forDateTime(getEndTime(DatesUtil.forDate(date), type));
+    }
+
+    /**
+     * 获取范围结束的时间
+     *
+     * @param type 1、当日;2、本周;3、本月;4、季度;5、年度
+     */
+    public static Date getEndTime(Date date, int type) {
+        Date result = null;
+        if (type == 1)
+            result = DateUtil.endOfDay(date);
+        else if (type == 2)
+            result = DateUtil.endOfWeek(date);
+        else if (type == 3)
+            result = DateUtil.endOfMonth(date);
+        else if (type == 4)
+            result = DateUtil.endOfQuarter(date);
+        else if (type == 5)
+            result = DateUtil.endOfYear(date);
+        return result;
+    }
+
+    /**
+     * 根据传入的字符串时间格式化日期
+     *
+     * @param str
+     * @return dateTime
+     */
+    public static Date forDateTime(String str) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        Date dateTime = null;
+        try {
+            dateTime = sdf.parse(str);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return dateTime;
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDateTime16(Date date) {
+        if (date == null)
+            date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+        return sdf.format(date);
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDateTime14(Date date) {
+        if (date == null)
+            date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm");
+        return sdf.format(date);
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDateTimeAll14() {
+        Date date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+        return sdf.format(date);
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDateTimeAll17() {
+        Date date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
+        return sdf.format(date);
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDateTimeAll10() {
+        Date date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
+        return sdf.format(date);
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30:10
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDateTime(Date date) {
+        if (date == null)
+            date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return sdf.format(date);
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30:10
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDateTime() {
+        return DatesUtil.forDateTime(new Date());
+    }
+
+    /**
+     * 日时格式化
+     *
+     * @return 2002-02-02 01:30:10
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static Date forDate(String str) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Date dateTime = null;
+        try {
+            dateTime = sdf.parse(str);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return dateTime;
+    }
+
+    /**
+     * 日期格式化
+     *
+     * @return 2002-02-02
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDate(Date date) {
+        if (date == null)
+            date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        return sdf.format(date);
+    }
+
+    /**
+     * 日期格式化
+     *
+     * @return 2002-02-02
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String forDate() {
+        return DatesUtil.forDate(new Date());
+    }
+
+    /**
+     * 是过去
+     *
+     * @return true
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static boolean overTimeFlag(Date date) {
+        return date == null ? false : date.compareTo(new Date()) < 0;
+    }
+
+    /**
+     * 加上分钟数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static Date addMinute(Date date, int minute) {
+        Date result = date == null ? new Date() : (Date) date.clone();
+        result.setMinutes(result.getMinutes() + minute);
+        return result;
+    }
+
+    /**
+     * 加上小时数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static Date addHour(Date date, BigDecimal hour) {
+        Date result = (Date) date.clone();
+        result.setMinutes(date.getMinutes() + hour.multiply(new BigDecimal(60)).intValue());
+        return result;
+    }
+
+    /**
+     * 加上小时数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String addHour(String dateTime, BigDecimal hour) {
+        return DatesUtil.forDateTime(DatesUtil.addHour(DatesUtil.forDateTime(dateTime), hour));
+    }
+
+    /**
+     * 加上小时数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String addHour(String dateTime, int hour) {
+        return DatesUtil.forDateTime(DatesUtil.addHour(DatesUtil.forDateTime(dateTime), new BigDecimal(hour)));
+    }
+
+    /**
+     * 加上天数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static String addDay(String dateTime, int count) {
+        return DatesUtil.forDate(DatesUtil.addHour(DatesUtil.forDate(dateTime), new BigDecimal(count * 24)));
+    }
+
+    /**
+     * 加上天数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static Date addDay(Date dateTime, int count) {
+        return DatesUtil.addHour(dateTime, new BigDecimal(count * 24));
+    }
+
+    /**
+     * 相差分钟
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    /*public static int minuteCount(Date sDate, Date eDate) {
+        return BigDecimalUtil.divide(new BigDecimal(Math.toIntExact(eDate.getTime() - sDate.getTime())), new BigDecimal(1000 * 60), 0, BigDecimal.ROUND_CEILING).intValue();
+    }*/
+
+    /**
+     * 相差小时数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static double hourCount(String sDate, String eDate) {
+        return Math.toIntExact(DatesUtil.forDateTime(eDate).getTime() - DatesUtil.forDateTime(sDate).getTime()) / (1000 * 60 * 60 * 1.0);
+    }
+
+    /**
+     * 相差小时数
+     *
+     * @return 小时数
+     * @author xiang
+     * @date 2023/3/20 18:00
+     **/
+    public static double hourCount(Date s_date, Date e_date) {
+        return Math.toIntExact(e_date.getTime() - s_date.getTime()) / (1000 * 60 * 60 * 1.0);
+    }
+
+    /**
+     * 获取两个日期之间的所有整点和半点
+     *
+     * @param startDate 开始日期
+     * @param endDate   结束日期
+     * @return 集合(2023 - 05 - 18 18 : 00, 2023 - 05 - 18 18 : 30)
+     */
+    public static List<String> hourMinuteBetween(Date startDate, Date endDate) {
+        int hour = (int) DatesUtil.hourCount(startDate, endDate);
+        List<String> list = new ArrayList<>();
+        int startHour = startDate.getHours();
+        for (int i = startHour; i < startHour + hour; i++) {
+            Date startTime = (Date) startDate.clone();
+            startTime.setHours(i);
+            String date = DatesUtil.forDate(startTime) + " ";
+            list.add(date + DatesUtil.forJiDian(i, 0));
+            list.add(date + DatesUtil.forJiDian(i, 30));
+        }
+        //删除最后两条
+        return list.stream().distinct().collect(Collectors.toList());
+    }
+
+    /**
+     * 获取两个日期之间的所有日期
+     *
+     * @param startDate 开始日期
+     * @param endDate   结束日期
+     * @return List集合
+     */
+    public static List<String> dateBetween(Date startDate, Date endDate) {
+        Date sDate = new Date(startDate.getTime());
+        List<String> list = new ArrayList<>();
+        while (sDate.compareTo(endDate) < 1) {
+            list.add(DatesUtil.forDate(sDate));
+            sDate.setHours(24);
+        }
+        return list;
+    }
+
+    /**
+     * 获取两个日期之间的所有日期
+     *
+     * @param s_date 开始日期
+     * @param e_date 结束日期
+     * @param length 日期转换长度 4:年,7:月,10/19:日
+     * @return List集合
+     */
+    public static List<String> dateBetween(String s_date, String e_date, int length) {
+        List<String> result = new ArrayList<>();
+        //年
+        if (length == 4) {
+            int startYear = Integer.parseInt(s_date.substring(0, 4));
+            int endYear = Integer.parseInt(e_date.substring(0, 4));
+            for (int i = 0; i < endYear - startYear + 1; i++) {
+                result.add(String.valueOf(startYear + i));
+            }
+        }
+        //月
+        else if (length == 7) {
+            List<String> dateLst = DatesUtil.dateBetween(DatesUtil.forDate(s_date), DatesUtil.forDate(e_date));
+            result = dateLst.stream().map(m -> m.substring(0, 7)).distinct().collect(Collectors.toList());
+        }
+        //日
+        else if (length == 10) {
+            result = DatesUtil.dateBetween(DatesUtil.forDate(s_date), DatesUtil.forDate(e_date));
+        }
+        //时
+        else if (length == 13) {
+            List<String> timeResult = new ArrayList<>(result.size() * 24);
+            result = DatesUtil.dateBetween(DatesUtil.forDate(s_date), DatesUtil.forDate(e_date));
+            result.forEach(r -> {
+                timeResult.add(r + " 00");
+                timeResult.add(r + " 01");
+                timeResult.add(r + " 02");
+                timeResult.add(r + " 03");
+                timeResult.add(r + " 04");
+                timeResult.add(r + " 05");
+                timeResult.add(r + " 06");
+                timeResult.add(r + " 07");
+                timeResult.add(r + " 08");
+                timeResult.add(r + " 09");
+                timeResult.add(r + " 10");
+                timeResult.add(r + " 11");
+                timeResult.add(r + " 12");
+                timeResult.add(r + " 13");
+                timeResult.add(r + " 14");
+                timeResult.add(r + " 15");
+                timeResult.add(r + " 16");
+                timeResult.add(r + " 17");
+                timeResult.add(r + " 18");
+                timeResult.add(r + " 19");
+                timeResult.add(r + " 20");
+                timeResult.add(r + " 21");
+                timeResult.add(r + " 22");
+                timeResult.add(r + " 23");
+            });
+            return timeResult;
+        }
+        return result;
+    }
+
+    /**
+     * 获取两个日期之间的所有周期
+     *
+     * @param startDate 开始日期
+     * @param endDate   结束日期
+     * @return 集合(5, 6, 7)
+     */
+    public static List<String> weekBetween(Date startDate, Date endDate) {
+        Date sDate = new Date(startDate.getTime());
+        List<String> list = new ArrayList<>();
+        while (sDate.compareTo(endDate) < 1) {
+            list.add(String.valueOf(sDate.getDay()));
+            sDate.setHours(24);
+        }
+        return list.stream().distinct().collect(Collectors.toList());
+    }
+
+    /**
+     * 在两个两个日期之间
+     *
+     * @param startDate 开始日期
+     * @param endDate   结束日期
+     * @return boolean
+     */
+    public static boolean dateBetween(Date date, Date startDate, Date endDate) {
+        if (date == null || startDate == null || endDate == null)
+            return false;
+        return startDate.compareTo(date) < 1 && endDate.compareTo(date) > -1;
+    }
+
+    /***
+     * 两段日期是否有交集
+     * 其中一个结束日可以为空
+     * @param startDateOne  开始日一
+     * @param endDateOne    结束日一
+     * @param startDateTwo  开始日二
+     * @param endDateTwo    结束日二
+     * @return 交集true
+     */
+    public static Boolean isInterSection(Date startDateOne, Date endDateOne, Date startDateTwo, Date endDateTwo) {
+        if (endDateOne == null)
+            return startDateOne.compareTo(startDateTwo) > -1 && startDateOne.compareTo(endDateTwo) < 1;
+        if (endDateTwo == null)
+            return startDateTwo.compareTo(startDateOne) > -1 && startDateTwo.compareTo(endDateOne) < 1;
+        Date maxStartDate = startDateOne;
+        if (maxStartDate.before(startDateTwo))
+            maxStartDate = startDateTwo;
+        Date minEndDate = endDateOne;
+        if (endDateTwo.before(minEndDate))
+            minEndDate = endDateTwo;
+        return maxStartDate.compareTo(minEndDate) < 1;
+    }
+
+    /**
+     * 拼接完整日期
+     *
+     * @param date       日期
+     * @param hourMinute 时分
+     * @return 01:30
+     * @author xiang
+     * @date 2023/3/11 18:00:00
+     **/
+    public static String forDateTime(String date, String hourMinute) {
+        return StringUtils.join(date, " ", hourMinute, ":00");
+    }
+
+    /**
+     * 获取时分格式化
+     *
+     * @return 01:30
+     * @author xiang
+     * @date 2023/3/11 18:00
+     **/
+    public static String forJiDian(Date date) {
+        return DatesUtil.forJiDian(date.getHours(), date.getMinutes());
+    }
+
+    /**
+     * 获取时分格式化
+     *
+     * @return 01:30
+     * @author xiang
+     * @date 2023/3/11 18:00
+     **/
+    public static String forJiDian(int h, int m) {
+        return StringUtils.join(String.format("%02d", h % 24), ":", String.format("%02d", m));
+    }
+
+    /**
+     * 相差天数
+     *
+     * @param date1 开始时间
+     * @param date2 结束时间
+     * @return 天数
+     */
+    public static int differentDaysByMillisecond(String date1, String date2) {
+        Long dates = (DatesUtil.forDate(date2).getTime() - DatesUtil.forDate(date1).getTime()) / 1000 / 3600 / 24;
+        return dates.intValue();
+    }
+
+    public static void main(String[] args) {
+
+    }
+
+}

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

@@ -0,0 +1,108 @@
+package vip.xiaonuo.common.util;
+
+import cn.hutool.core.util.RandomUtil;
+
+import java.util.Random;
+
+/**
+ * 提供通用唯一识别码(universally unique identifier)(UUID)实现
+ *
+ * @author xianghao
+ * @Date 2021/10/15
+ */
+public class HexUtils {
+
+    //组成元素62位
+    private static String str62 = "ZXCVBNMASDFGHJKLQWERTYUIOPzxcvbnmasdfghjklqwertyuiop1234567890";
+    //组成元素16位
+    private static String str16 = "abcdef1234567890";
+    //组成元素10位
+    private static String str10 = "1234567890";
+    //随机工具
+    private static Random random = new Random();
+
+    /**
+     * 生成62进制密钥
+     *
+     * @param length:密钥长度
+     * @return String
+     */
+    public static String getByte62UUID(int length) {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < length; i++) {
+            sb.append(str62.charAt(random.nextInt(62)));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 生成16进制数
+     *
+     * @param length:密钥长度
+     * @return 16进制数
+     */
+    public static String getByte16UUID(int length) {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < length; i++) {
+            sb.append(str16.charAt(random.nextInt(16)));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 生成10进制密钥
+     *
+     * @param length:密钥长度
+     * @return String
+     */
+    public static String getByte10UUID(int length) {
+        return RandomUtil.randomString(str10, length);
+    }
+
+    /**
+     * 生成 11位时间戳+8位密钥
+     *
+     * @return Long
+     */
+    public static Long getByteTime11UUID8() {
+        return Long.parseLong(System.currentTimeMillis() / 100 + HexUtils.getByte10UUID(8));
+    }
+
+
+    /**
+     * 生成 13位时间戳+6位密钥
+     *
+     * @return Long
+     */
+    public static String getByteTime13UUID6() {
+        return System.currentTimeMillis() + HexUtils.getByte16UUID(6);
+    }
+
+    /**
+     * 生成 14位时间+5位16进制随机
+     *
+     * @return String
+     */
+    public static String getByteTime14UUID5() {
+        return DatesUtil.forDateTimeAll14() + HexUtils.getByte16UUID(5);
+    }
+
+    /**
+     * 生成 17位时间+2位16进制随机
+     *
+     * @return String
+     */
+    public static String getByteTime17UUID2() {
+        return DatesUtil.forDateTimeAll17() + HexUtils.getByte16UUID(2);
+    }
+
+    /**
+     * 生成 17位时间+2位16进制随机
+     *
+     * @return String
+     */
+    public static String getByteTime10UUID7() {
+        return DatesUtil.forDateTimeAll10() + HexUtils.getByte16UUID(7);
+    }
+
+}

+ 291 - 0
snowy-common/src/main/java/vip/xiaonuo/common/util/QRCodeUtil.java

@@ -0,0 +1,291 @@
+package vip.xiaonuo.common.util;
+
+import com.google.zxing.*;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import sun.misc.BASE64Encoder;
+
+import javax.imageio.ImageIO;
+import javax.swing.filechooser.FileSystemView;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+@Component
+@Slf4j
+public class QRCodeUtil {
+    /**
+     * CODE_WIDTH:二维码宽度,单位像素
+     * CODE_HEIGHT:二维码高度,单位像素
+     * FRONT_COLOR:二维码前景色,0x000000 表示黑色
+     * BACKGROUND_COLOR:二维码背景色,0xFFFFFF 表示白色
+     * 演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白
+     */
+    private static final int CODE_WIDTH = 400;
+    private static final int CODE_HEIGHT = 400;
+    private static final int FRONT_COLOR = 0x000000;
+    private static final int BACKGROUND_COLOR = 0xFFFFFF;
+
+
+    /**
+     * @param codeContent        二维码参数内容,如果是一个网页地址,如 https://www.baidu.com/ 则 微信扫一扫会直接进入此地址, 如果是一些参数,如
+     *                           1541656080837,则微信扫一扫会直接回显这些参数值
+     * @param codeImgFileSaveDir 二维码图片保存的目录,如 D:/codes
+     * @param fileName           二维码图片文件名称,带格式,如 123.png
+     */
+    public static void createCodeToFile(String codeContent, File codeImgFileSaveDir, String fileName) {
+        try {
+            if (codeContent == null || "".equals(codeContent)) {
+                log.info("二维码内容为空,不进行操作...");
+                return;
+            }
+            codeContent = codeContent.trim();
+            if (codeImgFileSaveDir == null || codeImgFileSaveDir.isFile()) {
+                codeImgFileSaveDir = FileSystemView.getFileSystemView().getHomeDirectory();
+                log.info("二维码图片存在目录为空,默认放在桌面...");
+            }
+            if (!codeImgFileSaveDir.exists()) {
+                codeImgFileSaveDir.mkdirs();
+                log.info("二维码图片存在目录不存在,开始创建...");
+            }
+            if (fileName == null || "".equals(fileName)) {
+                fileName = new Date().getTime() + ".png";
+                log.info("二维码图片文件名为空,随机生成 png 格式图片...");
+            }
+
+            BufferedImage bufferedImage = getBufferedImage(codeContent);
+
+            /*
+             * javax.imageio.ImageIO:java扩展的图像IO
+             * write(RenderedImage im, String formatName, File output)
+             *       im:待写入的图像, formatName:图像写入的格式,output:写入的图像文件,文件不存在时会自动创建
+             */
+            File codeImgFile = new File(codeImgFileSaveDir, fileName);
+            ImageIO.write(bufferedImage, "png", codeImgFile);
+
+            log.info("二维码图片生成成功:" + codeImgFile.getPath());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /**
+     * 生成二维码并输出到输出流, 通常用于输出到网页上进行显示
+     * 输出到网页与输出到磁盘上的文件中,区别在于最后一句 ImageIO.write
+     * write(RenderedImage im,String formatName,File output):写到文件中
+     * write(RenderedImage im,String formatName,OutputStream output):输出到输出流中
+     *
+     * @param codeContent  :二维码内容
+     * @param outputStream :输出流,比如 HttpServletResponse 的 getOutputStream
+     */
+    public static void createCodeToOutputStream(String codeContent, OutputStream outputStream) {
+        try {
+            if (codeContent == null || "".equals(codeContent.trim())) {
+                log.info("二维码内容为空,不进行操作...");
+                return;
+            }
+            codeContent = codeContent.trim();
+
+            BufferedImage bufferedImage = getBufferedImage(codeContent);
+            /*
+             * 区别就是以一句,输出到输出流中,如果第三个参数是 File,则输出到文件中
+             */
+            ImageIO.write(bufferedImage, "png", outputStream);
+            log.info("二维码图片生成到输出流成功...");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("发生错误: {}!", e.getMessage());
+        }
+    }
+
+
+    /**
+     * 生成二维码并输出到输出流, 通常用于输出到网页上进行显示
+     * 输出到网页与输出到磁盘上的文件中,区别在于最后一句 ImageIO.write
+     * write(RenderedImage im,String formatName,File output):写到文件中
+     * write(RenderedImage im,String formatName,OutputStream output):输出到输出流中
+     *
+     * @param codeContent :二维码内容
+     */
+    public static String createCodeToBase64(String codeContent) {
+        String png_base64 = null;
+        try {
+            if (codeContent == null || "".equals(codeContent.trim())) {
+                log.info("二维码内容为空,不进行操作...");
+                return null;
+            }
+            codeContent = codeContent.trim();
+
+            BufferedImage bufferedImage = getBufferedImage(codeContent);
+            log.info("二维码图片生成到输出流成功...");
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();//io流
+            ImageIO.write(bufferedImage, "png", baos);//写入流中
+            byte[] bytes = baos.toByteArray();//转换成字节
+            BASE64Encoder encoder = new BASE64Encoder();
+            png_base64 = encoder.encodeBuffer(bytes).trim();//转换成base64串
+            png_base64 = "data:image/jpg;base64," + png_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
+
+            //        ImageIO.write(bufferedImage, "png", new File("D:/qrcode1.png"));
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("发生错误: {}!", e.getMessage());
+        }
+        return png_base64;
+    }
+
+
+    private static BufferedImage getBufferedImage(String codeContent) throws WriterException {
+        /*
+         * com.google.zxing.EncodeHintType:编码提示类型,枚举类型
+         * EncodeHintType.CHARACTER_SET:设置字符编码类型
+         * EncodeHintType.ERROR_CORRECTION:设置误差校正
+         * ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
+         *   不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
+         * EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近
+         */
+        Map<EncodeHintType, Object> hints = new HashMap();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+        hints.put(EncodeHintType.MARGIN, 1);
+
+        /*
+         * MultiFormatWriter:多格式写入,这是一个工厂类,里面重载了两个 encode 方法,用于写入条形码或二维码
+         *      encode(String contents,BarcodeFormat format,int width, int height,Map<EncodeHintType,?> hints)
+         *      contents:条形码/二维码内容
+         *      format:编码类型,如 条形码,二维码 等
+         *      width:码的宽度
+         *      height:码的高度
+         *      hints:码内容的编码类型
+         * BarcodeFormat:枚举该程序包已知的条形码格式,即创建何种码,如 1 维的条形码,2 维的二维码 等
+         * BitMatrix:位(比特)矩阵或叫2D矩阵,也就是需要的二维码
+         */
+        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+        BitMatrix bitMatrix = multiFormatWriter.encode(codeContent, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
+
+        /*
+         * java.awt.image.BufferedImage:具有图像数据的可访问缓冲图像,实现了 RenderedImage 接口
+         * BitMatrix 的 get(int x, int y) 获取比特矩阵内容,指定位置有值,则返回true,将其设置为前景色,否则设置为背景色
+         * BufferedImage 的 setRGB(int x, int y, int rgb) 方法设置图像像素
+         *      x:像素位置的横坐标,即列
+         *      y:像素位置的纵坐标,即行
+         *      rgb:像素的值,采用 16 进制,如 0xFFFFFF 白色
+         */
+        BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR);
+        for (int x = 0; x < CODE_WIDTH; x++) {
+            for (int y = 0; y < CODE_HEIGHT; y++) {
+                bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? FRONT_COLOR : BACKGROUND_COLOR);
+            }
+        }
+        return bufferedImage;
+    }
+
+
+    /**
+     * 根据本地二维码图片解析二维码内容 注:图片必须是二维码图片,但也可以是微信用户二维码名片,上面有名称、头像也是可以的)
+     *
+     * @param file 本地二维码图片文件,如 E:\logs\2.jpg
+     * @return
+     * @throws Exception
+     */
+    public static String parseQRCodeByFile(File file) {
+        String resultStr = null;
+        if (file == null || file.isDirectory() || !file.exists()) {
+            return resultStr;
+        }
+        try {
+            /*
+             * ImageIO的BufferedImage read(URL input)方法用于读取网络图片文件转为内存缓冲图像
+             * 同理还有:read(File input)、read(InputStream input)、、read(ImageInputStream stream)
+             */
+            BufferedImage bufferedImage = ImageIO.read(file);
+            /*
+             * com.google.zxing.client.j2se.BufferedImageLuminanceSource:缓冲图像亮度源
+             * 将 java.awt.image.BufferedImage 转为 zxing 的 缓冲图像亮度源
+             * 关键就是下面这几句:HybridBinarizer 用于读取二维码图像数据,BinaryBitmap 二进制位图
+             */
+            BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
+            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+            Hashtable hints = new Hashtable();
+            hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
+            /*
+             * 如果图片不是二维码图片,则 decode 抛异常:com.google.zxing.NotFoundException
+             * MultiFormatWriter 的 encode 用于对内容进行编码成 2D 矩阵
+             * MultiFormatReader 的 decode 用于读取二进制位图数据
+             */
+            Result result = new MultiFormatReader().decode(bitmap, hints);
+            resultStr = result.getText();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (NotFoundException e) {
+            e.printStackTrace();
+            log.error("图片非二维码图片, 路径是: {}!", file.getPath());
+        }
+        return resultStr;
+    }
+
+
+    /**
+     * 根据网络二维码图片解析二维码内容, 区别仅仅在于 ImageIO.read(url); 这一个重载的方法)
+     *
+     * @param url 二维码图片网络地址,如 https://res.wx.qq.com/mpres/htmledition/images/mp_qrcode3a7b38.gif
+     * @return
+     * @throws Exception
+     */
+    public static String parseQRCodeByUrl(URL url) {
+        String resultStr = null;
+        if (url == null) {
+            return resultStr;
+        }
+        try {
+            /*
+             * ImageIO 的 BufferedImage read(URL input) 方法用于读取网络图片文件转为内存缓冲图像
+             * 同理还有:read(File input)、read(InputStream input)、、read(ImageInputStream stream)
+             * 如果图片网络地址错误,比如不能访问,则 read 抛异常:javax.imageio.IIOException: Can't get input stream from URL!
+             */
+            BufferedImage bufferedImage = ImageIO.read(url);
+            /*
+             * com.google.zxing.client.j2se.BufferedImageLuminanceSource:缓冲图像亮度源
+             * 将 java.awt.image.BufferedImage 转为 zxing 的 缓冲图像亮度源
+             * 关键就是下面这几句:HybridBinarizer 用于读取二维码图像数据,BinaryBitmap 二进制位图
+             */
+            BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
+            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+            Hashtable hints = new Hashtable();
+            /*
+             * 如果内容包含中文,则解码的字符集格式应该和编码时一致
+             */
+            hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
+            /*
+             * 如果图片不是二维码图片,则 decode 抛异常:com.google.zxing.NotFoundException
+             * MultiFormatWriter 的 encode 用于对内容进行编码成 2D 矩阵
+             * MultiFormatReader 的 decode 用于读取二进制位图数据
+             */
+            Result result = new MultiFormatReader().decode(bitmap, hints);
+            resultStr = result.getText();
+        } catch (IOException e) {
+            e.printStackTrace();
+            log.error("二维码图片地址错误, 地址是: {}!", url);
+        } catch (NotFoundException e) {
+            e.printStackTrace();
+            log.error("图片非二维码图片, 地址是: {}!", url);
+        }
+        return resultStr;
+    }
+
+    public static void main(String[] args) {
+        QRCodeUtil.createCodeToOutputStream("严格狼爷", null);
+    }
+}

二进制
snowy-common/src/main/resources/ip2region.xdb


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

@@ -0,0 +1,11 @@
+# 插件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-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接口

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

@@ -0,0 +1,36 @@
+<?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>
+
+    <properties>
+        <sa.token.version>1.31.0</sa.token.version>
+    </properties>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <!-- sa-token-core -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-core</artifactId>
+            <version>${sa.token.version}</version>
+        </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<String> getRoleCodeListByUserId(String userId);
+
+    /**
+     * 根据用户id获取按钮码集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:54
+     */
+    List<String> getButtonCodeListListByUserId(String userId);
+
+    /**
+     * 根据用户id获取移动端按钮码集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:54
+     */
+    List<String> getMobileButtonCodeListListByUserId(String userId);
+
+    /**
+     * 根据用户id获取权限集合
+     *
+     * @author xuyuxiang
+     * @date 2022/4/27 22:54
+     */
+    List<JSONObject> getPermissionListByUserId(String userId, 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);
+    }
+}

+ 65 - 0
snowy-plugin-api/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/util/StpLoginUserUtil.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.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;
+    }
+
+    /**
+     * 是超管或者业管
+     *
+     * @author xuyuxiang
+     * @date 2022/7/8 10:41
+     **/
+    public static boolean isAdmin(String account) {
+        return "bizAdmin".equals(account) || "superAdmin".equals(account);
+    }
+}

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

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

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

@@ -0,0 +1,25 @@
+<?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>
+            <version>${project.parent.version}</version>
+        </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接口

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

@@ -0,0 +1,25 @@
+<?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>
+            <version>${project.parent.version}</version>
+        </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接口

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

@@ -0,0 +1,101 @@
+<?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>
+
+    <properties>
+        <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>
+        <ali.oss.version>3.14.0</ali.oss.version>
+        <minio.version>3.0.12</minio.version>
+        <javax.mail.version>1.6.2</javax.mail.version>
+        <aliyun.sdk.dm.version>3.3.1</aliyun.sdk.dm.version>
+        <aliyun.sdk.ecs.version>3.1.0</aliyun.sdk.ecs.version>
+        <aliyun.sdk.dysmsapi.version>2.0.9</aliyun.sdk.dysmsapi.version>
+        <oshi.core.version>6.2.2</oshi.core.version>
+    </properties>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+            <version>${project.parent.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>
+    </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);
+}

+ 131 - 0
snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevFileApi.java

@@ -0,0 +1,131 @@
+/*
+ * 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);
+
+    /**
+     * 根据上传文件id修改图片前缀文件路径
+     *
+     * @param fileId 文件id
+     * @author xuyuxiang
+     * @date 2022/6/22 17:44
+     **/
+    String storageFilePathReturnUrlById(String fileId);
+
+    /**
+     * 上传文件并修改图片前缀文件路径
+     *
+     * @param file 文件
+     * @author xuyuxiang
+     * @date 2022/6/22 17:44
+     **/
+    String storageFilePathReturnUrl(MultipartFile file);
+
+    /**
+     * 修改图片前缀文件路径
+     *
+     * @param storagePath 文件存储路径
+     * @author xuyuxiang
+     * @date 2022/6/22 17:44
+     **/
+    String storageFilePathReturnUrl(String storagePath);
+}

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

+ 95 - 0
snowy-plugin-api/snowy-plugin-dev-api/src/main/java/vip/xiaonuo/dev/api/DevMessageApi.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.dev.api;
+
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.List;
+
+/**
+ * 站内信API接口
+ *
+ * @author xuyuxiang
+ * @date 2022/6/22 17:33
+ **/
+public interface DevMessageApi {
+
+    /**
+     * 发送站内信,默认:分类系统通知
+     *
+     * @param receiverIdList 接收的用户id集合
+     * @param subject 主题
+     * @author xuyuxiang
+     * @date 2022/6/22 17:35
+     **/
+    void sendMessage(List<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 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);
+
+}

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

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

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

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

@@ -0,0 +1,25 @@
+<?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>
+            <version>${project.parent.version}</version>
+        </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接口

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

@@ -0,0 +1,25 @@
+<?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>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+</project>

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

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

+ 33 - 0
snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/api/MobileButtonApi.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 java.util.List;
+
+/**
+ * 移动端按钮API
+ *
+ * @author xuyuxiang
+ * @date 2023/2/1 9:52
+ **/
+public interface MobileButtonApi {
+
+    /**
+     * 根据键获取值
+     *
+     * @param idList
+     * @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);
+}

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

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

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

@@ -0,0 +1,25 @@
+<?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>
+            <version>${project.parent.version}</version>
+        </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);
+}

+ 75 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysOrgApi.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 组织API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:35
+ **/
+public interface SysOrgApi {
+
+    /**
+     * 根据id获取名称
+     *
+     * @author xuyuxiang
+     * @date 2022/8/4 10:12
+     **/
+    String getNameById(String orgId);
+
+    /**
+     * 根据组织id获取部门主管id
+     *
+     * @author xuyuxiang
+     * @date 2022/6/6 14:50
+     **/
+    String getSupervisorIdByOrgId(String orgId);
+
+    /**
+     * 获取组织树选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:46
+     **/
+    List<Tree<String>> orgTreeSelector();
+
+    /**
+     * 获取组织列表选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:45
+     **/
+    List<JSONObject> orgListSelector(String parentId);
+
+    /**
+     * 获取登陆用户组织及下辖组织列表id
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:45
+     **/
+    List<String> orgIdLst();
+
+    /**
+     * 获取组织及下辖组织列表id
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:45
+     **/
+    List<String> orgIdLst(String orgId);
+}

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

@@ -0,0 +1,42 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 职位API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:35
+ **/
+public interface SysPositionApi {
+
+    /**
+     * 根据id获取名称
+     *
+     * @author xuyuxiang
+     * @date 2022/8/4 10:13
+     **/
+    String getNameById(String positionId);
+
+    /**
+     * 获取职位选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:47
+     **/
+    List<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);
+}

+ 58 - 0
snowy-plugin-api/snowy-plugin-sys-api/src/main/java/vip/xiaonuo/sys/api/SysRoleApi.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.sys.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 角色API
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:36
+ **/
+public interface SysRoleApi {
+
+    /**
+     * 判断组织下是否存在角色
+     *
+     * @author xuyuxiang
+     * @date 2022/8/2 11:16
+     */
+    boolean orgHasRole(List<String> orgIdList);
+
+    /**
+     * 获取角色选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/7/22 14:49
+     **/
+    List<JSONObject> roleSelector(String orgId, String category, String searchKey);
+
+    /**
+     * 代码生成菜单按钮授权
+     *
+     * @author xuyuxiang
+     * @date 2022/11/1 15:58
+     **/
+    void grantForGenMenuAndButton(String menuId);
+
+    /**
+     * 获取角色名称
+     *
+     * @author xuyuxiang
+     * @date 2022/11/1 15:58
+     **/
+    List<String> getNameByCodes(List<String> roleCodes);
+}

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

@@ -0,0 +1,150 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.sys.api;
+
+import cn.hutool.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * 用户Api
+ *
+ * @author xuyuxiang
+ * @date 2022/6/6 11:33
+ **/
+public interface SysUserApi {
+
+    /**
+     * 根据用户id获取用户对象,没有则返回null
+     *
+     * @author xuyuxiang
+     * @date 2022/6/20 18:19
+     **/
+    JSONObject getUserByIdWithoutException(String userId);
+
+    /**
+     * 根据用户id获取用户对象列表,没有的则为空,都没有则返回空集合
+     *
+     * @author xuyuxiang
+     * @date 2022/6/20 18:19
+     **/
+    List<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);
+
+    /**
+     * 获取用户拥有粮库
+     * 根据code
+     *
+     * @author xuyuxiang
+     * @date 2022/5/13 21:00
+     */
+    List<String> ownDepot(String userId);
+
+    /**
+     * 获取用户过滤粮库
+     * 根据code
+     *
+     * @author xuyuxiang
+     * @date 2022/5/13 21:00
+     */
+    List<String> ownDepotGrep();
+
+    /**
+     * 获取用户下辖组织idLst
+     * 根据code
+     *
+     * @author xuyuxiang
+     * @date 2022/5/13 21:00
+     */
+    List<String> ownOrgGrep(Boolean loginFlag);
+
+    /**
+     * 获取下辖组织idLst
+     * 根据code
+     *
+     * @author xuyuxiang
+     * @date 2022/5/13 21:00
+     */
+    List<String> ownOrgGrep(String orgId);
+
+    /**
+     * 给用户授权角色
+     *
+     * @author xuyuxiang
+     * @date 2022/8/1 18:28
+     */
+    void grantRole(String userId, List<String> roleIdList);
+
+    /**
+     * 给用户授权粮库
+     *
+     * @author xuyuxiang
+     * @date 2022/8/1 18:28
+     */
+    void grantDepot(String userId, List<String> depotIdList);
+
+    /**
+     * 根据组织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
+     **/
+    String getSupervisorIdByUserIdAndOrgIdAndPositionId(String userId, String orgId, String positionId);
+
+    /**
+     * 获取用户选择器
+     *
+     * @author xuyuxiang
+     * @date 2022/4/24 20:08
+     */
+    List<JSONObject> userSelector(String orgId, String searchKey);
+}

+ 11 - 0
snowy-plugin/README.md

@@ -0,0 +1,11 @@
+# 插件模块
+
+####登录鉴权插件: snowy-plugin-auth
+
+####业务功能插件: snowy-plugin-biz
+
+####C端功能插件: snowy-plugin-client
+
+####开发工具插件: snowy-plugin-dev
+
+####系统功能插件: 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 @@
+# 登录鉴权插件

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

@@ -0,0 +1,73 @@
+<?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>
+
+    <properties>
+        <sa.token.version>1.31.0</sa.token.version>
+        <just.auth.version>1.16.5</just.auth.version>
+    </properties>
+
+    <dependencies>
+
+        <!-- 每个插件都要引入自己的对外接口 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-auth-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <!-- 引入开发工具接口,用于获取配置 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-dev-api</artifactId>
+            <version>${project.parent.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>
+    </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.SaAnnotationInterceptor;
+import cn.dev33.satoken.stp.StpInterface;
+import cn.dev33.satoken.stp.StpLogic;
+import cn.dev33.satoken.strategy.SaStrategy;
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
+import vip.xiaonuo.auth.core.util.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 SaAnnotationInterceptor()).addPathPatterns("/**");
+    }
+
+    @Bean("stpLogic")
+    public StpLogic getStpLogic() {
+        // 重写Sa-Token的StpLogic,默认客户端类型为B
+        return new StpLogic(SaClientTypeEnum.B.getValue());
+    }
+
+    @Bean("stpClientLogic")
+    public StpLogic getStpClientLogic() {
+        // 重写Sa-Token的StpLogic,默认客户端类型为C
+        return new StpLogic(SaClientTypeEnum.C.getValue());
+    }
+
+    @Bean
+    public void rewriteSaStrategy() {
+        // 重写Sa-Token的注解处理器,增加注解合并功能
+        SaStrategy.me.getAnnotation = AnnotatedElementUtils::getMergedAnnotation;
+    }
+
+    /**
+     * 权限认证接口实现类,集成权限认证功能
+     *
+     * @author xuyuxiang
+     * @date 2022/7/7 16:16
+     **/
+    @Component
+    public static class StpInterfaceImpl implements StpInterface {
+
+        /**
+         * 返回一个账号所拥有的权限码集合
+         */
+        @Override
+        public List<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"));
+    }
+}

+ 68 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/core/util/AuthExceptionUtil.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.auth.core.util;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.exception.*;
+import cn.hutool.http.HttpStatus;
+import lombok.extern.slf4j.Slf4j;
+import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.common.util.CommonServletUtil;
+
+@Slf4j
+public class AuthExceptionUtil {
+
+    /**
+     * 根据错误类型获取对应的CommonResult(只处理SaToken相关异常)
+     *
+     * @author xuyuxiang
+     * @date 2021/10/11 15:52
+     **/
+    public static CommonResult<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 {
+            // 未知异常才打印
+            e.printStackTrace();
+            // 未知异常返回服务器异常(此处不可能执行进入,因为本方法处理的一定是SaToken的异常,此处仅为安全性考虑)
+            commonResult = CommonResult.error("服务器异常");
+        }
+        log.error(">>> {},请求地址:{}", commonResult.getMsg(), SaHolder.getRequest().getUrl());
+        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端登录日志
+            SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserById(Convert.toStr(loginId));
+            if(ObjectUtil.isNotEmpty(saBaseLoginUser)) {
+                devLogApi.executeLoginLog(saBaseLoginUser.getName());
+            } else {
+                devLogApi.executeLoginLog(null);
+            }
+        } else {
+            clientLoginUserApi.updateUserLoginInfo(Convert.toStr(loginId), loginModel.getDevice());
+        }
+    }
+
+    /** 每次注销时触发 */
+    @Override
+    public void doLogout(String loginType, Object loginId, String tokenValue) {
+        if(SaClientTypeEnum.B.getValue().equals(loginType)) {
+            // 记录B端登出日志
+            SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserById(Convert.toStr(loginId));
+            if(ObjectUtil.isNotEmpty(saBaseLoginUser)) {
+                devLogApi.executeLogoutLog(saBaseLoginUser.getName());
+            } else {
+                devLogApi.executeLogoutLog(null);
+            }
+        }
+    }
+
+    /** 每次被踢下线时触发 */
+    @Override
+    public void doKickout(String loginType, Object loginId, String tokenValue) {
+        // ...
+    }
+
+    /** 每次被顶下线时触发 */
+    @Override
+    public void doReplaced(String loginType, Object loginId, String tokenValue) {
+        // ...
+    }
+
+    /** 每次被封禁时触发 */
+    @Override
+    public void doDisable(String loginType, Object loginId, 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) {
+        // ...
+    }
+}

部分文件因为文件数量过多而无法显示