Przeglądaj źródła

完成渠道地图三接口

JensionDzero 1 rok temu
rodzic
commit
a95c61dde3
100 zmienionych plików z 6947 dodań i 0 usunięć
  1. 18 0
      .editorconfig
  2. 50 0
      .gitignore
  3. 12 0
      .run/ruoyi-monitor-admin.run.xml
  4. 12 0
      .run/ruoyi-server.run.xml
  5. 12 0
      .run/ruoyi-xxl-job-admin.run.xml
  6. 20 0
      LICENSE
  7. 139 0
      benyun-core/pom.xml
  8. 16 0
      benyun-core/src/main/java/com/benyun/core/BenyunCoreApplication.java
  9. 60 0
      benyun-core/src/main/java/com/benyun/core/config/DataSourceHealthConfig.java
  10. 78 0
      benyun-core/src/main/java/com/benyun/core/config/RedisConfiguration.java
  11. 5 0
      benyun-core/src/main/java/com/benyun/core/constant/ChannelMapContant.java
  12. 12 0
      benyun-core/src/main/java/com/benyun/core/constant/RedisContant.java
  13. 24 0
      benyun-core/src/main/java/com/benyun/core/controller/ChannelAnalyseController.java
  14. 113 0
      benyun-core/src/main/java/com/benyun/core/controller/ChannelMapController.java
  15. 9 0
      benyun-core/src/main/java/com/benyun/core/dao/AddrCategoryDao.java
  16. 10 0
      benyun-core/src/main/java/com/benyun/core/dao/WdInfoDao.java
  17. 20 0
      benyun-core/src/main/java/com/benyun/core/dao/WdTopologicalDao.java
  18. 29 0
      benyun-core/src/main/java/com/benyun/core/entity/AddrCategory.java
  19. 9 0
      benyun-core/src/main/java/com/benyun/core/entity/CityTierBody.java
  20. 13 0
      benyun-core/src/main/java/com/benyun/core/entity/WdCount.java
  21. 16 0
      benyun-core/src/main/java/com/benyun/core/entity/WdCountBody.java
  22. 81 0
      benyun-core/src/main/java/com/benyun/core/entity/WdInfo.java
  23. 44 0
      benyun-core/src/main/java/com/benyun/core/entity/WdTopological.java
  24. 62 0
      benyun-core/src/main/java/com/benyun/core/entity/vo/ChannelMapAceeptVo.java
  25. 36 0
      benyun-core/src/main/java/com/benyun/core/init/BenyunCoreInit.java
  26. 11 0
      benyun-core/src/main/java/com/benyun/core/service/ChannelAnalyseService.java
  27. 15 0
      benyun-core/src/main/java/com/benyun/core/service/ChannelMapService.java
  28. 11 0
      benyun-core/src/main/java/com/benyun/core/service/WdTopologicalService.java
  29. 58 0
      benyun-core/src/main/java/com/benyun/core/service/impl/ChannelAnalyseServiceImpl.java
  30. 209 0
      benyun-core/src/main/java/com/benyun/core/service/impl/ChannelMapServiceImpl.java
  31. 29 0
      benyun-core/src/main/java/com/benyun/core/service/impl/WdTopologicalServiceImpl.java
  32. 84 0
      benyun-core/src/main/java/com/benyun/core/utils/HashUtil.java
  33. 44 0
      benyun-core/src/main/java/com/benyun/core/utils/SpringUtil.java
  34. 57 0
      benyun-core/src/main/java/com/benyun/core/utils/sharding/ConsistenceHashAlgorithm.java
  35. 118 0
      benyun-core/src/main/java/com/benyun/core/utils/sharding/ConsistenceHashUtil.java
  36. 58 0
      benyun-core/src/main/java/com/benyun/core/utils/sharding/InitTableNodesToHashLoop.java
  37. 4 0
      benyun-core/src/main/java/com/benyun/core/valida/ChannelAreaGroup.java
  38. 4 0
      benyun-core/src/main/java/com/benyun/core/valida/ChannelListGroup.java
  39. 4 0
      benyun-core/src/main/java/com/benyun/core/valida/ChannelPointGroup.java
  40. 140 0
      benyun-core/src/main/resources/application.yaml
  41. 13 0
      benyun-core/src/test/java/com/benyun/core/BenyunCoreApplicationTests.java
  42. 22 0
      benyun-core/src/test/java/com/benyun/core/service/ChannelAnalyeServiceImpTest.java
  43. 89 0
      benyun-core/src/test/java/com/benyun/core/service/ChannelMapServiceImplTest.java
  44. 61 0
      benyun-core/src/test/java/com/benyun/core/service/WdTopologicalServiceImplTest.java
  45. 456 0
      pom.xml
  46. 23 0
      ruoyi-admin/Dockerfile
  47. 132 0
      ruoyi-admin/pom.xml
  48. 24 0
      ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
  49. 18 0
      ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java
  50. 134 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java
  51. 168 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java
  52. 88 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java
  53. 74 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java
  54. 90 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java
  55. 137 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java
  56. 119 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
  57. 116 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java
  58. 125 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java
  59. 32 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
  60. 144 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
  61. 127 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
  62. 80 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java
  63. 105 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssConfigController.java
  64. 109 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java
  65. 115 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java
  66. 126 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  67. 40 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java
  68. 228 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
  69. 249 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
  70. 186 0
      ruoyi-admin/src/main/resources/application-dev.yml
  71. 189 0
      ruoyi-admin/src/main/resources/application-prod.yml
  72. 270 0
      ruoyi-admin/src/main/resources/application.yml
  73. 8 0
      ruoyi-admin/src/main/resources/banner.txt
  74. 49 0
      ruoyi-admin/src/main/resources/i18n/messages.properties
  75. 49 0
      ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
  76. 49 0
      ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  77. BIN
      ruoyi-admin/src/main/resources/ip2region.xdb
  78. 129 0
      ruoyi-admin/src/main/resources/logback-plus.xml
  79. 28 0
      ruoyi-admin/src/main/resources/spy.properties
  80. 45 0
      ruoyi-admin/src/test/java/com/ruoyi/test/AssertUnitTest.java
  81. 70 0
      ruoyi-admin/src/test/java/com/ruoyi/test/DemoUnitTest.java
  82. 72 0
      ruoyi-admin/src/test/java/com/ruoyi/test/ParamUnitTest.java
  83. 54 0
      ruoyi-admin/src/test/java/com/ruoyi/test/TagUnitTest.java
  84. 170 0
      ruoyi-common/pom.xml
  85. 24 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/CellMerge.java
  86. 28 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataColumn.java
  87. 18 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataPermission.java
  88. 29 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java
  89. 44 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java
  90. 32 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java
  91. 30 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java
  92. 47 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
  93. 41 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
  94. 29 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
  95. 24 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java
  96. 39 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java
  97. 21 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java
  98. 85 0
      ruoyi-common/src/main/java/com/ruoyi/common/captcha/UnsignedMathGenerator.java
  99. 54 0
      ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
  100. 44 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java

+ 18 - 0
.editorconfig

@@ -0,0 +1,18 @@
+# http://editorconfig.org
+root = true
+
+# 空格替代Tab缩进在各种编辑工具下效果一致
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{json,yml,yaml}]
+indent_size = 2
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false

+ 50 - 0
.gitignore

@@ -0,0 +1,50 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
+/benyun-core/.mvn/
+/benyun-core/.gitignore
+/benyun-core/mvnw
+/benyun-core/mvnw.cmd

+ 12 - 0
.run/ruoyi-monitor-admin.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
+    <deployment type="dockerfile">
+      <settings>
+        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.8.0" />
+        <option name="buildOnly" value="true" />
+        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
+      </settings>
+    </deployment>
+    <method v="2" />
+  </configuration>
+</component>

+ 12 - 0
.run/ruoyi-server.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
+    <deployment type="dockerfile">
+      <settings>
+        <option name="imageTag" value="ruoyi/ruoyi-server:4.8.0" />
+        <option name="buildOnly" value="true" />
+        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
+      </settings>
+    </deployment>
+    <method v="2" />
+  </configuration>
+</component>

+ 12 - 0
.run/ruoyi-xxl-job-admin.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="ruoyi-xxl-job-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
+    <deployment type="dockerfile">
+      <settings>
+        <option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.8.0" />
+        <option name="buildOnly" value="true" />
+        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" />
+      </settings>
+    </deployment>
+    <method v="2" />
+  </configuration>
+</component>

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2019 RuoYi-Vue-Plus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 139 - 0
benyun-core/pom.xml

@@ -0,0 +1,139 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.2</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.benyun.core</groupId>
+    <artifactId>benyun-core</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>benyun-core</name>
+    <description>benyun-core</description>
+    <properties>
+        <java.version>11</java.version>
+        <ruoyi-vue-plus.version>4.8.0</ruoyi-vue-plus.version>
+        <spring-boot.version>2.7.13</spring-boot.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>11</java.version>
+        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
+        <spring-boot.mybatis>2.2.2</spring-boot.mybatis>
+        <springdoc.version>1.6.15</springdoc.version>
+        <poi.version>5.2.3</poi.version>
+        <easyexcel.version>3.3.1</easyexcel.version>
+        <velocity.version>2.3</velocity.version>
+        <satoken.version>1.35.0.RC</satoken.version>
+        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
+        <p6spy.version>3.9.1</p6spy.version>
+        <hutool.version>5.8.18</hutool.version>
+        <okhttp.version>4.10.0</okhttp.version>
+        <spring-boot-admin.version>2.7.10</spring-boot-admin.version>
+        <redisson.version>3.20.1</redisson.version>
+        <lock4j.version>2.2.3</lock4j.version>
+        <dynamic-ds.version>3.5.2</dynamic-ds.version>
+        <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
+        <xxl-job.version>2.4.0</xxl-job.version>
+        <lombok.version>1.18.26</lombok.version>
+        <bouncycastle.version>1.72</bouncycastle.version>
+        <!-- 离线IP地址定位库 -->
+        <ip2region.version>2.7.0</ip2region.version>
+
+        <!-- 临时修复 snakeyaml 漏洞 -->
+        <snakeyaml.version>1.33</snakeyaml.version>
+
+        <!-- OSS 配置 -->
+        <aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
+        <!-- SMS 配置 -->
+        <sms4j.version>2.2.0</sms4j.version>
+    </properties>
+    <dependencies>
+
+        <!--redis坐标-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+
+        <!--mysql驱动-->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.17</version>
+        </dependency>
+
+        <!-- 通用工具-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common</artifactId>
+            <version>${ruoyi-vue-plus.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>1.1.23</version>
+        </dependency>
+
+        <!-- 分页插件 -->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <version>1.4.2</version>
+        </dependency>
+
+        <!--shardingsphere start-->
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
+            <version>4.0.0-RC1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>sharding-jdbc-spring-namespace</artifactId>
+            <version>4.0.0-RC1</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 16 - 0
benyun-core/src/main/java/com/benyun/core/BenyunCoreApplication.java

@@ -0,0 +1,16 @@
+package com.benyun.core;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@MapperScan("com.benyun.core.dao")
+public class BenyunCoreApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(BenyunCoreApplication.class, args);
+    }
+
+}

+ 60 - 0
benyun-core/src/main/java/com/benyun/core/config/DataSourceHealthConfig.java

@@ -0,0 +1,60 @@
+package com.benyun.core.config;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadata;
+import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
+import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+public class DataSourceHealthConfig{
+
+    /**
+     * 解决新版Spring中,集成sharding jdbc 健康检查异常
+     */
+    @Bean
+    DataSourcePoolMetadataProvider dataSourcePoolMetadataProvider() {
+        return dataSource -> dataSource instanceof HikariDataSource
+            ? new HikariDataSourcePoolMetadata((HikariDataSource) dataSource)
+            : new NotAvailableDataSourcePoolMetadata();
+    }
+
+    /**
+     * 不可用的数据源池元数据.
+     */
+    private static class NotAvailableDataSourcePoolMetadata implements DataSourcePoolMetadata {
+        @Override
+        public Float getUsage() {
+            return null;
+        }
+
+        @Override
+        public Integer getActive() {
+            return null;
+        }
+
+        @Override
+        public Integer getMax() {
+            return null;
+        }
+
+        @Override
+        public Integer getMin() {
+            return null;
+        }
+
+        @Override
+        public String getValidationQuery() {
+            return "select 1";
+        }
+
+        @Override
+        public Boolean getDefaultAutoCommit() {
+            return null;
+        }
+    }
+
+}
+

+ 78 - 0
benyun-core/src/main/java/com/benyun/core/config/RedisConfiguration.java

@@ -0,0 +1,78 @@
+package com.benyun.core.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+@Configuration
+@Slf4j
+public class RedisConfiguration {
+    // @Bean 把方法的返回值对象放到IOC容器中
+    @Bean
+    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<Object,Object>  redisTemplate = new RedisTemplate();
+
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        // 设置Redis的key使用字符串序列化器,解决key可读性差的问题。
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+
+        //redis和Springboot直接交互的序列化器
+        redisTemplate.setDefaultSerializer(valueSerializer());
+
+        // 设置Redis的value使用JSON序列化器,解决value可读性差的问题。
+        redisTemplate.setValueSerializer(valueSerializer());
+        redisTemplate.setHashValueSerializer(valueSerializer());
+
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        return redisTemplate;
+    }
+
+    /**
+     * 值采用JSON序列化
+     * @return
+     */
+    private RedisSerializer<Object> valueSerializer() {
+        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
+        ObjectMapper om = new ObjectMapper();
+        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+
+        //LocalDate、LocalDateTime序列化器
+        JavaTimeModule timeModule = new JavaTimeModule();
+        timeModule.addDeserializer(LocalDate.class,
+            new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+        timeModule.addDeserializer(LocalDateTime.class,
+            new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        timeModule.addSerializer(LocalDate.class,
+            new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+        timeModule.addSerializer(LocalDateTime.class,
+            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+
+        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        om.registerModule(timeModule);
+
+        jackson2JsonRedisSerializer.setObjectMapper(om);
+        return jackson2JsonRedisSerializer;
+    }
+
+}

+ 5 - 0
benyun-core/src/main/java/com/benyun/core/constant/ChannelMapContant.java

@@ -0,0 +1,5 @@
+package com.benyun.core.constant;
+
+public class ChannelMapContant {
+    public static String[] FULL_PROVINCE = new String[]{"110000000000", "120000000000", "130000000000", "140000000000", "150000000000", "210000000000", "220000000000", "230000000000", "310000000000", "320000000000", "330000000000", "340000000000", "350000000000", "360000000000", "370000000000", "410000000000", "420000000000", "430000000000", "440000000000", "450000000000", "460000000000", "500000000000", "510000000000", "520000000000", "530000000000", "540000000000", "610000000000", "620000000000", "630000000000", "640000000000", "650000000000"};
+}

+ 12 - 0
benyun-core/src/main/java/com/benyun/core/constant/RedisContant.java

@@ -0,0 +1,12 @@
+package com.benyun.core.constant;
+
+public class RedisContant {
+    public static String CHANNEL_MAP_AREA = "channel_area";  //保存渠道地图area数据的Hash名
+    public static int CHANNEL_MAP_AREA_TIME = 30;  //渠道地图area数据的超时时间
+    public static String CHANNEL_MAP_LIST = "channel_list";  //保存渠道地图list数据的Hash名
+
+    public static int CHANNEL_MAP_LIST_TIME = 30;  //渠道地图list数据的超时时间
+    public static String CHANNEL_MAP_POINT = "channel_point"; //保存渠道地图point数据的Hash名
+
+    public static int CHANNEL_MAP_POINT_TIME = 30;  //渠道地图point数据的超时时间
+}

+ 24 - 0
benyun-core/src/main/java/com/benyun/core/controller/ChannelAnalyseController.java

@@ -0,0 +1,24 @@
+package com.benyun.core.controller;
+
+import com.benyun.core.service.ChannelAnalyseService;
+import com.ruoyi.common.core.domain.R;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+
+@RestController
+@RequestMapping("/channelAnalyse")
+public class ChannelAnalyseController {
+
+    @Autowired
+    ChannelAnalyseService channelAnalyseService;
+
+    //TODO 未加入到redis
+    @RequestMapping("/cityTier")
+    public R cityTier(String searchText){
+        HashMap hashMap = channelAnalyseService.cityTier(searchText);
+        return R.ok(hashMap);
+    }
+}

+ 113 - 0
benyun-core/src/main/java/com/benyun/core/controller/ChannelMapController.java

@@ -0,0 +1,113 @@
+package com.benyun.core.controller;
+
+import com.benyun.core.constant.ChannelMapContant;
+import com.benyun.core.constant.RedisContant;
+import com.benyun.core.entity.WdCount;
+import com.benyun.core.entity.WdInfo;
+import com.benyun.core.entity.vo.ChannelMapAceeptVo;
+import com.benyun.core.service.ChannelMapService;
+import com.benyun.core.utils.HashUtil;
+import com.benyun.core.valida.ChannelAreaGroup;
+import com.benyun.core.valida.ChannelListGroup;
+import com.benyun.core.valida.ChannelPointGroup;
+import com.github.pagehelper.PageInfo;
+import com.ruoyi.common.core.domain.R;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+
+@RestController
+@RequestMapping("/channelMap")
+public class ChannelMapController {
+    @Autowired
+    ChannelMapService channelMapService;
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @RequestMapping("/getArea")
+    public R area(@Validated(value={ChannelAreaGroup.class}) ChannelMapAceeptVo channel) {
+
+        //如果没需要查的省码,则查全国的省份
+        if ((channel.getAddrCode() == null || channel.getAddrCode().length == 0)) {
+            if ("province".equals(channel.getRankType())) {
+                String[] strings = ChannelMapContant.FULL_PROVINCE;
+                channel.setAddrCode(strings);
+            } else
+                return R.fail("请求参数错误");
+        }
+
+        String md5 = channel.getHash();
+
+        //2.查看redis中是否存在有缓存
+        WdCount wdCount = (WdCount) redisTemplate.boundHashOps(RedisContant.CHANNEL_MAP_AREA).get(md5);
+        if (wdCount != null) {
+            return R.ok(wdCount);
+        }
+
+        //3.redis中没有,直接请求
+        WdCount area = channelMapService.area(Arrays.asList(channel.getChannel()), channel.getRankType(), channel.getSearchText(), channel.getAddrCode());
+        if(area == null)
+            return R.fail("请求参数错误");
+
+        //4.保存到redis中
+        redisTemplate.boundHashOps(RedisContant.CHANNEL_MAP_AREA).put(md5,area);
+        redisTemplate.expire(RedisContant.CHANNEL_MAP_AREA,RedisContant.CHANNEL_MAP_AREA_TIME, TimeUnit.MINUTES); //30分钟
+        return R.ok(area);
+    }
+
+    //TODO 这里还需要思考返回来的是什么数据
+    @RequestMapping("/list")
+    public R list(@Validated(value={ChannelListGroup.class}) ChannelMapAceeptVo channel){
+        //如果没需要查的省码,则查全国的省份
+        if ((channel.getAddrCode() == null || channel.getAddrCode().length == 0)) {
+            if ("province".equals(channel.getRankType())) {
+                String[] strings =  ChannelMapContant.FULL_PROVINCE;
+                channel.setAddrCode(strings);
+            } else
+                return R.fail("传值不对");
+        }
+
+        String md5 = channel.getHash();
+        PageInfo<WdInfo> pageInfo = (PageInfo<WdInfo>) redisTemplate.boundHashOps(RedisContant.CHANNEL_MAP_LIST).get(md5);
+        if(pageInfo != null){
+            return R.ok(pageInfo);
+        }
+
+        PageInfo<WdInfo> list = channelMapService.list(channel.getChannel(), channel.getRankType(), channel.getSearchText(), channel.getOrderby(), channel.getAddrCode(), channel.getPageNum(), channel.getPageSize());
+        if(list == null)
+            return R.fail("请求参数错误");
+
+        //4.保存到redis中
+        redisTemplate.boundHashOps(RedisContant.CHANNEL_MAP_LIST).put(md5,list);
+        redisTemplate.expire(RedisContant.CHANNEL_MAP_LIST,RedisContant.CHANNEL_MAP_LIST_TIME, TimeUnit.MINUTES); //30分钟
+        return R.ok(list);
+    }
+
+    @RequestMapping("/point")
+    public R point(@Validated(value={ChannelPointGroup.class}) ChannelMapAceeptVo channel){
+        String md5 = channel.getHash();
+        PageInfo<WdInfo> pageInfo = (PageInfo<WdInfo>) redisTemplate.boundHashOps(RedisContant.CHANNEL_MAP_POINT).get(md5);
+        if(pageInfo != null){
+            return R.ok(pageInfo);
+        }
+
+        PageInfo<WdInfo> point = channelMapService.point(channel.getChannel(), channel.getSearchText(), channel.getAddrCode(), channel.getPageNum(), channel.getPageSize());
+        if(point == null)
+            return R.fail("请求参数错误");
+
+        //4.保存到redis中
+        redisTemplate.boundHashOps(RedisContant.CHANNEL_MAP_POINT).put(md5,point);
+        redisTemplate.expire(RedisContant.CHANNEL_MAP_POINT,RedisContant.CHANNEL_MAP_POINT_TIME, TimeUnit.MINUTES); //30分钟
+        return R.ok(point);
+    }
+
+}

+ 9 - 0
benyun-core/src/main/java/com/benyun/core/dao/AddrCategoryDao.java

@@ -0,0 +1,9 @@
+package com.benyun.core.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.benyun.core.entity.AddrCategory;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AddrCategoryDao extends BaseMapper<AddrCategory> {
+}

+ 10 - 0
benyun-core/src/main/java/com/benyun/core/dao/WdInfoDao.java

@@ -0,0 +1,10 @@
+package com.benyun.core.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.benyun.core.entity.WdInfo;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.context.annotation.Lazy;
+
+@Mapper
+public interface WdInfoDao extends BaseMapper<WdInfo> {
+}

+ 20 - 0
benyun-core/src/main/java/com/benyun/core/dao/WdTopologicalDao.java

@@ -0,0 +1,20 @@
+package com.benyun.core.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.benyun.core.entity.WdTopological;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import javax.security.sasl.SaslServer;
+import java.util.List;
+
+@Mapper
+public interface WdTopologicalDao extends BaseMapper<WdTopological> {
+
+    @Insert("insert into bl_wd_topological values(#{centerWdId},#{centerWdName},#{radius},#{aroundWdId},#{aroundWdName},#{aroundWdTypeCode},#{aroundTypeCodeTx},#{aroundTypeCodeGd},#{aroundTypeCodeBy},#{distance},#{addrCode},#{addrCodeInfo},#{addrInfo},#{lat},#{lng},#{geoHash})")
+    public void insertWdTopological(WdTopological wdTopological);
+
+    @Select("select * from bl_wd_topological where center_wd_id = #{centerWdId}")
+    public List<WdTopological> selectWdTopologicalListById(String centerWdId);
+}

+ 29 - 0
benyun-core/src/main/java/com/benyun/core/entity/AddrCategory.java

@@ -0,0 +1,29 @@
+package com.benyun.core.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@TableName("ddt_addr_category")
+public class AddrCategory {
+    @TableField("addr_code")
+    private String addrCode;
+    @TableField("province")
+    private String province;
+    @TableField("city")
+    private String city;
+    @TableField("district")
+    private String district;
+    @TableField("addr_info")
+    private String addrInfo;
+    @TableField("lat_gd")
+    private BigDecimal latGd;
+    @TableField("lng_gd")
+    private BigDecimal lngGd;
+
+    @TableField("city_tier")
+    private String cityTier;
+}

+ 9 - 0
benyun-core/src/main/java/com/benyun/core/entity/CityTierBody.java

@@ -0,0 +1,9 @@
+package com.benyun.core.entity;
+
+import lombok.Data;
+
+@Data
+public class CityTierBody {
+    private String name;
+    private long count;
+}

+ 13 - 0
benyun-core/src/main/java/com/benyun/core/entity/WdCount.java

@@ -0,0 +1,13 @@
+package com.benyun.core.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class WdCount implements Serializable {
+    List<WdCountBody> wdCountBodyList;
+    long total;
+
+}

+ 16 - 0
benyun-core/src/main/java/com/benyun/core/entity/WdCountBody.java

@@ -0,0 +1,16 @@
+package com.benyun.core.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class WdCountBody implements Serializable {
+    private String name;
+    private BigDecimal lat;
+    private BigDecimal lng;
+    private String addrCode;
+    private Long count;
+    private float radio;
+}

+ 81 - 0
benyun-core/src/main/java/com/benyun/core/entity/WdInfo.java

@@ -0,0 +1,81 @@
+package com.benyun.core.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("ddt_wd_info")
+public class WdInfo {
+    @TableField("wd_id")
+    private String wdId;
+    @TableField("wd_img")
+    private String wdImg;
+    @TableField("wd_name")
+    private String wdName;
+    @TableField("create_date")
+    @JsonSerialize(using = LocalDateSerializer.class)
+    @JsonDeserialize(using = LocalDateDeserializer.class)
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate createDate;
+    @TableField("addr_info")
+    private String addrInfo;
+    @TableField("addr_code")
+    private String addrCode;
+    @TableField("lat")
+    private BigDecimal lat;
+    @TableField("lng")
+    private BigDecimal lng;
+    @TableField("type_code_tx")
+    private String typeCodeTx;
+    @TableField("type_name_tx")
+    private String typeNameTx;
+    @TableField("type_code_gd")
+    private String typeCodeGd;
+    @TableField("type_name_gd")
+    private String typeNameGd;
+    @TableField("type_code_by")
+    private String typeCodeBy;
+    @TableField("type_name_by")
+    private String typeNameBy;
+    @TableField("rent")
+    private Double rent;
+    @TableField("audit")
+    private int audit;
+    @TableField("wd_type_code")
+    private String wdTypeCode;
+    @TableField("enter_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonSerialize(using = LocalDateTimeSerializer.class)
+    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
+    private LocalDateTime enterTime;
+    @TableField("update_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonSerialize(using = LocalDateTimeSerializer.class)
+    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
+    private LocalDateTime updateTime;
+    @TableField("collect_user_id")
+    private String collectUserId;
+    @TableField("collect_person")
+    private String collectPerson;
+    @TableField("collect_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonSerialize(using = LocalDateTimeSerializer.class)
+    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
+    private LocalDateTime collectTime;
+    @TableField("hash")
+    private String hash;
+    @TableField("geo_hash")
+    private String geoHash;
+}

+ 44 - 0
benyun-core/src/main/java/com/benyun/core/entity/WdTopological.java

@@ -0,0 +1,44 @@
+package com.benyun.core.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@TableName("bl_wd_topological")
+public class WdTopological {
+    @TableField("center_wd_id")
+    public String centerWdId;
+    @TableField("center_wd_name")
+    public String centerWdName;
+    @TableField("radius")
+    public int radius;
+    @TableField("around_wd_id")
+    public String aroundWdId;
+    @TableField("around_wd_name")
+    public String aroundWdName;
+    @TableField("around_wd_type_code")
+    public String aroundWdTypeCode;
+    @TableField("around_type_code_tx")
+    public String aroundTypeCodeTx;
+    @TableField("around_type_code_gd")
+    public String aroundTypeCodeGd;
+    @TableField("around_type_code_by")
+    public String aroundTypeCodeBy;
+    @TableField("distance")
+    public int distance;
+    @TableField("addr_code")
+    public String addrCode;
+    @TableField("addr_code_info")
+    public String addrCodeInfo;
+    @TableField("addr_info")
+    public String addrInfo;
+    @TableField("lat")
+    public BigDecimal lat;
+    @TableField("lng")
+    public BigDecimal lng;
+    @TableField("geo_hash")
+    public String geoHash;
+}

+ 62 - 0
benyun-core/src/main/java/com/benyun/core/entity/vo/ChannelMapAceeptVo.java

@@ -0,0 +1,62 @@
+package com.benyun.core.entity.vo;
+
+import com.benyun.core.utils.HashUtil;
+import com.benyun.core.valida.ChannelAreaGroup;
+import com.benyun.core.valida.ChannelListGroup;
+import com.benyun.core.valida.ChannelPointGroup;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+
+@Data
+public class ChannelMapAceeptVo {
+    @NotEmpty(groups = {ChannelPointGroup.class, ChannelAreaGroup.class, ChannelListGroup.class})
+    String[] channel;  //网点类型,渠道(0:基础设施网点、1:门店网点、2:小区网点、3:楼宇网点、4:交通设施网点、5:公司网点)
+    @NotBlank(groups = { ChannelAreaGroup.class, ChannelListGroup.class},message = "rankType不能为空")
+    String rankType; //搜索等级(province:搜索省、city:搜素市、zone:搜素区)
+
+    String searchText; //搜索关键字
+
+    /*
+    * 地区编码code
+    * 当 rankType = province,city 需要提交省区码编码
+    * 当 rankType = zone 需要提交市区码编码
+    * */
+    @NotEmpty(groups = {ChannelPointGroup.class})
+    String[] addrCode;
+
+    String orderby = "collect_time"; //排序字段,(list接口)
+
+    /*
+    * 分页字段(list、point接口)
+    * */
+    int pageNum = 1;
+    int pageSize = 10;
+
+    public String getHash(){
+        StringBuilder stringBuilder = new StringBuilder();
+
+        //搜索渠道
+        for (String s : this.channel) {
+            stringBuilder.append(s);
+        }
+
+        //搜索等级
+        stringBuilder.append(this.rankType);
+
+        //搜索文本
+        stringBuilder.append(this.searchText);
+
+        //搜索地区
+        for (String s : this.addrCode) {
+            stringBuilder.append(s);
+        }
+
+        //生成Hash
+        String s = stringBuilder.toString();
+        String md5 = HashUtil.hash(s, "MD5");
+
+        return md5;
+    }
+}

+ 36 - 0
benyun-core/src/main/java/com/benyun/core/init/BenyunCoreInit.java

@@ -0,0 +1,36 @@
+package com.benyun.core.init;
+
+import com.benyun.core.dao.AddrCategoryDao;
+import com.benyun.core.entity.AddrCategory;
+import com.benyun.core.utils.HashUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+
+@Configuration
+public class BenyunCoreInit {
+    @Autowired
+    AddrCategoryDao addrCategoryDao;
+
+    @Bean("cityTierMap")
+    public HashMap getCityTierMap(){
+        HashMap<String, String> hashMap = new HashMap<>();
+        for (AddrCategory addrCategory : addrCategoryDao.selectList(null)) {
+            if(addrCategory.getCityTier() != null && !addrCategory.getCityTier().equals("")){
+                if(addrCategory.getDistrict()!=null && !addrCategory.getDistrict().equals("")){
+                    hashMap.put(addrCategory.getAddrCode(), addrCategory.getCityTier());
+                }else if(addrCategory.getCity()!=null && !addrCategory.getCity().equals("")){
+                    hashMap.put(addrCategory.getAddrCode().substring(0,4), addrCategory.getCityTier());
+                }else
+                    hashMap.put(addrCategory.getAddrCode().substring(0,2), addrCategory.getCityTier());
+            }
+        }
+//        for (String s : hashMap.keySet()) {
+//            System.out.println(s + " " +hashMap.get(s));
+//        }
+
+        return hashMap;
+    }
+}

+ 11 - 0
benyun-core/src/main/java/com/benyun/core/service/ChannelAnalyseService.java

@@ -0,0 +1,11 @@
+package com.benyun.core.service;
+
+import com.benyun.core.entity.CityTierBody;
+import com.ruoyi.common.core.domain.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+public interface ChannelAnalyseService {
+    public HashMap cityTier(String searchText);
+}

+ 15 - 0
benyun-core/src/main/java/com/benyun/core/service/ChannelMapService.java

@@ -0,0 +1,15 @@
+package com.benyun.core.service;
+
+import com.benyun.core.entity.WdCount;
+import com.benyun.core.entity.WdCountBody;
+import com.benyun.core.entity.WdInfo;
+import com.github.pagehelper.PageInfo;
+
+import java.util.List;
+
+public interface ChannelMapService {
+    public WdCount area(List<String> channel, String rankType, String searchText, String[] addrCode);
+    public PageInfo<WdInfo> list(String[] channel, String rankType, String searchText, String orderby, String[] addrCode, int pageNum, int pageSize);
+
+    public PageInfo<WdInfo> point(String[] channel, String searchText, String[] addrCode, int pageNum, int pageSize);
+}

+ 11 - 0
benyun-core/src/main/java/com/benyun/core/service/WdTopologicalService.java

@@ -0,0 +1,11 @@
+package com.benyun.core.service;
+
+import com.benyun.core.entity.WdTopological;
+
+import java.util.List;
+
+public interface WdTopologicalService {
+    public void insertList(List<WdTopological> list);
+
+    public List<WdTopological> selectWdTopologicalListById(String centerWdId);
+}

+ 58 - 0
benyun-core/src/main/java/com/benyun/core/service/impl/ChannelAnalyseServiceImpl.java

@@ -0,0 +1,58 @@
+package com.benyun.core.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.benyun.core.dao.WdInfoDao;
+import com.benyun.core.entity.CityTierBody;
+import com.benyun.core.entity.WdInfo;
+import com.benyun.core.service.ChannelAnalyseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+
+@Service
+public class ChannelAnalyseServiceImpl implements ChannelAnalyseService {
+
+    @Autowired
+    WdInfoDao wdInfoDao;
+
+    @Autowired
+    @Qualifier("cityTierMap")
+    HashMap<String,String> cityTierMap;
+
+    @Override
+    public HashMap cityTier(String searchText) {
+        HashMap<String,Long> result = new HashMap<>();
+        result.put("一线",0L);
+        result.put("新一线",0L);
+        result.put("二线",0L);
+        result.put("三线",0L);
+        result.put("四线",0L);
+        result.put("五线",0L);
+        result.put("其他",0L);
+
+        //1.获取所有符合条件的网点信息
+        QueryWrapper<WdInfo> queryWrapper = new QueryWrapper<>();
+        if(searchText != null && !searchText.equals(""))
+            queryWrapper.like("wd_name",searchText);
+        List<WdInfo> wdInfos = wdInfoDao.selectList(queryWrapper);
+        System.out.println(wdInfos.size());
+        for (WdInfo wdInfo : wdInfos) {
+            String addrCode = wdInfo.getAddrCode();
+            if(cityTierMap.containsKey(addrCode)){
+                result.put(cityTierMap.get(addrCode),result.get(cityTierMap.get(addrCode))+1);
+            }else if(cityTierMap.containsKey(addrCode.substring(0,4))){
+                result.put(cityTierMap.get(addrCode.substring(0,4)),result.get(cityTierMap.get(addrCode.substring(0,4)))+1);
+            }else if(cityTierMap.containsKey(addrCode.substring(0,2))){
+                result.put(cityTierMap.get(addrCode.substring(0,2)),result.get(cityTierMap.get(addrCode.substring(0,2)))+1);
+            }
+            else
+                result.put("其他",result.get("其他")+1);
+        }
+
+
+        return result;
+    }
+}

+ 209 - 0
benyun-core/src/main/java/com/benyun/core/service/impl/ChannelMapServiceImpl.java

@@ -0,0 +1,209 @@
+package com.benyun.core.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.benyun.core.dao.AddrCategoryDao;
+import com.benyun.core.dao.WdInfoDao;
+import com.benyun.core.entity.AddrCategory;
+import com.benyun.core.entity.WdCount;
+import com.benyun.core.entity.WdCountBody;
+import com.benyun.core.entity.WdInfo;
+import com.benyun.core.service.ChannelMapService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@Service
+public class ChannelMapServiceImpl implements ChannelMapService {
+
+    @Autowired
+    private AddrCategoryDao addrCategoryDao;
+
+    @Autowired
+    private WdInfoDao originWdInfoDao;
+
+    @Override
+    public WdCount area(List<String> channel, String rankType, String searchText, String[] addrCode) {
+        long start = System.currentTimeMillis();
+        List<String> addrCodeList = new ArrayList<>();
+
+        //1.根据不同级别得到地区码
+        if ("province".equals(rankType)) {
+            //省码
+            Collections.addAll(addrCodeList, addrCode);
+        } else if ("city".equals(rankType)) {
+            //省的所有市
+            for (String s : addrCode) {
+                String substring = s.substring(0, 2);
+                addrCodeList.add(substring);
+            }
+        } else if ("zone".equals(rankType)) {
+            //市的所有区
+            for (String s : addrCode) {
+                String substring = s.substring(0, 4);
+                addrCodeList.add(substring);
+            }
+        } else {
+            return null;
+        }
+
+        //2.找到相关地区信息
+        QueryWrapper<AddrCategory> queryWrapper = new QueryWrapper<>();
+        if ("province".equals(rankType)) {
+            queryWrapper.eq("city", "");
+        } else if ("city".equals(rankType)) {
+            queryWrapper.eq("district", "").and(addrCategoryQueryWrapper -> {
+                addrCategoryQueryWrapper.ne("city", "");
+            });
+        } else if ("zone".equals(rankType)) {
+            queryWrapper.ne("district", "");
+        }
+        queryWrapper.and(addrCategoryQueryWrapper -> {
+            addrCategoryQueryWrapper.or(addrCategoryQueryWrapper1 -> {
+                for (String s : addrCodeList) {
+                    addrCategoryQueryWrapper1.likeRight("addr_code", s).or();
+                }
+            });
+        });
+
+        //TODO 前置分类删选还没做
+
+
+        List<AddrCategory> addrCategories = addrCategoryDao.selectList(queryWrapper);
+
+
+        //3.组装地区信息
+        List<WdCountBody> result = new ArrayList<>();
+        for (AddrCategory addrCategory : addrCategories) {
+            WdCountBody wdCountBody = new WdCountBody();
+
+            //组装
+            wdCountBody.setLat(addrCategory.getLatGd());
+            wdCountBody.setLng(addrCategory.getLngGd());
+            wdCountBody.setAddrCode(addrCategory.getAddrCode());
+            if ("province".equals(rankType)) {
+                wdCountBody.setName(addrCategory.getProvince());
+            } else if ("city".equals(rankType)) {
+                wdCountBody.setName(addrCategory.getCity());
+            } else if ("zone".equals(rankType)) {
+                wdCountBody.setName(addrCategory.getDistrict());
+            }
+            result.add(wdCountBody);
+        }
+
+        //4.统计地区网点信息
+        Long total = 0L;
+        for (WdCountBody wdCountBody : result) {
+            QueryWrapper<WdInfo> queryWrapper1 = new QueryWrapper<>();
+            String code = wdCountBody.getAddrCode();
+
+            //根据地区前缀,使用模糊查询
+            if ("province".equals(rankType))
+                code = code.substring(0, 2);
+            else if ("city".equals(rankType))
+                code = code.substring(0, 4);
+
+            queryWrapper1.likeRight("addr_code", code).and(originWdInfoQueryWrapper -> {
+                originWdInfoQueryWrapper.in("wd_type_code", channel);
+            });
+            if (searchText != null && !searchText.trim().equals("")) {
+                queryWrapper1.and(originWdInfoQueryWrapper -> {
+                    originWdInfoQueryWrapper.like("wd_name", searchText);
+                });
+            }
+            Long aLong = originWdInfoDao.selectCount(queryWrapper1);
+            wdCountBody.setCount(aLong);
+            total += aLong;
+        }
+
+        //5.计算radio
+        for (WdCountBody wdCountBody : result) {
+            if (total != 0)
+                wdCountBody.setRadio((float) wdCountBody.getCount() / total);
+            else
+                wdCountBody.setRadio(0.0f);
+        }
+        long end = System.currentTimeMillis();
+        System.out.println("运行时间:" + (end - start));
+
+        WdCount wdCount = new WdCount();
+        wdCount.setWdCountBodyList(result);
+        wdCount.setTotal(total);
+        return wdCount;
+    }
+
+    @Override
+    public PageInfo<WdInfo> list(String[] channel, String rankType, String searchText, String orderby, String[] addrCode, int pageNum, int pageSize) {
+        //1.根据不同级别得到地区码
+        List<String> addrCodeList = new ArrayList<>();
+        if ("province".equals(rankType)) {
+            //省码
+            for (String s : addrCode) {
+                String substring = s.substring(0, 2);
+                addrCodeList.add(substring);
+            }
+        } else if ("city".equals(rankType)) {
+            //省的所有市
+            for (String s : addrCode) {
+                String substring = s.substring(0, 4);
+                addrCodeList.add(substring);
+            }
+        } else if ("zone".equals(rankType)) {
+            //区
+            for (String s : addrCode) {
+                String substring = s.substring(0, 6);
+                addrCodeList.add(substring);
+            }
+        } else {
+            return null;
+        }
+
+
+        //2.根据地区前缀找到所有的网点
+        QueryWrapper<WdInfo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.in("wd_type_code", channel).and(originWdInfoQueryWrapper -> {
+            for (String s : addrCodeList) {
+                originWdInfoQueryWrapper.likeRight("addr_code", s).or();
+            }
+        });
+
+        if (searchText != null && !searchText.trim().equals("")) {
+            queryWrapper.and(originWdInfoQueryWrapper -> {
+                originWdInfoQueryWrapper.like("wd_name", searchText);
+            });
+        }
+        if (orderby != null && !orderby.trim().equals("")) {
+            queryWrapper.orderByDesc(orderby);
+        }
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<WdInfo> originWdInfos = originWdInfoDao.selectList(queryWrapper);
+        PageInfo<WdInfo> PageInfo = new PageInfo<>(originWdInfos);
+        return PageInfo;
+    }
+
+    @Override
+    public PageInfo<WdInfo> point(String[] channel, String searchText, String[] addrCode, int pageNum, int pageSize) {
+        QueryWrapper<WdInfo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.in("wd_type_code", channel);
+        for (String s : addrCode) {
+            queryWrapper.and(originWdInfoQueryWrapper -> {
+                originWdInfoQueryWrapper.eq("addr_code",s);
+            });
+        }
+        if (searchText != null && !searchText.trim().equals("")) {
+            queryWrapper.and(originWdInfoQueryWrapper -> {
+                originWdInfoQueryWrapper.like("wd_name", searchText);
+            });
+        }
+        PageHelper.startPage(pageNum, pageSize);
+        List<WdInfo> originWdInfos = originWdInfoDao.selectList(queryWrapper);
+        PageInfo<WdInfo> PageInfo = new PageInfo<>(originWdInfos);
+        return PageInfo;
+    }
+}

+ 29 - 0
benyun-core/src/main/java/com/benyun/core/service/impl/WdTopologicalServiceImpl.java

@@ -0,0 +1,29 @@
+package com.benyun.core.service.impl;
+
+import com.benyun.core.dao.WdTopologicalDao;
+import com.benyun.core.entity.WdTopological;
+import com.benyun.core.service.WdTopologicalService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+public class WdTopologicalServiceImpl implements WdTopologicalService {
+    @Autowired
+    WdTopologicalDao wdTopologicalDao;
+
+    @Override
+    public void insertList(List<WdTopological> list) {
+        for (WdTopological wdTopological : list) {
+            wdTopologicalDao.insertWdTopological(wdTopological);
+        }
+    }
+
+    @Override
+    public List<WdTopological> selectWdTopologicalListById(String centerWdId) {
+        return wdTopologicalDao.selectWdTopologicalListById(centerWdId);
+    }
+}

+ 84 - 0
benyun-core/src/main/java/com/benyun/core/utils/HashUtil.java

@@ -0,0 +1,84 @@
+package com.benyun.core.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class HashUtil {
+    /**
+     * 计算字符串的hash值
+     * @param string    明文
+     * @param algorithm 算法名
+     * @return          字符串的hash值
+     */
+    public static String hash(String string, String algorithm) {
+        if (string.isEmpty()) {
+            return "";
+        }
+        MessageDigest hash = null;
+        try {
+            hash = MessageDigest.getInstance(algorithm);
+            byte[] bytes = hash.digest(string.getBytes("UTF-8"));
+            String result = "";
+            for (byte b : bytes) {
+                String temp = Integer.toHexString(b & 0xff);
+                if (temp.length() == 1) {
+                    temp = "0" + temp;
+                }
+                result += temp;
+            }
+            return result;
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * 计算文件的hash值
+     * @param file      文件File
+     * @param algorithm 算法名
+     * @return          文件的hash值
+     */
+    public static String hash(File file, String algorithm) {
+        if (file == null || !file.isFile() || !file.exists()) {
+            return "";
+        }
+        FileInputStream in = null;
+        String result = "";
+        byte buffer[] = new byte[0124];
+        int len;
+        try {
+            MessageDigest hash = MessageDigest.getInstance(algorithm);
+            in = new FileInputStream(file);
+            while ((len = in.read(buffer)) != -1) {
+                hash.update(buffer, 0, len);
+            }
+            byte[] bytes = hash.digest();
+
+            for (byte b : bytes) {
+                String temp = Integer.toHexString(b & 0xff);
+                if (temp.length() == 1) {
+                    temp = "0" + temp;
+                }
+                result += temp;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }finally {
+            if(null!=in){
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return result;
+    }
+}

+ 44 - 0
benyun-core/src/main/java/com/benyun/core/utils/SpringUtil.java

@@ -0,0 +1,44 @@
+package com.benyun.core.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringUtil implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        if(SpringUtil.applicationContext == null) {
+            SpringUtil.applicationContext = applicationContext;
+        }
+
+        System.out.println("========ApplicationContext配置成功,在普通类可以通过调用SpringUtils.getAppContext()获取applicationContext对象,applicationContext="+SpringUtil.applicationContext+"========");
+
+        System.out.println("---------------------------------------------------------------------");
+    }
+
+    //获取applicationContext
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    //通过name获取 Bean.
+    public static Object getBean(String name){
+        return getApplicationContext().getBean(name);
+    }
+
+    //通过class获取Bean.
+    public static <T> T getBean(Class<T> clazz){
+        return getApplicationContext().getBean(clazz);
+    }
+
+    //通过name,以及Clazz返回指定的Bean
+    public static <T> T getBean(String name,Class<T> clazz){
+        return getApplicationContext().getBean(name, clazz);
+    }
+
+}

+ 57 - 0
benyun-core/src/main/java/com/benyun/core/utils/sharding/ConsistenceHashAlgorithm.java

@@ -0,0 +1,57 @@
+package com.benyun.core.utils.sharding;
+
+import com.benyun.core.utils.SpringUtil;
+import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
+import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
+import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
+import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.SortedMap;
+
+@Component
+public class ConsistenceHashAlgorithm implements RangeShardingAlgorithm<Long>, PreciseShardingAlgorithm<Long> {
+
+
+    /**
+     * 范围查找时需要用到改分片算法,这里暂不完善了
+     * @param collection
+     * @param rangeShardingValue
+     * @return
+     */
+    @Override
+    public Collection<String> doSharding(Collection collection, RangeShardingValue rangeShardingValue) {
+        System.out.println(collection);
+        System.out.println(rangeShardingValue);
+        return collection;
+    }
+
+    /**
+     * @param collection collection 配置文件中解析到的所有分片节点
+     * @param preciseShardingValue 解析到的sql值
+     * @return
+     */
+    @Override
+    public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) {
+        InitTableNodesToHashLoop initTableNodesToHashLoop = SpringUtil.getBean(InitTableNodesToHashLoop.class);
+        if (CollectionUtils.isEmpty(collection)) {
+            return preciseShardingValue.getLogicTableName();
+        }
+
+        //这里主要为了兼容当联表查询时,如果两个表非关联表则
+        //当对副表分表时shardingValue这里传递进来的依然是主表的名称,
+        //但availableTargetNames中确是副表名称,所有这里要从availableTargetNames中匹配真实表
+        ArrayList<String> availableTargetNameList = new ArrayList<>(collection);
+        String logicTableName = availableTargetNameList.get(0).replaceAll("[^(a-zA-Z_)]", "");
+        SortedMap<Long, String> tableHashNode =
+            initTableNodesToHashLoop .getTableVirtualNodes().get(logicTableName);
+
+        ConsistenceHashUtil consistentHashAlgorithm = new ConsistenceHashUtil(tableHashNode,
+            collection);
+
+        return consistentHashAlgorithm.getTableNode(String.valueOf(preciseShardingValue.getValue()));
+    }
+}

+ 118 - 0
benyun-core/src/main/java/com/benyun/core/utils/sharding/ConsistenceHashUtil.java

@@ -0,0 +1,118 @@
+package com.benyun.core.utils.sharding;
+
+import lombok.Getter;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * 一致性哈希算法工具类
+ * @author manji
+ * @Date 2023/5/4
+ */
+public class ConsistenceHashUtil {
+
+    //存储所有节点,按照hash值排序的
+    @Getter
+    private SortedMap<Long, String> virtualNodes = new TreeMap<>();
+
+    // 设置虚拟节点的个数
+    private static final int VIRTUAL_NODES = 3;
+
+
+    public ConsistenceHashUtil() {
+    }
+
+
+    public ConsistenceHashUtil(SortedMap<Long, String> virtualTableNodes, Collection<String> tableNodes) {
+        if (Objects.isNull(virtualTableNodes)) {
+            virtualTableNodes = initNodesToHashLoop(tableNodes);
+        }
+
+        this.virtualNodes = virtualTableNodes;
+    }
+
+    public SortedMap<Long, String> initNodesToHashLoop(Collection<String> tableNodes) {
+        SortedMap<Long, String> virtualTableNodes = new TreeMap<>();
+        for (String node : tableNodes) {
+            for (int i = 0; i < VIRTUAL_NODES; i++) {
+                String s = String.valueOf(i);
+                String virtualNodeName = node + "-manji" + s;
+                long hash = getHash(virtualNodeName);
+
+                virtualTableNodes.put(hash, virtualNodeName);
+            }
+        }
+
+        return virtualTableNodes;
+    }
+
+    /**
+     * 通过计算key的hash
+     * 计算映射的表节点
+     *
+     * @param key
+     * @return
+     */
+    public String getTableNode(String key) {
+        String virtualNode = getVirtualTableNode(key);
+        //虚拟节点名称截取后获取真实节点
+        if (!StringUtils.isEmpty(virtualNode)) {
+            return virtualNode.substring(0, virtualNode.indexOf("-"));
+        }
+        return null;
+    }
+
+    /**
+     * 获取虚拟节点
+     * @param key
+     * @return
+     */
+    public String getVirtualTableNode(String key) {
+        long hash = getHash(key);
+        // 得到大于该Hash值的所有Map
+        SortedMap<Long, String> subMap = virtualNodes.tailMap(hash);
+        String virtualNode;
+        if (subMap.isEmpty()) {
+            //如果没有比该key的hash值大的,则从第一个node开始
+            Long i = virtualNodes.firstKey();
+            //返回对应的服务器
+            virtualNode = virtualNodes.get(i);
+        } else {
+            //第一个Key就是顺时针过去离node最近的那个结点
+            Long i = subMap.firstKey();
+            //返回对应的服务器
+            virtualNode = subMap.get(i);
+        }
+
+        return virtualNode;
+    }
+
+    /**
+     * 使用FNV1_32_HASH算法计算key的Hash值
+     * 也可以使用 MurmurHash3 或者别的加密方式
+     * @param key
+     * @return
+     */
+    public long getHash(String key) {
+//        return MurmurHash3.murmurhash3_x86_32(key);
+        final int p = 16777619;
+        int hash = (int) 2166136261L;
+        for (int i = 0; i < key.length(); i++)
+            hash = (hash ^ key.charAt(i)) * p;
+        hash += hash << 13;
+        hash ^= hash >> 7;
+        hash += hash << 3;
+        hash ^= hash >> 17;
+        hash += hash << 5;
+
+        // 如果算出来的值为负数则取其绝对值
+        if (hash < 0)
+            hash = Math.abs(hash);
+        return hash;
+    }
+
+}

+ 58 - 0
benyun-core/src/main/java/com/benyun/core/utils/sharding/InitTableNodesToHashLoop.java

@@ -0,0 +1,58 @@
+package com.benyun.core.utils.sharding;
+
+import com.benyun.core.utils.sharding.ConsistenceHashUtil;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.core.rule.ShardingRule;
+import org.apache.shardingsphere.core.rule.TableRule;
+import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.ShardingDataSource;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.SortedMap;
+import java.util.stream.Collectors;
+
+/**
+ * 初始化hash环
+ * @author manji
+ * @Date 2023/5/4
+ */
+@Slf4j
+@Component
+@Lazy
+public class InitTableNodesToHashLoop {
+
+    @Resource
+    private ShardingDataSource shardingDataSource;
+
+    @Getter
+    private HashMap<String, SortedMap<Long, String>> tableVirtualNodes = new HashMap<>();
+
+    @PostConstruct
+    public void init() {
+        try {
+            ShardingRule rule = shardingDataSource.getShardingContext().getShardingRule();
+            Collection<TableRule> tableRules = rule.getTableRules();
+            ConsistenceHashUtil consistenceHashUtil = new ConsistenceHashUtil();
+            for (TableRule tableRule : tableRules) {
+                String logicTable = tableRule.getLogicTable();
+
+                tableVirtualNodes.put(logicTable,
+                    consistenceHashUtil.initNodesToHashLoop(
+                        tableRule.getActualDataNodes()
+                            .stream()
+                            .map(dataNode -> {
+                                return dataNode.getTableName();
+                            })
+                            .collect(Collectors.toList()))
+                );
+            }
+        } catch (Exception e) {
+            log.error("分表节点初始化失败 {}", e);
+        }
+    }
+}

+ 4 - 0
benyun-core/src/main/java/com/benyun/core/valida/ChannelAreaGroup.java

@@ -0,0 +1,4 @@
+package com.benyun.core.valida;
+
+public interface ChannelAreaGroup {
+}

+ 4 - 0
benyun-core/src/main/java/com/benyun/core/valida/ChannelListGroup.java

@@ -0,0 +1,4 @@
+package com.benyun.core.valida;
+
+public interface ChannelListGroup {
+}

+ 4 - 0
benyun-core/src/main/java/com/benyun/core/valida/ChannelPointGroup.java

@@ -0,0 +1,4 @@
+package com.benyun.core.valida;
+
+public interface ChannelPointGroup {
+}

+ 140 - 0
benyun-core/src/main/resources/application.yaml

@@ -0,0 +1,140 @@
+server:
+  port: 8000
+  servlet:
+    context-path: /bailianAi
+--- # 数据源配置
+#spring:
+#  shardingsphere:
+#    datasource:
+#      names: ds1
+#      ds1:
+#        type: com.alibaba.druid.pool.DruidDataSource
+#        driver-class-name: com.mysql.cj.jdbc.Driver
+#        url: jdbc:mysql://localhost:3306/ddt?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+#        username: root
+#        password: Aa@1230
+#    sharding:
+#      tables:
+#        bl_wd_topological:
+#          actual-data-nodes: ds1.bl_wd_topological_$->{1..2}
+#          key-generator:
+#            column: center_wd_id
+#            type: SNOWFLAKE
+#          table-strategy:
+#            standard:
+#              sharding-column: center_wd_id
+#              precise-algorithm-class-name: com.benyun.core.utils.sharding.ConsistenceHashAlgorithm
+spring:
+  main:
+    allow-bean-definition-overriding: true
+
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+    dynamic:
+      # 性能分析插件(有性能损耗 不建议生产环境使用)
+      p6spy: false
+      # 设置默认的数据源或者数据源组,默认值即为 master
+      primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
+      datasource:
+        # 主库数据源
+        master:
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
+          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
+          url: jdbc:mysql://localhost:3306/bailian-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username: root
+          password: Aa@1230
+        # 从库数据源
+        slave:
+          lazy: true
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://localhost:3306/bailian-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username: root
+          password: Aa@1230
+      #        oracle:
+      #          type: ${spring.datasource.type}
+      #          driverClassName: oracle.jdbc.OracleDriver
+      #          url: jdbc:oracle:thin:@//localhost:1521/XE
+      #          username: ROOT
+      #          password: root
+      #          hikari:
+      #            connectionTestQuery: SELECT 1 FROM DUAL
+      #        postgres:
+      #          type: ${spring.datasource.type}
+      #          driverClassName: org.postgresql.Driver
+      #          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+      #          username: root
+      #          password: root
+      #        sqlserver:
+      #          type: ${spring.datasource.type}
+      #          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+      #          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+      #          username: SA
+      #          password: root
+      hikari:
+        # 最大连接池数量
+        maxPoolSize: 20
+        # 最小空闲线程数量
+        minIdle: 10
+        # 配置获取连接等待超时的时间
+        connectionTimeout: 30000
+        # 校验超时时间
+        validationTimeout: 5000
+        # 空闲连接存活最大时间,默认10分钟
+        idleTimeout: 600000
+        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+        maxLifetime: 1800000
+        # 连接测试query(配置检测连接是否有效)
+        connectionTestQuery: SELECT 1
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
+  shardingsphere:
+    datasource:
+      names: ds1
+      ds1:
+        type: com.zaxxer.hikari.HikariDataSource
+        driver-class-name: com.mysql.cj.jdbc.Driver
+        jdbcUrl: jdbc:mysql://localhost:3306/bailian-ai?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+        username: root
+        password: Aa@1230
+    sharding:
+      tables:
+        bl_wd_topological:
+          actual-data-nodes: ds1.bl_wd_topological_$->{1..2}
+          key-generator:
+            column: center_wd_id
+            type: SNOWFLAKE
+          table-strategy:
+            standard:
+              sharding-column: center_wd_id
+              precise-algorithm-class-name: com.benyun.core.utils.sharding.ConsistenceHashAlgorithm
+  redis: #redis配置
+    host: 127.0.0.1  #redis服务地址
+    port: 6379       #redis连接端口
+    timeout: 5000ms  #连接超时时间
+    # password:  密码
+    pool:
+      minIdle: 1   #连接池中的最小空闲连接
+      maxIdle: 10  #连接池中的最大空闲连接
+      maxWait: 3   #连接池最大阻塞等待时间(使用负值表示没有限制)
+      maxActive: 8 #连接池最大连接数(使用赋值表示没有限制)
+
+#mybatis-plus配置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/**/*.xml  #告诉mybatis数据库映射文件mapper.xml的位置
+#  configuration:
+#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #显示sql日志
+  global-config:
+    db-config:
+      id-type: auto
+
+pagehelper:
+  helper-dialect: mysql
+  reasonable: true
+  support-methods-arguments: true
+  params: count=countSql

+ 13 - 0
benyun-core/src/test/java/com/benyun/core/BenyunCoreApplicationTests.java

@@ -0,0 +1,13 @@
+package com.benyun.core;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class BenyunCoreApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}

+ 22 - 0
benyun-core/src/test/java/com/benyun/core/service/ChannelAnalyeServiceImpTest.java

@@ -0,0 +1,22 @@
+package com.benyun.core.service;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.HashMap;
+
+@SpringBootTest
+public class ChannelAnalyeServiceImpTest {
+    @Autowired
+    ChannelAnalyseService channelAnalyseService;
+
+    @Test
+    public void cityTierTest(){
+        String searchText = "蜜雪";
+        HashMap<String,Long> hashMap = channelAnalyseService.cityTier(searchText);
+        for (String o : hashMap.keySet()) {
+            System.out.println(o+" "+hashMap.get(o));
+        }
+    }
+}

+ 89 - 0
benyun-core/src/test/java/com/benyun/core/service/ChannelMapServiceImplTest.java

@@ -0,0 +1,89 @@
+package com.benyun.core.service;
+
+import com.benyun.core.entity.WdCount;
+import com.benyun.core.entity.WdCountBody;
+import com.benyun.core.entity.WdInfo;
+import com.benyun.core.utils.HashUtil;
+import com.github.pagehelper.PageInfo;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SpringBootTest
+public class ChannelMapServiceImplTest {
+    @Autowired
+    ChannelMapService channelMapService;
+
+    @Test
+    public void areaProvinceTest() {
+        List<String> channel = Arrays.asList("1", "2", "3", "4", "5", "0");
+        String rankType = "province";
+        String searchText = "";
+        String[] addrCode = new String[]{"110000000000", "120000000000", "130000000000", "140000000000", "150000000000", "210000000000", "220000000000", "230000000000", "310000000000", "320000000000", "330000000000", "340000000000", "350000000000", "360000000000", "370000000000", "410000000000", "420000000000", "430000000000", "440000000000", "450000000000", "460000000000", "500000000000", "510000000000", "520000000000", "530000000000", "540000000000", "610000000000", "620000000000", "630000000000", "640000000000", "650000000000"};
+        WdCount area = channelMapService.area(channel, rankType, searchText, addrCode);
+        for (WdCountBody wdCountBody : area.getWdCountBodyList()) {
+            System.out.println(wdCountBody);
+        }
+        System.out.println(area.getTotal());
+    }
+
+    //市
+    @Test
+    public void areaCityest() {
+        List<String> channel = Arrays.asList("1", "2", "3", "4", "5", "0");
+        String rankType = "city";
+        String searchText = "蜜";
+        String[] addrCode =  new String[]{"450000000000"}; //省码
+        WdCount area = channelMapService.area(channel, rankType, searchText, addrCode);
+        for (WdCountBody wdCountBody : area.getWdCountBodyList()) {
+            System.out.println(wdCountBody);
+        }
+        System.out.println(area.getTotal());
+    }
+
+    //区
+    @Test
+    public void areaZoneTest() {
+        List<String> channel = Arrays.asList("1", "2", "3", "4", "5", "0");
+        String rankType = "zone";
+        String searchText = "蜜";
+        String[] addrCode = new String[]{"500000000000"}; //市码
+        WdCount area = channelMapService.area(channel, rankType, searchText, addrCode);
+        for (WdCountBody wdCountBody : area.getWdCountBodyList()) {
+            System.out.println(wdCountBody);
+        }
+        System.out.println(area.getTotal());
+    }
+
+    @Test
+    public void listProvinceTest(){
+        String orderby = "collect_time";
+        int pageNum = 1;
+        int pageSize = 10;
+        String[] channel = new String[]{"1", "2", "3", "4", "5", "0"};
+        String[] addrCode = new String[]{"450000000000"};
+        String rankType = "province";
+        String searchText = "蜜";
+        PageInfo<WdInfo> list = channelMapService.list(channel, rankType, searchText, orderby, addrCode, pageNum, pageSize);
+        for (WdInfo originWdInfo : list.getList()) {
+            System.out.println("originWdInfo = " + originWdInfo);
+        }
+    }
+
+    @Test
+    public void pointTest(){
+        int pageNum = 1;
+        int pageSize = 10;
+        String[] channel = new String[]{"1", "2", "3", "4", "5", "0"};
+        String[] addrCode = new String[]{"450202000000"}; //区码
+        String searchText = "";
+        PageInfo<WdInfo> list = channelMapService.point(channel, searchText,  addrCode, pageNum, pageSize);
+        for (WdInfo originWdInfo : list.getList()) {
+            System.out.println("originWdInfo = " + originWdInfo);
+        }
+    }
+
+}

+ 61 - 0
benyun-core/src/test/java/com/benyun/core/service/WdTopologicalServiceImplTest.java

@@ -0,0 +1,61 @@
+package com.benyun.core.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.benyun.core.dao.WdInfoDao;
+import com.benyun.core.entity.WdInfo;
+import com.benyun.core.entity.WdTopological;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+@SpringBootTest
+public class WdTopologicalServiceImplTest {
+    @Autowired
+    WdTopologicalService wdTopologicalService;
+
+    @Autowired
+    WdInfoDao wdInfoDao;
+
+    @Test
+    public void insertListTest(){
+        QueryWrapper<WdInfo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("wd_type_code","2");
+        List<WdInfo> wdInfos = wdInfoDao.selectList(queryWrapper);
+        List<WdTopological> list = new ArrayList<>();
+        for (WdInfo wdInfo : wdInfos) {
+            WdTopological wdTopological = new WdTopological();
+            wdTopological.setCenterWdId(wdInfo.getWdId());
+            wdTopological.setCenterWdName(wdInfo.getWdName());
+            wdTopological.setAddrCode(wdInfo.getAddrCode());
+            wdTopological.setAddrInfo(wdInfo.getAddrInfo());
+            wdTopological.setAddrCodeInfo("1232");
+            wdTopological.setDistance(13);
+            wdTopological.setRadius(0);
+            wdTopological.setLng(BigDecimal.valueOf(2));
+            wdTopological.setLat(BigDecimal.valueOf(2));
+            wdTopological.setGeoHash("123");
+            wdTopological.setAroundTypeCodeBy("123");
+            wdTopological.setAroundTypeCodeGd("123");
+            wdTopological.setAroundTypeCodeTx("123");
+            wdTopological.setAroundWdTypeCode("123");
+            wdTopological.setAroundWdName("123");
+            wdTopological.setAroundWdId("123");
+            list.add(wdTopological);
+        }
+        wdTopologicalService.insertList(list);
+    }
+
+    @Test
+    public void selectWdToplogicalListByIdTest(){
+        String centerWdId = "0040cf53ac454b678e6c9faa50eb224e";
+        List<WdTopological> list = wdTopologicalService.selectWdTopologicalListById(centerWdId);
+        for (WdTopological wdTopological : list) {
+            System.out.println(wdTopological);
+        }
+
+    }
+}

+ 456 - 0
pom.xml

@@ -0,0 +1,456 @@
+<?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>com.ruoyi</groupId>
+    <artifactId>Bailian-Al</artifactId>
+    <version>4.8.0</version>
+
+    <name>RuoYi-Vue-Plus</name>
+    <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
+    <description>RuoYi-Vue-Plus后台管理系统</description>
+
+    <properties>
+        <ruoyi-vue-plus.version>4.8.0</ruoyi-vue-plus.version>
+        <spring-boot.version>2.7.13</spring-boot.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
+        <spring-boot.mybatis>2.2.2</spring-boot.mybatis>
+        <springdoc.version>1.6.15</springdoc.version>
+        <poi.version>5.2.3</poi.version>
+        <easyexcel.version>3.3.1</easyexcel.version>
+        <velocity.version>2.3</velocity.version>
+        <satoken.version>1.35.0.RC</satoken.version>
+        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
+        <p6spy.version>3.9.1</p6spy.version>
+        <hutool.version>5.8.18</hutool.version>
+        <okhttp.version>4.10.0</okhttp.version>
+        <spring-boot-admin.version>2.7.10</spring-boot-admin.version>
+        <redisson.version>3.20.1</redisson.version>
+        <lock4j.version>2.2.3</lock4j.version>
+        <dynamic-ds.version>3.5.2</dynamic-ds.version>
+        <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
+        <xxl-job.version>2.4.0</xxl-job.version>
+        <lombok.version>1.18.26</lombok.version>
+        <bouncycastle.version>1.72</bouncycastle.version>
+        <!-- 离线IP地址定位库 -->
+        <ip2region.version>2.7.0</ip2region.version>
+
+        <!-- 临时修复 snakeyaml 漏洞 -->
+        <snakeyaml.version>1.33</snakeyaml.version>
+
+        <!-- OSS 配置 -->
+        <aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
+        <!-- SMS 配置 -->
+        <sms4j.version>2.2.0</sms4j.version>
+    </properties>
+
+    <profiles>
+        <profile>
+            <id>local</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>local</profiles.active>
+                <logging.level>debug</logging.level>
+            </properties>
+        </profile>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>dev</profiles.active>
+                <logging.level>debug</logging.level>
+            </properties>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>prod</id>
+            <properties>
+                <profiles.active>prod</profiles.active>
+                <logging.level>warn</logging.level>
+            </properties>
+        </profile>
+    </profiles>
+
+    <!-- 依赖声明 -->
+    <dependencyManagement>
+        <dependencies>
+
+            <!-- SpringBoot的依赖配置-->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- hutool 的依赖配置-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-bom</artifactId>
+                <version>${hutool.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-webmvc-core</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-javadoc</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.poi</groupId>
+                <artifactId>poi</artifactId>
+                <version>${poi.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.poi</groupId>
+                <artifactId>poi-ooxml</artifactId>
+                <version>${poi.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>easyexcel</artifactId>
+                <version>${easyexcel.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.apache.poi</groupId>
+                        <artifactId>poi-ooxml-schemas</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <!-- velocity代码生成使用模板 -->
+            <dependency>
+                <groupId>org.apache.velocity</groupId>
+                <artifactId>velocity-engine-core</artifactId>
+                <version>${velocity.version}</version>
+            </dependency>
+
+            <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-spring-boot-starter</artifactId>
+                <version>${satoken.version}</version>
+            </dependency>
+            <!-- Sa-Token 整合 jwt -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-jwt</artifactId>
+                <version>${satoken.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>cn.hutool</groupId>
+                        <artifactId>hutool-all</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <!-- dynamic-datasource 多数据源-->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+                <version>${dynamic-ds.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <!-- sql性能分析插件 -->
+            <dependency>
+                <groupId>p6spy</groupId>
+                <artifactId>p6spy</artifactId>
+                <version>${p6spy.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp</artifactId>
+                <version>${okhttp.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.amazonaws</groupId>
+                <artifactId>aws-java-sdk-s3</artifactId>
+                <version>${aws-java-sdk-s3.version}</version>
+            </dependency>
+
+            <!--短信sms4j-->
+            <dependency>
+                <groupId>org.dromara.sms4j</groupId>
+                <artifactId>sms4j-spring-boot-starter</artifactId>
+                <version>${sms4j.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>de.codecentric</groupId>
+                <artifactId>spring-boot-admin-starter-server</artifactId>
+                <version>${spring-boot-admin.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>de.codecentric</groupId>
+                <artifactId>spring-boot-admin-starter-client</artifactId>
+                <version>${spring-boot-admin.version}</version>
+            </dependency>
+
+            <!--redisson-->
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-boot-starter</artifactId>
+                <version>${redisson.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.redisson</groupId>
+                        <artifactId>redisson-spring-data-30</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-data-27</artifactId>
+                <version>${redisson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+                <version>${lock4j.version}</version>
+            </dependency>
+
+            <!-- xxl-job-core -->
+            <dependency>
+                <groupId>com.xuxueli</groupId>
+                <artifactId>xxl-job-core</artifactId>
+                <version>${xxl-job.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>transmittable-thread-local</artifactId>
+                <version>${alibaba-ttl.version}</version>
+            </dependency>
+
+            <!-- 离线IP地址定位库 ip2region -->
+            <dependency>
+                <groupId>org.lionsoul</groupId>
+                <artifactId>ip2region</artifactId>
+                <version>${ip2region.version}</version>
+            </dependency>
+
+            <!-- 临时修复 snakeyaml 漏洞 -->
+            <dependency>
+                <groupId>org.yaml</groupId>
+                <artifactId>snakeyaml</artifactId>
+                <version>${snakeyaml.version}</version>
+            </dependency>
+
+            <!-- 加密包引入 -->
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15to18</artifactId>
+                <version>${bouncycastle.version}</version>
+            </dependency>
+
+            <!-- 定时任务 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-job</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 代码生成-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-generator</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 核心模块-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-framework</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 系统模块-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-system</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- 通用工具-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-common</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- OSS对象存储模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-oss</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- SMS短信模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-sms</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- demo模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-demo</artifactId>
+                <version>${ruoyi-vue-plus.version}</version>
+            </dependency>
+
+            <!-- benyun-core -->
+            <dependency>
+                <groupId>com.benyun.core</groupId>
+                <artifactId>benyun-core</artifactId>
+                <version>0.0.1-SNAPSHOT</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.benyun.core</groupId>
+                <artifactId>benyun-core</artifactId>
+                <version>0.0.1-SNAPSHOT</version>
+            </dependency>
+
+
+        </dependencies>
+    </dependencyManagement>
+
+    <modules>
+        <module>ruoyi-admin</module>
+        <module>ruoyi-framework</module>
+        <module>ruoyi-system</module>
+        <module>ruoyi-job</module>
+        <module>ruoyi-generator</module>
+        <module>ruoyi-common</module>
+        <module>ruoyi-demo</module>
+        <module>ruoyi-extend</module>
+        <module>ruoyi-oss</module>
+        <module>ruoyi-sms</module>
+
+    </modules>
+    <packaging>pom</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.9.0</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>com.github.therapi</groupId>
+                            <artifactId>therapi-runtime-javadoc-scribe</artifactId>
+                            <version>0.15.0</version>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.springframework.boot</groupId>
+                            <artifactId>spring-boot-configuration-processor</artifactId>
+                            <version>${spring-boot.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+            <!-- 单元测试使用 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.22.2</version>
+                <configuration>
+                    <argLine>-Dfile.encoding=UTF-8</argLine>
+                    <!-- 根据打包环境执行对应的@Tag测试方法 -->
+                    <groups>${profiles.active}</groups>
+                    <!-- 排除标签 -->
+                    <excludedGroups>exclude</excludedGroups>
+                </configuration>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!-- 关闭过滤 -->
+                <filtering>false</filtering>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!-- 引入所有 匹配文件进行过滤 -->
+                <includes>
+                    <include>application*</include>
+                    <include>bootstrap*</include>
+                    <include>banner*</include>
+                </includes>
+                <!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 -->
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>huawei nexus</name>
+            <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>huawei nexus</name>
+            <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+</project>
+
+

+ 23 - 0
ruoyi-admin/Dockerfile

@@ -0,0 +1,23 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER Lion Li
+
+RUN mkdir -p /ruoyi/server/logs \
+    /ruoyi/server/temp \
+    /ruoyi/skywalking/agent
+
+WORKDIR /ruoyi/server
+
+ENV SERVER_PORT=8080
+
+EXPOSE ${SERVER_PORT}
+
+ADD ./target/ruoyi-admin.jar ./app.jar
+
+ENTRYPOINT ["java", \
+            "-Djava.security.egd=file:/dev/./urandom", \
+            "-Dserver.port=${SERVER_PORT}", \
+            # 应用名称 如果想区分集群节点监控 改成不同的名称即可
+#            "-Dskywalking.agent.service_name=ruoyi-server", \
+#            "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \
+            "-jar", "app.jar"]

+ 132 - 0
ruoyi-admin/pom.xml

@@ -0,0 +1,132 @@
+<?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">
+    <parent>
+        <artifactId>Bailian-Al</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>4.8.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>ruoyi-admin</artifactId>
+
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <!-- Oracle -->
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc8</artifactId>
+        </dependency>
+        <!-- PostgreSql -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <!-- SqlServer -->
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-framework</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-system</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-job</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-oss</artifactId>
+        </dependency>
+
+        <!-- 代码生成-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-generator</artifactId>
+        </dependency>
+
+        <!--  demo模块  -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-demo</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- skywalking 整合 logback -->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.skywalking</groupId>-->
+<!--            <artifactId>apm-toolkit-logback-1.x</artifactId>-->
+<!--            <version>${与你的agent探针版本保持一致}</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.skywalking</groupId>-->
+<!--            <artifactId>apm-toolkit-trace</artifactId>-->
+<!--            <version>${与你的agent探针版本保持一致}</version>-->
+<!--        </dependency>-->
+
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.2.2</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 24 - 0
ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java

@@ -0,0 +1,24 @@
+package com.ruoyi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+
+/**
+ * 启动程序
+ *
+ * @author ruoyi
+ */
+
+@SpringBootApplication
+public class RuoYiApplication {
+
+    public static void main(String[] args) {
+        System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication application = new SpringApplication(RuoYiApplication.class);
+        application.setApplicationStartup(new BufferingApplicationStartup(2048));
+        application.run(args);
+        System.out.println("(♥◠‿◠)ノ゙  RuoYi-Vue-Plus启动成功   ლ(´ڡ`ლ)゙");
+    }
+
+}

+ 18 - 0
ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java

@@ -0,0 +1,18 @@
+package com.ruoyi;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ *
+ * @author ruoyi
+ */
+public class RuoYiServletInitializer extends SpringBootServletInitializer {
+
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+        return application.sources(RuoYiApplication.class);
+    }
+
+}

+ 134 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java

@@ -0,0 +1,134 @@
+package com.ruoyi.web.controller.common;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.enums.CaptchaType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.email.MailUtils;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.framework.config.properties.CaptchaProperties;
+import com.ruoyi.framework.config.properties.MailProperties;
+import com.ruoyi.system.service.ISysConfigService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.sms4j.api.SmsBlend;
+import org.dromara.sms4j.api.entity.SmsResponse;
+import org.dromara.sms4j.core.factory.SmsFactory;
+import org.dromara.sms4j.provider.enumerate.SupplierType;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.constraints.NotBlank;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 验证码操作处理
+ *
+ * @author Lion Li
+ */
+@SaIgnore
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class CaptchaController {
+
+    private final CaptchaProperties captchaProperties;
+    private final ISysConfigService configService;
+    private final MailProperties mailProperties;
+
+    /**
+     * 短信验证码
+     *
+     * @param phonenumber 用户手机号
+     */
+    @GetMapping("/captchaSms")
+    public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
+        String key = CacheConstants.CAPTCHA_CODE_KEY + phonenumber;
+        String code = RandomUtil.randomNumbers(4);
+        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        // 验证码模板id 自行处理 (查数据库或写死均可)
+        String templateId = "";
+        LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
+        map.put("code", code);
+        SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
+        SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
+        if (!"OK".equals(smsResponse.getCode())) {
+            log.error("验证码短信发送异常 => {}", smsResponse);
+            return R.fail(smsResponse.getMessage());
+        }
+        return R.ok();
+    }
+
+    /**
+     * 邮箱验证码
+     *
+     * @param email 邮箱
+     */
+    @GetMapping("/captchaEmail")
+    public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
+        if (!mailProperties.getEnabled()) {
+            return R.fail("当前系统没有开启邮箱功能!");
+        }
+        String key = CacheConstants.CAPTCHA_CODE_KEY + email;
+        String code = RandomUtil.randomNumbers(4);
+        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        try {
+            MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
+        } catch (Exception e) {
+            log.error("验证码短信发送异常 => {}", e.getMessage());
+            return R.fail(e.getMessage());
+        }
+        return R.ok();
+    }
+
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/captchaImage")
+    public R<Map<String, Object>> getCode() {
+        Map<String, Object> ajax = new HashMap<>();
+        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        ajax.put("captchaEnabled", captchaEnabled);
+        if (!captchaEnabled) {
+            return R.ok(ajax);
+        }
+        // 保存验证码信息
+        String uuid = IdUtil.simpleUUID();
+        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
+        // 生成验证码
+        CaptchaType captchaType = captchaProperties.getType();
+        boolean isMath = CaptchaType.MATH == captchaType;
+        Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
+        CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
+        AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
+        captcha.setGenerator(codeGenerator);
+        captcha.createCode();
+        String code = captcha.getCode();
+        if (isMath) {
+            ExpressionParser parser = new SpelExpressionParser();
+            Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
+            code = exp.getValue(String.class);
+        }
+        RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        ajax.put("uuid", uuid);
+        ajax.put("img", captcha.getImageBase64());
+        return R.ok(ajax);
+    }
+
+}

+ 168 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java

@@ -0,0 +1,168 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.collection.CollUtil;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.CacheNames;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.utils.JsonUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.redis.CacheUtils;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.system.domain.SysCache;
+import lombok.RequiredArgsConstructor;
+import org.redisson.spring.data.connection.RedissonConnectionFactory;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 缓存监控
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/cache")
+public class CacheController {
+
+    private final RedissonConnectionFactory connectionFactory;
+
+    private final static List<SysCache> CACHES = new ArrayList<>();
+
+    static {
+        CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户"));
+        CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息"));
+        CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典"));
+        CACHES.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
+        CACHES.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
+        CACHES.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
+        CACHES.add(new SysCache(CacheNames.SYS_OSS_CONFIG, "OSS配置"));
+        CACHES.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
+    }
+
+    /**
+     * 获取缓存监控列表
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping()
+    public R<Map<String, Object>> getInfo() throws Exception {
+        RedisConnection connection = connectionFactory.getConnection();
+        Properties info = connection.info();
+        Properties commandStats = connection.info("commandstats");
+        Long dbSize = connection.dbSize();
+
+        Map<String, Object> result = new HashMap<>(3);
+        result.put("info", info);
+        result.put("dbSize", dbSize);
+
+        List<Map<String, String>> pieList = new ArrayList<>();
+        if (commandStats != null) {
+            commandStats.stringPropertyNames().forEach(key -> {
+                Map<String, String> data = new HashMap<>(2);
+                String property = commandStats.getProperty(key);
+                data.put("name", StringUtils.removeStart(key, "cmdstat_"));
+                data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
+                pieList.add(data);
+            });
+        }
+        result.put("commandStats", pieList);
+        return R.ok(result);
+    }
+
+    /**
+     * 获取缓存监控缓存名列表
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping("/getNames")
+    public R<List<SysCache>> cache() {
+        return R.ok(CACHES);
+    }
+
+    /**
+     * 获取缓存监控Key列表
+     *
+     * @param cacheName 缓存名
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping("/getKeys/{cacheName}")
+    public R<Collection<String>> getCacheKeys(@PathVariable String cacheName) {
+        Collection<String> cacheKeys = new HashSet<>(0);
+        if (isCacheNames(cacheName)) {
+            Set<Object> keys = CacheUtils.keys(cacheName);
+            if (CollUtil.isNotEmpty(keys)) {
+                cacheKeys = keys.stream().map(Object::toString).collect(Collectors.toList());
+            }
+        } else {
+            cacheKeys = RedisUtils.keys(cacheName + "*");
+        }
+        return R.ok(cacheKeys);
+    }
+
+    /**
+     * 获取缓存监控缓存值详情
+     *
+     * @param cacheName 缓存名
+     * @param cacheKey  缓存key
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @GetMapping("/getValue/{cacheName}/{cacheKey}")
+    public R<SysCache> getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) {
+        Object cacheValue;
+        if (isCacheNames(cacheName)) {
+            cacheValue = CacheUtils.get(cacheName, cacheKey);
+        } else {
+            cacheValue = RedisUtils.getCacheObject(cacheKey);
+        }
+        SysCache sysCache = new SysCache(cacheName, cacheKey, JsonUtils.toJsonString(cacheValue));
+        return R.ok(sysCache);
+    }
+
+    /**
+     * 清理缓存监控缓存名
+     *
+     * @param cacheName 缓存名
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @DeleteMapping("/clearCacheName/{cacheName}")
+    public R<Void> clearCacheName(@PathVariable String cacheName) {
+        if (isCacheNames(cacheName)) {
+            CacheUtils.clear(cacheName);
+        } else {
+            RedisUtils.deleteKeys(cacheName + "*");
+        }
+        return R.ok();
+    }
+
+    /**
+     * 清理缓存监控Key
+     *
+     * @param cacheKey key名
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @DeleteMapping("/clearCacheKey/{cacheName}/{cacheKey}")
+    public R<Void> clearCacheKey(@PathVariable String cacheName, @PathVariable String cacheKey) {
+        if (isCacheNames(cacheName)) {
+            CacheUtils.evict(cacheName, cacheKey);
+        } else {
+            RedisUtils.deleteObject(cacheKey);
+        }
+        return R.ok();
+    }
+
+    /**
+     * 清理全部缓存监控
+     */
+    @SaCheckPermission("monitor:cache:list")
+    @DeleteMapping("/clearCacheAll")
+    public R<Void> clearCacheAll() {
+        RedisUtils.deleteKeys("*");
+        return R.ok();
+    }
+
+    private boolean isCacheNames(String cacheName) {
+        return !StringUtils.contains(cacheName, ":");
+    }
+}

+ 88 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java

@@ -0,0 +1,88 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.system.domain.SysLogininfor;
+import com.ruoyi.system.service.ISysLogininforService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 系统访问记录
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/logininfor")
+public class SysLogininforController extends BaseController {
+
+    private final ISysLogininforService logininforService;
+
+    /**
+     * 获取系统访问记录列表
+     */
+    @SaCheckPermission("monitor:logininfor:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysLogininfor> list(SysLogininfor logininfor, PageQuery pageQuery) {
+        return logininforService.selectPageLogininforList(logininfor, pageQuery);
+    }
+
+    /**
+     * 导出系统访问记录列表
+     */
+    @Log(title = "登录日志", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("monitor:logininfor:export")
+    @PostMapping("/export")
+    public void export(SysLogininfor logininfor, HttpServletResponse response) {
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        ExcelUtil.exportExcel(list, "登录日志", SysLogininfor.class, response);
+    }
+
+    /**
+     * 批量删除登录日志
+     * @param infoIds 日志ids
+     */
+    @SaCheckPermission("monitor:logininfor:remove")
+    @Log(title = "登录日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{infoIds}")
+    public R<Void> remove(@PathVariable Long[] infoIds) {
+        return toAjax(logininforService.deleteLogininforByIds(infoIds));
+    }
+
+    /**
+     * 清理系统访问记录
+     */
+    @SaCheckPermission("monitor:logininfor:remove")
+    @Log(title = "登录日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/clean")
+    public R<Void> clean() {
+        logininforService.cleanLogininfor();
+        return R.ok();
+    }
+
+    @SaCheckPermission("monitor:logininfor:unlock")
+    @Log(title = "账户解锁", businessType = BusinessType.OTHER)
+    @GetMapping("/unlock/{userName}")
+    public R<Void> unlock(@PathVariable("userName") String userName) {
+        String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName;
+        if (RedisUtils.hasKey(loginName)) {
+            RedisUtils.deleteObject(loginName);
+        }
+        return R.ok();
+    }
+
+}

+ 74 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java

@@ -0,0 +1,74 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.service.ISysOperLogService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 操作日志记录
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/operlog")
+public class SysOperlogController extends BaseController {
+
+    private final ISysOperLogService operLogService;
+
+    /**
+     * 获取操作日志记录列表
+     */
+    @SaCheckPermission("monitor:operlog:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOperLog> list(SysOperLog operLog, PageQuery pageQuery) {
+        return operLogService.selectPageOperLogList(operLog, pageQuery);
+    }
+
+    /**
+     * 导出操作日志记录列表
+     */
+    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("monitor:operlog:export")
+    @PostMapping("/export")
+    public void export(SysOperLog operLog, HttpServletResponse response) {
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        ExcelUtil.exportExcel(list, "操作日志", SysOperLog.class, response);
+    }
+
+    /**
+     * 批量删除操作日志记录
+     * @param operIds 日志ids
+     */
+    @Log(title = "操作日志", businessType = BusinessType.DELETE)
+    @SaCheckPermission("monitor:operlog:remove")
+    @DeleteMapping("/{operIds}")
+    public R<Void> remove(@PathVariable Long[] operIds) {
+        return toAjax(operLogService.deleteOperLogByIds(operIds));
+    }
+
+    /**
+     * 清理操作日志记录
+     */
+    @Log(title = "操作日志", businessType = BusinessType.CLEAN)
+    @SaCheckPermission("monitor:operlog:remove")
+    @DeleteMapping("/clean")
+    public R<Void> clean() {
+        operLogService.cleanOperLog();
+        return R.ok();
+    }
+}

+ 90 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java

@@ -0,0 +1,90 @@
+package com.ruoyi.web.controller.monitor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.dto.UserOnlineDTO;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StreamUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.system.domain.SysUserOnline;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 在线用户监控
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/monitor/online")
+public class SysUserOnlineController extends BaseController {
+
+    /**
+     * 获取在线用户监控列表
+     *
+     * @param ipaddr   IP地址
+     * @param userName 用户名
+     */
+    @SaCheckPermission("monitor:online:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysUserOnline> list(String ipaddr, String userName) {
+        // 获取所有未过期的 token
+        List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
+        List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
+        for (String key : keys) {
+            String token = StringUtils.substringAfterLast(key, ":");
+            // 如果已经过期则跳过
+            if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
+                continue;
+            }
+            userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
+        }
+        if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) {
+            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+                StringUtils.equals(ipaddr, userOnline.getIpaddr()) &&
+                    StringUtils.equals(userName, userOnline.getUserName())
+            );
+        } else if (StringUtils.isNotEmpty(ipaddr)) {
+            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+                StringUtils.equals(ipaddr, userOnline.getIpaddr())
+            );
+        } else if (StringUtils.isNotEmpty(userName)) {
+            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
+                StringUtils.equals(userName, userOnline.getUserName())
+            );
+        }
+        Collections.reverse(userOnlineDTOList);
+        userOnlineDTOList.removeAll(Collections.singleton(null));
+        List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
+        return TableDataInfo.build(userOnlineList);
+    }
+
+    /**
+     * 强退用户
+     *
+     * @param tokenId token值
+     */
+    @SaCheckPermission("monitor:online:forceLogout")
+    @Log(title = "在线用户", businessType = BusinessType.FORCE)
+    @DeleteMapping("/{tokenId}")
+    public R<Void> forceLogout(@PathVariable String tokenId) {
+        try {
+            StpUtil.kickoutByTokenValue(tokenId);
+        } catch (NotLoginException ignored) {
+        }
+        return R.ok();
+    }
+}

+ 137 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java

@@ -0,0 +1,137 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysConfig;
+import com.ruoyi.system.service.ISysConfigService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 参数配置 信息操作处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/config")
+public class SysConfigController extends BaseController {
+
+    private final ISysConfigService configService;
+
+    /**
+     * 获取参数配置列表
+     */
+    @SaCheckPermission("system:config:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysConfig> list(SysConfig config, PageQuery pageQuery) {
+        return configService.selectPageConfigList(config, pageQuery);
+    }
+
+    /**
+     * 导出参数配置列表
+     */
+    @Log(title = "参数管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:config:export")
+    @PostMapping("/export")
+    public void export(SysConfig config, HttpServletResponse response) {
+        List<SysConfig> list = configService.selectConfigList(config);
+        ExcelUtil.exportExcel(list, "参数数据", SysConfig.class, response);
+    }
+
+    /**
+     * 根据参数编号获取详细信息
+     *
+     * @param configId 参数ID
+     */
+    @SaCheckPermission("system:config:query")
+    @GetMapping(value = "/{configId}")
+    public R<SysConfig> getInfo(@PathVariable Long configId) {
+        return R.ok(configService.selectConfigById(configId));
+    }
+
+    /**
+     * 根据参数键名查询参数值
+     *
+     * @param configKey 参数Key
+     */
+    @GetMapping(value = "/configKey/{configKey}")
+    public R<Void> getConfigKey(@PathVariable String configKey) {
+        return R.ok(configService.selectConfigByKey(configKey));
+    }
+
+    /**
+     * 新增参数配置
+     */
+    @SaCheckPermission("system:config:add")
+    @Log(title = "参数管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysConfig config) {
+        if (!configService.checkConfigKeyUnique(config)) {
+            return R.fail("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        configService.insertConfig(config);
+        return R.ok();
+    }
+
+    /**
+     * 修改参数配置
+     */
+    @SaCheckPermission("system:config:edit")
+    @Log(title = "参数管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysConfig config) {
+        if (!configService.checkConfigKeyUnique(config)) {
+            return R.fail("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        configService.updateConfig(config);
+        return R.ok();
+    }
+
+    /**
+     * 根据参数键名修改参数配置
+     */
+    @SaCheckPermission("system:config:edit")
+    @Log(title = "参数管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateByKey")
+    public R<Void> updateByKey(@RequestBody SysConfig config) {
+        configService.updateConfig(config);
+        return R.ok();
+    }
+
+    /**
+     * 删除参数配置
+     *
+     * @param configIds 参数ID串
+     */
+    @SaCheckPermission("system:config:remove")
+    @Log(title = "参数管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{configIds}")
+    public R<Void> remove(@PathVariable Long[] configIds) {
+        configService.deleteConfigByIds(configIds);
+        return R.ok();
+    }
+
+    /**
+     * 刷新参数缓存
+     */
+    @SaCheckPermission("system:config:remove")
+    @Log(title = "参数管理", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public R<Void> refreshCache() {
+        configService.resetConfigCache();
+        return R.ok();
+    }
+}

+ 119 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java

@@ -0,0 +1,119 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.convert.Convert;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysDeptService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 部门信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dept")
+public class SysDeptController extends BaseController {
+
+    private final ISysDeptService deptService;
+
+    /**
+     * 获取部门列表
+     */
+    @SaCheckPermission("system:dept:list")
+    @GetMapping("/list")
+    public R<List<SysDept>> list(SysDept dept) {
+        List<SysDept> depts = deptService.selectDeptList(dept);
+        return R.ok(depts);
+    }
+
+    /**
+     * 查询部门列表(排除节点)
+     *
+     * @param deptId 部门ID
+     */
+    @SaCheckPermission("system:dept:list")
+    @GetMapping("/list/exclude/{deptId}")
+    public R<List<SysDept>> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) {
+        List<SysDept> depts = deptService.selectDeptList(new SysDept());
+        depts.removeIf(d -> d.getDeptId().equals(deptId)
+            || StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId)));
+        return R.ok(depts);
+    }
+
+    /**
+     * 根据部门编号获取详细信息
+     *
+     * @param deptId 部门ID
+     */
+    @SaCheckPermission("system:dept:query")
+    @GetMapping(value = "/{deptId}")
+    public R<SysDept> getInfo(@PathVariable Long deptId) {
+        deptService.checkDeptDataScope(deptId);
+        return R.ok(deptService.selectDeptById(deptId));
+    }
+
+    /**
+     * 新增部门
+     */
+    @SaCheckPermission("system:dept:add")
+    @Log(title = "部门管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysDept dept) {
+        if (!deptService.checkDeptNameUnique(dept)) {
+            return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        return toAjax(deptService.insertDept(dept));
+    }
+
+    /**
+     * 修改部门
+     */
+    @SaCheckPermission("system:dept:edit")
+    @Log(title = "部门管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysDept dept) {
+        Long deptId = dept.getDeptId();
+        deptService.checkDeptDataScope(deptId);
+        if (!deptService.checkDeptNameUnique(dept)) {
+            return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        } else if (dept.getParentId().equals(deptId)) {
+            return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
+        } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())
+            && deptService.selectNormalChildrenDeptById(deptId) > 0) {
+            return R.fail("该部门包含未停用的子部门!");
+        }
+        return toAjax(deptService.updateDept(dept));
+    }
+
+    /**
+     * 删除部门
+     *
+     * @param deptId 部门ID
+     */
+    @SaCheckPermission("system:dept:remove")
+    @Log(title = "部门管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{deptId}")
+    public R<Void> remove(@PathVariable Long deptId) {
+        if (deptService.hasChildByDeptId(deptId)) {
+            return R.warn("存在下级部门,不允许删除");
+        }
+        if (deptService.checkDeptExistUser(deptId)) {
+            return R.warn("部门存在用户,不允许删除");
+        }
+        deptService.checkDeptDataScope(deptId);
+        return toAjax(deptService.deleteDeptById(deptId));
+    }
+}

+ 116 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java

@@ -0,0 +1,116 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictDataService;
+import com.ruoyi.system.service.ISysDictTypeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 数据字典信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dict/data")
+public class SysDictDataController extends BaseController {
+
+    private final ISysDictDataService dictDataService;
+    private final ISysDictTypeService dictTypeService;
+
+    /**
+     * 查询字典数据列表
+     */
+    @SaCheckPermission("system:dict:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysDictData> list(SysDictData dictData, PageQuery pageQuery) {
+        return dictDataService.selectPageDictDataList(dictData, pageQuery);
+    }
+
+    /**
+     * 导出字典数据列表
+     */
+    @Log(title = "字典数据", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:dict:export")
+    @PostMapping("/export")
+    public void export(SysDictData dictData, HttpServletResponse response) {
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        ExcelUtil.exportExcel(list, "字典数据", SysDictData.class, response);
+    }
+
+    /**
+     * 查询字典数据详细
+     *
+     * @param dictCode 字典code
+     */
+    @SaCheckPermission("system:dict:query")
+    @GetMapping(value = "/{dictCode}")
+    public R<SysDictData> getInfo(@PathVariable Long dictCode) {
+        return R.ok(dictDataService.selectDictDataById(dictCode));
+    }
+
+    /**
+     * 根据字典类型查询字典数据信息
+     *
+     * @param dictType 字典类型
+     */
+    @GetMapping(value = "/type/{dictType}")
+    public R<List<SysDictData>> dictType(@PathVariable String dictType) {
+        List<SysDictData> data = dictTypeService.selectDictDataByType(dictType);
+        if (ObjectUtil.isNull(data)) {
+            data = new ArrayList<>();
+        }
+        return R.ok(data);
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @SaCheckPermission("system:dict:add")
+    @Log(title = "字典数据", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysDictData dict) {
+        dictDataService.insertDictData(dict);
+        return R.ok();
+    }
+
+    /**
+     * 修改保存字典类型
+     */
+    @SaCheckPermission("system:dict:edit")
+    @Log(title = "字典数据", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysDictData dict) {
+        dictDataService.updateDictData(dict);
+        return R.ok();
+    }
+
+    /**
+     * 删除字典类型
+     *
+     * @param dictCodes 字典code串
+     */
+    @SaCheckPermission("system:dict:remove")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictCodes}")
+    public R<Void> remove(@PathVariable Long[] dictCodes) {
+        dictDataService.deleteDictDataByIds(dictCodes);
+        return R.ok();
+    }
+}

+ 125 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java

@@ -0,0 +1,125 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysDictType;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictTypeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 数据字典信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dict/type")
+public class SysDictTypeController extends BaseController {
+
+    private final ISysDictTypeService dictTypeService;
+
+    /**
+     * 查询字典类型列表
+     */
+    @SaCheckPermission("system:dict:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysDictType> list(SysDictType dictType, PageQuery pageQuery) {
+        return dictTypeService.selectPageDictTypeList(dictType, pageQuery);
+    }
+
+    /**
+     * 导出字典类型列表
+     */
+    @Log(title = "字典类型", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:dict:export")
+    @PostMapping("/export")
+    public void export(SysDictType dictType, HttpServletResponse response) {
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        ExcelUtil.exportExcel(list, "字典类型", SysDictType.class, response);
+    }
+
+    /**
+     * 查询字典类型详细
+     *
+     * @param dictId 字典ID
+     */
+    @SaCheckPermission("system:dict:query")
+    @GetMapping(value = "/{dictId}")
+    public R<SysDictType> getInfo(@PathVariable Long dictId) {
+        return R.ok(dictTypeService.selectDictTypeById(dictId));
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @SaCheckPermission("system:dict:add")
+    @Log(title = "字典类型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysDictType dict) {
+        if (!dictTypeService.checkDictTypeUnique(dict)) {
+            return R.fail("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dictTypeService.insertDictType(dict);
+        return R.ok();
+    }
+
+    /**
+     * 修改字典类型
+     */
+    @SaCheckPermission("system:dict:edit")
+    @Log(title = "字典类型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysDictType dict) {
+        if (!dictTypeService.checkDictTypeUnique(dict)) {
+            return R.fail("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dictTypeService.updateDictType(dict);
+        return R.ok();
+    }
+
+    /**
+     * 删除字典类型
+     *
+     * @param dictIds 字典ID串
+     */
+    @SaCheckPermission("system:dict:remove")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictIds}")
+    public R<Void> remove(@PathVariable Long[] dictIds) {
+        dictTypeService.deleteDictTypeByIds(dictIds);
+        return R.ok();
+    }
+
+    /**
+     * 刷新字典缓存
+     */
+    @SaCheckPermission("system:dict:remove")
+    @Log(title = "字典类型", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public R<Void> refreshCache() {
+        dictTypeService.resetDictCache();
+        return R.ok();
+    }
+
+    /**
+     * 获取字典选择框列表
+     */
+    @GetMapping("/optionselect")
+    public R<List<SysDictType>> optionselect() {
+        List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll();
+        return R.ok(dictTypes);
+    }
+}

+ 32 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java

@@ -0,0 +1,32 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.utils.StringUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 首页
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+public class SysIndexController {
+
+    /**
+     * 系统基础配置
+     */
+    private final RuoYiConfig ruoyiConfig;
+
+    /**
+     * 访问首页,提示语
+     */
+    @SaIgnore
+    @GetMapping("/")
+    public String index() {
+        return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
+    }
+}

+ 144 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -0,0 +1,144 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.EmailLoginBody;
+import com.ruoyi.common.core.domain.model.LoginBody;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.domain.model.SmsLoginBody;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.system.domain.vo.RouterVo;
+import com.ruoyi.system.service.ISysMenuService;
+import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.SysLoginService;
+import lombok.RequiredArgsConstructor;
+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 javax.validation.constraints.NotBlank;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 登录验证
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class SysLoginController {
+
+    private final SysLoginService loginService;
+    private final ISysMenuService menuService;
+    private final ISysUserService userService;
+
+    /**
+     * 登录方法
+     *
+     * @param loginBody 登录信息
+     * @return 结果
+     */
+    @SaIgnore
+    @PostMapping("/login")
+    public R<Map<String, Object>> login(@Validated @RequestBody LoginBody loginBody) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
+            loginBody.getUuid());
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 短信登录
+     *
+     * @param smsLoginBody 登录信息
+     * @return 结果
+     */
+    @SaIgnore
+    @PostMapping("/smsLogin")
+    public R<Map<String, Object>> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode());
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 邮件登录
+     *
+     * @param body 登录信息
+     * @return 结果
+     */
+    @PostMapping("/emailLogin")
+    public R<Map<String, Object>> emailLogin(@Validated @RequestBody EmailLoginBody body) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.emailLogin(body.getEmail(), body.getEmailCode());
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 小程序登录(示例)
+     *
+     * @param xcxCode 小程序code
+     * @return 结果
+     */
+    @SaIgnore
+    @PostMapping("/xcxLogin")
+    public R<Map<String, Object>> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.xcxLogin(xcxCode);
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 退出登录
+     */
+    @SaIgnore
+    @PostMapping("/logout")
+    public R<Void> logout() {
+        loginService.logout();
+        return R.ok("退出成功");
+    }
+
+    /**
+     * 获取用户信息
+     *
+     * @return 用户信息
+     */
+    @GetMapping("getInfo")
+    public R<Map<String, Object>> getInfo() {
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        SysUser user = userService.selectUserById(loginUser.getUserId());
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("user", user);
+        ajax.put("roles", loginUser.getRolePermission());
+        ajax.put("permissions", loginUser.getMenuPermission());
+        return R.ok(ajax);
+    }
+
+    /**
+     * 获取路由信息
+     *
+     * @return 路由信息
+     */
+    @GetMapping("getRouters")
+    public R<List<RouterVo>> getRouters() {
+        Long userId = LoginHelper.getUserId();
+        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
+        return R.ok(menuService.buildMenus(menus));
+    }
+}

+ 127 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java

@@ -0,0 +1,127 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.lang.tree.Tree;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysMenuService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 菜单信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/menu")
+public class SysMenuController extends BaseController {
+
+    private final ISysMenuService menuService;
+
+    /**
+     * 获取菜单列表
+     */
+    @SaCheckPermission("system:menu:list")
+    @GetMapping("/list")
+    public R<List<SysMenu>> list(SysMenu menu) {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return R.ok(menus);
+    }
+
+    /**
+     * 根据菜单编号获取详细信息
+     *
+     * @param menuId 菜单ID
+     */
+    @SaCheckPermission("system:menu:query")
+    @GetMapping(value = "/{menuId}")
+    public R<SysMenu> getInfo(@PathVariable Long menuId) {
+        return R.ok(menuService.selectMenuById(menuId));
+    }
+
+    /**
+     * 获取菜单下拉树列表
+     */
+    @GetMapping("/treeselect")
+    public R<List<Tree<Long>>> treeselect(SysMenu menu) {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return R.ok(menuService.buildMenuTreeSelect(menus));
+    }
+
+    /**
+     * 加载对应角色菜单列表树
+     *
+     * @param roleId 角色ID
+     */
+    @GetMapping(value = "/roleMenuTreeselect/{roleId}")
+    public R<Map<String, Object>> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
+        List<SysMenu> menus = menuService.selectMenuList(getUserId());
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
+        ajax.put("menus", menuService.buildMenuTreeSelect(menus));
+        return R.ok(ajax);
+    }
+
+    /**
+     * 新增菜单
+     */
+    @SaCheckPermission("system:menu:add")
+    @Log(title = "菜单管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysMenu menu) {
+        if (!menuService.checkMenuNameUnique(menu)) {
+            return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
+            return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        }
+        return toAjax(menuService.insertMenu(menu));
+    }
+
+    /**
+     * 修改菜单
+     */
+    @SaCheckPermission("system:menu:edit")
+    @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysMenu menu) {
+        if (!menuService.checkMenuNameUnique(menu)) {
+            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
+            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        } else if (menu.getMenuId().equals(menu.getParentId())) {
+            return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
+        }
+        return toAjax(menuService.updateMenu(menu));
+    }
+
+    /**
+     * 删除菜单
+     *
+     * @param menuId 菜单ID
+     */
+    @SaCheckPermission("system:menu:remove")
+    @Log(title = "菜单管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{menuId}")
+    public R<Void> remove(@PathVariable("menuId") Long menuId) {
+        if (menuService.hasChildByMenuId(menuId)) {
+            return R.warn("存在子菜单,不允许删除");
+        }
+        if (menuService.checkMenuExistRole(menuId)) {
+            return R.warn("菜单已分配,不允许删除");
+        }
+        return toAjax(menuService.deleteMenuById(menuId));
+    }
+}

+ 80 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java

@@ -0,0 +1,80 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.SysNotice;
+import com.ruoyi.system.service.ISysNoticeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 公告 信息操作处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/notice")
+public class SysNoticeController extends BaseController {
+
+    private final ISysNoticeService noticeService;
+
+    /**
+     * 获取通知公告列表
+     */
+    @SaCheckPermission("system:notice:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysNotice> list(SysNotice notice, PageQuery pageQuery) {
+        return noticeService.selectPageNoticeList(notice, pageQuery);
+    }
+
+    /**
+     * 根据通知公告编号获取详细信息
+     *
+     * @param noticeId 公告ID
+     */
+    @SaCheckPermission("system:notice:query")
+    @GetMapping(value = "/{noticeId}")
+    public R<SysNotice> getInfo(@PathVariable Long noticeId) {
+        return R.ok(noticeService.selectNoticeById(noticeId));
+    }
+
+    /**
+     * 新增通知公告
+     */
+    @SaCheckPermission("system:notice:add")
+    @Log(title = "通知公告", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysNotice notice) {
+        return toAjax(noticeService.insertNotice(notice));
+    }
+
+    /**
+     * 修改通知公告
+     */
+    @SaCheckPermission("system:notice:edit")
+    @Log(title = "通知公告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysNotice notice) {
+        return toAjax(noticeService.updateNotice(notice));
+    }
+
+    /**
+     * 删除通知公告
+     *
+     * @param noticeIds 公告ID串
+     */
+    @SaCheckPermission("system:notice:remove")
+    @Log(title = "通知公告", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{noticeIds}")
+    public R<Void> remove(@PathVariable Long[] noticeIds) {
+        return toAjax(noticeService.deleteNoticeByIds(noticeIds));
+    }
+}

+ 105 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssConfigController.java

@@ -0,0 +1,105 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.bo.SysOssConfigBo;
+import com.ruoyi.system.domain.vo.SysOssConfigVo;
+import com.ruoyi.system.service.ISysOssConfigService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+
+/**
+ * 对象存储配置
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @date 2021-08-13
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/oss/config")
+public class SysOssConfigController extends BaseController {
+
+    private final ISysOssConfigService iSysOssConfigService;
+
+    /**
+     * 查询对象存储配置列表
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
+        return iSysOssConfigService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取对象存储配置详细信息
+     *
+     * @param ossConfigId OSS配置ID
+     */
+    @SaCheckPermission("system:oss:query")
+    @GetMapping("/{ossConfigId}")
+    public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long ossConfigId) {
+        return R.ok(iSysOssConfigService.queryById(ossConfigId));
+    }
+
+    /**
+     * 新增对象存储配置
+     */
+    @SaCheckPermission("system:oss:add")
+    @Log(title = "对象存储配置", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
+        return toAjax(iSysOssConfigService.insertByBo(bo));
+    }
+
+    /**
+     * 修改对象存储配置
+     */
+    @SaCheckPermission("system:oss:edit")
+    @Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
+        return toAjax(iSysOssConfigService.updateByBo(bo));
+    }
+
+    /**
+     * 删除对象存储配置
+     *
+     * @param ossConfigIds OSS配置ID串
+     */
+    @SaCheckPermission("system:oss:remove")
+    @Log(title = "对象存储配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ossConfigIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ossConfigIds) {
+        return toAjax(iSysOssConfigService.deleteWithValidByIds(Arrays.asList(ossConfigIds), true));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:oss:edit")
+    @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
+        return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
+    }
+}

+ 109 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java

@@ -0,0 +1,109 @@
+package com.ruoyi.web.controller.system;
+
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.bo.SysOssBo;
+import com.ruoyi.system.domain.vo.SysOssVo;
+import com.ruoyi.system.service.ISysOssService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotEmpty;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 文件上传 控制层
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/oss")
+public class SysOssController extends BaseController {
+
+    private final ISysOssService iSysOssService;
+
+    /**
+     * 查询OSS对象存储列表
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
+        return iSysOssService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 查询OSS对象基于id串
+     *
+     * @param ossIds OSS对象ID串
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/listByIds/{ossIds}")
+    public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
+                                       @PathVariable Long[] ossIds) {
+        List<SysOssVo> list = iSysOssService.listByIds(Arrays.asList(ossIds));
+        return R.ok(list);
+    }
+
+    /**
+     * 上传OSS对象存储
+     *
+     * @param file 文件
+     */
+    @SaCheckPermission("system:oss:upload")
+    @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
+        if (ObjectUtil.isNull(file)) {
+            return R.fail("上传文件不能为空");
+        }
+        SysOssVo oss = iSysOssService.upload(file);
+        Map<String, String> map = new HashMap<>(2);
+        map.put("url", oss.getUrl());
+        map.put("fileName", oss.getOriginalName());
+        map.put("ossId", oss.getOssId().toString());
+        return R.ok(map);
+    }
+
+    /**
+     * 下载OSS对象
+     *
+     * @param ossId OSS对象ID
+     */
+    @SaCheckPermission("system:oss:download")
+    @GetMapping("/download/{ossId}")
+    public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
+        iSysOssService.download(ossId,response);
+    }
+
+    /**
+     * 删除OSS对象存储
+     *
+     * @param ossIds OSS对象ID串
+     */
+    @SaCheckPermission("system:oss:remove")
+    @Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ossIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ossIds) {
+        return toAjax(iSysOssService.deleteWithValidByIds(Arrays.asList(ossIds), true));
+    }
+
+}

+ 115 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java

@@ -0,0 +1,115 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysPost;
+import com.ruoyi.system.service.ISysPostService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 岗位信息操作处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/post")
+public class SysPostController extends BaseController {
+
+    private final ISysPostService postService;
+
+    /**
+     * 获取岗位列表
+     */
+    @SaCheckPermission("system:post:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysPost> list(SysPost post, PageQuery pageQuery) {
+        return postService.selectPagePostList(post, pageQuery);
+    }
+
+    /**
+     * 导出岗位列表
+     */
+    @Log(title = "岗位管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:post:export")
+    @PostMapping("/export")
+    public void export(SysPost post, HttpServletResponse response) {
+        List<SysPost> list = postService.selectPostList(post);
+        ExcelUtil.exportExcel(list, "岗位数据", SysPost.class, response);
+    }
+
+    /**
+     * 根据岗位编号获取详细信息
+     *
+     * @param postId 岗位ID
+     */
+    @SaCheckPermission("system:post:query")
+    @GetMapping(value = "/{postId}")
+    public R<SysPost> getInfo(@PathVariable Long postId) {
+        return R.ok(postService.selectPostById(postId));
+    }
+
+    /**
+     * 新增岗位
+     */
+    @SaCheckPermission("system:post:add")
+    @Log(title = "岗位管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysPost post) {
+        if (!postService.checkPostNameUnique(post)) {
+            return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        } else if (!postService.checkPostCodeUnique(post)) {
+            return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        }
+        return toAjax(postService.insertPost(post));
+    }
+
+    /**
+     * 修改岗位
+     */
+    @SaCheckPermission("system:post:edit")
+    @Log(title = "岗位管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysPost post) {
+        if (!postService.checkPostNameUnique(post)) {
+            return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        } else if (!postService.checkPostCodeUnique(post)) {
+            return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        }
+        return toAjax(postService.updatePost(post));
+    }
+
+    /**
+     * 删除岗位
+     *
+     * @param postIds 岗位ID串
+     */
+    @SaCheckPermission("system:post:remove")
+    @Log(title = "岗位管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{postIds}")
+    public R<Void> remove(@PathVariable Long[] postIds) {
+        return toAjax(postService.deletePostByIds(postIds));
+    }
+
+    /**
+     * 获取岗位选择框列表
+     */
+    @GetMapping("/optionselect")
+    public R<List<SysPost>> optionselect() {
+        List<SysPost> posts = postService.selectPostAll();
+        return R.ok(posts);
+    }
+}

+ 126 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java

@@ -0,0 +1,126 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.secure.BCrypt;
+import cn.hutool.core.io.FileUtil;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.MimeTypeUtils;
+import com.ruoyi.system.domain.SysOss;
+import com.ruoyi.system.domain.vo.SysOssVo;
+import com.ruoyi.system.service.ISysOssService;
+import com.ruoyi.system.service.ISysUserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 个人信息 业务处理
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/user/profile")
+public class SysProfileController extends BaseController {
+
+    private final ISysUserService userService;
+    private final ISysOssService iSysOssService;
+
+    /**
+     * 个人信息
+     */
+    @GetMapping
+    public R<Map<String, Object>> profile() {
+        SysUser user = userService.selectUserById(getUserId());
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("user", user);
+        ajax.put("roleGroup", userService.selectUserRoleGroup(user.getUserName()));
+        ajax.put("postGroup", userService.selectUserPostGroup(user.getUserName()));
+        return R.ok(ajax);
+    }
+
+    /**
+     * 修改用户
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> updateProfile(@RequestBody SysUser user) {
+        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
+        }
+        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        user.setUserId(getUserId());
+        user.setUserName(null);
+        user.setPassword(null);
+        user.setAvatar(null);
+        user.setDeptId(null);
+        if (userService.updateUserProfile(user) > 0) {
+            return R.ok();
+        }
+        return R.fail("修改个人信息异常,请联系管理员");
+    }
+
+    /**
+     * 重置密码
+     *
+     * @param newPassword 旧密码
+     * @param oldPassword 新密码
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping("/updatePwd")
+    public R<Void> updatePwd(String oldPassword, String newPassword) {
+        SysUser user = userService.selectUserById(LoginHelper.getUserId());
+        String userName = user.getUserName();
+        String password = user.getPassword();
+        if (!BCrypt.checkpw(oldPassword, password)) {
+            return R.fail("修改密码失败,旧密码错误");
+        }
+        if (BCrypt.checkpw(newPassword, password)) {
+            return R.fail("新密码不能与旧密码相同");
+        }
+
+        if (userService.resetUserPwd(userName, BCrypt.hashpw(newPassword)) > 0) {
+            return R.ok();
+        }
+        return R.fail("修改密码异常,请联系管理员");
+    }
+
+    /**
+     * 头像上传
+     *
+     * @param avatarfile 用户头像
+     */
+    @Log(title = "用户头像", businessType = BusinessType.UPDATE)
+    @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Map<String, Object>> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) {
+        Map<String, Object> ajax = new HashMap<>();
+        if (!avatarfile.isEmpty()) {
+            String extension = FileUtil.extName(avatarfile.getOriginalFilename());
+            if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
+                return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
+            }
+            SysOssVo oss = iSysOssService.upload(avatarfile);
+            String avatar = oss.getUrl();
+            if (userService.updateUserAvatar(getUsername(), avatar)) {
+                ajax.put("imgUrl", avatar);
+                return R.ok(ajax);
+            }
+        }
+        return R.fail("上传图片异常,请联系管理员");
+    }
+}

+ 40 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java

@@ -0,0 +1,40 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.model.RegisterBody;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.SysRegisterService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 注册验证
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class SysRegisterController extends BaseController {
+
+    private final SysRegisterService registerService;
+    private final ISysConfigService configService;
+
+    /**
+     * 用户注册
+     */
+    @SaIgnore
+    @PostMapping("/register")
+    public R<Void> register(@Validated @RequestBody RegisterBody user) {
+        if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) {
+            return R.fail("当前系统没有开启注册功能!");
+        }
+        registerService.register(user);
+        return R.ok();
+    }
+}

+ 228 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java

@@ -0,0 +1,228 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysUserRole;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.SysPermissionService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 角色信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/role")
+public class SysRoleController extends BaseController {
+
+    private final ISysRoleService roleService;
+    private final ISysUserService userService;
+    private final ISysDeptService deptService;
+    private final SysPermissionService permissionService;
+
+    /**
+     * 获取角色信息列表
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysRole> list(SysRole role, PageQuery pageQuery) {
+        return roleService.selectPageRoleList(role, pageQuery);
+    }
+
+    /**
+     * 导出角色信息列表
+     */
+    @Log(title = "角色管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:role:export")
+    @PostMapping("/export")
+    public void export(SysRole role, HttpServletResponse response) {
+        List<SysRole> list = roleService.selectRoleList(role);
+        ExcelUtil.exportExcel(list, "角色数据", SysRole.class, response);
+    }
+
+    /**
+     * 根据角色编号获取详细信息
+     *
+     * @param roleId 角色ID
+     */
+    @SaCheckPermission("system:role:query")
+    @GetMapping(value = "/{roleId}")
+    public R<SysRole> getInfo(@PathVariable Long roleId) {
+        roleService.checkRoleDataScope(roleId);
+        return R.ok(roleService.selectRoleById(roleId));
+    }
+
+    /**
+     * 新增角色
+     */
+    @SaCheckPermission("system:role:add")
+    @Log(title = "角色管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        if (!roleService.checkRoleNameUnique(role)) {
+            return R.fail("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        } else if (!roleService.checkRoleKeyUnique(role)) {
+            return R.fail("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+        return toAjax(roleService.insertRole(role));
+
+    }
+
+    /**
+     * 修改保存角色
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        if (!roleService.checkRoleNameUnique(role)) {
+            return R.fail("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        } else if (!roleService.checkRoleKeyUnique(role)) {
+            return R.fail("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+
+        if (roleService.updateRole(role) > 0) {
+            roleService.cleanOnlineUserByRole(role.getRoleId());
+            return R.ok();
+        }
+        return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
+    }
+
+    /**
+     * 修改保存数据权限
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/dataScope")
+    public R<Void> dataScope(@RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        return toAjax(roleService.authDataScope(role));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysRole role) {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        return toAjax(roleService.updateRoleStatus(role));
+    }
+
+    /**
+     * 删除角色
+     *
+     * @param roleIds 角色ID串
+     */
+    @SaCheckPermission("system:role:remove")
+    @Log(title = "角色管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{roleIds}")
+    public R<Void> remove(@PathVariable Long[] roleIds) {
+        return toAjax(roleService.deleteRoleByIds(roleIds));
+    }
+
+    /**
+     * 获取角色选择框列表
+     */
+    @SaCheckPermission("system:role:query")
+    @GetMapping("/optionselect")
+    public R<List<SysRole>> optionselect() {
+        return R.ok(roleService.selectRoleAll());
+    }
+
+    /**
+     * 查询已分配用户角色列表
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping("/authUser/allocatedList")
+    public TableDataInfo<SysUser> allocatedList(SysUser user, PageQuery pageQuery) {
+        return userService.selectAllocatedList(user, pageQuery);
+    }
+
+    /**
+     * 查询未分配用户角色列表
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping("/authUser/unallocatedList")
+    public TableDataInfo<SysUser> unallocatedList(SysUser user, PageQuery pageQuery) {
+        return userService.selectUnallocatedList(user, pageQuery);
+    }
+
+    /**
+     * 取消授权用户
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancel")
+    public R<Void> cancelAuthUser(@RequestBody SysUserRole userRole) {
+        return toAjax(roleService.deleteAuthUser(userRole));
+    }
+
+    /**
+     * 批量取消授权用户
+     *
+     * @param roleId  角色ID
+     * @param userIds 用户ID串
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancelAll")
+    public R<Void> cancelAuthUserAll(Long roleId, Long[] userIds) {
+        return toAjax(roleService.deleteAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 批量选择用户授权
+     *
+     * @param roleId  角色ID
+     * @param userIds 用户ID串
+     */
+    @SaCheckPermission("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/selectAll")
+    public R<Void> selectAuthUserAll(Long roleId, Long[] userIds) {
+        roleService.checkRoleDataScope(roleId);
+        return toAjax(roleService.insertAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 获取对应角色部门树列表
+     *
+     * @param roleId 角色ID
+     */
+    @SaCheckPermission("system:role:list")
+    @GetMapping(value = "/deptTree/{roleId}")
+    public R<Map<String, Object>> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
+        ajax.put("depts", deptService.selectDeptTreeList(new SysDept()));
+        return R.ok(ajax);
+    }
+}

+ 249 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java

@@ -0,0 +1,249 @@
+package com.ruoyi.web.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.secure.BCrypt;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.PageQuery;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.excel.ExcelResult;
+import com.ruoyi.common.helper.LoginHelper;
+import com.ruoyi.common.utils.StreamUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.vo.SysUserExportVo;
+import com.ruoyi.system.domain.vo.SysUserImportVo;
+import com.ruoyi.system.listener.SysUserImportListener;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysPostService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 用户信息
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/user")
+public class SysUserController extends BaseController {
+
+    private final ISysUserService userService;
+    private final ISysRoleService roleService;
+    private final ISysPostService postService;
+    private final ISysDeptService deptService;
+
+    /**
+     * 获取用户列表
+     */
+    @SaCheckPermission("system:user:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysUser> list(SysUser user, PageQuery pageQuery) {
+        return userService.selectPageUserList(user, pageQuery);
+    }
+
+    /**
+     * 导出用户列表
+     */
+    @Log(title = "用户管理", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("system:user:export")
+    @PostMapping("/export")
+    public void export(SysUser user, HttpServletResponse response) {
+        List<SysUser> list = userService.selectUserList(user);
+        List<SysUserExportVo> listVo = BeanUtil.copyToList(list, SysUserExportVo.class);
+        for (int i = 0; i < list.size(); i++) {
+            SysDept dept = list.get(i).getDept();
+            SysUserExportVo vo = listVo.get(i);
+            if (ObjectUtil.isNotEmpty(dept)) {
+                vo.setDeptName(dept.getDeptName());
+                vo.setLeader(dept.getLeader());
+            }
+        }
+        ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response);
+    }
+
+    /**
+     * 导入数据
+     *
+     * @param file          导入文件
+     * @param updateSupport 是否更新已存在数据
+     */
+    @Log(title = "用户管理", businessType = BusinessType.IMPORT)
+    @SaCheckPermission("system:user:import")
+    @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
+        ExcelResult<SysUserImportVo> result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport));
+        return R.ok(result.getAnalysis());
+    }
+
+    /**
+     * 获取导入模板
+     */
+    @PostMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", SysUserImportVo.class, response);
+    }
+
+    /**
+     * 根据用户编号获取详细信息
+     *
+     * @param userId 用户ID
+     */
+    @SaCheckPermission("system:user:query")
+    @GetMapping(value = {"/", "/{userId}"})
+    public R<Map<String, Object>> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
+        userService.checkUserDataScope(userId);
+        Map<String, Object> ajax = new HashMap<>();
+        List<SysRole> roles = roleService.selectRoleAll();
+        ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        ajax.put("posts", postService.selectPostAll());
+        if (ObjectUtil.isNotNull(userId)) {
+            SysUser sysUser = userService.selectUserById(userId);
+            ajax.put("user", sysUser);
+            ajax.put("postIds", postService.selectPostListByUserId(userId));
+            ajax.put("roleIds", StreamUtils.toList(sysUser.getRoles(), SysRole::getRoleId));
+        }
+        return R.ok(ajax);
+    }
+
+    /**
+     * 新增用户
+     */
+    @SaCheckPermission("system:user:add")
+    @Log(title = "用户管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody SysUser user) {
+        if (!userService.checkUserNameUnique(user)) {
+            return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
+        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+            return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
+        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+            return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        user.setPassword(BCrypt.hashpw(user.getPassword()));
+        return toAjax(userService.insertUser(user));
+    }
+
+    /**
+     * 修改用户
+     */
+    @SaCheckPermission("system:user:edit")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@Validated @RequestBody SysUser user) {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        if (!userService.checkUserNameUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
+        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
+        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
+            return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        return toAjax(userService.updateUser(user));
+    }
+
+    /**
+     * 删除用户
+     *
+     * @param userIds 角色ID串
+     */
+    @SaCheckPermission("system:user:remove")
+    @Log(title = "用户管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public R<Void> remove(@PathVariable Long[] userIds) {
+        if (ArrayUtil.contains(userIds, getUserId())) {
+            return R.fail("当前用户不能删除");
+        }
+        return toAjax(userService.deleteUserByIds(userIds));
+    }
+
+    /**
+     * 重置密码
+     */
+    @SaCheckPermission("system:user:resetPwd")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resetPwd")
+    public R<Void> resetPwd(@RequestBody SysUser user) {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        user.setPassword(BCrypt.hashpw(user.getPassword()));
+        return toAjax(userService.resetPwd(user));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:user:edit")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysUser user) {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        return toAjax(userService.updateUserStatus(user));
+    }
+
+    /**
+     * 根据用户编号获取授权角色
+     *
+     * @param userId 用户ID
+     */
+    @SaCheckPermission("system:user:query")
+    @GetMapping("/authRole/{userId}")
+    public R<Map<String, Object>> authRole(@PathVariable Long userId) {
+        SysUser user = userService.selectUserById(userId);
+        List<SysRole> roles = roleService.selectRolesByUserId(userId);
+        Map<String, Object> ajax = new HashMap<>();
+        ajax.put("user", user);
+        ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        return R.ok(ajax);
+    }
+
+    /**
+     * 用户授权角色
+     *
+     * @param userId  用户Id
+     * @param roleIds 角色ID串
+     */
+    @SaCheckPermission("system:user:edit")
+    @Log(title = "用户管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authRole")
+    public R<Void> insertAuthRole(Long userId, Long[] roleIds) {
+        userService.checkUserDataScope(userId);
+        userService.insertUserAuth(userId, roleIds);
+        return R.ok();
+    }
+
+    /**
+     * 获取部门树列表
+     */
+    @SaCheckPermission("system:user:list")
+    @GetMapping("/deptTree")
+    public R<List<Tree<Long>>> deptTree(SysDept dept) {
+        return R.ok(deptService.selectDeptTreeList(dept));
+    }
+
+}

+ 186 - 0
ruoyi-admin/src/main/resources/application-dev.yml

@@ -0,0 +1,186 @@
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: true
+  url: http://localhost:9090/admin
+  instance:
+    service-host-type: IP
+  username: ruoyi
+  password: 123456
+
+--- # xxl-job 配置
+xxl.job:
+  # 执行器开关
+  enabled: true
+  # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
+  admin-addresses: http://localhost:9100/xxl-job-admin
+  # 执行器通讯TOKEN:非空时启用
+  access-token: xxl-job
+  executor:
+    # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
+    appname: xxl-job-executor
+    # 执行器端口号 执行器从9101开始往后写
+    port: 9101
+    # 执行器注册:默认IP:PORT
+    address:
+    # 执行器IP:默认自动获取IP
+    ip:
+    # 执行器运行日志文件存储磁盘路径
+    logpath: ./logs/xxl-job
+    # 执行器日志文件保存天数:大于3生效
+    logretentiondays: 30
+
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+    dynamic:
+      # 性能分析插件(有性能损耗 不建议生产环境使用)
+      p6spy: true
+      # 设置默认的数据源或者数据源组,默认值即为 master
+      primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
+      datasource:
+        # 主库数据源
+        master:
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
+          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
+          url: jdbc:mysql://localhost:3306/bailian-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username: root
+          password: Aa@1230
+        # 从库数据源
+        slave:
+          lazy: true
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://localhost:3306/bailian-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username:
+          password:
+#        oracle:
+#          type: ${spring.datasource.type}
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//localhost:1521/XE
+#          username: ROOT
+#          password: root
+#          hikari:
+#            connectionTestQuery: SELECT 1 FROM DUAL
+#        postgres:
+#          type: ${spring.datasource.type}
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          type: ${spring.datasource.type}
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
+      hikari:
+        # 最大连接池数量
+        maxPoolSize: 20
+        # 最小空闲线程数量
+        minIdle: 10
+        # 配置获取连接等待超时的时间
+        connectionTimeout: 30000
+        # 校验超时时间
+        validationTimeout: 5000
+        # 空闲连接存活最大时间,默认10分钟
+        idleTimeout: 600000
+        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+        maxLifetime: 1800000
+        # 连接测试query(配置检测连接是否有效)
+        connectionTestQuery: SELECT 1
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
+
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
+spring:
+  redis:
+    # 地址
+    host: localhost
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 0
+    # 密码(如没有密码请注释掉)
+    # password:
+    # 连接超时时间
+    timeout: 10s
+    # 是否开启ssl
+    ssl: false
+
+redisson:
+  # redis key前缀
+  keyPrefix:
+  # 线程池数量
+  threads: 4
+  # Netty线程池数量
+  nettyThreads: 8
+  # 单节点配置
+  singleServerConfig:
+    # 客户端名称
+    clientName: ${ruoyi.name}
+    # 最小空闲连接数
+    connectionMinimumIdleSize: 8
+    # 连接池大小
+    connectionPoolSize: 32
+    # 连接空闲超时,单位:毫秒
+    idleConnectionTimeout: 10000
+    # 命令等待超时,单位:毫秒
+    timeout: 3000
+    # 发布和订阅连接池大小
+    subscriptionConnectionPoolSize: 50
+
+--- # mail 邮件发送
+mail:
+  enabled: false
+  host: smtp.163.com
+  port: 465
+  # 是否需要用户名密码验证
+  auth: true
+  # 发送方,遵循RFC-822标准
+  from: xxx@163.com
+  # 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
+  user: xxx@163.com
+  # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
+  pass: xxxxxxxxxx
+  # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
+  starttlsEnable: true
+  # 使用SSL安全连接
+  sslEnable: true
+  # SMTP超时时长,单位毫秒,缺省值不超时
+  timeout: 0
+  # Socket连接超时值,单位毫秒,缺省值不超时
+  connectionTimeout: 0
+
+--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
+# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
+sms:
+  # 阿里云 dysmsapi.aliyuncs.com
+  alibaba:
+    #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
+    requestUrl: dysmsapi.aliyuncs.com
+    #阿里云的accessKey
+    accessKeyId: xxxxxxx
+    #阿里云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+  tencent:
+    #请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
+    requestUrl: sms.tencentcloudapi.com
+    #腾讯云的accessKey
+    accessKeyId: xxxxxxx
+    #腾讯云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+    #短信sdkAppId
+    sdkAppId: appid
+    #地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
+    territory: ap-guangzhou

+ 189 - 0
ruoyi-admin/src/main/resources/application-prod.yml

@@ -0,0 +1,189 @@
+--- # 临时文件存储位置 避免临时文件被系统清理报错
+spring.servlet.multipart.location: /ruoyi/server/temp
+
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: true
+  url: http://localhost:9090/admin
+  instance:
+    service-host-type: IP
+  username: ruoyi
+  password: 123456
+
+--- # xxl-job 配置
+xxl.job:
+  # 执行器开关
+  enabled: true
+  # 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
+  admin-addresses: http://localhost:9100/xxl-job-admin
+  # 执行器通讯TOKEN:非空时启用
+  access-token: xxl-job
+  executor:
+    # 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
+    appname: xxl-job-executor
+    # 执行器端口号 执行器从9101开始往后写
+    port: 9101
+    # 执行器注册:默认IP:PORT
+    address:
+    # 执行器IP:默认自动获取IP
+    ip:
+    # 执行器运行日志文件存储磁盘路径
+    logpath: ./logs/xxl-job
+    # 执行器日志文件保存天数:大于3生效
+    logretentiondays: 30
+
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+    dynamic:
+      # 性能分析插件(有性能损耗 不建议生产环境使用)
+      p6spy: false
+      # 设置默认的数据源或者数据源组,默认值即为 master
+      primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
+      datasource:
+        # 主库数据源
+        master:
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
+          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username: root
+          password: root
+        # 从库数据源
+        slave:
+          lazy: true
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+          username:
+          password:
+#        oracle:
+#          type: ${spring.datasource.type}
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//localhost:1521/XE
+#          username: ROOT
+#          password: root
+#          hikari:
+#            connectionTestQuery: SELECT 1 FROM DUAL
+#        postgres:
+#          type: ${spring.datasource.type}
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          type: ${spring.datasource.type}
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
+      hikari:
+        # 最大连接池数量
+        maxPoolSize: 20
+        # 最小空闲线程数量
+        minIdle: 10
+        # 配置获取连接等待超时的时间
+        connectionTimeout: 30000
+        # 校验超时时间
+        validationTimeout: 5000
+        # 空闲连接存活最大时间,默认10分钟
+        idleTimeout: 600000
+        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+        maxLifetime: 1800000
+        # 连接测试query(配置检测连接是否有效)
+        connectionTestQuery: SELECT 1
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
+
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
+spring:
+  redis:
+    # 地址
+    host: localhost
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 0
+    # 密码(如没有密码请注释掉)
+    # password:
+    # 连接超时时间
+    timeout: 10s
+    # 是否开启ssl
+    ssl: false
+
+redisson:
+  # redis key前缀
+  keyPrefix:
+  # 线程池数量
+  threads: 16
+  # Netty线程池数量
+  nettyThreads: 32
+  # 单节点配置
+  singleServerConfig:
+    # 客户端名称
+    clientName: ${ruoyi.name}
+    # 最小空闲连接数
+    connectionMinimumIdleSize: 32
+    # 连接池大小
+    connectionPoolSize: 64
+    # 连接空闲超时,单位:毫秒
+    idleConnectionTimeout: 10000
+    # 命令等待超时,单位:毫秒
+    timeout: 3000
+    # 发布和订阅连接池大小
+    subscriptionConnectionPoolSize: 50
+
+--- # mail 邮件发送
+mail:
+  enabled: false
+  host: smtp.163.com
+  port: 465
+  # 是否需要用户名密码验证
+  auth: true
+  # 发送方,遵循RFC-822标准
+  from: xxx@163.com
+  # 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
+  user: xxx@163.com
+  # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
+  pass: xxxxxxxxxx
+  # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
+  starttlsEnable: true
+  # 使用SSL安全连接
+  sslEnable: true
+  # SMTP超时时长,单位毫秒,缺省值不超时
+  timeout: 0
+  # Socket连接超时值,单位毫秒,缺省值不超时
+  connectionTimeout: 0
+
+--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
+# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
+sms:
+  # 阿里云 dysmsapi.aliyuncs.com
+  alibaba:
+    #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
+    requestUrl: dysmsapi.aliyuncs.com
+    #阿里云的accessKey
+    accessKeyId: xxxxxxx
+    #阿里云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+  tencent:
+    #请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
+    requestUrl: sms.tencentcloudapi.com
+    #腾讯云的accessKey
+    accessKeyId: xxxxxxx
+    #腾讯云的accessKeySecret
+    accessKeySecret: xxxxxxx
+    #短信签名
+    signature: 测试
+    #短信sdkAppId
+    sdkAppId: appid
+    #地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
+    territory: ap-guangzhou

+ 270 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -0,0 +1,270 @@
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: RuoYi-Vue-Plus
+  # 版本
+  version: ${ruoyi-vue-plus.version}
+  # 版权年份
+  copyrightYear: 2022
+  # 实例演示开关
+  demoEnabled: true
+  # 获取ip地址开关
+  addressEnabled: true
+  # 缓存懒加载
+  cacheLazy: false
+
+captcha:
+  # 页面 <参数设置> 可开启关闭 验证码校验
+  # 验证码类型 math 数组计算 char 字符验证
+  type: MATH
+  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
+  category: CIRCLE
+  # 数字验证码位数
+  numberLength: 1
+  # 字符验证码长度
+  charLength: 4
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 8080
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  # undertow 配置
+  undertow:
+    # HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
+    max-http-post-size: -1
+    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
+    # 每块buffer的空间大小,越小的空间被利用越充分
+    buffer-size: 512
+    # 是否分配的直接内存
+    direct-buffers: true
+    threads:
+      # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
+      io: 8
+      # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
+      worker: 256
+
+# 日志配置
+logging:
+  level:
+    com.ruoyi: @logging.level@
+    org.springframework: warn
+  config: classpath:logback-plus.xml
+
+# 用户配置
+user:
+  password:
+    # 密码最大错误次数
+    maxRetryCount: 5
+    # 密码锁定时间(默认10分钟)
+    lockTime: 10
+
+# Spring配置
+spring:
+  application:
+    name: ${ruoyi.name}
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: i18n/messages
+  profiles:
+    active: @profiles.active@
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size: 10MB
+      # 设置总上传的文件大小
+      max-request-size: 20MB
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
+  mvc:
+    format:
+      date-time: yyyy-MM-dd HH:mm:ss
+  jackson:
+    # 日期格式化
+    date-format: yyyy-MM-dd HH:mm:ss
+    serialization:
+      # 格式化输出
+      indent_output: false
+      # 忽略无法转换的对象
+      fail_on_empty_beans: false
+    deserialization:
+      # 允许对象忽略json中不存在的属性
+      fail_on_unknown_properties: false
+
+# Sa-Token配置
+sa-token:
+  # token名称 (同时也是cookie名称)
+  token-name: Authorization
+  # token有效期 设为一天 (必定过期) 单位: 秒
+  timeout: 86400
+  # 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
+  # token最低活跃时间 (指定时间无操作就过期) 单位: 秒
+  active-timeout: 1800
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # 是否尝试从header里读取token
+  is-read-header: true
+  # 是否尝试从cookie里读取token
+  is-read-cookie: false
+  # token前缀
+  token-prefix: "Bearer"
+  # jwt秘钥
+  jwt-secret-key: abcdefghijklmnopqrstuvwxyz
+
+# security配置
+security:
+  # 排除路径
+  excludes:
+    # 静态资源
+    - /*.html
+    - /**/*.html
+    - /**/*.css
+    - /**/*.js
+    # 公共路径
+    - /favicon.ico
+    - /error
+    # swagger 文档配置
+    - /*/api-docs
+    - /*/api-docs/**
+    # actuator 监控配置
+    - /actuator
+    - /actuator/**
+
+# MyBatisPlus配置
+# https://baomidou.com/config/
+mybatis-plus:
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
+  # 例如 com.**.**.mapper
+  mapperPackage: com.ruoyi.**.mapper
+  # 对应的 XML 文件位置
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 实体扫描,多个package用逗号或者分号分隔
+  typeAliasesPackage: com.ruoyi.**.domain
+  # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
+  checkConfigLocation: false
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    mapUnderscoreToCamelCase: true
+    # MyBatis 自动映射策略
+    # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
+    autoMappingBehavior: PARTIAL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    autoMappingUnknownColumnBehavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+  global-config:
+    # 是否打印 Logo banner
+    banner: true
+    dbConfig:
+      # 主键类型
+      # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
+      idType: ASSIGN_ID
+      # 逻辑已删除值
+      logicDeleteValue: 2
+      # 逻辑未删除值
+      logicNotDeleteValue: 0
+      # 字段验证策略之 insert,在 insert 的时候的字段验证策略
+      # IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
+      insertStrategy: NOT_NULL
+      # 字段验证策略之 update,在 update 的时候的字段验证策略
+      updateStrategy: NOT_NULL
+      # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
+      where-strategy: NOT_NULL
+
+# 数据加密
+mybatis-encryptor:
+  # 是否开启加密
+  enable: false
+  # 默认加密算法
+  algorithm: BASE64
+  # 编码方式 BASE64/HEX。默认BASE64
+  encode: BASE64
+  # 安全秘钥 对称算法的秘钥 如:AES,SM4
+  password:
+  # 公私钥 非对称算法的公私钥 如:SM2,RSA
+  publicKey:
+  privateKey:
+
+springdoc:
+  api-docs:
+    # 是否开启接口文档
+    enabled: true
+#  swagger-ui:
+#    # 持久化认证数据
+#    persistAuthorization: true
+  info:
+    # 标题
+    title: '标题:${ruoyi.name}后台管理系统_接口文档'
+    # 描述
+    description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
+    # 版本
+    version: '版本号: ${ruoyi-vue-plus.version}'
+    # 作者信息
+    contact:
+      name: Lion Li
+      email: crazylionli@163.com
+      url: https://gitee.com/dromara/RuoYi-Vue-Plus
+  components:
+    # 鉴权方式配置
+    security-schemes:
+      apiKey:
+        type: APIKEY
+        in: HEADER
+        name: ${sa-token.token-name}
+  #这里定义了两个分组,可定义多个,也可以不定义
+  group-configs:
+    - group: 1.演示模块
+      packages-to-scan: com.ruoyi.demo
+    - group: 2.系统模块
+      packages-to-scan: com.ruoyi.web
+    - group: 3.代码生成模块
+      packages-to-scan: com.ruoyi.generator
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /system/notice
+  # 匹配链接
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 全局线程池相关配置
+thread-pool:
+  # 是否开启线程池
+  enabled: false
+  # 队列最大长度
+  queueCapacity: 128
+  # 线程池维护线程所允许的空闲时间
+  keepAliveSeconds: 300
+
+--- # 分布式锁 lock4j 全局配置
+lock4j:
+  # 获取分布式锁超时时间,默认为 3000 毫秒
+  acquire-timeout: 3000
+  # 分布式锁的超时时间,默认为 30 秒
+  expire: 30000
+
+--- # Actuator 监控端点的配置项
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS
+    logfile:
+      external-file: ./logs/sys-console.log

+ 8 - 0
ruoyi-admin/src/main/resources/banner.txt

@@ -0,0 +1,8 @@
+Application Version: ${ruoyi-vue-plus.version}
+Spring Boot Version: ${spring-boot.version}
+__________            _____.___.__         ____   ____                     __________.__
+\______   \__ __  ____\__  |   |__|        \   \ /   /_ __   ____          \______   \  |  __ __  ______
+ |       _/  |  \/  _ \/   |   |  |  ______ \   Y   /  |  \_/ __ \   ______ |     ___/  | |  |  \/  ___/
+ |    |   \  |  (  <_> )____   |  | /_____/  \     /|  |  /\  ___/  /_____/ |    |   |  |_|  |  /\___ \
+ |____|_  /____/ \____// ______|__|           \___/ |____/  \___  >         |____|   |____/____//____  >
+        \/             \/                                       \/                                   \/

+ 49 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -0,0 +1,49 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+user.password.not.blank=用户密码不能为空
+user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
+user.password.not.valid=* 5-50个字符
+user.email.not.valid=邮箱格式错误
+user.email.not.blank=邮箱不能为空
+user.phonenumber.not.blank=用户手机号不能为空
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
+email.code.not.blank=邮箱验证码不能为空
+email.code.retry.limit.count=邮箱验证码输入错误{0}次
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
+xcx.code.not.blank=小程序code不能为空

+ 49 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -0,0 +1,49 @@
+#错误消息
+not.null=* Required fill in
+user.jcaptcha.error=Captcha error
+user.jcaptcha.expire=Captcha invalid
+user.not.exists=Sorry, your account: {0} does not exist
+user.password.not.match=User does not exist/Password error
+user.password.retry.limit.count=Password input error {0} times
+user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
+user.password.delete=Sorry, your account:{0} has been deleted
+user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
+role.blocked=Role disabled,please contact administrators
+user.logout.success=Exit successful
+length.not.valid=The length must be between {min} and {max} characters
+user.username.not.blank=Username cannot be blank
+user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
+user.username.length.valid=Account length must be between {min} and {max} characters
+user.password.not.blank=Password cannot be empty
+user.password.length.valid=Password length must be between {min} and {max} characters
+user.password.not.valid=* 5-50 characters
+user.email.not.valid=Mailbox format error
+user.email.not.blank=Mailbox cannot be blank
+user.phonenumber.not.blank=Phone number cannot be blank
+user.mobile.phone.number.not.valid=Phone number format error
+user.login.success=Login successful
+user.register.success=Register successful
+user.register.save.error=Failed to save user {0}, The registered account already exists
+user.register.error=Register failed, please contact system administrator
+user.notfound=Please login again
+user.forcelogout=The administrator is forced to exit,please login again
+user.unknown.error=Unknown error, please login again
+##文件上传消息
+upload.exceed.maxSize=The uploaded file size exceeds the limit file size!<br/>the maximum allowed file size is:{0}MB!
+upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
+##权限
+no.permission=You do not have permission to the data,please contact your administrator to add permissions [{0}]
+no.create.permission=You do not have permission to create data,please contact your administrator to add permissions [{0}]
+no.update.permission=You do not have permission to modify data,please contact your administrator to add permissions [{0}]
+no.delete.permission=You do not have permission to delete data,please contact your administrator to add permissions [{0}]
+no.export.permission=You do not have permission to export data,please contact your administrator to add permissions [{0}]
+no.view.permission=You do not have permission to view data,please contact your administrator to add permissions [{0}]
+repeat.submit.message=Repeat submit is not allowed, please try again later
+rate.limiter.message=Visit too frequently, please try again later
+sms.code.not.blank=Sms code cannot be blank
+sms.code.retry.limit.count=Sms code input error {0} times
+sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
+email.code.not.blank=Email code cannot be blank
+email.code.retry.limit.count=Email code input error {0} times
+email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
+xcx.code.not.blank=Mini program code cannot be blank

+ 49 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -0,0 +1,49 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+user.password.not.blank=用户密码不能为空
+user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
+user.password.not.valid=* 5-50个字符
+user.email.not.valid=邮箱格式错误
+user.email.not.blank=邮箱不能为空
+user.phonenumber.not.blank=用户手机号不能为空
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
+email.code.not.blank=邮箱验证码不能为空
+email.code.retry.limit.count=邮箱验证码输入错误{0}次
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
+xcx.code.not.blank=小程序code不能为空

BIN
ruoyi-admin/src/main/resources/ip2region.xdb


+ 129 - 0
ruoyi-admin/src/main/resources/logback-plus.xml

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <property name="log.path" value="./logs"/>
+    <property name="console.log.pattern"
+              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${console.log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- 控制台输出 -->
+    <appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-console.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大 1天 -->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <!-- 系统日志输出 -->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- info异步输出 -->
+    <appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_info"/>
+    </appender>
+
+    <!-- error异步输出 -->
+    <appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_error"/>
+    </appender>
+
+    <!-- 整合 skywalking 控制台输出 tid -->
+<!--    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">-->
+<!--        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!--            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!--                <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!--            </layout>-->
+<!--            <charset>utf-8</charset>-->
+<!--        </encoder>-->
+<!--    </appender>-->
+
+    <!-- 整合 skywalking 推送采集日志 -->
+<!--    <appender name="sky_log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">-->
+<!--        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!--            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!--                <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!--            </layout>-->
+<!--            <charset>utf-8</charset>-->
+<!--        </encoder>-->
+<!--    </appender>-->
+
+    <!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="console" />
+        <appender-ref ref="async_info" />
+        <appender-ref ref="async_error" />
+        <appender-ref ref="file_console" />
+<!--        <appender-ref ref="sky_log"/>-->
+    </root>
+
+</configuration>

+ 28 - 0
ruoyi-admin/src/main/resources/spy.properties

@@ -0,0 +1,28 @@
+# p6spy 性能分析插件配置文件
+modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
+# 自定义日志打印
+logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
+#日志输出到控制台
+appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
+# 使用日志系统记录 sql
+#appender=com.p6spy.engine.spy.appender.Slf4JLogger
+# 设置 p6spy driver 代理
+#deregisterdrivers=true
+# 取消JDBC URL前缀
+useprefix=true
+# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
+excludecategories=info,debug,result,commit,resultset
+# 日期格式
+dateformat=yyyy-MM-dd HH:mm:ss
+# SQL语句打印时间格式
+databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
+# 实际驱动可多个
+#driverlist=org.h2.Driver
+# 是否开启慢SQL记录
+outagedetection=true
+# 慢SQL记录标准 2 秒
+outagedetectioninterval=2
+# 是否过滤 Log
+filter=true
+# 过滤 Log 时所排除的 sql 关键字,以逗号分隔
+exclude=SELECT 1

+ 45 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/AssertUnitTest.java

@@ -0,0 +1,45 @@
+package com.ruoyi.test;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * 断言单元测试案例
+ *
+ * @author Lion Li
+ */
+@DisplayName("断言单元测试案例")
+public class AssertUnitTest {
+
+    @DisplayName("测试 assertEquals 方法")
+    @Test
+    public void testAssertEquals() {
+        Assertions.assertEquals("666", new String("666"));
+        Assertions.assertNotEquals("666", new String("666"));
+    }
+
+    @DisplayName("测试 assertSame 方法")
+    @Test
+    public void testAssertSame() {
+        Object obj = new Object();
+        Object obj1 = obj;
+        Assertions.assertSame(obj, obj1);
+        Assertions.assertNotSame(obj, obj1);
+    }
+
+    @DisplayName("测试 assertTrue 方法")
+    @Test
+    public void testAssertTrue() {
+        Assertions.assertTrue(true);
+        Assertions.assertFalse(true);
+    }
+
+    @DisplayName("测试 assertNull 方法")
+    @Test
+    public void testAssertNull() {
+        Assertions.assertNull(null);
+        Assertions.assertNotNull(null);
+    }
+
+}

+ 70 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/DemoUnitTest.java

@@ -0,0 +1,70 @@
+package com.ruoyi.test;
+
+import com.ruoyi.common.config.RuoYiConfig;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 单元测试案例
+ *
+ * @author Lion Li
+ */
+@SpringBootTest // 此注解只能在 springboot 主包下使用 需包含 main 方法与 yml 配置文件
+@DisplayName("单元测试案例")
+public class DemoUnitTest {
+
+    @Autowired
+    private RuoYiConfig ruoYiConfig;
+
+    @DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
+    @Test
+    public void testTest() {
+        System.out.println(ruoYiConfig);
+    }
+
+    @Disabled
+    @DisplayName("测试 @Disabled 注解")
+    @Test
+    public void testDisabled() {
+        System.out.println(ruoYiConfig);
+    }
+
+    @Timeout(value = 2L, unit = TimeUnit.SECONDS)
+    @DisplayName("测试 @Timeout 注解")
+    @Test
+    public void testTimeout() throws InterruptedException {
+        Thread.sleep(3000);
+        System.out.println(ruoYiConfig);
+    }
+
+
+    @DisplayName("测试 @RepeatedTest 注解")
+    @RepeatedTest(3)
+    public void testRepeatedTest() {
+        System.out.println(666);
+    }
+
+    @BeforeAll
+    public static void testBeforeAll() {
+        System.out.println("@BeforeAll ==================");
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+    @AfterAll
+    public static void testAfterAll() {
+        System.out.println("@AfterAll ==================");
+    }
+
+}

+ 72 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/ParamUnitTest.java

@@ -0,0 +1,72 @@
+package com.ruoyi.test;
+
+import com.ruoyi.common.enums.UserType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * 带参数单元测试案例
+ *
+ * @author Lion Li
+ */
+@DisplayName("带参数单元测试案例")
+public class ParamUnitTest {
+
+    @DisplayName("测试 @ValueSource 注解")
+    @ParameterizedTest
+    @ValueSource(strings = {"t1", "t2", "t3"})
+    public void testValueSource(String str) {
+        System.out.println(str);
+    }
+
+    @DisplayName("测试 @NullSource 注解")
+    @ParameterizedTest
+    @NullSource
+    public void testNullSource(String str) {
+        System.out.println(str);
+    }
+
+    @DisplayName("测试 @EnumSource 注解")
+    @ParameterizedTest
+    @EnumSource(UserType.class)
+    public void testEnumSource(UserType type) {
+        System.out.println(type.getUserType());
+    }
+
+    @DisplayName("测试 @MethodSource 注解")
+    @ParameterizedTest
+    @MethodSource("getParam")
+    public void testMethodSource(String str) {
+        System.out.println(str);
+    }
+
+    public static Stream<String> getParam() {
+        List<String> list = new ArrayList<>();
+        list.add("t1");
+        list.add("t2");
+        list.add("t3");
+        return list.stream();
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+
+}

+ 54 - 0
ruoyi-admin/src/test/java/com/ruoyi/test/TagUnitTest.java

@@ -0,0 +1,54 @@
+package com.ruoyi.test;
+
+import org.junit.jupiter.api.*;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 标签单元测试案例
+ *
+ * @author Lion Li
+ */
+@SpringBootTest
+@DisplayName("标签单元测试案例")
+public class TagUnitTest {
+
+    @Tag("dev")
+    @DisplayName("测试 @Tag dev")
+    @Test
+    public void testTagDev() {
+        System.out.println("dev");
+    }
+
+    @Tag("prod")
+    @DisplayName("测试 @Tag prod")
+    @Test
+    public void testTagProd() {
+        System.out.println("prod");
+    }
+
+    @Tag("local")
+    @DisplayName("测试 @Tag local")
+    @Test
+    public void testTagLocal() {
+        System.out.println("local");
+    }
+
+    @Tag("exclude")
+    @DisplayName("测试 @Tag exclude")
+    @Test
+    public void testTagExclude() {
+        System.out.println("exclude");
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+
+}

+ 170 - 0
ruoyi-common/pom.xml

@@ -0,0 +1,170 @@
+<?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">
+    <parent>
+        <artifactId>Bailian-Al</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>4.8.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common</artifactId>
+
+    <description>
+        common通用工具
+    </description>
+
+    <dependencies>
+
+        <!-- Spring框架基本的核心工具 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+
+        <!-- SpringWeb模块 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+        <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+        </dependency>
+        <!-- Sa-Token 整合 jwt -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-jwt</artifactId>
+        </dependency>
+
+        <!-- 自定义验证注解 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!--常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- JSON工具类 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
+
+        <!-- yml解析器 -->
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>
+
+        <!-- servlet包 -->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <!-- dynamic-datasource 多数据源-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-captcha</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-jwt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-extra</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-webmvc-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-javadoc</artifactId>
+        </dependency>
+
+        <!--  自动生成YML配置关联JSON文件  -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+
+        <!--redisson-->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-data-27</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 加密包引入 -->
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15to18</artifactId>
+        </dependency>
+
+        <!-- 离线IP地址定位库 -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 24 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/CellMerge.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.excel.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 列单元格合并(合并列相同项)
+ *
+ * 需搭配 {@link CellMergeStrategy} 策略使用
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+	/**
+	 * col index
+	 */
+	int index() default -1;
+
+}

+ 28 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataColumn.java

@@ -0,0 +1,28 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限
+ *
+ * 一个注解只能对应一个模板
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataColumn {
+
+    /**
+     * 占位符关键字
+     */
+    String[] key() default "deptName";
+
+    /**
+     * 占位符替换值
+     */
+    String[] value() default "dept_id";
+
+}

+ 18 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataPermission.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限组
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+    DataColumn[] value();
+
+}

+ 29 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DictDataMapper.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.jackson.DictDataJsonSerializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 字典数据映射注解
+ *
+ * @author itino
+ * @deprecated 建议使用通用翻译注解
+ */
+@Deprecated
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@JacksonAnnotationsInside
+@JsonSerialize(using = DictDataJsonSerializer.class)
+public @interface DictDataMapper {
+
+    /**
+     * 设置字典的type值 (如: sys_user_sex)
+     */
+    String dictType() default "";
+}

+ 44 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/EncryptField.java

@@ -0,0 +1,44 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.AlgorithmType;
+import com.ruoyi.common.enums.EncodeType;
+
+import java.lang.annotation.*;
+
+/**
+ * 字段加密注解
+ *
+ * @author 老马
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptField {
+
+    /**
+     * 加密算法
+     */
+    AlgorithmType algorithm() default AlgorithmType.DEFAULT;
+
+    /**
+     * 秘钥。AES、SM4需要
+     */
+    String password() default "";
+
+    /**
+     * 公钥。RSA、SM2需要
+     */
+    String publicKey() default "";
+
+    /**
+     * 私钥。RSA、SM2需要
+     */
+    String privateKey() default "";
+
+    /**
+     * 编码方式。对加密算法为BASE64的不起作用
+     */
+    EncodeType encode() default EncodeType.DEFAULT;
+
+}

+ 32 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelDictFormat.java

@@ -0,0 +1,32 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 字典格式化
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+    /**
+     * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+     */
+    String dictType() default "";
+
+    /**
+     * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+     */
+    String readConverterExp() default "";
+
+    /**
+     * 分隔符,读取字符串组内容
+     */
+    String separator() default StringUtils.SEPARATOR;
+
+}

+ 30 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/ExcelEnumFormat.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 枚举格式化
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+    /**
+     * 字典枚举类型
+     */
+    Class<? extends Enum<?>> enumClass();
+
+    /**
+     * 字典枚举类中对应的code属性名称,默认为code
+     */
+    String codeField() default "code";
+
+    /**
+     * 字典枚举类中对应的text属性名称,默认为text
+     */
+    String textField() default "text";
+
+}

+ 47 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java

@@ -0,0 +1,47 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.enums.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义操作日志记录注解
+ *
+ * @author ruoyi
+ */
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log {
+    /**
+     * 模块
+     */
+    String title() default "";
+
+    /**
+     * 功能
+     */
+    BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 操作人类别
+     */
+    OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 是否保存请求的参数
+     */
+    boolean isSaveRequestData() default true;
+
+    /**
+     * 是否保存响应的参数
+     */
+    boolean isSaveResponseData() default true;
+
+    /**
+     * 排除指定的请求参数
+     */
+    String[] excludeParamNames() default {};
+
+}

+ 41 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java

@@ -0,0 +1,41 @@
+package com.ruoyi.common.annotation;
+
+import com.ruoyi.common.enums.LimitType;
+
+import java.lang.annotation.*;
+
+/**
+ * 限流注解
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RateLimiter {
+    /**
+     * 限流key,支持使用Spring el表达式来动态获取方法上的参数值
+     * 格式类似于  #code.id #{#code}
+     */
+    String key() default "";
+
+    /**
+     * 限流时间,单位秒
+     */
+    int time() default 60;
+
+    /**
+     * 限流次数
+     */
+    int count() default 100;
+
+    /**
+     * 限流类型
+     */
+    LimitType limitType() default LimitType.DEFAULT;
+
+    /**
+     * 提示消息 支持国际化 格式为 {code}
+     */
+    String message() default "{rate.limiter.message}";
+}

+ 29 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 自定义注解防止表单重复提交
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit {
+
+    /**
+     * 间隔时间(ms),小于此时间视为重复提交
+     */
+    int interval() default 5000;
+
+    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
+
+    /**
+     * 提示消息 支持国际化 格式为 {code}
+     */
+    String message() default "{repeat.submit.message}";
+
+}

+ 24 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.enums.SensitiveStrategy;
+import com.ruoyi.common.jackson.SensitiveJsonSerializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 数据脱敏注解
+ *
+ * @author zhujie
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveJsonSerializer.class)
+public @interface Sensitive {
+    SensitiveStrategy strategy();
+}

+ 39 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Translation.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.translation.handler.TranslationHandler;
+
+import java.lang.annotation.*;
+
+/**
+ * 通用翻译注解
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Documented
+@JacksonAnnotationsInside
+@JsonSerialize(using = TranslationHandler.class)
+public @interface Translation {
+
+    /**
+     * 类型 (需与实现类上的 {@link com.ruoyi.common.annotation.TranslationType} 注解type对应)
+     * <p>
+     * 默认取当前字段的值 如果设置了 @{@link Translation#mapper()} 则取映射字段的值
+     */
+    String type();
+
+    /**
+     * 映射字段 (如果不为空则取此字段的值)
+     */
+    String mapper() default "";
+
+    /**
+     * 其他条件 例如: 字典type(sys_user_sex)
+     */
+    String other() default "";
+
+}

+ 21 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/TranslationType.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 翻译类型注解 (标注到{@link com.ruoyi.common.translation.TranslationInterface} 的实现类)
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+public @interface TranslationType {
+
+    /**
+     * 类型
+     */
+    String type();
+
+}

+ 85 - 0
ruoyi-common/src/main/java/com/ruoyi/common/captcha/UnsignedMathGenerator.java

@@ -0,0 +1,85 @@
+package com.ruoyi.common.captcha;
+
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.core.math.Calculator;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 无符号计算生成器
+ *
+ * @author Lion Li
+ */
+public class UnsignedMathGenerator implements CodeGenerator {
+
+    private static final long serialVersionUID = -5514819971774091076L;
+
+    private static final String OPERATORS = "+-*";
+
+    /**
+     * 参与计算数字最大长度
+     */
+    private final int numberLength;
+
+    /**
+     * 构造
+     */
+    public UnsignedMathGenerator() {
+        this(2);
+    }
+
+    /**
+     * 构造
+     *
+     * @param numberLength 参与计算最大数字位数
+     */
+    public UnsignedMathGenerator(int numberLength) {
+        this.numberLength = numberLength;
+    }
+
+    @Override
+    public String generate() {
+        final int limit = getLimit();
+        int a = RandomUtil.randomInt(limit);
+        int b = RandomUtil.randomInt(limit);
+        String max = Integer.toString(Math.max(a,b));
+        String min = Integer.toString(Math.min(a,b));
+        max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE);
+        min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE);
+
+        return max + RandomUtil.randomChar(OPERATORS) + min + '=';
+    }
+
+    @Override
+    public boolean verify(String code, String userInputCode) {
+        int result;
+        try {
+            result = Integer.parseInt(userInputCode);
+        } catch (NumberFormatException e) {
+            // 用户输入非数字
+            return false;
+        }
+
+        final int calculateResult = (int) Calculator.conversion(code);
+        return result == calculateResult;
+    }
+
+    /**
+     * 获取验证码长度
+     *
+     * @return 验证码长度
+     */
+    public int getLength() {
+        return this.numberLength * 2 + 2;
+    }
+
+    /**
+     * 根据长度获取参与计算数字最大值
+     *
+     * @return 最大值
+     */
+    private int getLimit() {
+        return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength));
+    }
+}

+ 54 - 0
ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java

@@ -0,0 +1,54 @@
+package com.ruoyi.common.config;
+
+import lombok.Data;
+import lombok.Getter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ *
+ * @author Lion Li
+ */
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "ruoyi")
+public class RuoYiConfig {
+
+    /**
+     * 项目名称
+     */
+    private String name;
+
+    /**
+     * 版本
+     */
+    private String version;
+
+    /**
+     * 版权年份
+     */
+    private String copyrightYear;
+
+    /**
+     * 实例演示开关
+     */
+    private boolean demoEnabled;
+
+    /**
+     * 缓存懒加载
+     */
+    private boolean cacheLazy;
+
+    /**
+     * 获取地址开关
+     */
+    @Getter
+    private static boolean addressEnabled;
+
+    public void setAddressEnabled(boolean addressEnabled) {
+        RuoYiConfig.addressEnabled = addressEnabled;
+    }
+
+}

+ 44 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java

@@ -0,0 +1,44 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 缓存的key 常量
+ *
+ * @author ruoyi
+ */
+public interface CacheConstants {
+
+    /**
+     * 在线用户 redis key
+     */
+    String ONLINE_TOKEN_KEY = "online_tokens:";
+
+    /**
+     * 验证码 redis key
+     */
+    String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 参数管理 cache key
+     */
+    String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 防重提交 redis key
+     */
+    String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 限流 redis key
+     */
+    String RATE_LIMIT_KEY = "rate_limit:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików