Ver código fonte

first commit

cocoKnight 1 ano atrás
commit
fd0fb24960
55 arquivos alterados com 4251 adições e 0 exclusões
  1. 38 0
      .gitignore
  2. 8 0
      .idea/.gitignore
  3. 19 0
      .idea/encodings.xml
  4. 14 0
      .idea/misc.xml
  5. 124 0
      .idea/uiDesigner.xml
  6. 0 0
      README.md
  7. 101 0
      app/pom.xml
  8. 24 0
      app/src/main/java/com/dongzili/App01Start.java
  9. 158 0
      app/src/main/resources/application-dev.yml
  10. 5 0
      app/src/main/resources/application.yml
  11. 100 0
      app02/pom.xml
  12. 24 0
      app02/src/main/java/com/dongzili/App02Start.java
  13. 158 0
      app02/src/main/resources/application-dev.yml
  14. 5 0
      app02/src/main/resources/application.yml
  15. 102 0
      auth/pom.xml
  16. 99 0
      auth/src/main/java/com/dongzili/auth/controller/AuthController.java
  17. 24 0
      auth/src/main/java/com/dongzili/auth/controller/RoleController.java
  18. 96 0
      auth/src/main/java/com/dongzili/auth/controller/UserController.java
  19. 58 0
      auth/src/main/java/com/dongzili/auth/service/UserService.java
  20. 9 0
      auth/src/main/java/com/dongzili/auth/vo/LoginDto.java
  21. 100 0
      data/pom.xml
  22. 127 0
      data/src/main/java/com/dongzili/data/base/BaseController.java
  23. 59 0
      data/src/main/java/com/dongzili/data/init/DBInitConfig.java
  24. 63 0
      data/src/main/java/com/dongzili/data/mybatisplus/CustomizePaginationInterceptor.java
  25. 36 0
      data/src/main/java/com/dongzili/data/mybatisplus/JsonTypeHandler.java
  26. 40 0
      data/src/main/java/com/dongzili/data/mybatisplus/MybatisPlusConfig.java
  27. 172 0
      data/src/main/java/com/dongzili/data/mybatisplus/MybatisPlusMapperConfig.java
  28. 71 0
      data/src/main/java/com/dongzili/data/mybatisplus/MybatisPlusUtil.java
  29. 27 0
      data/src/main/java/com/dongzili/data/mybatisplus/PageTotalCache.java
  30. 47 0
      data/src/main/java/com/dongzili/data/utils/BCryptUtils.java
  31. 799 0
      data/src/main/java/com/dongzili/data/utils/LocalDateUtils.java
  32. 96 0
      data/src/main/java/com/dongzili/data/utils/RandomUtils.java
  33. 326 0
      data/src/main/java/com/dongzili/data/utils/ReflectUtils.java
  34. 17 0
      data/src/main/resources/sqlite.sql
  35. 60 0
      entity/pom.xml
  36. 20 0
      entity/src/main/java/com/dongzili/entity/Role.java
  37. 39 0
      entity/src/main/java/com/dongzili/entity/User.java
  38. 144 0
      entity/src/main/java/com/dongzili/entity/Users.java
  39. 10 0
      entity/src/main/java/com/dongzili/entity/vo/UpdatePwdVo.java
  40. 11 0
      entity/src/main/java/com/dongzili/enums/EnumDesc.java
  41. 41 0
      entity/src/main/java/com/dongzili/enums/SexEnum.java
  42. 38 0
      entity/src/main/java/com/dongzili/enums/UserStatusEnum.java
  43. 30 0
      entity/src/main/java/com/dongzili/vo/OrderType.java
  44. 13 0
      entity/src/main/java/com/dongzili/vo/OrderVo.java
  45. 46 0
      entity/src/main/java/com/dongzili/vo/PageRequest.java
  46. 27 0
      entity/src/main/java/com/dongzili/vo/QueryPeriodVo.java
  47. 116 0
      entity/src/main/java/com/dongzili/vo/R.java
  48. 69 0
      entity/src/main/java/com/dongzili/vo/ResponseCode.java
  49. 115 0
      gateway/pom.xml
  50. 30 0
      gateway/src/main/java/com/dongzili/GatewayApplication.java
  51. 23 0
      gateway/src/main/java/com/dongzili/config/GatewayConfig.java
  52. 85 0
      gateway/src/main/java/com/dongzili/config/SatokenPredicateFactory.java
  53. 90 0
      gateway/src/main/resources/application.yml
  54. 98 0
      pom.xml
  55. BIN
      visit.db

+ 38 - 0
.gitignore

@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 19 - 0
.idea/encodings.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/app/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/app/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/app02/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/app02/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/auth/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/auth/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/data/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/data/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/entity/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/entity/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/gateway/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/gateway/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+  </component>
+</project>

+ 14 - 0
.idea/misc.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8 (3)" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>

+ 124 - 0
.idea/uiDesigner.xml

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+</project>

+ 0 - 0
README.md


+ 101 - 0
app/pom.xml

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.dongzili</groupId>
+        <artifactId>springcloud-base</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>app</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.dongzili</groupId>
+            <artifactId>data</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.dongzili</groupId>
+            <artifactId>auth</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+            <version>${spring-cloud-alibaba.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.cloud</groupId>
+                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!--   loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器  -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+
+
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.dongzili.App01Start</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+        </plugins>
+    </build>
+</project>

+ 24 - 0
app/src/main/java/com/dongzili/App01Start.java

@@ -0,0 +1,24 @@
+package com.dongzili;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.RestTemplate;
+
+@EnableDiscoveryClient
+@SpringBootApplication
+public class App01Start {
+
+    public static void main(String[] args) {
+        SpringApplication.run(App01Start.class, args);
+    }
+
+    @Bean
+    @LoadBalanced
+    public RestTemplate restTemplate(RestTemplateBuilder builder) {
+        return builder.build();
+    }
+}

+ 158 - 0
app/src/main/resources/application-dev.yml

@@ -0,0 +1,158 @@
+server:
+  port: 8098
+
+  servlet:
+    context-path: /api
+    multipart:
+      max-file-size: 2000MB
+      max-request-size: 2000MB
+  tomcat:
+    max-swallow-size: 5000MB #重要的一行,修改tomcat的吞吐量
+
+  compression:
+    #是否对响应数据开启gzip压缩,默认false
+    enabled: true
+    #响应内容长度超过设置大小时进行压缩,默认值为2048(2KB,不带单位时默认为字节)
+    min-response-size: 10KB
+    #对指定的响应类型进行压缩,值是数组,用逗号隔开
+    mime-types: application/json,application/xml,text/html,text/xml,text/plain
+
+
+spring:
+  thymeleaf:
+    cache: false
+    mode: HTML
+    encoding: UTF-8
+    prefix: classpath:/templates/
+    suffix: .html
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 2000MB
+      max-request-size: 2000MB
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      maximum-pool-size: 10
+      minimum-idle: 5
+      idle-timeout: 30000
+      max-lifetime: 1800000
+      connection-timeout: 30000
+      connection-test-query: SELECT 1
+    dynamic:
+      primary: main #设置默认的数据源或者数据源组,默认值即为master
+      strict: true #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
+      datasource:
+        main:
+          url: jdbc:sqlite:./visit.db # 改为数据库路径
+          driver-class-name: org.sqlite.JDBC
+        xnyy:
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          #    url: jdbc:mysql://127.0.0.1:3306/wlxt-log?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
+          url: jdbc:mysql://127.0.0.1:3308/xnyy-edu?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: root
+
+
+  # 配置业务使用的 Redis 连接
+  redis:
+    # Redis数据库索引(默认为0)
+    database: 7
+    # Redis服务器地址
+    host: 127.0.0.1
+    # Redis服务器连接端口
+    port: 6379
+    # Redis服务器连接密码(默认为空)
+    password:
+    # 连接超时时间
+    timeout: 10s
+    jedis:
+      pool:
+        max-idle: 0
+        max-active: 32
+        max-wait: 100
+        min-idle: 4
+  cloud:
+    nacos:
+      discovery:
+        # 开启nacos作为服务注册中心,默认值:true
+        enabled: true
+        # nacos集群服务注册地址
+        server-addr: 127.0.0.1:8848
+        # nacos注册地址命名空间
+        namespace: public
+        # nacos用户名
+        username: nacos
+        # nacos密码
+        password: nacos
+        # 命名空间,默认 public,可设置dev,pro等,相同特征的服务分类,先去nacos命名空间创建
+        #        namespace: public
+        # 分组,默认 DEFAULT_GROUP 相同特征的服务划分的更细
+        group: DEFAULT_GROUP
+        # 临时实例,默认true,false永久实例,即使宕机也不会从nacos服务中删除,可应对雪崩保护,避免服务被删除
+        ephemeral: true
+        # 权重 1-100 数值越大权重越大分配的流量就越大,通常结合权重负载均衡策略
+        weight: 100
+
+
+# mybatis 相关 configurations
+mybatis-plus:
+  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
+  mapper-locations: classpath:mapper/*.xml
+  # 以下配置均有默认值,可以不设置
+  global-config:
+    db-config:
+      # 主键类型,大小写不敏感 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"
+      id-type: auto
+  configuration:
+    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
+    #    mapUnderscoreToCamelCase: false
+    jdbc-type-for-null: 'NULL'
+    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
+    call-setters-on-nulls: true
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    map-underscore-to-camel-case: false
+
+
+# Sa-Token配置
+sa-token:
+  # jwt秘钥
+  jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
+  is-read-header: true
+  is-read-cookie: false
+  # token前缀
+  # Token前缀 与 Token值 之间必须有一个空格。
+  # 一旦配置了 Token前缀,则前端提交 Token 时,必须带有前缀,否则会导致框架无法读取 Token。
+  # 由于Cookie中无法存储空格字符,也就意味配置 Token 前缀后,Cookie 鉴权方式将会失效,此时只能将 Token 提交到header里进行传输
+  #  token-prefix: Bearer
+  # token 名称 (同时也是cookie名称)
+  token-name: satoken
+  #  token-name: Authorization
+  #  token-name: satoken
+  # token 有效期,单位s 默认30天, -1代表永不过期
+  timeout: 2592000
+  #  timeout: 86400
+  #  timeout: 60
+  # token 临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
+  active-timeout: 86400
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # token风格
+  token-style: uuid
+  # 是否输出操作日志
+  is-log: true
+  alone-redis:
+
+    # Redis数据库索引(默认为0)
+    database: 8
+    # Redis服务器地址
+    host: 127.0.0.1
+    # Redis服务器连接端口
+    port: 6379
+    # Redis服务器连接密码(默认为空)
+    password:
+    # 连接超时时间
+    timeout: 10s

+ 5 - 0
app/src/main/resources/application.yml

@@ -0,0 +1,5 @@
+spring:
+  profiles:
+    active: dev
+  application:
+    name: scb-demo-01

+ 100 - 0
app02/pom.xml

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.dongzili</groupId>
+        <artifactId>springcloud-base</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>app02</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+
+        <dependency>
+            <groupId>com.dongzili</groupId>
+            <artifactId>data</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.dongzili</groupId>
+            <artifactId>auth</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+            <version>${spring-cloud-alibaba.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.cloud</groupId>
+                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!--   loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器  -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+
+
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.dongzili.App02Start</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+        </plugins>
+    </build>
+</project>

+ 24 - 0
app02/src/main/java/com/dongzili/App02Start.java

@@ -0,0 +1,24 @@
+package com.dongzili;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.RestTemplate;
+
+@EnableDiscoveryClient
+@SpringBootApplication
+public class App02Start {
+
+    public static void main(String[] args) {
+        SpringApplication.run(App02Start.class, args);
+    }
+
+    @Bean
+    @LoadBalanced
+    public RestTemplate restTemplate(RestTemplateBuilder builder) {
+        return builder.build();
+    }
+}

+ 158 - 0
app02/src/main/resources/application-dev.yml

@@ -0,0 +1,158 @@
+server:
+  port: 8099
+
+  servlet:
+    context-path: /api
+    multipart:
+      max-file-size: 2000MB
+      max-request-size: 2000MB
+  tomcat:
+    max-swallow-size: 5000MB #重要的一行,修改tomcat的吞吐量
+
+  compression:
+    #是否对响应数据开启gzip压缩,默认false
+    enabled: true
+    #响应内容长度超过设置大小时进行压缩,默认值为2048(2KB,不带单位时默认为字节)
+    min-response-size: 10KB
+    #对指定的响应类型进行压缩,值是数组,用逗号隔开
+    mime-types: application/json,application/xml,text/html,text/xml,text/plain
+
+
+spring:
+  thymeleaf:
+    cache: false
+    mode: HTML
+    encoding: UTF-8
+    prefix: classpath:/templates/
+    suffix: .html
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 2000MB
+      max-request-size: 2000MB
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      maximum-pool-size: 10
+      minimum-idle: 5
+      idle-timeout: 30000
+      max-lifetime: 1800000
+      connection-timeout: 30000
+      connection-test-query: SELECT 1
+    dynamic:
+      primary: main #设置默认的数据源或者数据源组,默认值即为master
+      strict: true #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
+      datasource:
+        main:
+          url: jdbc:sqlite:./visit.db # 改为数据库路径
+          driver-class-name: org.sqlite.JDBC
+        xnyy:
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          #    url: jdbc:mysql://127.0.0.1:3306/wlxt-log?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
+          url: jdbc:mysql://127.0.0.1:3308/xnyy-edu?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: root
+
+
+  # 配置业务使用的 Redis 连接
+  redis:
+    # Redis数据库索引(默认为0)
+    database: 7
+    # Redis服务器地址
+    host: 127.0.0.1
+    # Redis服务器连接端口
+    port: 6379
+    # Redis服务器连接密码(默认为空)
+    password:
+    # 连接超时时间
+    timeout: 10s
+    jedis:
+      pool:
+        max-idle: 0
+        max-active: 32
+        max-wait: 100
+        min-idle: 4
+  cloud:
+    nacos:
+      discovery:
+        # 开启nacos作为服务注册中心,默认值:true
+        enabled: true
+        # nacos集群服务注册地址
+        server-addr: 127.0.0.1:8848
+        # nacos注册地址命名空间
+        namespace: public
+        # nacos用户名
+        username: nacos
+        # nacos密码
+        password: nacos
+        # 命名空间,默认 public,可设置dev,pro等,相同特征的服务分类,先去nacos命名空间创建
+        #        namespace: public
+        # 分组,默认 DEFAULT_GROUP 相同特征的服务划分的更细
+        group: DEFAULT_GROUP
+        # 临时实例,默认true,false永久实例,即使宕机也不会从nacos服务中删除,可应对雪崩保护,避免服务被删除
+        ephemeral: true
+        # 权重 1-100 数值越大权重越大分配的流量就越大,通常结合权重负载均衡策略
+        weight: 100
+
+
+# mybatis 相关 configurations
+mybatis-plus:
+  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
+  mapper-locations: classpath:mapper/*.xml
+  # 以下配置均有默认值,可以不设置
+  global-config:
+    db-config:
+      # 主键类型,大小写不敏感 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"
+      id-type: auto
+  configuration:
+    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
+    #    mapUnderscoreToCamelCase: false
+    jdbc-type-for-null: 'NULL'
+    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
+    call-setters-on-nulls: true
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    map-underscore-to-camel-case: false
+
+
+# Sa-Token配置
+sa-token:
+  # jwt秘钥
+  jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
+  is-read-header: true
+  is-read-cookie: false
+  # token前缀
+  # Token前缀 与 Token值 之间必须有一个空格。
+  # 一旦配置了 Token前缀,则前端提交 Token 时,必须带有前缀,否则会导致框架无法读取 Token。
+  # 由于Cookie中无法存储空格字符,也就意味配置 Token 前缀后,Cookie 鉴权方式将会失效,此时只能将 Token 提交到header里进行传输
+  #  token-prefix: Bearer
+  # token 名称 (同时也是cookie名称)
+  token-name: satoken
+  #  token-name: Authorization
+  #  token-name: satoken
+  # token 有效期,单位s 默认30天, -1代表永不过期
+  timeout: 2592000
+  #  timeout: 86400
+  #  timeout: 60
+  # token 临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
+  active-timeout: 86400
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # token风格
+  token-style: uuid
+  # 是否输出操作日志
+  is-log: true
+  alone-redis:
+
+    # Redis数据库索引(默认为0)
+    database: 8
+    # Redis服务器地址
+    host: 127.0.0.1
+    # Redis服务器连接端口
+    port: 6379
+    # Redis服务器连接密码(默认为空)
+    password:
+    # 连接超时时间
+    timeout: 10s

+ 5 - 0
app02/src/main/resources/application.yml

@@ -0,0 +1,5 @@
+spring:
+  profiles:
+    active: dev
+  application:
+    name: scb-demo-02

+ 102 - 0
auth/pom.xml

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.dongzili</groupId>
+        <artifactId>springcloud-base</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>auth</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <sa-token.version>1.44.0</sa-token.version>
+    </properties>
+    <dependencies>
+
+        <dependency>
+            <groupId>com.dongzili</groupId>
+            <artifactId>data</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+            <version>${sa-token.version}</version>
+        </dependency>
+        <!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-redis-jackson</artifactId>
+            <version>${sa-token.version}</version>
+        </dependency>
+        <!-- Sa-Token插件:权限缓存与业务缓存分离 -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-alone-redis</artifactId>
+            <version>${sa-token.version}</version>
+        </dependency>
+        <!-- jedis-->
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
+        <!-- 提供Redis连接池 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+            <version>${spring-cloud-alibaba.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.cloud</groupId>
+                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!--   loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器  -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+
+
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+
+</project>

+ 99 - 0
auth/src/main/java/com/dongzili/auth/controller/AuthController.java

@@ -0,0 +1,99 @@
+package com.dongzili.auth.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dongzili.auth.service.UserService;
+import com.dongzili.auth.vo.LoginDto;
+import com.dongzili.entity.User;
+import com.dongzili.data.mybatisplus.MybatisPlusUtil;
+import com.dongzili.data.utils.BCryptUtils;
+import com.dongzili.vo.R;
+import com.dongzili.vo.ResponseCode;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+
+    /**
+     * 登录接口
+     * @param loginDto
+     * @return
+     */
+    @PostMapping("/login")
+    public R<?> login(@RequestBody LoginDto loginDto){
+
+        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(User::getUsername,loginDto.getUsername());
+        User user = MybatisPlusUtil.getMapper(User.class).selectOne(queryWrapper, false);
+        if (user != null && BCryptUtils.checkByBCrypt(loginDto.getPassword(), user.getPassword())){
+            UserService.fillUser(user);
+            StpUtil.login(user.getId());
+            StpUtil.getSession().set("user", user);
+            return R.success(user, StpUtil.getTokenInfo().tokenValue);
+        }
+        return R.error(ResponseCode.USERNAME_OR_PASSWORD_ERROR);
+    }
+
+    @GetMapping("/logout")
+    public R<?> logout(){
+        StpUtil.logout();
+        return R.success();
+    }
+
+//    /** 用户列表 */
+//    @PostMapping("/list")
+//    public R<?> list(@RequestBody PageRequest<User> request) {
+//        LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>(request.getEntity());
+//        if (request.getLikeEntity() != null && StringUtils.hasText(request.getLikeEntity().getUsername())){
+//            query.like(User::getUsername, request.getLikeEntity().getUsername());
+//        }
+//        IPage<User> pageData = MybatisPlusUtil.getMapper(User.class).selectPage(new Page<>(request.getCurrent(), request.getSize()), query);
+//        UserService.fillUsers(pageData.getRecords());
+//        return R.success(pageData.getRecords(), pageData.getTotal());
+//    }
+//
+//    /** 添加用户 */
+//    @PostMapping("/add")
+//    public R<?> add(@RequestBody User user){
+//        if (!StringUtils.hasText(user.getPassword()) || !StringUtils.hasText(user.getUsername())){
+//            R.error(ResponseCode.PARAM_ERROR);
+//        }
+//        user.setId(null);
+//        if (user.getRoleIds() == null || user.getRoleIds().isEmpty()){
+//            user.setRoleIds(Collections.singletonList(1001));
+//        }
+//        String encryptPassword = BCryptUtils.encryptByBCrypt(user.getPassword());
+//        user.setPassword(encryptPassword);
+//        return R.success(MybatisPlusUtil.getMapper(User.class).insert(user));
+//    }
+//
+//    /** 更新用户信息 */
+//    @PostMapping("/update")
+//    public R<?> update(@RequestBody User user){
+//        if (user.getId() == null){
+//            R.error(ResponseCode.PARAM_ERROR);
+//        }
+//        user.setPassword(null);
+//        return R.success(MybatisPlusUtil.getMapper(User.class).updateById(user));
+//    }
+//
+//    /** 更新密码 */
+//    @PostMapping("/updatePwd")
+//    public R<?> updatePwd(@RequestBody User user){
+//        if (!StringUtils.hasText(user.getPassword())){
+//            R.error(ResponseCode.PARAM_ERROR);
+//        }
+//        user.setId(StpUtil.getLoginIdAsInt());
+//        String encryptPassword = BCryptUtils.encryptByBCrypt(user.getPassword());
+//        user.setPassword(encryptPassword);
+//        return R.success(MybatisPlusUtil.getMapper(User.class).updateById(user));
+//    }
+//
+//    /** 根据id删除 */
+//    @GetMapping("/delete/{id}")
+//    public R<?> delete(@PathVariable("id") Integer id){
+//        return R.success(MybatisPlusUtil.getMapper(User.class).deleteById(id));
+//    }
+}

+ 24 - 0
auth/src/main/java/com/dongzili/auth/controller/RoleController.java

@@ -0,0 +1,24 @@
+package com.dongzili.auth.controller;
+
+import com.dongzili.data.base.BaseController;
+import com.dongzili.entity.Role;
+import com.dongzili.data.mybatisplus.MybatisPlusUtil;
+import com.dongzili.vo.R;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 角色控制器
+ */
+@RestController
+@RequestMapping("role")
+public class RoleController extends BaseController<Role> {
+
+    @GetMapping("all")
+    public R<List<Role>> all(){
+        return R.success(MybatisPlusUtil.getMapper(Role.class).selectList(null));
+    }
+}

+ 96 - 0
auth/src/main/java/com/dongzili/auth/controller/UserController.java

@@ -0,0 +1,96 @@
+package com.dongzili.auth.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.stp.StpUtil;
+import com.dongzili.auth.service.UserService;
+import com.dongzili.data.base.BaseController;
+import com.dongzili.entity.User;
+import com.dongzili.entity.vo.UpdatePwdVo;
+import com.dongzili.data.mybatisplus.MybatisPlusUtil;
+import com.dongzili.data.utils.BCryptUtils;
+import com.dongzili.vo.PageRequest;
+import com.dongzili.vo.R;
+import com.dongzili.vo.ResponseCode;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collections;
+import java.util.List;
+
+@RestController
+@RequestMapping("user")
+public class UserController extends BaseController<User> {
+
+    @SaCheckPermission(orRole = {"super-admin"})
+    @Override
+    public R<Integer> add(@RequestBody User entity) {
+        if (!StringUtils.hasText(entity.getPassword()) || !StringUtils.hasText(entity.getUsername())){
+            R.error(ResponseCode.PARAM_ERROR);
+        }
+        entity.setId(null);
+        if (entity.getRoleIds() == null || entity.getRoleIds().isEmpty()){
+            entity.setRoleIds(Collections.singletonList(1001));
+        }
+        String encryptPassword = BCryptUtils.encryptByBCrypt(entity.getPassword());
+        entity.setPassword(encryptPassword);
+        return super.add(entity);
+    }
+
+    /**
+     * 根据ID更新用户信息,包括密码,权限:super-admin
+     * @param entity
+     * @return
+     */
+    @SaCheckPermission(orRole = {"super-admin"})
+    @Override
+    public R<Integer> update(@RequestBody User entity) {
+        if (StringUtils.hasText(entity.getPassword())){
+            String encryptPassword = BCryptUtils.encryptByBCrypt(entity.getPassword());
+            entity.setPassword(encryptPassword);
+        }
+        return super.update(entity);
+    }
+
+    /** 更新登录者的密码 */
+    @PostMapping("/updatePwd")
+    public R<Integer> updatePwd(@RequestBody UpdatePwdVo pwd){
+        if (!StringUtils.hasText(pwd.getNewPassword()) || !StringUtils.hasText(pwd.getOldPassword())){
+            R.error(ResponseCode.PARAM_ERROR);
+        }
+        Integer userId = StpUtil.getLoginIdAsInt();
+        User user = MybatisPlusUtil.getMapper(User.class).selectById(userId);
+        if (!BCryptUtils.checkByBCrypt(pwd.getOldPassword(), user.getPassword())){
+            return R.error("旧密码错误");
+        }
+        String encryptPassword = BCryptUtils.encryptByBCrypt(pwd.getNewPassword());
+        user.setPassword(encryptPassword);
+        return R.success(MybatisPlusUtil.getMapper(User.class).updateById(user));
+    }
+    /** 删除用户,权限:super-admin */
+    @SaCheckPermission(orRole = {"super-admin"})
+    @Override
+    public R<Integer> delete(@PathVariable Integer id) {
+        return super.delete(id);
+    }
+
+    @Override
+    public R<User> get(@PathVariable Integer id) {
+        R<User> r = super.get(id);
+        UserService.fillUser(r.getData());
+        return r;
+    }
+
+    /**
+     * 列表,权限:super-admin
+     * @param request
+     * @return
+     */
+    @SaCheckPermission(orRole = {"super-admin"})
+    @Override
+    public R<List<User>> list(@RequestBody PageRequest<User> request) {
+        R<List<User>> r =super.list(request);
+        UserService.fillUsers(r.getData());
+        return r;
+    }
+
+}

+ 58 - 0
auth/src/main/java/com/dongzili/auth/service/UserService.java

@@ -0,0 +1,58 @@
+package com.dongzili.auth.service;
+
+import com.dongzili.entity.Role;
+import com.dongzili.entity.User;
+import com.dongzili.data.mybatisplus.MybatisPlusUtil;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class UserService {
+
+    public static User getUserById(Integer id){
+        User user = MybatisPlusUtil.getMapper(User.class).selectById(id);
+        fillUser(user);
+        return user;
+    }
+
+    /** 填充用户角色信息 */
+    public static void fillUser(User user){
+        if (user != null){
+            if (user.getRoleIds()==null || user.getRoleIds().isEmpty()){
+                user.setRoleIds(Collections.singletonList(1001));
+            }
+            List<Role> roles = MybatisPlusUtil.getMapper(Role.class).selectBatchIds(user.getRoleIds());
+            List<String> codes = roles.stream().map(Role::getCode).distinct().collect(Collectors.toList());
+            user.setRoles(roles);
+            user.setRoleCodes(codes);
+        }
+    }
+
+    /** 批量填充用户角色信息 */
+    public static void fillUsers(List<User> users){
+        if (users == null || users.isEmpty()) {
+            return;
+        }
+        Set<Integer> roleIds = users.stream()
+                .flatMap(user -> user.getRoleIds().stream())
+                .collect(Collectors.toSet());
+        roleIds.add(1001);
+        Map<Integer, Role> roleMap =MybatisPlusUtil.getMapper(Role.class).selectBatchIds(roleIds).stream().collect(Collectors.toMap(Role::getId, role -> role));
+        users.forEach(user -> {
+            List<Role> roles = new ArrayList<>();
+            List<String> codes = new ArrayList<>();
+            if (user.getRoleIds()==null || users.isEmpty()){
+                user.setRoleIds(Collections.singletonList(1001));
+            }
+            for (Integer roleId : user.getRoleIds()) {
+                Role role = roleMap.get(roleId);
+                if (role != null){
+                    roles.add(role);
+                    codes.add(role.getCode());
+                }
+            }
+            user.setRoles(roles.stream().distinct().collect(Collectors.toList()));
+            user.setRoleCodes(codes);
+        });
+    }
+}

+ 9 - 0
auth/src/main/java/com/dongzili/auth/vo/LoginDto.java

@@ -0,0 +1,9 @@
+package com.dongzili.auth.vo;
+
+import lombok.Data;
+
+@Data
+public class LoginDto {
+    private String username;
+    private String password;
+}

+ 100 - 0
data/pom.xml

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.dongzili</groupId>
+        <artifactId>springcloud-base</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>data</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.dongzili</groupId>
+            <artifactId>entity</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>org.xerial</groupId>-->
+<!--            <artifactId>sqlite-jdbc</artifactId>-->
+<!--            <version>${sqlite.version}</version>-->
+<!--        </dependency>-->
+        <!-- sqlite -->
+        <dependency>
+            <groupId>org.xerial</groupId>
+            <artifactId>sqlite-jdbc</artifactId>
+            <version>3.41.2.2</version>
+        </dependency>
+        <!-- mybatis plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis-typehandlers-jsr310</artifactId>
+            <version>${mybatis-typehandlers.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.bytebuddy</groupId>
+            <artifactId>byte-buddy</artifactId>
+            <version>1.15.10</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <version>${dynamic-datasource.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <version>${validation.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mindrot</groupId>
+            <artifactId>jbcrypt</artifactId>
+            <version>0.4</version>
+        </dependency>
+        <!-- 提供Redis连接池 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-redis</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 127 - 0
data/src/main/java/com/dongzili/data/base/BaseController.java

@@ -0,0 +1,127 @@
+package com.dongzili.data.base;
+
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.dongzili.data.mybatisplus.MybatisPlusUtil;
+import com.dongzili.data.utils.ReflectUtils;
+import com.dongzili.vo.OrderType;
+import com.dongzili.vo.OrderVo;
+import com.dongzili.vo.PageRequest;
+import com.dongzili.vo.R;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+public class BaseController<T> {
+
+    private final Class<T> tClass;
+    protected BaseMapper<T> baseMapper;
+    public BaseController(){
+        this.tClass = resolveEntityClass();
+        DS ds = tClass.getAnnotation(DS.class);
+        if (ds != null){
+            DynamicDataSourceContextHolder.push(ds.value());
+        }else{
+            DynamicDataSourceContextHolder.push("main");
+        }
+        baseMapper = MybatisPlusUtil.getMapper(this.tClass);
+    }
+    /** 列表查询 */
+    @PostMapping("/list")
+    public R<List<T>> list(@RequestBody PageRequest<T> request) {
+        QueryWrapper<T> query = new QueryWrapper<>(request.getEntity());
+        // like 字段用 or 连接
+        Map<String, Object> fieldValueMap = ReflectUtils.getNotNullFieldValueMap(request.getLikeEntity());
+        if (!fieldValueMap.isEmpty()){
+            query.and(m -> {
+                fieldValueMap.forEach((k, v)->{
+                    if(v instanceof String){
+                        m.or().like(k, v);
+                    }
+                    else if (v instanceof List){
+                        for (Object o : (List<?>) v) {
+                            m.or().like(k, o);
+                        }
+                    }else {
+                        m.or().eq(k, v);
+                    }
+                });
+            });
+        }
+
+        // 排序字段
+        addOrderByCondition(request, query);
+
+        IPage<T> pageData = MybatisPlusUtil.getMapper(this.tClass).selectPage(new Page<>(request.getCurrent(), request.getSize()), query);
+        return R.success(pageData.getRecords(), pageData.getTotal());
+    }
+
+    /** 添加 */
+    @PostMapping("/add")
+    public R<Integer> add(@RequestBody T entity){
+        if(MybatisPlusUtil.getMapper(this.tClass).insert(entity) > 0){
+            return R.success();
+        }
+        return R.error();
+    }
+
+    /** 更新 */
+    @PostMapping("/update")
+    public R<Integer> update(@RequestBody T entity){
+        if(MybatisPlusUtil.getMapper(this.tClass).updateById(entity) > 0){
+            return R.success();
+        }
+        return R.error();
+    }
+
+    /** 根据id删除 */
+    @GetMapping("/delete/{id}")
+    public R<Integer> delete(@PathVariable("id") Integer id){
+        if(MybatisPlusUtil.getMapper(this.tClass).deleteById(id) > 0){
+            return R.success();
+        }
+        return R.error();
+    }
+    /** 根据id查询 */
+    @GetMapping("/get/{id}")
+    public R<T> get(@PathVariable("id") Integer id){
+        return R.success(MybatisPlusUtil.getMapper(this.tClass).selectById(id));
+    }
+
+    private Class<T> resolveEntityClass() {
+        Type superClass = getClass().getGenericSuperclass();
+        if (superClass instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) superClass;
+            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+            return (Class<T>) actualTypeArguments[0];
+        }
+        throw new IllegalArgumentException("Class is not parameterized with generic types");
+    }
+
+    /**
+     * 添加排序条件
+     */
+    private static <T> void addOrderByCondition(PageRequest<T> request, QueryWrapper<T> queryWrapper) {
+        if(request.getOrders()!=null && !request.getOrders().isEmpty()){
+            for (OrderVo orderVo : request.getOrders()){
+                if(OrderType.Desc.equals(orderVo.getOrderType())){
+                    queryWrapper.orderByDesc(orderVo.getFiledName());
+                }else{
+                    queryWrapper.orderByAsc(orderVo.getFiledName());
+                }
+            }
+        }
+    }
+
+}

+ 59 - 0
data/src/main/java/com/dongzili/data/init/DBInitConfig.java

@@ -0,0 +1,59 @@
+package com.dongzili.data.init;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.sql.Connection;
+import java.util.Map;
+
+@DependsOn("dataSource")
+@Configuration
+public class DBInitConfig {
+
+    @Value("${spring.datasource.dynamic.datasource.main.url}")
+    private String dbUrl;
+
+    @PostConstruct
+    public void initSqlite() throws Exception {
+
+        String dbPath = dbUrl.replaceAll("jdbc:sqlite:", "");
+        File dbFile = new File(dbPath);
+
+        // 1. 判断文件是否存在
+        if (!dbFile.exists()) {
+            // 2. 如果不存在,创建目录和空文件
+            if (!dbFile.getParentFile().exists()) {
+                if(dbFile.getParentFile().mkdirs()){
+                    Files.createFile(Paths.get(dbFile.toURI()));
+                }else{
+                    throw new Exception("创建数据库目录失败: "+dbFile.getParentFile().getAbsolutePath());
+                }
+            }else{
+                Files.createFile(Paths.get(dbFile.toURI()));
+            }
+        }
+        String path= dbFile.getAbsolutePath();
+        long dbSize = dbFile.length();
+        if (dbFile.length() < 16384){
+            // 2. 获取数据源
+            Map<String, DynamicRoutingDataSource> dataSourceMap = SpringUtil.getBeansOfType(DynamicRoutingDataSource.class);
+            DataSource mainDataSource = dataSourceMap.get("dataSource").getDataSources().get("main");
+            // 3. 获取连接并执行 SQL 脚本
+            try (Connection connection = mainDataSource.getConnection()) {
+                ScriptUtils.executeSqlScript(connection, new ClassPathResource("sqlite.sql"));
+
+            }
+        }
+        System.out.println("数据库初始化完毕...");
+    }
+}

+ 63 - 0
data/src/main/java/com/dongzili/data/mybatisplus/CustomizePaginationInterceptor.java

@@ -0,0 +1,63 @@
+package com.dongzili.data.mybatisplus;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.ParameterUtils;
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.apache.ibatis.cache.CacheKey;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+
+import java.sql.SQLException;
+import java.util.List;
+
+public class CustomizePaginationInterceptor extends PaginationInnerInterceptor {
+
+    public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
+        IPage<?> page = (IPage<?>)ParameterUtils.findPage(parameter).orElse(null);
+        if (page != null && page.getSize() >= 0L && page.searchCount() && resultHandler == Executor.NO_RESULT_HANDLER) {
+
+            String totalCacheKey = PageTotalCache.generateKey(ms.getId(), page.getRecords());
+            if (page.getCurrent() > 1){
+                Long cachedTotal = PageTotalCache.getTotal(totalCacheKey);
+                if (cachedTotal != null) {
+                    page.setTotal(cachedTotal); // 设置缓存的总数
+                    return true;
+                }
+            }
+
+            MappedStatement countMs = this.buildCountMappedStatement(ms, page.countId());
+            BoundSql countSql;
+            if (countMs != null) {
+                countSql = countMs.getBoundSql(parameter);
+            } else {
+                countMs = this.buildAutoCountMappedStatement(ms);
+                String countSqlStr = this.autoCountSql(page, boundSql.getSql());
+                PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
+                countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);
+                PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());
+            }
+
+            CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);
+            List<Object> result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql);
+            long total = 0L;
+            if (CollectionUtils.isNotEmpty(result)) {
+                Object o = result.get(0);
+                if (o != null) {
+                    total = Long.parseLong(o.toString());
+                }
+            }
+
+            page.setTotal(total);
+            PageTotalCache.putTotal(totalCacheKey, total);
+
+            return this.continuePage(page);
+        } else {
+            return true;
+        }
+    }
+}

+ 36 - 0
data/src/main/java/com/dongzili/data/mybatisplus/JsonTypeHandler.java

@@ -0,0 +1,36 @@
+package com.dongzili.data.mybatisplus;
+
+import cn.hutool.json.JSONUtil;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.springframework.context.annotation.Configuration;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+@Configuration
+public class JsonTypeHandler extends BaseTypeHandler<List<Integer>> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<Integer> integers, JdbcType jdbcType) throws SQLException {
+        preparedStatement.setString(i, JSONUtil.toJsonStr(integers));
+    }
+
+    @Override
+    public List<Integer> getNullableResult(ResultSet resultSet, String s) throws SQLException {
+        return JSONUtil.toList(resultSet.getString(s), Integer.class);
+    }
+
+    @Override
+    public List<Integer> getNullableResult(ResultSet resultSet, int i) throws SQLException {
+        return JSONUtil.toList(resultSet.getString(i), Integer.class);
+    }
+
+    @Override
+    public List<Integer> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
+        return JSONUtil.toList(callableStatement.getString(i), Integer.class);
+    }
+}

+ 40 - 0
data/src/main/java/com/dongzili/data/mybatisplus/MybatisPlusConfig.java

@@ -0,0 +1,40 @@
+package com.dongzili.data.mybatisplus;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 配置分页插件
+ *
+ */
+@Configuration
+@MapperScan("com.dongzili.*")
+public class MybatisPlusConfig {
+
+    /**
+     * 分页插件
+     */
+//    @Bean
+//    public PaginationInterceptor paginationInterceptor() {
+//        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
+//        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
+//        // paginationInterceptor.setOverflow(false);
+//        // 设置最大单页限制数量,默认 500 条,-1 不受限制
+//        // paginationInterceptor.setLimit(500);
+//        // 开启 count 的 join 优化,只针对部分 left join PaginationInnerInterceptor
+//        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
+//        return paginationInterceptor;
+//    }
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        CustomizePaginationInterceptor customizePaginationInterceptor = new CustomizePaginationInterceptor();
+        interceptor.addInnerInterceptor(customizePaginationInterceptor);
+        return interceptor;
+    }
+
+
+}
+

+ 172 - 0
data/src/main/java/com/dongzili/data/mybatisplus/MybatisPlusMapperConfig.java

@@ -0,0 +1,172 @@
+package com.dongzili.data.mybatisplus;
+
+import cn.hutool.core.util.ClassUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.mybatis.spring.MyBatisSystemException;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.mapper.MapperFactoryBean;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.lang.reflect.Constructor;
+import java.util.*;
+
+
+@Configuration
+@Slf4j
+@AutoConfigureAfter(SpringUtil.class)
+public class MybatisPlusMapperConfig{
+
+    public static final Map<String, Class<?>> classMap = new HashMap<>();
+    @Resource
+    private SqlSessionTemplate sqlSessionTemplate; // 使用SqlSessionTemplate代替SqlSessionFactory
+
+    private static final List<String> basePackages = Collections.singletonList(
+            "com.dongzili"
+    );
+
+    // 根据类名获取 bean name
+    private String getBeanName(String className) {
+        int index = className.lastIndexOf(".");
+        String simpleClassName = index != -1 ? className.substring(index + 1) : className;
+        char firstChar = simpleClassName.charAt(0);
+        if (firstChar >= 'A' && firstChar <= 'Z') {
+            firstChar -= (char) ('A' - 'a');
+        }
+        return firstChar + simpleClassName.substring(1);
+    }
+
+
+    @PostConstruct
+    public void setup() {
+        if (sqlSessionTemplate == null) {
+            log.error("SqlSessionTemplate 未正确注入,请检查配置。");
+            return;
+        }
+
+        for (String packageName : basePackages) {
+            if (!StringUtils.hasText(packageName)) {
+                continue;
+            }
+
+            // 扫描数据库实体,根据实体创建对应 mapper、service
+            Set<Class<?>> entityClassSet = ClassUtil.scanPackage(packageName);
+            if (entityClassSet.isEmpty()) {
+                continue;
+            }
+
+            for (Class<?> entityClass : entityClassSet) {
+                // 只创建带有TableName注解的实体
+                TableName tableName = entityClass.getAnnotation(TableName.class);
+                if (tableName == null) {
+                    continue;
+                }
+
+                classMap.put(tableName.value(), entityClass);
+                createMapperAndService(entityClass, packageName);
+            }
+        }
+    }
+
+    private void createMapperAndService(Class<?> entityClass, String packageName) {
+//        String mapperClassName = packageName + ".mapper" + "." + entityClass.getSimpleName() + "AutoMapper";
+//        String serviceImplClassName = packageName + ".service" + "." + entityClass.getSimpleName() + "AutoServiceImpl";
+//        String serviceClassName = packageName + ".service" + "." + entityClass.getSimpleName() + "AutoService";
+        String mapperClassName = entityClass.getName().replace(entityClass.getSimpleName(),"mapper") + "." + entityClass.getSimpleName() + "AutoMapper";
+        String serviceImplClassName = entityClass.getName().replace(entityClass.getSimpleName(),"service") + "." + entityClass.getSimpleName() + "AutoServiceImpl";
+        String serviceClassName = entityClass.getName().replace(entityClass.getSimpleName(),"service") + "." + entityClass.getSimpleName() + "AutoService";
+
+        log.debug("开始处理实体类: {}", entityClass.getName());
+
+        /* 创建 mapper */
+        try {
+            try (DynamicType.Unloaded<?> mapperType = new ByteBuddy()
+                    .makeInterface(TypeDescription.Generic.Builder.parameterizedType(BaseMapper.class, entityClass).build())
+                    .name(mapperClassName)
+                    .annotateType(AnnotationDescription.Builder.ofType(org.apache.ibatis.annotations.Mapper.class).build())
+                    .make()) {
+
+                Class<?> mapperClass = mapperType.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+                        .getLoaded();
+
+                MapperFactoryBean<?> factoryBean = new MapperFactoryBean<>(mapperClass);
+                factoryBean.setSqlSessionTemplate(sqlSessionTemplate);
+                sqlSessionTemplate.getConfiguration().addMapper(mapperClass);
+
+                SpringUtil.registerBean(getBeanName(mapperClassName), factoryBean.getObject());
+                log.info("自动注册 mybatisplus mapper bean -> 名称:{}, 类名:{}", getBeanName(mapperClassName), mapperClassName);
+            }
+        } catch (Exception e) {
+            if (e instanceof MyBatisSystemException) {
+                log.error("MyBatis系统异常,注册Mapper失败: {}", e.getMessage(), e);
+            } else if (e instanceof BeanCreationException) {
+                log.error("Spring Bean创建异常,注册Mapper失败: {}", e.getMessage(), e);
+            } else {
+                log.error("自动注册 mybatisplus mapper bean 失败,原因:{}", e.getMessage(), e);
+            }
+            throw new RuntimeException("自动注册 mybatisplus mapper bean 失败!" + mapperClassName, e);
+        }
+
+        /* 创建 service */
+        try {
+            try (DynamicType.Unloaded<?> serviceType = new ByteBuddy()
+                    .makeInterface(TypeDescription.Generic.Builder.parameterizedType(IService.class, entityClass).build())
+                    .name(serviceClassName)
+                    .make()) {
+
+                Class<?> serviceClass = serviceType.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+                        .getLoaded();
+
+                List<AnnotationDescription> annotations = new ArrayList<>();
+                annotations.add(AnnotationDescription.Builder.ofType(Service.class).build());
+                annotations.add(AnnotationDescription.Builder.ofType(Transactional.class).build());
+
+                try (DynamicType.Unloaded<?> serviceImplType = new ByteBuddy()
+                        .subclass(TypeDescription.Generic.Builder.parameterizedType(ServiceImpl.class,
+                                Class.forName(mapperClassName), entityClass).build())
+                        .implement(serviceClass)
+                        .name(serviceImplClassName)
+                        .annotateType(annotations)
+                        .make()) {
+
+                    Class<?> serviceImplClass = serviceImplType.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+                            .getLoaded();
+
+                    // 使用构造器创建实例,比newInstance更灵活
+                    Constructor<?> constructor = serviceImplClass.getDeclaredConstructor();
+                    constructor.setAccessible(true);
+                    SpringUtil.registerBean(getBeanName(serviceImplClassName), constructor.newInstance());
+
+                    log.info("自动注册 mybatisplus Service Bean -> 名称:{}, 类名:{}", getBeanName(serviceImplClassName), serviceImplClassName);
+                }
+            }
+        } catch (Exception e) {
+            if (e instanceof ClassNotFoundException) {
+                log.error("类未找到异常,可能是包名或类名配置错误: {}", e.getMessage(), e);
+            } else if (e instanceof NoSuchMethodException) {
+                log.error("构造器未找到异常: {}", e.getMessage(), e);
+            } else {
+                log.error("自动注册 mybatisplus Service Bean 失败,原因:{}", e.getMessage(), e);
+            }
+            throw new RuntimeException("自动注册 mybatisplus Service Bean 失败!" + serviceImplClassName, e);
+        }
+
+        log.debug("成功创建Mapper和Service: {}", entityClass.getName());
+    }
+}

+ 71 - 0
data/src/main/java/com/dongzili/data/mybatisplus/MybatisPlusUtil.java

@@ -0,0 +1,71 @@
+package com.dongzili.data.mybatisplus;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MybatisPlusUtil {
+    /**
+     * 服务缓存
+     */
+    private static final ConcurrentHashMap<Class<?>, IService<?>> SERVICE_CACHE = new ConcurrentHashMap<>();
+
+    /**
+     * Mapper缓存
+     */
+    private static final ConcurrentHashMap<Class<?>, BaseMapper<?>> MAPPER_CACHE = new ConcurrentHashMap<>();
+
+    /**
+     * 获取Service(带缓存)
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> IService<T> getService(final Class<T> t) {
+        try {
+            DS ds = t.getAnnotation(DS.class);
+            if (ds != null){
+                DynamicDataSourceContextHolder.push(ds.value());
+            }else{
+                DynamicDataSourceContextHolder.push("main");
+            }
+            return (IService<T>) SERVICE_CACHE.computeIfAbsent(t, k -> {
+                String beanName = getBeanName(t) + "AutoServiceImpl";
+                return SpringUtil.getBean(beanName);
+            });
+        } catch (NoSuchBeanDefinitionException e) {
+            throw new RuntimeException("未找到对应的Service实现: " + getBeanName(t) + "AutoServiceImpl", e);
+        }
+    }
+
+    /**
+     * 获取Mapper(带缓存)
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> BaseMapper<T> getMapper(final Class<T> t) {
+        try {
+            DS ds = t.getAnnotation(DS.class);
+            if (ds != null){
+                DynamicDataSourceContextHolder.push(ds.value());
+            }else{
+                DynamicDataSourceContextHolder.push("main");
+            }
+            return (BaseMapper<T>) MAPPER_CACHE.computeIfAbsent(t, k -> {
+                String beanName = getBeanName(t) + "AutoMapper";
+                return SpringUtil.getBean(beanName);
+            });
+        } catch (NoSuchBeanDefinitionException e) {
+            throw new RuntimeException("未找到对应的Mapper: " + getBeanName(t) + "AutoMapper", e);
+        }
+    }
+
+    /**
+     * 获取bean名称
+     */
+    private static <T> String getBeanName(final Class<T> t) {
+        return t.getSimpleName().substring(0, 1).toLowerCase() + t.getSimpleName().substring(1);
+    }
+}

+ 27 - 0
data/src/main/java/com/dongzili/data/mybatisplus/PageTotalCache.java

@@ -0,0 +1,27 @@
+package com.dongzili.data.mybatisplus;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PageTotalCache {
+    private static final ConcurrentHashMap<String, Long> TOTAL_CACHE = new ConcurrentHashMap<>();
+    
+    // 生成缓存键(根据方法签名和参数)
+    public static String generateKey(String methodName, Object params) {
+        return methodName + ":" + (params != null ? params.hashCode() : 0);
+    }
+    
+    // 存储总数
+    public static void putTotal(String key, Long total) {
+        TOTAL_CACHE.put(key, total);
+    }
+    
+    // 获取总数
+    public static Long getTotal(String key) {
+        return TOTAL_CACHE.get(key);
+    }
+    
+    // 清除缓存(可选)
+    public static void clearCache(String key) {
+        TOTAL_CACHE.remove(key);
+    }
+}

+ 47 - 0
data/src/main/java/com/dongzili/data/utils/BCryptUtils.java

@@ -0,0 +1,47 @@
+package com.dongzili.data.utils;
+
+import org.mindrot.jbcrypt.BCrypt;
+
+
+public class BCryptUtils {
+
+    /**
+     * 加密
+     * @param password
+     * @return
+     */
+    public static String encryptByBCrypt( String password) {
+
+        return BCrypt.hashpw(password, BCrypt.gensalt());
+    }
+
+    /**
+     * 校验
+     * @param password
+     * @param encryptStr
+     * @return
+     */
+    public static boolean checkByBCrypt(String password, String encryptStr) {
+        try{
+            return BCrypt.checkpw(password, encryptStr);
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static void main(String[] args) {
+
+        String password1 = "ZXjq123!@#";
+        String password2 = "ASDf4321";
+        String password3 = "qwer1234!";
+
+        String encryptStr = BCryptUtils.encryptByBCrypt(password3);
+        System.out.println("加密后的密码:" + encryptStr);
+
+        String existEncryptStr = "$2a$10$TU1LbewFZPAwPFh7SugODuDHky61XkZaQDGYorc4Cb60LyffkiSU.";
+        String newPassword = "ASDf4321";
+        System.out.println("是否匹配:" + BCryptUtils.checkByBCrypt(password2, existEncryptStr));
+
+    }
+
+}

+ 799 - 0
data/src/main/java/com/dongzili/data/utils/LocalDateUtils.java

@@ -0,0 +1,799 @@
+package com.dongzili.data.utils;
+
+import org.springframework.lang.NonNull;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Java8日期时间工具类
+ *
+ * @author JourWon
+ * @date 2020/12/13
+ */
+public class LocalDateUtils {
+
+    public static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    public static final DateTimeFormatter datetimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    /**
+     * 显示年月日时分秒,例如 2015-08-11 09:51:53.
+     */
+    public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * 仅显示年月日,例如 2015-08-11.
+     */
+    public static final String DATE_PATTERN = "yyyy-MM-dd";
+
+    /**
+     * 仅显示时分秒,例如 09:51:53.
+     */
+    public static final String TIME_PATTERN = "HH:mm:ss";
+
+    /**
+     * 显示年月日时分秒(无符号),例如 20150811095153.
+     */
+    public static final String UNSIGNED_DATETIME_PATTERN = "yyyyMMddHHmmss";
+
+    /**
+     * 仅显示年月日(无符号),例如 20150811.
+     */
+    public static final String UNSIGNED_DATE_PATTERN = "yyyyMMdd";
+
+    /**
+     * 春天;
+     */
+    public static final Integer SPRING = 1;
+
+    /**
+     * 夏天;
+     */
+    public static final Integer SUMMER = 2;
+
+    /**
+     * 秋天;
+     */
+    public static final Integer AUTUMN = 3;
+
+    /**
+     * 冬天;
+     */
+    public static final Integer WINTER = 4;
+
+    /**
+     * 星期日;
+     */
+    public static final String SUNDAY = "星期日";
+
+    /**
+     * 星期一;
+     */
+    public static final String MONDAY = "星期一";
+
+    /**
+     * 星期二;
+     */
+    public static final String TUESDAY = "星期二";
+
+    /**
+     * 星期三;
+     */
+    public static final String WEDNESDAY = "星期三";
+
+    /**
+     * 星期四;
+     */
+    public static final String THURSDAY = "星期四";
+
+    /**
+     * 星期五;
+     */
+    public static final String FRIDAY = "星期五";
+
+    /**
+     * 星期六;
+     */
+    public static final String SATURDAY = "星期六";
+
+    /**
+     * 年
+     */
+    private static final String YEAR = "year";
+
+    /**
+     * 月
+     */
+    private static final String MONTH = "month";
+
+    /**
+     * 周
+     */
+    private static final String WEEK = "week";
+
+    /**
+     * 日
+     */
+    private static final String DAY = "day";
+
+    /**
+     * 时
+     */
+    private static final String HOUR = "hour";
+
+    /**
+     * 分
+     */
+    private static final String MINUTE = "minute";
+
+    /**
+     * 秒
+     */
+    private static final String SECOND = "second";
+
+
+
+    /**
+     * 获取当前日期和时间字符串.
+     *
+     * @return String 日期时间字符串,例如 2015-08-11 09:51:53
+     */
+    public static String getLocalDateTimeStr() {
+        return format(LocalDateTime.now(), DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取当前日期字符串.
+     *
+     * @return String 日期字符串,例如2015-08-11
+     */
+    public static String getLocalDateStr() {
+        return format(LocalDate.now(), DATE_PATTERN);
+    }
+
+    /**
+     * 获取当前时间字符串.
+     *
+     * @return String 时间字符串,例如 09:51:53
+     */
+    public static String getLocalTimeStr() {
+        return format(LocalTime.now(), TIME_PATTERN);
+    }
+
+    /**
+     * 获取当前星期字符串.
+     *
+     * @return String 当前星期字符串,例如 星期二
+     */
+    public static String getDayOfWeekStr() {
+        return format(LocalDate.now(), "E");
+    }
+
+    /**
+     * 获取指定日期是星期几
+     *
+     * @param localDate 日期
+     * @return String 星期几
+     */
+    public static String getDayOfWeekStr(LocalDate localDate) {
+        String[] weekOfDays = {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY};
+        int dayOfWeek = localDate.getDayOfWeek().getValue() - 1;
+        return weekOfDays[dayOfWeek];
+    }
+
+    /**
+     * 获取日期时间字符串
+     *
+     * @param temporal 需要转化的日期时间
+     * @param pattern  时间格式
+     * @return String 日期时间字符串,例如 2015-08-11 09:51:53
+     */
+    public static String format(TemporalAccessor temporal, String pattern) {
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
+        return dateTimeFormatter.format(temporal);
+    }
+
+    /**
+     * 获取日期时间字符串
+     *
+     * @param temporal 需要转化的日期时间
+     * @param pattern  时间格式
+     * @return String 日期时间字符串,例如 2015-08-11 09:51:53
+     */
+    public static String format(TemporalAccessor temporal, DateTimeFormatter pattern) {
+        return pattern.format(temporal);
+    }
+
+    /**
+     * 日期时间字符串转换为日期时间(java.time.LocalDateTime)
+     *
+     * @param localDateTimeStr 日期时间字符串
+     * @param pattern          日期时间格式 例如DATETIME_PATTERN
+     * @return LocalDateTime 日期时间
+     */
+    public static LocalDateTime parseLocalDateTime(String localDateTimeStr, String pattern) {
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
+        return LocalDateTime.parse(localDateTimeStr, dateTimeFormatter);
+    }
+    /**
+     * 日期时间字符串转换为日期时间(java.time.LocalDateTime)
+     *
+     * @param localDateTimeStr 日期时间字符串
+     * @param pattern          日期时间格式 例如DATETIME_PATTERN
+     * @return LocalDateTime 日期时间
+     */
+    public static LocalDateTime parseLocalDateTime(String localDateTimeStr, DateTimeFormatter pattern) {
+        return LocalDateTime.parse(localDateTimeStr, pattern);
+    }
+
+    /**
+     * 日期字符串转换为日期(java.time.LocalDate)
+     *
+     * @param localDateStr 日期字符串
+     * @param pattern      日期格式 例如DATE_PATTERN
+     * @return LocalDate 日期
+     */
+    public static LocalDate parseLocalDate(String localDateStr, String pattern) {
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
+        return LocalDate.parse(localDateStr, dateTimeFormatter);
+    }
+
+    /**
+     * 获取指定日期时间加上指定数量日期时间单位之后的日期时间.
+     *
+     * @param localDateTime 日期时间
+     * @param num           数量
+     * @param chronoUnit    日期时间单位
+     * @return LocalDateTime 新的日期时间
+     */
+    public static LocalDateTime plus(LocalDateTime localDateTime, int num, ChronoUnit chronoUnit) {
+        return localDateTime.plus(num, chronoUnit);
+    }
+
+    /**
+     * 获取指定日期时间减去指定数量日期时间单位之后的日期时间.
+     *
+     * @param localDateTime 日期时间
+     * @param num           数量
+     * @param chronoUnit    日期时间单位
+     * @return LocalDateTime 新的日期时间
+     */
+    public static LocalDateTime minus(LocalDateTime localDateTime, int num, ChronoUnit chronoUnit) {
+        return localDateTime.minus(num, chronoUnit);
+    }
+
+    /**
+     * 根据ChronoUnit计算两个日期时间之间相隔日期时间
+     *
+     * @param start      开始日期时间
+     * @param end        结束日期时间
+     * @param chronoUnit 日期时间单位
+     * @return long 相隔日期时间
+     */
+    public static long getChronoUnitBetween(LocalDateTime start, LocalDateTime end, ChronoUnit chronoUnit) {
+        if(start == null || end == null){
+            return 0;
+        }
+        return Math.abs(start.until(end, chronoUnit));
+    }
+
+    /**
+     * 根据ChronoUnit计算两个日期之间相隔年数或月数或天数
+     *
+     * @param start      开始日期
+     * @param end        结束日期
+     * @param chronoUnit 日期时间单位,(ChronoUnit.YEARS,ChronoUnit.MONTHS,ChronoUnit.WEEKS,ChronoUnit.DAYS)
+     * @return long 相隔年数或月数或天数
+     */
+    public static long getChronoUnitBetween(LocalDate start, LocalDate end, ChronoUnit chronoUnit) {
+        return Math.abs(start.until(end, chronoUnit));
+    }
+
+    /**
+     * 获取本年第一天的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfYearStr() {
+        return getFirstDayOfYearStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取本年最后一天的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfYearStr() {
+        return getLastDayOfYearStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取指定日期当年第一天的日期字符串
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfYearStr(LocalDateTime localDateTime) {
+        return getFirstDayOfYearStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取指定日期当年最后一天的日期字符串
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfYearStr(LocalDateTime localDateTime) {
+        return getLastDayOfYearStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取一年的开始时间
+     * @param year
+     * @return
+     */
+    public static LocalDateTime getStartOfYear(int year) {
+        // 获取指定年的开始时间
+        return LocalDateTime.of(year, Month.JANUARY, 1, 0, 0);
+    }
+
+    /**
+     * 获取一年的结束时间
+     * @param year
+     * @return
+     */
+    public static LocalDateTime getLastOfYear(int year) {
+        // 获取指定年的开始时间
+        return LocalDateTime.of(year, Month.DECEMBER, 31, 23, 59, 59, 999999999);
+    }
+
+    /**
+     * 获取指定日期当年第一天的日期字符串,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @param pattern       日期时间格式
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfYearStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.withDayOfYear(1).withHour(0).withMinute(0).withSecond(0), pattern);
+    }
+
+    /**
+     * 获取指定日期当年最后一天的日期字符串,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @param pattern       日期时间格式
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfYearStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.with(TemporalAdjusters.lastDayOfYear()).withHour(23).withMinute(59).withSecond(59), pattern);
+    }
+
+    /**
+     * 获取本月第一天的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfMonthStr() {
+        return getFirstDayOfMonthStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取本月最后一天的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfMonthStr() {
+        return getLastDayOfMonthStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取指定日期当月第一天的日期字符串
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getFirstDayOfMonthStr(LocalDateTime localDateTime) {
+        return getFirstDayOfMonthStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取指定日期当月最后一天的日期字符串
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfMonthStr(LocalDateTime localDateTime) {
+        return getLastDayOfMonthStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取指定日期当月第一天的日期字符串,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfMonthStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0), pattern);
+    }
+
+    /**
+     * 获取指定日期当月最后一天的日期字符串,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @param pattern       日期时间格式
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfMonthStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59), pattern);
+    }
+
+    /**
+     * 获取本周第一天的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfWeekStr() {
+        return getFirstDayOfWeekStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取本周最后一天的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfWeekStr() {
+        return getLastDayOfWeekStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取指定日期当周第一天的日期字符串,这里第一天为周一
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfWeekStr(LocalDateTime localDateTime) {
+        return getFirstDayOfWeekStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取指定日期当周最后一天的日期字符串,这里最后一天为周日
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfWeekStr(LocalDateTime localDateTime) {
+        return getLastDayOfWeekStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取指定日期当周第一天的日期字符串,这里第一天为周一,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @param pattern       日期时间格式
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getFirstDayOfWeekStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.with(DayOfWeek.MONDAY).withHour(0).withMinute(0).withSecond(0), pattern);
+    }
+
+    /**
+     * 获取指定日期当周最后一天的日期字符串,这里最后一天为周日,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @param pattern       日期时间格式
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getLastDayOfWeekStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.with(DayOfWeek.SUNDAY).withHour(23).withMinute(59).withSecond(59), pattern);
+    }
+
+    /**
+     * 获取今天开始时间的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getStartTimeOfDayStr() {
+        return getStartTimeOfDayStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取今天结束时间的日期字符串
+     *
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getEndTimeOfDayStr() {
+        return getEndTimeOfDayStr(LocalDateTime.now());
+    }
+
+    /**
+     * 获取指定日期开始时间的日期字符串
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 00:00:00
+     */
+    public static String getStartTimeOfDayStr(LocalDateTime localDateTime) {
+        return getStartTimeOfDayStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取指定日期结束时间的日期字符串
+     *
+     * @param localDateTime 指定日期时间
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getEndTimeOfDayStr(LocalDateTime localDateTime) {
+        return getEndTimeOfDayStr(localDateTime, DATETIME_PATTERN);
+    }
+
+    /**
+     * 获取指定日期开始时间的日期字符串,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @param pattern       日期时间格式
+     * @return String 格式:yyyy-MM-dd HH:mm:ss
+     */
+    public static String getStartTimeOfDayStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.withHour(0).withMinute(0).withSecond(0), pattern);
+    }
+
+    /**
+     * 获取指定日期结束时间的日期字符串,带日期格式化参数
+     *
+     * @param localDateTime 指定日期时间
+     * @param pattern       日期时间格式
+     * @return String 格式:yyyy-MM-dd 23:59:59
+     */
+    public static String getEndTimeOfDayStr(LocalDateTime localDateTime, String pattern) {
+        return format(localDateTime.withHour(23).withMinute(59).withSecond(59), pattern);
+    }
+
+    /**
+     * 切割日期。按照周期切割成小段日期段。例如: <br>
+     *
+     * @param startDate 开始日期(yyyy-MM-dd)
+     * @param endDate   结束日期(yyyy-MM-dd)
+     * @param period    周期(天,周,月,年)
+     * @return 切割之后的日期集合
+     * <li>startDate="2019-02-28",endDate="2019-03-05",period="day"</li>
+     * <li>结果为:[2019-02-28, 2019-03-01, 2019-03-02, 2019-03-03, 2019-03-04, 2019-03-05]</li><br>
+     * <li>startDate="2019-02-28",endDate="2019-03-25",period="week"</li>
+     * <li>结果为:[2019-02-28,2019-03-06, 2019-03-07,2019-03-13, 2019-03-14,2019-03-20,
+     * 2019-03-21,2019-03-25]</li><br>
+     * <li>startDate="2019-02-28",endDate="2019-05-25",period="month"</li>
+     * <li>结果为:[2019-02-28,2019-02-28, 2019-03-01,2019-03-31, 2019-04-01,2019-04-30,
+     * 2019-05-01,2019-05-25]</li><br>
+     * <li>startDate="2019-02-28",endDate="2020-05-25",period="year"</li>
+     * <li>结果为:[2019-02-28,2019-12-31, 2020-01-01,2020-05-25]</li><br>
+     */
+    public static List<String> listDateStrs(String startDate, String endDate, String period) {
+        List<String> result = new ArrayList<>();
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_PATTERN);
+        LocalDate end = LocalDate.parse(endDate, dateTimeFormatter);
+        LocalDate start = LocalDate.parse(startDate, dateTimeFormatter);
+        LocalDate tmp = start;
+        switch (period) {
+            case DAY:
+                while (start.isBefore(end) || start.isEqual(end)) {
+                    result.add(start.toString());
+                    start = start.plusDays(1);
+                }
+                break;
+            case WEEK:
+                while (tmp.isBefore(end) || tmp.isEqual(end)) {
+                    if (tmp.plusDays(6).isAfter(end)) {
+                        result.add(tmp.toString() + "," + end);
+                    } else {
+                        result.add(tmp.toString() + "," + tmp.plusDays(6));
+                    }
+                    tmp = tmp.plusDays(7);
+                }
+                break;
+            case MONTH:
+                while (tmp.isBefore(end) || tmp.isEqual(end)) {
+                    LocalDate lastDayOfMonth = tmp.with(TemporalAdjusters.lastDayOfMonth());
+                    if (lastDayOfMonth.isAfter(end)) {
+                        result.add(tmp.toString() + "," + end);
+                    } else {
+                        result.add(tmp.toString() + "," + lastDayOfMonth);
+                    }
+                    tmp = lastDayOfMonth.plusDays(1);
+                }
+                break;
+            case YEAR:
+                while (tmp.isBefore(end) || tmp.isEqual(end)) {
+                    LocalDate lastDayOfYear = tmp.with(TemporalAdjusters.lastDayOfYear());
+                    if (lastDayOfYear.isAfter(end)) {
+                        result.add(tmp.toString() + "," + end);
+                    } else {
+                        result.add(tmp.toString() + "," + lastDayOfYear);
+                    }
+                    tmp = lastDayOfYear.plusDays(1);
+                }
+                break;
+            default:
+                break;
+        }
+        return result;
+    }
+
+    /**
+     * 获取某日的开始时间
+     * @param localDate
+     * @return
+     */
+    public static LocalDateTime getStartTimeOfDay(LocalDate localDate){
+        return localDate.atStartOfDay();
+    }
+
+    /**
+     * 获取某日的结束时间
+     * @param localDate
+     * @return
+     */
+    public static LocalDateTime getLastTimeOfDay(LocalDate localDate){
+        return localDate.atTime(LocalTime.MAX);
+    }
+
+    /**
+     * 将秒数转换成 天\时\分\秒
+     * @param seconds
+     * @return
+     */
+    public static String convertSeconds(long seconds) {
+        // 定义时间单位
+        final long SECONDS_IN_A_DAY = 86400; // 24 * 60 * 60
+        final long SECONDS_IN_AN_HOUR = 3600; // 60 * 60
+        final long SECONDS_IN_A_MINUTE = 60;  // 60
+
+        // 计算天、小时、分钟和秒
+        long days = seconds / SECONDS_IN_A_DAY;
+        seconds %= SECONDS_IN_A_DAY;
+
+        long hours = seconds / SECONDS_IN_AN_HOUR;
+        seconds %= SECONDS_IN_AN_HOUR;
+
+        long minutes = seconds / SECONDS_IN_A_MINUTE;
+        seconds %= SECONDS_IN_A_MINUTE;
+
+        // 格式化输出
+        return String.format("%d天 %d小时 %d分钟 %d秒", days, hours, minutes, seconds);
+    }
+
+    /**
+     * 判断两个LocalDateTime是否是同一天
+     * @param dateTime1
+     * @param dateTime2
+     * @return
+     */
+    public static boolean isSameDay(@NonNull LocalDateTime dateTime1,@NonNull LocalDateTime dateTime2) {
+        return dateTime1.getYear() == dateTime2.getYear() &&
+                dateTime1.getMonthValue() == dateTime2.getMonthValue() &&
+                dateTime1.getDayOfMonth() == dateTime2.getDayOfMonth();
+    }
+
+    /**
+     * 判断指定时间是否在...之间
+     * @param start
+     * @param end
+     * @return
+     */
+    public static boolean isNowInRange(LocalDateTime start, LocalDateTime end, LocalDateTime now) {
+        // 当前时间在start之后(含)且在end之前(不含)
+        return (!now.isBefore(start) && now.isBefore(end)) || isSameDay(end, now);
+    }
+
+
+
+
+    public static void main(String[] args) {
+
+        LocalDateTime date1 = parseLocalDateTime("2024-06-30 00:00:00", datetimeFormat);
+        LocalDateTime date2 = parseLocalDateTime("2024-06-30 00:00:00", datetimeFormat);
+        System.out.println(isSameDay(date1, date2) );
+
+        LocalDateTime date3 = parseLocalDateTime("2024-06-29 13:00:00", datetimeFormat);
+        System.out.println(isSameDay(date2,date3) && date3.isAfter(date2));
+//        System.out.println(format(randomTimeWithToday(parseLocalDateTime("2024-05-06 18:32:00", datetimeFormat), 30), datetimeFormat));
+
+//        System.out.println(getLocalDateTimeStr());
+//        System.out.println(getLocalDateStr());
+//        System.out.println(getLocalTimeStr());
+//        System.out.println(getDayOfWeekStr());
+//        System.out.println(getDayOfWeekStr(LocalDate.now()));
+//
+//        System.out.println("========");
+//        System.out.println(format(LocalDate.now(), UNSIGNED_DATE_PATTERN));
+//
+//        System.out.println("========");
+//        System.out.println(parseLocalDateTime("2020-12-13 11:14:12", DATETIME_PATTERN));
+//        System.out.println(parseLocalDate("2020-12-13", DATE_PATTERN));
+//
+//        System.out.println("========");
+//        System.out.println(plus(LocalDateTime.now(), 3, ChronoUnit.HOURS));
+//        System.out.println(minus(LocalDateTime.now(), 4, ChronoUnit.DAYS));
+//
+//        System.out.println("========");
+//        System.out.println(getChronoUnitBetween(LocalDateTime.now(), parseLocalDateTime("2020-12-12 12:03:12", DATETIME_PATTERN), ChronoUnit.MINUTES));
+//        System.out.println(getChronoUnitBetween(LocalDate.now(), parseLocalDate("2021-12-12", DATE_PATTERN), ChronoUnit.WEEKS));
+//
+//        System.out.println("========");
+//        System.out.println(getFirstDayOfYearStr());
+//        System.out.println(getFirstDayOfYearStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getFirstDayOfYearStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println(getLastDayOfYearStr());
+//        System.out.println(getLastDayOfYearStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getLastDayOfYearStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println("========");
+//        System.out.println(getFirstDayOfMonthStr());
+//        System.out.println(getFirstDayOfMonthStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getFirstDayOfMonthStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println(getLastDayOfMonthStr());
+//        System.out.println(getLastDayOfMonthStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getLastDayOfMonthStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println("========");
+//        System.out.println(getFirstDayOfWeekStr());
+//        System.out.println(getFirstDayOfWeekStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getFirstDayOfWeekStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println(getLastDayOfWeekStr());
+//        System.out.println(getLastDayOfWeekStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getLastDayOfWeekStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println("========");
+//        System.out.println(getStartTimeOfDayStr());
+//        System.out.println(getStartTimeOfDayStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getStartTimeOfDayStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println(getEndTimeOfDayStr());
+//        System.out.println(getEndTimeOfDayStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN)));
+//        System.out.println(getEndTimeOfDayStr(parseLocalDateTime("2021-12-12 12:03:12", DATETIME_PATTERN), UNSIGNED_DATETIME_PATTERN));
+//
+//        System.out.println("========");
+//        List<String> dateStrs = listDateStrs("2019-01-30", "2020-12-13", YEAR);
+//        for (String dateStr : dateStrs) {
+//            System.out.println(dateStr);
+//        }
+//
+//        System.out.println("========");
+//        List<String> dateStrs1 = listDateStrs("2019-01-30", "2020-12-13", MONTH);
+//        for (String dateStr : dateStrs1) {
+//            System.out.println(dateStr);
+//        }
+//
+//        System.out.println("========");
+//        List<String> dateStrs2 = listDateStrs("2020-12-01", "2020-12-13", DAY);
+//        for (String dateStr : dateStrs2) {
+//            System.out.println(dateStr);
+//        }
+    }
+
+    public static String dateFormat(LocalDate date) {
+        if (date == null) {
+            return null;
+        }
+        return date.format(DateTimeFormatter.ofPattern(DATE_PATTERN));
+    }
+    public static String dateTimeFormat(LocalDateTime dateTime) {
+        if (dateTime == null) {
+            return null;
+        }
+        return dateTime.format(DateTimeFormatter.ofPattern(DATETIME_PATTERN));
+    }
+
+    public static String dateTimeFormat(LocalDateTime dateTime, String pattern) {
+        if (dateTime == null) {
+            return null;
+        }
+        return dateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+}

+ 96 - 0
data/src/main/java/com/dongzili/data/utils/RandomUtils.java

@@ -0,0 +1,96 @@
+package com.dongzili.data.utils;
+
+import cn.hutool.core.util.RandomUtil;
+import org.springframework.lang.NonNull;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Random;
+
+public class RandomUtils {
+
+    /**
+     * 返回指定范围的随机数
+     * @param min 最小值
+     * @param length 步长
+     * @return int
+     */
+    public static int randomInt(int min, int length) {
+        return RandomUtil.randomInt(min, min+length);
+    }
+
+    public static LocalDateTime randomTimeWithToday(@NonNull LocalDateTime baseTime, LocalDate now, int minutes, int plusDay) {
+        Random random = new Random();
+        int randomOffsetMinutes = random.nextInt(minutes*2) - minutes; // -30到+29分钟
+        return baseTime.with(now.plusDays(plusDay)).plusMinutes(randomOffsetMinutes);
+    }
+
+    /**
+     * 返回指定日期,指定范围的随机时间
+     * @param now
+     * @param startHour
+     * @param endHour
+     * @return
+     */
+    public static LocalDateTime randomTimeWithToday(@NonNull LocalDate now, int startHour, int endHour) {
+        int randomOffsetMinutes = RandomUtil.randomInt(startHour*60*60 , endHour*60*60); // -30到+29分钟
+        return now.atStartOfDay().plusSeconds(randomOffsetMinutes);
+    }
+
+//    public static void main(String[] args) {
+//        LocalDateTime r = randomTimeWithToday(LocalDate.now(), 6, 23);
+//
+//        System.out.println(r.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+//    }
+
+    /**
+     * 正态分布随机分配
+     * @param totalRecords 总记录数
+     * @param days 天数
+     * @return int[]
+     */
+    public static int[] distributeRecords(int totalRecords, int days) {
+        Random random = new Random();
+        double mean = (double) totalRecords / days;  // 均值
+        double stdDev = mean / 3;                    // 标准差,控制波动大小
+
+        int[] distribution = new int[days];
+        int sum = 0;
+
+        // 为每一天生成符合正态分布的记录数
+        for (int i = 0; i < days; i++) {
+            double value;
+            // 确保生成的记录数为正数
+            do {
+                value = random.nextGaussian() * stdDev + mean;
+            } while (value <= 0);
+
+            distribution[i] = (int) Math.round(value);
+            sum += distribution[i];
+        }
+
+        // 调整总和,确保等于totalRecords
+        if (sum != totalRecords) {
+            int difference = totalRecords - sum;
+            if (difference > 0) {
+                // 增加记录数
+                for (int i = 0; i < difference; i++) {
+                    distribution[random.nextInt(days)]++;
+                }
+            } else {
+                // 减少记录数
+                difference = -difference;
+                for (int i = 0; i < difference; i++) {
+                    int index;
+                    do {
+                        index = random.nextInt(days);
+                    } while (distribution[index] <= 0);
+                    distribution[index]--;
+                }
+            }
+        }
+        return distribution;
+    }
+}
+
+

+ 326 - 0
data/src/main/java/com/dongzili/data/utils/ReflectUtils.java

@@ -0,0 +1,326 @@
+package com.dongzili.data.utils;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.redis.core.index.Indexed;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 反射相关工具类
+ */
+public class ReflectUtils {
+
+    /**
+     * 获取不为空的属性的数据库字段名和值
+     * @param obj
+     * @param columns
+     * @return
+     */
+    public static Map<String, Object> getNotNullFieldValueMap(Object obj, List<String> columns) {
+        Map<String, Object> fieldValueMap = new HashMap<>();
+        Class<?> clazz = obj.getClass();
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            try {
+                if(columns.contains(field.getName())){
+                    continue;
+                }
+                field.setAccessible(true);
+                Object value = field.get(obj);
+                if (value != null) {
+                    String fieldName = getFieldName(field);
+                    fieldValueMap.put(fieldName, value);
+                }
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        return fieldValueMap;
+    }
+
+    /**
+     * 获取不为空的属性的数据库字段名和值
+     * @param obj
+     * @return
+     */
+    public static Map<String, Object> getNotNullFieldValueMap(Object obj) {
+        Map<String, Object> fieldValueMap = new HashMap<>();
+        if (obj == null){
+            return fieldValueMap;
+        }
+        Class<?> clazz = obj.getClass();
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            try {
+                field.setAccessible(true);
+                TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
+                if (tableFieldAnnotation != null && !tableFieldAnnotation.exist()) {
+                    continue; // Ignore if exist is false
+                }
+                Object value = field.get(obj);
+                if (value != null) {
+                    if (value instanceof List<?>){
+                        if(!((List<?>)value).isEmpty()){
+                            fieldValueMap.put(getFieldName(field), value);
+                        }
+                    }else if (value instanceof String){
+                        if(StringUtils.hasText(value.toString())){
+                            fieldValueMap.put(getFieldName(field), value);
+                        }
+                    }else{
+                        fieldValueMap.put(getFieldName(field), value);
+                    }
+
+                }
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        return fieldValueMap;
+    }
+
+    public static String getFieldName(Field field) {
+        TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
+        if (tableFieldAnnotation != null && !tableFieldAnnotation.value().isEmpty()) {
+            return tableFieldAnnotation.value();
+        } else {
+            return field.getName();
+        }
+    }
+
+
+    /**
+     * 获取实体类上 @TableName 注解中定义的数据库表名
+     * @param clazz 实体类的 Class 对象
+     * @return 数据库表名,如果未找到注解或注解未设置 value 属性,则返回 null
+     */
+    public static String getTableName(Class<?> clazz) {
+        TableName tableNameAnnotation = AnnotationUtil.getAnnotation(clazz, TableName.class);
+        if (tableNameAnnotation != null) {
+            return tableNameAnnotation.value();
+        }
+        return "";
+    }
+
+    /**
+     * 获取实体字段上 @TableField 注解中定义的列名
+     * @param clazz
+     * @return
+     */
+    public static String getColumnNames(Class<?> clazz) {
+        List<String> columns = new ArrayList<>();
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableField.class)) {
+                TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
+                if(tableFieldAnnotation!=null && tableFieldAnnotation.exist()){
+                    columns.add(tableFieldAnnotation.value()+" as `"+field.getName()+"`");
+                }
+
+            }else if(field.isAnnotationPresent(TableId.class)){
+                TableId tableFieldAnnotation = field.getAnnotation(TableId.class);
+                if(tableFieldAnnotation!=null){
+                    columns.add(tableFieldAnnotation.value()+" as `"+field.getName()+"`");
+                }
+            }
+            else{
+                columns.add("`"+field.getName()+"`");
+            }
+        }
+        return String.join(",", columns);
+    }
+
+    /**
+     * 获取主键数据库字段名称
+     * @param clazz
+     * @return
+     */
+    public static String getPrimaryColumnKey(Class<?> clazz) {
+
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableId.class)) {
+                TableId tableFieldAnnotation = field.getAnnotation(TableId.class);
+                if(tableFieldAnnotation!=null){
+                    return tableFieldAnnotation.value();
+                }
+
+            }
+        }
+        return "id";
+    }
+    public static Field getPrimaryField(Class<?> clazz) {
+
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableId.class) || field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(Indexed.class) || field.getName().equalsIgnoreCase("id")) {
+                TableId tableFieldAnnotation = field.getAnnotation(TableId.class);
+                if(tableFieldAnnotation!=null){
+                    return field;
+                }
+
+            }
+        }
+        return null;
+    }
+    /**
+     * 获取主键实体字段名称
+     * @param clazz
+     * @return
+     */
+    public static String getPrimaryFieldKey(Class<?> clazz) {
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableId.class)) {
+                TableId tableFieldAnnotation = field.getAnnotation(TableId.class);
+                if(tableFieldAnnotation!=null){
+                    return field.getName();
+                }
+
+            }
+        }
+        return "id";
+    }
+
+
+    public static <T> Object getPrimaryValue(Class<T> clazz, T entity) throws IllegalAccessException {
+
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableId.class)) {
+                TableId tableFieldAnnotation = field.getAnnotation(TableId.class);
+                if(tableFieldAnnotation!=null){
+                    return field.get(entity);
+                }
+
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 获取实体类的主键值
+     */
+    public static <T> Object getPrimaryValue(T entity) {
+        try{
+            Field[] fields = entity.getClass().getDeclaredFields();
+            for (Field field : fields) {
+                field.setAccessible(true);
+                if (field.isAnnotationPresent(TableId.class) || field.isAnnotationPresent(Id.class) || field.getName().equalsIgnoreCase("id")) {
+                    return field.get(entity);
+                }
+            }
+        }catch (Exception e){
+            return null;
+        }
+        return null;
+    }
+
+
+    public static <T> T setPrimaryValue(Class<T> clazz, Object value) throws IllegalAccessException, InstantiationException {
+
+        T cloneEntity = clazz.newInstance();
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableId.class)) {
+                TableId tableFieldAnnotation = field.getAnnotation(TableId.class);
+                if(tableFieldAnnotation!=null){
+                    field.set(cloneEntity, value);
+                    return cloneEntity;
+                }
+
+            }
+        }
+        return null;
+    }
+
+    public static <T> T setPrimaryValue(T entity, Object value) throws IllegalAccessException, InstantiationException {
+
+//        T cloneEntity = entity.getClass().newInstance();
+        Field[] fields = entity.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(TableId.class)) {
+                TableId tableFieldAnnotation = field.getAnnotation(TableId.class);
+                if(tableFieldAnnotation!=null){
+                    field.set(entity, value);
+                    return entity;
+                }
+
+            }
+        }
+        return null;
+    }
+
+    public static <T> T getObjByKeys(Class<T> clazz, T entity){
+        try{
+            Object id = getPrimaryValue(clazz, entity);
+            if(id != null){
+                return setPrimaryValue(clazz, id);
+            }
+            T cloneEntity = clazz.newInstance();
+            Field[] fields = clazz.getDeclaredFields();
+            boolean hasKey = false;
+            // 遍历所有属性
+            for (Field field : fields) {
+                // 如果属性上有此注解,则进行赋值操作
+                if (field.isAnnotationPresent(TableId.class)) {
+                    field.setAccessible(true);
+                    Object value = field.get(entity);
+                    field.set(cloneEntity, value);
+                    hasKey = true;
+                }
+            }
+            return hasKey ? cloneEntity : null;
+        }catch (Exception e){
+            return null;
+        }
+    }
+
+
+    /**
+     * 根据字段获取数据库字段名称
+     * @param clazz
+     * @param fieldName
+     * @return
+     * @param <E>
+     */
+    public static <E> String getColumnName(Class<E> clazz, String fieldName) {
+        try {
+            Field[] fields = clazz.getDeclaredFields();
+            for (Field field : fields) {
+                field.setAccessible(true);
+                if(field.getName().equals(fieldName)){
+                    if(field.isAnnotationPresent(TableField.class)){
+                        TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
+                        if(tableFieldAnnotation!=null){
+                            return tableFieldAnnotation.value();
+                        }
+                    }else{
+                        return field.getName();
+                    }
+                }
+            }
+        }catch (Exception e){
+            return null;
+        }
+        return null;
+    }
+
+
+}

+ 17 - 0
data/src/main/resources/sqlite.sql

@@ -0,0 +1,17 @@
+-- DROP TABLE IF EXISTS t_user;
+CREATE TABLE IF NOT EXISTS t_user(
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    username  TEXT NOT NULL UNIQUE,
+    password TEXT NOT NULL,
+    roleIds TEXT NOT NULL
+);
+insert into t_user(id, username,password, roleIds) values(1000, 'admin','$2a$10$NIkz3yq8nYIUApwKMG2Ux.KxAtesj2JTqILwmW2ZUELKf0jQCpNVK', '[1000]');
+
+-- DROP TABLE IF EXISTS t_role;
+CREATE TABLE IF NOT EXISTS t_role(
+   id INTEGER PRIMARY KEY AUTOINCREMENT,
+   code  TEXT NOT NULL UNIQUE,
+   name TEXT NOT NULL
+);
+insert into t_role(id, code,name) values(1000, 'super-admin','超级管理员');
+insert into t_role(id, code,name) values(1001, 'admin','普通管理员');

+ 60 - 0
entity/pom.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.dongzili</groupId>
+        <artifactId>springcloud-base</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>entity</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+
+<!--        <dependency>-->
+<!--            <groupId>com.mysql</groupId>-->
+<!--            <artifactId>mysql-connector-j</artifactId>-->
+<!--            <scope>runtime</scope>-->
+<!--        </dependency>-->
+        <!-- mybatis plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis-typehandlers-jsr310</artifactId>
+            <version>${mybatis-typehandlers.version}</version>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>cn.hutool</groupId>-->
+<!--            <artifactId>hutool-all</artifactId>-->
+<!--            <version>5.8.25</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>net.bytebuddy</groupId>-->
+<!--            <artifactId>byte-buddy</artifactId>-->
+<!--            <version>1.15.10</version>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <version>${dynamic-datasource.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <version>${validation.version}</version>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 20 - 0
entity/src/main/java/com/dongzili/entity/Role.java

@@ -0,0 +1,20 @@
+package com.dongzili.entity;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * 角色
+ */
+@DS("main")
+@Data
+@Accessors(chain = true)
+@TableName("t_role")
+public class Role {
+
+    private Integer id;
+    private String code;
+    private String name;
+}

+ 39 - 0
entity/src/main/java/com/dongzili/entity/User.java

@@ -0,0 +1,39 @@
+package com.dongzili.entity;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * 用户基本信息
+ */
+@DS("main")
+@Data
+@Accessors(chain = true)
+@TableName("t_user")
+public class User {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    private String username;
+
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    private String password;
+
+
+    private List<Integer> roleIds;
+
+    @TableField(exist = false)
+    private List<Role> roles;
+    @TableField(exist = false)
+    private List<String> roleCodes;
+
+}

+ 144 - 0
entity/src/main/java/com/dongzili/entity/Users.java

@@ -0,0 +1,144 @@
+package com.dongzili.entity;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.dongzili.enums.SexEnum;
+import com.dongzili.enums.UserStatusEnum;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+* 用户表
+* @TableName u_users
+*/
+
+@DS("xnyy")
+@Data
+@TableName(value ="u_users")
+public class Users {
+
+    @TableId(value="`id`", type = IdType.AUTO)
+    private Long id;
+
+//    /**
+//    * 工号/学号
+//    */
+//    @TableField("`userNo`")
+//    private String userNo;
+//
+//    /**
+//    * 用户名
+//    */
+//    @TableField("`userName`")
+//    private String userName;
+
+    /**
+    * 密码
+    */
+    @JsonIgnore
+    @TableField("`password`")
+    private String password;
+
+    /**
+    * 盐值
+    */
+    @JsonIgnore
+    @TableField("`salt`")
+    private String salt;
+
+    /**
+    * 真实名称
+    */
+    @TableField("`realname`")
+    private String realname;
+    /**
+     * 性别
+     */
+    @TableField("`sex`")
+    private SexEnum sex;
+
+    /**
+    * 科室
+    */
+    @TableField("`department`")
+    private String department;
+
+    /**
+    * 专业
+    */
+    @TableField("`major`")
+    private String major;
+
+    /**
+    * 学历
+    */
+    @TableField("`education`")
+    private String education;
+
+    /**
+    * 入学年份
+    */
+    @TableField("`enrollmentYear`")
+    private Integer enrollmentYear;
+
+    /**
+     * 状态
+     */
+    @TableField("`status`")
+    private UserStatusEnum status;
+
+    /**
+     * 系统内部标识
+     */
+    @TableField("`sign`")
+    private Boolean sign;
+
+    /**
+     * 手机号
+     */
+    @Size(min = 11, max = 11, message = "手机号长度必须为11位")
+    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+    @TableField("`phone`")
+    private Long phone;
+
+    /**
+     * 头像
+     */
+    @TableField("`avatar`")
+    private String avatar;
+
+    /**
+    * 创建时间
+    */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @JsonProperty("createTime")
+    @TableField("`createTime`")
+    private LocalDateTime createTime;
+
+    /**
+    * 更新时间
+    */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @JsonProperty("modifyTime")
+    @TableField("`modifyTime`")
+    private LocalDateTime modifyTime;
+
+    @TableField(exist = false)
+    private List<Long> roleIds;
+
+    @TableField(exist = false)
+    private List<Long> permissionIds;
+
+
+
+}

+ 10 - 0
entity/src/main/java/com/dongzili/entity/vo/UpdatePwdVo.java

@@ -0,0 +1,10 @@
+package com.dongzili.entity.vo;
+
+import lombok.Data;
+
+@Data
+public class UpdatePwdVo {
+
+    private String oldPassword;
+    private String newPassword;
+}

+ 11 - 0
entity/src/main/java/com/dongzili/enums/EnumDesc.java

@@ -0,0 +1,11 @@
+package com.dongzili.enums;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnumDesc {
+}

+ 41 - 0
entity/src/main/java/com/dongzili/enums/SexEnum.java

@@ -0,0 +1,41 @@
+package com.dongzili.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.Getter;
+
+/**
+ * 性别枚举
+ */
+@Getter
+//@JsonSerialize(using = EnumDescSerializer.class)
+//@JsonDeserialize(using = EnumDescDeserializer.class)
+public enum SexEnum {
+    /**
+     * 未知
+     */
+    UNKNOWN(0, "未知"),
+    /**
+     * 男
+     */
+    MALE(1, "男"),
+    /**
+     * 女
+     */
+    FEMALE(2, "女");
+    /**
+     * 枚举值
+     */
+    @EnumValue
+    private final Integer code;
+    /**
+     * 描述
+     */
+    @EnumDesc
+    private final String desc;
+
+    SexEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+}

+ 38 - 0
entity/src/main/java/com/dongzili/enums/UserStatusEnum.java

@@ -0,0 +1,38 @@
+package com.dongzili.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.Getter;
+
+@Getter
+public enum UserStatusEnum {
+    NORMAL(1, "正常"),
+    DISABLE(2, "禁用");
+
+    @EnumValue
+    private final Integer code;
+    @EnumDesc
+    private final String desc;
+
+    UserStatusEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public static UserStatusEnum objOfName(String name) {
+        for (UserStatusEnum e : values()) {
+            if (e.name().equals(name)) {
+                return e;
+            }
+        }
+        return null;
+    }
+    public static String descOfName(String name) {
+        for (UserStatusEnum e : values()) {
+            if (e.name().equals(name)) {
+                return e.getDesc();
+            }
+        }
+        return "";
+    }
+
+}

+ 30 - 0
entity/src/main/java/com/dongzili/vo/OrderType.java

@@ -0,0 +1,30 @@
+package com.dongzili.vo;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.Getter;
+
+/**
+ * 排序方式
+ */
+@Getter
+public enum OrderType {
+
+
+    /**
+     * 降序
+     */
+    Desc(1),
+    /**
+     * 升序
+     */
+    Asc(2)
+    ;
+
+    @EnumValue//标记数据库存的值
+    private final int value;
+
+    OrderType(int value) {
+        this.value = value;
+    }
+
+}

+ 13 - 0
entity/src/main/java/com/dongzili/vo/OrderVo.java

@@ -0,0 +1,13 @@
+package com.dongzili.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class OrderVo {
+    private String filedName;
+    private OrderType orderType;
+}

+ 46 - 0
entity/src/main/java/com/dongzili/vo/PageRequest.java

@@ -0,0 +1,46 @@
+package com.dongzili.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class PageRequest<T> {
+
+    /** 当前页数,从1开始 */
+    private Integer current;
+    /** 每页记录数 **/
+    private Integer size;
+    /** 总数 **/
+    private Integer total;
+    /**搜索关键字*/
+    private String keyword;
+    /** 请求数据实体 */
+    @Valid
+    private T entity;
+    /** 请求数据实体数组 */
+    @Valid
+    private List<T> entities;
+    /** 消息ID */
+    private String messageId;
+    /** 模糊搜索承载实体 */
+    private T likeEntity ;
+    /** 排除范围查询实体 */
+    private T notInEntity ;
+    /** 排序字段 */
+    private List<OrderVo> orders;
+    /** 根据指定时间区间查询字段 */
+    private Map<String, QueryPeriodVo> queryPeriod;
+
+
+    public PageRequest() {
+        current = 1;
+        size = 10;
+    }
+}

+ 27 - 0
entity/src/main/java/com/dongzili/vo/QueryPeriodVo.java

@@ -0,0 +1,27 @@
+package com.dongzili.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+/**
+ * 查询期间
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class QueryPeriodVo {
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime startTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime endTime;
+
+
+}

+ 116 - 0
entity/src/main/java/com/dongzili/vo/R.java

@@ -0,0 +1,116 @@
+package com.dongzili.vo;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.StringJoiner;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class R<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Integer code;
+
+    private String message;
+
+    private String traceId;
+
+    private String status;
+
+    private Long count;
+
+    private long times;
+
+    private T data;
+
+    public R(Integer code, String message, String status){
+        this.code = code;
+        this.message = message;
+        this.status = status;
+    }
+
+    public R(Integer code, String message, String status, T data){
+        this(code,message,status);
+        this.status = status;
+        this.data = data;
+    }
+    public R(){}
+
+    public static <T> R<T> success(){
+        return new R<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage(), ResponseCode.SUCCESS.getStatus());
+    }
+    public static <T> R<T> success(String message){
+        return new R<>(ResponseCode.SUCCESS.getCode(), message, ResponseCode.SUCCESS.getStatus());
+    }
+
+    public static <T> R<T> success(T data, Long count){
+        if (data instanceof List){
+            R<T> r =new R<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage(), ResponseCode.SUCCESS.getStatus(),data);
+            r.setCount(count);
+            return r;
+        }
+        return new R<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage(), ResponseCode.SUCCESS.getStatus(),data);
+    }
+
+    public static <T> R<T> success(T data){
+        if (data instanceof List){
+            R<T> r =new R<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage(), ResponseCode.SUCCESS.getStatus(),data);
+            r.setCount((long) ((List<?>) data).size());
+            return r;
+        }
+        return new R<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage(), ResponseCode.SUCCESS.getStatus(),data);
+    }
+
+    public static <T> R<T> success(T data, String message){
+        if (data instanceof List){
+            R<T> r =new R<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage(), ResponseCode.SUCCESS.getStatus(),data);
+            r.setCount((long) ((List<?>) data).size());
+            return r;
+        }
+        return new R<>(ResponseCode.SUCCESS.getCode(), message, ResponseCode.SUCCESS.getStatus(),data);
+    }
+
+    public static <T> R<T> fail(){
+        return new R<>(ResponseCode.FAILURE.getCode(),ResponseCode.FAILURE.getMessage(), ResponseCode.FAILURE.getStatus());
+    }
+
+    public static <T> R<T> fail(Integer code, String message, String status){
+        return new R<>(code,message, status);
+    }
+
+    public static <T> R<T> error(){
+        return new R<>(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getMessage(), ResponseCode.ERROR.getStatus());
+    }
+
+    public static <T> R<T> error(String msg){
+        return new R<>(ResponseCode.ERROR.getCode(), msg, ResponseCode.ERROR.getStatus());
+    }
+
+    public static <T> R<T> error(String msg, T data){
+        return new R<>(ResponseCode.ERROR.getCode(), msg, ResponseCode.ERROR.getStatus(), data);
+    }
+
+    public static <T> R<T> error(ResponseCode response){
+        return new R<>(response.getCode(), response.getMessage(), response.getStatus());
+    }
+//    public static <T> ApiResponse<T> error(ResponseCode response, String msg){
+//        return new ApiResponse<>(response.getCode(), msg, response.getStatus());
+//    }
+    public static <T> R<T> error(ResponseCode response, T data){
+        return new R<>(response.getCode(), response.getMessage(), response.getStatus(), data);
+    }
+
+    @Override
+    public String toString() {
+        return (new StringJoiner(", ", R.class.getSimpleName() + "[", "]"))
+                .add("code=" + this.code)
+                .add("message='" + this.message + "'")
+                .add("data=" + this.data)
+                .toString();
+    }
+}

+ 69 - 0
entity/src/main/java/com/dongzili/vo/ResponseCode.java

@@ -0,0 +1,69 @@
+package com.dongzili.vo;
+
+import lombok.Getter;
+
+@Getter
+public enum ResponseCode {
+
+    SUCCESS(200,"操作成功!", "Success"),
+    FAILURE(201,"操作失败", "Failure"),
+
+    USER_NOT_LOGIN(401, "未登录", "Failure"),
+    /**系统相关的错误码:5开头**/
+    ERROR(500,"系统异常,请稍后重试", "Failure"),
+    /**参数相关的错误码:1开头**/
+    PARAM_ERROR(1000,"参数异常", "Failure"),
+    PARAM_RSA_ERROR(1001,"参数rsa解密异常", "Failure"),
+    JSON_PARSE_ERROR(1002,"json格式错误" , "Failure" ),
+    PARAM_DUPLICATE(1003, "数据约束冲突,请检查请求参数是否有误", "Failure"),
+
+
+    /**权限相关的错误码:2开头**/
+    INVALID_TOKEN(2001,"未登录或登录信息已过期,请重新登录!", "Failure"),
+    ACCESS_DENIED(2002,"授权已到期或无权访问,请联系管理员", "Failure"),
+    USERNAME_OR_PASSWORD_ERROR(2003,"用户名或密码错误", "Failure"),
+    ACCESS_DENIED_Copyright(2004,"该资源未授权,请联系管理员", "Failure"),
+    Visitors_NotAllowed(2005,"游客身份受限,请去登录...", "Failure"),
+    LOGIN_5ERROR_LIMIT(2006,"登录失败次数超过5次限制,请10分钟后再尝试..." , "Failure"),
+    LOGIN_4ERROR_LIMIT(2007,"登录失败已达4次,如果再次登陆失败该账号将被锁定10分钟,是否继续?" , "Failure"),
+    USER_DISALLOWED(2008, "该账号已禁用", "Failure"),
+
+    MULTI_AGENCY_WARN(3001,"当前IP有多个机构IP段匹配", "Failure"),
+    NONE_AGENCY_WARN(3002,"当前IP不在任何机构IP段内", "Failure"),
+    NOT_AGENCY_WARN(3002,"当前IP不在指定机构IP段内", "Failure"),
+    AGENCY_NOT_FOUND(3002,"机构不存在", "Failure"),
+    PCATEGORY_STATUS_BAN(3004,"父分类被禁用,无法新增子分类", "Failure"),
+    CATEGORY_START_GROUP_EXIST(3005,"当前等级已存在正在使用的分类组,无法新增", "Failure"),
+    NOT_PERMISSION(3006,"无权限操作", "Failure"),
+
+    CATEGORY_GROUP_START_REPEAT(3007,"当前级别已有分类组在使用", "Failure"),
+    DELETE_FAIL(3008,"删除失败", "Failure"),
+    UPLOAD_FAIL(3009,"上传失败", "Failure"),
+    DATA_UNAUTHORIZED(3010,"数据未授权", "Failure"),
+
+    NO_RECORD(3010, "未找到数据", "Failure"),
+    ACCOUNT_REQUIRE(3011, "账号不能为空", "Failure"),
+    PHONE_OR_MAIL_ERROR(3012, "手机号或邮箱错误", "Failure"),
+    ACCOUNT_NOT_REGISTERED(3013, "该账号还没有注册", "Failure"),
+    ACCOUNT_LOCKED(3014, "当前用户已被锁定,请联系管理员!", "Failure"),
+    ACCOUNT_ADJUST_REQUIRE(3015, "当前用户还未被审核,请联系管理员!", "Failure"),
+
+    LIMIT_ERROR(4000,"服务器繁忙,请稍后重试...", "Failure");
+
+    private final Integer code;
+
+    private final String message;
+
+    private final String status;
+
+//    ResponseCode(int code, String message){
+//        this.code = code;
+//        this.message = message;
+//    }
+    ResponseCode(Integer code, String message, String status){
+        this.code = code;
+        this.message = message;
+        this.status = status;
+    }
+
+}

+ 115 - 0
gateway/pom.xml

@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.dongzili</groupId>
+        <artifactId>springcloud-base</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>gateway</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+            <version>${spring-cloud-alibaba.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.cloud</groupId>
+                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+        <!--网关依赖gateway-->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-gateway</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+        <!--SpringCloud-ailibaba-config配置中心-->
+        <!--        <dependency>-->
+        <!--            <groupId>com.alibaba.cloud</groupId>-->
+        <!--            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
+        <!--        </dependency>-->
+
+
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- springCloud -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- springCloudAlibaba -->
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.dongzili.gateway.XnyyEduGatewayApplication</mainClass>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 30 - 0
gateway/src/main/java/com/dongzili/GatewayApplication.java

@@ -0,0 +1,30 @@
+package com.dongzili;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * @描述:
+ * @作者: 96511
+ * @日期: 2024-12-21 22:48
+ */
+
+@EnableDiscoveryClient
+@SpringBootApplication
+public class GatewayApplication {
+    public static void main(String[] args) {
+//        SpringApplication.run(XnyyEduGatewayApplication.class, args);
+
+        ConfigurableApplicationContext applicationContext = SpringApplication.run(GatewayApplication.class, args);
+        String name = applicationContext.getEnvironment().getProperty("spring.cloud.gateway.routes[0].id");
+        String name1 = applicationContext.getEnvironment().getProperty("spring.cloud.gateway.routes[1].id");
+        String post = applicationContext.getEnvironment().getProperty("spring.cloud.gateway.discovery.locator.enabled");
+        System.out.println("name :"+name);
+        System.out.println("name1 :"+name1);
+        System.out.println("post :"+post);
+    }
+
+
+}

+ 23 - 0
gateway/src/main/java/com/dongzili/config/GatewayConfig.java

@@ -0,0 +1,23 @@
+package com.dongzili.config;
+
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class GatewayConfig {
+
+
+//    @Bean
+//    public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ClientIdRoutingFilterFactory clientIdRoutingFilterFactory) {
+//        return builder.routes()
+//                .route("route1", r -> r.path("/api/**")
+//                        .filters(f -> f.filter(clientIdRoutingFilterFactory.apply(new ClientIdRoutingFilterFactory.Config())))  // 使用工厂创建的过滤器
+//                        .uri("lb://default-service"))  // 默认服务
+//                .build();
+//    }
+
+//    @Bean
+//    public SaTokenFilterFactory saTokenFilterFactory(SaTokenFilter saTokenFilter) {
+//        return new SaTokenFilterFactory(saTokenFilter);
+//    }
+
+}

+ 85 - 0
gateway/src/main/java/com/dongzili/config/SatokenPredicateFactory.java

@@ -0,0 +1,85 @@
+package com.dongzili.config;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.config.GatewayProperties;
+import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.server.ServerWebExchange;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+@Component
+public class SatokenPredicateFactory extends AbstractRoutePredicateFactory<SatokenPredicateFactory.Config> {
+
+//    @Value("${spring.cloud.gateway.routes}")
+//    private RouteDefinition[] routes;
+
+    @Autowired
+    GatewayProperties gatewayProperties;
+
+    public SatokenPredicateFactory() {
+        super(Config.class);
+    }
+
+    public List<String> shortcutFieldOrder() {
+        return Arrays.asList("header", "regexp");
+    }
+
+    public Predicate<ServerWebExchange> apply(final Config config) {
+        final boolean hasRegex = !ObjectUtils.isEmpty(config.remainder);
+        return new GatewayPredicate() {
+            public boolean test(ServerWebExchange exchange) {
+                String satoken = exchange.getRequest().getHeaders().getFirst(config.header);
+                if (!StringUtils.hasText(satoken)){
+                    return true;
+                }
+                if(hasRegex){
+                    int count = gatewayProperties.getRoutes().size();
+                    int lastCharAscii = satoken.charAt(satoken.length() - 1);
+
+                    System.out.println("satoken最后一个字符的ascii码: " + lastCharAscii + " ");
+                    System.out.println("remainder=" + config.remainder +", 服务数量:"+count+", lastCharAscii%length = "+ lastCharAscii % count+" ");
+                    return lastCharAscii % count == config.remainder;
+                }
+                return true;
+            }
+
+            public Object getConfig() {
+                return config;
+            }
+
+            public String toString() {
+                return String.format("Header: %s regexp=%s", config.header, config.remainder);
+            }
+        };
+    }
+
+    @Getter
+    @Validated
+    public static class Config {
+        private @NotEmpty String header;
+//        private String regexp;
+        private Integer remainder;
+        public Config() {
+        }
+
+        public Config setHeader(String header) {
+            this.header = header;
+            return this;
+        }
+
+        public Config setRemainder(Integer remainder) {
+            this.remainder = remainder;
+            return this;
+        }
+    }
+
+}

+ 90 - 0
gateway/src/main/resources/application.yml

@@ -0,0 +1,90 @@
+server:
+  port: 8000
+spring:
+  profiles:
+    active: dev
+  application:
+    name: gateway
+  cloud:
+    nacos:
+      discovery:
+        # 开启nacos作为服务注册中心,默认值:true
+        enabled: true
+        # nacos集群服务注册地址
+        server-addr: 127.0.0.1:8848
+        # nacos注册地址命名空间
+        namespace: public
+        # nacos用户名
+        username: nacos
+        # nacos密码
+        password: nacos
+        # 命名空间,默认 public,可设置dev,pro等,相同特征的服务分类,先去nacos命名空间创建
+        #        namespace: public
+        # 分组,默认 DEFAULT_GROUP 相同特征的服务划分的更细
+        group: DEFAULT_GROUP
+        # 临时实例,默认true,false永久实例,即使宕机也不会从nacos服务中删除,可应对雪崩保护,避免服务被删除
+        ephemeral: true
+        # 权重 1-100 数值越大权重越大分配的流量就越大,通常结合权重负载均衡策略
+        weight: 100
+    #      config:
+    #        server-addr: ${spring.cloud.nacos.discovery.server-addr}
+    #        username: ${spring.cloud.nacos.discovery.username}
+    #        password: ${spring.cloud.nacos.discovery.password}
+    #        file-extension: yaml
+    #        group: DEFAULT_GROUP
+    #          # 共享配置
+    #        shared-configs:
+    #          - data-id: xnyy-config-${spring.profiles.active}.yaml
+    #            group: DEFAULT_GROUP
+    #            refresh: true  # 是否自动刷新配置,默认为 fals
+
+    gateway:
+      global-cors:
+        corsConfigurations:
+          '[/**]':
+            #            allowedOrigins: "https://docs.spring.io"
+            allowedMethods:
+              - GET
+              - POST
+      discovery:
+        locator:
+          enabled: true
+      routes:
+        - id: scb-demo-01
+          uri: 'lb://scb-demo-01'
+          predicates:
+            - Path=/api/**
+            - name: SatokenPredicateFactory
+              args:
+                header: satoken
+                remainder: 0
+            ## 按同一组名的权重进行负载均衡
+            - Weight=group1, 50
+#          filters:
+#            - name: SaTokenFilterFactory  # 使用我们自定义的过滤器
+        - id: scb-demo-02
+          uri: 'lb://scb-demo-02'
+          predicates:
+            - Path=/api/**
+            - name: SatokenPredicateFactory
+              args:
+                header: satoken
+                remainder: 1
+            ## 按同一组名的权重进行负载均衡
+            - Weight=group1, 50
+#          filters:
+#            - name: SaTokenFilterFactory   # 使用我们自定义的过滤器
+
+#        - id: xnyy-edu-backend03
+#          uri: 'lb://xnyy-edu-backend03'
+#          predicates:
+#            - Path=/api/**
+#            ## 按同一组名的权重进行负载均衡
+#            - Weight=group1, 10
+
+logging:
+  level:
+    org.springframework.cloud.gateway: DEBUG
+    org.springframework.cloud.loadbalancer: DEBUG
+
+

+ 98 - 0
pom.xml

@@ -0,0 +1,98 @@
+<?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.dongzili</groupId>
+    <artifactId>springcloud-base</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <modules>
+        <module>auth</module>
+        <module>app</module>
+        <module>data</module>
+        <module>entity</module>
+        <module>gateway</module>
+        <module>app02</module>
+    </modules>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+        <spring-boot.version>2.7.6</spring-boot.version>
+        <spring-cloud.version>2021.0.5</spring-cloud.version>
+        <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
+        <dynamic-datasource.version>3.5.2</dynamic-datasource.version>
+        <mybatis-plus.version>3.5.4</mybatis-plus.version>
+        <mybatis-typehandlers.version>1.0.2</mybatis-typehandlers.version>
+        <validation.version>2.7.6</validation.version>
+        <jackson-datatype.version>2.13.4</jackson-datatype.version>
+        <sqlite.version>3.36.0.3</sqlite.version>
+    </properties>
+
+    <dependencies>
+        <!--springboot基本场景启动器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <!--springboot测试场景启动器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.25</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+<!--            <version>${jackson-datatype.version}</version>-->
+        </dependency>
+
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <!-- springboot版本管理器 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-parent</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- springCloud -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- springCloudAlibaba -->
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+
+
+        </dependencies>
+    </dependencyManagement>
+</project>

BIN
visit.db