From 54e88191b3cb0d250ee3a3cfce65523b6f29f28c Mon Sep 17 00:00:00 2001
From: 谢茂盛 <A807732>
Date: Tue, 2 Apr 2024 14:17:24 +0800
Subject: [PATCH] feat: init项目

---
 pom.xml                                                                               |  213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/Application.java                                     |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/annotation/AnonymousAccess.java               |   15 +++++++++++++++
 src/main/java/com/canrd/webmagic/common/constant/Constant.java                        |  332 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/constant/ElAdminConstant.java                 |   25 +++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/constant/ServerResult.java                    |  232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/constant/ServerResultCode.java                |  114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/directory/Path.java                           |   39 +++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/exception/BadRequestException.java            |   26 ++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/exception/BusinessException.java              |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/exception/BusinessExceptionHandlerAdvice.java |   36 ++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/exception/EntityExistException.java           |   19 +++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/exception/EntityNotFoundException.java        |   19 +++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/exception/ErrorInfo.java                      |   11 +++++++++++
 src/main/java/com/canrd/webmagic/common/exception/handler/ApiError.java               |   39 +++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/exception/handler/GlobalExceptionHandler.java |  134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/jsr303/ListValueConstraintValidator.java      |   45 +++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/jsr303/OperateGroup.java                      |   77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/jsr303/annotation/ListIntValue.java           |   32 ++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/DateUtil.java                           |  591 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/DateUtils.java                          |  569 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/EncryptUtils.java                       |   84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/FileUtil.java                           |  360 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/JsonUtil.java                           |  183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/LocalDateTimeUtil.java                  |   35 +++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/PageUtils.java                          |  136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/RedisUtil.java                          | 1443 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/RedisUtils.java                         |  646 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/RequestContextUtil.java                 |   35 +++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/RequestHolder.java                      |   19 +++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/RequestStringUtils.java                 |  199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/ServletUtils.java                       |   40 ++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/SpringContextHolder.java                |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/StringUtils.java                        | 1005 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/TemplateFormatUtils.java                |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/ThrowableUtil.java                      |   36 ++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/common/utils/TransactionHelper.java                  |   27 +++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/config/AdminMetaObjectHandler.java                   |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/config/ConfigurerAdapter.java                        |   32 ++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/config/MybatisPlusConfig.java                        |    9 +++++++++
 src/main/java/com/canrd/webmagic/config/RedisConfig.java                              |   55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/config/RestTemplateConfig.java                       |   26 ++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/config/WebConfig.java                                |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/controller/TestController.java                       |   88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/domain/dto/BaseDO.java                               |   75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/domain/dto/TestDO.java                               |   32 ++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/domain/vo/BasePageVO.java                            |   43 +++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/domain/vo/TestQueryVO.java                           |   33 +++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/domain/vo/TestVO.java                                |   31 +++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/mapper/TestMapper.java                               |   16 ++++++++++++++++
 src/main/java/com/canrd/webmagic/processor/BaiduHotSearchPageProcessor.java           |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/processor/GithubRepoPageProcessor.java               |   38 ++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/processor/NatureSearchPageProcessor.java             |   69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/service/TestService.java                             |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/canrd/webmagic/service/impl/TestServiceImpl.java                    |  133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/application-local.yml                                              |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/application-prod.yml                                               |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/application-test.yml                                               |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/application.yml                                                    |    6 ++++++
 src/main/resources/ip2region/ip2region.db                                             | Bin 0 -> 6502265 bytes
 src/main/resources/log4j2-dev.xml                                                     |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/log4j2-prod.xml                                                    |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 62 files changed, 8753 insertions(+), 0 deletions(-)
 create mode 100644 pom.xml
 create mode 100644 src/main/java/com/canrd/webmagic/Application.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/annotation/AnonymousAccess.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/constant/Constant.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/constant/ElAdminConstant.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/constant/ServerResult.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/constant/ServerResultCode.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/directory/Path.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/BadRequestException.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/BusinessException.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/BusinessExceptionHandlerAdvice.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/EntityExistException.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/EntityNotFoundException.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/ErrorInfo.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/handler/ApiError.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/exception/handler/GlobalExceptionHandler.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/jsr303/ListValueConstraintValidator.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/jsr303/OperateGroup.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/jsr303/annotation/ListIntValue.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/DateUtil.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/DateUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/EncryptUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/FileUtil.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/JsonUtil.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/LocalDateTimeUtil.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/PageUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/RedisUtil.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/RedisUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/RequestContextUtil.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/RequestHolder.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/RequestStringUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/ServletUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/SpringContextHolder.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/StringUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/TemplateFormatUtils.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/ThrowableUtil.java
 create mode 100644 src/main/java/com/canrd/webmagic/common/utils/TransactionHelper.java
 create mode 100644 src/main/java/com/canrd/webmagic/config/AdminMetaObjectHandler.java
 create mode 100644 src/main/java/com/canrd/webmagic/config/ConfigurerAdapter.java
 create mode 100644 src/main/java/com/canrd/webmagic/config/MybatisPlusConfig.java
 create mode 100644 src/main/java/com/canrd/webmagic/config/RedisConfig.java
 create mode 100644 src/main/java/com/canrd/webmagic/config/RestTemplateConfig.java
 create mode 100644 src/main/java/com/canrd/webmagic/config/WebConfig.java
 create mode 100644 src/main/java/com/canrd/webmagic/controller/TestController.java
 create mode 100644 src/main/java/com/canrd/webmagic/domain/dto/BaseDO.java
 create mode 100644 src/main/java/com/canrd/webmagic/domain/dto/TestDO.java
 create mode 100644 src/main/java/com/canrd/webmagic/domain/vo/BasePageVO.java
 create mode 100644 src/main/java/com/canrd/webmagic/domain/vo/TestQueryVO.java
 create mode 100644 src/main/java/com/canrd/webmagic/domain/vo/TestVO.java
 create mode 100644 src/main/java/com/canrd/webmagic/mapper/TestMapper.java
 create mode 100644 src/main/java/com/canrd/webmagic/processor/BaiduHotSearchPageProcessor.java
 create mode 100644 src/main/java/com/canrd/webmagic/processor/GithubRepoPageProcessor.java
 create mode 100644 src/main/java/com/canrd/webmagic/processor/NatureSearchPageProcessor.java
 create mode 100644 src/main/java/com/canrd/webmagic/service/TestService.java
 create mode 100644 src/main/java/com/canrd/webmagic/service/impl/TestServiceImpl.java
 create mode 100644 src/main/resources/application-local.yml
 create mode 100644 src/main/resources/application-prod.yml
 create mode 100644 src/main/resources/application-test.yml
 create mode 100644 src/main/resources/application.yml
 create mode 100644 src/main/resources/ip2region/ip2region.db
 create mode 100644 src/main/resources/log4j2-dev.xml
 create mode 100644 src/main/resources/log4j2-prod.xml

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0b645cf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.2.5.RELEASE</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.canrd</groupId>
+    <artifactId>webmagic-canrd-service</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven-compiler-plugin>3.7.0</maven-compiler-plugin>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <joda-time.version>2.9.9</joda-time.version>
+        <springboot.version>2.2.5.RELEASE</springboot.version>
+        <lombok.version>1.18.12</lombok.version>
+        <baomidou.version>3.4.0</baomidou.version>
+        <hutool-crypto.version>5.6.1</hutool-crypto.version>
+        <hutool-all.version>5.0.6</hutool-all.version>
+        <mysql-connector.version>8.0.11</mysql-connector.version>
+        <druid.version>1.1.23</druid.version>
+        <fastjson.version>1.2.28</fastjson.version>
+        <poi.version>3.17</poi.version>
+        <poi-ooxml.version>3.17</poi-ooxml.version>
+        <poi-excel.version>poi-317.8</poi-excel.version>
+        <commons-csv.version>1.6</commons-csv.version>
+        <commons-lang3.version>3.8.1</commons-lang3.version>
+        <commons-pool2.version>2.11.1</commons-pool2.version>
+        <ip2region.version>1.7.2</ip2region.version>
+        <userAgentUtils.version>1.20</userAgentUtils.version>
+        <swagger.version>2.9.2</swagger.version>
+        <swagger-annotations.version>1.5.21</swagger-annotations.version>
+        <swagger-models.version>1.5.21</swagger-models.version>
+        <guava.version>20.0</guava.version>
+        <easy-captcha.version>1.6.2</easy-captcha.version>
+        <aliyun-java-sdk-core.version>4.0.3</aliyun-java-sdk-core.version>
+        <aliyun-sdk-oss.version>3.15.0</aliyun-sdk-oss.version>
+        <thumbnailator.version>0.4.8</thumbnailator.version>
+        <jjwt.version>0.10.6</jjwt.version>
+        <easyexcel.version>2.2.3</easyexcel.version>
+        <webmagic.version>0.10.0</webmagic.version>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>2.2.5.RELEASE</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- webmagic核心库 -->
+        <dependency>
+            <groupId>us.codecraft</groupId>
+            <artifactId>webmagic-core</artifactId>
+            <version>${webmagic.version}</version>
+        </dependency>
+
+
+        <!-- webmagic扩展库 -->
+        <dependency>
+            <groupId>us.codecraft</groupId>
+            <artifactId>webmagic-extension</artifactId>
+            <version>${webmagic.version}</version>
+        </dependency>
+
+
+        <!-- Lombok 依赖-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+            <version>${springboot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <version>${springboot.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${baomidou.version}</version>
+        </dependency>
+        <!--        <dependency>-->
+        <!--            <groupId>com.baomidou</groupId>-->
+        <!--            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>-->
+        <!--        </dependency>-->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql-connector.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-log4j2</artifactId>
+            <version>${springboot.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-crypto</artifactId>
+            <version>${hutool-crypto.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+            <version>${springboot.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+            <version>${commons-pool2.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>${commons-lang3.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+            <version>${ip2region.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>eu.bitwalker</groupId>
+            <artifactId>UserAgentUtils</artifactId>
+            <version>${userAgentUtils.version}</version>
+        </dependency>
+
+        <!--工具包-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool-all.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>${poi.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>${poi-ooxml.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>${joda-time.version}</version>
+        </dependency>
+
+
+
+    </dependencies>
+    <build>
+        <finalName>webmagic-canrd.service-1.0-SNAPSHOT</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+            </plugin>
+
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <executable>true</executable>
+                </configuration>
+            </plugin>
+
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/Application.java b/src/main/java/com/canrd/webmagic/Application.java
new file mode 100644
index 0000000..25cb130
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/Application.java
@@ -0,0 +1,56 @@
+package com.canrd.webmagic;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.canrd.webmagic.common.directory.Path;
+import com.canrd.webmagic.common.utils.SpringContextHolder;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * @author: xms
+ * @description:
+ * @date: 2024/1/12 14:00
+ * @version: 1.0
+ */
+@EnableAsync
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}, scanBasePackages = {"com.canrd.webmagic"})
+@MapperScan("com.canrd.webmagic.**.mapper")
+@EnableScheduling
+@EnableTransactionManagement
+public class Application {
+
+    private static void setLogPath() {
+        String appPath = Path.getAppPath(Application.class);
+        System.setProperty("logging.path", appPath);
+    }
+
+    @Bean
+    public SpringContextHolder springContextHolder() {
+        return new SpringContextHolder();
+    }
+
+    public static void main(String[] args) {
+        setLogPath();
+        SpringApplication.run(Application.class, args);
+    }
+
+    /**
+     * 分页插件
+     *
+     * @return
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
+        return interceptor;
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/annotation/AnonymousAccess.java b/src/main/java/com/canrd/webmagic/common/annotation/AnonymousAccess.java
new file mode 100644
index 0000000..bafd8c5
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/annotation/AnonymousAccess.java
@@ -0,0 +1,15 @@
+package com.canrd.webmagic.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 用于标记匿名访问方法
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnonymousAccess {
+
+}
diff --git a/src/main/java/com/canrd/webmagic/common/constant/Constant.java b/src/main/java/com/canrd/webmagic/common/constant/Constant.java
new file mode 100644
index 0000000..302791c
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/constant/Constant.java
@@ -0,0 +1,332 @@
+package com.canrd.webmagic.common.constant;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Constant {
+    /**
+     * 分隔符英文的横杠
+     */
+    public static final String CROSS_BAR_CHARACTER = "-";
+    /**
+     * 英文的 .
+     */
+    public static final String POINT_BAR_CHARACTER = ".";
+    /**
+     * 英文的 !
+     */
+    public static final String EXCLAMATION_MARK_CHARACTER = "!";
+    /**
+     * 英文的 /
+     */
+    public static final String SLASH_MARK_CHARACTER = "/";
+    /**
+     * 英文的 :
+     */
+    public static final String COLON_CHARACTER = ":";
+
+    /**
+     * 英文的 ;
+     */
+    public static final String SEMICOLON_CHARACTER = ";";
+
+    /**
+     * 英文的逗号
+     */
+    public static final String COMMA_CHARACTER = ",";
+    /**
+     * 英文的*号
+     */
+    public static final String START_CHARACTER = "*";
+    /**
+     * 分隔符
+     */
+    public static final String SPLIT_SYMBOL = ",|,";
+
+    /**
+     * 特殊分隔符 _
+     */
+    public static final String SPECIAL_KEY = "_";
+
+
+    /**
+     * 括号
+     */
+    public static final String BRACKETS_RIGHT = "]";
+    /**
+     * 括号
+     */
+    public static final String BRACKETS_LEFT = "[";
+
+    /**
+     * 手机号码正则校验
+     */
+    public static final String PHONE_REGEXP = "^[1][3,4,5,6,7,8,9][0-9]{9}$";
+
+    /**
+     * 脱敏手机号
+     */
+    public static final String PHONE_DESENSITIZATION_REGEXP = "^[1][3,4,5,6,7,8,9][0-9][*]{4}[0-9]{4}$";
+
+    /**
+     * 邮箱
+     */
+    public static final String EMAIL_REGEXP = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$";
+
+    /**
+     * 三位数字
+     */
+    public static final String THREE_DIGITS_REGEXP = "\\d{3}";
+
+    /**
+     * 中文 英文 数字
+     */
+    public static final String CHI_EN_NUM_REGEXP = "^[A-z0-9\\u4e00-\\u9fa5]*$";
+
+
+    /**
+     * 仅包含英文和数字
+     */
+    public static final String EN_REGEXP = "^[a-zA-Z]+$";
+
+    public static final String EN_NUM_REGEXP = "^[a-z0-9A-Z]+$";
+
+    public static final String LINE_EN_NUM_REGEXP = "^[a-z0-9A-Z\\-]+$";
+
+    /**
+     * 仅包含英文和中文
+     */
+    public static final String CHI_EN_REGEXP = "^[A-z\\u4e00-\\u9fa5]*$";
+
+    /**
+     * 纯数字
+     */
+    public static final String NUMERIC_REGEXP = "^\\d+$";
+    /**
+     * 纯中文
+     */
+    public static final String CHI_REGEXP = "^[\u4e00-\u9fa5]+$";
+    /**
+     * 不超过两位小数
+     */
+    public static final String DICMAL_REGEXP = "^(([1-9]{1}\\d*)|([0]{1}))(\\.(\\d){0,3})?$";
+
+    /**
+     * 默认空字符串
+     */
+    public static final String EMPTY_STRING = "";
+
+    /**
+     * 统一返回data的key
+     */
+    public static final String RESULT_CHARACTER = "result";
+
+    /**
+     * 统一返回data的key
+     */
+    public static final String SUCCESS_RESULT_CHARACTER = "success";
+
+    /**
+     * 返回的默认结果集
+     */
+    public static final String RESULT_FAIL = null;
+
+    /**
+     * ENABLE_FLAG
+     */
+    public static final String ENABLE_FLAG = "enable_flag";
+
+    /**
+     * 是否可用 10-可用 20-删除
+     */
+    public static final int ENABLE_TEN = 10;
+    /**
+     * 是否可用 10-可用 20-删除
+     */
+    public static final int UNABLE_TWENTY = 20;
+
+    public static final int ONE = 1;
+
+    public static final Integer INTEGER_ONE = 1;
+
+    public static final int TWO = 2;
+
+    public static final int THREE = 3;
+
+    public static final int FOUR = 4;
+
+    public static final int FIVE = 5;
+
+    public static final int SIX = 6;
+
+    public static final int SEVEN = 7;
+
+    public static final int THIRTY = 30;
+
+    public static final int NINETEEN = 19;
+
+    public static final int THOUSAND = 1000;
+
+    public static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss";
+
+    public static final int ZERO = 0;
+
+    public static final String STRING_ZERO = "0";
+
+    public static final String ZERO_STRING = "0";
+
+    public static final String THOUSAND_STRING = "1000";
+
+    public static final String SYSTEM_USER = "System";
+
+    public static final String percent50 = "0.5";
+
+    public static final String DELETE_SUCCESS_RESULT_CHARACTER = "删除成功";
+
+
+    /**
+     * token rediskey
+     */
+    public static final String TOKEN_FLAG = "token:";
+    /**
+     * token 限制api请求
+     */
+    public static final String PERMISSION_API_LIMIT = "token_api_limit:";
+
+    /**
+     * token过期时间
+     */
+    public static final Integer TOKEN_EXPIRE_HOURS = 1;
+    /**
+     * 密码3次错误锁定
+     */
+    public static final int LOCK_ERROR_TIMES = 3;
+
+    /**
+     * session字段
+     */
+    public static final String HEAD_TOKEN = "Authorization";
+    public static final String ACCESS_TOKEN = "token";
+    public static final String REQUEST_ORIGIN = "source";
+    public static final String REQUEST_IP = "ip";
+    public static final String USER_INFO = "userInfo";
+    public static final String FILED_ID = "id";
+    public static final String FILED_USER_NAME = "username";
+    public static final String FILED_DEVICE_NUM = "deviceNum";
+
+    /**
+     * 请求来源
+     */
+    public static final String ORIGIN_WEB = "WEB";
+    public static final String ORIGIN_PDA = "PDA";
+
+    /**
+     * 1001 参数校验不通过
+     * 1002 签名验证不通过
+     */
+    public static final List<String> ERROR_CODE_LIST = Arrays.asList("1001", "1002");
+
+    /**
+     * 分布式锁超时时间
+     */
+    public static final long REDIS_LOCK_DEFAULT_TIME_OUT_MILLIS = 3000;
+    public static final long REDIS_LOCK_FIVE_SECONDS_TIME_OUT_MILLIS = 5000;
+
+    /**
+     * 系统来源WMS
+     */
+    public static final String SYS_ORIGIN_WMS = "WMS";
+
+    public static final String STRING_ONE = "1";
+
+
+    public static final String STRING_TWO = "2";
+
+    public static final String STRING_THREE = "3";
+
+    public static final String STRING_FOUR = "4";
+
+    public static final String STRING_FIVE = "5";
+
+    public static final String STRING_SIX = "6";
+
+    public static final String STRING_SEVEN = "7";
+
+    public static final String STRING_EIGHT = "8";
+
+    public static final String STRING_NINE = "9";
+
+    public static final String STRING_TEN = "10";
+
+    public static final String STRING_ELEVEN = "11";
+
+    public static final String STRING_TWELVE = "12";
+
+    /**
+     * 异常延时队列后缀
+     */
+    public static final String ERROR_DELAY_QUEUE_SUFFIX = "error";
+
+    /**
+     * 推送出库数据延时队列 redis key prefix
+     */
+    public static final String OUTBOUND_NOTIFY_DELAY_QUEUE_PREFIX = "outbound:notify:";
+
+    /**
+     * 同步MES库存队列 redis key prefix
+     */
+    public static final String MES_STOCK_SYNC_QUEUE_PREFIX = "mes:stock_sync:";
+
+    /**
+     * 分拣详情 错误码: 157205   返回值段
+     */
+    public static final String SEPARATE_PICK_QUERY_RETURN_FIELD = "separatePickQuery";
+    /**
+     * 限制上限值
+     */
+    public static final Integer LIMIT_999 = 999;
+
+    /**
+     * 东八区
+     */
+    public static final String GMT_8 = "GMT+8";
+
+    /**
+     * 英文的 "
+     */
+    public static final String QUOTATION_MARK_CHARACTER = "\"";
+
+
+    /**
+     * 英文的 \"
+     */
+    public static final String QUOTATION_SLASH_MARK_CHARACTER = "\\\"";
+
+    public static final String SUCCESS_VALUE = "success";
+
+
+    public static final String TRUE = "true";
+
+    public static final String ROOT_PARENT_ID = "0";
+
+    public static final Long ROOT_PARENT_ID_LONG = 0L;
+
+
+    /**
+     * 短信验证码 redis key
+     */
+    public static final String SMS_AUTH_CODE_PREFIX = "sms:auth:code:";
+
+    public static final String DEFAULT_PASSWORD = "JBXT123456";
+
+    /**
+     * 账号在线队列 redis key
+     */
+    public static final String ACCOUNT_ONLINE_LIST = "account:online:";
+
+    /**
+     * 缓存导入excel错误信息 redis key
+     */
+    public static final String EXCEL_IMPORT_ERROR_PREFIX = "excel:import:error:";
+
+}
diff --git a/src/main/java/com/canrd/webmagic/common/constant/ElAdminConstant.java b/src/main/java/com/canrd/webmagic/common/constant/ElAdminConstant.java
new file mode 100644
index 0000000..f2dd076
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/constant/ElAdminConstant.java
@@ -0,0 +1,25 @@
+package com.canrd.webmagic.common.constant;
+
+/**
+ * 常用静态常量
+ *
+ * @date 2018-12-26
+ */
+public class ElAdminConstant {
+
+    public static final String RESET_PASS = "重置密码";
+
+    public static final String RESET_MAIL = "重置邮箱";
+
+    /**
+     * 用于IP定位转换
+     */
+    public static final String REGION = "内网IP|内网IP";
+
+    /**
+     * 常用接口
+     */
+    public static class Url {
+        public static final String SM_MS_URL = "https://sm.ms/api";
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/constant/ServerResult.java b/src/main/java/com/canrd/webmagic/common/constant/ServerResult.java
new file mode 100644
index 0000000..72a6df6
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/constant/ServerResult.java
@@ -0,0 +1,232 @@
+package com.canrd.webmagic.common.constant;
+
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Date: 2020/9/5
+ * 统一的返回结果实体
+ */
+public class ServerResult<T> implements Serializable {
+    /**
+     * 序列化ID
+     */
+    private static final long serialVersionUID = -5809782578272943999L;
+    /**
+     * 返回的状态码
+     */
+    private int result = ServerResultCode.SUCCESS.getErrorCode();
+    /**
+     * 返回的消息
+     */
+    private String message = ServerResultCode.SUCCESS.getErrorDesc();
+    /**
+     * 返回的数据实体
+     */
+    private T data = null;
+    /**
+     *
+     */
+    final int INIT_SIZE = 128;
+
+    /**
+     *
+     */
+    public ServerResult() {
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buffer = new StringBuilder(INIT_SIZE);
+        buffer.append("{\"result\":").append(this.result);
+        buffer.append(",\"message\":\"").append(this.message != null ? message : "").append("\"");
+        buffer.append(",\"data\":").append(data).append("}");
+        return buffer.toString();
+    }
+
+    public int getResult() {
+        return result;
+    }
+
+    public ServerResult setResult(int result) {
+        this.result = result;
+        return this;
+    }
+
+    public ServerResult setResult(ServerResultCode serverResultCode) {
+        this.result = serverResultCode.getErrorCode();
+        this.message = serverResultCode.getErrorDesc();
+        return this;
+    }
+
+    public ServerResult setResult(int errorCode, String errorDesc) {
+        this.result = errorCode;
+        this.message = errorDesc;
+        return this;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public ServerResult<T> setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public ServerResult setData(T data) {
+        this.data = data;
+        return this;
+    }
+
+    public static <T> ServerResult<T> success() {
+        return new ServerResult()
+                .setResult(ServerResultCode.SUCCESS)
+                .setMessage(ServerResultCode.SUCCESS.getErrorDesc())
+                .setData(defaultResult(ServerResultCode.SUCCESS));
+    }
+
+    public static <T> ServerResult<T> success(T data) {
+        return new ServerResult()
+                .setResult(ServerResultCode.SUCCESS)
+                .setMessage(ServerResultCode.SUCCESS.getErrorDesc())
+                .setData(data);
+    }
+
+    public static <T> ServerResult<T> success(T data, String message) {
+        return new ServerResult()
+                .setResult(ServerResultCode.SUCCESS)
+                .setMessage(message)
+                .setData(data);
+    }
+
+    /**
+     * @description 错误码使用系统错误 1000 错误类型自定义
+     * @author dengbin
+     * @date 2020/9/12
+     */
+    public static <T> ServerResult<T> fail(String message) {
+        return new ServerResult()
+                .setResult(ServerResultCode.FAIL)
+                .setMessage(message)
+                .setData(defaultResult(Constant.RESULT_FAIL));
+    }
+
+    /**
+     * 错误时需要返回相关数据时使用
+     *
+     * @param data
+     * @param <T>
+     * @return
+     */
+    public static <T> ServerResult<T> fail(T data, ServerResultCode serverResultCode) {
+        return new ServerResult()
+                .setData(data)
+                .setResult(serverResultCode)
+                .setMessage(serverResultCode.getErrorDesc());
+    }
+
+    /**
+     * 错误时需要返回相关数据时使用(不设值 data)
+     *
+     * @param sr
+     * @param
+     * @return
+     */
+    public static <T> ServerResult<T> fail(ServerResult<?> sr) {
+        return new ServerResult<>()
+                .setMessage(sr.getMessage())
+                .setResult(sr.getResult());
+
+    }
+
+    /**
+     * @description 错误码使用上面的枚举定义的,错误信息重新定义
+     * @author dengbin
+     * @date 2020/9/12
+     */
+    public static <T> ServerResult<T> fail(ServerResultCode serverResultCode, String msg) {
+        return new ServerResult()
+                .setResult(serverResultCode)
+                .setMessage(msg)
+                .setData(defaultResult(Constant.RESULT_FAIL));
+    }
+
+    /**
+     * @return
+     * @description 直接选用上面的枚举错误码,输出定义的错误信息
+     * @author dengbin
+     * @date 2020/9/12
+     */
+    public static <T> ServerResult<T> fail(ServerResultCode serverResultCode) {
+        return new ServerResult()
+                .setResult(serverResultCode)
+                .setMessage(serverResultCode.getErrorDesc())
+                .setData(defaultResult(Constant.RESULT_FAIL));
+    }
+
+    /**
+     * 错误时需要返回相关数据时使用
+     *
+     * @param data
+     * @param <T>
+     * @return
+     */
+    public static <T> ServerResult<T> fail(T data, int errorCode, String errorDesc) {
+        return new ServerResult()
+                .setData(data)
+                .setResult(errorCode, errorDesc);
+    }
+
+
+    public static <T> ServerResult<T> fail() {
+        return new ServerResult()
+                .setResult(ServerResultCode.FAIL.getErrorCode())
+                .setMessage(ServerResultCode.FAIL.getErrorDesc())
+                .setData(defaultResult(Constant.RESULT_FAIL));
+    }
+
+
+    /**
+     * @description 直接选用上面的枚举错误码,输出定义的错误信息
+     * @author dengbin
+     * @date 2020/9/12
+     */
+    public ServerResult failEnum(ServerResultCode serverResultCode) {
+        setResult(serverResultCode);
+        setMessage(serverResultCode.getErrorDesc());
+        return this;
+    }
+
+    public static <T> ServerResult<T> fail(int errorCode, String errorDesc) {
+        return new ServerResult()
+                .setResult(errorCode, errorDesc)
+                .setData(defaultResult(Constant.RESULT_FAIL));
+
+    }
+
+    /**
+     * 判断当前的状态码是否是SUCCESS
+     *
+     * @return 结果 ture or false
+     */
+    public boolean checkSuccess() {
+        return result == ServerResultCode.SUCCESS.getErrorCode();
+    }
+
+    public boolean checkNotSuccess() {
+        return !checkSuccess();
+    }
+
+    private static Map<String, Object> defaultResult(Object object) {
+        Map<String, Object> map = new HashMap<>(Constant.ONE);
+        map.put(Constant.RESULT_CHARACTER, null);
+        return map;
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/constant/ServerResultCode.java b/src/main/java/com/canrd/webmagic/common/constant/ServerResultCode.java
new file mode 100644
index 0000000..af50baa
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/constant/ServerResultCode.java
@@ -0,0 +1,114 @@
+package com.canrd.webmagic.common.constant;
+
+
+import com.canrd.webmagic.common.exception.ErrorInfo;
+import lombok.Setter;
+
+
+/**
+ * 请求成功 返回 0
+ */
+public enum ServerResultCode implements ErrorInfo {
+    //成功 "0"
+    SUCCESS(0, "成功"),
+    FAIL(1000, "系统内部错误,请联系业务系统运维管理员"),
+    FIlE_UPLOAD_TOO_LARGE(1051, "上传文件太大,图片文件一般小于2兆"),
+
+    UNAUTHORIZED(401, "登录状态过期"),
+
+    //空指针异常
+    NULL_POINT(1001, "空指针异常"),
+
+    //校验异常
+    RUN_ERROR(1002, "程序运行报错"),
+    ILLEGAL_ARGUMENT(1003, "参数非法"),
+    FILED_ERROR(1004, "传入参数错误"),
+    PARAM_ERROR(1005, "入参为空"),
+    EMPTY_RESULT(1006, "数据不存在"),
+    EMPTY_LIST(1007, "查询结果为空"),
+    IMG_CAPTCHA_ERROR(1008, "图片验证码错误"),
+    IMG_CAPTCHA_EXPIRE_ERROR(1009, "图片验证码不存在或已过期"),
+    SMS_CAPTCHA_ERROR(1010, "短信验证码错误"),
+    SMS_CAPTCHA_EXPIRE_ERROR(1011, "短信验证码不存在或已过期"),
+
+    //认证授权异常
+    UNAUTHENTICATION(401, "未登录"),
+
+
+    //用户
+    USER_NOT_EXIT(20001, "用户不存在"),
+    USER_UN_ENABLE(20002, "用户未激活"),
+
+
+    // 公司
+    COMPANY_NOT_EXIT(30001, "公司不存在"),
+
+    // 短信
+    SMS_SEND_OVER_LIMIT_ERROR(40001, "发送频次过高,请一分钟后再发送"),
+    EMAIL_SEND_OVER_LIMIT_ERROR(40002, "发送频次过高(一天最多修改三次),请第二天后再发送"),
+
+    // 会员
+    MEMBER_PHONE_REGISTER_ERROR(50001, "手机已经被注册使用!"),
+    MEMBER_AGREE_AGREEMENT_CHOOSE_ERROR(50002, "必须同意注册协议才可进行注册操作!"),
+    MEMBER_CONFIRM_PASSWORD_ERROR(50003, "两次密码输入不一致!"),
+    MEMBER_LOGIN_PHONE_ERROR(50004, "手机号没有注册,请注册后登录!"),
+    MEMBER_LOGIN_LOCKED_TIME_ERROR(50005, "账号被锁定,请稍后再试试!"),
+    MEMBER_RECOVER_PASSWORD_ERROR(50006, "对不起,此密码找回链接已失效!"),
+    MEMBER_RECOVER_PASSWORD_EXPIRE_ERROR(50007, "对不起,此密码找回链接已过期!"),
+
+    //产品
+    PRODUCT_NOT_EXIST_ERROR(60001, "此商品已下架!"),
+    PRODUCT_NOT_MORE_STORE_ERROR(60002, "添加购物车失败,商品库存不足!"),
+
+    //订单
+    ORDER_BASE_INFO_EMPTY(70001, "订单基础信息不能为空!"),
+
+    //申请
+    APPLY_UNLOCK_FIELD_EXIST(80001, "还有未审批完结的申请,请等上一个申请单完结!"),
+    APPLY_NOT_EXIST(80002, "申请单不存在!"),
+
+    //上传图片
+    UPLOAD_IMAGES_ERROR(900021, "上传图片失败!"),
+    ;
+
+
+    ServerResultCode(Integer errorCode, String errorDesc) {
+        this.errorCode = errorCode;
+        this.errorDesc = errorDesc;
+    }
+
+    @Setter
+    private Integer errorCode;
+    @Setter
+    private String errorDesc;
+
+    @Override
+    public Integer getErrorCode() {
+        return this.errorCode;
+    }
+
+    @Override
+    public String getErrorDesc() {
+        return this.errorDesc;
+    }
+
+    /**
+     * 根据errorCode获得枚举
+     *
+     * @param errorCode
+     * @return
+     */
+    public static String getDescByCode(Integer errorCode) {
+        if (errorCode == null) {
+            return null;
+        }
+        ServerResultCode[] serverResults = values();
+        for (ServerResultCode serverResult : serverResults) {
+            if (serverResult.getErrorCode().equals(errorCode)) {
+                return serverResult.getErrorDesc();
+            }
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/common/directory/Path.java b/src/main/java/com/canrd/webmagic/common/directory/Path.java
new file mode 100644
index 0000000..ed2131f
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/directory/Path.java
@@ -0,0 +1,39 @@
+package com.canrd.webmagic.common.directory;
+
+import com.canrd.webmagic.common.constant.Constant;
+
+/**
+ * @author xms
+ */
+public class Path {
+    /**
+     * 获取程序启动路径
+     *
+     * @param cls,建议直接传 ServiceApplication.class
+     * @return 应用启动路径
+     */
+    public static String getAppPath(Class cls) {
+        String path = cls.getResource(Constant.SLASH_MARK_CHARACTER).getPath();
+        String os = System.getProperty("os.name").toLowerCase();
+        final String OSWINDOW = "windows";
+        if (os.indexOf(OSWINDOW) != -1 && path.length() > 1) {
+            //windows路径样例: /D:/work/code/shop-services/online-shop-oss/target/classes/
+            return path.substring(1);
+        }
+
+        //linux 路径样例 file:/opt/target/online-shop-oss-SNAPSHOT.jar!/BOOT-INF/classes!/";
+        final String FILE = "file:";
+        if (path.indexOf(FILE) != -1) {
+            path = path.substring(FILE.length());
+        }
+
+        int pos = -1;
+        if ((pos = path.indexOf(Constant.EXCLAMATION_MARK_CHARACTER)) != -1) {
+            if (-1 != (pos = path.lastIndexOf(Constant.SLASH_MARK_CHARACTER, pos))) {
+                path = path.substring(0, pos + 1);
+            }
+        }
+
+        return path;
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/exception/BadRequestException.java b/src/main/java/com/canrd/webmagic/common/exception/BadRequestException.java
new file mode 100644
index 0000000..21a78cf
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/BadRequestException.java
@@ -0,0 +1,26 @@
+package com.canrd.webmagic.common.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+
+/**
+ *
+ * @date 2018-11-23
+ * 统一异常处理
+ */
+@Getter
+public class BadRequestException extends RuntimeException {
+
+    private Integer status = BAD_REQUEST.value();
+
+    public BadRequestException(String msg) {
+        super(msg);
+    }
+
+    public BadRequestException(HttpStatus status, String msg) {
+        super(msg);
+        this.status = status.value();
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/exception/BusinessException.java b/src/main/java/com/canrd/webmagic/common/exception/BusinessException.java
new file mode 100644
index 0000000..ed77ab2
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/BusinessException.java
@@ -0,0 +1,78 @@
+package com.canrd.webmagic.common.exception;
+
+import com.canrd.webmagic.common.constant.ServerResult;
+import com.canrd.webmagic.common.constant.ServerResultCode;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * <p>基础异常类,所有自定义异常类都需要继承本类</p>
+ */
+@Slf4j
+public class BusinessException extends RuntimeException {
+
+    private static final long serialVersionUID = 3152549963899218489L;
+    @Setter
+    @Getter
+    private String errorDesc;
+    @Setter
+    @Getter
+    private Integer errorCode;
+    @Setter
+    @Getter
+    private Object data;
+
+    public BusinessException() {
+        super(ServerResultCode.FAIL.getErrorDesc());
+        this.errorCode = ServerResultCode.FAIL.getErrorCode();
+        this.errorDesc = ServerResultCode.FAIL.getErrorDesc();
+    }
+
+    public BusinessException(int errorCode, String errorDesc) {
+        super(errorDesc);
+        this.errorCode = errorCode;
+        this.errorDesc = errorDesc;
+    }
+
+    public BusinessException(ServerResultCode serverResultCode) {
+        super(serverResultCode.getErrorDesc());
+        this.errorCode = serverResultCode.getErrorCode();
+        this.errorDesc = serverResultCode.getErrorDesc();
+        log.error("业务异常: ", this);
+    }
+
+    public BusinessException(ErrorInfo errorInfo) {
+        super(errorInfo.getErrorDesc());
+        this.errorCode = errorInfo.getErrorCode();
+        this.errorDesc = errorInfo.getErrorDesc();
+        log.error("业务异常: ", this);
+    }
+
+    public BusinessException(ServerResult<?> serverResult) {
+        super(serverResult == null ? ServerResultCode.FAIL.getErrorDesc() : serverResult.getMessage());
+        if (serverResult == null) {
+            this.errorCode = ServerResultCode.FAIL.getErrorCode();
+            this.errorDesc = ServerResultCode.FAIL.getErrorDesc();
+        } else {
+            this.errorCode = serverResult.getResult();
+            this.errorDesc = serverResult.getMessage();
+            this.data = serverResult.getData();
+        }
+        log.error("业务异常: ", this);
+    }
+
+    public BusinessException(String message) {
+        super(message);
+    }
+
+    public BusinessException(Throwable cause) {
+        super(cause);
+        log.error("业务异常: ", this);
+    }
+
+    public BusinessException(String message, Throwable cause) {
+        super(message, cause);
+        log.error("业务异常: ", this);
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/exception/BusinessExceptionHandlerAdvice.java b/src/main/java/com/canrd/webmagic/common/exception/BusinessExceptionHandlerAdvice.java
new file mode 100644
index 0000000..2bd7acb
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/BusinessExceptionHandlerAdvice.java
@@ -0,0 +1,36 @@
+package com.canrd.webmagic.common.exception;
+
+import com.canrd.webmagic.common.constant.ServerResult;
+import com.canrd.webmagic.common.constant.ServerResultCode;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import java.util.Optional;
+
+/**
+ * 业务异常统一处理类
+ *
+ * @date 2021/1/11
+ */
+@ControllerAdvice
+@Order(5)
+public class BusinessExceptionHandlerAdvice {
+
+
+    @ExceptionHandler(value = {BusinessException.class})
+    @ResponseStatus(value = HttpStatus.OK)
+    public ResponseEntity<Object> handleException(BusinessException exception) throws Exception {
+        return new ResponseEntity<Object>(handleBusinessException(exception), HttpStatus.OK);
+    }
+
+    private ServerResult handleBusinessException(BusinessException exception) {
+        Integer code = Optional.ofNullable(exception.getErrorCode()).orElse(ServerResultCode.FAIL.getErrorCode());
+        String msg = Optional.ofNullable(exception.getMessage()).orElse(ServerResultCode.FAIL.getErrorDesc());
+        Object data = Optional.ofNullable(exception.getData()).orElse(null);
+        return ServerResult.fail(data, code, msg);
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/exception/EntityExistException.java b/src/main/java/com/canrd/webmagic/common/exception/EntityExistException.java
new file mode 100644
index 0000000..58fc391
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/EntityExistException.java
@@ -0,0 +1,19 @@
+package com.canrd.webmagic.common.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ *
+ * @date 2018-11-23
+ */
+public class EntityExistException extends RuntimeException {
+
+    public EntityExistException(Class clazz, String field, String val) {
+        super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val));
+    }
+
+    private static String generateMessage(String entity, String field, String val) {
+        return StringUtils.capitalize(entity)
+                + " with " + field + " " + val + " existed";
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/common/exception/EntityNotFoundException.java b/src/main/java/com/canrd/webmagic/common/exception/EntityNotFoundException.java
new file mode 100644
index 0000000..a02574d
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/EntityNotFoundException.java
@@ -0,0 +1,19 @@
+package com.canrd.webmagic.common.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ *
+ * @date 2018-11-23
+ */
+public class EntityNotFoundException extends RuntimeException {
+
+    public EntityNotFoundException(Class clazz, String field, String val) {
+        super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val));
+    }
+
+    private static String generateMessage(String entity, String field, String val) {
+        return StringUtils.capitalize(entity)
+                + " with " + field + " " + val + " does not exist";
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/common/exception/ErrorInfo.java b/src/main/java/com/canrd/webmagic/common/exception/ErrorInfo.java
new file mode 100644
index 0000000..c9d2ccf
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/ErrorInfo.java
@@ -0,0 +1,11 @@
+package com.canrd.webmagic.common.exception;
+
+/**
+ * @date 2023-01-12
+ */
+public interface ErrorInfo {
+
+    Integer getErrorCode();
+
+    String getErrorDesc();
+}
diff --git a/src/main/java/com/canrd/webmagic/common/exception/handler/ApiError.java b/src/main/java/com/canrd/webmagic/common/exception/handler/ApiError.java
new file mode 100644
index 0000000..5af0dd8
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/handler/ApiError.java
@@ -0,0 +1,39 @@
+package com.canrd.webmagic.common.exception.handler;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ *
+ * @date 2018-11-23
+ */
+@Data
+class ApiError {
+
+    private Integer status = 200;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime timestamp;
+    private String message;
+    private String errorCode;
+
+    private ApiError() {
+        timestamp = LocalDateTime.now();
+    }
+
+    public static ApiError error(String message){
+        ApiError apiError = new ApiError();
+        apiError.setMessage(message);
+        return apiError;
+    }
+
+    public static ApiError error(Integer status, String message){
+        ApiError apiError = new ApiError();
+        apiError.setStatus(status);
+        apiError.setMessage(message);
+        return apiError;
+    }
+}
+
+
diff --git a/src/main/java/com/canrd/webmagic/common/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/canrd/webmagic/common/exception/handler/GlobalExceptionHandler.java
new file mode 100644
index 0000000..4d54389
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/exception/handler/GlobalExceptionHandler.java
@@ -0,0 +1,134 @@
+package com.canrd.webmagic.common.exception.handler;
+
+import com.canrd.webmagic.common.constant.ServerResult;
+import com.canrd.webmagic.common.constant.ServerResultCode;
+import com.canrd.webmagic.common.exception.BusinessException;
+import com.canrd.webmagic.common.exception.EntityExistException;
+import com.canrd.webmagic.common.exception.EntityNotFoundException;
+import com.canrd.webmagic.common.utils.ThrowableUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+
+import java.util.Objects;
+
+/**
+ * @date 2018-11-23
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+    /**
+     * @param exception
+     * @return
+     * @throws Exception
+     */
+    @ExceptionHandler(value = RuntimeException.class)
+    public ResponseEntity<Object> handleServiceException(Exception exception) throws Exception {
+        return new ResponseEntity(translateException(exception), HttpStatus.OK);
+    }
+
+    /**
+     * @param exception
+     * @return
+     * @throws Exception
+     */
+    @ExceptionHandler(value = Exception.class)
+    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
+    public ResponseEntity<Object> handleException(Exception exception) throws Exception {
+        return new ResponseEntity<Object>(translateException(exception), HttpStatus.OK);
+    }
+
+
+    /**
+     * 处理所有接口数据验证异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<ServerResult> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
+        String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+        String msg = "不能为空";
+        if (msg.equals(message)) {
+            message = str[1] + ":" + message;
+        }
+        return buildResponseEntity(ServerResult.fail(message));
+    }
+
+    /**
+     * 处理自定义异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(value = BusinessException.class)
+    public ResponseEntity<ServerResult> badRequestException(BusinessException e) {
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ServerResult.fail(e.getErrorCode(), e.getMessage()));
+    }
+
+    /**
+     * 处理 EntityExist
+     */
+    @ExceptionHandler(value = EntityExistException.class)
+    public ResponseEntity<ServerResult> entityExistException(EntityExistException e) {
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ServerResult.fail(e.getMessage()));
+    }
+
+    /**
+     * 处理 EntityNotFound
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(value = EntityNotFoundException.class)
+    public ResponseEntity<ServerResult> entityNotFoundException(EntityNotFoundException e) {
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ServerResult.fail(HttpStatus.NOT_FOUND.value(), e.getMessage()));
+    }
+
+    /**
+     * @param serverResult
+     * @return
+     */
+    private ResponseEntity<ServerResult> buildResponseEntity(ServerResult serverResult) {
+        return new ResponseEntity<>(serverResult, HttpStatus.valueOf(200));
+    }
+
+    /**
+     * @param e
+     * @return
+     * @throws Exception
+     */
+    private Object translateException(Exception e) throws Exception {
+        ServerResult serverResult = null;
+        if (e instanceof NullPointerException) {
+            serverResult = ServerResult.fail(ServerResultCode.NULL_POINT);
+        } else if (e instanceof IllegalArgumentException) {
+            serverResult = ServerResult.fail(ServerResultCode.FAIL);
+        } else if (e instanceof MaxUploadSizeExceededException) {
+            serverResult = ServerResult.fail(ServerResultCode.FIlE_UPLOAD_TOO_LARGE);
+        } else if (e instanceof BusinessException) {
+            serverResult = ServerResult.fail(((BusinessException) e).getErrorCode(), ((BusinessException) e).getErrorDesc());
+        } else {
+            serverResult = ServerResult.fail(ServerResultCode.FAIL);
+            serverResult.setMessage(serverResult.getMessage() + "|,业务异常:" + e.getMessage());
+        }
+
+        log.error("业务异常", e);
+        return serverResult;
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/jsr303/ListValueConstraintValidator.java b/src/main/java/com/canrd/webmagic/common/jsr303/ListValueConstraintValidator.java
new file mode 100644
index 0000000..032ec30
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/jsr303/ListValueConstraintValidator.java
@@ -0,0 +1,45 @@
+package com.canrd.webmagic.common.jsr303;
+
+
+import com.canrd.webmagic.common.jsr303.annotation.ListIntValue;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 自定义校验注解 -枚举值校验
+ *
+ * @author fanzhenyu
+ * @date 2023-01-15
+ */
+public class ListValueConstraintValidator implements ConstraintValidator<ListIntValue, Integer> {
+    private Set<Integer> set = new HashSet<>();
+    // 是否必填
+    private boolean require;
+
+    @Override
+    public void initialize(ListIntValue constraintAnnotation) {
+        for (int i : constraintAnnotation.enumValue()) {
+            set.add(i);
+        }
+        require = constraintAnnotation.require();
+    }
+
+    /**
+     * 判断是否通过校验
+     *
+     * @param value   传入的值
+     * @param context
+     * @return
+     */
+    @Override
+    public boolean isValid(Integer value, ConstraintValidatorContext context) {
+        if (!require && value == null) {
+            return true;
+        }
+        return set.contains(value);
+    }
+
+}
diff --git a/src/main/java/com/canrd/webmagic/common/jsr303/OperateGroup.java b/src/main/java/com/canrd/webmagic/common/jsr303/OperateGroup.java
new file mode 100644
index 0000000..7801802
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/jsr303/OperateGroup.java
@@ -0,0 +1,77 @@
+package com.canrd.webmagic.common.jsr303;
+
+
+public class OperateGroup {
+    /**
+     * 新增校验
+     */
+    public interface Save {
+    }
+
+    /**
+     * 删除校验
+     */
+    public interface Delete {
+    }
+
+    /**
+     * 修改校验
+     */
+    public interface Update {
+    }
+
+    /**
+     * 查询列表
+     */
+    public interface List {
+    }
+
+    /**
+     * 分页查询
+     */
+    public interface Page {
+    }
+
+    /**
+     * 长度校验
+     */
+    public interface Length {
+    }
+
+    /**
+     * 格式校验
+     */
+    public interface Format {
+    }
+
+    /**
+     * 详情
+     */
+    public interface Detail {
+    }
+
+    /**
+     * 单条
+     */
+    public interface One {
+    }
+
+
+    public interface Trace {
+
+    }
+
+    /**
+     * 批量查询
+     */
+    public interface BatchQuery {
+    }
+
+
+    /**
+     * 导出
+     */
+    public interface Export {
+    }
+
+}
diff --git a/src/main/java/com/canrd/webmagic/common/jsr303/annotation/ListIntValue.java b/src/main/java/com/canrd/webmagic/common/jsr303/annotation/ListIntValue.java
new file mode 100644
index 0000000..2648f06
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/jsr303/annotation/ListIntValue.java
@@ -0,0 +1,32 @@
+package com.canrd.webmagic.common.jsr303.annotation;
+
+
+import com.canrd.webmagic.common.jsr303.ListValueConstraintValidator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+/**
+ * @date 2023-01-15
+ */
+@Constraint(validatedBy = {ListValueConstraintValidator.class})
+@Documented
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ListIntValue {
+
+
+    // 配置文件中错误提示信息的名称
+    String message() default "非法的类型";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+    // 自定义值的类型
+    int[] enumValue() default {};
+
+    // 是否必填,默认允许为空
+    boolean require() default false;
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/DateUtil.java b/src/main/java/com/canrd/webmagic/common/utils/DateUtil.java
new file mode 100644
index 0000000..c00c04f
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/DateUtil.java
@@ -0,0 +1,591 @@
+package com.canrd.webmagic.common.utils;
+
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * @Date: 2020/9/1
+ * 基于Java8的时间工具类
+ */
+public class DateUtil {
+    /**
+     * 例如:2018-12-28
+     */
+    public static final String DATE = "yyyy-MM-dd";
+    /**
+     * 例如:2018.12.28
+     */
+    public static final String DATE_WITH_POINT = "yyyy.MM.dd";
+    /**
+     * 例如:2018-12-28 10:00:00
+     */
+    public static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * 例如:2018-12-28 10:00:00:215
+     */
+    public static final String DATE_TIME_MS = "yyyy-MM-dd HH:mm:ss.SSS";
+    /**
+     * 例如:10:00:00
+     */
+    public static final String TIME = "HHmmss";
+    /**
+     * 例如:10:00:00
+     */
+    public static final String TIME_HAVE_SECOND = "HH:mm:ss";
+    /**
+     * 例如:10:00
+     */
+    public static final String TIME_WITHOUT_SECOND = "HH:mm";
+
+    /**
+     * 例如:2018-12-28 10:00
+     */
+    public static final String DATE_TIME_WITHOUT_SECONDS = "yyyy-MM-dd HH:mm";
+
+    /**
+     * 例如 2020/04/16 12:23:0
+     */
+    public static final String DATE_TIME_WITHOUT_SECONDS_OTHER = "yyyy/MM/dd HH:mm:ss";
+
+    /**
+     * 例如:2020年04月23日 10:00
+     */
+    public static final String DATE_CHINESE_TIME = "yyyy年MM月dd日 HH:mm";
+
+    /**
+     * 例如:20181228100000215
+     */
+    public static final String MAX_DATE = "9999-12-31 59:59:59";
+
+    /**
+     * 例如:20210822082134
+     */
+    public static final String DATE_TIME_HMS = "yyyyMMddHHmmssSSS";
+
+    public static final String YYMMDD = "yyyyMMdd";
+
+    public static final String YMMDD = "yyMMdd";
+
+    public static final String YYMMDDHHMMSS = "yyMMddHHmmss";
+
+    public static final String MONTH_TIME = "MM-dd HH:mm:ss";
+
+    /**
+     * 例如:2018-12-28 10:00
+     */
+    public static final String DDMMYYYYHHMMSS = "dd/MM/yyyy HH:mm:ss";
+
+    public static LocalDateTime getCurrentTime() {
+        LocalDateTime now = LocalDateTime.now();
+        return now;
+    }
+
+    public static Date getNextDay() {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime tomorrow = now.minusDays(-1);
+        return Date.from(tomorrow.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    public static Date getAppointDay(Integer count) {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime tomorrow = now.minusDays(count);
+        return Date.from(tomorrow.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 获取年
+     *
+     * @return 年
+     */
+    public static int getYear() {
+        LocalDateTime localDateTime = LocalDateTime.now();
+        return localDateTime.get(ChronoField.YEAR);
+    }
+
+    /**
+     * 获取月份
+     *
+     * @return 月份
+     */
+    public static int getMonth() {
+        LocalDateTime localDateTime = LocalDateTime.now();
+        return localDateTime.get(ChronoField.MONTH_OF_YEAR);
+    }
+
+    /**
+     * 获取某月的第几天
+     *
+     * @return 几号
+     */
+    public static int getMonthOfDay() {
+        LocalDateTime localDateTime = LocalDateTime.now();
+        return localDateTime.get(ChronoField.DAY_OF_MONTH);
+    }
+
+    /**
+     * 格式化日期为字符串
+     *
+     * @param date    date
+     * @param pattern 格式
+     * @return 日期字符串
+     */
+    public static String formatOrElseBlank(Date date, String pattern) {
+        if (date == null || pattern == null) {
+            return "";
+        }
+        Instant instant = date.toInstant();
+
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+
+        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * 格式化日期为字符串
+     *
+     * @param date    date
+     * @param pattern 格式
+     * @return 日期字符串
+     */
+    public static String format(Date date, String pattern) {
+
+        Instant instant = date.toInstant();
+
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+
+        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    public static String formatNow(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now();
+
+        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+
+    public static String format(LocalDateTime date, String pattern) {
+        return date.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    public static String format(LocalDate date, String pattern) {
+        return date.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * 解析字符串日期为LocalDateTime
+     *
+     * @param dateStr 日期字符串
+     * @param pattern 格式
+     * @return Date
+     */
+    public static LocalDateTime parse(String dateStr, String pattern) {
+        return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * 解析字符串日期为LocalDateTime
+     *
+     * @param dateStr 日期字符串
+     * @param pattern 格式
+     * @return Date
+     */
+    public static LocalDateTime parseStrByPattern(String dateStr, String pattern) {
+        try {
+            return parse(dateStr, pattern);
+        } catch (Exception e) {
+            return getCurrentTime();
+        }
+    }
+
+    /**
+     * 解析字符串日期为LocalDate
+     *
+     * @param dateStr
+     * @param pattern
+     * @return
+     */
+    public static LocalDate parseDate(String dateStr, String pattern) {
+        return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * @param dateTimeStr
+     * @param prePattern
+     * @param expectPattern
+     * @return
+     */
+    public static String format(String dateTimeStr, String prePattern, String expectPattern) {
+        LocalDateTime localDateTime = parseDate(dateTimeStr, prePattern).atStartOfDay();
+        return localDateTime.format(DateTimeFormatter.ofPattern(expectPattern));
+    }
+
+    /**
+     * String -> Date
+     *
+     * @param date
+     * @return
+     */
+    public static Date parseStringToDate(String date, String pattern) {
+        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+        try {
+            return sdf.parse(date);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 为Date增加分钟,减传负数
+     *
+     * @param date        日期
+     * @param plusMinutes 要增加的分钟数
+     * @return 新的日期
+     */
+    public static Date addMinutes(Date date, Long plusMinutes) {
+        LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+        LocalDateTime newDateTime = dateTime.plusMinutes(plusMinutes);
+        return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 增加时间
+     *
+     * @param date date
+     * @param hour 要增加的小时数
+     * @return new date
+     */
+    public static Date addHour(Date date, Long hour) {
+        LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+        LocalDateTime localDateTime = dateTime.plusHours(hour);
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * @return 返回当天的起始时间
+     */
+    public static Date getStartTime() {
+
+        LocalDateTime now = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
+        return localDateTime2Date(now);
+    }
+
+
+    /**
+     * @return 返回当天的结束时间
+     */
+    public static Date getEndTime() {
+        LocalDateTime now = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59).withNano(999999999);
+        return localDateTime2Date(now);
+    }
+
+    /**
+     * @return 获取入参当天结束时间
+     */
+    public static LocalDateTime getEndTime(LocalDate localDate) {
+        return localDate.atTime(23, 59, 59, 999999999);
+    }
+
+    /**
+     * 减月份
+     *
+     * @param monthsToSubtract 月份
+     * @return Date
+     */
+    public static Date minusMonths(long monthsToSubtract) {
+        LocalDate localDate = LocalDate.now().minusMonths(monthsToSubtract);
+
+        return localDate2Date(localDate);
+    }
+
+    /**
+     * LocalDate类型转为Date
+     *
+     * @param localDate LocalDate object
+     * @return Date object
+     */
+    public static Date localDate2Date(LocalDate localDate) {
+
+        ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
+
+        return Date.from(zonedDateTime.toInstant());
+    }
+
+    /**
+     * Date类型转为LocalDateTime
+     *
+     * @param date Date object
+     * @return localDate LocalDateTime
+     */
+    public static LocalDateTime date2LocalDateTime(Date date) {
+
+        Instant instant = date.toInstant();
+        ZoneId zoneId = ZoneId.systemDefault();
+        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
+        return localDateTime;
+    }
+
+
+    /**
+     * LocalDateTime类型转为Date
+     *
+     * @param localDateTime LocalDateTime object
+     * @return Date object
+     */
+    public static Date localDateTime2Date(LocalDateTime localDateTime) {
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 查询当前年的第一天
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getFirstDayOfCurrentYear(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now().withMonth(1).withDayOfMonth(1);
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    /**
+     * 查询前一年最后一个月第一天
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getLastMonthFirstDayOfPreviousYear(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now().minusYears(1L).withMonth(12).withDayOfMonth(1);
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    /**
+     * 查询前一年最后一个月第一天
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getLastMonthLastDayOfPreviousYear(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now().minusYears(1L).with(TemporalAdjusters.lastDayOfYear());
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    /**
+     * 获取当前日期
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getCurrentDay(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now();
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    public static String getTransitionTime(LocalDateTime time, String pattern) {
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+        return format(localDateTime2Date(time), pattern);
+    }
+
+
+    /**
+     * 用于判断两个时间段有没有重合, 重合: true,未重合: false
+     *
+     * @param aStartTime
+     * @param aEndTime
+     * @param bStartTime
+     * @param bEndTime
+     * @return
+     * @author A80068
+     */
+    public static boolean judgeTimeSlotCoincidence(LocalDateTime aStartTime, LocalDateTime aEndTime, LocalDateTime bStartTime, LocalDateTime bEndTime) {
+        /**
+         * 旧版
+         */
+        //boolean flag;
+        //if(aStartTime.isAfter(bStartTime)){
+        //    if (aStartTime.isBefore(bEndTime) || aStartTime.isEqual(bEndTime)){
+        //        flag = true;
+        //    }else{
+        //        flag = false;
+        //    }
+        //}else if(aStartTime.isBefore(bStartTime)){
+        //    if(aEndTime.isAfter(bStartTime) || aEndTime.isEqual(bStartTime)){
+        //        flag = true;
+        //    }else{
+        //        flag = false;
+        //    }
+        //}else{
+        //    flag = true;
+        //}
+        //return flag;
+        /**
+         * 优化版
+         */
+        boolean flag = false;
+        if ((aStartTime.isBefore(bEndTime) || aStartTime.isEqual(bEndTime))) {
+            if ((aEndTime.isAfter(bStartTime)) || aEndTime.isEqual(bStartTime)) {
+                flag = true;
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * @param aStartTimeStr
+     * @param aEndTimeStr
+     * @param bStartTimeStr
+     * @param bEndTimeStr
+     * @return
+     * @author A80068
+     */
+    public static boolean judgeTimeSlotCoincidenceString(String aStartTimeStr, String aEndTimeStr, String bStartTimeStr, String bEndTimeStr) {
+
+        LocalDateTime aStartTime = parse(aStartTimeStr, DATE_TIME);
+        LocalDateTime aEndTime = parse(aEndTimeStr, DATE_TIME);
+        LocalDateTime bStartTime = parse(bStartTimeStr, DATE_TIME);
+        LocalDateTime bEndTime = parse(bEndTimeStr, DATE_TIME);
+
+        return judgeTimeSlotCoincidence(aStartTime, aEndTime, bStartTime, bEndTime);
+    }
+
+    /**
+     * @param strGMT
+     * @return
+     * @throws ParseException
+     * @throws ParseException
+     */
+    public static Date getCST(String strGMT) throws ParseException, ParseException {
+        DateFormat df = new SimpleDateFormat("EEE, d-MMM-yyyy HH:mm:ss z", Locale.ENGLISH);
+        return df.parse(strGMT);
+    }
+
+    /**
+     * @param dateCST
+     * @return
+     */
+    public static Date getGMT(Date dateCST) throws ParseException {
+        DateFormat df = new SimpleDateFormat("EEE, d-MMM-yyyy HH:mm:ss z", Locale.ENGLISH);
+        df.setTimeZone(TimeZone.getTimeZone("GMT")); // modify Time Zone.
+        return df.parse(df.format(dateCST));
+    }
+
+    /**
+     * LocalDateTime转化为cron表达式
+     *
+     * @param dateTime
+     * @return
+     */
+    public static String dateToCron(LocalDateTime dateTime) {
+        if (dateTime == null) {
+            return "";
+        }
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");
+        return formatter.format(dateTime);
+    }
+
+    /**
+     * cron表达式转为LocalDateTime
+     *
+     * @param cron
+     * @return
+     */
+    public static LocalDateTime cronToDate(String cron) {
+        if (StringUtils.isNotBlank(cron)) {
+            return null;
+        }
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");
+        return LocalDateTime.parse(cron, formatter);
+    }
+
+    public static String timeToString(long time) {
+        DateTimeFormatter ftf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        return ftf.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()));
+    }
+
+    public static String timeToString(String time) {
+        try {
+            return timeToString(Long.parseLong(time));
+        } catch (Exception e) {
+        }
+        return time;
+    }
+
+    /**
+     * 比较时间大小
+     *
+     * @param date1
+     * @param date2
+     * @return
+     */
+    public static int compareDate(Date date1, Date date2) {
+        if (date1 == null || date2 == null) {
+            return -2;
+        }
+        if (date1.getTime() < date2.getTime()) {
+            return -1;
+        } else if (date1.getTime() > date2.getTime()) {
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * 判斷两个时间是否在同一分钟
+     */
+    public static boolean isMinuteIdentical(LocalDateTime date1, LocalDateTime date2) {
+        return date1.getYear() == date2.getYear() && date1.getMonth() == date2.getMonth() && date1.getMinute() == date2.getMinute();
+    }
+
+    /**
+     * 时区
+     */
+    public static class Zone {
+        public static final String GMT8 = "GMT+8";
+    }
+
+    public static LocalDateTime parseProductDate(String dateStr) {
+        if (StringUtils.isBlank(dateStr)) {
+            return null;
+        }
+        if (dateStr.length() == 8) {
+            dateStr = dateStr + " 00:00:00";
+        }
+        if (dateStr.contains("/")) {
+            dateStr = dateStr.replaceAll("/", "-");
+        }
+        try {
+            return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/DateUtils.java b/src/main/java/com/canrd/webmagic/common/utils/DateUtils.java
new file mode 100644
index 0000000..8d34fb2
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/DateUtils.java
@@ -0,0 +1,569 @@
+package com.canrd.webmagic.common.utils;
+
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * @Date: 2020/9/1
+ * 基于Java8的时间工具类
+ */
+public class DateUtils {
+    /**
+     * 例如:2018-12-28
+     */
+    public static final String DATE = "yyyy-MM-dd";
+    /**
+     * 例如:2018.12.28
+     */
+    public static final String DATE_WITH_POINT = "yyyy.MM.dd";
+    /**
+     * 例如:2018-12-28 10:00:00
+     */
+    public static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * 例如:2018-12-28 10:00:00:215
+     */
+    public static final String DATE_TIME_MS = "yyyy-MM-dd HH:mm:ss.SSS";
+    /**
+     * 例如:10:00:00
+     */
+    public static final String TIME = "HHmmss";
+    /**
+     * 例如:10:00:00
+     */
+    public static final String TIME_HAVE_SECOND = "HH:mm:ss";
+    /**
+     * 例如:10:00
+     */
+    public static final String TIME_WITHOUT_SECOND = "HH:mm";
+
+    /**
+     * 例如:2018-12-28 10:00
+     */
+    public static final String DATE_TIME_WITHOUT_SECONDS = "yyyy-MM-dd HH:mm";
+
+    /**
+     * 例如 2020/04/16 12:23:0
+     */
+    public static final String DATE_TIME_WITHOUT_SECONDS_OTHER = "yyyy/MM/dd HH:mm:ss";
+
+    /**
+     * 例如:2020年04月23日 10:00
+     */
+    public static final String DATE_CHINESE_TIME = "yyyy年MM月dd日 HH:mm";
+
+    /**
+     * 例如:20181228100000215
+     */
+    public static final String MAX_DATE = "9999-12-31 59:59:59";
+
+    /**
+     * 例如:20210822082134
+     */
+    public static final String DATE_TIME_HMS = "yyyyMMddHHmmssSSS";
+
+    public static final String YYMMDD = "yyyyMMdd";
+
+    public static final String YMMDD = "yyMMdd";
+
+    public static final String YYMMDDHHMMSS = "yyMMddHHmmss";
+
+    public static final String MONTH_TIME = "MM-dd HH:mm:ss";
+
+    public static LocalDateTime getCurrentTime() {
+        LocalDateTime now = LocalDateTime.now();
+        return now;
+    }
+
+    public static Date getNextDay() {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime tomorrow = now.minusDays(-1);
+        return Date.from(tomorrow.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    public static Date getAppointDay(Integer count) {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime tomorrow = now.minusDays(count);
+        return Date.from(tomorrow.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 获取年
+     *
+     * @return 年
+     */
+    public static int getYear() {
+        LocalDateTime localDateTime = LocalDateTime.now();
+        return localDateTime.get(ChronoField.YEAR);
+    }
+
+    /**
+     * 获取月份
+     *
+     * @return 月份
+     */
+    public static int getMonth() {
+        LocalDateTime localDateTime = LocalDateTime.now();
+        return localDateTime.get(ChronoField.MONTH_OF_YEAR);
+    }
+
+    /**
+     * 获取某月的第几天
+     *
+     * @return 几号
+     */
+    public static int getMonthOfDay() {
+        LocalDateTime localDateTime = LocalDateTime.now();
+        return localDateTime.get(ChronoField.DAY_OF_MONTH);
+    }
+
+    /**
+     * 格式化日期为字符串
+     *
+     * @param date    date
+     * @param pattern 格式
+     * @return 日期字符串
+     */
+    public static String formatOrElseBlank(Date date, String pattern) {
+        if (date == null || pattern == null) {
+            return "";
+        }
+        Instant instant = date.toInstant();
+
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+
+        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * 格式化日期为字符串
+     *
+     * @param date    date
+     * @param pattern 格式
+     * @return 日期字符串
+     */
+    public static String format(Date date, String pattern) {
+
+        Instant instant = date.toInstant();
+
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+
+        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    public static String formatNow(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now();
+
+        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+
+    public static String format(LocalDateTime date, String pattern) {
+        return date.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    public static String format(LocalDate date, String pattern) {
+        return date.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * 解析字符串日期为LocalDateTime
+     *
+     * @param dateStr 日期字符串
+     * @param pattern 格式
+     * @return Date
+     */
+    public static LocalDateTime parse(String dateStr, String pattern) {
+        return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * 解析字符串日期为LocalDateTime
+     *
+     * @param dateStr 日期字符串
+     * @param pattern 格式
+     * @return Date
+     */
+    public static LocalDateTime parseStrByPattern(String dateStr, String pattern) {
+        try {
+            return parse(dateStr, pattern);
+        } catch (Exception e) {
+            return getCurrentTime();
+        }
+    }
+
+    /**
+     * 解析字符串日期为LocalDate
+     *
+     * @param dateStr
+     * @param pattern
+     * @return
+     */
+    public static LocalDate parseDate(String dateStr, String pattern) {
+        return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * @param dateTimeStr
+     * @param prePattern
+     * @param expectPattern
+     * @return
+     */
+    public static String format(String dateTimeStr, String prePattern, String expectPattern) {
+        LocalDateTime localDateTime = parseDate(dateTimeStr, prePattern).atStartOfDay();
+        return localDateTime.format(DateTimeFormatter.ofPattern(expectPattern));
+    }
+
+    /**
+     * String -> Date
+     *
+     * @param date
+     * @return
+     */
+    public static Date parseStringToDate(String date, String pattern) {
+        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+        try {
+            return sdf.parse(date);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 为Date增加分钟,减传负数
+     *
+     * @param date        日期
+     * @param plusMinutes 要增加的分钟数
+     * @return 新的日期
+     */
+    public static Date addMinutes(Date date, Long plusMinutes) {
+        LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+        LocalDateTime newDateTime = dateTime.plusMinutes(plusMinutes);
+        return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 增加时间
+     *
+     * @param date date
+     * @param hour 要增加的小时数
+     * @return new date
+     */
+    public static Date addHour(Date date, Long hour) {
+        LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+        LocalDateTime localDateTime = dateTime.plusHours(hour);
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * @return 返回当天的起始时间
+     */
+    public static Date getStartTime() {
+
+        LocalDateTime now = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
+        return localDateTime2Date(now);
+    }
+
+
+    /**
+     * @return 返回当天的结束时间
+     */
+    public static Date getEndTime() {
+        LocalDateTime now = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59).withNano(999999999);
+        return localDateTime2Date(now);
+    }
+
+    /**
+     * @return 获取入参当天结束时间
+     */
+    public static LocalDateTime getEndTime(LocalDate localDate) {
+        return localDate.atTime(23, 59, 59, 999999999);
+    }
+
+    /**
+     * 减月份
+     *
+     * @param monthsToSubtract 月份
+     * @return Date
+     */
+    public static Date minusMonths(long monthsToSubtract) {
+        LocalDate localDate = LocalDate.now().minusMonths(monthsToSubtract);
+
+        return localDate2Date(localDate);
+    }
+
+    /**
+     * LocalDate类型转为Date
+     *
+     * @param localDate LocalDate object
+     * @return Date object
+     */
+    public static Date localDate2Date(LocalDate localDate) {
+
+        ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
+
+        return Date.from(zonedDateTime.toInstant());
+    }
+
+    /**
+     * Date类型转为LocalDateTime
+     *
+     * @param date Date object
+     * @return localDate LocalDateTime
+     */
+    public static LocalDateTime date2LocalDateTime(Date date) {
+
+        Instant instant = date.toInstant();
+        ZoneId zoneId = ZoneId.systemDefault();
+        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
+        return localDateTime;
+    }
+
+
+    /**
+     * LocalDateTime类型转为Date
+     *
+     * @param localDateTime LocalDateTime object
+     * @return Date object
+     */
+    public static Date localDateTime2Date(LocalDateTime localDateTime) {
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 查询当前年的第一天
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getFirstDayOfCurrentYear(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now().withMonth(1).withDayOfMonth(1);
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    /**
+     * 查询前一年最后一个月第一天
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getLastMonthFirstDayOfPreviousYear(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now().minusYears(1L).withMonth(12).withDayOfMonth(1);
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    /**
+     * 查询前一年最后一个月第一天
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getLastMonthLastDayOfPreviousYear(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now().minusYears(1L).with(TemporalAdjusters.lastDayOfYear());
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    /**
+     * 获取当前日期
+     *
+     * @param pattern 格式,默认格式yyyyMMdd
+     * @return 20190101
+     */
+    public static String getCurrentDay(String pattern) {
+        LocalDateTime localDateTime = LocalDateTime.now();
+
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+
+        return format(localDateTime2Date(localDateTime), pattern);
+    }
+
+    public static String getTransitionTime(LocalDateTime time, String pattern) {
+        if (StringUtils.isEmpty(pattern)) {
+            pattern = "yyyyMMdd";
+        }
+        return format(localDateTime2Date(time), pattern);
+    }
+
+
+    /**
+     * 用于判断两个时间段有没有重合, 重合: true,未重合: false
+     *
+     * @param aStartTime
+     * @param aEndTime
+     * @param bStartTime
+     * @param bEndTime
+     * @return
+     * @author A80068
+     */
+    public static boolean judgeTimeSlotCoincidence(LocalDateTime aStartTime, LocalDateTime aEndTime, LocalDateTime bStartTime, LocalDateTime bEndTime) {
+        /**
+         * 旧版
+         */
+        //boolean flag;
+        //if(aStartTime.isAfter(bStartTime)){
+        //    if (aStartTime.isBefore(bEndTime) || aStartTime.isEqual(bEndTime)){
+        //        flag = true;
+        //    }else{
+        //        flag = false;
+        //    }
+        //}else if(aStartTime.isBefore(bStartTime)){
+        //    if(aEndTime.isAfter(bStartTime) || aEndTime.isEqual(bStartTime)){
+        //        flag = true;
+        //    }else{
+        //        flag = false;
+        //    }
+        //}else{
+        //    flag = true;
+        //}
+        //return flag;
+        /**
+         * 优化版
+         */
+        boolean flag = false;
+        if ((aStartTime.isBefore(bEndTime) || aStartTime.isEqual(bEndTime))) {
+            if ((aEndTime.isAfter(bStartTime)) || aEndTime.isEqual(bStartTime)) {
+                flag = true;
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * @param aStartTimeStr
+     * @param aEndTimeStr
+     * @param bStartTimeStr
+     * @param bEndTimeStr
+     * @return
+     * @author A80068
+     */
+    public static boolean judgeTimeSlotCoincidenceString(String aStartTimeStr, String aEndTimeStr, String bStartTimeStr, String bEndTimeStr) {
+
+        LocalDateTime aStartTime = parse(aStartTimeStr, DATE_TIME);
+        LocalDateTime aEndTime = parse(aEndTimeStr, DATE_TIME);
+        LocalDateTime bStartTime = parse(bStartTimeStr, DATE_TIME);
+        LocalDateTime bEndTime = parse(bEndTimeStr, DATE_TIME);
+
+        return judgeTimeSlotCoincidence(aStartTime, aEndTime, bStartTime, bEndTime);
+    }
+
+    /**
+     * @param strGMT
+     * @return
+     * @throws ParseException
+     * @throws ParseException
+     */
+    public static Date getCST(String strGMT) throws ParseException, ParseException {
+        DateFormat df = new SimpleDateFormat("EEE, d-MMM-yyyy HH:mm:ss z", Locale.ENGLISH);
+        return df.parse(strGMT);
+    }
+
+    /**
+     * @param dateCST
+     * @return
+     */
+    public static Date getGMT(Date dateCST) throws ParseException {
+        DateFormat df = new SimpleDateFormat("EEE, d-MMM-yyyy HH:mm:ss z", Locale.ENGLISH);
+        df.setTimeZone(TimeZone.getTimeZone("GMT")); // modify Time Zone.
+        return df.parse(df.format(dateCST));
+    }
+
+    /**
+     * LocalDateTime转化为cron表达式
+     *
+     * @param dateTime
+     * @return
+     */
+    public static String dateToCron(LocalDateTime dateTime) {
+        if (dateTime == null) {
+            return "";
+        }
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");
+        return formatter.format(dateTime);
+    }
+
+    /**
+     * cron表达式转为LocalDateTime
+     *
+     * @param cron
+     * @return
+     */
+    public static LocalDateTime cronToDate(String cron) {
+        if (StringUtils.isNotBlank(cron)) {
+            return null;
+        }
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");
+        return LocalDateTime.parse(cron, formatter);
+    }
+
+    public static String timeToString(long time) {
+        DateTimeFormatter ftf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        return ftf.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()));
+    }
+
+    public static String timeToString(String time) {
+        try {
+            return timeToString(Long.parseLong(time));
+        } catch (Exception e) {
+        }
+        return time;
+    }
+
+    /**
+     * 比较时间大小
+     *
+     * @param date1
+     * @param date2
+     * @return
+     */
+    public static int compareDate(Date date1, Date date2) {
+        if (date1 == null || date2 == null) {
+            return -2;
+        }
+        if (date1.getTime() < date2.getTime()) {
+            return -1;
+        } else if (date1.getTime() > date2.getTime()) {
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * 判斷两个时间是否在同一分钟
+     */
+    public static boolean isMinuteIdentical(LocalDateTime date1, LocalDateTime date2) {
+        return date1.getYear() == date2.getYear() && date1.getMonth() == date2.getMonth() && date1.getMinute() == date2.getMinute();
+    }
+
+    /**
+     * 时区
+     */
+    public static class Zone {
+        public static final String GMT8 = "GMT+8";
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/EncryptUtils.java b/src/main/java/com/canrd/webmagic/common/utils/EncryptUtils.java
new file mode 100644
index 0000000..73b8f52
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/EncryptUtils.java
@@ -0,0 +1,84 @@
+package com.canrd.webmagic.common.utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 加密
+ *
+ * @date 2018-11-23
+ */
+public class EncryptUtils {
+
+    private static String strParam = "Passw0rd";
+
+    private static Cipher cipher;
+
+    private static IvParameterSpec iv = new IvParameterSpec(strParam.getBytes(StandardCharsets.UTF_8));
+
+    private static DESKeySpec getDesKeySpec(String source) throws Exception {
+        if (source == null || source.length() == 0) {
+            return null;
+        }
+        cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        String strKey = "Passw0rd";
+        return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * 对称加密
+     */
+    public static String desEncrypt(String source) throws Exception {
+        DESKeySpec desKeySpec = getDesKeySpec(source);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
+        return byte2hex(
+                cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
+    }
+
+    /**
+     * 对称解密
+     */
+    public static String desDecrypt(String source) throws Exception {
+        byte[] src = hex2byte(source.getBytes());
+        DESKeySpec desKeySpec = getDesKeySpec(source);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
+        byte[] retByte = cipher.doFinal(src);
+        return new String(retByte);
+    }
+
+    private static String byte2hex(byte[] inStr) {
+        String stmp;
+        StringBuilder out = new StringBuilder(inStr.length * 2);
+        for (byte b : inStr) {
+            stmp = Integer.toHexString(b & 0xFF);
+            if (stmp.length() == 1) {
+                // 如果是0至F的单位字符串,则添加0
+                out.append("0").append(stmp);
+            } else {
+                out.append(stmp);
+            }
+        }
+        return out.toString();
+    }
+
+    private static byte[] hex2byte(byte[] b) {
+        int size = 2;
+        if ((b.length % size) != 0) {
+            throw new IllegalArgumentException("长度不是偶数");
+        }
+        byte[] b2 = new byte[b.length / 2];
+        for (int n = 0; n < b.length; n += size) {
+            String item = new String(b, n, 2);
+            b2[n / 2] = (byte) Integer.parseInt(item, 16);
+        }
+        return b2;
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/FileUtil.java b/src/main/java/com/canrd/webmagic/common/utils/FileUtil.java
new file mode 100644
index 0000000..f00a2b2
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/FileUtil.java
@@ -0,0 +1,360 @@
+package com.canrd.webmagic.common.utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.poi.excel.BigExcelWriter;
+import cn.hutool.poi.excel.ExcelUtil;
+import org.apache.poi.util.IOUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.activation.MimetypesFileTypeMap;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.security.MessageDigest;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * File工具类,扩展 hutool 工具包
+ *
+ * @date 2018-12-27
+ */
+public class FileUtil extends cn.hutool.core.io.FileUtil {
+
+    /**
+     * 定义GB的计算常量
+     */
+    private static final int GB = 1024 * 1024 * 1024;
+    /**
+     * 定义MB的计算常量
+     */
+    private static final int MB = 1024 * 1024;
+    /**
+     * 定义KB的计算常量
+     */
+    private static final int KB = 1024;
+
+    /**
+     * 格式化小数
+     */
+    private static final DecimalFormat DF = new DecimalFormat("0.00");
+
+    /**
+     * MultipartFile转File
+     */
+    public static File toFile(MultipartFile multipartFile) {
+        // 获取文件名
+        String fileName = multipartFile.getOriginalFilename();
+        // 获取文件后缀
+        String prefix = "." + getExtensionName(fileName);
+        File file = null;
+        try {
+            // 用uuid作为文件名,防止生成的临时文件重复
+            file = File.createTempFile(IdUtil.simpleUUID(), prefix);
+            // MultipartFile to File
+            multipartFile.transferTo(file);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return file;
+    }
+
+    /**
+     * 获取文件扩展名,不带 .
+     */
+    public static String getExtensionName(String filename) {
+        if ((filename != null) && (filename.length() > 0)) {
+            int dot = filename.lastIndexOf('.');
+            if ((dot > -1) && (dot < (filename.length() - 1))) {
+                return filename.substring(dot + 1);
+            }
+        }
+        return filename;
+    }
+
+    /**
+     * Java文件操作 获取不带扩展名的文件名
+     */
+    public static String getFileNameNoEx(String filename) {
+        if ((filename != null) && (filename.length() > 0)) {
+            int dot = filename.lastIndexOf('.');
+            if ((dot > -1) && (dot < (filename.length()))) {
+                return filename.substring(0, dot);
+            }
+        }
+        return filename;
+    }
+
+    /**
+     * 文件大小转换
+     */
+    public static String getSize(long size) {
+        String resultSize;
+        if (size / GB >= 1) {
+            //如果当前Byte的值大于等于1GB
+            resultSize = DF.format(size / (float) GB) + "GB   ";
+        } else if (size / MB >= 1) {
+            //如果当前Byte的值大于等于1MB
+            resultSize = DF.format(size / (float) MB) + "MB   ";
+        } else if (size / KB >= 1) {
+            //如果当前Byte的值大于等于1KB
+            resultSize = DF.format(size / (float) KB) + "KB   ";
+        } else {
+            resultSize = size + "B   ";
+        }
+        return resultSize;
+    }
+
+    /**
+     * inputStream 转 File
+     */
+    public static File inputStreamToFile(InputStream ins, String name) throws Exception {
+        File file = new File(System.getProperty("java.io.tmpdir") + File.separator + name);
+        if (file.exists()) {
+            return file;
+        }
+        OutputStream os = new FileOutputStream(file);
+        int bytesRead;
+        int len = 8192;
+        byte[] buffer = new byte[len];
+        while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
+            os.write(buffer, 0, bytesRead);
+        }
+        os.close();
+        ins.close();
+        return file;
+    }
+
+    /**
+     * 将文件转换为byte数组,作为图片数据导入
+     *
+     * @param file
+     * @return byte[]
+     */
+    public static byte[] imageParseBytes(File file) {
+        FileInputStream fileInputStream = null;
+        try {
+            fileInputStream = new FileInputStream(file);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+        return imageParseBytes(fileInputStream);
+    }
+
+    /**
+     * 将流转换为byte数组,作为图片数据导入
+     *
+     * @param fis
+     * @return byte[]
+     */
+    public static byte[] imageParseBytes(InputStream fis) {
+        byte[] buffer = null;
+        ByteArrayOutputStream bos = null;
+        try {
+            bos = new ByteArrayOutputStream(1024);
+            byte[] b = new byte[1024];
+            int n;
+            while ((n = fis.read(b)) != -1) {
+                bos.write(b, 0, n);
+            }
+            buffer = bos.toByteArray();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                fis.close();
+                bos.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return buffer;
+    }
+
+    /**
+     * 将文件名解析成文件的上传路径
+     */
+    public static File upload(MultipartFile file, String filePath) {
+        Date date = new Date();
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS");
+        String name = getFileNameNoEx(file.getOriginalFilename());
+        String suffix = getExtensionName(file.getOriginalFilename());
+        String nowStr = "-" + format.format(date);
+        try {
+            String fileName = name + nowStr + "." + suffix;
+            String path = filePath + fileName;
+            // getCanonicalFile 可解析正确各种路径
+            File dest = new File(path).getCanonicalFile();
+            // 检测是否存在目录
+            if (!dest.getParentFile().exists()) {
+                dest.getParentFile().mkdirs();
+            }
+            // 文件写入
+            file.transferTo(dest);
+            return dest;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static String fileToBase64(File file) throws Exception {
+        FileInputStream inputFile = new FileInputStream(file);
+        String base64;
+        byte[] buffer = new byte[(int) file.length()];
+        inputFile.read(buffer);
+        inputFile.close();
+        base64 = Base64.encode(buffer);
+        return base64.replaceAll("[\\s*\t\n\r]", "");
+    }
+
+    /**
+     * 导出excel
+     */
+    public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {
+        String tempPath = System.getProperty("java.io.tmpdir") + IdUtil.fastSimpleUUID() + ".xlsx";
+        File file = new File(tempPath);
+        BigExcelWriter writer = ExcelUtil.getBigWriter(file);
+        // 一次性写出内容,使用默认样式,强制输出标题
+        writer.write(list, true);
+        //response为HttpServletResponse对象
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
+        //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
+        response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
+        ServletOutputStream out = response.getOutputStream();
+        // 终止后删除临时文件
+        file.deleteOnExit();
+        writer.flush(out, true);
+        //此处记得关闭输出Servlet流
+        IoUtil.close(out);
+    }
+
+    public static String getFileType(String type) {
+        String documents = "txt doc pdf ppt pps xlsx xls docx";
+        String music = "mp3 wav wma mpa ram ra aac aif m4a";
+        String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
+        String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
+        if (image.contains(type)) {
+            return "images";
+        } else if (documents.contains(type)) {
+            return "docs";
+        } else if (music.contains(type)) {
+            return "musics";
+        } else if (video.contains(type)) {
+            return "videos";
+        } else {
+            return "others";
+        }
+    }
+
+    public static String getFileTypeByMimeType(String type) {
+        String mimeType = new MimetypesFileTypeMap().getContentType("." + type);
+        return mimeType.split("/")[0];
+    }
+
+    public static void checkSize(long maxSize, long size) {
+        // 1M
+        int len = 1024 * 1024;
+        if (size > (maxSize * len)) {
+            throw new RuntimeException("文件超出规定大小");
+        }
+    }
+
+    /**
+     * 判断两个文件是否相同
+     */
+    public static boolean check(File file1, File file2) {
+        String img1Md5 = getMd5(file1);
+        String img2Md5 = getMd5(file2);
+        return img1Md5.equals(img2Md5);
+    }
+
+    /**
+     * 判断两个文件是否相同
+     */
+    public static boolean check(String file1Md5, String file2Md5) {
+        return file1Md5.equals(file2Md5);
+    }
+
+    private static byte[] getByte(File file) {
+        // 得到文件长度
+        byte[] b = new byte[(int) file.length()];
+        try {
+            InputStream in = new FileInputStream(file);
+            try {
+                in.read(b);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+        return b;
+    }
+
+    private static String getMd5(byte[] bytes) {
+        // 16进制字符
+        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+        try {
+            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
+            mdTemp.update(bytes);
+            byte[] md = mdTemp.digest();
+            int j = md.length;
+            char[] str = new char[j * 2];
+            int k = 0;
+            // 移位 输出字符串
+            for (byte byte0 : md) {
+                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+                str[k++] = hexDigits[byte0 & 0xf];
+            }
+            return new String(str);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 下载文件
+     *
+     * @param request  /
+     * @param response /
+     * @param file     /
+     */
+    public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) {
+        response.setCharacterEncoding(request.getCharacterEncoding());
+        response.setContentType("application/octet-stream");
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(file);
+            response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
+            IOUtils.copy(fis, response.getOutputStream());
+            response.flushBuffer();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                    if (deleteOnExit) {
+                        file.deleteOnExit();
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public static String getMd5(File file) {
+        return getMd5(getByte(file));
+    }
+
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/JsonUtil.java b/src/main/java/com/canrd/webmagic/common/utils/JsonUtil.java
new file mode 100644
index 0000000..9487fc4
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/JsonUtil.java
@@ -0,0 +1,183 @@
+package com.canrd.webmagic.common.utils;
+
+
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+
+/**
+ *
+ * @author achims
+ * @date 2019/11/4
+ * 用于转换成json字符串以及解析成对象
+ */
+@Slf4j
+public class JsonUtil {
+
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    static {
+        log.info("注册jackson-datatype-jsr310的java8时间处理模块");
+        mapper.findAndRegisterModules();
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        mapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
+            @Override
+            public void serialize(Object paramT, JsonGenerator paramJsonGenerator,
+                                  SerializerProvider paramSerializerProvider) throws IOException {
+                //设置返回null转为 空字符串""
+                paramJsonGenerator.writeString("");
+            }
+        });
+    }
+
+
+
+
+    /**
+     * Create empty json node
+     *
+     * @return
+     */
+    public static ObjectNode createJsonNode() {
+        return mapper.createObjectNode();
+    }
+
+    /**
+     * Create empty json Array
+     *
+     * @return
+     */
+    public static ArrayNode createJsonArray() {
+        return mapper.createArrayNode();
+
+    }
+
+    /**
+     * Convert the object to corresponding json string
+     *
+     * @param obj
+     * @return
+     */
+    public static String toJsonString(Object obj) {
+        try {
+            mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+            return mapper.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 转成格式化后的json,便于调试排错
+     * @param obj
+     * @return
+     */
+    public static String toJsonStringWithDefaultPretty(Object obj) {
+        try {
+            mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+            return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+
+            return null;
+        }
+    }
+
+    public static <T> T jsonToEntity(String json, Class<T> clazz) {
+        if (StringUtils.isEmpty(json)) return null;
+        try {
+            return mapper.readValue(json, clazz);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 借助json做中转,实体类转实体类
+     * @param obj 实体
+     * @param clazz 目标类型
+     * @param <T> 目标泛型
+     * @return 结果
+     */
+    public static <T> T entityConvert(Object obj, Class<T> clazz) {
+        return jsonToEntity(toJsonString(obj), clazz);
+    }
+
+    /**
+     * 带泛型返回不告警
+     * @param json json字符
+     * @param typeReference TypeReference
+     * @param <R> 返回类型
+     * @param <T> 转换的类型
+     * @return 转换结果
+     * @author 刘迪榕
+     * @version Date 2022-08-26
+     */
+    public static <R, T extends R> R jsonToEntity(String json, TypeReference<T> typeReference) {
+        if (StringUtils.isEmpty(json)) return null;
+        try {
+            return mapper.readValue(json, typeReference);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static JsonNode readStringAsJsonNode(String json) {
+        try {
+            return mapper.readValue(json, JsonNode.class);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static JsonNode valueToTree(Object value) {
+        return mapper.valueToTree(value);
+    }
+
+    public static  <T> T  treeToValue(JsonNode jsonNode, Class<T> clazz) {
+        try {
+            return mapper.treeToValue(jsonNode, clazz);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 是否爲json格式的數據
+     *
+     * @param test
+     * @return
+     */
+    public final static boolean isJSONValid(String test) {
+        try {
+            JSONObject.parseObject(test);
+        } catch (JSONException ex) {
+            try {
+                JSONObject.parseArray(test);
+            } catch (JSONException ex1) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public <T> T paraseJsonTOClass(String json, Class<T> cla) {
+        try {
+            return JSONObject.parseObject(json, cla);
+        } catch (JSONException ex) {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/LocalDateTimeUtil.java b/src/main/java/com/canrd/webmagic/common/utils/LocalDateTimeUtil.java
new file mode 100644
index 0000000..e8c523c
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/LocalDateTimeUtil.java
@@ -0,0 +1,35 @@
+package com.canrd.webmagic.common.utils;
+
+import org.joda.time.DateTime;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author: xms
+ * @description: TODO
+ * @date: 2023/1/16 16:35
+ * @version: 1.0
+ */
+public class LocalDateTimeUtil {
+
+    /**
+     * @param dateTime
+     * @return
+     */
+    public static LocalDateTime toLocalDateTime(DateTime dateTime) {
+        return LocalDateTime.of(dateTime.getYear(), dateTime.getMonthOfYear(),
+                dateTime.getDayOfMonth(), dateTime.getHourOfDay(),
+                dateTime.getMinuteOfHour(), dateTime.getSecondOfMinute());
+    }
+
+    /**
+     *
+     * @param localDateTime
+     * @return
+     */
+    public static DateTime toDateTime(LocalDateTime localDateTime) {
+        return new DateTime().withYear(localDateTime.getYear()).withMonthOfYear(localDateTime.getMonthValue())
+                .withDayOfMonth(localDateTime.getDayOfMonth()).withHourOfDay(localDateTime.getHour())
+                .withMinuteOfHour(localDateTime.getMinute()).withSecondOfMinute(localDateTime.getSecond());
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/PageUtils.java b/src/main/java/com/canrd/webmagic/common/utils/PageUtils.java
new file mode 100644
index 0000000..057cfe7
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/PageUtils.java
@@ -0,0 +1,136 @@
+package com.canrd.webmagic.common.utils;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.canrd.webmagic.domain.vo.BasePageVO;
+import org.springframework.util.CollectionUtils;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * @Author: dengbin
+ * @Date: 2020/9/11
+ * 分页工具类
+ */
+
+public class PageUtils implements Serializable {
+    private static final long serialVersionUID = 4359709211352400087L;
+    private static final String PAGE_INDEX = "pageNo";
+    private static final String PAGE_SIZE = "pageSize";
+    private static final String TOTAL_ROWS = "total";
+    private static final String TOTAL_PAGES = "pages";
+    private static final String RECORDS = "records";
+
+    private static final int DEFAULT_PAGE_INDEX = 1;
+    private static final int DEFAULT_PAGE_SIZE = 10;
+    private static final int DEFAULT_MAP_CAPACITY = 5;
+
+    /**
+     * 统一分页返回对象
+     *
+     * @param list
+     * @return
+     */
+    public static Map<String, Object> getUnifiedPageReturn(IPage list) {
+        if (list == null) {
+            return getUnifiedEmptyPage(DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE);
+        }
+
+        Map<String, Object> resultMap = new HashMap<>(DEFAULT_MAP_CAPACITY);
+        resultMap.put(PAGE_INDEX, list.getCurrent());
+        resultMap.put(PAGE_SIZE, list.getSize());
+        resultMap.put(RECORDS, list.getRecords());
+        resultMap.put(TOTAL_PAGES, list.getPages());
+        resultMap.put(TOTAL_ROWS, list.getTotal());
+
+        return resultMap;
+    }
+
+    /**
+     * 返回空分页信息给前端
+     * 返回空或其他字段不满足前端要求,会报错
+     *
+     * @return
+     */
+    public static Map<String, Object> getUnifiedEmptyPage(int pageIndex, int pageSize) {
+        Map<String, Object> resultMap = new HashMap<>(DEFAULT_MAP_CAPACITY);
+        resultMap.put(PAGE_INDEX, pageIndex);
+        resultMap.put(PAGE_SIZE, pageSize);
+        resultMap.put(RECORDS, new ArrayList<>());
+        resultMap.put(TOTAL_PAGES, 0);
+        resultMap.put(TOTAL_ROWS, 0);
+        return resultMap;
+    }
+
+    public static Map<String, Object> getUnifiedEmptyPage(BasePageVO pageVO) {
+        return getUnifiedEmptyPage(pageVO.getCurrent(), pageVO.getSize());
+    }
+
+
+    /**
+     * 将旧的分页转换成新的分页对象
+     *
+     * @param oldPage 旧的分页对象,例如:xxDo的分页对象
+     * @param records 新的分页对象,例如:xxVo的分页对象
+     * @param <T>     新的分页对象的类型
+     * @return 统一的返回的分页对象
+     */
+    public static <T> Map<String, Object> transferPage(IPage<?> oldPage, List<T> records) {
+        Page<T> newPage = new Page<>();
+        newPage.setCurrent(oldPage.getCurrent());
+        newPage.setSize(oldPage.getSize());
+        newPage.setRecords(records);
+        newPage.setPages(oldPage.getPages());
+        newPage.setTotal(oldPage.getTotal());
+        return getUnifiedPageReturn(newPage);
+    }
+
+    /**
+     * 统一分页返回对象
+     *
+     * @param records 分页数据
+     * @param pageVO  分页参数
+     * @return
+     */
+    public static Map<String, Object> getPageReturn(List records, BasePageVO pageVO) {
+        if (records == null) {
+            return getUnifiedEmptyPage(DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE);
+        }
+        if (CollectionUtils.isEmpty(records)) {
+            return getUnifiedEmptyPage(Math.toIntExact(pageVO.getCurrent()), Math.toIntExact(pageVO.getSize()));
+        }
+        Map<String, Object> resultMap = new HashMap<>(DEFAULT_MAP_CAPACITY);
+        resultMap.put(PAGE_INDEX, pageVO.getCurrent());
+        resultMap.put(PAGE_SIZE, pageVO.getSize());
+        resultMap.put(RECORDS, records);
+        resultMap.put(TOTAL_PAGES, (pageVO.getTotal() + pageVO.getSize() - 1) / pageVO.getSize());
+        resultMap.put(TOTAL_ROWS, pageVO.getTotal());
+
+        return resultMap;
+    }
+
+    public static <T> Page<T> getRAMPageReturn(Integer current, Integer size, List<T> records) {
+        if (Objects.isNull(current)) {
+            current = 1;
+        }
+        if (Objects.isNull(size)) {
+            size = 10;
+        }
+        int startIndex = (current - 1) * size;
+        int endIndex = Math.min(current * size, records.size());
+        if (startIndex > endIndex) {
+            startIndex = endIndex;
+        }
+        Page<T> page = new Page<>();
+        page.setCurrent(current);
+        page.setSize(size);
+        page.setRecords(records.subList(startIndex, endIndex));
+        int totalPages = size == 0 ? 0 : records.size() % size == 0 ? (records.size() / size) : (records.size() / size + 1);
+        page.setPages(totalPages);
+        page.setTotal(records.size());
+        return page;
+    }
+
+
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/RedisUtil.java b/src/main/java/com/canrd/webmagic/common/utils/RedisUtil.java
new file mode 100644
index 0000000..0a5a669
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/RedisUtil.java
@@ -0,0 +1,1443 @@
+package com.canrd.webmagic.common.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.connection.DataType;
+import org.springframework.data.redis.core.*;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @Date: 2020/9/1
+ */
+@Slf4j
+@Repository("redisUtil")
+public class RedisUtil {
+
+    @Resource(name = "stringRedisTemplate")
+    private StringRedisTemplate redisTemplate;
+
+
+    /* -------------------key相关操作--------------------- */
+
+    /**
+     * 删除key
+     *
+     * @param key key key
+     */
+    public void delete(String key) {
+        redisTemplate.delete(key);
+    }
+
+    /**
+     * 批量删除key
+     *
+     * @param keys keys
+     */
+    public void delete(Collection<String> keys) {
+        redisTemplate.delete(keys);
+    }
+
+    /**
+     * 序列化key
+     *
+     * @param key key
+     * @return
+     */
+    public byte[] dump(String key) {
+        return redisTemplate.dump(key);
+    }
+
+    /**
+     * 是否存在key
+     *
+     * @param key key
+     * @return
+     */
+    public Boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 设置过期时间
+     *
+     * @param key     key
+     * @param timeout
+     * @param unit
+     * @return
+     */
+    public Boolean expire(String key, long timeout, TimeUnit unit) {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 设置过期时间
+     *
+     * @param key  key
+     * @param date
+     * @return
+     */
+    public Boolean expireAt(String key, Date date) {
+        return redisTemplate.expireAt(key, date);
+    }
+
+    /**
+     * 查找匹配的key
+     *
+     * @param pattern
+     * @return
+     */
+    public Set<String> keys(String pattern) {
+        return redisTemplate.keys(pattern);
+    }
+
+    /**
+     * 将当前数据库的 key 移动到给定的数据库 db 当中
+     *
+     * @param key     key
+     * @param dbIndex
+     * @return
+     */
+    public Boolean move(String key, int dbIndex) {
+        return redisTemplate.move(key, dbIndex);
+    }
+
+    /**
+     * 移除 key 的过期时间,key 将持久保持
+     *
+     * @param key key
+     * @return
+     */
+    public Boolean persist(String key) {
+        return redisTemplate.persist(key);
+    }
+
+    /**
+     * 返回 key 的剩余的过期时间
+     *
+     * @param key  key
+     * @param unit
+     * @return
+     */
+    public Long getExpire(String key, TimeUnit unit) {
+        return redisTemplate.getExpire(key, unit);
+    }
+
+    /**
+     * 返回 key 的剩余的过期时间
+     *
+     * @param key key
+     * @return
+     */
+    public Long getExpire(String key) {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 从当前数据库中随机返回一个 key
+     *
+     * @return
+     */
+    public String randomKey() {
+        return redisTemplate.randomKey();
+    }
+
+    /**
+     * 修改 key 的名称
+     *
+     * @param oldKey
+     * @param newKey
+     */
+    public void rename(String oldKey, String newKey) {
+        redisTemplate.rename(oldKey, newKey);
+    }
+
+    /**
+     * 仅当 newkey 不存在时,将 oldKey 改名为 newkey
+     *
+     * @param oldKey
+     * @param newKey
+     * @return
+     */
+    public Boolean renameIfAbsent(String oldKey, String newKey) {
+        return redisTemplate.renameIfAbsent(oldKey, newKey);
+    }
+
+    /**
+     * 返回 key 所储存的值的类型
+     *
+     * @param key key
+     * @return
+     */
+    public DataType type(String key) {
+        return redisTemplate.type(key);
+    }
+
+    /**
+     * -------------------string相关操作---------------------
+     *//*
+
+    /**
+     * 设置指定 key 的值
+     * @param key key
+     * @param value val
+     */
+    public void set(String key, String value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 设置缓存,指定有效时间
+     * 时间单位为 秒
+     *
+     * @param key
+     * @param value
+     * @param expireTime
+     */
+    public void set(String key, String value, Long expireTime) {
+        redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置缓存,指定有效时间
+     * 时间单位 自定义
+     *
+     * @param key
+     * @param value
+     * @param expireTime
+     * @param timeUnit
+     */
+    public void set(String key, String value, Long expireTime, TimeUnit timeUnit) {
+        redisTemplate.opsForValue().set(key, value, expireTime, timeUnit);
+    }
+
+    /**
+     * 获取指定 key 的值
+     *
+     * @param key key
+     * @return
+     */
+    public String get(String key) {
+        return redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 返回 key 中字符串值的子字符
+     *
+     * @param key   key
+     * @param start
+     * @param end
+     * @return
+     */
+    public String getRange(String key, long start, long end) {
+        return redisTemplate.opsForValue().get(key, start, end);
+    }
+
+    /**
+     * 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
+     *
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public String getAndSet(String key, String value) {
+        return redisTemplate.opsForValue().getAndSet(key, value);
+    }
+
+    /**
+     * 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
+     *
+     * @param key    key
+     * @param offset
+     * @return
+     */
+    public Boolean getBit(String key, long offset) {
+        return redisTemplate.opsForValue().getBit(key, offset);
+    }
+
+    /**
+     * 批量获取
+     *
+     * @param keys keys
+     * @return 多个key的value
+     */
+    public List<String> multiGet(Collection<String> keys) {
+        return redisTemplate.opsForValue().multiGet(keys);
+    }
+
+    public List<String> multiGetPip(Collection<String> keys) {
+        return redisTemplate.executePipelined((RedisCallback<String>) connection -> {
+            keys.forEach(a -> connection.get(a.getBytes()));
+            return null;
+        }).stream()
+                .filter(Objects::nonNull)
+                .map(Object::toString)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
+     *
+     * @param key    key
+     * @param offset 偏移
+     * @param value  val
+     */
+    public Boolean setBit(String key, long offset, boolean value) {
+        return redisTemplate.opsForValue().setBit(key, offset, value);
+    }
+
+    /**
+     * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
+     *
+     * @param key     key
+     * @param value   val
+     * @param timeout 过期时间
+     * @param unit    时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
+     *                秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
+     */
+    public void setEx(String key, String value, long timeout, TimeUnit unit) {
+        redisTemplate.opsForValue().set(key, value, timeout, unit);
+    }
+
+    /**
+     * 只有在 key 不存在时设置 key 的值
+     *
+     * @param key   key
+     * @param value val
+     * @return 之前已经存在返回false, 不存在返回true
+     */
+    public Boolean setIfAbsent(String key, String value) {
+        return redisTemplate.opsForValue().setIfAbsent(key, value);
+    }
+
+
+    /**
+     * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
+     *
+     * @param key    key
+     * @param value  val
+     * @param offset 从指定位置开始覆写
+     */
+    public void setRange(String key, String value, long offset) {
+        redisTemplate.opsForValue().set(key, value, offset);
+    }
+
+    /**
+     * 获取字符串的长度
+     *
+     * @param key key
+     */
+    public Long size(String key) {
+        return redisTemplate.opsForValue().size(key);
+    }
+
+    /**
+     * 批量添加
+     */
+    public void multiSet(Map<String, String> maps) {
+        redisTemplate.opsForValue().multiSet(maps);
+    }
+
+    /**
+     * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
+     *
+     * @return 之前已经存在返回false, 不存在返回true
+     */
+    public Boolean multiSetIfAbsent(Map<String, String> maps) {
+        return redisTemplate.opsForValue().multiSetIfAbsent(maps);
+    }
+
+    /**
+     * 增加(自增长), 负数则为自减
+     *
+     * @param key       key
+     * @param increment 步长
+     */
+    public Long incrBy(String key, long increment) {
+        return redisTemplate.opsForValue().increment(key, increment);
+    }
+
+    /**
+     * @param key       key
+     * @param increment 步长
+     */
+    public Double incrByFloat(String key, double increment) {
+        return redisTemplate.opsForValue().increment(key, increment);
+    }
+
+    /**
+     * 增加(自增长), 负数则为自减
+     *
+     * @param key       key       redisKey
+     * @param decrement 步长
+     */
+    public Long decreByLong(String key, long decrement) {
+        return redisTemplate.opsForValue().decrement(key, decrement);
+    }
+
+    /**
+     * @param key key redisKey
+     */
+    public Long decreBy(String key) {
+        return redisTemplate.opsForValue().decrement(key);
+    }
+
+    /**
+     * 追加到末尾
+     *
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Integer append(String key, String value) {
+        return redisTemplate.opsForValue().append(key, value);
+    }
+
+    /** -------------------hash相关操作------------------------- */
+
+    /**
+     * 获取存储在哈希表中指定字段的值
+     *
+     * @param key   key
+     * @param field
+     * @return
+     */
+    public Object hGet(String key, String field) {
+        return redisTemplate.opsForHash().get(key, field);
+    }
+
+    /**
+     * 获取所有给定字段的值
+     *
+     * @param key key
+     * @return
+     */
+    public Map<Object, Object> hGetAll(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 获取所有给定字段的值
+     *
+     * @param key    key
+     * @param fields
+     * @return
+     */
+    public List<Object> hMultiGet(String key, Collection<Object> fields) {
+        return redisTemplate.opsForHash().multiGet(key, fields);
+    }
+
+    public void hPut(String key, String hashKey, String value) {
+        redisTemplate.opsForHash().put(key, hashKey, value);
+    }
+
+    public void hPutAll(String key, Map<String, String> maps) {
+        redisTemplate.opsForHash().putAll(key, maps);
+    }
+
+    /**
+     * 仅当hashKey不存在时才设置
+     *
+     * @param key     key
+     * @param hashKey
+     * @param value   val
+     * @return
+     */
+    public Boolean hPutIfAbsent(String key, String hashKey, String value) {
+        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
+    }
+
+    /**
+     * 删除一个或多个哈希表字段
+     *
+     * @param key    key
+     * @param fields
+     * @return
+     */
+    public Long hDelete(String key, Object... fields) {
+        return redisTemplate.opsForHash().delete(key, fields);
+    }
+
+    /**
+     * 查看哈希表 key 中,指定的字段是否存在
+     *
+     * @param key   key
+     * @param field
+     * @return
+     */
+    public Boolean hExists(String key, String field) {
+        return redisTemplate.opsForHash().hasKey(key, field);
+    }
+
+    /**
+     * 为哈希表 key 中的指定字段的整数值加上增量 increment
+     *
+     * @param key       key
+     * @param field
+     * @param increment
+     * @return
+     */
+    public Long hIncrBy(String key, Object field, long increment) {
+        return redisTemplate.opsForHash().increment(key, field, increment);
+    }
+
+    /**
+     * 为哈希表 key 中的指定字段的整数值加上增量 increment
+     *
+     * @param key   key
+     * @param field
+     * @param delta
+     * @return
+     */
+    public Double hIncrByFloat(String key, Object field, double delta) {
+        return redisTemplate.opsForHash().increment(key, field, delta);
+    }
+
+    /**
+     * 获取所有哈希表中的字段
+     *
+     * @param key key
+     * @return
+     */
+    public Set<Object> hKeys(String key) {
+        return redisTemplate.opsForHash().keys(key);
+    }
+
+    /**
+     * 获取哈希表中字段的数量
+     *
+     * @param key key
+     * @return
+     */
+    public Long hSize(String key) {
+        return redisTemplate.opsForHash().size(key);
+    }
+
+    /**
+     * 获取哈希表中所有值
+     *
+     * @param key key
+     * @return
+     */
+    public List<Object> hValues(String key) {
+        return redisTemplate.opsForHash().values(key);
+    }
+
+    /**
+     * 迭代哈希表中的键值对
+     *
+     * @param key     key
+     * @param options
+     * @return
+     */
+    public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
+        return redisTemplate.opsForHash().scan(key, options);
+    }
+
+    /** ------------------------list相关操作---------------------------- */
+
+    /**
+     * 通过索引获取列表中的元素
+     *
+     * @param key   key
+     * @param index
+     * @return
+     */
+    public String lIndex(String key, long index) {
+        return redisTemplate.opsForList().index(key, index);
+    }
+
+    /**
+     * 获取列表指定范围内的元素
+     *
+     * @param key   key
+     * @param start 开始位置, 0是开始位置
+     * @param end   结束位置, -1返回所有
+     * @return
+     */
+    public List<String> lRange(String key, long start, long end) {
+        return redisTemplate.opsForList().range(key, start, end);
+    }
+
+    /**
+     * 存储在list头部
+     *
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lLeftPush(String key, String value) {
+        return redisTemplate.opsForList().leftPush(key, value);
+    }
+
+    /**
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lLeftPushAll(String key, String... value) {
+        return redisTemplate.opsForList().leftPushAll(key, value);
+    }
+
+    /**
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lLeftPushAll(String key, Collection<String> value) {
+        return redisTemplate.opsForList().leftPushAll(key, value);
+    }
+
+    /**
+     * 当list存在的时候才加入
+     *
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lLeftPushIfPresent(String key, String value) {
+        return redisTemplate.opsForList().leftPushIfPresent(key, value);
+    }
+
+    /**
+     * 如果pivot存在,再pivot前面添加
+     *
+     * @param key   key
+     * @param pivot
+     * @param value val
+     * @return
+     */
+    public Long lLeftPush(String key, String pivot, String value) {
+        return redisTemplate.opsForList().leftPush(key, pivot, value);
+    }
+
+    /**
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lRightPush(String key, String value) {
+        return redisTemplate.opsForList().rightPush(key, value);
+    }
+
+    /**
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lRightPushAll(String key, String... value) {
+        return redisTemplate.opsForList().rightPushAll(key, value);
+    }
+
+    /**
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lRightPushAll(String key, Collection<String> value) {
+        return redisTemplate.opsForList().rightPushAll(key, value);
+    }
+
+    /**
+     * 为已存在的列表添加值
+     *
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Long lRightPushIfPresent(String key, String value) {
+        return redisTemplate.opsForList().rightPushIfPresent(key, value);
+    }
+
+    /**
+     * 在pivot元素的右边添加值
+     *
+     * @param key   key
+     * @param pivot
+     * @param value val
+     * @return
+     */
+    public Long lRightPush(String key, String pivot, String value) {
+        return redisTemplate.opsForList().rightPush(key, pivot, value);
+    }
+
+    /**
+     * 通过索引设置列表元素的值
+     *
+     * @param key   key
+     * @param index 位置
+     * @param value val
+     */
+    public void lSet(String key, long index, String value) {
+        redisTemplate.opsForList().set(key, index, value);
+    }
+
+    /**
+     * 移出并获取列表的第一个元素
+     *
+     * @param key key
+     * @return 删除的元素
+     */
+    public String lLeftPop(String key) {
+        return redisTemplate.opsForList().leftPop(key);
+    }
+
+    /**
+     * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
+     *
+     * @param key     key
+     * @param timeout 等待时间
+     * @param unit    时间单位
+     * @return
+     */
+    public String lbLeftPop(String key, long timeout, TimeUnit unit) {
+        return redisTemplate.opsForList().leftPop(key, timeout, unit);
+    }
+
+    /**
+     * 移除并获取列表最后一个元素
+     *
+     * @param key key
+     * @return 删除的元素
+     */
+    public String lRightPop(String key) {
+        return redisTemplate.opsForList().rightPop(key);
+    }
+
+    /**
+     * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
+     *
+     * @param key     key
+     * @param timeout 等待时间
+     * @param unit    时间单位
+     * @return
+     */
+    public String lbRightPop(String key, long timeout, TimeUnit unit) {
+        return redisTemplate.opsForList().rightPop(key, timeout, unit);
+    }
+
+    /**
+     * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
+     *
+     * @param sourceKey
+     * @param destinationKey
+     * @return
+     */
+    public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
+        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
+    }
+
+    /**
+     * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
+     *
+     * @param sourceKey
+     * @param destinationKey
+     * @param timeout
+     * @param unit
+     * @return
+     */
+    public String lbRightPopAndLeftPush(String sourceKey, String destinationKey,
+                                        long timeout, TimeUnit unit) {
+        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit);
+    }
+
+    /**
+     * 删除集合中值等于value得元素
+     *
+     * @param key   key
+     * @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;
+     *              index<0, 从尾部开始删除第一个值等于value的元素;
+     * @param value val
+     * @return
+     */
+    public Long lRemove(String key, long index, String value) {
+        return redisTemplate.opsForList().remove(key, index, value);
+    }
+
+    /**
+     * 裁剪list
+     *
+     * @param key   key
+     * @param start
+     * @param end
+     */
+    public void lTrim(String key, long start, long end) {
+        redisTemplate.opsForList().trim(key, start, end);
+    }
+
+    /**
+     * 获取列表长度
+     *
+     * @param key key
+     * @return
+     */
+    public Long lLen(String key) {
+        return redisTemplate.opsForList().size(key);
+    }
+
+    /** --------------------set相关操作-------------------------- */
+
+    /**
+     * set添加元素
+     *
+     * @param key    key
+     * @param values vals
+     * @return
+     */
+    public Long sAdd(String key, String... values) {
+        return redisTemplate.opsForSet().add(key, values);
+    }
+
+    /**
+     * set移除元素
+     *
+     * @param key    key
+     * @param values vals
+     * @return
+     */
+    public Long sRemove(String key, Object... values) {
+        return redisTemplate.opsForSet().remove(key, values);
+    }
+
+    /**
+     * 移除并返回集合的一个随机元素
+     *
+     * @param key key
+     * @return
+     */
+    public String sPop(String key) {
+        return redisTemplate.opsForSet().pop(key);
+    }
+
+    /**
+     * 移除并返回集合的多个随机元素
+     *
+     * @param key key
+     * @return
+     */
+    public List<String> sPop(String key, int count) {
+        return redisTemplate.opsForSet().pop(key, count);
+    }
+
+    /**
+     * 将元素value从一个集合移到另一个集合
+     *
+     * @param key     key
+     * @param value   val
+     * @param destKey
+     * @return
+     */
+    public Boolean sMove(String key, String value, String destKey) {
+        return redisTemplate.opsForSet().move(key, value, destKey);
+    }
+
+    /**
+     * 获取集合的大小
+     *
+     * @param key key
+     * @return
+     */
+    public Long sSize(String key) {
+        return redisTemplate.opsForSet().size(key);
+    }
+
+    /**
+     * 判断集合是否包含value
+     *
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Boolean sIsMember(String key, Object value) {
+        return redisTemplate.opsForSet().isMember(key, value);
+    }
+
+    /**
+     * 获取两个集合的交集
+     *
+     * @param key      key
+     * @param otherKey
+     * @return
+     */
+    public Set<String> sIntersect(String key, String otherKey) {
+        return redisTemplate.opsForSet().intersect(key, otherKey);
+    }
+
+    /**
+     * 获取key集合与多个集合的交集
+     *
+     * @param key       key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sIntersect(String key, Collection<String> otherKeys) {
+        return redisTemplate.opsForSet().intersect(key, otherKeys);
+    }
+
+    /**
+     * key集合与otherKey集合的交集存储到destKey集合中
+     *
+     * @param key      key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long sIntersectAndStore(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey);
+    }
+
+    /**
+     * key集合与多个集合的交集存储到destKey集合中
+     *
+     * @param key       key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long sIntersectAndStore(String key, Collection<String> otherKeys, String destKey) {
+        return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey);
+    }
+
+    /**
+     * 获取两个集合的并集
+     *
+     * @param key       key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sUnion(String key, String otherKeys) {
+        return redisTemplate.opsForSet().union(key, otherKeys);
+    }
+
+    /**
+     * 获取key集合与多个集合的并集
+     *
+     * @param key       key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sUnion(String key, Collection<String> otherKeys) {
+        return redisTemplate.opsForSet().union(key, otherKeys);
+    }
+
+    /**
+     * key集合与otherKey集合的并集存储到destKey中
+     *
+     * @param key      key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long sUnionAndStore(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
+    }
+
+    /**
+     * key集合与多个集合的并集存储到destKey中
+     *
+     * @param key       key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long sUnionAndStore(String key, Collection<String> otherKeys, String destKey) {
+        return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
+    }
+
+    /**
+     * 获取两个集合的差集
+     *
+     * @param key      key
+     * @param otherKey
+     * @return
+     */
+    public Set<String> sDifference(String key, String otherKey) {
+        return redisTemplate.opsForSet().difference(key, otherKey);
+    }
+
+    /**
+     * 获取key集合与多个集合的差集
+     *
+     * @param key       key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sDifference(String key, Collection<String> otherKeys) {
+        return redisTemplate.opsForSet().difference(key, otherKeys);
+    }
+
+    /**
+     * key集合与otherKey集合的差集存储到destKey中
+     *
+     * @param key      key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long sDifference(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey);
+    }
+
+    /**
+     * key集合与多个集合的差集存储到destKey中
+     *
+     * @param key       key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long sDifference(String key, Collection<String> otherKeys, String destKey) {
+        return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, destKey);
+    }
+
+    /**
+     * 获取集合所有元素
+     *
+     * @param key key
+     * @param
+     * @param
+     * @return
+     */
+    public Set<String> setMembers(String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 随机获取集合中的一个元素
+     *
+     * @param key key
+     * @return
+     */
+    public String sRandomMember(String key) {
+        return redisTemplate.opsForSet().randomMember(key);
+    }
+
+    /**
+     * 随机获取集合中count个元素
+     *
+     * @param key   key
+     * @param count
+     * @return
+     */
+    public List<String> sRandomMembers(String key, long count) {
+        return redisTemplate.opsForSet().randomMembers(key, count);
+    }
+
+    /**
+     * 随机获取集合中count个元素并且去除重复的
+     *
+     * @param key   key
+     * @param count
+     * @return
+     */
+    public Set<String> sDistinctRandomMembers(String key, long count) {
+        return redisTemplate.opsForSet().distinctRandomMembers(key, count);
+    }
+
+    /**
+     * @param key     key
+     * @param options
+     * @return
+     */
+    public Cursor<String> sScan(String key, ScanOptions options) {
+        return redisTemplate.opsForSet().scan(key, options);
+    }
+
+    /**------------------zSet相关操作--------------------------------*/
+
+    /**
+     * 添加元素,有序集合是按照元素的score值由小到大排列
+     *
+     * @param key   key
+     * @param value val
+     * @param score
+     * @return
+     */
+    public Boolean zAdd(String key, String value, double score) {
+        return redisTemplate.opsForZSet().add(key, value, score);
+    }
+
+    /**
+     * @param key    key
+     * @param values val
+     * @return
+     */
+    public Long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> values) {
+        return redisTemplate.opsForZSet().add(key, values);
+    }
+
+    /**
+     * @param key    key
+     * @param values val
+     * @return 已删除元素的数量
+     */
+    public Long zRemove(String key, Object... values) {
+        return redisTemplate.opsForZSet().remove(key, values);
+    }
+
+    /**
+     * 增加元素的score值,并返回增加后的值
+     *
+     * @param key   key
+     * @param value val
+     * @param delta 分数值
+     * @return 增加后的值
+     */
+    public Double zIncrementScore(String key, String value, double delta) {
+        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
+    }
+
+    /**
+     * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
+     *
+     * @param key   key
+     * @param value val
+     * @return 0表示第一位
+     */
+    public Long zRank(String key, Object value) {
+        return redisTemplate.opsForZSet().rank(key, value);
+
+    }
+
+    /**
+     * 返回元素在集合的排名,按元素的score值由大到小排列
+     *
+     * @param key   key
+     * @param value val
+     */
+    public Long zReverseRank(String key, Object value) {
+        return redisTemplate.opsForZSet().reverseRank(key, value);
+    }
+
+    /**
+     * 获取集合的元素, 从小到大排序
+     *
+     * @param key   key
+     * @param start 开始位置
+     * @param end   结束位置, -1查询所有
+     */
+    public Set<String> zRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().range(key, start, end);
+    }
+
+    /**
+     * 获取集合元素, 并且把score值也获取
+     *
+     * @param key   key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start, long end) {
+        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
+    }
+
+    /**
+     * 根据Score值查询集合元素
+     *
+     * @param key key
+     * @param min 最小值
+     * @param max 最大值
+     * @return
+     */
+    public Set<String> zRangeByScore(String key, double min, double max) {
+        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
+    }
+
+    /**
+     * 根据Score值查询集合元素, 从小到大排序
+     *
+     * @param key key
+     * @param min 最小值
+     * @param max 最大值
+     * @return
+     */
+    public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key,
+                                                                          double min, double max) {
+        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
+    }
+
+    /**
+     * @param key   key
+     * @param min
+     * @param max
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double min, double max, long start, long end) {
+        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, start, end);
+    }
+
+    /**
+     * 获取集合的元素, 从大到小排序
+     *
+     * @param key   key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<String> zReverseRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().reverseRange(key, start, end);
+    }
+
+    /**
+     * 获取集合的元素, 从大到小排序, 并返回score值
+     *
+     * @param key   key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key,
+                                                                          long start, long end) {
+        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
+    }
+
+    /**
+     * 根据Score值查询集合元素, 从大到小排序
+     *
+     * @param key key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Set<String> zReverseRangeByScore(String key, double min, double max) {
+        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
+    }
+
+    /**
+     * 根据Score值查询集合元素, 从大到小排序
+     *
+     * @param key key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Set<ZSetOperations.TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double min, double max) {
+        return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max);
+    }
+
+    /**
+     * @param key   key
+     * @param min
+     * @param max
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<String> zReverseRangeByScore(String key, double min,
+                                            double max, long start, long end) {
+        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end);
+    }
+
+    /**
+     * 根据score值获取集合元素数量
+     *
+     * @param key key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Long zCount(String key, double min, double max) {
+        return redisTemplate.opsForZSet().count(key, min, max);
+    }
+
+    /**
+     * 获取集合大小
+     *
+     * @param key key
+     * @return
+     */
+    public Long zSize(String key) {
+        return redisTemplate.opsForZSet().size(key);
+    }
+
+    /**
+     * 获取集合大小
+     *
+     * @param key key
+     * @return
+     */
+    public Long zzCard(String key) {
+        return redisTemplate.opsForZSet().zCard(key);
+    }
+
+    /**
+     * 获取集合中value元素的score值
+     *
+     * @param key   key
+     * @param value val
+     * @return
+     */
+    public Double zScore(String key, Object value) {
+        return redisTemplate.opsForZSet().score(key, value);
+    }
+
+    /**
+     * 移除指定索引位置的成员
+     *
+     * @param key   key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Long zRemoveRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().removeRange(key, start, end);
+    }
+
+    /**
+     * 根据指定的score值的范围来移除成员
+     *
+     * @param key key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Long zRemoveRangeByScore(String key, double min, double max) {
+        return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
+    }
+
+    /**
+     * 获取key和otherKey的并集并存储在destKey中
+     *
+     * @param key      key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long zUnionAndStore(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
+    }
+
+    /**
+     * @param key       key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long zUnionAndStore(String key, Collection<String> otherKeys, String destKey) {
+        return redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
+    }
+
+    /**
+     * 交集
+     *
+     * @param key      key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long zIntersectAndStore(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey);
+    }
+
+    /**
+     * 交集
+     *
+     * @param key       key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long zIntersectAndStore(String key, Collection<String> otherKeys, String destKey) {
+        return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey);
+    }
+
+    /**
+     * @param key     key
+     * @param options
+     * @return
+     */
+    public Cursor<ZSetOperations.TypedTuple<String>> zScan(String key, ScanOptions options) {
+        return redisTemplate.opsForZSet().scan(key, options);
+    }
+
+    /* -------------------锁相关操作--------------------- */
+
+
+    /**
+     * 获得锁
+     */
+    public boolean getLock(String lockId, String value, long millisecond) {
+        Boolean success = redisTemplate.opsForValue().setIfAbsent(lockId, value, millisecond, TimeUnit.MILLISECONDS);
+        return success != null && success;
+    }
+
+    /**
+     * 获得锁,并自旋,返回
+     */
+    public boolean getLock(String lockId, String value, long millisecond, long timeout) {
+        long start = System.currentTimeMillis();
+        do {
+            boolean success = getLock(lockId, value, millisecond);
+            if (success) {
+                return true;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        } while (System.currentTimeMillis() - start < timeout);
+        return false;
+    }
+
+    /**
+     * 释放锁
+     */
+    public void releaseLock(String id, String lockValue) {
+        if (StringUtils.isNotEmpty(lockValue) && lockValue.equals(this.get(id))) {
+            log.debug("RedisUtil releaseLock, key: {}, value: {}", id, lockValue);
+            redisTemplate.delete(id);
+        }
+    }
+
+    /* -------------------HyperLogLog相关操作--------------------- */
+
+    /**
+     * HyperLogLog 添加元素
+     * <p> key不存在则新增
+     *
+     * @param key   key
+     * @param value value集合
+     * @return true表示加入成功
+     * @author A80080
+     * @createDate 2021/3/26
+     */
+    public boolean pfAdd(String key, String... value) {
+        return redisTemplate.opsForHyperLogLog().add(key, value) == 1;
+    }
+
+    /**
+     * HyperLogLog 统计key中元素个数
+     * <p> key 不存在则返回0
+     *
+     * @param key key
+     * @return key中数据大小(基数统计存在误差)
+     * @author A80080
+     * @createDate 2021/3/26
+     */
+    public Long pfCount(String key) {
+        return redisTemplate.opsForHyperLogLog().size(key);
+    }
+
+    /**
+     * 合并HyperLogLog key,并统计新key元素个数
+     * <p>不删除参与合并的key; 新key不存在则新增,
+     *
+     * @param destination 合并后的key名称
+     * @param sourceKeys  参与合并的key
+     * @return 合并后的key元素个数
+     * @author A80080
+     * @createDate 2021/3/26
+     */
+    public Long pfMerge(String destination, String... sourceKeys) {
+        return redisTemplate.opsForHyperLogLog().union(destination, sourceKeys);
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/common/utils/RedisUtils.java b/src/main/java/com/canrd/webmagic/common/utils/RedisUtils.java
new file mode 100644
index 0000000..56975f7
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/RedisUtils.java
@@ -0,0 +1,646 @@
+package com.canrd.webmagic.common.utils;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.Cursor;
+import org.springframework.data.redis.core.RedisConnectionUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author /
+ */
+@Component
+@SuppressWarnings({"unchecked", "all"})
+public class RedisUtils {
+
+    private RedisTemplate<Object, Object> redisTemplate;
+    @Value("${jwt.online-key}")
+    private String onlineKey;
+
+    public RedisUtils(RedisTemplate<Object, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    // =============================common============================
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key  键
+     * @param time 时间(秒)
+     */
+    public boolean expire(String key, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 根据 key 获取过期时间
+     *
+     * @param key 键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public long getExpire(Object key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 查找匹配key
+     *
+     * @param pattern key
+     * @return /
+     */
+    public List<String> scan(String pattern) {
+        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+        Cursor<byte[]> cursor = rc.scan(options);
+        List<String> result = new ArrayList<>();
+        while (cursor.hasNext()) {
+            result.add(new String(cursor.next()));
+        }
+        try {
+            RedisConnectionUtils.releaseConnection(rc, factory);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    /**
+     * 分页查询 key
+     *
+     * @param patternKey key
+     * @param page       页码
+     * @param size       每页数目
+     * @return /
+     */
+    public List<String> findKeysForPage(String patternKey, int page, int size) {
+        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
+        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+        Cursor<byte[]> cursor = rc.scan(options);
+        List<String> result = new ArrayList<>(size);
+        int tmpIndex = 0;
+        int fromIndex = page * size;
+        int toIndex = page * size + size;
+        while (cursor.hasNext()) {
+            if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
+                result.add(new String(cursor.next()));
+                tmpIndex++;
+                continue;
+            }
+            // 获取到满足条件的数据后,就可以退出了
+            if (tmpIndex >= toIndex) {
+                break;
+            }
+            tmpIndex++;
+            cursor.next();
+        }
+        try {
+            RedisConnectionUtils.releaseConnection(rc, factory);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    /**
+     * 判断key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     *
+     * @param key 可以传一个值 或多个
+     */
+    public void del(String... key) {
+        if (key != null && key.length > 0) {
+            if (key.length == 1) {
+                redisTemplate.delete(key[0]);
+            } else {
+                redisTemplate.delete(CollectionUtils.arrayToList(key));
+            }
+        }
+    }
+
+    // ============================String=============================
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 批量获取
+     *
+     * @param keys
+     * @return
+     */
+    public List<Object> multiGet(List<String> keys) {
+        Object obj = redisTemplate.opsForValue().multiGet(Collections.singleton(keys));
+        return null;
+    }
+
+    /**
+     * 普通缓存放入
+     *
+     * @param key   键
+     * @param value 值
+     * @return true成功 false失败
+     */
+    public boolean set(String key, Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key      键
+     * @param value    值
+     * @param time     时间
+     * @param timeUnit 类型
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, timeUnit);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    // ================================Map=================================
+
+    /**
+     * HashGet
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return 值
+     */
+    public Object hget(String key, String item) {
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取hashKey对应的所有键值
+     *
+     * @param key 键
+     * @return 对应的多个键值
+     */
+    public Map<Object, Object> hmget(String key) {
+        return redisTemplate.opsForHash().entries(key);
+
+    }
+
+    /**
+     * HashSet
+     *
+     * @param key 键
+     * @param map 对应多个键值
+     * @return true 成功 false 失败
+     */
+    public boolean hmset(String key, Map<String, Object> map) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     *
+     * @param key  键
+     * @param map  对应多个键值
+     * @param time 时间(秒)
+     * @return true成功 false失败
+     */
+    public boolean hmset(String key, Map<String, Object> map, long time) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value, long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 可以使多个 不能为null
+     */
+    public void hdel(String key, Object... item) {
+        redisTemplate.opsForHash().delete(key, item);
+    }
+
+    /**
+     * 判断hash表中是否有该项的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return true 存在 false不存在
+     */
+    public boolean hHasKey(String key, String item) {
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要增加几(大于0)
+     * @return
+     */
+    public double hincr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * hash递减
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要减少记(小于0)
+     * @return
+     */
+    public double hdecr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, -by);
+    }
+
+    // ============================set=============================
+
+    /**
+     * 根据key获取Set中的所有值
+     *
+     * @param key 键
+     * @return
+     */
+    public Set<Object> sGet(String key) {
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 根据value从一个set中查询,是否存在
+     *
+     * @param key   键
+     * @param value 值
+     * @return true 存在 false不存在
+     */
+    public boolean sHasKey(String key, Object value) {
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSet(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     *
+     * @param key    键
+     * @param time   时间(秒)
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSetAndTime(String key, long time, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 获取set缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long sGetSetSize(String key) {
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 移除值为value的
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public long setRemove(String key, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    // ===============================list=================================
+
+    /**
+     * 获取list缓存的内容
+     *
+     * @param key   键
+     * @param start 开始
+     * @param end   结束 0 到 -1代表所有值
+     * @return
+     */
+    public List<Object> lGet(String key, long start, long end) {
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取list缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long lGetListSize(String key) {
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 通过索引 获取list中的值
+     *
+     * @param key   键
+     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+     * @return
+     */
+    public Object lGetIndex(String key, long index) {
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, Object value, long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value, long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引修改list中的某条数据
+     *
+     * @param key   键
+     * @param index 索引
+     * @param value 值
+     * @return /
+     */
+    public boolean lUpdateIndex(String key, long index, Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 移除N个值为value
+     *
+     * @param key   键
+     * @param count 移除多少个
+     * @param value 值
+     * @return 移除的个数
+     */
+    public long lRemove(String key, long count, Object value) {
+        try {
+            return redisTemplate.opsForList().remove(key, count, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/RequestContextUtil.java b/src/main/java/com/canrd/webmagic/common/utils/RequestContextUtil.java
new file mode 100644
index 0000000..c1ce21d
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/RequestContextUtil.java
@@ -0,0 +1,35 @@
+package com.canrd.webmagic.common.utils;
+
+import org.springframework.web.context.ContextLoader;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+
+public class RequestContextUtil {
+
+    public static HttpServletRequest getRequest() {
+        return getRequestAttributes().getRequest();
+    }
+
+    public static HttpServletResponse getResponse() {
+        return getRequestAttributes().getResponse();
+    }
+
+    public static HttpSession getSession() {
+        return getRequest().getSession();
+    }
+
+    public static ServletRequestAttributes getRequestAttributes() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
+    }
+
+    public static ServletContext getServletContext() {
+        return ContextLoader.getCurrentWebApplicationContext().getServletContext();
+    }
+
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/RequestHolder.java b/src/main/java/com/canrd/webmagic/common/utils/RequestHolder.java
new file mode 100644
index 0000000..e35b2b2
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/RequestHolder.java
@@ -0,0 +1,19 @@
+package com.canrd.webmagic.common.utils;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
+
+/**
+ * 获取 HttpServletRequest
+ *
+ * @date 2018-11-24
+ */
+public class RequestHolder {
+
+    public static HttpServletRequest getHttpServletRequest() {
+        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/RequestStringUtils.java b/src/main/java/com/canrd/webmagic/common/utils/RequestStringUtils.java
new file mode 100644
index 0000000..8df31c1
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/RequestStringUtils.java
@@ -0,0 +1,199 @@
+package com.canrd.webmagic.common.utils;
+
+import cn.hutool.core.io.resource.ClassPathResource;
+import com.canrd.webmagic.common.constant.ElAdminConstant;
+import eu.bitwalker.useragentutils.Browser;
+import eu.bitwalker.useragentutils.UserAgent;
+import org.lionsoul.ip2region.DataBlock;
+import org.lionsoul.ip2region.DbConfig;
+import org.lionsoul.ip2region.DbSearcher;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
+ */
+public class RequestStringUtils extends org.apache.commons.lang3.StringUtils {
+
+    private static final char SEPARATOR = '_';
+
+    private static final String UNKNOWN = "unknown";
+
+    /**
+     * 驼峰命名法工具
+     *
+     * @return toCamelCase(" hello_world ") == "helloWorld"
+     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
+     * toUnderScoreCase("helloWorld") = "hello_world"
+     */
+    public static String toCamelCase(String s) {
+        if (s == null) {
+            return null;
+        }
+
+        s = s.toLowerCase();
+
+        StringBuilder sb = new StringBuilder(s.length());
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+
+            if (c == SEPARATOR) {
+                upperCase = true;
+            } else if (upperCase) {
+                sb.append(Character.toUpperCase(c));
+                upperCase = false;
+            } else {
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * 驼峰命名法工具
+     *
+     * @return toCamelCase(" hello_world ") == "helloWorld"
+     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
+     * toUnderScoreCase("helloWorld") = "hello_world"
+     */
+    public static String toCapitalizeCamelCase(String s) {
+        if (s == null) {
+            return null;
+        }
+        s = toCamelCase(s);
+        return s.substring(0, 1).toUpperCase() + s.substring(1);
+    }
+
+    /**
+     * 驼峰命名法工具
+     *
+     * @return toCamelCase(" hello_world ") == "helloWorld"
+     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
+     * toUnderScoreCase("helloWorld") = "hello_world"
+     */
+    static String toUnderScoreCase(String s) {
+        if (s == null) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+
+            boolean nextUpperCase = true;
+
+            if (i < (s.length() - 1)) {
+                nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
+            }
+
+            if ((i > 0) && Character.isUpperCase(c)) {
+                if (!upperCase || !nextUpperCase) {
+                    sb.append(SEPARATOR);
+                }
+                upperCase = true;
+            } else {
+                upperCase = false;
+            }
+
+            sb.append(Character.toLowerCase(c));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * 获取ip地址
+     */
+    public static String getIp(HttpServletRequest request) {
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        String comma = ",";
+        String localhost = "127.0.0.1";
+        if (ip.contains(comma)) {
+            ip = ip.split(",")[0];
+        }
+        if (localhost.equals(ip)) {
+            // 获取本机真正的ip地址
+            try {
+                ip = InetAddress.getLocalHost().getHostAddress();
+            } catch (UnknownHostException e) {
+                e.printStackTrace();
+            }
+        }
+        return ip;
+    }
+
+    /**
+     * 根据ip获取详细地址
+     */
+    public static String getCityInfo(String ip) {
+        DbSearcher searcher = null;
+        try {
+            String path = "ip2region/ip2region.db";
+            String name = "ip2region.db";
+            DbConfig config = new DbConfig();
+            File file = FileUtil.inputStreamToFile(new ClassPathResource(path).getStream(), name);
+            searcher = new DbSearcher(config, file.getPath());
+            Method method;
+            method = searcher.getClass().getMethod("btreeSearch", String.class);
+            DataBlock dataBlock;
+            dataBlock = (DataBlock) method.invoke(searcher, ip);
+            String address = dataBlock.getRegion().replace("0|", "");
+            char symbol = '|';
+            if (address.charAt(address.length() - 1) == symbol) {
+                address = address.substring(0, address.length() - 1);
+            }
+            return address.equals(ElAdminConstant.REGION) ? "内网IP" : address;
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (searcher != null) {
+                try {
+                    searcher.close();
+                } catch (IOException ignored) {
+                }
+            }
+
+        }
+        return "";
+    }
+
+    public static String getBrowser(HttpServletRequest request) {
+        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
+        Browser browser = userAgent.getBrowser();
+        return browser.getName();
+    }
+
+    /**
+     * 获得当天是周几
+     */
+    public static String getWeekDay() {
+        String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(new Date());
+
+        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
+        if (w < 0) {
+            w = 0;
+        }
+        return weekDays[w];
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/ServletUtils.java b/src/main/java/com/canrd/webmagic/common/utils/ServletUtils.java
new file mode 100644
index 0000000..72a0bc4
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/ServletUtils.java
@@ -0,0 +1,40 @@
+package com.canrd.webmagic.common.utils;
+
+import com.canrd.webmagic.common.constant.ServerResult;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @date 2023-02-02
+ */
+@Slf4j
+public class ServletUtils {
+
+
+    public static void renderServerResult(HttpServletResponse response, ServerResult serverResult, HttpStatus httpStatus) {
+        try {
+            response.setStatus(httpStatus.value());
+            response.setContentType("application/json");
+            response.setCharacterEncoding("utf-8");
+            response.getWriter().print(JsonUtil.toJsonString(serverResult));
+        } catch (IOException e) {
+            log.error("ServletUtils#renderServerResult:", e);
+        }
+    }
+
+
+    public static void renderExcelFileNotFound(HttpServletResponse response) {
+        try {
+            response.setStatus(404);
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
+            //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
+            response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
+            response.sendError(HttpStatus.NOT_FOUND.value(), "下载失败,资源不存在");
+        } catch (IOException e) {
+            log.error("ServletUtils#renderWithResourceNotFound:", e);
+        }
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/SpringContextHolder.java b/src/main/java/com/canrd/webmagic/common/utils/SpringContextHolder.java
new file mode 100644
index 0000000..0f8d41a
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/SpringContextHolder.java
@@ -0,0 +1,66 @@
+package com.canrd.webmagic.common.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ * @author Jie
+ * @date 2019-01-07
+ */
+@Slf4j
+public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
+
+    private static ApplicationContext applicationContext = null;
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) {
+        assertContextInjected();
+        return (T) applicationContext.getBean(name);
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    public static <T> T getBean(Class<T> requiredType) {
+        assertContextInjected();
+        return applicationContext.getBean(requiredType);
+    }
+
+    /**
+     * 检查ApplicationContext不为空.
+     */
+    private static void assertContextInjected() {
+        if (applicationContext == null) {
+            throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
+                    ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
+        }
+    }
+
+    /**
+     * 清除SpringContextHolder中的ApplicationContext为Null.
+     */
+    private static void clearHolder() {
+        log.debug("清除SpringContextHolder中的ApplicationContext:"
+                + applicationContext);
+        applicationContext = null;
+    }
+
+    @Override
+    public void destroy() {
+        SpringContextHolder.clearHolder();
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        if (SpringContextHolder.applicationContext != null) {
+            log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
+        }
+        SpringContextHolder.applicationContext = applicationContext;
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/StringUtils.java b/src/main/java/com/canrd/webmagic/common/utils/StringUtils.java
new file mode 100644
index 0000000..77f998a
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/StringUtils.java
@@ -0,0 +1,1005 @@
+package com.canrd.webmagic.common.utils;
+
+import com.canrd.webmagic.common.constant.Constant;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.CollectionUtils;
+
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @param
+ * @version 0.1.0
+ * @Description
+ * @return
+ * @date 2021/4/16 17:44
+ * @since 0.1.0
+ */
+@Slf4j
+public class StringUtils {
+    /**
+     * 首字母变小写
+     *
+     * @param str
+     * @return
+     */
+    public static String firstCharToLowerCase(String str) {
+        char firstChar = str.charAt(0);
+        if (firstChar >= 'A' && firstChar <= 'Z') {
+            char[] arr = str.toCharArray();
+            arr[0] += ('a' - 'A');
+            return new String(arr);
+        }
+        return str;
+    }
+
+    /**
+     * 首字母变大写
+     *
+     * @param str
+     * @return
+     */
+    public static String firstCharToUpperCase(String str) {
+        char firstChar = str.charAt(0);
+        if (firstChar >= 'a' && firstChar <= 'z') {
+            char[] arr = str.toCharArray();
+            arr[0] -= ('a' - 'A');
+            return new String(arr);
+        }
+        return str;
+    }
+
+    /**
+     * 判断是否为空
+     *
+     * @param str
+     * @return
+     */
+    public static boolean isEmpty(final String str) {
+        return (str == null) || (str.length() == 0);
+    }
+
+    /**
+     * 判断是否不为空
+     *
+     * @param str
+     * @return
+     */
+    public static boolean isNotEmpty(final String str) {
+        return !isEmpty(str);
+    }
+
+    /**
+     * 判断是否空白
+     *
+     * @param str
+     * @return
+     */
+    public static boolean isBlank(final String str) {
+        int strLen;
+        if ((str == null) || ((strLen = str.length()) == 0)) {
+            return true;
+        }
+        for (int i = 0; i < strLen; i++) {
+            if (!Character.isWhitespace(str.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 判断是否不是空白
+     *
+     * @param str
+     * @return
+     */
+    public static boolean isNotBlank(final String str) {
+        return !isBlank(str);
+    }
+
+    /**
+     * 判断多个字符串全部是否为空
+     *
+     * @param strings
+     * @return
+     */
+    public static boolean isAllEmpty(String... strings) {
+        if (strings == null) {
+            return true;
+        }
+        for (String str : strings) {
+            if (isNotEmpty(str)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 判断多个字符串其中任意一个是否为空
+     *
+     * @param strings
+     * @return
+     */
+    public static boolean isHasEmpty(String... strings) {
+        if (strings == null) {
+            return true;
+        }
+        for (String str : strings) {
+            if (isEmpty(str)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断多个字符串是否都为blank
+     *
+     * @param strings
+     * @return
+     */
+    public static boolean isAllBlank(String... strings) {
+        if (strings == null) {
+            return true;
+        }
+        for (String str : strings) {
+            if (isNotBlank(str)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * checkValue为 null 或者为 "" 时返回 defaultValue
+     *
+     * @param checkValue
+     * @param defaultValue
+     * @return
+     */
+    public static String isEmpty(String checkValue, String defaultValue) {
+        return isEmpty(checkValue) ? defaultValue : checkValue;
+    }
+
+    /**
+     * 字符串不为 null 而且不为 "" 并且等于other
+     *
+     * @param str
+     * @param other
+     * @return
+     */
+    public static boolean isNotEmptyAndEqualsOther(String str, String other) {
+        return !isEmpty(str) && str.equals(other);
+    }
+
+    /**
+     * 字符串不为 null 而且不为 "" 并且不等于other
+     *
+     * @param str
+     * @param other
+     * @return
+     */
+    public static boolean isNotEmptyAndNotEqualsOther(String str, String... other) {
+        if (isEmpty(str)) {
+            return false;
+        }
+        for (String s : other) {
+            if (str.equals(s)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 字符串不等于other
+     *
+     * @param str
+     * @param other
+     * @return
+     */
+    public static boolean isNotEqualsOther(String str, String... other) {
+        for (String s : other) {
+            if (s.equals(str)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 判断字符串不为空
+     *
+     * @param strings
+     * @return
+     */
+    public static boolean isNotEmpty(String... strings) {
+        if (strings == null || strings.length == 0) {
+            return false;
+        }
+        for (String str : strings) {
+            if (str == null || "".equals(str.trim())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 比较字符相等
+     *
+     * @param value
+     * @param equals
+     * @return
+     */
+    public static boolean equals(String value, String equals) {
+        if (isAllEmpty(value, equals)) {
+            return true;
+        }
+        //进一步判断value是不是空,如果是空,要直接equals会报NPE异常
+        if (value == null) {
+            return false;
+        }
+        return value.equals(equals);
+    }
+
+    /**
+     * @description 将字符串转换为数字数组
+     * @author 黄楷涵
+     * @date 2020/9/15
+     */
+    public static int[] stringParseToInt(String str, String regex) {
+        return stringParseToInt(str.split(regex));
+    }
+
+    /**
+     * @description 将字符串数组转换为数字数组
+     * @author dengbin
+     * @date 2020/9/15
+     */
+    public static int[] stringParseToInt(String[] str) {
+        int[] num = new int[str.length];
+        for (int i = 0; i < str.length; i++) {
+            num[i] = Integer.parseInt(str[i]);
+        }
+        return num;
+    }
+
+    /**
+     * 比较字符串不相等
+     *
+     * @param value
+     * @param equals
+     * @return
+     */
+    public static boolean isNotEquals(String value, String equals) {
+        return !equals(value, equals);
+    }
+
+    public static String[] split(String content, String separatorChars) {
+        return splitWorker(content, separatorChars, -1, false);
+    }
+
+    public static String[] split(String str, String separatorChars, int max) {
+        return splitWorker(str, separatorChars, max, false);
+    }
+
+    public static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+    private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) {
+        if (str == null) {
+            return null;
+        }
+        int len = str.length();
+        if (len == 0) {
+            return EMPTY_STRING_ARRAY;
+        }
+        List<String> list = new ArrayList<String>();
+        int sizePlus1 = 1;
+        int i = 0, start = 0;
+        boolean match = false;
+        boolean lastMatch = false;
+        if (separatorChars == null) {
+            while (i < len) {
+                if (Character.isWhitespace(str.charAt(i))) {
+                    if (match || preserveAllTokens) {
+                        lastMatch = true;
+                        if (sizePlus1++ == max) {
+                            i = len;
+                            lastMatch = false;
+                        }
+                        list.add(str.substring(start, i));
+                        match = false;
+                    }
+                    start = ++i;
+                    continue;
+                }
+                lastMatch = false;
+                match = true;
+                i++;
+            }
+        } else if (separatorChars.length() == 1) {
+            char sep = separatorChars.charAt(0);
+            while (i < len) {
+                if (str.charAt(i) == sep) {
+                    if (match || preserveAllTokens) {
+                        lastMatch = true;
+                        if (sizePlus1++ == max) {
+                            i = len;
+                            lastMatch = false;
+                        }
+                        list.add(str.substring(start, i));
+                        match = false;
+                    }
+                    start = ++i;
+                    continue;
+                }
+                lastMatch = false;
+                match = true;
+                i++;
+            }
+        } else {
+            while (i < len) {
+                if (separatorChars.indexOf(str.charAt(i)) >= 0) {
+                    if (match || preserveAllTokens) {
+                        lastMatch = true;
+                        if (sizePlus1++ == max) {
+                            i = len;
+                            lastMatch = false;
+                        }
+                        list.add(str.substring(start, i));
+                        match = false;
+                    }
+                    start = ++i;
+                    continue;
+                }
+                lastMatch = false;
+                match = true;
+                i++;
+            }
+        }
+        if (match || (preserveAllTokens && lastMatch)) {
+            list.add(str.substring(start, i));
+        }
+        return list.toArray(EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * 消除转义字符
+     *
+     * @param str
+     * @return
+     */
+    public static String escapeXml(String str) {
+        if (str == null) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < str.length(); ++i) {
+            char c = str.charAt(i);
+            switch (c) {
+                case '\u00FF':
+                case '\u0024':
+                    break;
+                case '&':
+                    sb.append("&amp;");
+                    break;
+                case '<':
+                    sb.append("&lt;");
+                    break;
+                case '>':
+                    sb.append("&gt;");
+                    break;
+                case '\"':
+                    sb.append("&quot;");
+                    break;
+                case '\'':
+                    sb.append("&apos;");
+                    break;
+                default:
+                    if (c <= '\u001F') {
+                        break;
+                    }
+                    if (c >= '\uE000' && c <= '\uF8FF') {
+                        break;
+                    }
+                    if (c >= '\uFFF0') {
+                        break;
+                    }
+                    sb.append(c);
+                    break;
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 将字符串中特定模式的字符转换成map中对应的值
+     *
+     * @param s   需要转换的字符串
+     * @param map 转换所需的键值对集合
+     * @return 转换后的字符串
+     */
+    public static String replace(String s, Map<String, Object> map) {
+        StringBuilder ret = new StringBuilder((int) (s.length() * 1.5));
+        int cursor = 0;
+        for (int start, end; (start = s.indexOf("${", cursor)) != -1 && (end = s.indexOf("}", start)) != -1; ) {
+            ret.append(s.substring(cursor, start)).append(map.get(s.substring(start + 2, end)));
+            cursor = end + 1;
+        }
+        ret.append(s.substring(cursor, s.length()));
+        return ret.toString();
+    }
+
+    public static String replace(String s, Object... objs) {
+        if (objs == null || objs.length == 0) {
+            return s;
+        }
+        if (!s.contains("{}")) {
+            return s;
+        }
+        StringBuilder ret = new StringBuilder((int) (s.length() * 1.5));
+        int cursor = 0;
+        int index = 0;
+        for (int start; (start = s.indexOf("{}", cursor)) != -1; ) {
+            ret.append(s.substring(cursor, start));
+            if (index < objs.length) {
+                ret.append(objs[index]);
+            } else {
+                ret.append("{}");
+            }
+            cursor = start + 2;
+            index++;
+        }
+        ret.append(s.substring(cursor, s.length()));
+        return ret.toString();
+    }
+
+
+    /**
+     * 转换为字节数组
+     *
+     * @param bytes
+     * @return
+     */
+    public static String toString(byte[] bytes) {
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 转换为字节数组
+     *
+     * @param str
+     * @return
+     */
+    public static byte[] getBytes(String str) {
+        return str != null ? str.getBytes(StandardCharsets.UTF_8) : null;
+    }
+
+    public static boolean isNumeric(String cs) {
+        if (isEmpty(cs)) {
+            return false;
+        }
+        for (int i = 0, sz = cs.length(); i < sz; ++i) {
+            if (!Character.isDigit(cs.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+
+    }
+
+    /**
+     * 手机号脱敏
+     *
+     * @param phoneNumber
+     * @return
+     */
+    public static String desensitizedPhoneNumber(String phoneNumber) {
+        if (StringUtils.isNotEmpty(phoneNumber)) {
+            phoneNumber = phoneNumber.replaceAll("(\\w{3})\\w*(\\w{4})", "$1****$2");
+        }
+        return phoneNumber;
+    }
+
+    /**
+     * 校验3位小数
+     *
+     * @param str
+     * @return
+     */
+    public static boolean checkDecimal(String str) {
+        return Pattern.compile(Constant.DICMAL_REGEXP).matcher(str).find();
+    }
+
+    /**
+     * 校验11位国内手机号
+     * <p>规则: 11位数,首位必须为1,第二位可以是3-9;其他位数不限制
+     *
+     * @param phone 手机号
+     * @return 格式正确则返回false; 反之为true
+     * @author A80080
+     * @createDate 2020/12/19
+     */
+    public static boolean checkPhoneNum(String phone) {
+        return StringUtils.isEmpty(phone) || !Pattern.compile("^1([3-9])[0-9]{9}$").matcher(phone).find();
+    }
+
+    /**
+     * @param str
+     * @return boolean
+     * @Description 判断字符串是否含有空格
+     * @version 0.1.0
+     * @author 邓彬
+     * @date 2021/1/13 18:10
+     * @since 0.1.0
+     */
+    public static boolean checkStringContainEmpty(String str) {
+        return !StringUtils.isEmpty(str) && str.contains(" ");
+    }
+
+    /**
+     * 字符串切分
+     *
+     * @param splitStr
+     * @param splitFlag
+     * @return
+     */
+    public static List<String> splitTag(String splitStr, String splitFlag) {
+        return StringUtils.isBlank(splitStr) || StringUtils.isBlank(splitFlag) ?
+                new ArrayList<>() :
+                Arrays.stream(splitStr.split(splitFlag)).collect(Collectors.toList());
+    }
+
+    /**
+     * @param name
+     * @return true代表全是汉字
+     * @Description 校验String是否全是中文
+     * @version 0.1.0
+     * @author 邓彬
+     * @date 2021/1/18 19:54
+     * @since 0.1.0
+     */
+    public static boolean checkNameChina(String name) {
+        boolean res = true;
+        char[] cTemp = name.toCharArray();
+        for (int i = 0; i < name.length(); i++) {
+            if (!isChinese(cTemp[i])) {
+                res = false;
+                break;
+            }
+        }
+        return res;
+    }
+
+    /**
+     * @param c
+     * @return true代表是汉字
+     * @Description 判定输入的是否是汉字
+     * @version 0.1.0
+     * @author 邓彬
+     * @date 2021/1/18 19:53
+     * @since 0.1.0
+     */
+    public static boolean isChinese(char c) {
+        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
+        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
+                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
+                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
+                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
+                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
+                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @param c
+     * @return true代表符合条件
+     * @Description 校验某个字符是否是a-z、A-Z、_、0-9
+     * @version 0.1.0
+     * @author 邓彬
+     * @date 2021/1/18 19:56
+     * @since 0.1.0
+     */
+    public static boolean isWord(char c) {
+        return Pattern.compile("[\\w]").matcher("" + c).matches();
+    }
+
+    /**
+     * @param str
+     * @return boolean
+     * @Description 字符串是否仅包含数字和字母
+     * @version 0.1.0
+     * @author 邓彬
+     * @date 2021/1/18 20:00
+     * @since 0.1.0
+     */
+    public static boolean isLetterDigit(String str) {
+        return str.matches("^[a-z0-9A-Z]+$");
+    }
+
+    /**
+     * @param str
+     * @return boolean
+     * @Description 判断是否是纯数字
+     * @version 0.1.0
+     * @author 邓彬
+     * @date 2021/1/23 16:33
+     * @since 0.1.0
+     */
+    public static boolean isDigit(String str) {
+        return str.matches("^[0-9]+$");
+    }
+
+    /**
+     * 是否合法手机号
+     *
+     * @param phone
+     * @return
+     */
+    public static boolean isMobilePhone(String phone) {
+        return Pattern.compile(Constant.PHONE_REGEXP).matcher(phone).matches();
+    }
+
+    /**
+     * 是否脱敏手机号
+     *
+     * @param phone
+     * @return
+     */
+    public static boolean isDesensitizationMobilePhone(String phone) {
+        return Pattern.compile(Constant.PHONE_DESENSITIZATION_REGEXP).matcher(phone).matches();
+    }
+
+    /**
+     * 判断String是否是整数<br>
+     * 支持10进制
+     *
+     * @param s String
+     * @return 是否为整数
+     */
+    public static boolean isInteger(String s) {
+        try {
+            Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 判断对象能否转
+     *
+     * @param o Object
+     * @return 是否为Integer
+     */
+    public static boolean isInteger(Object o) {
+        try {
+            Integer.parseInt(String.valueOf(o));
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    public static String escapeCharacter(String str) {
+        if (!isEmpty(str)) {
+            if (str.contains("\\")) {
+                str = str.replaceAll("\\\\", "\\\\\\\\");
+            }
+        }
+        return str;
+    }
+
+    /**
+     * @param str
+     * @return boolean
+     * @Description 匹配字符串是,数字、26个英文字母、下划线组成的8-16位的字符串组合
+     * @version 0.1.0
+     * @author A80077-刘始达
+     * @date 2021/04/06
+     * @since 0.1.0
+     */
+    public static boolean checkPWD(String str) {
+        return str.matches("^(?=.*([a-zA-Z].*))(?=.*[0-9].*)[a-zA-Z0-9-_]{8,16}+$");
+    }
+
+    /**
+     * 判断是否包含特殊字符
+     *
+     * @param str
+     * @return
+     */
+    public static boolean checkSpecificSymbol(String str) {
+        String regEx = "[ _`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]|\n|\r|\t";
+        Pattern p = Pattern.compile(regEx);
+        Matcher m = p.matcher(str);
+        return m.find();
+    }
+
+    /**
+     * 拼接图片标签url
+     *
+     * @param str
+     * @param ossDomain
+     * @return
+     */
+    public static String replaceHtmlTag(String str, String ossDomain) {
+        if (StringUtils.isEmpty(str)) {
+            return "";
+        }
+        if (StringUtils.isEmpty(ossDomain)) {
+            return str;
+        }
+        return replaceHtmlTag(str, "img", "src", "src=\"" + ossDomain, "\"");
+    }
+
+    /**
+     * html格式处理:替换指定标签的属性和值
+     *
+     * @param str       需要处理的字符串
+     * @param tag       标签名称
+     * @param tagAttrib 要替换的标签属性值
+     * @param startTag  新标签开始标记
+     * @param endTag    新标签结束标记
+     * @return
+     */
+    public static String replaceHtmlTag(String str, String tag, String tagAttrib, String startTag, String endTag) {
+        String regxpForTag = "<\\s*" + tag + "\\s+([^>]*)\\s*";
+        String regxpForTagAttrib = tagAttrib + "=\\s*\"([^\"]+)\"";
+        Pattern patternForTag = Pattern.compile(regxpForTag, Pattern.CASE_INSENSITIVE);
+        Pattern patternForAttrib = Pattern.compile(regxpForTagAttrib, Pattern.CASE_INSENSITIVE);
+        Matcher matcherForTag = patternForTag.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        boolean result = matcherForTag.find();
+        while (result) {
+            StringBuffer stringBuffer = new StringBuffer("<" + tag + " ");
+            Matcher matcherForAttrib = patternForAttrib.matcher(matcherForTag.group(1));
+            if (matcherForAttrib.find()) {
+                String attributeStr = matcherForAttrib.group(1);
+                if (!attributeStr.contains("https") && !attributeStr.contains("http")) {
+                    matcherForAttrib.appendReplacement(stringBuffer, startTag + attributeStr + endTag);
+                }
+            }
+            matcherForAttrib.appendTail(stringBuffer);
+            matcherForTag.appendReplacement(sb, stringBuffer.toString());
+            result = matcherForTag.find();
+        }
+        matcherForTag.appendTail(sb);
+        return sb.toString();
+    }
+
+    /**
+     * @param str
+     * @return java.util.Map<String, List < String>>
+     * @Description 字符串的连续切割 返回中文词组 数字和字母的集合
+     * @version 0.1.0
+     * @author 邓彬
+     * @date 2021/4/15 13:53
+     * @since 0.1.0
+     */
+    public static Map<String, List<String>> convertStrToChineseList(String str) {
+        if (StringUtils.isEmpty(str)) {
+            return null;
+        }
+        Map<String, List<String>> resultMap = new HashMap<>();
+        List<String> chineseList;
+        List<String> charList;
+        char[] list = str.toCharArray();
+        StringBuilder chineseStr = new StringBuilder();
+        StringBuilder charStr = new StringBuilder();
+        for (char c : list) {
+            if (StringUtils.isChinese(c)) {
+                chineseStr.append(c);
+                charStr.append(Constant.SLASH_MARK_CHARACTER);
+            } else if (Character.isLetterOrDigit(c)) {
+                charStr.append(c);
+                chineseStr.append(Constant.SLASH_MARK_CHARACTER);
+            } else {
+                charStr.append(Constant.SLASH_MARK_CHARACTER);
+                chineseStr.append(Constant.SLASH_MARK_CHARACTER);
+            }
+
+        }
+        chineseList = Arrays.stream(chineseStr.toString()
+                .split(Constant.SLASH_MARK_CHARACTER))
+                .filter(StringUtils::isNotEmpty).collect(Collectors.toList());
+        charList = Arrays.stream(charStr.toString()
+                .split(Constant.SLASH_MARK_CHARACTER))
+                .filter(StringUtils::isNotEmpty).map(String::toLowerCase).collect(Collectors.toList());
+
+        resultMap.put("chineseKey", chineseList);
+        resultMap.put("charKey", charList);
+        return resultMap;
+    }
+
+    /**
+     * 单个字符转换为小写
+     *
+     * @param c
+     * @return
+     */
+    public static char singleCharToLowerCase(char c) {
+        if (c >= 'A' && c <= 'Z') {
+            c += ('a' - 'A');
+            return c;
+        }
+        return c;
+    }
+
+    public static boolean isLong(String shopId) {
+        try {
+            Long.parseLong(shopId);
+        } catch (NumberFormatException e) {
+            return false;
+        }
+        return true;
+    }
+
+    public static String priceFormatting(String price) {
+        if (StringUtils.isEmpty(price)) {
+            return Constant.EMPTY_STRING;
+        }
+
+        String newPrice = "";
+        // 价格不存在小数点,则直接返回,无需处理
+        if (!price.contains(Constant.POINT_BAR_CHARACTER)) {
+            newPrice = price;
+            return newPrice;
+        }
+
+        // 有小数点,则分割字符串
+        String[] s = price.split("\\.");
+
+        // 小数位数大于0 且小于2 且存在非0字符
+        if (s[1].length() > 0 && s[1].length() <= 2 && !judgeIsNumeric(s[1])) {
+            BigDecimal bigDecimal = new BigDecimal(price).setScale(2, BigDecimal.ROUND_HALF_UP);
+            newPrice = bigDecimal.toPlainString();
+        }
+        // 【保留这个else if】小数位数大于0 且大于2 且存在非0字符,理论上不会存在,因为新增商品时已经限定最多输入2个小数点
+        else if (s[1].length() > 0 && s[1].length() > 2 && !judgeIsNumeric(s[1])) {
+            BigDecimal bigDecimal = new BigDecimal(price).setScale(2, BigDecimal.ROUND_HALF_UP);
+            String[] split = bigDecimal.toPlainString().split("\\.");
+            if (split[1].length() > 0 && judgeIsNumeric(split[1])) {
+                newPrice = new BigDecimal(bigDecimal.toPlainString()).setScale(0, BigDecimal.ROUND_HALF_UP).toPlainString();
+            } else {
+                newPrice = bigDecimal.toPlainString();
+            }
+        } else {
+            // 小数位数大于0 且小于2 且小数点都是0
+            newPrice = new BigDecimal(price).setScale(0, BigDecimal.ROUND_HALF_UP).toPlainString();
+        }
+        return newPrice;
+    }
+
+    /**
+     * 判断字符串是否只包含0
+     *
+     * @param str
+     * @return
+     */
+    private static boolean judgeIsNumeric(String str) {
+        return Pattern.compile("[0]*").matcher(str).matches();
+    }
+
+    /**
+     * 字符串以","分隔后转为String[]
+     *
+     * @param s
+     * @return
+     */
+    public static String[] string2ArraySplitByComma(String s) {
+        if (s == null || s.length() == 0) {
+            return new String[]{};
+        }
+        return s.split(Constant.COMMA_CHARACTER);
+    }
+
+    public static boolean isStringArrayContainsSpecifiedValue(String[] strings, String value) {
+        if (null == strings || strings.length <= 0 || value == null || value.length() == 0) {
+            return false;
+        }
+        boolean isContainsSpecifiedValue = false;
+        for (String s : strings) {
+            if (s.equals(value)) {
+                isContainsSpecifiedValue = true;
+                break;
+            }
+        }
+        return isContainsSpecifiedValue;
+    }
+
+    /**
+     * 字符串以","分隔后转为list
+     *
+     * @param s
+     * @return
+     */
+    public static List<String> string2ListSplitByComma(String s) {
+        List<String> stringList = new ArrayList<>();
+        if (s == null || s.length() == 0) {
+            return stringList;
+        }
+        String[] strArray = string2ArraySplitByComma(s);
+
+        for (String st : strArray) {
+            if (null != st && st.trim() != null || st.trim().length() != 0) {
+                stringList.add(st.trim());
+            }
+        }
+
+        return stringList;
+    }
+
+    /**
+     * 字符串按照特殊符号
+     * ".*[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\\\]+.*"
+     * 截取转成字符串集合
+     *
+     * @param str
+     * @return
+     */
+    public static List<String> splitStringBySpecificSymbol(String str) throws PatternSyntaxException {
+        String regEx = "[`~!@#$%^&*()\\-+=|{}':;,\\[\\].<>/?!¥…()—【】‘;:”“’。,、?\\\\]";
+        Pattern p = Pattern.compile(regEx);
+        Matcher m = p.matcher(str);
+        return Stream.of(m.replaceAll("_").split("_")).map(String::trim).filter(StringUtils::isNotBlank).collect(Collectors.toList());
+    }
+
+    /**
+     * 将list转为字符串,用逗号隔开
+     *
+     * @param list
+     * @return
+     */
+    public static String listTransToStr(List<String> list) {
+        if (CollectionUtils.isEmpty(list)) return null;
+        StringBuilder sb = new StringBuilder();
+        list.forEach(x -> {
+            sb.append(x);
+            sb.append(",");
+        });
+        String str = sb.toString();
+        return str.substring(0, str.length() - 1);
+    }
+
+    /**
+     * 是否包含中英文
+     *
+     * @param str
+     * @return
+     */
+    public static boolean checkCnAndEn(String str) {
+        String regEx = Constant.CHI_EN_REGEXP;
+        Pattern p = Pattern.compile(regEx);
+        Matcher m = p.matcher(str);
+        return m.find();
+    }
+
+
+    /**
+     * @param code 要隐藏显示的字符串
+     * @param head 前面保留的位数
+     * @param tail 后面保留的位数
+     * @return 处理后的字符串
+     */
+    public static String getEncryptCode(String code, int head, int tail) {
+        int body = code.length() - head - tail;
+        String regexVar = "(\\w{%d})(\\w{%d})(\\w{%d})";
+        String regex = String.format(regexVar, head, body, tail);
+        String bodyPart = code.replaceAll(regex, "$2");
+        String bodyEncrypt = bodyPart.replaceAll("\\w", "*");
+        String replacement = String.format("$1%s$3", bodyEncrypt);
+        return code.replaceAll(regex, replacement);
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/TemplateFormatUtils.java b/src/main/java/com/canrd/webmagic/common/utils/TemplateFormatUtils.java
new file mode 100644
index 0000000..d88c052
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/TemplateFormatUtils.java
@@ -0,0 +1,57 @@
+package com.canrd.webmagic.common.utils;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * 消息模板占位符替换工具
+ *
+ * @author fanzhenyu
+ * @date 2022-04-20
+ */
+public class TemplateFormatUtils {
+
+
+    /**
+     * 正则 匹配 { + "多个任意字符" + }
+     */
+    private static final String DEFAULT_REG = "\\{[\\w]+\\}";
+
+    /**
+     *
+     * @param text 原模板 占位符格式必须为:{fieldName}
+     * @param map 模板参数
+     * @return
+     */
+    public static String replaceTemplateContent(String text, Map<String, String> map){
+        return replaceTemplateContent(text,map,DEFAULT_REG);
+    }
+
+    /**
+     *
+     * @param text 原模板 占位符格式必须为:{fieldName}
+     * @param map 模板参数
+     * @param reg 自定义占位符样式 - 正则匹配
+     * @return
+     */
+    public static String replaceTemplateContent(String text,Map<String,String> map,String reg){
+
+        if(StringUtils.isBlank(reg)){
+            reg = DEFAULT_REG;
+        }
+
+        Pattern pattern = Pattern.compile(reg);
+        Matcher m = pattern.matcher(text);
+        while(m.find()){
+            String currentGroup = m.group();
+            String currentPattern = currentGroup.replaceAll("^\\{", "").replaceAll("\\}$", "").trim();
+            String mapValue = map.get(currentPattern);
+            if (mapValue != null){
+                text = text.replace(currentGroup, mapValue);
+            }
+        }
+        return text;
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/ThrowableUtil.java b/src/main/java/com/canrd/webmagic/common/utils/ThrowableUtil.java
new file mode 100644
index 0000000..da67ff3
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/ThrowableUtil.java
@@ -0,0 +1,36 @@
+package com.canrd.webmagic.common.utils;
+
+import com.canrd.webmagic.common.exception.BusinessException;
+
+import javax.validation.ConstraintViolationException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * 异常工具 2019-01-06
+ */
+public class ThrowableUtil {
+
+    /**
+     * 获取堆栈信息
+     */
+    public static String getStackTrace(Throwable throwable) {
+        StringWriter sw = new StringWriter();
+        try (PrintWriter pw = new PrintWriter(sw)) {
+            throwable.printStackTrace(pw);
+            return sw.toString();
+        }
+    }
+
+    public static void throwForeignKeyException(Throwable e, String msg) {
+        Throwable t = e.getCause();
+        while ((t != null) && !(t instanceof ConstraintViolationException)) {
+            t = t.getCause();
+        }
+        if (t != null) {
+            throw new BusinessException(msg);
+        }
+        assert false;
+        throw new BusinessException("删除失败:" + t.getMessage());
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/common/utils/TransactionHelper.java b/src/main/java/com/canrd/webmagic/common/utils/TransactionHelper.java
new file mode 100644
index 0000000..0ef7d58
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/common/utils/TransactionHelper.java
@@ -0,0 +1,27 @@
+package com.canrd.webmagic.common.utils;
+
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.function.Supplier;
+
+/**
+ * @author:
+ * @date: 2021/12/21
+ */
+@Component
+public class TransactionHelper {
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
+    public <T> T run(Supplier<T> command) {
+        return command.get();
+    }
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
+    public void run(Runnable command) {
+        command.run();
+    }
+
+
+}
diff --git a/src/main/java/com/canrd/webmagic/config/AdminMetaObjectHandler.java b/src/main/java/com/canrd/webmagic/config/AdminMetaObjectHandler.java
new file mode 100644
index 0000000..ddee7b5
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/config/AdminMetaObjectHandler.java
@@ -0,0 +1,66 @@
+package com.canrd.webmagic.config;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.canrd.webmagic.common.constant.Constant;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+
+@Slf4j
+@Component
+public class AdminMetaObjectHandler implements MetaObjectHandler {
+
+
+    private static final String CREATE_TIME = "createTime";
+    private static final String MODIFY_TIME = "modifyTime";
+    private static final String CREATE_BY = "createBy";
+    private static final String MODIFY_BY = "modifyBy";
+    private static final String ENABLE_FLAG = "enableFlag";
+    private static final String STRING_CLASS_NAME = "java.lang.String";
+    private static final String LONG_CLASS_NAME = "java.lang.Long";
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        LocalDateTime now = LocalDateTime.now();
+        this.strictInsertFill(metaObject, CREATE_TIME, LocalDateTime.class, now);
+        this.strictInsertFill(metaObject, MODIFY_TIME, LocalDateTime.class, now);
+        this.strictInsertFill(metaObject, ENABLE_FLAG, Integer.class, Constant.ENABLE_TEN);
+        try {
+//            UserDetails loginUser = SecurityUtils.getUserDetails();
+            this.strictInsertFill(metaObject, CREATE_BY, String.class, "system");
+            this.strictInsertFill(metaObject, MODIFY_BY, String.class, "system");
+
+//            this.strictInsertFill(metaObject, CREATE_BY, String.class, loginUser.getUsername());
+//            this.strictInsertFill(metaObject, MODIFY_BY, String.class, loginUser.getUsername());
+        } catch (Exception e) {
+            this.strictInsertFill(metaObject, CREATE_BY, String.class, "system");
+
+            this.strictInsertFill(metaObject, MODIFY_BY, String.class, "system");
+        }
+
+
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+
+        this.strictUpdateFill(metaObject, MODIFY_TIME, LocalDateTime.class, LocalDateTime.now());
+
+
+        try {
+//            UserDetails loginUser = SecurityUtils.getUserDetails();
+//            if (null == loginUser) {
+//                return;
+//            }
+//            this.strictUpdateFill(metaObject, MODIFY_BY, String.class, loginUser.getUsername());
+            this.strictUpdateFill(metaObject, MODIFY_BY, String.class, "system");
+
+        } catch (Exception e) {
+            this.strictUpdateFill(metaObject, MODIFY_BY, String.class, "system");
+        }
+
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/config/ConfigurerAdapter.java b/src/main/java/com/canrd/webmagic/config/ConfigurerAdapter.java
new file mode 100644
index 0000000..15e7898
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/config/ConfigurerAdapter.java
@@ -0,0 +1,32 @@
+package com.canrd.webmagic.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * WebMvcConfigurer
+ *
+ * @date 2018-11-30
+ */
+@Configuration
+@EnableWebMvc
+public class ConfigurerAdapter implements WebMvcConfigurer {
+
+
+    @Bean
+    public CorsFilter corsFilter() {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        config.addAllowedOrigin("*");
+        config.addAllowedHeader("*");
+        config.addAllowedMethod("*");
+        source.registerCorsConfiguration("/**", config);
+        return new CorsFilter(source);
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/config/MybatisPlusConfig.java b/src/main/java/com/canrd/webmagic/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..306a654
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/config/MybatisPlusConfig.java
@@ -0,0 +1,9 @@
+package com.canrd.webmagic.config;
+
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+public class MybatisPlusConfig {
+
+}
diff --git a/src/main/java/com/canrd/webmagic/config/RedisConfig.java b/src/main/java/com/canrd/webmagic/config/RedisConfig.java
new file mode 100644
index 0000000..832b184
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/config/RedisConfig.java
@@ -0,0 +1,55 @@
+package com.canrd.webmagic.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import javax.annotation.Resource;
+
+
+public class RedisConfig {
+
+
+    @Resource
+    private LettuceConnectionFactory lqlcfactory;
+
+    /**
+     * @param connectionFactory
+     * @return
+     */
+    @Bean
+    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate template = new RedisTemplate();
+        template.setConnectionFactory(connectionFactory);
+        return template;
+    }
+
+    /**
+     * 配置该bean是为了获取logistics 1.0存入redis的数据
+     * 序列化方式保持跟logistics 1.0统一
+     *
+     * @param factory
+     * @return
+     */
+    @Bean(name = "logisticsRedisTemplate")
+    public RedisTemplate<String, Object> logisticsRedisTemplate(RedisConnectionFactory factory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(lqlcfactory);
+        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        return redisTemplate;
+    }
+
+    @Bean
+    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
+        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
+        // lqlcfactory.setShareNativeConnection(true);
+        stringRedisTemplate.setConnectionFactory(lqlcfactory);
+        return stringRedisTemplate;
+
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/config/RestTemplateConfig.java b/src/main/java/com/canrd/webmagic/config/RestTemplateConfig.java
new file mode 100644
index 0000000..52f7ab0
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/config/RestTemplateConfig.java
@@ -0,0 +1,26 @@
+package com.canrd.webmagic.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+
+@Configuration
+public class RestTemplateConfig {
+
+    @Bean
+    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
+        return new RestTemplate(factory);
+    }
+
+    @Bean
+    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setReadTimeout(3000);
+        factory.setConnectTimeout(5000);
+        return factory;
+    }
+
+}
diff --git a/src/main/java/com/canrd/webmagic/config/WebConfig.java b/src/main/java/com/canrd/webmagic/config/WebConfig.java
new file mode 100644
index 0000000..663b95a
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/config/WebConfig.java
@@ -0,0 +1,96 @@
+package com.canrd.webmagic.config;
+
+import com.alibaba.fastjson.support.config.FastJsonConfig;
+import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+@Slf4j
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+
+    @Override
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        FastJsonHttpMessageConverter fastConverter = getFastJsonHttpMessageConverter();
+
+        StringHttpMessageConverter stringHttpMessageConverter = getStringHttpMessageConverter();
+
+        /**
+         * 添加StringHttpMessageConverter ,让其转化String。注意顺序,StringHttpMessageConverter 要在FastJsonHttpMessageConverter 之前。
+         *
+         * https://www.cnblogs.com/slankka/p/11437034.html
+         */
+        converters.add(0, fastConverter);
+        converters.add(0, stringHttpMessageConverter);
+        log.info("HttpMessageConverter顺序\n{}", converters.stream().map(c -> c.getClass().getName()).collect(Collectors.joining("\n")));
+    }
+
+    private static StringHttpMessageConverter getStringHttpMessageConverter() {
+        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
+        stringHttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_PLAIN));
+        return stringHttpMessageConverter;
+    }
+
+    private static FastJsonHttpMessageConverter getFastJsonHttpMessageConverter() {
+        FastJsonConfig fastJsonConfig = new FastJsonConfig();
+        //解决空值序列化的问题,改为不序列化
+        /*fastJsonConfig.setSerializeFilters(new PropertyFilter() {
+            @Override
+            public boolean apply(Object source,String name, Object value) {
+                //if(value.getClass().isPrimitive() == null){
+                if(value == null){
+                    return false;
+                }
+                return true;
+            }
+        });*/
+        fastJsonConfig.setCharset(StandardCharsets.UTF_8);
+        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
+        List<MediaType> supportedMediaTypes = new ArrayList<>();
+        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
+        supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
+        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
+//        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
+        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
+        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
+        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
+        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
+//        supportedMediaTypes.add(MediaType.APPLICATION_XML);
+        supportedMediaTypes.add(MediaType.IMAGE_GIF);
+        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
+        supportedMediaTypes.add(MediaType.IMAGE_PNG);
+        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
+        supportedMediaTypes.add(MediaType.TEXT_HTML);
+        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
+        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
+//        supportedMediaTypes.add(MediaType.TEXT_XML);
+        fastConverter.setSupportedMediaTypes(supportedMediaTypes);
+        fastConverter.setFastJsonConfig(fastJsonConfig);
+        fastConverter.setDefaultCharset(StandardCharsets.UTF_8);
+        return fastConverter;
+    }
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/**").addResourceLocations(
+                "classpath:/static/");
+        registry.addResourceHandler("swagger-ui.html").addResourceLocations(
+                "classpath:/META-INF/resources/");
+        registry.addResourceHandler("/webjars/**").addResourceLocations(
+                "classpath:/META-INF/resources/webjars/");
+        WebMvcConfigurer.super.addResourceHandlers(registry);
+    }
+}
diff --git a/src/main/java/com/canrd/webmagic/controller/TestController.java b/src/main/java/com/canrd/webmagic/controller/TestController.java
new file mode 100644
index 0000000..2bf7dc8
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/controller/TestController.java
@@ -0,0 +1,88 @@
+package com.canrd.webmagic.controller;
+
+
+import com.canrd.webmagic.common.constant.ServerResult;
+import com.canrd.webmagic.common.jsr303.OperateGroup;
+import com.canrd.webmagic.domain.vo.TestQueryVO;
+import com.canrd.webmagic.domain.vo.TestVO;
+import com.canrd.webmagic.service.TestService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * (Test)表控制层
+ *
+ * @author makejava
+ * @since 2024-01-12 14:36:56
+ */
+@RestController
+@RequestMapping("/lift-hub/test")
+public class TestController {
+    /**
+     * 服务对象
+     */
+    @Resource
+    private TestService testService;
+
+    /**
+     * 分页查询
+     *
+     * @param testQueryVO 查询条件
+     * @return 查询结果
+     */
+    @PostMapping("/list")
+    public ServerResult list(@RequestBody @Validated({OperateGroup.List.class}) TestQueryVO testQueryVO) {
+        return testService.list(testQueryVO);
+    }
+
+    /**
+     * 通过主键查询单条数据
+     *
+     * @param testQueryVO 查询条件
+     * @return 单条数据
+     */
+    @PostMapping("/query_by_id")
+    public ServerResult queryById(@RequestBody TestQueryVO testQueryVO) {
+        return testService.queryById(testQueryVO);
+    }
+
+    /**
+     * 新增数据
+     *
+     * @param testVO 数据VO
+     * @return 新增结果
+     */
+    @PostMapping("/add")
+    public ServerResult add(@RequestBody TestVO testVO) {
+        return testService.add(testVO);
+    }
+
+    /**
+     * 编辑数据
+     *
+     * @param testVO 数据VO
+     * @return 编辑结果
+     */
+    @PostMapping("/edit")
+    public ServerResult edit(@RequestBody TestVO testVO) {
+        return testService.edit(testVO);
+    }
+
+    /**
+     * 删除数据
+     *
+     * @param testQueryVO 查询条件
+     * @return 删除是否成功
+     */
+    @PostMapping("/delete_by_id")
+    public ServerResult deleteById(@RequestBody TestQueryVO testQueryVO) {
+        return testService.deleteById(testQueryVO);
+    }
+
+}
+
diff --git a/src/main/java/com/canrd/webmagic/domain/dto/BaseDO.java b/src/main/java/com/canrd/webmagic/domain/dto/BaseDO.java
new file mode 100644
index 0000000..3340708
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/domain/dto/BaseDO.java
@@ -0,0 +1,75 @@
+package com.canrd.webmagic.domain.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.canrd.webmagic.common.utils.DateUtil;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.apache.ibatis.type.LocalDateTimeTypeHandler;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+
+@Data
+@SuperBuilder(toBuilder = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseDO implements Serializable {
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT, typeHandler = LocalDateTimeTypeHandler.class)
+    @JsonFormat(pattern = DateUtil.DATE_TIME, timezone = DateUtil.Zone.GMT8)
+    @DateTimeFormat(pattern = DateUtil.DATE_TIME)
+    @JSONField(format = DateUtil.DATE_TIME)
+    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
+    @JsonSerialize(using = LocalDateTimeSerializer.class)
+    private LocalDateTime createTime;
+
+    /**
+     * 创建人
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /**
+     * 修改时间
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE, typeHandler = LocalDateTimeTypeHandler.class)
+    @JsonFormat(pattern = DateUtil.DATE_TIME, timezone = DateUtil.Zone.GMT8)
+    @DateTimeFormat(pattern = DateUtil.DATE_TIME)
+    @JSONField(format = DateUtil.DATE_TIME)
+    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
+    @JsonSerialize(using = LocalDateTimeSerializer.class)
+    private LocalDateTime modifyTime;
+
+    /**
+     * 修改人
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String modifyBy;
+
+    /**
+     * 是否可用 10-可用 20-删除 30-禁用
+     */
+    @TableLogic
+    @TableField(fill = FieldFill.INSERT)
+    private Integer enableFlag;
+
+    /**
+     * 版本号--乐观锁预留字段
+     */
+    private Integer version;
+}
diff --git a/src/main/java/com/canrd/webmagic/domain/dto/TestDO.java b/src/main/java/com/canrd/webmagic/domain/dto/TestDO.java
new file mode 100644
index 0000000..d9412b0
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/domain/dto/TestDO.java
@@ -0,0 +1,32 @@
+package com.canrd.webmagic.domain.dto;
+
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+import lombok.experimental.SuperBuilder;
+
+import java.io.Serializable;
+
+/**
+ * (Test)实体类
+ *
+ * @author makejava
+ * @since 2024-01-12 14:36:56
+ */
+@TableName("test")
+@Data
+@AllArgsConstructor
+@ToString
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+@SuperBuilder
+public class TestDO extends BaseDO implements Serializable {
+    private static final long serialVersionUID = 856841241099412904L;
+
+    private Integer id;
+
+    private String username;
+
+    private String email;
+
+}
diff --git a/src/main/java/com/canrd/webmagic/domain/vo/BasePageVO.java b/src/main/java/com/canrd/webmagic/domain/vo/BasePageVO.java
new file mode 100644
index 0000000..1995e9d
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/domain/vo/BasePageVO.java
@@ -0,0 +1,43 @@
+package com.canrd.webmagic.domain.vo;
+
+import com.canrd.webmagic.common.constant.Constant;
+import com.canrd.webmagic.common.jsr303.OperateGroup;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+
+@SuperBuilder(toBuilder = true)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BasePageVO {
+
+    /**
+     * 页码
+     */
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @NotNull(message = "页码不能为空", groups = {OperateGroup.List.class})
+    @Min(value = Constant.ONE, message = "页码不能小于1", groups = {OperateGroup.List.class})
+    private Integer current = 1;
+    /**
+     * 页
+     */
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    @NotNull(message = "每页大小不能为空", groups = {OperateGroup.List.class})
+    @Min(value = 1, message = "每页大小不能小于1", groups = {OperateGroup.List.class})
+    @Max(value = 5000, message = "每页大小不能大于5000", groups = {OperateGroup.List.class})
+    private Integer size = 10;
+
+    /**
+     * 总数
+     */
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Integer total;
+}
diff --git a/src/main/java/com/canrd/webmagic/domain/vo/TestQueryVO.java b/src/main/java/com/canrd/webmagic/domain/vo/TestQueryVO.java
new file mode 100644
index 0000000..645d29b
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/domain/vo/TestQueryVO.java
@@ -0,0 +1,33 @@
+package com.canrd.webmagic.domain.vo;
+
+import lombok.*;
+import lombok.experimental.SuperBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author makejava
+ * @since 2024-01-12 14:36:56
+ */
+@Data
+@AllArgsConstructor
+@ToString
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+@SuperBuilder
+public class TestQueryVO extends BasePageVO implements Serializable {
+    private static final long serialVersionUID = 542072710112757366L;
+
+    private List<Long> ids;
+
+
+    private Integer id;
+
+    private String username;
+
+    private String email;
+
+
+}
+
diff --git a/src/main/java/com/canrd/webmagic/domain/vo/TestVO.java b/src/main/java/com/canrd/webmagic/domain/vo/TestVO.java
new file mode 100644
index 0000000..1ab4616
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/domain/vo/TestVO.java
@@ -0,0 +1,31 @@
+package com.canrd.webmagic.domain.vo;
+
+
+import lombok.*;
+import lombok.experimental.SuperBuilder;
+
+import java.io.Serializable;
+
+/**
+ * (Test)实体类
+ *
+ * @author makejava
+ * @since 2024-01-12 14:36:56
+ */
+@Data
+@AllArgsConstructor
+@ToString
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+@SuperBuilder
+public class TestVO implements Serializable {
+    private static final long serialVersionUID = 257625796831027116L;
+
+    private Integer id;
+
+    private String username;
+
+    private String email;
+
+
+}
diff --git a/src/main/java/com/canrd/webmagic/mapper/TestMapper.java b/src/main/java/com/canrd/webmagic/mapper/TestMapper.java
new file mode 100644
index 0000000..5d47008
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/mapper/TestMapper.java
@@ -0,0 +1,16 @@
+package com.canrd.webmagic.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.canrd.webmagic.domain.dto.TestDO;
+
+/**
+ * (Test)表数据库访问层
+ *
+ * @author makejava
+ * @since 2024-01-12 14:36:56
+ */
+public interface TestMapper extends BaseMapper<TestDO> {
+
+
+}
+
diff --git a/src/main/java/com/canrd/webmagic/processor/BaiduHotSearchPageProcessor.java b/src/main/java/com/canrd/webmagic/processor/BaiduHotSearchPageProcessor.java
new file mode 100644
index 0000000..d43ba90
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/processor/BaiduHotSearchPageProcessor.java
@@ -0,0 +1,66 @@
+package com.canrd.webmagic.processor;
+
+import us.codecraft.webmagic.Page;
+import us.codecraft.webmagic.Site;
+import us.codecraft.webmagic.Spider;
+import us.codecraft.webmagic.processor.PageProcessor;
+import us.codecraft.webmagic.selector.Selectable;
+import us.codecraft.webmagic.selector.XpathSelector;
+
+import java.util.List;
+
+/**
+ * @author: xms
+ * @description: TODO
+ * @date: 2024/4/1 14:19
+ * @version: 1.0
+ */
+public class BaiduHotSearchPageProcessor implements PageProcessor {
+
+    // 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
+    private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
+
+    /**
+     * 定制爬虫逻辑的核心接口,在这里编写抽取逻辑
+     *
+     * @param page
+     */
+    @Override
+    public void process(Page page) {
+
+        System.out.println(page.getHtml());
+        /**
+         * 通过page.getHtml()可以获取到main函数中Spider.create(new BaiduHotSearchPageProcessor()).addUrl中的地址的网页内容
+         * 1、通过$或css()方法获取到该page html下某元素dom
+         */
+        Selectable selectable = page.getHtml().$(".theme-hot").select(
+                new XpathSelector("a[@class='item-wrap_2oCLZ']")
+        );
+        List<Selectable> nodes = selectable.nodes();
+
+        /**
+         * 获取到指定的dom后,从这些dom中提取元素内容。
+         */
+        System.out.println("今日百度热搜:");
+        for (int i = 1; i <= nodes.size() - 1; i++) {
+            Selectable node = nodes.get(i);
+            String link = node.$(".item-wrap_2oCLZ", "href").get();
+            String title = node.$(".c-single-text-ellipsis", "text").get();
+            System.out.printf("%d、%s,访问地址:%s%n", i, title, link);
+        }
+    }
+
+    @Override
+    public Site getSite() {
+        return site;
+    }
+
+    public static void main(String[] args) {
+        // 创建一个Spider,并把我们的处理器放进去
+        Spider.create(new BaiduHotSearchPageProcessor())
+                // 添加这个Spider要爬取的网页地址
+                .addUrl("https://top.baidu.com/board?platform=pc&sa=pcindex_entry")
+                // 开启5个线程执行,并开始爬取
+                .thread(5).run();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/processor/GithubRepoPageProcessor.java b/src/main/java/com/canrd/webmagic/processor/GithubRepoPageProcessor.java
new file mode 100644
index 0000000..28153b6
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/processor/GithubRepoPageProcessor.java
@@ -0,0 +1,38 @@
+package com.canrd.webmagic.processor;
+
+import us.codecraft.webmagic.Page;
+import us.codecraft.webmagic.Site;
+import us.codecraft.webmagic.Spider;
+import us.codecraft.webmagic.processor.PageProcessor;
+
+/**
+ * @author: xms
+ * @description: TODO
+ * @date: 2024/4/1 12:11
+ * @version: 1.0
+ */
+public class GithubRepoPageProcessor implements PageProcessor {
+
+    private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
+
+    @Override
+    public void process(Page page) {
+        page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
+        page.putField("author", page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString());
+        page.putField("name", page.getHtml().xpath("//h1[@class='public']/strong/a/text()").toString());
+        if (page.getResultItems().get("name")==null){
+            //skip this page
+            page.setSkip(true);
+        }
+        page.putField("readme", page.getHtml().xpath("//div[@id='readme']/tidyText()"));
+    }
+
+    @Override
+    public Site getSite() {
+        return site;
+    }
+
+    public static void main(String[] args) {
+        Spider.create(new GithubRepoPageProcessor()).addUrl("https://github.com/code4craft").thread(5).run();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/processor/NatureSearchPageProcessor.java b/src/main/java/com/canrd/webmagic/processor/NatureSearchPageProcessor.java
new file mode 100644
index 0000000..4439fbe
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/processor/NatureSearchPageProcessor.java
@@ -0,0 +1,69 @@
+package com.canrd.webmagic.processor;
+
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.TextNode;
+import us.codecraft.webmagic.Page;
+import us.codecraft.webmagic.Site;
+import us.codecraft.webmagic.Spider;
+import us.codecraft.webmagic.processor.PageProcessor;
+import us.codecraft.webmagic.selector.HtmlNode;
+import us.codecraft.webmagic.selector.Selectable;
+import us.codecraft.webmagic.selector.XpathSelector;
+
+import java.util.List;
+
+/**
+ * @author: xms
+ * @description: TODO
+ * @date: 2024/4/1 14:19
+ * @version: 1.0
+ */
+public class NatureSearchPageProcessor implements PageProcessor {
+
+    // 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
+    private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
+
+    /**
+     * 定制爬虫逻辑的核心接口,在这里编写抽取逻辑
+     *
+     * @param page
+     */
+    @Override
+    public void process(Page page) {
+
+        System.out.println(page.getHtml());
+        /**
+         * 通过page.getHtml()可以获取到main函数中Spider.create(new BaiduHotSearchPageProcessor()).addUrl中的地址的网页内容
+         * 1、通过$或css()方法获取到该page html下某元素dom
+         */
+        Selectable selectable = page.getHtml().$(".app-article-list-row").select(
+                new XpathSelector("li[@class='app-article-list-row__item']")
+        );
+        List<Selectable> nodes = selectable.nodes();
+
+        /**
+         * 获取到指定的dom后,从这些dom中提取元素内容。
+         */
+        System.out.println("今日百度热搜:");
+        for (int i = 1; i <= nodes.size() - 1; i++) {
+            Selectable node = nodes.get(i).$(".u-full-height").nodes().get(2).nodes().get(0).$(".u-full-height").select(new XpathSelector("a[@class='c-card__link u-link-inherit']")).nodes().get(0);
+            String link = node.$("a","href").get();
+            String title = node.$("a","text").get();
+            System.out.printf("%d、%s,访问地址:%s%n", i, title, link);
+        }
+    }
+
+    @Override
+    public Site getSite() {
+        return site;
+    }
+
+    public static void main(String[] args) {
+        // 创建一个Spider,并把我们的处理器放进去
+        Spider.create(new NatureSearchPageProcessor())
+                // 添加这个Spider要爬取的网页地址
+                .addUrl("https://www.nature.com/search?q=battery&page=1")
+                // 开启5个线程执行,并开始爬取
+                .thread(5).run();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/canrd/webmagic/service/TestService.java b/src/main/java/com/canrd/webmagic/service/TestService.java
new file mode 100644
index 0000000..47a39b2
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/service/TestService.java
@@ -0,0 +1,57 @@
+package com.canrd.webmagic.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.canrd.webmagic.common.constant.ServerResult;
+import com.canrd.webmagic.domain.dto.TestDO;
+import com.canrd.webmagic.domain.vo.TestQueryVO;
+import com.canrd.webmagic.domain.vo.TestVO;
+
+/**
+ * (Test)表服务接口
+ *
+ * @author makejava
+ * @since 2024-01-12 14:36:57
+ */
+public interface TestService extends IService<TestDO> {
+
+    /**
+     * 通过ID查询单条数据
+     *
+     * @param testQueryVO 主键
+     * @return 实例对象
+     */
+    ServerResult queryById(TestQueryVO testQueryVO);
+
+    /**
+     * 分页查询
+     *
+     * @param testQueryVO 筛选条件
+     * @return 查询结果
+     */
+    ServerResult list(TestQueryVO testQueryVO);
+
+    /**
+     * 新增数据
+     *
+     * @param testVO 数据VO
+     * @return 新增结果
+     */
+    ServerResult add(TestVO testVO);
+
+    /**
+     * 修改数据
+     *
+     * @param testVO 数据VO
+     * @return 编辑结果
+     */
+    ServerResult edit(TestVO testVO);
+
+    /**
+     * 通过主键删除数据
+     *
+     * @param testQueryVO 筛选条件
+     * @return 是否成功
+     */
+    ServerResult deleteById(TestQueryVO testQueryVO);
+
+}
diff --git a/src/main/java/com/canrd/webmagic/service/impl/TestServiceImpl.java b/src/main/java/com/canrd/webmagic/service/impl/TestServiceImpl.java
new file mode 100644
index 0000000..befd08c
--- /dev/null
+++ b/src/main/java/com/canrd/webmagic/service/impl/TestServiceImpl.java
@@ -0,0 +1,133 @@
+package com.canrd.webmagic.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.canrd.webmagic.common.constant.Constant;
+import com.canrd.webmagic.common.constant.ServerResult;
+import com.canrd.webmagic.common.utils.PageUtils;
+import com.canrd.webmagic.domain.dto.TestDO;
+import com.canrd.webmagic.domain.vo.TestQueryVO;
+import com.canrd.webmagic.domain.vo.TestVO;
+import com.canrd.webmagic.mapper.TestMapper;
+import com.canrd.webmagic.service.TestService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * (Test)表服务实现类
+ *
+ * @author makejava
+ * @since 2024-01-12 14:36:57
+ */
+@Slf4j
+@Service
+public class TestServiceImpl extends ServiceImpl<TestMapper, TestDO> implements TestService {
+
+
+    /**
+     * 通过ID查询单条数据
+     * <p>
+     * testQueryVO 主键
+     *
+     * @return 实例对象
+     */
+    @Override
+    public ServerResult queryById(TestQueryVO testQueryVO) {
+        if (Objects.isNull(testQueryVO.getId())) {
+            return ServerResult.fail("id 不能为空");
+        }
+        TestDO TestDo = getById(testQueryVO.getId());
+        if (Objects.isNull(TestDo)) {
+            return ServerResult.success(null);
+        }
+        return ServerResult.success(BeanUtil.copyProperties(TestDo, TestVO.class));
+    }
+
+    /**
+     * 分页查询
+     *
+     * @param testQueryVO 筛选条件
+     * @return 查询结果
+     */
+    @Override
+    public ServerResult list(TestQueryVO testQueryVO) {
+
+        LambdaQueryWrapper<TestDO> queryWapper = new LambdaQueryWrapper<TestDO>()
+                .eq(TestDO::getEnableFlag, Constant.ENABLE_TEN)
+                .orderByDesc(TestDO::getId);
+        Page page = new Page<>(testQueryVO.getCurrent(), testQueryVO.getSize());
+        IPage<TestDO> iPage = page(page, queryWapper);
+        testQueryVO.setTotal(Long.valueOf(iPage.getTotal()).intValue());
+        return ServerResult.success(PageUtils.getPageReturn(iPage.getRecords(), testQueryVO));
+    }
+
+    /**
+     * 新增数据
+     *
+     * @param testVO 实例对象
+     * @return 实例对象
+     */
+    @Override
+    public ServerResult add(TestVO testVO) {
+        //todo 校验
+        if (Objects.nonNull(testVO.getId())) {
+            testVO.setId(null);
+        }
+        TestDO testDo = BeanUtil.copyProperties(testVO, TestDO.class);
+
+        save(testDo);
+
+        return ServerResult.success();
+    }
+
+    /**
+     * 修改数据
+     *
+     * @param testVO 实例对象
+     * @return 实例对象
+     */
+    @Override
+    public ServerResult edit(TestVO testVO) {
+        //todo 校验
+        if (Objects.isNull(testVO.getId())) {
+            return ServerResult.fail("id 不能为空");
+        }
+        TestDO testDo = BeanUtil.copyProperties(testVO, TestDO.class);
+
+        updateById(testDo);
+
+        return ServerResult.success();
+    }
+
+    /**
+     * 通过主键删除数据
+     *
+     * @param testQueryVO 筛选条件
+     * @return 是否成功
+     */
+    @Override
+    public ServerResult deleteById(TestQueryVO testQueryVO) {
+        List<Long> ids = testQueryVO.getIds();
+        if (CollUtil.isEmpty(ids)) {
+            return ServerResult.fail("ids 参数不能为空");
+        }
+        List<TestDO> testList = listByIds(ids);
+        if (CollUtil.isEmpty(testList)) {
+            return ServerResult.success();
+        }
+        //todo 校验是否可以逻辑删除
+        LambdaUpdateWrapper<TestDO> updateWrapper = new LambdaUpdateWrapper<TestDO>()
+                .in(TestDO::getId, ids)
+                .set(TestDO::getEnableFlag, Constant.UNABLE_TWENTY);
+        update(updateWrapper);
+        return ServerResult.success();
+    }
+}
diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml
new file mode 100644
index 0000000..bd542a3
--- /dev/null
+++ b/src/main/resources/application-local.yml
@@ -0,0 +1,186 @@
+mybatis-plus:
+  configuration:
+    cache-enabled: false
+    call-setters-on-nulls: true
+    jdbc-type-for-null: 'null'
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      capital-mode: false
+      field-strategy: NOT_NULL
+      id-type: AUTO
+      logic-delete-field: enable_flag
+      logic-delete-value: 20
+      logic-not-delete-value: 10
+  mapper-locations: classpath:/mapper/**.xml
+  type-aliases-package: com.order.erp.**.dto
+#spring:
+#  datasource:
+#    dynamic:
+#      primary: overtime #设置默认的数据源或者数据源组,默认值即为master
+#      strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.
+#      datasource:
+#        wms_warehouse:
+#          url: jdbc:mysql://127.0.0.1:3306/overtime?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true&useAffectedRows=true&autoReconnectForPools=true&rewriteBatchedStatements=true
+#          username: root
+#          password: root
+#          driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
+#          druid:
+#            initial-size: 5
+#            max-active: 20
+#            max-evictable-idle-time-millis: 300000
+#            max-wait: 60000
+#            min-evictable-idle-time-millis: 300000
+#            min-idle: 5
+#            time-between-eviction-runs-millis: 60000
+#    type: com.alibaba.druid.pool.DruidDataSource
+spring:
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 100MB
+      max-request-size: 20MB
+      file-size-threshold: 20MB
+  datasource:
+    db-type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    initial-size: 5
+    max-active: 30
+    max-wait: 30000
+    min-idle: 5
+    #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+    timeBetweenEvictionRunsMillis: 30000
+    #配置一个连接在池中最小生存的时间,单位是毫秒,30000=30s
+    minEvictableIdleTimeMillis: 30000
+    validationQuery: SELECT 'x'
+    testWhileIdle: true
+    testOnBorrow: true
+    testOnReturn: true
+    password: canrd@2024
+    time-between-eviction-runs-millis: 1000
+    url: jdbc:mysql://39.108.227.113:3307/order-erp1?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true&useAffectedRows=true&autoReconnectForPools=true
+    username: root
+  redis:
+    database: 0
+    host: 39.108.227.113
+    lettuce:
+      pool:
+        max-active: 2000
+        max-idle: 10
+        max-wait: -1
+        min-idle: 3
+        time-between-eviction-runs: 100
+    password: ''
+    port: 6379
+    timeout: 2000
+  mail:
+    # 配置 SMTP 服务器地址
+    host: smtp.mxhichina.com
+    # 发送者邮箱,已开通POP3/SMTP服务的邮箱,也就是你自己的
+    username: system@canrd.com
+    # 配置密码,注意不是真正的密码,而是刚刚申请到的授权码
+    password: Kelude2015
+    # 邮件接收者
+    mailRecipient: #邮件接收者邮箱
+    # 端口号465或587(QQ邮箱发送邮件仅支持587端口协议)
+    port: 587
+    # 默认的邮件编码为UTF-8
+    default-encoding: UTF-8
+    # 配置SSL 加密工厂
+    properties:
+      mail:
+        smtp:
+          socketFactoryClass: javax.net.ssl.SSLSocketFactory
+        #表示开启 DEBUG 模式,这样,邮件发送过程的日志会在控制台打印出来,方便排查错误
+        debug: true
+
+  freemarker:
+    template-loader-path: classpath:/template/
+    suffix: .flt
+    enabled: true
+    cache: false
+    charset: UTF-8
+    content-type: text/html
+    allow-request-override: false
+    check-template-location: true
+    expose-request-attributes: false
+    expose-session-attributes: false
+    expose-spring-macro-helpers: false
+
+
+logging:
+  config: classpath:log4j2-dev.xml
+
+#登录图形验证码有效时间/分钟
+loginCode:
+  expiration: 2
+
+#密码加密传输,前端公钥加密,后端私钥解密
+rsa:
+  private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
+
+
+#jwt
+jwt:
+  header: Authorization
+  # 令牌前缀
+  token-start-with: Bearer
+  # 必须使用最少88位的Base64对该令牌进行编码
+  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
+  # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
+  token-validity-in-seconds: 720000000
+  # 在线用户key
+  online-key: online-token
+  # 验证码
+  code-key: code-key
+
+outsys:
+  sms:
+    regionId: cn-hangzhou
+    accessKeyId: LTAIZCPI7OaWud0m
+    secret: nvtGeScBwRztGeoj8WSp5OWalalgpK
+    domain: dysmsapi.aliyuncs.com
+    version: 2017-05-25
+    action: SendSms
+    signName: canrd
+    templateCode: SMS_173005236
+  email:
+    host: http://core.canrd.com
+    passwordRecoverKey: http://www.canrd.com/canrd/shop/member/passwordModify
+
+
+
+system:
+  isLoginFailureLock: true
+  loginFailureLockTime: 5
+  loginFailureLockCount: 3
+
+openai:
+  token: Bearer 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
+
+# 文件存储路径
+file:
+  path: /home/canrd/order-erp/files/
+  host: http://39.108.227.113
+  avatar: /home/order-erp/avatar/
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+# 阿里pss图片服务
+oss:
+  endpoint: https://oss-cn-qingdao.aliyuncs.com
+  accessKeyId: LTAIZCPI7OaWud0m
+  accessKeySecret: nvtGeScBwRztGeoj8WSp5OWalalgpK
+  bucket: order-erp
+
+
+db:
+  mysql:
+    ip: 39.108.227.113
+    port: 3307
+    user: root
+    password: 123456
+    databaseName: order-erp
+    savePath: /home/canrd/order-erp/files/backup/
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
new file mode 100644
index 0000000..e1c30ea
--- /dev/null
+++ b/src/main/resources/application-prod.yml
@@ -0,0 +1,186 @@
+mybatis-plus:
+  configuration:
+    cache-enabled: false
+    call-setters-on-nulls: true
+    jdbc-type-for-null: 'null'
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      capital-mode: false
+      field-strategy: NOT_NULL
+      id-type: AUTO
+      logic-delete-field: enable_flag
+      logic-delete-value: 20
+      logic-not-delete-value: 10
+  mapper-locations: classpath:/mapper/**.xml
+  type-aliases-package: com.order.erp.**.dto
+#spring:
+#  datasource:
+#    dynamic:
+#      primary: overtime #设置默认的数据源或者数据源组,默认值即为master
+#      strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.
+#      datasource:
+#        wms_warehouse:
+#          url: jdbc:mysql://127.0.0.1:3306/overtime?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true&useAffectedRows=true&autoReconnectForPools=true&rewriteBatchedStatements=true
+#          username: root
+#          password: root
+#          driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
+#          druid:
+#            initial-size: 5
+#            max-active: 20
+#            max-evictable-idle-time-millis: 300000
+#            max-wait: 60000
+#            min-evictable-idle-time-millis: 300000
+#            min-idle: 5
+#            time-between-eviction-runs-millis: 60000
+#    type: com.alibaba.druid.pool.DruidDataSource
+spring:
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 100MB
+      max-request-size: 20MB
+      file-size-threshold: 20MB
+  datasource:
+    db-type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    initial-size: 5
+    max-active: 30
+    max-wait: 30000
+    min-idle: 5
+    #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+    timeBetweenEvictionRunsMillis: 30000
+    #配置一个连接在池中最小生存的时间,单位是毫秒,30000=30s
+    minEvictableIdleTimeMillis: 30000
+    validationQuery: SELECT 'x'
+    testWhileIdle: true
+    testOnBorrow: true
+    testOnReturn: true
+    password: 123456
+    time-between-eviction-runs-millis: 1000
+    url: jdbc:mysql://172.17.0.1:3306/order-erp?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true&useAffectedRows=true&autoReconnectForPools=true
+    username: root
+  redis:
+    database: 0
+    host: 172.17.0.1
+    lettuce:
+      pool:
+        max-active: 2000
+        max-idle: 10
+        max-wait: -1
+        min-idle: 3
+        time-between-eviction-runs: 100
+    password: ''
+    port: 6379
+    timeout: 2000
+  mail:
+    # 配置 SMTP 服务器地址
+    host: xxx
+    # 发送者邮箱,已开通POP3/SMTP服务的邮箱,也就是你自己的
+    username: xxxx
+    # 配置密码,注意不是真正的密码,而是刚刚申请到的授权码
+    password: xxx
+    # 邮件接收者
+    mailRecipient: #邮件接收者邮箱
+    # 端口号465或587(QQ邮箱发送邮件仅支持587端口协议)
+    port: 587
+    # 默认的邮件编码为UTF-8
+    default-encoding: UTF-8
+    # 配置SSL 加密工厂
+    properties:
+      mail:
+        smtp:
+          socketFactoryClass: javax.net.ssl.SSLSocketFactory
+        #表示开启 DEBUG 模式,这样,邮件发送过程的日志会在控制台打印出来,方便排查错误
+        debug: true
+
+  freemarker:
+    template-loader-path: classpath:/template/
+    suffix: .flt
+    enabled: true
+    cache: false
+    charset: UTF-8
+    content-type: text/html
+    allow-request-override: false
+    check-template-location: true
+    expose-request-attributes: false
+    expose-session-attributes: false
+    expose-spring-macro-helpers: false
+
+
+logging:
+  config: classpath:log4j2-prod.xml
+
+#登录图形验证码有效时间/分钟
+loginCode:
+  expiration: 2
+
+#密码加密传输,前端公钥加密,后端私钥解密
+rsa:
+  private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
+
+
+#jwt
+jwt:
+  header: Authorization
+  # 令牌前缀
+  token-start-with: Bearer
+  # 必须使用最少88位的Base64对该令牌进行编码
+  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
+  # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
+  token-validity-in-seconds: 720000000
+  # 在线用户key
+  online-key: online-token
+  # 验证码
+  code-key: code-key
+
+outsys:
+  sms:
+    regionId: cn-hangzhou
+    accessKeyId: LTAIZCPI7OaWud0m
+    secret: nvtGeScBwRztGeoj8WSp5OWalalgpK
+    domain: dysmsapi.aliyuncs.com
+    version: 2017-05-25
+    action: SendSms
+    signName: canrd
+    templateCode: SMS_173005236
+  email:
+    host: xxxx
+    passwordRecoverKey: xxxxx
+
+
+
+system:
+  isLoginFailureLock: true
+  loginFailureLockTime: 5
+  loginFailureLockCount: 3
+
+openai:
+  token: Bearer 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
+
+# 文件存储路径
+file:
+  path: /home/canrd/order-erp/files/
+  host: http://47.104.8.35
+  avatar: /home/order-erp/avatar/
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+# 阿里pss图片服务
+oss:
+  endpoint: https://oss-cn-qingdao.aliyuncs.com
+  accessKeyId: LTAI5t7u1gXR2vm82sd6CkVz
+  accessKeySecret: m4NzHZZsZiauKmRO8y7DihmcGNdQk4
+  bucket: alterego
+
+
+db:
+  mysql:
+    ip: 172.17.0.1
+    port: 3306
+    user: root
+    password: 123456
+    databaseName: order-erp
+    savePath: /home/canrd/order-erp/files/backup/
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
new file mode 100644
index 0000000..2dbd6a1
--- /dev/null
+++ b/src/main/resources/application-test.yml
@@ -0,0 +1,186 @@
+mybatis-plus:
+  configuration:
+    cache-enabled: false
+    call-setters-on-nulls: true
+    jdbc-type-for-null: 'null'
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      capital-mode: false
+      field-strategy: NOT_NULL
+      id-type: AUTO
+      logic-delete-field: enable_flag
+      logic-delete-value: 20
+      logic-not-delete-value: 10
+  mapper-locations: classpath:/mapper/**.xml
+  type-aliases-package: com.order.erp.**.dto
+#spring:
+#  datasource:
+#    dynamic:
+#      primary: overtime #设置默认的数据源或者数据源组,默认值即为master
+#      strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.
+#      datasource:
+#        wms_warehouse:
+#          url: jdbc:mysql://127.0.0.1:3306/overtime?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true&useAffectedRows=true&autoReconnectForPools=true&rewriteBatchedStatements=true
+#          username: root
+#          password: root
+#          driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
+#          druid:
+#            initial-size: 5
+#            max-active: 20
+#            max-evictable-idle-time-millis: 300000
+#            max-wait: 60000
+#            min-evictable-idle-time-millis: 300000
+#            min-idle: 5
+#            time-between-eviction-runs-millis: 60000
+#    type: com.alibaba.druid.pool.DruidDataSource
+spring:
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 100MB
+      max-request-size: 20MB
+      file-size-threshold: 20MB
+  datasource:
+    db-type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    initial-size: 5
+    max-active: 30
+    max-wait: 30000
+    min-idle: 5
+    #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+    timeBetweenEvictionRunsMillis: 30000
+    #配置一个连接在池中最小生存的时间,单位是毫秒,30000=30s
+    minEvictableIdleTimeMillis: 30000
+    validationQuery: SELECT 'x'
+    testWhileIdle: true
+    testOnBorrow: true
+    testOnReturn: true
+    password: 123456
+    time-between-eviction-runs-millis: 1000
+    url: jdbc:mysql://39.108.227.113:3307/order-erp?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true&useAffectedRows=true&autoReconnectForPools=true
+    username: root
+  redis:
+    database: 0
+    host: 39.108.227.113
+    lettuce:
+      pool:
+        max-active: 2000
+        max-idle: 10
+        max-wait: -1
+        min-idle: 3
+        time-between-eviction-runs: 100
+    password: ''
+    port: 6379
+    timeout: 2000
+  mail:
+    # 配置 SMTP 服务器地址
+    host: smtp.mxhichina.com
+    # 发送者邮箱,已开通POP3/SMTP服务的邮箱,也就是你自己的
+    username: system@canrd.com
+    # 配置密码,注意不是真正的密码,而是刚刚申请到的授权码
+    password: Kelude2015
+    # 邮件接收者
+    mailRecipient: #邮件接收者邮箱
+    # 端口号465或587(QQ邮箱发送邮件仅支持587端口协议)
+    port: 587
+    # 默认的邮件编码为UTF-8
+    default-encoding: UTF-8
+    # 配置SSL 加密工厂
+    properties:
+      mail:
+        smtp:
+          socketFactoryClass: javax.net.ssl.SSLSocketFactory
+        #表示开启 DEBUG 模式,这样,邮件发送过程的日志会在控制台打印出来,方便排查错误
+        debug: true
+
+  freemarker:
+    template-loader-path: classpath:/template/
+    suffix: .flt
+    enabled: true
+    cache: false
+    charset: UTF-8
+    content-type: text/html
+    allow-request-override: false
+    check-template-location: true
+    expose-request-attributes: false
+    expose-session-attributes: false
+    expose-spring-macro-helpers: false
+
+
+logging:
+  config: classpath:log4j2-dev.xml
+
+#登录图形验证码有效时间/分钟
+loginCode:
+  expiration: 2
+
+#密码加密传输,前端公钥加密,后端私钥解密
+rsa:
+  private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
+
+
+#jwt
+jwt:
+  header: Authorization
+  # 令牌前缀
+  token-start-with: Bearer
+  # 必须使用最少88位的Base64对该令牌进行编码
+  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
+  # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
+  token-validity-in-seconds: 720000000
+  # 在线用户key
+  online-key: online-token
+  # 验证码
+  code-key: code-key
+
+outsys:
+  sms:
+    regionId: cn-hangzhou
+    accessKeyId: LTAIZCPI7OaWud0m
+    secret: nvtGeScBwRztGeoj8WSp5OWalalgpK
+    domain: dysmsapi.aliyuncs.com
+    version: 2017-05-25
+    action: SendSms
+    signName: canrd
+    templateCode: SMS_173005236
+  email:
+    host: http://core.canrd.com
+    passwordRecoverKey: http://www.canrd.com/canrd/shop/member/passwordModify
+
+
+
+system:
+  isLoginFailureLock: true
+  loginFailureLockTime: 5
+  loginFailureLockCount: 3
+
+openai:
+  token: Bearer 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
+
+# 文件存储路径
+file:
+  path: /home/canrd/order-erp/files/
+  host: http://39.108.227.113
+  avatar: /home/order-erp/avatar/
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+# 阿里pss图片服务
+oss:
+  endpoint: https://oss-cn-qingdao.aliyuncs.com
+  accessKeyId: LTAIZCPI7OaWud0m
+  accessKeySecret: nvtGeScBwRztGeoj8WSp5OWalalgpK
+  bucket: order-erp
+
+
+db:
+  mysql:
+    ip: 39.108.227.113
+    port: 3307
+    user: root
+    password: 123456
+    databaseName: order-erp
+    savePath: /home/canrd/order-erp/files/backup/
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..88e0b99
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,6 @@
+server:
+  port: 9000
+
+spring:
+  profiles:
+    active: local
\ No newline at end of file
diff --git a/src/main/resources/ip2region/ip2region.db b/src/main/resources/ip2region/ip2region.db
new file mode 100644
index 0000000..43e1daf
Binary files /dev/null and b/src/main/resources/ip2region/ip2region.db differ
diff --git a/src/main/resources/log4j2-dev.xml b/src/main/resources/log4j2-dev.xml
new file mode 100644
index 0000000..e70ff2c
--- /dev/null
+++ b/src/main/resources/log4j2-dev.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
+<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出 -->
+<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数 -->
+<configuration status="debug" monitorInterval="30">
+    <!--<contextName>log4j2</contextName>-->
+    <properties>
+        <!--${sys:catalina.home}表示linux中环境变量中的tomcat根目录 用户主目录-->
+        <!--原来用logback时候在统一配置中心也配置一个logging.path=/opt/tomcat-log/${spring.application.name}   LOG_PATH是内置变量-->
+        <!--${sys:user.home}  用户主目录-->
+       <!-- <Property name="log_path">${sys:user.home}/logs</Property>-->
+<!--        <Property name="log_path" value="./logs/" />-->
+        <property name="console_log_pattern">%d|%t|%traceId|%-5level|%F:%L|%M|%m%n</property>
+        <!-- 保留日志天数 D H M S 分别对应天 小时 分钟 秒 -->
+        <property name="KEEP_LOG_DAY">60D</property>
+        <!-- 日志切割的最小单位 -->
+        <property name="EVERY_FILE_SIZE">100M</property>
+    </properties>
+    <!--先定义所有的appender -->
+    <appenders>
+        <console name="Console" target="SYSTEM_OUT">
+            <!--输出日志的格式 -->
+            <PatternLayout  charset="UTF-8" pattern="${console_log_pattern}"/>
+            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
+        </console>
+        <!--这个输出控制台的配置 -->
+        <!--<console name="Console" target="SYSTEM_OUT" follow="false">-->
+        <!--&lt;!&ndash;输出日志的格式 &ndash;&gt;-->
+        <!--<PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" />-->
+        <!--</console>-->
+
+        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
+        <RollingFile name="RollingFileInfo" fileName="${sys:logging.path}/logs/overtime.log" filePattern="${sys:logging.path}/logs/$${date:yyyy-MM-dd}/info-%d{yyyy-MM-dd}-%i.log">
+            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
+            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
+            <!--<Filters>-->
+            <!--<ThresholdFilter level="INFO"/>-->
+            <!--<ThresholdFilter level="WARN" onMatch="DENY"-->
+            <!--onMismatch="NEUTRAL"/>-->
+            <!--</Filters>-->
+            <PatternLayout  charset="UTF-8" pattern="${console_log_pattern}"/>
+            <Policies>
+                <!-- 归档每天的文件 -->
+                <!--<TimeBasedTriggeringPolicy interval="1" modulate="true"/>-->
+                <TimeBasedTriggeringPolicy />
+                <!-- 限制单个文件大小 -->
+                <SizeBasedTriggeringPolicy size="${EVERY_FILE_SIZE}"/>
+            </Policies>
+            <!-- 限制每天文件个数 --> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了100 -->
+            <DefaultRolloverStrategy  max="256">
+                <Delete basePath="${sys:logging.path}/logs/" maxDepth="3">
+                    <IfFileName glob="*/*info*.log"/>
+                    <IfLastModified age="${KEEP_LOG_DAY}"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+    </appenders>
+    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
+    <loggers>
+        <!--过滤掉spring和mybatis的一些无用的DEBUG信息 -->
+        <logger name="org.springframework" level="debug" >
+            <ThresholdFilter level="debug"/>
+            <appender-ref ref="RollingFileInfo" />
+        </logger>
+        <logger name="org.mybatis" level="DEBUG" >
+            <ThresholdFilter level="debug"/>
+            <appender-ref ref="RollingFileInfo" />
+        </logger>
+        <logger name="com.canrd.shop" level="DEBUG" >
+            <ThresholdFilter level="debug"/>
+            <appender-ref ref="RollingFileInfo" />
+        </logger>
+        <!--<root level="all">-->
+        <root level="debug">
+            <appender-ref ref="Console" />
+        </root>
+    </loggers>
+</configuration>
diff --git a/src/main/resources/log4j2-prod.xml b/src/main/resources/log4j2-prod.xml
new file mode 100644
index 0000000..1515486
--- /dev/null
+++ b/src/main/resources/log4j2-prod.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
+<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出 -->
+<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数 -->
+<configuration status="INFO" monitorInterval="30">
+    <!--<contextName>log4j2</contextName>-->
+    <properties>
+        <!--${sys:catalina.home}表示linux中环境变量中的tomcat根目录 用户主目录-->
+        <!--原来用logback时候在统一配置中心也配置一个logging.path=/opt/tomcat-log/${spring.application.name}   LOG_PATH是内置变量-->
+        <!--${sys:user.home}  用户主目录-->
+       <!-- <Property name="log_path">${sys:user.home}/logs</Property>-->
+<!--        <Property name="log_path" value="./logs/" />-->
+        <property name="console_log_pattern">%d|%t|%traceId|%-5level|%F:%L|%M|%m%n</property>
+        <!-- 保留日志天数 D H M S 分别对应天 小时 分钟 秒 -->
+        <property name="KEEP_LOG_DAY">60D</property>
+        <!-- 日志切割的最小单位 -->
+        <property name="EVERY_FILE_SIZE">100M</property>
+    </properties>
+    <!--先定义所有的appender -->
+    <appenders>
+        <console name="Console" target="SYSTEM_OUT">
+            <!--输出日志的格式 -->
+            <PatternLayout  charset="UTF-8" pattern="${console_log_pattern}"/>
+            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
+        </console>
+        <!--这个输出控制台的配置 -->
+        <!--<console name="Console" target="SYSTEM_OUT" follow="false">-->
+        <!--&lt;!&ndash;输出日志的格式 &ndash;&gt;-->
+        <!--<PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" />-->
+        <!--</console>-->
+
+        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
+        <RollingFile name="RollingFileInfo" fileName="${sys:logging.path}/logs/overtime.log" filePattern="${sys:logging.path}/logs/$${date:yyyy-MM-dd}/info-%d{yyyy-MM-dd}-%i.log">
+            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
+            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
+            <!--<Filters>-->
+            <!--<ThresholdFilter level="INFO"/>-->
+            <!--<ThresholdFilter level="WARN" onMatch="DENY"-->
+            <!--onMismatch="NEUTRAL"/>-->
+            <!--</Filters>-->
+            <PatternLayout  charset="UTF-8" pattern="${console_log_pattern}"/>
+            <Policies>
+                <!-- 归档每天的文件 -->
+                <!--<TimeBasedTriggeringPolicy interval="1" modulate="true"/>-->
+                <TimeBasedTriggeringPolicy />
+                <!-- 限制单个文件大小 -->
+                <SizeBasedTriggeringPolicy size="${EVERY_FILE_SIZE}"/>
+            </Policies>
+            <!-- 限制每天文件个数 --> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了100 -->
+            <DefaultRolloverStrategy  max="256">
+                <Delete basePath="${sys:logging.path}/logs/" maxDepth="3">
+                    <IfFileName glob="*/*info*.log"/>
+                    <IfLastModified age="${KEEP_LOG_DAY}"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+    </appenders>
+    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
+    <loggers>
+        <logger name="org.springframework" level="info" >
+            <ThresholdFilter level="info"/>
+            <appender-ref ref="RollingFileInfo" />
+        </logger>
+        <logger name="org.mybatis" level="info" >
+            <ThresholdFilter level="info"/>
+            <appender-ref ref="RollingFileInfo" />
+        </logger>
+        <logger name="com.canrd.shop" level="info" >
+            <ThresholdFilter level="info"/>
+            <appender-ref ref="RollingFileInfo" />
+        </logger>
+    </loggers>
+</configuration>
--
libgit2 0.23.3