lutong 3 gün önce
işleme
973ba2c8f9
100 değiştirilmiş dosya ile 10055 ekleme ve 0 silme
  1. 34 0
      .gitignore
  2. 17 0
      alien-activity/pom.xml
  3. 84 0
      alien-comment/pom.xml
  4. 379 0
      alien-common-core/pom.xml
  5. 137 0
      alien-common-core/src/main/java/shop/alien/util/ali/AliOSSUtil.java
  6. 81 0
      alien-common-core/src/main/java/shop/alien/util/ali/Demo.java
  7. 33 0
      alien-common-core/src/main/java/shop/alien/util/analysis/UrlUtil.java
  8. 29 0
      alien-common-core/src/main/java/shop/alien/util/analysis/UserAgentUtil.java
  9. 57 0
      alien-common-core/src/main/java/shop/alien/util/base64/Img2Base64.java
  10. 56 0
      alien-common-core/src/main/java/shop/alien/util/byteutil/ByteToFileUtil.java
  11. 123 0
      alien-common-core/src/main/java/shop/alien/util/codechange/URLtoUTF8.java
  12. 58 0
      alien-common-core/src/main/java/shop/alien/util/codechange/ZhToUnicode.java
  13. 136 0
      alien-common-core/src/main/java/shop/alien/util/command/CommandUtil.java
  14. 56 0
      alien-common-core/src/main/java/shop/alien/util/common/AlipayTradeAppPay.java
  15. 74 0
      alien-common-core/src/main/java/shop/alien/util/common/AlipayTradeRefund.java
  16. 52 0
      alien-common-core/src/main/java/shop/alien/util/common/CommendUtil.java
  17. 133 0
      alien-common-core/src/main/java/shop/alien/util/common/Constants.java
  18. 114 0
      alien-common-core/src/main/java/shop/alien/util/common/DateUtils.java
  19. 65 0
      alien-common-core/src/main/java/shop/alien/util/common/DistanceUtil.java
  20. 20 0
      alien-common-core/src/main/java/shop/alien/util/common/EnumUtil.java
  21. 61 0
      alien-common-core/src/main/java/shop/alien/util/common/FileUpload.java
  22. 171 0
      alien-common-core/src/main/java/shop/alien/util/common/JwtUtil.java
  23. 44 0
      alien-common-core/src/main/java/shop/alien/util/common/ListToPage.java
  24. 66 0
      alien-common-core/src/main/java/shop/alien/util/common/RandomCreateUtil.java
  25. 36 0
      alien-common-core/src/main/java/shop/alien/util/common/StringToJsonConverterUtils.java
  26. 11 0
      alien-common-core/src/main/java/shop/alien/util/common/TokenInfo.java
  27. 24 0
      alien-common-core/src/main/java/shop/alien/util/common/UniqueRandomNumGenerator.java
  28. 18 0
      alien-common-core/src/main/java/shop/alien/util/common/UrlEncode.java
  29. 66 0
      alien-common-core/src/main/java/shop/alien/util/common/VideoDurationFFmpeg.java
  30. 99 0
      alien-common-core/src/main/java/shop/alien/util/common/VideoUtils.java
  31. 45 0
      alien-common-core/src/main/java/shop/alien/util/common/WebTool.java
  32. 39 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/Constant.java
  33. 58 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/CouponStatusEnum.java
  34. 32 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/CouponTypeEnum.java
  35. 92 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/DiscountCouponEnum.java
  36. 61 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/LawyerStatusEnum.java
  37. 50 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/OcrTypeEnum.java
  38. 51 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/OrderActionType.java
  39. 69 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/OrderStatusEnum.java
  40. 26 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/PaymentEnum.java
  41. 52 0
      alien-common-core/src/main/java/shop/alien/util/common/constant/ViolationEnum.java
  42. 178 0
      alien-common-core/src/main/java/shop/alien/util/common/generator/CodeGenerator.java
  43. 24 0
      alien-common-core/src/main/java/shop/alien/util/common/generator/MyDMQuery.java
  44. 51 0
      alien-common-core/src/main/java/shop/alien/util/common/generator/MyDataSourceConfig.java
  45. 162 0
      alien-common-core/src/main/java/shop/alien/util/common/netease/ImageCheckUtil.java
  46. 136 0
      alien-common-core/src/main/java/shop/alien/util/common/netease/TextCheckUtil.java
  47. 150 0
      alien-common-core/src/main/java/shop/alien/util/common/netease/util/HttpClient4Utils.java
  48. 24 0
      alien-common-core/src/main/java/shop/alien/util/common/netease/util/SignatureMethodEnum.java
  49. 244 0
      alien-common-core/src/main/java/shop/alien/util/common/netease/util/SignatureUtils.java
  50. 82 0
      alien-common-core/src/main/java/shop/alien/util/common/netease/util/Utils.java
  51. 44 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/DeepseekClient.java
  52. 18 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/ImageModerationResultVO.java
  53. 249 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/ImageModerationUtil.java
  54. 45 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/ImageReviewServiceEnum.java
  55. 120 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/ImageUrlDemo.java
  56. 13 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/TextModerationResultVO.java
  57. 272 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/TextModerationUtil.java
  58. 44 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/TextReviewServiceEnum.java
  59. 136 0
      alien-common-core/src/main/java/shop/alien/util/common/safe/video/VideoModerationUtil.java
  60. 127 0
      alien-common-core/src/main/java/shop/alien/util/compress/CompressUtil.java
  61. 256 0
      alien-common-core/src/main/java/shop/alien/util/compress/DecompressionUtil.java
  62. 52 0
      alien-common-core/src/main/java/shop/alien/util/cron/CronUtil.java
  63. 87 0
      alien-common-core/src/main/java/shop/alien/util/database/Sqlite.java
  64. 100 0
      alien-common-core/src/main/java/shop/alien/util/date/DateUtils.java
  65. 133 0
      alien-common-core/src/main/java/shop/alien/util/encryption/AesCbcUtil.java
  66. 112 0
      alien-common-core/src/main/java/shop/alien/util/encryption/AesEcbUtil.java
  67. 41 0
      alien-common-core/src/main/java/shop/alien/util/encryption/Base64Util.java
  68. 66 0
      alien-common-core/src/main/java/shop/alien/util/encryption/Md5Util.java
  69. 46 0
      alien-common-core/src/main/java/shop/alien/util/encryption/Sha256Util.java
  70. 261 0
      alien-common-core/src/main/java/shop/alien/util/excel/EasyExcelUtil.java
  71. 64 0
      alien-common-core/src/main/java/shop/alien/util/excel/ExcelWriteTest.java
  72. 37 0
      alien-common-core/src/main/java/shop/alien/util/excel/UserExcel.java
  73. 201 0
      alien-common-core/src/main/java/shop/alien/util/file/FileGetDir.java
  74. 43 0
      alien-common-core/src/main/java/shop/alien/util/file/FileSizeCalcUtil.java
  75. 514 0
      alien-common-core/src/main/java/shop/alien/util/file/FileUtil.java
  76. 174 0
      alien-common-core/src/main/java/shop/alien/util/generator/CodeGenerator.java
  77. 24 0
      alien-common-core/src/main/java/shop/alien/util/generator/MyDMQuery.java
  78. 52 0
      alien-common-core/src/main/java/shop/alien/util/generator/MyDataSourceConfig.java
  79. 127 0
      alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpGetRequestUtil.java
  80. 65 0
      alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpGetWithUrl.java
  81. 88 0
      alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpPostRequestUtil.java
  82. 67 0
      alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpPostWithForm.java
  83. 231 0
      alien-common-core/src/main/java/shop/alien/util/httpsend/spring/GetMethod.java
  84. 101 0
      alien-common-core/src/main/java/shop/alien/util/httpsend/spring/PostMethod.java
  85. 60 0
      alien-common-core/src/main/java/shop/alien/util/image/ImageUtil.java
  86. 80 0
      alien-common-core/src/main/java/shop/alien/util/image/WebpConvertUtil.java
  87. 245 0
      alien-common-core/src/main/java/shop/alien/util/ip/IpAddressUtil.java
  88. 47 0
      alien-common-core/src/main/java/shop/alien/util/ip/IpConnectTestUtil.java
  89. 369 0
      alien-common-core/src/main/java/shop/alien/util/lunar/LunarUtils.java
  90. 65 0
      alien-common-core/src/main/java/shop/alien/util/map/DistanceUtil.java
  91. 73 0
      alien-common-core/src/main/java/shop/alien/util/md5/FileMd5Util.java
  92. 644 0
      alien-common-core/src/main/java/shop/alien/util/myBaticsPlus/QueryBuilder.java
  93. 33 0
      alien-common-core/src/main/java/shop/alien/util/ocr/OCRUtil.java
  94. 79 0
      alien-common-core/src/main/java/shop/alien/util/openoffice/LocallinkToPDF.java
  95. 55 0
      alien-common-core/src/main/java/shop/alien/util/openoffice/NewOpenOffice.java
  96. 105 0
      alien-common-core/src/main/java/shop/alien/util/openoffice/OpenOfficeUtil.java
  97. 73 0
      alien-common-core/src/main/java/shop/alien/util/openoffice/RemotelinkToPDF.java
  98. 58 0
      alien-common-core/src/main/java/shop/alien/util/pdf/PdfToImgUtil.java
  99. 67 0
      alien-common-core/src/main/java/shop/alien/util/port/NetUtils.java
  100. 32 0
      alien-common-core/src/main/java/shop/alien/util/port/ServerPortUtils.java

+ 34 - 0
.gitignore

@@ -0,0 +1,34 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+/log/

+ 17 - 0
alien-activity/pom.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>shop.alien</groupId>
+    <artifactId>alien-activity</artifactId>
+    <version>1.0.0</version>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    
+</project>

+ 84 - 0
alien-comment/pom.xml

@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>shop.alien</groupId>
+        <artifactId>alien-cloud</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>alien-comment</artifactId>
+    <version>1.0.0</version>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.1.2</version> <!-- 使用最新版本 -->
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>prepare-package</phase> <!-- 在package之前执行 -->
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/lib</outputDirectory> <!-- 指定输出目录 -->
+                            <includeScope>runtime</includeScope> <!-- 仅包含运行时依赖 -->
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>shop.alien.job.AlienJobApplication</mainClass>
+                    <layout>ZIP</layout>
+                    <includes>
+                        <include>
+                            <groupId>nothing</groupId>
+                            <artifactId>nothing</artifactId>
+                        </include>
+                    </includes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <profiles>
+        <profile>
+            <id>test</id>
+            <properties>
+                <profiles.active>test</profiles.active>
+                <nacos.server-addr>192.168.2.252:8848</nacos.server-addr>
+                <nacos.namespace>0e1e2d77-56e8-422c-8317-6f71d7285e59</nacos.namespace>
+                <nacos.username>nacos</nacos.username>
+                <nacos.password>ngfriend198092</nacos.password>
+            </properties>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+    </profiles>
+</project>

+ 379 - 0
alien-common-core/pom.xml

@@ -0,0 +1,379 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>shop.alien</groupId>
+        <artifactId>alien-cloud</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>alien-common-core</artifactId>
+    <version>1.0.0</version>
+
+    <dependencies>
+
+<!--        &lt;!&ndash;允许你使用流(Flux和Mono)来处理异步、非阻塞的数据&ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-webflux</artifactId>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+        </dependency>
+
+        <!-- sqlite -->
+        <dependency>
+            <groupId>org.xerial</groupId>
+            <artifactId>sqlite-jdbc</artifactId>
+        </dependency>
+
+        <!--打印, pdf转换成img-->
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>dom4j</groupId>
+            <artifactId>dom4j</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <version>3.3.1.tmp</version>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- mybatisPlus Freemarker 模版引擎 -->
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!--解析url-->
+        <dependency>
+            <groupId>io.github.url-detector</groupId>
+            <artifactId>url-detector</artifactId>
+        </dependency>
+
+        <!--alibaba easyExcel-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>4.0.3</version>
+        </dependency>
+        <!-- 阿里云内容安全 -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>green20220302</artifactId>
+            <version>2.20.0</version>
+        </dependency>
+
+        <!-- 阿里云OCR文字识别 -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>ocr_api20210707</artifactId>
+            <version>3.1.3</version>
+        </dependency>
+
+        <!--openoffice-->
+        <dependency>
+            <groupId>com.artofsolving</groupId>
+            <artifactId>jodconverter</artifactId>
+            <version>2.2.1</version>
+        </dependency>
+
+        <!--生成二维码 -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <!--生成二维码End -->
+
+        <!-- redis -->
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>3.2.0</version>
+        </dependency>
+
+        <!--汉字转换拼音-->
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+            <version>2.5.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.sejda.imageio</groupId>
+            <artifactId>webp-imageio</artifactId>
+            <version>0.1.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
+
+        <!--webdav-->
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-webdav</artifactId>
+            <version>2.18.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.7.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.13.1</version>
+        </dependency>
+
+        <!-- tar解压依赖 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <version>1.20</version>
+        </dependency>
+
+        <!-- rar解压依赖 -->
+        <dependency>
+            <groupId>net.sf.sevenzipjbinding</groupId>
+            <artifactId>sevenzipjbinding</artifactId>
+            <version>16.02-2.01</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.sf.sevenzipjbinding</groupId>
+            <artifactId>sevenzipjbinding-all-platforms</artifactId>
+            <version>16.02-2.01</version>
+        </dependency>
+
+        <!-- 7z解压依赖 -->
+        <dependency>
+            <groupId>org.tukaani</groupId>
+            <artifactId>xz</artifactId>
+            <version>1.9</version>
+        </dependency>
+
+        <!-- zip解压依赖 -->
+        <dependency>
+            <groupId>org.apache.ant</groupId>
+            <artifactId>ant</artifactId>
+            <version>1.10.12</version>
+        </dependency>
+
+        <!--系统信息-->
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+            <version>6.3.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>5.12.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.sourceforge.tess4j</groupId>
+            <artifactId>tess4j</artifactId>
+            <version>5.11.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna-platform</artifactId>
+            <version>5.12.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.6tail</groupId>
+            <artifactId>lunar</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.6tail</groupId>
+            <artifactId>tyme4j</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>shop.alien</groupId>
+            <artifactId>alien-entity</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.seleniumhq.selenium</groupId>
+            <artifactId>selenium-java</artifactId>
+            <version>3.8.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>32.1.1-jre</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <version>2.3.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+            <version>1.9</version> <!-- 使用最新版本 -->
+        </dependency>
+
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.40.8.ALL</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.17.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.github.openfeign</groupId>
+            <artifactId>feign-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>1.2.21</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis</artifactId>
+            <version>3.5.15</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-openfeign-core</artifactId>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>org.springframework</groupId>-->
+<!--            <artifactId>spring-webmvc</artifactId>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-redis</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+    </build>
+
+</project>

+ 137 - 0
alien-common-core/src/main/java/shop/alien/util/ali/AliOSSUtil.java

@@ -0,0 +1,137 @@
+package shop.alien.util.ali;
+
+import com.aliyun.oss.ClientBuilderConfiguration;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.common.comm.SignVersion;
+import com.aliyun.oss.model.PutObjectRequest;
+import com.aliyun.oss.model.PutObjectResult;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.util.file.FileUtil;
+
+import java.io.File;
+
+/**
+ * 阿里云oss工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/7/17 8:51
+ */
+@Slf4j
+@Component
+public class AliOSSUtil {
+
+    @Value("${ali.oss.accessKeyId}")
+    private String accessKeyId;
+
+    @Value("${ali.oss.accessKeySecret}")
+    private String accessKeySecret;
+
+    @Value("${ali.oss.endPoint}")
+    private String endPoint;
+
+    @Value("${ali.oss.bucketName}")
+    private String bucketName;
+
+    /**
+     * oss上传文件
+     *
+     * @param multipartFile 前端上传的文件
+     * @param ossFilePath   oss中文件全路径(image/xxx.jpg)
+     * @return filePath
+     */
+    public String uploadFile(MultipartFile multipartFile, String ossFilePath) {
+        // 验证参数
+        if (multipartFile == null || multipartFile.isEmpty()) {
+            log.error("AliOSSUtil.uploadFile ERROR: multipartFile is null or empty");
+            return null;
+        }
+        
+        if (StringUtils.isEmpty(ossFilePath)) {
+            log.error("AliOSSUtil.uploadFile ERROR: ossFilePath is empty");
+            return null;
+        }
+        
+        File convertFile = FileUtil.convert(multipartFile);
+        if (convertFile == null) {
+            log.error("AliOSSUtil.uploadFile ERROR: convertFile is null");
+            return null;
+        }
+        
+        return uploadFile(convertFile, ossFilePath);
+    }
+
+    /**
+     * oss上传文件
+     *
+     * @param file        后端文件
+     * @param ossFilePath oss中文件全路径(image/xxx.jpg)
+     * @return filePath
+     */
+    public String uploadFile(File file, String ossFilePath) {
+        // 验证参数
+        if (file == null) {
+            log.error("AliOSSUtil.uploadFile ERROR: file is null");
+            return null;
+        }
+        
+        if (StringUtils.isEmpty(ossFilePath)) {
+            log.error("AliOSSUtil.uploadFile ERROR: ossFilePath is empty");
+            return null;
+        }
+        
+        // 检查文件是否存在且不为空
+        if (!file.exists()) {
+            log.error("AliOSSUtil.uploadFile ERROR: file does not exist, path: {}", file.getAbsolutePath());
+            return null;
+        }
+        
+        if (file.length() == 0) {
+            log.error("AliOSSUtil.uploadFile ERROR: file is empty, path: {}", file.getAbsolutePath());
+            return null;
+        }
+        
+        // 验证ossFilePath格式 - 不能以/开头
+        if (ossFilePath.startsWith("/")) {
+            log.error("AliOSSUtil.uploadFile ERROR: ossFilePath cannot start with '/': {}", ossFilePath);
+            return null;
+        }
+        
+        if (ossFilePath.contains("..") || ossFilePath.contains("\\")) {
+            log.error("AliOSSUtil.uploadFile ERROR: invalid ossFilePath format: {}", ossFilePath);
+            return null;
+        }
+
+        // 创建OSSClient实例。
+        // 当OSSClient实例不再使用时,调用shutdown方法以释放资源。
+        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
+        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
+        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
+        try {
+            // 创建PutObjectRequest对象。
+            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, ossFilePath, file);
+            // 上传文件
+            PutObjectResult result = ossClient.putObject(putObjectRequest);
+            String eTag = result.getETag();
+            if (StringUtils.isNotEmpty(eTag)) {
+                //删除临时文件
+                file.delete();
+                return "https://" + bucketName + "." + endPoint + "/" + ossFilePath;
+            }
+            return null;
+        } catch (Exception e) {
+            log.error("AliOSSUtil.uploadFile ERROR: {}", e.getMessage(), e);
+            return null;
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+}

+ 81 - 0
alien-common-core/src/main/java/shop/alien/util/ali/Demo.java

@@ -0,0 +1,81 @@
+package shop.alien.util.ali;
+
+import com.aliyun.oss.*;
+import com.aliyun.oss.common.auth.CredentialsProviderFactory;
+import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
+import com.aliyun.oss.common.comm.SignVersion;
+import com.aliyun.oss.model.PutObjectRequest;
+import com.aliyun.oss.model.PutObjectResult;
+
+import java.io.File;
+
+public class Demo {
+
+    public static void main(String[] args) throws Exception {
+
+        String accessKeyId = "LTAI5tG6wSYrSgN3Cwek17Du"; // 你的AccessKey ID
+        String accessKeySecret = "PQDPdTtDy0v848p0Bl9vT5yHmpt79H"; // 你的AccessKey Secret
+
+        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
+        String endpoint = "oss-cn-beijing.aliyuncs.com";
+        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
+        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
+        // 填写Bucket名称,例如examplebucket。
+        String bucketName = "alien-volume";
+        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
+        String objectName = "image/微信图片_2025-07-15_104613_218.jpg";
+        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
+        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
+        String filePath = "C:\\Users\\17238\\Pictures\\微信图片_2025-07-15_104613_218.jpg";
+        // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
+        String region = "cn-beijing";
+
+        // 创建OSSClient实例。
+        // 当OSSClient实例不再使用时,调用shutdown方法以释放资源。
+        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
+        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
+//        OSS ossClient = OSSClientBuilder.create()
+//                .endpoint(endpoint)
+//                .credentialsProvider(credentialsProvider)
+//                .clientConfiguration(clientBuilderConfiguration)
+//                .region(region)
+//                .build();
+
+        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+
+        try {
+            // 创建PutObjectRequest对象。
+            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
+            // 默认使用Bucket的ACL设置(推荐生产环境使用Private)
+//            ObjectMetadata metadata = new ObjectMetadata();
+//             metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
+//             metadata.setObjectAcl(CannedAccessControlList.PublicRead);
+//             putObjectRequest.setMetadata(metadata);
+
+            // 上传文件。
+            PutObjectResult result = ossClient.putObject(putObjectRequest);
+            String fileUrl = "https://" + bucketName + "." + endpoint + "/" + objectName;
+            System.out.println("文件访问地址: " + fileUrl);
+        } catch (OSSException oe) {
+            System.out.println("Caught an OSSException, which means your request made it to OSS, "
+                    + "but was rejected with an error response for some reason.");
+            System.out.println("Error Message:" + oe.getErrorMessage());
+            System.out.println("Error Code:" + oe.getErrorCode());
+            System.out.println("Request ID:" + oe.getRequestId());
+            System.out.println("Host ID:" + oe.getHostId());
+        } catch (ClientException ce) {
+            System.out.println("Caught an ClientException, which means the client encountered "
+                    + "a serious internal problem while trying to communicate with OSS, "
+                    + "such as not being able to access the network.");
+            System.out.println("Error Message:" + ce.getMessage());
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+
+
+    }
+
+
+}

+ 33 - 0
alien-common-core/src/main/java/shop/alien/util/analysis/UrlUtil.java

@@ -0,0 +1,33 @@
+package shop.alien.util.analysis;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jsoup.Jsoup;
+
+import java.io.IOException;
+
+/**
+ * Url工具类
+ */
+@Slf4j
+public class UrlUtil {
+
+    /**
+     * 获取重定向地址
+     *
+     * @param userAgent User-Agent
+     * @param url       地址
+     * @return 重定向地址
+     */
+    public static String getRealUrl(String userAgent, String url) {
+        try {
+            return Jsoup.connect(url)
+                    .userAgent(userAgent)
+                    .followRedirects(true)
+                    .execute()
+                    .url().toString();
+        } catch (IOException e) {
+            log.error(e.getMessage());
+            return "";
+        }
+    }
+}

+ 29 - 0
alien-common-core/src/main/java/shop/alien/util/analysis/UserAgentUtil.java

@@ -0,0 +1,29 @@
+package shop.alien.util.analysis;
+
+import cn.hutool.core.util.RandomUtil;
+
+public class UserAgentUtil {
+
+    public static final String[] USER_AGENT_ARRAY = new String[]{
+            "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
+            "Mozilla/5.0 (Linux; U; Android 2.2.1; zh-cn; HTC_Wildfire_A3333 Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
+            "MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
+            "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10",
+            "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13"
+    };
+
+    public static final String[] PC_USER_AGENT_ARRAY = new String[]{
+            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36"
+    };
+
+    public static String getOne() {
+        int random = RandomUtil.randomInt(0, USER_AGENT_ARRAY.length - 1);
+        return USER_AGENT_ARRAY[random];
+    }
+
+    public static String getPC() {
+        int random = RandomUtil.randomInt(0, PC_USER_AGENT_ARRAY.length - 1);
+        return PC_USER_AGENT_ARRAY[random];
+    }
+}

+ 57 - 0
alien-common-core/src/main/java/shop/alien/util/base64/Img2Base64.java

@@ -0,0 +1,57 @@
+package shop.alien.util.base64;
+
+import lombok.extern.slf4j.Slf4j;
+//import sun.misc.BASE64Encoder;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Base64;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2023/1/30 14:03
+ */
+@Slf4j
+public class Img2Base64 {
+
+    /**
+     * 图片转base64
+     *
+     * @param filePath 图片路径
+     * @return String
+     */
+    public static String imgToBase64(String filePath) {
+        try {
+            InputStream is = new FileInputStream(filePath);
+            byte[] b = new byte[is.available()];
+            is.read(b);
+            is.close();
+            return Base64.getEncoder().encodeToString(b);
+        } catch (Exception e) {
+            log.error("Img2Base64/imgToBase64 ERROR Msg={}", e.getMessage());
+            return null;
+        }
+    }
+
+//    /**
+//     * 使用sun.misc.BASE64Encoder将图片转换成Base64
+//     * 图片转换成Base64
+//     *
+//     * @param filePath 图片的路径
+//     * @return String
+//     */
+//    public static String imgToBASE64Encoder(String filePath) {
+//        try {
+//            InputStream inputStream = new FileInputStream(filePath);
+//            byte[] data = new byte[inputStream.available()];
+//            inputStream.read(data);
+//            inputStream.close();
+//            BASE64Encoder encoder = new BASE64Encoder();
+//            return encoder.encode(data);
+//        } catch (Exception e) {
+//            log.error("Img2Base64/imgToBASE64Encoder ERROR Msg={}", e.getMessage());
+//            return null;
+//        }
+//    }
+}

+ 56 - 0
alien-common-core/src/main/java/shop/alien/util/byteutil/ByteToFileUtil.java

@@ -0,0 +1,56 @@
+package shop.alien.util.byteutil;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 二进制数组转文件
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/5/9 10:01
+ */
+public class ByteToFileUtil {
+
+    /**
+     * logger
+     */
+    private static final Logger logger = LoggerFactory.getLogger(ByteToFileUtil.class);
+
+    /**
+     * 二进制数组转图片
+     *
+     * @param bytes    二进制数组
+     * @param filePath 文件路径
+     * @param fileName 文件名
+     * @throws IOException IOException
+     */
+    public static String byteToImage(byte[] bytes, String filePath, String fileName) throws IOException {
+        //文件路径+文件名+文件格式
+        String pathName = filePath + File.separator + fileName + ".jpg";
+        logger.info("ByteToFileUtil/byteToImage/pathName={}", pathName);
+        File path = new File(filePath);
+        if (!path.exists()) {
+            path.mkdirs();
+        }
+        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
+        BufferedImage bufferedImage = ImageIO.read(byteArrayInputStream);
+        try {
+            //可以是jpg,png,gif格式
+            File file = new File(pathName);
+            ImageIO.write(bufferedImage, "jpg", file);
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            logger.info("导出成功,文件路径:{}", pathName);
+            byteArrayInputStream.close();
+        }
+        return pathName;
+    }
+}

+ 123 - 0
alien-common-core/src/main/java/shop/alien/util/codechange/URLtoUTF8.java

@@ -0,0 +1,123 @@
+package shop.alien.util.codechange;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 中文转浏览器地址栏编码
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/7/13 10:42
+ */
+public class URLtoUTF8 {
+
+    /**
+     * 转换为浏览器编码
+     *
+     * @param s 内容
+     * @return 转换后的内容
+     */
+    public static String toUtf8String(String s) {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (c >= 0 && c <= 255) {
+                sb.append(c);
+            } else {
+                byte[] b;
+                try {
+                    b = String.valueOf(c).getBytes(StandardCharsets.UTF_8);
+                } catch (Exception ex) {
+                    System.out.println(ex);
+                    b = new byte[0];
+                }
+                for (int value : b) {
+                    int k = value;
+                    if (k < 0) {
+                        k += 256;
+                    }
+                    sb.append("%").append(Integer.toHexString(k).toUpperCase());
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 将浏览器编码转换为汉字
+     *
+     * @param s 内容
+     * @return 转换成中文
+     */
+    public static String unescape(String s) {
+        StringBuilder stringBuilder = new StringBuilder();
+        int l = s.length();
+        int ch = -1;
+        int b, sumb = 0;
+        for (int i = 0, more = -1; i < l; i++) {
+            /* Get next byte b from URL segment s */
+            switch (ch = s.charAt(i)) {
+                case '%':
+                    ch = s.charAt(++i);
+                    int hb = (Character.isDigit((char) ch) ? ch - '0'
+                            : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
+                    ch = s.charAt(++i);
+                    int lb = (Character.isDigit((char) ch) ? ch - '0'
+                            : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
+                    b = (hb << 4) | lb;
+                    break;
+                case '+':
+                    b = ' ';
+                    break;
+                default:
+                    b = ch;
+            }
+            /* Decode byte b as UTF-8, sumb collects incomplete chars */
+            if ((b & 0xc0) == 0x80) {
+                // 10xxxxxx (continuation byte)
+                // Add 6 bits to sumb
+                sumb = (sumb << 6) | (b & 0x3f);
+                if (--more == 0) {
+                    // Add char to sbuf
+                    stringBuilder.append((char) sumb);
+                }
+            } else if ((b & 0x80) == 0x00) {
+                // 0xxxxxxx (yields 7 bits)
+                // Store in sbuf
+                stringBuilder.append((char) b);
+            } else if ((b & 0xe0) == 0xc0) {
+                // 110xxxxx (yields 5 bits)
+                sumb = b & 0x1f;
+                // Expect 1 more byte
+                more = 1;
+            } else if ((b & 0xf0) == 0xe0) {
+                // 1110xxxx (yields 4 bits)
+                sumb = b & 0x0f;
+                // Expect 2 more bytes
+                more = 2;
+            } else if ((b & 0xf8) == 0xf0) {
+                // 11110xxx (yields 3 bits)
+                sumb = b & 0x07;
+                // Expect 3 more bytes
+                more = 3;
+            } else if ((b & 0xfc) == 0xf8) {
+                // 111110xx (yields 2 bits)
+                sumb = b & 0x03;
+                // Expect 4 more bytes
+                more = 4;
+            } else /*if ((b & 0xfe) == 0xfc)*/ {
+                // 1111110x (yields 1 bit)
+                sumb = b & 0x01;
+                // Expect 5 more bytes
+                more = 5;
+            }
+            /* We don't test if the UTF-8 encoding is well-formed */
+        }
+        return stringBuilder.toString();
+    }
+
+    public static void main(String[] args) {
+        System.out.println(URLtoUTF8.toUtf8String("你好吗"));
+        System.out.println(URLtoUTF8.unescape("%E4%BD%A0%E5%A5%BD%E5%90%97"));
+    }
+}

+ 58 - 0
alien-common-core/src/main/java/shop/alien/util/codechange/ZhToUnicode.java

@@ -0,0 +1,58 @@
+package shop.alien.util.codechange;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2020/8/10 9:01
+ */
+public class ZhToUnicode {
+
+    public static void main(String[] args) {
+        String sourceData = "这是原始的数据!!!";
+        String unicodeEncode = unicodeEncode(sourceData);
+        System.out.println("编码结果:" + unicodeEncode);
+
+        String unicodeDecode = unicodeDecode(unicodeEncode);
+        System.out.println("解码结果:" + unicodeDecode);
+    }
+
+    /**
+     * 中文转Unicode
+     *
+     * @param string 中文内容
+     * @return 转换后内容
+     */
+    public static String unicodeEncode(String string) {
+        char[] utfBytes = string.toCharArray();
+        StringBuilder unicodeBytes = new StringBuilder();
+        for (char utfByte : utfBytes) {
+            String hexB = Integer.toHexString(utfByte);
+            if (hexB.length() <= 2) {
+                hexB = "00" + hexB;
+            }
+            unicodeBytes.append("\\u").append(hexB);
+        }
+        return unicodeBytes.toString();
+    }
+
+    /**
+     * Unicode转中文
+     *
+     * @param string unicode码
+     * @return 转换后内容
+     */
+    public static String unicodeDecode(String string) {
+        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
+        Matcher matcher = pattern.matcher(string);
+        char ch;
+        while (matcher.find()) {
+            ch = (char) Integer.parseInt(matcher.group(2), 16);
+            string = string.replace(matcher.group(1), ch + "");
+        }
+        return string;
+    }
+
+}

+ 136 - 0
alien-common-core/src/main/java/shop/alien/util/command/CommandUtil.java

@@ -0,0 +1,136 @@
+package shop.alien.util.command;
+
+import lombok.extern.slf4j.Slf4j;
+import shop.alien.util.random.RandomNumber;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 命令行工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/24 10:13
+ */
+@Slf4j
+public class CommandUtil {
+
+    public static boolean test(String... a) {
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        // 重定向错误流,防阻塞
+        processBuilder.redirectErrorStream(true);
+        processBuilder.command(a);
+        try {
+            Process process = processBuilder.start();
+            // 获取流信息, 一般用于打印有返回值的例如:ipconfig
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                System.out.println(line);
+            }
+            if (process.waitFor() == 0) {
+                return true;
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            return false;
+        }
+        return false;
+    }
+
+    /**
+     * ImageMagick图片格式转换
+     *
+     * @param imageFilePath 图片文件路径
+     * @param convertType   转换格式
+     * @return boolean
+     */
+    public static String imageConvert(String imageFilePath, String convertType) {
+        log.info("CommandUtil.imageConvert imageFilePath={}, convertType={}", imageFilePath, convertType);
+        imageFilePath = imageFilePath.replace("\\", "/");
+        String[] pathList = imageFilePath.split("\\.");
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        // 重定向错误流,防阻塞
+        processBuilder.redirectErrorStream(true);
+        String convertFile = pathList[0] + "." + convertType;
+        // 如果转换文件存在,则重命名
+        if (new File(convertFile).exists()) {
+            convertFile = pathList[0] + "_" + RandomNumber.createRandom(1000, 9999) + "." + convertType;
+        }
+        processBuilder.command("magick", imageFilePath, convertFile);
+        try {
+            Process process = processBuilder.start();
+            if (0 == process.waitFor()) {
+                return convertFile;
+            }
+            return null;
+        } catch (IOException | InterruptedException e) {
+            log.error("CommandUtil.imageConvert ERROR Msg={}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * FFMpeg视频截取图片
+     *
+     * @param videoFilePath 视频媒体文件
+     */
+    public static String videoCaptureImage(String videoFilePath) {
+        log.info("CommandUtil.videoCaptureImage videoFilePath={}", videoFilePath);
+        String[] file = videoFilePath.split("\\.");
+        // 截图保存位置
+        String imgFilePath = file[0] + ".jpg";
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        // 重定向错误流,防阻塞
+        processBuilder.redirectErrorStream(true);
+        // 调用ffmpeg 执行截取命令,需要服务器中安装了ffmpeg并配置了环境变量
+        processBuilder.command("ffmpeg", "-i", videoFilePath, "-ss", "00:00:01", "-vframes", "1", imgFilePath);
+        try {
+            Process process = processBuilder.start();
+            if (0 == process.waitFor()) {
+                return imgFilePath;
+            }
+            return "";
+        } catch (IOException | InterruptedException e) {
+            log.error("CommandUtil.videoCaptureImage ERROR Msg={}", e.getMessage());
+            return "";
+        }
+    }
+
+    /**
+     * FFMpeg视频截取图片
+     *
+     * @param videoFilePath 视频媒体文件
+     */
+    public static String getVideoTime(String videoFilePath) {
+        log.info("CommandUtil.getVideoTime videoFilePath={}", videoFilePath);
+        // 截图保存位置
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        // 重定向错误流,防阻塞
+        processBuilder.redirectErrorStream(true);
+        // 调用ffmpeg 执行截取命令,需要服务器中安装了ffmpeg并配置了环境变量
+        //ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 2497682.mp4
+        processBuilder.command("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoFilePath);
+        try {
+            Process process = processBuilder.start();
+            // 获取流信息
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            String videoLength = "";
+            while ((line = reader.readLine()) != null) {
+                System.out.println(line);
+                videoLength = line;
+            }
+            if (0 == process.waitFor()) {
+                return videoLength;
+            }
+            return "";
+        } catch (IOException | InterruptedException e) {
+            log.error("CommandUtil.captureImage ERROR Msg={}", e.getMessage());
+            return "";
+        }
+    }
+}

+ 56 - 0
alien-common-core/src/main/java/shop/alien/util/common/AlipayTradeAppPay.java

@@ -0,0 +1,56 @@
+package shop.alien.util.common;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeAppPayModel;
+import com.alipay.api.request.AlipayTradeAppPayRequest;
+import com.alipay.api.response.AlipayTradeAppPayResponse;
+
+public class AlipayTradeAppPay {
+
+    public static String executeAlipayPayment(String outTradeNo, String totalAmount, String subject) {
+        try {
+            AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
+            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
+            model.setOutTradeNo(outTradeNo);
+            model.setTotalAmount(totalAmount);
+            model.setSubject(subject);
+            model.setPassbackParams(UrlEncode.getUrlEncode(outTradeNo));
+            request.setBizModel(model);
+            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
+            String orderStr = "";
+            if (response.isSuccess()) {
+                orderStr = response.getBody();
+            } else {
+                System.out.println("调用失败 response:" + response);
+                orderStr = "调用失败";
+            }
+            return orderStr;
+        } catch (AlipayApiException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * 用户端配置
+     *
+     * @return
+     */
+    private static AlipayConfig getAlipayConfig() {
+        String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBKWeug/nzFhN2wXFj0G4wlUkcSSnYr4PNX0Q6TmEKr3UTfescvGLNhFOQgZD4XDj5CSuNyPffT55UjtkAGR2oXZhBqHSE9tZKfIm10m4QJOBvxlKhhMicDVqgo6SJ8E3Lr/J44BFb5K6ze4sO36y+1Am4nTiwU7xgtA5BGiYt/O11GxSx2cFSgS59vuFN4CekkP/iWPbOxEaErOL9upKriJEODayvoUm6sVpE6pc0P1Hz8MtA24lB+PRsJp/a7Zs0BW2h0XNL8dZ5dZ4YfTclT01AHIFQHkodl3epRBpiaSlI0AQ4nK3x7rB40WTaYRiFWUsLyfHuCPQ28CgSHuztAgMBAAECggEABHzQymJxe72nnXepHeOvAGcfbCkqmAm5FZiawT/Z5UolUjEMyynMcQGI+btWsxpZLGXiKoHyAsM8aAmyhmtY+XGPCbD1o20EEwBytGuEQXFWjuugEVaQtcNNA8nLvLMpsMsDLKn0UHIIk9nWAgVJdjPdxplHz/CI2f9Hg4GKFAzpXwV1c574+rSDIkpa2NDFI2lqwtbew7/1ETaCiBXSbqw4X/tk+2wgUdT8u4uA/Ts5bVZvvHm54U9ZdTLs2rHzIk2O9VbKB3waHT1WkQzrPF8+JrPvcYjsK7ROxi07N1e5kv4TuxLfK3rXvmoUI8TvidSLhmq57VXhU9iNZBEHwQKBgQD4GR58WhOotPHcNI5oKRkJSektNSz/pXFLjh3oF/y98kOuFfeH903Z+JQ/5fgjzQjKra3HqjsVDlTjhCSZtNc/QpYafMf6TGzX+41QNnjlxxeZgYa2CCPOAlOw8d1a6YV0v8mjjnWoh2+vaYaqBKZmm2uid9zyklbg8I0DZiYaUQKBgQCFRogto52N6i5QSMwPGrPJOabKqkpA2EtbEgnkrpybN5DFQ+OqwWujQVkwWNlufHAhWdb2OCJ+bb2dCwrmX8kHB/SF+VSlYGsRdDAONF9aVGHqnaJB1yvfwp4ArNUb0vORJun22jRCpmtN0dA7lS/PnwqgCZw8LNWkAFuVewSl3QKBgF+PdVGadHYH9BzIVY3DPrR4NotGLyXs+J2MiPJrwtr65Jy6M1S4qdDqUVfnYVOQ1vESQpxkckti8MmNjXdy03G3WQ+svm4nX8k1SDH8OUbwD6P94wBcVEY2WTwPfR5WUiQs9yK7bZBTUm5zK/5yuhFNjgDPQFhii3oqzXXgs45xAoGADjZ4XU2ehrzblYo94Lp7Q9FXHTPN2V53os2oqm+ImfDyCmd0Bwi7ftyAM2y2O1cf6h6XkwhnfU4cs3uy/OBoIszRYdw2D7DZmjHm0gz5wjtPeeK3pAfbUPCDQxUrYN09RlR9DOs2OYlf1huy9jexspWGX4zG0ZACdIvpOGa3Fy0CgYEA8waJ9ciOG50VjX/f/vYRKQUQTFQKhvZ0xcFnA8iT30FNfBs7XOhiBaXF25mul34hHhAxVuUIlEmOqyEkLO/ZWioOtWy//gRZIFL8qHoCaM8lS8xwT/xOz8nfDiu2hpISEXAquU2PNdP4yGiLg4qyafrvdctRw4TtpHEOrtYYyt8=";
+        String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgSlnroP58xYTdsFxY9BuMJVJHEkp2K+DzV9EOk5hCq91E33rHLxizYRTkIGQ+Fw4+Qkrjcj330+eVI7ZABkdqF2YQah0hPbWSnyJtdJuECTgb8ZSoYTInA1aoKOkifBNy6/yeOARW+Sus3uLDt+svtQJuJ04sFO8YLQOQRomLfztdRsUsdnBUoEufb7hTeAnpJD/4lj2zsRGhKzi/bqSq4iRDg2sr6FJurFaROqXND9R8/DLQNuJQfj0bCaf2u2bNAVtodFzS/HWeXWeGH03JU9NQByBUB5KHZd3qUQaYmkpSNAEOJyt8e6weNFk2mEYhVlLC8nx7gj0NvAoEh7s7QIDAQAB";
+        AlipayConfig alipayConfig = new AlipayConfig();
+        alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
+        alipayConfig.setAppId("2021004192631044");
+        alipayConfig.setPrivateKey(privateKey);
+        alipayConfig.setFormat("json");
+        alipayConfig.setAlipayPublicKey(alipayPublicKey);
+        alipayConfig.setCharset("UTF-8");
+        alipayConfig.setSignType("RSA2");
+        return alipayConfig;
+    }
+}

+ 74 - 0
alien-common-core/src/main/java/shop/alien/util/common/AlipayTradeRefund.java

@@ -0,0 +1,74 @@
+package shop.alien.util.common;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeRefundModel;
+import com.alipay.api.request.AlipayTradeRefundRequest;
+import com.alipay.api.response.AlipayTradeRefundResponse;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AlipayTradeRefund {
+
+    /**
+     * appId
+     */
+    @Value("${app.user.appId}")
+    private String userAppId;
+
+    /**
+     * app私钥
+     */
+    @Value("${app.user.appPrivateKey}")
+    private String userAppPrivateKey;
+
+    /**
+     * 支付宝公钥
+     */
+    @Value("${app.user.aliPayPublicKey}")
+    private String userAliPayPublicKey;
+
+    /**
+     * 用户端配置
+     *
+     * @return
+     */
+    private AlipayConfig getAlipayConfig() {
+        AlipayConfig alipayConfig = new AlipayConfig();
+        alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
+        alipayConfig.setAppId(userAppId);
+        alipayConfig.setPrivateKey(userAppPrivateKey);
+        alipayConfig.setFormat("json");
+        alipayConfig.setAlipayPublicKey(userAliPayPublicKey);
+        alipayConfig.setCharset("UTF-8");
+        alipayConfig.setSignType("RSA2");
+        return alipayConfig;
+    }
+
+    public String processRefund(String outTradeNo, String refundAmount, String refundReason) {
+        try {
+            AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+            AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
+            AlipayTradeRefundModel model = new AlipayTradeRefundModel();
+            model.setOutTradeNo(outTradeNo);
+            model.setRefundAmount(refundAmount);
+            model.setRefundReason(refundReason);
+            request.setBizModel(model);
+            AlipayTradeRefundResponse response = alipayClient.execute(request);
+            String refundReslut = "";
+            if (response.isSuccess()) {
+                refundReslut = "调用成功";
+            } else {
+                refundReslut = "调用失败";
+            }
+            return refundReslut;
+        } catch (AlipayApiException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+}

+ 52 - 0
alien-common-core/src/main/java/shop/alien/util/common/CommendUtil.java

@@ -0,0 +1,52 @@
+package shop.alien.util.common;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 命令行工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/6/11 9:30
+ */
+@Slf4j
+public class CommendUtil {
+
+    public static void main(String[] args) {
+        String commendInfo = getCommendInfo("C:\\Users\\PC\\Desktop\\GitCommitVersion.bat");
+        String[] split = commendInfo.split(",");
+        String lastLine = split[split.length - 1];
+        System.out.println(lastLine);
+    }
+
+    public static String getCommendInfo(String commendContent) {
+        try {
+            StringBuilder stringBuilder = new StringBuilder();
+            ProcessBuilder processBuilder = new ProcessBuilder(commendContent);
+            Process process = processBuilder.start();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                stringBuilder.append(line).append(",");
+            }
+            int exitCode = process.waitFor();
+            if (exitCode != 0) {
+                BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+                StringBuilder errorMessage = new StringBuilder();
+                String errorLine;
+                while ((errorLine = errorReader.readLine()) != null) {
+                    errorMessage.append(errorLine).append(",");
+                }
+                throw new IOException("命令执行失败,退出码: " + exitCode + ", 错误信息: " + errorMessage);
+            }
+            return stringBuilder.toString().trim();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}

+ 133 - 0
alien-common-core/src/main/java/shop/alien/util/common/Constants.java

@@ -0,0 +1,133 @@
+package shop.alien.util.common;
+
+/**
+ * 项目通用常量定义类
+ */
+public class Constants {
+    
+    /**
+     * 通知相关常量
+     */
+    public static class Notice {
+        /**
+         * 系统通知类型
+         */
+        public static final Integer SYSTEM_NOTICE = 1;
+    }
+    
+    /**
+     * 图片类型常量
+     */
+    public static class ImageType {
+        /**
+         * 二手商品图片类型
+         */
+        public static final Integer SECOND_HAND_GOODS = 18;
+
+        /**
+         * 二手商品图片类型
+         */
+        public static final Integer SECOND_HAND_REPORT = 19;
+        
+        /**
+         * 二手商品记录图片类型
+         */
+        public static final Integer SECOND_HAND_RECORD = 23;
+    }
+    
+    /**
+     * 删除标志常量
+     */
+    public static class DeleteFlag {
+        /**
+         * 未删除
+         */
+        public static final Integer NOT_DELETED = 0;
+        
+        /**
+         * 已删除
+         */
+        public static final Integer DELETED = 1;
+    }
+    
+    /**
+     * 审核状态常量
+     */
+    public static class AuditStatus {
+        /**
+         * 审核失败
+         */
+        public static final Integer FAILED = 2;
+        
+        /**
+         * 审核通过
+         */
+        public static final Integer PASSED = 1;
+
+        /**
+         * 审核中
+         */
+        public static final Integer UNDER_REVIEW = 0;
+
+    }
+    
+    /**
+     * 点赞类型常量
+     */
+    public static class LikeType {
+        /**
+         * 二手商品点赞
+         */
+        public static final Integer SECOND_HAND_GOODS = 6;
+    }
+    
+    /**
+     * 收藏业务类型常量
+     */
+    public static class CollectBusinessType {
+        /**
+         * 默认业务类型
+         */
+        public static final Integer DEFAULT = 1;
+    }
+    
+    /**
+     * 交易状态常量
+     */
+    public static class TradeStatus {
+        /**
+         * 交易成功
+         */
+        public static final Integer SUCCESS = 1;
+        
+        /**
+         * 待确认
+         */
+        public static final Integer PENDING_CONFIRMATION = 1;
+        
+        /**
+         * 已拒绝
+         */
+        public static final Integer REJECTED = 2;
+        
+        /**
+         * 待交易
+         */
+        public static final Integer PENDING_TRADE = 3;
+        
+        /**
+         * 交易成功
+         */
+        public static final Integer TRADE_SUCCESS = 4;
+        
+        /**
+         * 交易失败
+         */
+        public static final Integer TRADE_FAILED = 5;
+        
+        /**
+         * 交易取消
+         */
+        public static final Integer TRADE_CANCELLED = 6;
+    }
+}

+ 114 - 0
alien-common-core/src/main/java/shop/alien/util/common/DateUtils.java

@@ -0,0 +1,114 @@
+package shop.alien.util.common;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 日期计算工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2023/5/24 16:03
+ */
+public class DateUtils {
+
+    public static void main(String[] args) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//        Date date = calcHours(new Date(), 1);
+//        System.out.println(sdf.format(date));
+//
+//        boolean b = dateCompare(new Date(), date);
+//        System.out.println(b);
+
+
+        Date date1 = sdf.parse("2024-05-10 19:10:00");
+        Date date2 = sdf.parse("2024-05-10 19:11:00");
+        System.out.println(calcMinute(date1, 1));
+        System.out.println(dateCompare(date1, calcMinute(date1, 1)));
+
+    }
+
+    /**
+     * 天数加减
+     *
+     * @param date 日期
+     * @param days 加减天数
+     * @return 处理后的日期
+     */
+    public static Date calcDays(Date date, Integer days) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.DAY_OF_MONTH, days);
+        return c.getTime();
+    }
+
+    /**
+     * 小时加减
+     *
+     * @param date  日期
+     * @param hours 加减时间
+     * @return 处理后的日期
+     */
+    public static Date calcHours(Date date, Integer hours) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.HOUR_OF_DAY, hours);
+        return c.getTime();
+    }
+
+    /**
+     * 分钟加减
+     *
+     * @param date   日期
+     * @param minute 加减时间
+     * @return 处理后的日期
+     */
+    public static Date calcMinute(Date date, Integer minute) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.MINUTE, minute);
+        return c.getTime();
+    }
+
+    /**
+     * 日期比较
+     *
+     * @param dateOne 日期1
+     * @param dateTwo 日期2
+     * @return 是否大于
+     */
+    public static boolean dateCompare(Date dateOne, Date dateTwo) {
+        return dateOne.compareTo(dateTwo) > 0;
+    }
+
+    /**
+     * 格式化日期
+     *
+     * @param date   日期
+     * @param format 格式
+     * @return 格式化后的日期
+     */
+    public static String formatDate(Date date, String format) {
+        if (format.isEmpty())
+            format = "yyyy-MM-dd HH:mm:ss";
+        SimpleDateFormat sdf = new SimpleDateFormat(format);
+        return sdf.format(date);
+    }
+
+    /**
+     * 时间相减
+     * @param start
+     * @return
+     */
+    public static int subtraction(Date start , Date end) {
+        LocalDate startDate = start.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        LocalDate endDate = end.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        return (int) ChronoUnit.DAYS.between(startDate, endDate);
+    }
+
+}

+ 65 - 0
alien-common-core/src/main/java/shop/alien/util/common/DistanceUtil.java

@@ -0,0 +1,65 @@
+package shop.alien.util.common;
+
+import java.text.DecimalFormat;
+
+/**
+ * 两个经纬度距离计算
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/3/14 15:39
+ */
+public class DistanceUtil {
+
+    private static final int EARTH_RADIUS = 6371; // 地球半径,单位为千米
+
+    /**
+     * Haversine公式
+     * <p>
+     * Haversine公式是一种常用的方法,用于计算球面上两个点之间的距离。在此方法中,假设地球是一个完美的球体,忽略了地球的椭球形状。
+     *
+     * @param lon1 经度1
+     * @param lat1 纬度1
+     * @param lon2 经度2
+     * @param lat2 纬度2
+     * @return 两点之间的距离,单位为千米
+     */
+    public static double haversineCalculateDistance(double lon1, double lat1, double lon2, double lat2) {
+        double dLat = Math.toRadians(lat2 - lat1);
+        double dLon = Math.toRadians(lon2 - lon1);
+        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
+                * Math.sin(dLon / 2) * Math.sin(dLon / 2);
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+        DecimalFormat df = new DecimalFormat("#.##");
+        return Double.parseDouble(df.format(EARTH_RADIUS * c));
+    }
+
+    /**
+     * Vincenty公式
+     * <p>
+     * Vincenty公式是一种更准确的计算方法,可以考虑地球的椭球形状。与Haversine公式相比,Vincenty公式的计算结果更准确,但计算量稍大,适用于更高精度的计算。
+     *
+     * @param lon1 经度1
+     * @param lat1 纬度1
+     * @param lon2 经度2
+     * @param lat2 纬度2
+     * @return 两点之间的距离,单位为千米
+     */
+    public static double vincentyCalculateDistance(double lon1, double lat1, double lon2, double lat2) {
+        double phi1 = Math.toRadians(lat1);
+        double lambda1 = Math.toRadians(lon1);
+        double phi2 = Math.toRadians(lat2);
+        double lambda2 = Math.toRadians(lon2);
+
+        double deltaLambda = Math.abs(lambda1 - lambda2);
+        double numerator = Math.sqrt(
+                Math.pow(Math.cos(phi2) * Math.sin(deltaLambda), 2) +
+                        Math.pow(Math.cos(phi1) * Math.sin(phi2) - Math.sin(phi1) * Math.cos(phi2) * Math.cos(deltaLambda), 2)
+        );
+        double denominator = Math.sin(phi1) * Math.sin(phi2) + Math.cos(phi1) * Math.cos(phi2) * Math.cos(deltaLambda);
+        DecimalFormat df = new DecimalFormat("#.##");
+        return Double.parseDouble(df.format(EARTH_RADIUS * Math.atan2(numerator, denominator)));
+    }
+
+}

+ 20 - 0
alien-common-core/src/main/java/shop/alien/util/common/EnumUtil.java

@@ -0,0 +1,20 @@
+package shop.alien.util.common;
+
+import shop.alien.util.common.constant.ViolationEnum;
+import java.util.HashMap;
+import java.util.Map;
+
+// 枚举工具类,通过code获取value
+public class EnumUtil {
+    private static final Map<Integer, String> STATUS_MAP = new HashMap<>();
+
+    static {
+        for (ViolationEnum status : ViolationEnum.values()) {
+            STATUS_MAP.put(status.getCode(), status.getValue());
+        }
+    }
+
+    public static String getStatusValue(int code) {
+        return STATUS_MAP.getOrDefault(code, "未知状态码");
+    }
+}

+ 61 - 0
alien-common-core/src/main/java/shop/alien/util/common/FileUpload.java

@@ -0,0 +1,61 @@
+package shop.alien.util.common;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@Configuration
+public class FileUpload {
+
+    @Value("${spring.web.resources.static-locations}")
+    private String uploadDir;
+
+    public String saveImage(MultipartFile image) {
+        try {
+            String cleanUploadDir = uploadDir.replace("file:///", "").replace("\\", "/");
+            Path path = Paths.get(cleanUploadDir, image.getOriginalFilename());
+            Files.createDirectories(path.getParent());
+            Files.write(path, image.getBytes());
+            return image.getOriginalFilename();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+//    public String getVideoDuration(MultipartFile image) {
+//        String cleanUploadDir = uploadDir.replace("file:///", "").replace("\\", "/");
+//        Path path = Paths.get(cleanUploadDir, image.getOriginalFilename());
+//        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(path.toString())) {
+//            String fileExtension = getFileExtension(path.toString());
+//            if ("mp4".equalsIgnoreCase(fileExtension)) {
+//                grabber.setFormat("mp4");
+//            } else if ("avi".equalsIgnoreCase(fileExtension)) {
+//                grabber.setFormat("avi");
+//            } else if ("mkv".equalsIgnoreCase(fileExtension)) {
+//                grabber.setFormat("matroska");
+//            }
+//            grabber.start();
+//            double durationInSeconds = grabber.getLengthInTime() / 1_000_000.0;
+//            grabber.stop();
+//            long hours = (long) durationInSeconds / 3600;
+//            long minutes = (long) (durationInSeconds % 3600) / 60;
+//            long seconds = (long) (durationInSeconds % 60);
+//            return String.format("%dh %dm %ds", hours, minutes, seconds);
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//            return "获取时长失败";
+//        }
+//    }
+
+//    private String getFileExtension(String filePath) {
+//        File file = new File(filePath);
+//        String name = file.getName();
+//        int lastIndexOfDot = name.lastIndexOf('.');
+//        return (lastIndexOfDot == -1) ? "" : name.substring(lastIndexOfDot + 1);
+//    }
+}

+ 171 - 0
alien-common-core/src/main/java/shop/alien/util/common/JwtUtil.java

@@ -0,0 +1,171 @@
+package shop.alien.util.common;
+
+import com.alibaba.fastjson.JSONObject;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.util.StringUtil;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.http.HttpServletRequest;
+import java.security.SecureRandom;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Jwt工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/1/21 16:58
+ */
+@Slf4j
+public class JwtUtil {
+
+    // 使用更安全的密钥生成方式
+//    private static final String KEY = Base64.getEncoder().encodeToString(SecureRandom.getSeed(256));
+
+    private static final String KEY = "hicYCHhHvcwg2lfrhRi9gqFM9TSzOk04JIhrHzGLaa9tL0TD+CYEE8VUy5qHmKX2mVtGM+/YXfut7+je4AVgJSDlJq1p4PYZhKU5gOoxMQD27Wmlpy3uR4kTdQEjMn9jG5a5slKrUD5lSGIrDfKNIYn0N50VbBh2Je0k7BOv1XUcy0wjapPSWmTi7lEFVDV56Ve0RSgkb0UTTyMq+uYknOgEPFGlBOXeSpVmh3VZrndmN8jZ1u40L2LzQyjb2JNaevOMT5v19ZqiYdo2x5AoAj5d4Z7chogct13QqOLvBeplGESLD8gzbS3+X7HMI4cDKsxbwGxFZT8md3efQQyfXw==";
+
+//    // 确保生成的密钥符合HMAC-SHA算法要求
+//    static {
+//        if (KEY.length() < 256) {
+//            throw new IllegalStateException("Key length is too short!");
+//        }
+//    }
+
+    public static SecretKey generateKey() {
+        byte[] encodedKey = Base64.getDecoder().decode(KEY);
+        return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
+    }
+
+    /**
+     * 创建token
+     *
+     * @param id        是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
+     * @param issuer    jwt签发人
+     * @param subject   代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
+     * @param ttlMillis 过期时间
+     * @return token
+     */
+    public static String createJWT(String id, String issuer, String subject, long ttlMillis) {
+        // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
+        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+        // 生成JWT的时间
+        long nowMillis = System.currentTimeMillis();
+        Date now = new Date(nowMillis);
+        // 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
+        Map<String, Object> claims = new HashMap<>();
+        claims.put("uid", "123456");
+        claims.put("user_name", "admin");
+        claims.put("nick_name", "X-rapido");
+        SecretKey key = generateKey();
+        // 这里其实就是new一个JwtBuilder,设置jwt的body
+        JwtBuilder builder = Jwts.builder()
+                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
+                .setClaims(claims)
+                // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
+                .setId(id)
+                // iat: jwt的签发时间
+                .setIssuedAt(now)
+                // issuer:jwt签发人
+                .setIssuer(issuer)
+                // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
+                .setSubject(subject)
+                // 设置签名使用的签名算法和签名使用的秘钥
+                .signWith(signatureAlgorithm, key);
+        // 设置过期时间
+        if (ttlMillis >= 0) {
+            long expMillis = nowMillis + ttlMillis;
+            Date exp = new Date(expMillis);
+            builder.setExpiration(exp);
+        }
+        return builder.compact();
+    }
+
+    /**
+     * 解密jwt
+     *
+     * @param jwt
+     * @return
+     */
+    public static Claims parseJWT(String jwt) {
+        //签名秘钥,和生成的签名的秘钥一模一样
+        SecretKey key = generateKey();
+        return Jwts.parser()
+                .setSigningKey(key)
+                .parseClaimsJws(jwt).getBody();
+    }
+
+    /**
+     * 检查token
+     *
+     * @param jwtToken
+     * @return
+     */
+    public static boolean checkToken(String jwtToken) {
+        Claims claims = JwtUtil.parseJWT(jwtToken);
+        //获取token的过期时间,和当前时间作比较,如果小于当前时间,则token过期
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        Date expiration = claims.getExpiration();
+        log.info("Token的过期时间:" + df.format(expiration));
+//        if (expiration.before(new Date())) {
+//            return false;
+//        }
+        return true;
+    }
+
+    /**
+     * 获取token信息
+     */
+    public static JSONObject getTokenInfo(String token) {
+        Claims claims = JwtUtil.parseJWT(token);
+        return JSONObject.parseObject(claims.get("sub").toString());
+    }
+
+
+    /**
+     * 非控制器获取token
+     */
+    public static String getToken() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes != null) {
+            HttpServletRequest request = attributes.getRequest();
+            String token = request.getHeader("Authorization");
+            return token;
+        }
+        return null;
+    }
+
+    public static JSONObject getCurrentUserInfo() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes != null) {
+            HttpServletRequest request = attributes.getRequest();
+            String token = request.getHeader("Authorization");
+            Claims claims = JwtUtil.parseJWT(token);
+            return JSONObject.parseObject(claims.get("sub").toString());
+        }
+        return null;
+    }
+
+    public static boolean hasToken() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes != null) {
+            HttpServletRequest request = attributes.getRequest();
+            String token = request.getHeader("Authorization");
+            if (StringUtil.isNotBlank(token)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 44 - 0
alien-common-core/src/main/java/shop/alien/util/common/ListToPage.java

@@ -0,0 +1,44 @@
+package shop.alien.util.common;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 将 List 转换为 IPage 对象
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/25 14:32
+ */
+public class ListToPage {
+
+    /**
+     * 将 List 转换为 IPage 对象。
+     *
+     * @param <T>      泛型类型
+     * @param list     数据列表
+     * @param pageNum  当前页码
+     * @param pageSize 每页大小
+     * @return 分页对象
+     */
+    public static <T> IPage<T> setPage(List<T> list, Integer pageNum, Integer pageSize) {
+        IPage<T> iPage = new Page<>(pageNum, pageSize);
+        iPage.setTotal(list.size());
+        iPage.setCurrent(pageNum);
+        iPage.setSize(pageSize);
+        int pages = (int) Math.ceil((double) iPage.getTotal() / iPage.getSize());
+        iPage.setPages(pages);
+        int fromIndex = (pageNum - 1) * pageSize;
+        int toIndex = Math.min(pageNum * pageSize, list.size());
+        List<T> records = new ArrayList<T>();
+        if(fromIndex <= toIndex && fromIndex >= 0){
+            records = list.subList(fromIndex, toIndex);
+        }
+        iPage.setRecords(records);
+        return iPage;
+    }
+
+}

+ 66 - 0
alien-common-core/src/main/java/shop/alien/util/common/RandomCreateUtil.java

@@ -0,0 +1,66 @@
+package shop.alien.util.common;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+/**
+ * 生成随机数工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/12 15:17
+ */
+public class RandomCreateUtil {
+
+    /**
+     * 生成随机数
+     *
+     * @param length 位数
+     * @return 随机数
+     */
+    public static String getRandomNum(Integer length) {
+        List<String> list = Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
+        Collections.shuffle(list);
+        List<String> collect = list.stream().limit(length).collect(Collectors.toList());
+        StringBuilder stringBuilder = new StringBuilder();
+        collect.forEach(stringBuilder::append);
+        return stringBuilder.toString();
+    }
+
+    /**
+     * 生成随机数
+     *
+     * @param min 最小值
+     * @param max 最大值
+     * @return 随机数
+     */
+    public static Integer getRandomNum(Integer min, Integer max) {
+        Random random = new Random();
+        return random.nextInt((max - min) + 1) + min;
+    }
+
+    /**
+     * 生成随机字符串
+     *
+     * @param length 长度
+     * @return 字符串
+     */
+    public static String getRandomNStr(Integer length) {
+        StringBuilder sb = new StringBuilder();
+        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        for (int i = 0; i < length; i++) {
+            int index = ThreadLocalRandom.current().nextInt(chars.length());
+            sb.append(chars.charAt(index));
+        }
+        return sb.toString();
+    }
+
+    public static void main(String[] args) {
+        RandomCreateUtil randomCreateUtil = new RandomCreateUtil();
+        System.out.println(randomCreateUtil.getRandomNStr(20));
+    }
+}

+ 36 - 0
alien-common-core/src/main/java/shop/alien/util/common/StringToJsonConverterUtils.java

@@ -0,0 +1,36 @@
+package shop.alien.util.common;
+
+public class StringToJsonConverterUtils {
+
+    public static String convertToJson(String input) {
+        // 移除外层大括号并清理空格
+        String cleaned = input.trim().replaceAll("^\\{|}$", "").trim();
+        String[] pairs = cleaned.split(",\\s*");
+        StringBuilder jsonBuilder = new StringBuilder("{");
+        for (int i = 0; i < pairs.length; i++) {
+            String pair = pairs[i];
+            int eqIndex = pair.indexOf('=');
+            if (eqIndex == -1) continue; // 跳过无效格式
+
+            String key = pair.substring(0, eqIndex).trim();
+            String value = pair.substring(eqIndex + 1).trim();
+
+            jsonBuilder.append("\"").append(key).append("\":");
+
+            if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+                jsonBuilder.append(value.toLowerCase());  // 布尔值
+            } else if (value.matches("-?\\d+(\\.\\d+)?")) {
+                jsonBuilder.append(value);                // 数字
+            } else {
+                // 转义特殊字符并添加引号
+                String escaped = value.replace("\\", "\\\\").replace("\"", "\\\"");
+                jsonBuilder.append("\"").append(escaped).append("\"");
+            }
+
+            if (i < pairs.length - 1) jsonBuilder.append(",");
+        }
+        jsonBuilder.append("}");
+        return jsonBuilder.toString();
+    }
+
+}

+ 11 - 0
alien-common-core/src/main/java/shop/alien/util/common/TokenInfo.java

@@ -0,0 +1,11 @@
+package shop.alien.util.common;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TokenInfo {
+}

+ 24 - 0
alien-common-core/src/main/java/shop/alien/util/common/UniqueRandomNumGenerator.java

@@ -0,0 +1,24 @@
+package shop.alien.util.common;
+
+import java.util.HashSet;
+import java.util.Random;
+
+public class UniqueRandomNumGenerator {
+
+    public static String generateUniqueCode(int length) {
+        HashSet<String> uniqueCodes = new HashSet<>();
+        Random random = new Random();
+        String code;
+
+        do {
+            StringBuilder codeBuilder = new StringBuilder(length);
+
+            for (int i = 0; i < length; i++) {
+                codeBuilder.append(random.nextInt(10));
+            }
+            code = codeBuilder.toString();
+        } while (!uniqueCodes.add(code));
+
+        return code;
+    }
+}

+ 18 - 0
alien-common-core/src/main/java/shop/alien/util/common/UrlEncode.java

@@ -0,0 +1,18 @@
+package shop.alien.util.common;
+
+import java.nio.charset.StandardCharsets;
+
+public class UrlEncode {
+    public static String getUrlEncode(String str) {
+        StringBuilder encodedStr = new StringBuilder();
+        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
+        for (byte b : bytes) {
+            String hex = Integer.toHexString(b & 0xFF);
+            if (hex.length() == 1) {
+                hex = "0" + hex;
+            }
+            encodedStr.append("%").append(hex.toUpperCase());
+        }
+        return encodedStr.toString();
+    }
+}

+ 66 - 0
alien-common-core/src/main/java/shop/alien/util/common/VideoDurationFFmpeg.java

@@ -0,0 +1,66 @@
+package shop.alien.util.common;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class VideoDurationFFmpeg {
+    //获取视频文件时长
+    public static long getVideoDuration(String filePath) throws IOException, InterruptedException {
+        // 构建 FFmpeg 命令
+        String[] command = {
+                "ffprobe",
+                "-v", "error",
+                "-show_entries", "format=duration",
+                "-of", "default=noprint_wrappers=1:nokey=1",
+                filePath
+        };
+        // 创建进程构建器
+        ProcessBuilder processBuilder = new ProcessBuilder(command);
+        // 启动进程
+        Process process = processBuilder.start();
+        // 读取进程输出
+        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+        String line;
+        long duration = 0;
+        while ((line = reader.readLine()) != null) {
+            try {
+                // 将输出转换为毫秒
+                duration = (long) (Double.parseDouble(line) * 1000);
+            } catch (NumberFormatException e) {
+                e.printStackTrace();
+            }
+        }
+        // 等待进程结束
+        int exitCode = process.waitFor();
+        if (exitCode != 0) {
+            // 若执行失败,读取错误信息
+            BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+            StringBuilder errorMessage = new StringBuilder();
+            String errorLine;
+            while ((errorLine = errorReader.readLine()) != null) {
+                errorMessage.append(errorLine).append("\n");
+            }
+            throw new IOException("FFmpeg 命令执行失败,退出码: " + exitCode + ", 错误信息: " + errorMessage.toString());
+        }
+        return duration;
+    }
+
+    public static String getFileNameFromUrl(String url) {
+        int lastIndex = url.lastIndexOf('/');
+        if (lastIndex != -1) {
+            return url.substring(lastIndex + 1);
+        }
+        return "";
+    }
+
+    public static void main(String[] args) {
+        String filePath = "D:/2497682.mp4";
+        try {
+            long duration = getVideoDuration(filePath);
+            System.out.println("视频时长: " + duration + " 毫秒");
+        } catch (IOException | InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 99 - 0
alien-common-core/src/main/java/shop/alien/util/common/VideoUtils.java

@@ -0,0 +1,99 @@
+package shop.alien.util.common;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.util.system.OSUtil;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 命令行调用FFMpeg截取图片
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/24 10:13
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class VideoUtils {
+
+    /**
+     * 视频截取图片
+     *
+     * @param videoFilePath 视频媒体文件
+     */
+    public String getImg(String videoFilePath) {
+        log.info("VideoUtils.getImg videoFilePath={}", videoFilePath);
+        String[] file = videoFilePath.split("\\.");
+        log.info("VideoUtils.getImg file={}", file);
+        // 截图保存位置
+        String imgFilePath = file[0] + ".jpg";
+        log.info("VideoUtils.getImg imgFilePath={}", imgFilePath);
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        // 重定向错误流,防阻塞
+        processBuilder.redirectErrorStream(true);
+
+        // 如果为本地测试,ffmpegPath地址需求修改为本地安装程序的地址(环境变量不好用)
+        String ffmpegPath = "ffmpeg";
+        if ("windows".equals(OSUtil.getOsName())) {
+            ffmpegPath = "C:/Program Files (x86)/ffmpeg-6.0/bin/ffmpeg.exe";
+        }
+
+        // 调用ffmpeg 执行截取命令,需要服务器中安装了ffmpeg并配置了环境变量
+        processBuilder.command(ffmpegPath, "-i", videoFilePath, "-ss", "00:00:01", "-vframes", "1", imgFilePath);
+        try {
+            Process process = processBuilder.start();
+            // 获取流信息
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                System.out.println(line);
+            }
+            System.out.println(process.waitFor());
+            if (0 == process.waitFor()) {
+                return imgFilePath;
+            }
+            return "";
+        } catch (IOException | InterruptedException e) {
+            log.error("VideoUtils.generateCover ERROR Msg={}", e.getMessage());
+            return "";
+        }
+    }
+
+    /**
+     * FFMpeg视频截取图片
+     *
+     * @param videoFilePath 视频媒体文件
+     */
+    public String getVideoTime(String videoFilePath) {
+        log.info("CommandUtil.getVideoTime videoFilePath={}", videoFilePath);
+        // 截图保存位置
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        // 重定向错误流,防阻塞
+        processBuilder.redirectErrorStream(true);
+        // 调用ffmpeg 执行截取命令,需要服务器中安装了ffmpeg并配置了环境变量
+        //ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 2497682.mp4
+        processBuilder.command("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoFilePath);
+        try {
+            Process process = processBuilder.start();
+            // 获取流信息
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                System.out.println(line);
+            }
+            System.out.println(process.waitFor());
+            if (0 == process.waitFor()) {
+                return line;
+            }
+            return "";
+        } catch (IOException | InterruptedException e) {
+            log.error("CommandUtil.captureImage ERROR Msg={}", e.getMessage());
+            return "";
+        }
+    }
+}

+ 45 - 0
alien-common-core/src/main/java/shop/alien/util/common/WebTool.java

@@ -0,0 +1,45 @@
+package shop.alien.util.common;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Web工具类
+ * 用于在非Controller层获取HttpServletRequest和HttpServletResponse
+ *
+ * @author system
+ */
+public class WebTool {
+
+    /**
+     * 获取当前请求的HttpServletRequest
+     *
+     * @return HttpServletRequest
+     * @throws IllegalStateException 如果当前不在Web请求上下文中
+     */
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            throw new IllegalStateException("当前不在Web请求上下文中,无法获取HttpServletRequest");
+        }
+        return attributes.getRequest();
+    }
+
+    /**
+     * 获取当前请求的HttpServletResponse
+     *
+     * @return HttpServletResponse
+     * @throws IllegalStateException 如果当前不在Web请求上下文中
+     */
+    public static HttpServletResponse getResponse() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            throw new IllegalStateException("当前不在Web请求上下文中,无法获取HttpServletResponse");
+        }
+        return attributes.getResponse();
+    }
+}
+

+ 39 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/Constant.java

@@ -0,0 +1,39 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 常量
+ */
+public class Constant {
+
+    // 主键
+    public static final String GEO_STORE_PRIMARY = "GEO_STORE_PRIMARY";
+
+    // name
+    public static final String GEO_STORE_NAME = "GEO_STORE_NAME";
+
+    public static final Integer LIMIT_COUNT = 100;
+
+
+    // 举报用户类型(1:商户,2:用户)
+    public static final String REPORTING_USER_TYPE = "reporting_user_type";
+
+    // 举报用户ID
+    public static final String REPORTING_USER_ID = "reporting_user_id";
+
+
+    public static final String PROCESSING_STATUS = "processing_status";
+
+    // 序列
+    public static final String SEQUENCE_1 = "1";
+    public static final String SEQUENCE_2 = "2";
+
+
+    // 序列
+    public static final Integer SEQUENCE_NUM_1 = 1;
+    public static final Integer SEQUENCE_NUM_2 = 2;
+
+    // 符号
+    public static final String SYMBOL_1  = ",";
+
+
+}

+ 58 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/CouponStatusEnum.java

@@ -0,0 +1,58 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 优惠券状态枚举
+ */
+public enum CouponStatusEnum {
+    /**
+     * 草稿
+     */
+    DRAFT(0, "草稿"),
+    /**
+     * 待审核
+     */
+    WAIT_AUDIT(1, "待审核"),
+    /**
+     * 未开始
+     */
+    NOT_START(2, "未开始"),
+    /**
+     * 审核拒绝
+     */
+    AUDIT_REJECT(3, "审核拒绝"),
+    /**
+     * 已售罄
+     */
+    SOLD_OUT(4, "已售罄"),
+    /**
+     * 进行中
+     */
+    ONGOING(5, "进行中"),
+    /**
+     * 已下架
+     */
+    SOLD_OFF(6, "已下架"),
+    /**
+     * 已结束
+     */
+    ENDED(7, "已结束"),
+    /**
+     * 已删除
+     */
+    DELETED(8, "2+手动下架");
+    private final int code;
+    private final String info;
+
+    CouponStatusEnum(int code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+}

+ 32 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/CouponTypeEnum.java

@@ -0,0 +1,32 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 券类型枚举
+ */
+public enum CouponTypeEnum {
+    /**
+     * 代金券
+     */
+    COUPON(1, "代金券"),
+    /**
+     * 团购
+     */
+    GROUP_BUY(2, "团购券");
+
+
+    private final Integer code;
+    private final String info;
+
+    CouponTypeEnum(Integer code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+}

+ 92 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/DiscountCouponEnum.java

@@ -0,0 +1,92 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 优惠券相关功能枚举
+ * */
+public enum DiscountCouponEnum {
+    /**用户优惠券状态*/
+    //待使用
+    WAITING_USED("0"),
+    //已使用
+    HAVE_BEEN_USED("1"),
+    //已作废
+    HAVE_BEEN_VOIDED("2"),
+
+    /**商家优惠券状态*/
+    //进行中
+    UNDER_WAY("0"),
+    //已结束
+    FINISHED("1"),
+    //未开始
+    HAVE_NOT_STARTED("2"),
+    //暂停领取
+    SUSPEND_GET("3"),
+    //已售罄
+    HAVE_SOLD_OUT("4"),
+    // 草稿
+    DRAFT("5"),
+
+    /**限制使用规则*/
+    //周规则限制
+    WEEKDAY_UNAVAILABLE("weekday_unavailable"),
+    //节日规则限制
+    HOLIDAY_UNAVAILABLE("holiday_unavailable"),
+
+    /**领取规则*/
+    CLAIM_RULE("claim_rule"),
+    //每日一领
+    DAY("day"),
+    //每周一领
+    WEEK("WEEK"),
+    //每月一领
+    MONTH("MONTH"),
+    //每年一领
+    YEAR("year"),
+    //自定义
+    CUSTOMIZE("customize"),
+
+    /**时间段规则*/
+    //可用时间段
+    AVAILABLE_TIME_QUANTUM("available_time_quantum"),
+    //不可用时间段
+    UNAVAILABLE_TIME_QUANTUM("unavailable_time_quantum"),
+
+    /**条件类型*/
+    //所有(未使用)
+    ALL("0"),
+    //即将过期
+    BE_ABOUT_TO_EXPORE("1"),
+    //已使用
+    HAVE_ALREADY_APPLIED("2"),
+    //已过期
+    HAVE_EXPIRED("3"),
+
+    /**优惠券领取规则*/
+    //可以领取
+    CAN_GET("1"),
+    //不可以领取
+    NO_GET("0"),
+
+    //占位最后一位
+    KEY("VALUE");
+
+    // 属性值
+    private final String description;
+    // 构造方法,用于初始化描述信息
+    DiscountCouponEnum(String description) {
+        this.description = description;
+    }
+    // 获取Value的方法
+    public String getValue() {
+        return description;
+    }
+
+// 测试带属性和方法的枚举的使用
+    public static void main(String[] args) {
+        DiscountCouponEnum discountCouponEnum = DiscountCouponEnum.WAITING_USED;
+        System.out.println("用户优惠券状态:" + discountCouponEnum);
+        System.out.println("值是:" + discountCouponEnum.getValue());
+    }
+
+}
+

+ 61 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/LawyerStatusEnum.java

@@ -0,0 +1,61 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 律师咨询订单状态枚举
+ */
+public enum LawyerStatusEnum {
+    //订单状态, 0:待支付, 2:进行中, 3:已完成, 4:已取消;
+    /**
+     * 待支付
+     */
+    WAIT_PAY(0, "待支付"),
+
+    /**
+     * 待支付
+     */
+    WAIT_ACCEPT(1, "待接单"),
+
+    /**
+     * 进行中
+     */
+    INPROGRESS(2, "进行中"),
+    /**
+     * 已完成
+     */
+    COMPLETE(3, "已完成"),
+    /**
+     * 已取消
+     */
+    CANCEL(4, "已取消"),
+
+    /**
+     * 已取消
+     */
+    REFUNDED(5, "已退款");
+
+    private final Integer status;
+    // 属性值
+    private final String description;
+    // 构造方法,用于初始化描述信息
+    LawyerStatusEnum(Integer status, String description) {
+        this.status = status;
+        this.description = description;
+    }
+    // 提供获取描述信息的方法
+    public static String getDescription(Integer status) {
+        for (LawyerStatusEnum orderStatusEnum : LawyerStatusEnum.values()) {
+            if (orderStatusEnum.status.equals(status)) {
+                return orderStatusEnum.description;
+            }
+        }
+        return null;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 50 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/OcrTypeEnum.java

@@ -0,0 +1,50 @@
+package shop.alien.util.common.constant;
+
+/**
+ * OCR识别类型枚举
+ * 
+ * @author ssk
+ * @date 2024/12/10
+ */
+public enum OcrTypeEnum {
+    /**
+     * 营业执照识别
+     */
+    BUSINESS_LICENSE("BUSINESS_LICENSE", "营业执照识别"),
+    
+    /**
+     * 身份证识别
+     */
+    ID_CARD("ID_CARD", "身份证识别"),
+    /**
+     * 食品管理许可证识别
+     */
+    FOOD_MANAGE_LICENSE("FOOD_MANAGE_LICENSE", "食品经营许可证识别"),
+
+    /**
+     * 银行卡识别
+     */
+    BANK_CARD("BANK_CARD", "银行卡识别"),
+    
+    /**
+     * 通用文字识别
+     */
+    GENERAL("GENERAL", "通用文字识别");
+    
+    private final String code;
+    private final String description;
+    
+    OcrTypeEnum(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+    
+    public String getCode() {
+        return code;
+    }
+    
+    public String getDescription() {
+        return description;
+    }
+}
+

+ 51 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/OrderActionType.java

@@ -0,0 +1,51 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 订单操作类型枚举
+ */
+public enum OrderActionType {
+    /**
+     * 接单
+     */
+    ACCEPT("1", "接单"),
+    
+    /**
+     * 取消
+     */
+    CANCEL("2", "取消");
+
+    private final String code;
+    private final String description;
+
+    OrderActionType(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 根据code获取枚举
+     *
+     * @param code 操作类型代码
+     * @return 枚举值,如果不存在则返回null
+     */
+    public static OrderActionType getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (OrderActionType type : OrderActionType.values()) {
+            if (type.code.equals(code)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}
+

+ 69 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/OrderStatusEnum.java

@@ -0,0 +1,69 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 订单状态枚举
+ */
+public enum OrderStatusEnum {
+    //(0,待支付;1,已支付/待使用;2,已核销;3,已过期;4,已取消;5.已退款,全退款了才算;
+    /**
+     * 待支付
+     */
+    WAIT_PAY(0, "待支付"),
+    /**
+     * 已支付/待使用
+     */
+    WAIT_USE(1, "已支付/待使用"),
+    /**
+     * 已核销
+     */
+    USED(2, "已核销"),
+    /**
+     * 已过期
+     */
+    EXPIRE(3, "已过期"),
+    /**
+     * 已取消
+     */
+    CANCEL(4, "已取消"),
+    /**
+     * 已退款
+     */
+    REFUND(5, "已退款"),
+
+    /**
+     * 退款失败
+     */
+    REFUND_FAILED(6, "退款失败"),
+
+    /**
+     * 已完成
+     */
+    COMPLETE(7, "已完成");
+
+
+    private final Integer status;
+    // 属性值
+    private final String description;
+    // 构造方法,用于初始化描述信息
+    OrderStatusEnum(Integer status, String description) {
+        this.status = status;
+        this.description = description;
+    }
+    // 提供获取描述信息的方法
+    public static String getDescription(Integer status) {
+        for (OrderStatusEnum orderStatusEnum : OrderStatusEnum.values()) {
+            if (orderStatusEnum.status.equals(status)) {
+                return orderStatusEnum.description;
+            }
+        }
+        return null;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 26 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/PaymentEnum.java

@@ -0,0 +1,26 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 支付枚举
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+public enum PaymentEnum {
+    /** 支付宝 */
+    ALIPAY("alipay"),
+    /** 微信支付 */
+    WECHAT_PAY("wechatPay"),
+    /** 银联支付 */
+    UNION_PAY("unionPay");
+
+    private final String type;
+
+    PaymentEnum(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+}

+ 52 - 0
alien-common-core/src/main/java/shop/alien/util/common/constant/ViolationEnum.java

@@ -0,0 +1,52 @@
+package shop.alien.util.common.constant;
+
+// 举报类型枚举
+public enum ViolationEnum {
+    VIOLATION1(1, "用户违规"),
+    VIOLATION2(2, "色情低俗"),
+    VIOLATION3(3, "违法违规"),
+    VIOLATION4(4, "谩骂嘲讽、煽动对立"),
+    VIOLATION5(5, "涉嫌诈骗"),
+    VIOLATION6(6, "人身攻击"),
+    VIOLATION7(7, "种族歧视"),
+    VIOLATION8(8, "政治敏感"),
+    VIOLATION9(9, "虚假、不实内容"),
+    VIOLATION10(10, "违反公德秩序"),
+    VIOLATION11(11, "危害人身安全"),
+    VIOLATION12(12, "网络暴力"),
+    VIOLATION13(13, "其他原因");
+
+    private final int code;
+    private final String value;
+
+    ViolationEnum(int code, String value) {
+        this.code = code;
+        this.value = value;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    // 通过code获取枚举实例
+    public static ViolationEnum getByCode(int code) {
+        for (ViolationEnum status : ViolationEnum.values()) {
+            if (status.getCode() == code) {
+                return status;
+            }
+        }
+        return null;
+    }
+
+    // 通过code获取value
+    public static String getValueByCode(int code) {
+        ViolationEnum status = getByCode(code);
+        return status != null ? status.getValue() : null;
+    }
+}
+
+

+ 178 - 0
alien-common-core/src/main/java/shop/alien/util/common/generator/CodeGenerator.java

@@ -0,0 +1,178 @@
+package shop.alien.util.common.generator;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.generator.AutoGenerator;
+import com.baomidou.mybatisplus.generator.InjectionConfig;
+import com.baomidou.mybatisplus.generator.config.*;
+import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
+import com.baomidou.mybatisplus.generator.config.po.TableFill;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
+import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
+import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * 代码生成器
+ */
+public class CodeGenerator {
+
+    /**
+     * <p>
+     * 读取控制台内容
+     * </p>
+     */
+    public static String scanner(String tip) {
+        Scanner scanner = new Scanner(System.in);
+        StringBuilder help = new StringBuilder();
+        help.append("请输入" + tip + ":");
+        System.out.println(help.toString());
+        if (scanner.hasNext()) {
+            String ipt = scanner.next();
+            if (StringUtils.isNotEmpty(ipt)) {
+                return ipt;
+            }
+        }
+        throw new MybatisPlusException("请输入正确的" + tip + "!");
+    }
+
+    /**
+     * 自动生成代码
+     */
+    public static void main(String[] args) {
+        // 代码生成器
+        AutoGenerator mpg = new AutoGenerator();
+        // TODO 全局配置
+        GlobalConfig gc = new GlobalConfig();
+        final String projectPath = System.getProperty("user.dir");
+        // 生成文件的输出目录【默认 D 盘根目录】
+        gc.setOutputDir(projectPath + "/src/main/java");
+        // 作者
+        gc.setAuthor("ssk");
+        // 是否打开输出目录
+        gc.setOpen(false);
+        // controller 命名方式,注意 %s 会自动填充表实体属性
+        gc.setControllerName("%sController");
+        // service 命名方式
+        gc.setServiceName("%sService");
+        // serviceImpl 命名方式
+        gc.setServiceImplName("%sServiceImpl");
+        // mapper 命名方式
+        gc.setMapperName("%sMapper");
+        // xml 命名方式
+        gc.setXmlName("%sMapper");
+        // 开启 swagger2 模式
+        gc.setSwagger2(true);
+        // 是否覆盖已有文件
+        gc.setFileOverride(true);
+        // 是否开启 ActiveRecord 模式
+        gc.setActiveRecord(true);
+        // 是否在xml中添加二级缓存配置
+        gc.setEnableCache(false);
+        // 是否开启 BaseResultMap
+        gc.setBaseResultMap(true);
+        // XML columList
+        gc.setBaseColumnList(true);
+        // 全局 相关配置
+        mpg.setGlobalConfig(gc);
+
+        // TODO 数据源配置
+        DataSourceConfig dsc = new MyDataSourceConfig();
+        dsc.setUrl("jdbc:mysql://8.152.195.41:3306/alien?serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false");
+        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
+        dsc.setUsername("root");
+        dsc.setPassword("wady@1213");
+        dsc.setDbType(DbType.MYSQL);
+
+        // 自定义数据库表字段类型转换【可选】
+        dsc.setTypeConvert(new MySqlTypeConvert() {
+            @Override
+            public DbColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
+                // bigint转换成Long
+                if (fieldType.toLowerCase().contains("bigint")) {
+                    return DbColumnType.LONG;
+                }
+                if (fieldType.toLowerCase().contains("datetime")) {
+                    return DbColumnType.DATE;
+                }
+                return (DbColumnType) super.processTypeConvert(globalConfig, fieldType);
+            }
+        });
+        mpg.setDataSource(dsc);
+        // TODO 包配置
+        PackageConfig pc = new PackageConfig();
+        // 父包名。如果为空,将下面子包名必须写全部, 否则就只需写子包名
+        pc.setParent("shop.alien.store");
+        // Entity包名
+        pc.setEntity("entity");
+        // Service包名
+        pc.setService("service");
+        // Service Impl包名
+        pc.setServiceImpl("service.impl");
+        mpg.setPackageInfo(pc);
+
+        // TODO 自定义配置
+        InjectionConfig cfg = new InjectionConfig() {
+            @Override
+            public void initMap() {
+                // to do nothing
+            }
+        };
+        // 输出文件配置
+        List<FileOutConfig> focList = new ArrayList<>();
+        focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
+            @Override
+            public String outputFile(TableInfo tableInfo) {
+                // 自定义输入文件名称
+                return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper.xml";
+            }
+        });
+
+        // 自定义输出文件
+        cfg.setFileOutConfigList(focList);
+        mpg.setCfg(cfg);
+        mpg.setTemplate(new TemplateConfig().setXml(null));
+
+        // TODO 策略配置
+        StrategyConfig strategy = new StrategyConfig();
+        strategy.setRestControllerStyle(true);
+        // 数据库表映射到实体的命名策略,驼峰原则
+        strategy.setNaming(NamingStrategy.underline_to_camel);
+        // 字数据库表字段映射到实体的命名策略,驼峰原则
+        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
+        // 实体是否生成 serialVersionUID
+        strategy.setEntitySerialVersionUID(true);
+        // 是否生成实体时,生成字段注解
+        strategy.setEntityTableFieldAnnotationEnable(true);
+        // 使用lombok
+        strategy.setEntityLombokModel(true);
+        // 设置逻辑删除键
+        strategy.setLogicDeleteFieldName("delete_flag");
+        // TODO 指定生成的bean的数据库表名
+        strategy.setInclude("store_group_package");
+//        strategy.setTablePrefix("T_");
+        // 驼峰转连字符
+        strategy.setControllerMappingHyphenStyle(true);
+        List<TableFill> list = new ArrayList<>();
+        TableFill createTime = new TableFill("created_time", FieldFill.INSERT);
+        TableFill updateTime = new TableFill("updated_time", FieldFill.INSERT_UPDATE);
+        TableFill createdUserId = new TableFill("created_user_id", FieldFill.INSERT);
+        TableFill updatedUserId = new TableFill("updated_user_id", FieldFill.INSERT_UPDATE);
+        list.add(createTime);
+        list.add(updateTime);
+        list.add(createdUserId);
+        list.add(updatedUserId);
+        strategy.setTableFillList(list);
+        mpg.setStrategy(strategy);
+        // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
+        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
+        mpg.execute();
+    }
+}
+

+ 24 - 0
alien-common-core/src/main/java/shop/alien/util/common/generator/MyDMQuery.java

@@ -0,0 +1,24 @@
+package shop.alien.util.common.generator;
+
+import com.baomidou.mybatisplus.generator.config.querys.DMQuery;
+
+/**
+ * @Author: liwanshan
+ * @Date: 2021/10/15/14:27
+ * @Description:
+ */
+public class MyDMQuery extends DMQuery {
+    private int flag = 1;
+    @Override
+    public String tableName() {
+        if (flag == 1) {
+            flag++;
+            // 有歧义的列名[TABLE_NAME] 解决方案
+            return "T1.TABLE_NAME";
+        } else {
+            // 列无效 解决方案
+            return "TABLE_NAME";
+        }
+    }
+}
+

+ 51 - 0
alien-common-core/src/main/java/shop/alien/util/common/generator/MyDataSourceConfig.java

@@ -0,0 +1,51 @@
+package shop.alien.util.common.generator;
+
+import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
+import com.baomidou.mybatisplus.generator.config.IDbQuery;
+import com.baomidou.mybatisplus.generator.config.querys.*;
+
+/**
+ * @Author: liwanshan
+ * @Date: 2021/10/15/14:27
+ * @Description:
+ */
+public class MyDataSourceConfig extends DataSourceConfig {
+
+    @Override
+    public IDbQuery getDbQuery() {
+        switch (this.getDbType()) {
+            case ORACLE:
+                setDbQuery(new OracleQuery());
+                break;
+            case SQL_SERVER:
+                setDbQuery(new SqlServerQuery());
+                break;
+            case POSTGRE_SQL:
+                setDbQuery(new PostgreSqlQuery());
+                break;
+            case DB2:
+                setDbQuery(new DB2Query());
+                break;
+            case MARIADB:
+                setDbQuery(new MariadbQuery());
+                break;
+            case H2:
+                setDbQuery(new H2Query());
+                break;
+            case SQLITE:
+                setDbQuery(new SqliteQuery());
+                break;
+            case DM:
+                // 重新指定自定义的DMQuery
+                setDbQuery(new MyDMQuery());
+                break;
+//            case KINGBASE_ES:
+//                setDbQuery(new KingbaseESQuery());
+//                break;
+            default:
+                setDbQuery(new MySqlQuery());
+        }
+        return super.getDbQuery();
+    }
+}
+

+ 162 - 0
alien-common-core/src/main/java/shop/alien/util/common/netease/ImageCheckUtil.java

@@ -0,0 +1,162 @@
+package shop.alien.util.common.netease;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.http.Consts;
+import org.apache.http.client.HttpClient;
+import shop.alien.util.common.netease.util.HttpClient4Utils;
+import shop.alien.util.common.netease.util.SignatureUtils;
+import shop.alien.util.common.netease.util.Utils;
+import shop.alien.util.common.netease.util.HttpClient4Utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/4/15 16:02
+ */
+public class ImageCheckUtil {
+
+    /**
+     * 产品密钥ID,产品标识
+     */
+    private final static String SECRETID = "3d5df66745bfcce3677b3750120b53c6";
+    /**
+     * 产品私有密钥,服务端生成签名信息使用,请严格保管,避免泄露
+     */
+    private final static String SECRETKEY = "4e8b4c4bbc063e4d9224b0ada5da40ab";
+    /**
+     * 业务ID,易盾根据产品业务特点分配
+     */
+    private final static String BUSINESSID = "4662902327fca4bf62efe73ea3fcfb12";
+    /**
+     * 易盾反垃圾云服务图片在线检测接口地址
+     */
+    private static String API_URL;
+    /**
+     * 实例化HttpClient,发送http请求使用,可根据需要自行调参
+     */
+    private static HttpClient httpClient = HttpClient4Utils.createHttpClient(100, 20, 10000, 2000, 2000);
+
+    /**
+     * 检测图片内容
+     *
+     * @param content   图片
+     * @param imageType 1:链接, 2:base64
+     * @return map
+     */
+    public static Map<String, String> check(String content, Integer imageType) {
+        Map<String, String> resultMap = new HashMap<>();
+        Map<String, String> params = new HashMap<>();
+        // 1.设置公共参数
+        params.put("secretId", SECRETID);
+        params.put("businessId", BUSINESSID);
+        params.put("version", "v5.1");
+        params.put("timestamp", String.valueOf(System.currentTimeMillis()));
+        params.put("nonce", String.valueOf(new Random().nextInt()));
+        // MD5, SM3, SHA1, SHA256
+        params.put("signatureMethod", "MD5");
+        // 2.设置私有参数
+        JsonArray jsonArray = new JsonArray();
+
+        if (imageType == 1) {
+            //链接
+            API_URL = "https://as.dun.163.com/v5/image/check";
+            JsonObject image1 = new JsonObject();
+            image1.addProperty("name", content);
+            image1.addProperty("type", 1);
+            image1.addProperty("data", content);
+            jsonArray.add(image1);
+        } else {
+            //base64
+            API_URL = "https://as.dun.163.com/v5/image/base64Check";
+            JsonObject image2 = new JsonObject();
+            String uuid = UUID.randomUUID().toString();
+            image2.addProperty("name", "{\"imageId\": " + uuid + ", \"contentId\": " + uuid + "}");
+            image2.addProperty("type", 2);
+            image2.addProperty("data", content);
+            jsonArray.add(image2);
+        }
+
+        params.put("images", jsonArray.toString());
+
+        // 预处理参数
+        params = Utils.pretreatmentParams(params);
+        // 3.生成签名信息
+        String signature = SignatureUtils.genSignature(SECRETKEY, params);
+        params.put("signature", signature);
+
+        // 4.发送HTTP请求,这里使用的是HttpClient工具包,产品可自行选择自己熟悉的工具包发送请求
+        String response = HttpClient4Utils.sendPost(httpClient, API_URL, params, Consts.UTF_8);
+
+        // 5.解析接口返回值
+        JsonObject resultObject = new JsonParser().parse(response).getAsJsonObject();
+        int code = resultObject.get("code").getAsInt();
+        String msg = resultObject.get("msg").getAsString();
+        if (code == 200) {
+            // 图片反垃圾结果
+            JsonArray resultArray = resultObject.getAsJsonArray("result");
+            for (JsonElement jsonElement : resultArray) {
+                JsonObject jObject = jsonElement.getAsJsonObject();
+                JsonObject antispam = jObject.get("antispam").getAsJsonObject();
+                int status = antispam.get("status").getAsInt();
+                // 检测状态 0 未开始、1检测中、2检测成功、3检测失败
+                if (2 == status) {
+                    // 图片维度结果
+                    //建议动作,0:通过,1:嫌疑,2:不通过
+                    int suggestion = antispam.get("suggestion").getAsInt();
+                    switch (suggestion) {
+                        case 0:
+                            resultMap.put("suggestion", "通过");
+                            resultMap.put("result", "0");
+                            break;
+                        case 1:
+                            resultMap.put("suggestion", "嫌疑");
+                            resultMap.put("result", "1");
+                            break;
+                        case 2:
+                            resultMap.put("suggestion", "不通过");
+                            resultMap.put("result", "1");
+                            break;
+                        default:
+                            break;
+                    }
+                } else {
+                    resultMap = null;
+                }
+            }
+        } else {
+            resultMap = null;
+        }
+        return resultMap;
+    }
+
+    /**
+     * file转换为base64
+     * 注意:这里转换为base64后,是不包含文件head头的
+     */
+    public static String fileToBase64(File file) {
+        Base64.Encoder base64 = Base64.getEncoder();
+        String base64Str = null;
+        try (FileInputStream fis = new FileInputStream(file);
+             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            byte[] b = new byte[1024];
+            int n;
+            while ((n = fis.read(b)) != -1) {
+                bos.write(b, 0, n);
+            }
+            base64Str = base64.encodeToString(bos.toByteArray());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return base64Str;
+    }
+
+}

+ 136 - 0
alien-common-core/src/main/java/shop/alien/util/common/netease/TextCheckUtil.java

@@ -0,0 +1,136 @@
+package shop.alien.util.common.netease;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.http.Consts;
+import org.apache.http.client.HttpClient;
+import shop.alien.util.common.netease.util.HttpClient4Utils;
+import shop.alien.util.common.netease.util.SignatureUtils;
+import shop.alien.util.common.netease.util.Utils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * 为本内容检测
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/4/15 15:24
+ */
+public class TextCheckUtil {
+
+    /**
+     * 产品密钥ID,产品标识
+     */
+    private final static String SECRETID = "3d5df66745bfcce3677b3750120b53c6";
+    /**
+     * 产品私有密钥,服务端生成签名信息使用,请严格保管,避免泄露
+     */
+    private final static String SECRETKEY = "4e8b4c4bbc063e4d9224b0ada5da40ab";
+    /**
+     * 业务ID,易盾根据产品业务特点分配
+     */
+    private final static String BUSINESSID = "5c577cdd30ae1cc9d4cb7968d0e5b7f9";
+    /**
+     * 易盾反垃圾云服务文本在线检测接口地址
+     */
+    private final static String API_URL = "https://as.dun.163.com/v5/text/check";
+    /**
+     * 实例化HttpClient,发送http请求使用,可根据需要自行调参
+     */
+    private static HttpClient httpClient = HttpClient4Utils.createHttpClient(100, 20, 2000, 2000, 2000);
+
+    /**
+     * 检测文本内容
+     *
+     * @param content 内容
+     * @return map
+     */
+    public static Map<String, String> check(String content) {
+        Map<String, String> resultMap = new HashMap<>();
+        Map<String, String> params = new HashMap<>();
+        // 1.设置公共参数
+        params.put("secretId", SECRETID);
+        params.put("businessId", BUSINESSID);
+        params.put("version", "v5");
+        params.put("timestamp", String.valueOf(System.currentTimeMillis()));
+        params.put("nonce", String.valueOf(new Random().nextInt()));
+        // MD5, SM3, SHA1, SHA256
+        params.put("signatureMethod", "MD5");
+
+        // 2.设置私有参数
+        params.put("dataId", UUID.randomUUID().toString());
+        params.put("content", content);
+
+        // 预处理参数
+        params = Utils.pretreatmentParams(params);
+        // 3.生成签名信息
+        String signature = SignatureUtils.genSignature(SECRETKEY, params);
+        params.put("signature", signature);
+
+        // 4.发送HTTP请求,这里使用的是HttpClient工具包,产品可自行选择自己熟悉的工具包发送请求
+        String response = HttpClient4Utils.sendPost(httpClient, API_URL, params, Consts.UTF_8);
+
+        // 5.解析接口返回值
+        JsonObject jObject = new JsonParser().parse(response).getAsJsonObject();
+        int code = jObject.get("code").getAsInt();
+        String msg = jObject.get("msg").getAsString();
+        if (code == 200) {
+            if (jObject.has("result")) {
+                JsonObject resultObject = jObject.getAsJsonObject("result");
+                // 内容安全结果
+                if (resultObject.has("antispam")) {
+                    JsonObject antispam = resultObject.getAsJsonObject("antispam");
+                    if (antispam != null) {
+                        //建议动作,0:通过,1:嫌疑,2:不通过
+                        int suggestion = antispam.get("suggestion").getAsInt();
+                        switch (suggestion) {
+                            case 0:
+                                resultMap.put("suggestion", "通过");
+                                resultMap.put("result", "0");
+                                break;
+                            case 1:
+                                resultMap.put("suggestion", "嫌疑");
+                                resultMap.put("result", "1");
+                                break;
+                            case 2:
+                                resultMap.put("suggestion", "不通过");
+                                resultMap.put("result", "1");
+                                break;
+                        }
+                        //结果类型,1:机器结果,2:人审结果
+                        int resultType = antispam.get("resultType").getAsInt();
+                        switch (resultType) {
+                            case 1:
+                                resultMap.put("resultType", "机器结果");
+                                break;
+                            case 2:
+                                resultMap.put("resultType", "人审结果");
+                                break;
+                        }
+                        //审核模式,0:纯机审,1:机审+部分人审,2:机审+全量人审
+                        int censorType = antispam.get("censorType").getAsInt();
+                        switch (censorType) {
+                            case 0:
+                                resultMap.put("censorType", "纯机审");
+                                break;
+                            case 1:
+                                resultMap.put("censorType", "机审+部分人审");
+                                break;
+                            case 2:
+                                resultMap.put("censorType", "机审+全量人审");
+                                break;
+                        }
+                    }
+                }
+            }
+        } else {
+            resultMap = null;
+        }
+        return resultMap;
+    }
+
+}

+ 150 - 0
alien-common-core/src/main/java/shop/alien/util/common/netease/util/HttpClient4Utils.java

@@ -0,0 +1,150 @@
+/*
+ * @(#) HttpClientUtils.java 2016年2月3日
+ * 
+ * Copyright 2010 NetEase.com, Inc. All rights reserved.
+ */
+package shop.alien.util.common.netease.util;
+
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.*;
+
+/**
+ * HttpClient工具类
+ * 
+ * @author hzgaomin
+ * @version 2016年2月3日
+ */
+public class HttpClient4Utils {
+    /**
+     * 实例化HttpClient
+     * 
+     * @param maxTotal
+     * @param maxPerRoute
+     * @param socketTimeout
+     * @param connectTimeout
+     * @param connectionRequestTimeout
+     * @return
+     */
+    public static HttpClient createHttpClient(int maxTotal, int maxPerRoute, int socketTimeout, int connectTimeout,
+            int connectionRequestTimeout) {
+        RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
+                .setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();
+        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
+        cm.setMaxTotal(maxTotal);
+        cm.setDefaultMaxPerRoute(maxPerRoute);
+        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm)
+                .setDefaultRequestConfig(defaultRequestConfig).build();
+        return httpClient;
+    }
+
+    /**
+     * 发送post请求
+     * 
+     * @param httpClient
+     * @param url 请求地址
+     * @param params 请求参数
+     * @param encoding 编码
+     * @return
+     */
+    public static String sendPost(HttpClient httpClient, String url, Map<String, String> params, Charset encoding) {
+        String resp = "";
+        HttpPost httpPost = new HttpPost(url);
+        if (params != null && params.size() > 0) {
+            List<NameValuePair> formParams = new ArrayList<NameValuePair>();
+            Iterator<Map.Entry<String, String>> itr = params.entrySet().iterator();
+            while (itr.hasNext()) {
+                Map.Entry<String, String> entry = itr.next();
+                formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+            }
+            UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(formParams, encoding);
+            httpPost.setEntity(postEntity);
+        }
+        CloseableHttpResponse response = null;
+        try {
+            response = (CloseableHttpResponse) httpClient.execute(httpPost);
+            resp = EntityUtils.toString(response.getEntity(), encoding);
+        } catch (Exception e) {
+            // log
+            e.printStackTrace();
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    // log
+                    e.printStackTrace();
+                }
+            }
+        }
+        return resp;
+    }
+
+    public static String sendGetByHeader(HttpClient httpClient, String url, Map<String, String> params,
+            Map<String, String> headers) throws Exception {
+        HttpGet httpGet = null;
+        if (MapUtils.isNotEmpty(params)) {
+            final List<NameValuePair> qparams = new LinkedList<NameValuePair>();
+            for (Map.Entry<String, String> param : params.entrySet()) {
+                qparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
+            }
+            httpGet = new HttpGet(url + "?" + URLEncodedUtils.format(qparams, "UTF-8"));
+        } else {
+            httpGet = new HttpGet(url);
+        }
+        if (headers != null) {
+            for (String key : headers.keySet()) {
+                httpGet.addHeader(key, headers.get(key));
+            }
+        }
+        CloseableHttpResponse response = null;
+        try {
+            response = (CloseableHttpResponse) httpClient.execute(httpGet);
+            int status = response.getStatusLine().getStatusCode();
+            if (status != HttpStatus.SC_OK) {
+                // 返回状态错误,直接抛异常,记录时精简一下log输出
+                throw new IllegalStateException(String.format("错误的接口返回状态值, status=%d, response=%s", status,
+                        StringUtils.abbreviate(EntityUtils.toString(response.getEntity()), 1024)));
+            }
+            // EntityUtils is not recommended
+            return EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // 该方法会释放连接。。。
+        } catch (Exception e) {
+            String msg = String.format("http接口调用出错url=%s, params=%s", url, params);
+            throw new Exception(msg, e);
+        } finally {
+            closeQuietly(response);
+        }
+    }
+
+    /**
+     * 关闭response
+     *
+     * @param response
+     */
+    public static void closeQuietly(CloseableHttpResponse response) {
+        if (response != null) {
+            try {
+                response.close();
+            } catch (IOException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+}

+ 24 - 0
alien-common-core/src/main/java/shop/alien/util/common/netease/util/SignatureMethodEnum.java

@@ -0,0 +1,24 @@
+package shop.alien.util.common.netease.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 签名方式
+ *
+ * @author yd-dev
+ * @version 2020-09-15
+ */
+public enum SignatureMethodEnum {
+    MD5,
+    SM3,
+    SHA1,
+    SHA256;
+    public static boolean isValid(String signatureMethod) {
+        try {
+            SignatureMethodEnum signatureMethodEnum = SignatureMethodEnum.valueOf(StringUtils.upperCase(signatureMethod));
+            return signatureMethodEnum != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

+ 244 - 0
alien-common-core/src/main/java/shop/alien/util/common/netease/util/SignatureUtils.java

@@ -0,0 +1,244 @@
+/*
+ * @(#) SignatureUtils.java 2016年2月2日
+ *
+ * Copyright 2010 NetEase.com, Inc. All rights reserved.
+ */
+package shop.alien.util.common.netease.util;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.crypto.digests.SM3Digest;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 生成及验证签名信息工具类
+ *
+ * @author hzgaomin
+ * @version 2016年2月2日
+ */
+public class SignatureUtils {
+
+    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
+            'f'};
+
+    /**
+     * 通过HttpServletRequest做签名验证
+     *
+     * @param request
+     * @param secretid
+     * @param secretkey
+     * @param businessid
+     * @return
+     */
+    public static boolean verifySignature(HttpServletRequest request, String secretid, String secretkey,
+            String businessid)
+            throws UnsupportedEncodingException {
+        String secretId = request.getParameter("secretId");
+        String businessId = request.getParameter("businessId");
+        String signature = request.getParameter("signature");
+        if (StringUtils.isEmpty(secretId) || StringUtils.isEmpty(signature)) {
+            // 签名参数为空,直接返回失败
+            return false;
+        }
+        Map<String, String> params = new HashMap<>();
+        for (String paramName : request.getParameterMap().keySet()) {
+            if (!"signature".equals(paramName)) {
+                params.put(paramName, request.getParameter(paramName));
+            }
+        }
+        // SECRETKEY:产品私有密钥 SECRETID:产品密钥ID BUSINESSID:业务ID,开通服务时,易盾会提供相关密钥信息
+        String serverSignature = genSignature(secretkey, request.getParameter("signatureMethod"), params);
+        // 客户根据需要确认是否鉴权是否要精确到业务维度,不需要则去掉businessid.equals(businessId)
+        return signature.equals(serverSignature) && secretid.equals(secretId) && businessid.equals(businessId);
+    }
+
+    /**
+     * 签名验证 解决方案没有businessId
+     *
+     * @param request
+     * @param secretid
+     * @param secretkey
+     * @return
+     */
+    public static boolean verifySignature(HttpServletRequest request, String secretid, String secretkey)
+            throws UnsupportedEncodingException {
+        String secretId = request.getParameter("secretId");
+        String signature = request.getParameter("signature");
+        if (StringUtils.isEmpty(secretId) || StringUtils.isEmpty(signature)) {
+            // 签名参数为空,直接返回失败
+            return false;
+        }
+        Map<String, String> params = new HashMap<>();
+        for (String paramName : request.getParameterMap().keySet()) {
+            if (!"signature".equals(paramName)) {
+                params.put(paramName, request.getParameter(paramName));
+            }
+        }
+        // SECRETKEY:产品私有密钥 SECRETID:产品密钥ID 开通服务时,易盾会提供相关密钥信息
+        String serverSignature = genSignature(secretkey, request.getParameter("signatureMethod"), params);
+        return signature.equals(serverSignature) && secretid.equals(secretId);
+    }
+
+    /**
+     * 验证签名是否匹配
+     *
+     * @param requestParams 签名的参数
+     * @param secretKey 签名的key
+     * @return 是否匹配
+     */
+    public static boolean verifySignature(Map<String, String[]> requestParams, String secretKey) {
+        if (StringUtils.isBlank(secretKey) || requestParams == null) {
+            return false;
+        }
+
+        Map<String, String> params = new HashMap<>();
+        String signature = null;
+        for (String paramName : requestParams.keySet()) {
+            String[] value = requestParams.get(paramName);
+            if (value == null || value.length == 0) {
+                continue;
+            }
+
+            if ("signature".equals(paramName)) {
+                signature = value[0];
+            } else {
+                params.put(paramName, value[0]);
+            }
+        }
+        if (StringUtils.isBlank(signature)) {
+            // 签名为空,直接返回失败
+            return false;
+        }
+        String generatedSignature = genSignature(secretKey, params);
+        if (StringUtils.isBlank(generatedSignature)) {
+            return false;
+        }
+
+        return signature.equals(generatedSignature);
+    }
+
+    /**
+     * 默认使用md5方式
+     *
+     * @param secretKey
+     * @param params
+     * @return
+     */
+    public static String genSignature(String secretKey, Map<String, String> params) {
+        return genSignature(secretKey, params.get("signatureMethod"), params);
+    }
+
+    public static String genOpenApiSignature(String secretKey, Map<String, String> params, Map<String, String> header) {
+        // 1. 参数名按照ASCII码表升序排序
+        String[] paramNames = params.keySet().toArray(new String[0]);
+        Arrays.sort(paramNames);
+
+        String timestamp = header.get("X-YD-TIMESTAMP");
+        String nonce = header.get("X-YD-NONCE");
+
+        // 2. 按照排序拼接参数名与参数值
+        StringBuilder paramBuffer = new StringBuilder();
+        for (String paramName : paramNames) {
+            String paramValue = params.get(paramName);
+
+            paramBuffer
+                    .append(paramName)
+                    .append(paramValue == null ? "" : paramValue);
+        }
+
+        // 3. 将secretKey,nonce,timestamp拼接到最后
+        paramBuffer.append(secretKey).append(nonce).append(timestamp);
+
+        try {
+            return DigestUtils.sha1Hex(paramBuffer.toString().getBytes("UTF-8"));
+        } catch (Exception e) {
+            System.out.println("[ERROR] not supposed to happen: " + e.getMessage());
+        }
+        return "";
+    }
+
+    /**
+     * 通用签名方式
+     * 
+     * @param secretKey
+     * @param signatureMethod
+     * @param params
+     * @return
+     */
+    public static String genSignature(String secretKey, String signatureMethod, Map<String, String> params) {
+        // 1. 参数名按照ASCII码表升序排序
+        String[] keys = params.keySet().toArray(new String[0]);
+        Arrays.sort(keys);
+        // 2. 按照排序拼接参数名与参数值
+        StringBuilder sb = new StringBuilder();
+        for (String key : keys) {
+            sb.append(key).append(params.get(key));
+        }
+        // 3. 将secretKey拼接到最后
+        sb.append(secretKey);
+        try {
+            // 默认使用MD5
+            SignatureMethodEnum signatureMethodEnum = StringUtils.isBlank(signatureMethod) ? SignatureMethodEnum.MD5
+                    : SignatureMethodEnum.valueOf(StringUtils.upperCase(signatureMethod));
+            switch (signatureMethodEnum) {
+                case MD5:
+                    return DigestUtils.md5Hex(sb.toString().getBytes("UTF-8"));
+                case SHA1:
+                    return DigestUtils.sha1Hex(sb.toString().getBytes("UTF-8"));
+                case SHA256:
+                    return DigestUtils.sha256Hex(sb.toString().getBytes("UTF-8"));
+                case SM3:
+                    return sm3DigestHex(sb.toString().getBytes("UTF-8"));
+                default:
+                    System.out.println("[ERROR] unsupported signature method: " + signatureMethod);
+                    return null;
+            }
+        } catch (Exception e) {
+            System.out.println("[ERROR] not supposed to happen: " + e.getMessage());
+        }
+        return null;
+    }
+
+    public static String sm3DigestHex(byte[] srcData) {
+        SM3Digest sm3Digest = new SM3Digest();
+        sm3Digest.update(srcData, 0, srcData.length);
+        byte[] hash = new byte[sm3Digest.getDigestSize()];
+        sm3Digest.doFinal(hash, 0);
+        return Hex.encodeHexString(hash);
+    }
+
+    public static String getCheckSum(String key, String secret, String nonce, String currentTime) {
+        StringBuilder stringBuffer = new StringBuilder(key).append(secret).append(nonce).append(currentTime);
+        return encode("sha1", stringBuffer.toString());
+    }
+
+    private static String encode(String algorithm, String value) {
+        String result = null;
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
+            messageDigest.update(value.getBytes("UTF-8"));
+            result = getFormattedText(messageDigest.digest());
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+        return result;
+    }
+
+    private static String getFormattedText(byte[] bytes) {
+        int length = bytes.length;
+        StringBuilder buf = new StringBuilder(length * 2);
+        for (int j = 0; j < length; j++) {
+            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
+            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
+        }
+        return buf.toString();
+    }
+
+}

+ 82 - 0
alien-common-core/src/main/java/shop/alien/util/common/netease/util/Utils.java

@@ -0,0 +1,82 @@
+/*
+ * @(#) Utils.java 2020-09-28
+ *
+ * Copyright 2020 NetEase.com, Inc. All rights reserved.
+ */
+
+package shop.alien.util.common.netease.util;
+
+import com.google.gson.JsonObject;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * @author yd-dev
+ * @version 2020-09-28
+ */
+public class Utils {
+
+    public static final String SECRET_ID = "secretId";
+    public static final String BUSINESS_ID = "businessId";
+    public static final String VERSION = "version";
+    public static final String TIMESTAMP = "timestamp";
+    public static final String NONCE = "nonce";
+    public static final String SIGNATURE_METHOD = "signatureMethod";
+    public static final String SIGNATURE = "signature";
+
+    public static Map<String, String> getCommonParams(String secretId, String version, String signatureMethod) {
+        return getCommonParams(secretId, "", version, signatureMethod);
+    }
+
+    public static Map<String, String> getCommonParams(String secretId, String businessId, String version,
+                                                      String signatureMethod) {
+        Map<String, String> params = new HashMap<>();
+        params.put(SECRET_ID, secretId);
+        if (StringUtils.isNotBlank(businessId)) {
+            params.put(BUSINESS_ID, businessId);
+        }
+        params.put(VERSION, version);
+        params.put(TIMESTAMP, String.valueOf(System.currentTimeMillis()));
+        params.put(NONCE, String.valueOf(new Random().nextInt()));
+        params.put(SIGNATURE_METHOD, signatureMethod);
+        return params;
+    }
+
+    public static void sign(Map<String, String> params, String secretKey) {
+        params.remove(SIGNATURE);
+        String signature = SignatureUtils.genSignature(secretKey, params);
+        params.put(SIGNATURE, signature);
+    }
+
+    public static String getStringProperty(JsonObject obj, String memberName) {
+        return obj.has(memberName) ? obj.get(memberName).getAsString() : "无";
+    }
+
+    public static Integer getIntegerProperty(JsonObject obj, String memberName) {
+        return obj.has(memberName) ? obj.get(memberName).getAsInt() : null;
+    }
+
+    public static Long getLongProperty(JsonObject obj, String memberName) {
+        return obj.has(memberName) ? obj.get(memberName).getAsLong() : null;
+    }
+
+    /**
+     * 预处理参数
+     *
+     * @param params
+     * @return
+     */
+    public static Map<String, String> pretreatmentParams(Map<String, String> params) {
+        Map<String, String> result = new HashMap<>(params.size());
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            // 过滤空值与原始参数中的signature,避免签名串与实际请求参数不一致导致签名校验失败
+            if (entry.getValue() != null && !"signature".equals(entry.getKey())) {
+                result.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return result;
+    }
+}

+ 44 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/DeepseekClient.java

@@ -0,0 +1,44 @@
+package shop.alien.util.common.safe;
+
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import okhttp3.*;
+
+import java.io.IOException;
+
+public class DeepseekClient {
+    private final String apiKey;
+    private final OkHttpClient client;
+
+    public DeepseekClient(String apiKey) {
+        this.apiKey = apiKey;
+        this.client = new OkHttpClient();
+    }
+
+    public String generateText(String prompt) throws IOException {
+        // 创建请求体
+        JSONObject messages = new JSONObject();
+        messages.put("role", "user");
+        messages.put("content", prompt);
+
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("model", "deepseek-chat");
+        requestBody.put("messages", JSON.parseArray(messages.toJSONString(), Object.class));
+        requestBody.put("stream", false);
+
+        Request request = new Request.Builder()
+                .url("https://api.deepseek.com/chat/completions")
+                .post(RequestBody.create(MediaType.get("application/json"), requestBody.toJSONString()))
+                .addHeader("Content-Type", "application/json")
+                .addHeader("Authorization", "Bearer " + apiKey)
+                .build();
+
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
+
+            JSONObject responseBody = JSON.parseObject(response.body().string());
+            return responseBody.getJSONArray("choices").getJSONObject(0).getJSONObject("message").getString("content");
+        }
+    }
+}

+ 18 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/ImageModerationResultVO.java

@@ -0,0 +1,18 @@
+package shop.alien.util.common.safe;
+
+import lombok.Data;
+
+/**
+ * 图片审核结果返回值对象
+ */
+@Data
+public class ImageModerationResultVO {
+    // 风险等级
+    private String riskLevel;
+    // 细分标签
+    private String labels;
+    // 细分标签描述
+    private String descriptions;
+    // 生成的商品文案
+    private String generatedText;
+}

+ 249 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/ImageModerationUtil.java

@@ -0,0 +1,249 @@
+package shop.alien.util.common.safe;
+
+import com.alibaba.fastjson.JSON;
+import com.aliyun.green20220302.Client;
+import com.aliyun.green20220302.models.*;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+@Slf4j
+@Component
+@RefreshScope
+public class ImageModerationUtil {
+
+    @Value("${ali.yundun.accessKeyID}")
+    private String accessKeyId;
+
+    @Value("${ali.yundun.secret}")
+    private String accessKeySecret;
+
+    @Value("${ali.yundun.imgEndpoint}")
+    private String ImgEndpoint;
+
+    /**
+     * 创建请求客户端
+     *
+     * @return Client
+     * @throws Exception 创建客户端异常
+     */
+    public Client createClient() throws Exception {
+        Config config = new Config();
+//        config.setAccessKeyId("LTAI5tReZWshffH78oQJPzZG");
+//        config.setAccessKeySecret("Gi30OLYAWunaDbwsv5qqdZ3PzbDyGP");
+        config.setAccessKeyId(accessKeyId);
+        config.setAccessKeySecret(accessKeySecret);
+        // 设置http代理。
+        //config.setHttpProxy("http://10.10.xx.xx:xxxx");
+        // 设置https代理。
+        //config.setHttpsProxy("https://10.10.xx.xx:xxxx");
+        // 接入区域和地址请根据实际情况修改
+        // 接入地址列表:https://help.aliyun.com/document_detail/467828.html?#section-uib-qkw-0c8
+//        config.setEndpoint("green-cip.cn-shanghai.aliyuncs.com");
+        config.setEndpoint(ImgEndpoint);
+        return new Client(config);
+    }
+
+    /**
+     * 提供给service调用的方法
+     *
+     * @param imageUrl    图片地址
+     * @param serviceEnum Service类型
+     * @return 检测结果
+     * @throws Exception 检测异常
+     */
+    public ImageModerationResponse invokeFunction(String imageUrl, String serviceEnum) throws Exception {
+        //注意,此处实例化的client请尽可能重复使用,避免重复建立连接,提升检测性能。
+        Client client = createClient();
+
+        // 创建RuntimeObject实例并设置运行参数
+        RuntimeOptions runtime = new RuntimeOptions();
+
+        // 检测参数构造。
+        Map<String, String> serviceParameters = new HashMap<>();
+        //公网可访问的URL。
+        serviceParameters.put("imageUrl", imageUrl);
+        //待检测数据唯一标识
+        serviceParameters.put("dataId", UUID.randomUUID().toString());
+
+        ImageModerationRequest request = new ImageModerationRequest();
+        // 图片检测service:内容安全控制台图片增强版规则配置的serviceCode,示例:baselineCheck
+        // 支持service请参考:https://help.aliyun.com/document_detail/467826.html?0#p-23b-o19-gff
+        request.setService(serviceEnum);
+        request.setServiceParameters(JSON.toJSONString(serviceParameters));
+
+        ImageModerationResponse response = null;
+        try {
+            response = client.imageModerationWithOptions(request, runtime);
+        } catch (Exception e) {
+            throw new Exception("调用服务失败" + e.getMessage());
+        }
+        return response;
+    }
+
+    /**
+     * 批量图片检测
+     *
+     * @param imageUrl    图片地址
+     * @param serviceEnum Service类型 服务名逗号拼接
+     * @return 检测结果
+     * @throws Exception 检测异常
+     */
+    public ImageBatchModerationResponse invokeBeachFunction(String imageUrl, String serviceEnum) throws Exception {
+        Client client = createClient();
+        RuntimeOptions runtime = new RuntimeOptions();
+        Map<String, String> serviceParameters = new HashMap<>();
+        serviceParameters.put("imageUrl", imageUrl);
+        serviceParameters.put("dataId", UUID.randomUUID().toString());
+        ImageBatchModerationRequest imageBatchModerationRequest = new ImageBatchModerationRequest();
+        imageBatchModerationRequest.setService(serviceEnum);
+        imageBatchModerationRequest.setServiceParameters(JSON.toJSONString(serviceParameters));
+        ImageBatchModerationResponse response = null;
+        try {
+            response = client.imageBatchModerationWithOptions(imageBatchModerationRequest, runtime);
+        } catch (Exception e) {
+            throw new Exception("调用服务失败" + e.getMessage());
+        }
+        return response;
+    }
+
+    /**
+     * 发布商品场景检测
+     * 顺序调用内容治理检测、AIGC图片风险检测、图片万物识别
+     *
+     * @param imageUrl 图片地址
+     * @return 检测结果VO
+     * @throws Exception 检测异常
+     */
+    public ImageModerationResultVO productPublishCheck(String imageUrl,List<String> servicesList) throws Exception {
+        ImageModerationResultVO resultVO = new ImageModerationResultVO();
+        // 批量服务key
+        StringBuilder serviceBatchEnum = new StringBuilder();
+        StringBuilder serviceEnum = new StringBuilder();
+        for (String service : servicesList) {
+            if (isSpecialService(service)) {
+                serviceBatchEnum.append(service).append(",");
+            } else {
+                serviceEnum.append(service).append(",");
+            }
+        }
+        // 若存在特殊服务,则拼接字符串  并调取多service阿里图片审核服务
+        if (serviceBatchEnum.length() > 0) {
+            serviceBatchEnum.setLength(serviceBatchEnum.length() - 1);
+            // 多service图片审核
+            resultVO = getImageModerationResultVO(imageUrl, serviceBatchEnum);
+        }
+        // TODO 现阶段使用的服务都支持多service 图片审核,后续如果有不支持的服务则 融合 使用单service图片审核
+
+
+        return resultVO;
+    }
+
+    private ImageModerationResultVO getImageModerationResultVO(String imageUrl, StringBuilder serviceEnum) throws Exception {
+        // 多service图片审核
+        ImageBatchModerationResponse response = invokeBeachFunction(imageUrl, serviceEnum.toString());
+        ImageModerationResultVO resultVO = new ImageModerationResultVO();
+
+        if (response != null && response.getStatusCode() == 200) {
+            ImageBatchModerationResponseBody body = response.getBody();
+            if (body != null && body.getCode() == 200) {
+                ImageBatchModerationResponseBody.ImageBatchModerationResponseBodyData data = body.getData();
+                if (data != null) {
+                    String riskLevel = data.getRiskLevel();
+                    List<ImageBatchModerationResponseBody.ImageBatchModerationResponseBodyDataResult> results = data.getResult();
+                    StringBuilder labelsBuilder = new StringBuilder();
+                    StringBuilder descriptionsBuilder = new StringBuilder();
+                    // 处理结果 多个服务总敏感程度
+                    if ("high".equals(riskLevel)) {
+                        for (ImageBatchModerationResponseBody.ImageBatchModerationResponseBodyDataResult result : results) {
+                            // 触发多个,如果置信分高于80 则拼接描述
+                            if (result.getConfidence() > 80) {
+                                String label = result.getLabel();
+                                String description = result.getDescription();
+                                // 拼接标签
+                                if (label != null && !label.isEmpty()) {
+                                    labelsBuilder.append(label).append(",");
+                                }
+                                // 拼接描述
+                                if (description != null && !description.isEmpty()) {
+                                    descriptionsBuilder.append(description).append(",");
+                                }
+                            }
+                        }
+                    }
+                    // 移除最后一个逗号
+                    if (labelsBuilder.length() > 0) {
+                        labelsBuilder.setLength(labelsBuilder.length() - 1);
+                    }
+                    if (descriptionsBuilder.length() > 0) {
+                        descriptionsBuilder.setLength(descriptionsBuilder.length() - 1);
+                    }
+                    resultVO.setRiskLevel(riskLevel);
+                    resultVO.setLabels(labelsBuilder.toString());
+                    resultVO.setDescriptions(descriptionsBuilder.toString());
+                }
+            }
+        }
+        return resultVO;
+    }
+
+    /**
+     * 聊一聊场景检测
+     * 顺序调用图片万物识别、内容治理检测
+     *
+     * @param imageUrl 图片地址
+     * @return 检测结果
+     * @throws Exception 检测异常
+     */
+    public ImageModerationResultVO chatCheck(String imageUrl) throws Exception {
+        // 批量图片检测
+        ImageModerationResponse response = invokeFunction(imageUrl, "generalRecognition");
+        ImageModerationResultVO resultVO = new ImageModerationResultVO();
+        ImageModerationResponseBody body = response.body;
+        ImageModerationResponseBody.ImageModerationResponseBodyDataExt dataExt = body.data.ext;
+        // 调用DeepSeek接口生成商品文案
+        String prompt = "根据以下标签生成一个吸引人的商品售卖文案(200字以内): " + dataExt;
+        String generatedText = callDeepSeekAPI(prompt);
+        resultVO.setGeneratedText(generatedText);
+        return resultVO;
+    }
+
+    /**
+     * 调用DeepSeek接口生成商品文案
+     *
+     * @param prompt 提示信息
+     * @return 生成的文案
+     * @throws IOException 调用异常
+     */
+    private String callDeepSeekAPI(String prompt) throws IOException {
+        DeepseekClient client = new DeepseekClient("sk-dd8a6e10972145c9847883791ac9fb41");
+        return client.generateText(prompt);
+    }
+
+    /**
+     * 判断服务是否为特殊服务,若是可以调取阿里多service审核服务,返回多个场景的总审核结果
+     *
+     * @param service 服务名称
+     * @return 是否为特殊服务
+     */
+    public static boolean isSpecialService(String service) {
+        return ImageReviewServiceEnum.BASELINE_CHECK.getService().equals(service)
+                || ImageReviewServiceEnum.BASELINE_CHECK_PRO.getService().equals(service)
+                || ImageReviewServiceEnum.TONALITY_IMPROVE.getService().equals(service)
+                || ImageReviewServiceEnum.AIGC_CHECK.getService().equals(service)
+                || ImageReviewServiceEnum.PROFILE_PHOTO_CHECK.getService().equals(service)
+                || ImageReviewServiceEnum.POST_IMAGE_CHECK.getService().equals(service)
+                || ImageReviewServiceEnum.ADVERTISING_CHECK.getService().equals(service)
+                || ImageReviewServiceEnum.LIVE_STREAM_CHECK.getService().equals(service);
+    }
+}

+ 45 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/ImageReviewServiceEnum.java

@@ -0,0 +1,45 @@
+package shop.alien.util.common.safe;
+
+import lombok.Getter;
+
+/**
+ * 图片审核服务枚举
+ */
+@Getter
+public enum ImageReviewServiceEnum {
+
+    BASELINE_CHECK("baselineCheck", "通用基线检测", "检测图片中是否存在色情、涉政、暴恐、违禁等红线类的违规内容。", "通用场景"),
+    BASELINE_CHECK_PRO("baselineCheck_pro", "通用基线检测_专业版", "在通用基线检测的基础上支持更细粒度的标签返回。", "通用场景"),
+    TONALITY_IMPROVE("tonalityImprove", "内容治理检测", "检测图片中是否存在广告引流、不良引导、辱骂等影响平台秩序、内容调性或影响用户体验的内容。", "通用场景"),
+    PROFILE_PHOTO_CHECK("profilePhotoCheck", "头像图片检测", "检测头像中是否存在违规、不宜传播或者影响平台秩序的内容。建议对头像场景的图片均进行该项检测。", "业务场景"),
+    POST_IMAGE_CHECK("postImageCheck", "帖子评论图片检测", "检测帖子中图片或者评论中图片是否存在违规、不宜传播或者影响平台秩序的内容。建议对贴图评论场景的图片均进行该项检测。", "业务场景"),
+    AIGC_CHECK("aigcCheck", "AIGC图片风险检测", "针对AIGC场景,检测AIGC生成的图片是否存在违规或者不宜传播的内容。建议AIGC生成的图片都进行该项检测。", "AIGC场景"),
+    AIGC_DETECTOR("aigcDetector", "AIGC图片生成判定", "针对各种场景,判断图片是否由AIGC生成。建议需要对图片的来源进行标识时使用。", "AIGC场景"),
+    AIGC_DETECTOR_PRO("aigcDetector_pro", "AIGC图片生成判定_专业版", "在判断图片是否由AIGC生成的基础上,增加判断图片是否疑似合成,是否有PS痕迹的检测能力。", "AIGC场景"),
+    AIGC_VIOLATION_DETECTION("aigcViolationDetection", "AIGC图片侵权检测", "针对AIGC场景,检测AIGC生成的图片是否存在侵权风险。建议AIGC生成的用于素材、宣传类的图片都进行该项检测。", "AIGC场景"),
+    ADVERTISING_CHECK("advertisingCheck", "营销素材检测", "针对有推广含义的图片(包括商品图、详情介绍页、投放营销的素材等)进行专门优化,检测是否有违反广告法以及其他违规或不宜传播的内容。", "业务场景"),
+    LIVE_STREAM_CHECK("liveStreamCheck", "视频\\直播截图检测", "检测视频/直播画面是否存在违规或不宜传播的内容。建议对涉及开放公网访问的视频/直播画面均可截图进行该项检测。", "业务场景"),
+    POST_IMAGE_CHECK_BY_VL("postImageCheckByVL", "大小模型融合图片审核服务", "综合应用图片审核大模型和专家模型能力,能够全方位识别图片中的色情、性感、涉政、暴恐、违禁、宗教、引流广告、不良等违规内容。", "通用场景"),
+    BASELINE_CHECK_BY_VL("baselineCheckByVL", "通用图片审核大模型服务", "基于图片审核场景定制训练的审核大模型,能够识别图片中的涉黄、涉政、暴恐、违禁、不良、辱骂、广告等风险。支持返回大模型的原始结果。", "通用场景"),
+    OSS_BASELINE_CHECK("oss_baselineCheck", "OSS基线检测(OSS普惠版专用)", "适用于OSS检测图片中是否存在违规或不宜传播的内容,包含对黑产类图片识别能力。", "通用场景"),
+    BAILIAN_QUERY_IMAGE_CHECK("bailianQueryImageCheck", "百炼输入图片检测", "百炼图片输入场景专用,检测图片中是否存在色情、性感、涉政、暴恐、违禁、宗教、广告引流、特殊标识、行为、特定物体、不良等违规内容。", "百炼场景"),
+    BAILIAN_RESPONSE_IMAGE_CHECK("bailianResponseImageCheck", "百炼生成图片检测", "百炼图片输出场景专用,检测图片中是否存在色情、性感、涉政、暴恐、违禁、宗教、广告引流、特殊标识、行为、特定物体、不良等违规内容。", "百炼场景"),
+    GENERAL_RECOGNITION("generalRecognition", "图片万物识别", "基于大模型能力,能识别图片中的多种元素并返回元素标签和置信度。", "特殊场景"),
+    IMG_QUERY_SECURITY_CHECK("img_query_security_check", "cts.pictureReviews.ruleConfig.service.img_query_security_check", "cts.pictureReviews.ruleConfig.service.description.img_query_security_check", "--"),
+    IMG_RESPONSE_SECURITY_CHECK("img_response_security_check", "cts.pictureReviews.ruleConfig.service.img_response_security_check", "cts.pictureReviews.ruleConfig.service.description.img_response_security_check", "--"),
+    RISK_DETECTION("riskDetection", "恶意图片检测", "检测图片中隐藏的视频、播放器等风险内容。建议在存储图片和CDN流量发现异常,疑似被黑产攻击时调用。", "特殊场景");
+
+    private final String service;
+    private final String serviceName;
+    private final String serviceDescription;
+    private final String serviceScene;
+
+    ImageReviewServiceEnum(String service, String serviceName, String serviceDescription, String serviceScene) {
+        this.service = service;
+        this.serviceName = serviceName;
+        this.serviceDescription = serviceDescription;
+        this.serviceScene = serviceScene;
+    }
+
+
+}

+ 120 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/ImageUrlDemo.java

@@ -0,0 +1,120 @@
+package shop.alien.util.common.safe;
+import com.alibaba.fastjson.JSON;
+import com.aliyun.green20220302.Client;
+import com.aliyun.green20220302.models.ImageModerationRequest;
+import com.aliyun.green20220302.models.ImageModerationResponse;
+import com.aliyun.green20220302.models.ImageModerationResponseBody;
+import com.aliyun.green20220302.models.ImageModerationResponseBody.ImageModerationResponseBodyData;
+import com.aliyun.green20220302.models.ImageModerationResponseBody.ImageModerationResponseBodyDataResult;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class ImageUrlDemo {
+    /**
+     * 创建请求客户端
+     *
+     * @param accessKeyId
+     * @param accessKeySecret
+     * @param endpoint
+     * @return
+     * @throws Exception
+     */
+    public static Client createClient(String accessKeyId, String accessKeySecret, String endpoint) throws Exception {
+        Config config = new Config();
+        config.setAccessKeyId(accessKeyId);
+        config.setAccessKeySecret(accessKeySecret);
+        // 设置http代理。
+        //config.setHttpProxy("http://10.10.xx.xx:xxxx");
+        // 设置https代理。
+        //config.setHttpsProxy("https://10.10.xx.xx:xxxx");
+        // 接入区域和地址请根据实际情况修改
+        // 接入地址列表:https://help.aliyun.com/document_detail/467828.html?#section-uib-qkw-0c8
+        config.setEndpoint(endpoint);
+        return new Client(config);
+    }
+
+    public static ImageModerationResponse invokeFunction(String accessKeyId, String accessKeySecret, String endpoint) throws Exception {
+        //注意,此处实例化的client请尽可能重复使用,避免重复建立连接,提升检测性能。
+        Client client = createClient(accessKeyId, accessKeySecret, endpoint);
+
+        // 创建RuntimeObject实例并设置运行参数
+        RuntimeOptions runtime = new RuntimeOptions();
+
+        // 检测参数构造。
+        Map<String, String> serviceParameters = new HashMap<>();
+        //公网可访问的URL。
+        serviceParameters.put("imageUrl", "http://devfile.ailien.shop/image/暴恐违禁2367012.jpg");
+        //待检测数据唯一标识
+        serviceParameters.put("dataId", UUID.randomUUID().toString());
+
+        ImageModerationRequest request = new ImageModerationRequest();
+        // 图片检测service:内容安全控制台图片增强版规则配置的serviceCode,示例:baselineCheck
+        // 支持service请参考:https://help.aliyun.com/document_detail/467826.html?0#p-23b-o19-gff
+        request.setService("generalRecognition");
+        request.setServiceParameters(JSON.toJSONString(serviceParameters));
+
+        ImageModerationResponse response = null;
+        try {
+            response = client.imageModerationWithOptions(request, runtime);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return response;
+    }
+
+    public static void main(String[] args) throws Exception {
+        /**
+         * 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
+         * 常见获取环境变量方式:
+         * 方式一:
+         *     获取RAM用户AccessKey ID:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
+         *     获取RAM用户AccessKey Secret:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
+         * 方式二:
+         *     获取RAM用户AccessKey ID:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_ID");
+         *     获取RAM用户AccessKey Secret:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
+         */
+        String accessKeyId = "LTAI5tReZWshffH78oQJPzZG";
+        String accessKeySecret = "Gi30OLYAWunaDbwsv5qqdZ3PzbDyGP";
+        // 接入区域和地址请根据实际情况修改。
+        ImageModerationResponse response = invokeFunction(accessKeyId, accessKeySecret, "green-cip.cn-shanghai.aliyuncs.com");
+        try {
+            // 自动路由。
+            if (response != null) {
+                //区域切换到cn-beijing。
+                if (500 == response.getStatusCode() || (response.getBody() != null && 500 == (response.getBody().getCode()))) {
+                    // 接入区域和地址请根据实际情况修改。
+                    response = invokeFunction(accessKeyId, accessKeySecret, "green-cip.cn-beijing.aliyuncs.com");
+                }
+            }
+            // 打印检测结果。
+            if (response != null) {
+                if (response.getStatusCode() == 200) {
+                    ImageModerationResponseBody body = response.getBody();
+                    System.out.println("requestId=" + body.getRequestId());
+                    System.out.println("code=" + body.getCode());
+                    System.out.println("msg=" + body.getMsg());
+                    if (body.getCode() == 200) {
+                        ImageModerationResponseBodyData data = body.getData();
+                        System.out.println("dataId=" + data.getDataId());
+                        List<ImageModerationResponseBodyDataResult> results = data.getResult();
+                        for (ImageModerationResponseBodyDataResult result : results) {
+                            System.out.println("label=" + result.getLabel());
+                            System.out.println("confidence=" + result.getConfidence());
+                        }
+                    } else {
+                        System.out.println("image moderation not success. code:" + body.getCode());
+                    }
+                } else {
+                    System.out.println("response not success. status:" + response.getStatusCode());
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 13 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/TextModerationResultVO.java

@@ -0,0 +1,13 @@
+package shop.alien.util.common.safe;
+
+import lombok.Data;
+
+/**
+ * 文本审核结果返回值对象
+ */
+@Data
+public class TextModerationResultVO {
+    // 扁平化的风险信息字段
+    private String riskLevel; // 风险等级
+    private String riskWords; // 命中风险内容
+}

+ 272 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/TextModerationUtil.java

@@ -0,0 +1,272 @@
+package shop.alien.util.common.safe;
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.green20220302.Client;
+import com.aliyun.green20220302.models.*;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.google.common.collect.Lists;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component
+public class TextModerationUtil {
+
+
+    @Value("${ali.yundun.accessKeyID}")
+    private String accessKeyId;
+
+    @Value("${ali.yundun.secret}")
+    private String accessKeySecret;
+
+    @Value("${ali.yundun.textEndpoint}")
+    private String textEndpoint;
+
+    @Value("${ali.yundun.regionId}")
+    private String regionId;
+
+    /**
+     * 创建请求客户端
+     * @return  Client
+     * @throws Exception 创建客户端异常
+     */
+    public Client createClient() throws Exception {
+        Config config = new Config();
+        config.setAccessKeyId(accessKeyId);
+        config.setAccessKeySecret(accessKeySecret);
+        //接入区域和地址请根据实际情况修改
+        config.setRegionId(regionId);
+        config.setEndpoint(textEndpoint);
+        //读取时超时时间,单位毫秒(ms)。
+        config.setReadTimeout(6000);
+        //连接时超时时间,单位毫秒(ms)。
+        config.setConnectTimeout(3000);
+        //设置http代理。
+        //config.setHttpProxy("http://xx.xx.xx.xx:xxxx");
+        //设置https代理。
+        //config.setHttpsProxy("https://xx.xx.xx.xx:xxxx");
+        return new Client(config);
+    }
+
+    /**
+     * 根据业务类型执行文本审核
+     * @param text 待审核文本
+     * @param servicesList 文本审核服务枚举
+     * @return 审核结果VO
+     * @throws Exception 调用服务异常
+     */
+    public TextModerationResultVO invokeFunction(String text, List<String> servicesList) throws Exception {
+        if (CollectionUtil.isEmpty(servicesList)) {
+            throw new IllegalArgumentException("至少需要一个审核服务");
+        }
+
+        Client client = createClient();
+        
+        TextModerationResultVO resultVO = new TextModerationResultVO();
+        
+        // 存储风险等级和原因的字符串
+        StringBuilder riskWordsBuilder = new StringBuilder();
+        String highestRiskLevel = "none"; // 默认未检测到风险
+        
+        try {
+            for (String service : servicesList) {
+                // 判断传入参service参数是否包含 chat_detection_pro、ad_compliance_detection_pro、llm_query_moderation、comment_detection_pro、
+                if (isSpecialService(service)) {
+                    // 处理特殊服务
+                    highestRiskLevel = handleSpecialService(text, service, client, riskWordsBuilder, highestRiskLevel);
+                } else {
+                    // 判断service中包含 url_detection ,将文本中的url进行检测 提取url 传入进行检测
+                    if ("url_detection".equals( service )) {
+                        // 写一个文本提取url 的方法
+                        List<String> urls = extractUrls(text);
+                        if (CollectionUtil.isNotEmpty(urls)){
+                            for (String url : urls) {
+                                // 链接调取
+                                highestRiskLevel = processTextModeration(client, url, service, riskWordsBuilder,highestRiskLevel);
+                            }
+                        }
+                    }else {
+                        // 文本调取
+                        highestRiskLevel = processTextModeration(client, text, service, riskWordsBuilder,highestRiskLevel);
+                    }
+                }
+            }
+            // 设置结果VO
+            resultVO.setRiskLevel(highestRiskLevel);
+            resultVO.setRiskWords(riskWordsBuilder.toString());
+        } catch (Exception e) {
+            throw new RuntimeException("解析失败" + e);
+        }
+        return resultVO;
+    }
+
+    /**
+     * 通用服务调取
+     * @param text 文本
+     * @param service  服务
+     * @param client  客户端
+     * @param riskWordsBuilder 危险词
+     * @param highestRiskLevel 最高危险等级
+     * @throws Exception 错误
+     */
+    private String processTextModeration(Client client, String text, String service, StringBuilder riskWordsBuilder,String highestRiskLevel) throws Exception {
+        // 调用文本检测服务
+        TextModerationResponse response = callTextModeration(client, text, service);
+        if (response != null) {
+            if (response.getStatusCode() == 200) {
+                TextModerationResponseBody result = response.getBody();
+                Integer code = result.getCode();
+                if (code != null && code == 200) {
+                    TextModerationResponseBody.TextModerationResponseBodyData data = result.getData();
+                    String reason = data.getReason();
+                    if (reason != null) {
+                        try {
+                            JSONObject reasonObj = JSON.parseObject(reason);
+                            String riskLevel = reasonObj.getString("riskLevel");
+                            if ("high".equals(riskLevel)) {
+                                highestRiskLevel = riskLevel;
+                                String riskWords = reasonObj.getString("riskWords");
+                                if (riskWords != null && !riskWords.isEmpty()) {
+                                    riskWordsBuilder.append(riskWords).append(",");
+                                }
+                            }
+                        } catch (Exception e) {
+                            // 解析失败,忽略该条原因
+                        }
+                    }
+                }
+            }
+        }
+        return highestRiskLevel;
+    }
+
+    /**
+     * 处理TextModerationPlus服务
+     * @param text 文本
+     * @param service  服务
+     * @param client  客户端
+     * @param riskWordsBuilder 危险词
+     * @param highestRiskLevel 最高危险等级
+     * @throws Exception 错误
+     */
+    private String handleSpecialService(String text, String service, Client client, StringBuilder riskWordsBuilder, String highestRiskLevel) throws Exception {
+        // 调用plus服务并获取响应
+        TextModerationPlusResponse response = callTextModerationPlusService(client, text, service);
+        // 根据response 返回参数处理结果 返回vo所需的参数
+        TextModerationPlusResponseBody body = response.body;
+        TextModerationPlusResponseBody.TextModerationPlusResponseBodyData data = body.getData();
+        if (data != null) {
+            // 分级:riskLevel
+            String riskLevel = data.getRiskLevel();
+            // high:高风险 medium:中风险 low:低风险 none:未检测到风险
+            if ("high".equals(riskLevel)) {
+                highestRiskLevel = riskLevel;
+                List<TextModerationPlusResponseBody.TextModerationPlusResponseBodyDataResult> resultList = data.getResult();
+                for (TextModerationPlusResponseBody.TextModerationPlusResponseBodyDataResult result : resultList) {
+                    // 文字内容检测运算后返回的标签,可能会检出多个标签和分值。
+                    String label = result.getLabel();
+                    // 检测到的敏感词,多个词用逗号分隔,部分标签不会返回敏感词。
+                    String riskWords = result.getRiskWords();
+                    String description = result.getDescription();
+                    if (StringUtils.isNotEmpty(riskWords)){
+                        riskWordsBuilder.append(riskWords).append(",");
+                    }
+                    // 获取 集合 getCustomizedHit 中 所有KeyWords 拼接为字符串并追加到 riskWordsBuilder
+                    if (CollectionUtil.isNotEmpty(result.getCustomizedHit())){
+                        result.getCustomizedHit().forEach(hit -> riskWordsBuilder.append(hit.getKeyWords()).append(","));
+                    }
+                    //置信分值,0到100分,保留到小数点后2位。部分标签无置信分。
+                    Float confidence = result.getConfidence();
+
+                }
+            }
+        }
+        return highestRiskLevel;
+    }
+
+    /**
+     * 调用TextModerationPlus服务
+     * @param text 文本
+     * @param service  服务
+     * @param client  客户端
+     * @return 检测结果
+     * @throws Exception 检测异常
+     */
+    private TextModerationPlusResponse callTextModerationPlusService(Client client, String text, String service) throws Exception {
+        // 包含调取plus服务
+        TextModerationPlusRequest textModerationPlusRequest = new TextModerationPlusRequest();
+        textModerationPlusRequest.setService(service);
+        // 构建服务参数
+        textModerationPlusRequest.setServiceParameters(buildServiceParameters(text));
+        return client.textModerationPlus(textModerationPlusRequest);
+    }
+
+    /**
+     * 调取普通服务
+     * @param text 文本
+     * @param service  服务
+     * @param client  客户端
+     * @return 检测结果
+     * @throws Exception 检测异常
+     */
+    private TextModerationResponse callTextModeration(Client client, String text, String service) throws Exception {
+        // 创建RuntimeObject实例并设置运行参数。
+        RuntimeOptions runtime = new RuntimeOptions();
+        runtime.readTimeout = 10000;
+        runtime.connectTimeout = 10000;
+        //检测参数构造
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("content", text);
+        // 调取普通服务
+        TextModerationRequest textModerationRequest = new TextModerationRequest();
+        textModerationRequest.setService(service);
+        textModerationRequest.setServiceParameters(serviceParameters.toJSONString());
+        return client.textModerationWithOptions(textModerationRequest, runtime);
+    }
+
+    /**
+     * 提取URL
+     * @param text 文本
+     * @return URL列表
+     */
+    private List<String> extractUrls(String text) {
+        List<String> urls = Lists.newArrayList();
+
+        // 使用改进后的正则表达式匹配URL
+        Pattern urlPattern = Pattern.compile("(https?://\\S+)(:\\d+)?(/\\S*)?");
+
+        Matcher matcher = urlPattern.matcher(text);
+
+        while (matcher.find()) {
+            urls.add(matcher.group());
+        }
+
+        return urls;
+    }
+
+
+    private String buildServiceParameters(String text) {
+        // 创建服务参数JSON
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("content", text);
+        return serviceParameters.toJSONString();
+    }
+    /**
+     * 判断服务是否为特殊服务
+     * @param service 服务名称
+     * @return 是否为特殊服务
+     */
+    public static boolean isSpecialService(String service) {
+        return TextReviewServiceEnum.CHAT_DETECTION_PRO.getService().equals(service)
+                || TextReviewServiceEnum.AD_COMPLIANCE_DETECTION_PRO.getService().equals(service)
+                || TextReviewServiceEnum.LLM_QUERY_MODERATION.getService().equals(service)
+                || TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService().equals(service);
+    }
+
+}

+ 44 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/TextReviewServiceEnum.java

@@ -0,0 +1,44 @@
+package shop.alien.util.common.safe;
+
+import lombok.Getter;
+
+/**
+ * @author: alien
+ * @date: 2023/10/31
+ */
+@Getter
+public enum TextReviewServiceEnum {
+    UGC_MODERATION_BY_LLM("ugc_moderation_byllm", "UGC场景文本审核大模型服务", "针对UGC场景,基于大模型能力的文本审核服务,能够高效精准地识别违规内容。", "通用场景"),
+    AIGC_MODERATION_BY_LLM("aigc_moderation_byllm", "AIGC场景文本审核大模型服务", "针对AIGC场景,基于大模型能力的文本审核服务,能够高效精准地识别违规内容。", "AIGC场景"),
+    LLM_QUERY_MODERATION("llm_query_moderation", "大语言模型输入文字检测", "检测大语言模型输入文字中是否存在色情、涉政、暴恐、违禁等红线类的违规内容。", "AIGC场景"),
+    LLM_RESPONSE_MODERATION("llm_response_moderation", "大语言模型生成文字检测", "检测大语言模型生成文字中是否存在色情、涉政、暴恐、违禁等红线类的违规内容。", "AIGC场景"),
+    COMMENT_DETECTION_PRO("comment_detection_pro", "公聊评论内容检测_专业版", "检测公聊评论中是否存在违规、不宜传播或者影响平台秩序的内容。建议对公聊评论场景的文本均进行该项检测。支持更细粒度的标签返回。", "业务场景"),
+    NICKNAME_DETECTION_PRO("nickname_detection_pro", "用户昵称检测_专业版", "检测用户昵称中是否存在违规、不宜传播或者影响平台秩序的内容。建议对用户昵称场景的文本均进行该项检测。支持更细粒度的标签返回。", "业务场景"),
+    CHAT_DETECTION_PRO("chat_detection_pro", "私聊互动内容检测_专业版", "检测私聊互动中是否存在违规、不宜传播或者影响平台秩序的内容。建议对私聊互动场景的文本均进行该项检测。支持更细粒度的标签返回。", "业务场景"),
+    AD_COMPLIANCE_DETECTION_PRO("ad_compliance_detection_pro", "广告法合规检测_专业版", "针对有推广含义的文本(包括商品标题、详情介绍页、投放营销的素材等)进行专门优化,检测是否有违反广告法以及其他违规或不宜传播的内容。支持更细粒度的标签返回。", "特殊场景"),
+    AI_ART_DETECTION("ai_art_detection", "AIGC类文字检测", "检测AIGC文生图中的文字是否存在色情、涉政、暴恐、违禁等红线类的违规内容。", "AIGC场景"),
+    COMMENT_DETECTION("comment_detection", "公聊评论内容检测", "检测公聊评论中是否存在违规、不宜传播或者影响平台秩序的内容。建议对公聊评论场景的文本均进行该项检测。", "业务场景"),
+    NICKNAME_DETECTION("nickname_detection", "用户昵称检测", "检测用户昵称中是否存在违规、不宜传播或者影响平台秩序的内容。建议对用户昵称场景的文本均进行该项检测。", "业务场景"),
+    CHAT_DETECTION("chat_detection", "私聊互动内容检测", "检测私聊互动中是否存在违规、不宜传播或者影响平台秩序的内容。建议对私聊互动场景的文本均进行该项检测。", "业务场景"),
+    AD_COMPLIANCE_DETECTION("ad_compliance_detection", "广告法合规检测", "针对有推广含义的文本(包括商品标题、详情介绍页、投放营销的素材等)进行专门优化,检测是否有违反广告法以及其他违规或不宜传播的内容。", "特殊场景"),
+    COMMENT_MULTILINGUAL_PRO("comment_multilingual_pro", "国际业务多语言检测", "针对多国语言进行检测,识别出文本中包含的违规、不宜传播或者影响平台秩序的内容。", "特殊场景"),
+    PGC_DETECTION("pgc_detection", "PGC通用物料检测", "针对新闻报道、专业视频、教育物料等具有较高制作标准的文本内容,检测判断是否存在违规、不宜传播或者影响平台秩序的内容。", "特殊场景"),
+    BAILIAN_QUERY_CHECK("bailian_query_check", "百炼文字输入检测", "支持对底线类违规(涉黄、涉政、涉暴等)、不良诱导信息的检测,支持对部分诱导性敏感话题进行检测。", "百炼场景"),
+    BAILIAN_RESPONSE_CHECK("bailian_response_check", "百炼文字输出检测", "支持对底线类违规(涉黄、涉政、涉暴等)、不良诱导信息的检测,支持对AI可能产生的辱骂、偏见、不良价值观信息进行检测。", "百炼场景"),
+    BAILIAN_QUERY_CHECK_PRO("bailian_query_check_pro", "百炼文字输入检测_pro", "支持对底线类违规(涉黄、涉政、涉暴等)、不良诱导信息的检测,支持对部分诱导性敏感话题进行检测。在部分场景中,该服务引入了审核大模型用于提升识别效果。", "百炼场景"),
+    BAILIAN_RESPONSE_CHECK_PRO("bailian_response_check_pro", "百炼文字输出检测_pro", "支持对底线类违规(涉黄、涉政、涉暴等)、不良诱导信息的检测,支持对AI可能产生的辱骂、偏见、不良价值观信息进行检测。在部分场景中,该服务引入了审核大模型用于提升识别效果。", "百炼场景"),
+    URL_DETECTION("url_detection", "URL风险链接检测", "支持检测网站是否涉及色情、赌博、钓鱼、欺诈等风险。", "--");
+
+    private final String service;
+    private final String serviceName;
+    private final String serviceDescription;
+    private final String serviceScene;
+
+    TextReviewServiceEnum(String service, String serviceName, String serviceDescription, String serviceScene) {
+        this.service = service;
+        this.serviceName = serviceName;
+        this.serviceDescription = serviceDescription;
+        this.serviceScene = serviceScene;
+    }
+
+}

+ 136 - 0
alien-common-core/src/main/java/shop/alien/util/common/safe/video/VideoModerationUtil.java

@@ -0,0 +1,136 @@
+package shop.alien.util.common.safe.video;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.green20220302.Client;
+import com.aliyun.green20220302.models.*;
+import com.aliyun.teaopenapi.models.Config;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+@Slf4j
+@Component
+public class VideoModerationUtil {
+
+    @Value("${ali.yundun.accessKeyID}")
+    private String accessKeyId;
+
+    @Value("${ali.yundun.secret}")
+    private String accessKeySecret;
+
+    @Value("${ali.yundun.videoEndpoint}")
+    private String imgEndpoint;
+
+    @Value("${ali.yundun.videoRegionId:cn-shanghai}")
+    private String regionId;
+
+    private Client client;
+
+    @PostConstruct
+    public void init() throws Exception {
+        this.client = createClient();
+    }
+
+    /**
+     * 创建请求客户端
+     *
+     * @return Client
+     * @throws Exception 创建客户端异常
+     */
+    public Client createClient() throws Exception {
+        Config config = new Config();
+        config.setAccessKeyId(accessKeyId);
+        config.setAccessKeySecret(accessKeySecret);
+        config.setEndpoint(imgEndpoint);
+        //接入区域和地址请根据实际情况修改。
+        config.setRegionId(regionId);
+        //连接时超时时间,单位毫秒(ms)。
+        config.setReadTimeout(6000);
+        //读取时超时时间,单位毫秒(ms)。
+        config.setConnectTimeout(3000);
+        return new Client(config);
+    }
+
+    /**
+     * 提交视频检测任务
+     *
+     * @param url  视频URL
+     * @param dataId 数据ID
+     * @return VideoModerationResponse
+     * @throws Exception 调用异常
+     */
+    public VideoModerationResponse submitVideoModerationTask(String url, String dataId) throws Exception {
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("url", url);
+        if (dataId != null) {
+            serviceParameters.put("dataId", dataId);
+        }
+
+        VideoModerationRequest videoModerationRequest = new VideoModerationRequest();
+        // 检测类型:videoDetectionByVL
+        videoModerationRequest.setService("videoDetectionByVL");
+        videoModerationRequest.setServiceParameters(serviceParameters.toJSONString());
+
+        try {
+            VideoModerationResponse response = client.videoModeration(videoModerationRequest);
+            log.info("提交视频审核任务结果: {}", JSON.toJSONString(response));
+            return response;
+        } catch (Exception e) {
+            log.error("提交视频审核任务失败", e);
+            throw new Exception("提交视频审核任务失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取视频检测结果
+     *
+     * @param taskId 任务ID
+     * @return VideoModerationResultResponse
+     * @throws Exception 调用异常
+     */
+    public VideoModerationResultResponse getVideoModerationResult(String taskId) throws Exception {
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("taskId", taskId);
+
+        VideoModerationResultRequest request = new VideoModerationResultRequest();
+        request.setService("videoDetection");
+        request.setServiceParameters(serviceParameters.toJSONString());
+
+        try {
+            VideoModerationResultResponse response = client.videoModerationResult(request);
+            log.info("获取视频审核结果: {}", JSON.toJSONString(response));
+            return response;
+        } catch (Exception e) {
+            log.error("获取视频审核结果失败", e);
+            throw new Exception("获取视频审核结果失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 取消视频检测任务
+     *
+     * @param taskId 任务ID
+     * @return VideoModerationCancelResponse
+     * @throws Exception 调用异常
+     */
+    public VideoModerationCancelResponse cancelVideoModerationTask(String taskId) throws Exception {
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("taskId", taskId);
+
+        VideoModerationCancelRequest request = new VideoModerationCancelRequest();
+        request.setService("liveStreamDetection");
+        request.setServiceParameters(serviceParameters.toJSONString());
+
+        try {
+            VideoModerationCancelResponse response = client.videoModerationCancel(request);
+            log.info("取消视频审核任务结果: {}", JSON.toJSONString(response));
+            return response;
+        } catch (Exception e) {
+            log.error("取消视频审核任务失败", e);
+            throw new Exception("取消视频审核任务失败: " + e.getMessage());
+        }
+    }
+}

+ 127 - 0
alien-common-core/src/main/java/shop/alien/util/compress/CompressUtil.java

@@ -0,0 +1,127 @@
+package shop.alien.util.compress;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 压缩文件工具类
+ *
+ * @author ssk
+ */
+public class CompressUtil {
+
+    /**
+     * 定义压缩类型
+     */
+    private static final String TYPE_ZIP = ".zip";
+    private static final String TYPE_7Z = ".7z";
+    private static final String TYPE_RAR = ".rar";
+
+    public static void main(String[] args) {
+        String srcFilePath = "D:\\MyTest\\new";
+        //使用时间命名zip文件,文件名内不能有特殊符号
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
+        String newFileName = sdf.format(new Date());
+        String destFilePath = "D:\\MyTest\\" + newFileName + TYPE_ZIP;
+        //第一个参数是需要压缩的源路径;第二个参数是压缩文件的目的路径,这边需要将压缩的文件名字加上去
+        compress(srcFilePath, destFilePath);
+    }
+
+    /**
+     * 压缩文件
+     *
+     * @param srcFilePath  压缩源路径
+     * @param destFilePath 压缩目的路径
+     */
+    public static void compress(String srcFilePath, String destFilePath) {
+        destFilePath = destFilePath + TYPE_ZIP;
+        File src = new File(srcFilePath);
+        if (!src.exists()) {
+            throw new RuntimeException(srcFilePath + "不存在");
+        }
+        File zipFile = new File(destFilePath);
+        try {
+            FileOutputStream fos = new FileOutputStream(zipFile);
+            ZipOutputStream zos = new ZipOutputStream(fos);
+            String baseDir = "";
+            compressByType(src, zos, baseDir);
+            zos.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 按照原路径的类型就行压缩。文件路径直接把文件压缩
+     *
+     * @param file    压缩源路径
+     * @param zos     压缩转换中文件
+     * @param baseDir 转换成所有系统都可以识别的路径
+     */
+    private static void compressByType(File file, ZipOutputStream zos, String baseDir) {
+        if (!file.exists()) {
+            return;
+        }
+        System.out.println("压缩路径" + baseDir + file.getName());
+        //判断文件是否是文件,如果是文件调用compressFile方法,如果是路径,则调用compressDir方法;
+        if (file.isFile()) {
+            compressFile(file, zos, baseDir);
+        } else if (file.isDirectory()) {
+            compressDir(file, zos, baseDir);
+        }
+    }
+
+    /**
+     * 压缩文件
+     *
+     * @param file    压缩源路径
+     * @param zos     压缩转换中文件
+     * @param baseDir 转换成所有系统都可以识别的路径
+     */
+    private static void compressFile(File file, ZipOutputStream zos, String baseDir) {
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+            ZipEntry entry = new ZipEntry(baseDir + file.getName());
+            zos.putNextEntry(entry);
+            int count;
+            byte[] buf = new byte[1024];
+            while ((count = bis.read(buf)) != -1) {
+                zos.write(buf, 0, count);
+            }
+            bis.close();
+        } catch (Exception e) {
+            System.err.println("ZipUtil:压缩文件失败");
+        }
+    }
+
+    /**
+     * 压缩文件夹
+     *
+     * @param dir     压缩源路径
+     * @param zos     压缩转换中文件
+     * @param baseDir 转换成所有系统都可以识别的路径
+     */
+    private static void compressDir(File dir, ZipOutputStream zos, String baseDir) {
+        if (!dir.exists()) {
+            return;
+        }
+        File[] files = dir.listFiles();
+        assert files != null;
+        if (files.length == 0) {
+            try {
+                zos.putNextEntry(new ZipEntry(baseDir + dir.getName() + File.separator));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        for (File file : files) {
+            compressByType(file, zos, baseDir + dir.getName() + File.separator);
+        }
+    }
+}

+ 256 - 0
alien-common-core/src/main/java/shop/alien/util/compress/DecompressionUtil.java

@@ -0,0 +1,256 @@
+package shop.alien.util.compress;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
+import org.apache.commons.compress.archivers.sevenz.SevenZFile;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipFile;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Enumeration;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * 解压文件工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/6/5 9:25
+ */
+@Slf4j
+public class DecompressionUtil {
+
+    public static void main(String[] args) {
+        String sourcePath = "Z:/douyin";
+        String targetPath = "E:/Cache";
+        String fileName = "ootd穿搭晚安20240604002708399950211.zip";
+        unZip(sourcePath, targetPath, fileName);
+    }
+
+    /**
+     * 解压zip
+     *
+     * @param sourcePath 原文件路径
+     * @param targetPath 目标文件路径
+     * @return boolean
+     */
+    public static boolean unZip(String sourcePath, String targetPath, String fileName) {
+        log.info("DecompressionUtil.unZip 文件名: {}, 文件路径: {}, 解压路径: {}", fileName, sourcePath, targetPath);
+        boolean flag = false;
+        targetPath += "/" + fileName.substring(0, fileName.lastIndexOf("."));
+        File srcFile = new File(sourcePath + "/" + fileName);
+        if (!srcFile.exists()) {
+            log.info("DecompressionUtil.unZip ERROR: 所指文件不存在");
+        }
+        // 开始解压
+        ZipFile zipFile = null;
+        try {
+            zipFile = new ZipFile(srcFile);
+            Enumeration<?> entries = zipFile.getEntries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) entries.nextElement();
+                System.out.println("解压: " + entry.getName());
+                // 如果是文件夹,就创建个文件夹
+                if (entry.isDirectory()) {
+                    String dirPath = targetPath + "/" + entry.getName();
+                    File dir = new File(dirPath);
+                    dir.mkdirs();
+                } else {
+                    // 如果是文件,就先创建一个文件,然后用io流把内容copy过去
+                    File targetFile = new File(targetPath + "/" + entry.getName());
+                    // 保证这个文件的父文件夹必须要存在
+                    if (!targetFile.getParentFile().exists()) {
+                        targetFile.getParentFile().mkdirs();
+                    }
+                    targetFile.createNewFile();
+                    // 将压缩文件内容写入到这个文件中
+                    InputStream is = zipFile.getInputStream(entry);
+                    FileOutputStream fos = new FileOutputStream(targetFile);
+                    int len;
+                    byte[] buf = new byte[2048];
+                    while ((len = is.read(buf)) != -1) {
+                        fos.write(buf, 0, len);
+                    }
+                    // 关流顺序,先打开的后关闭
+                    fos.close();
+                    is.close();
+                }
+            }
+            flag = true;
+        } catch (Exception e) {
+            log.info("DecompressionUtil.unZip ERROR: {}", e.getMessage());
+        } finally {
+            if (zipFile != null) {
+                try {
+                    zipFile.close();
+                } catch (IOException e) {
+                    log.info("DecompressionUtil.unZip ERROR: {}", e.getMessage());
+                }
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 解压7z
+     *
+     * @param sourcePath 原文件路径
+     * @param targetPath 目标文件路径
+     * @return boolean
+     */
+    public static boolean un7z(String sourcePath, String targetPath, String fileName) {
+        log.info("DecompressionUtil.un7z 文件名: {}, 文件路径: {}, 解压路径: {}", fileName, sourcePath, targetPath);
+        targetPath += "/" + fileName.substring(0, fileName.lastIndexOf("."));
+        boolean flag = false;
+        SevenZFile zIn = null;
+        try {
+            File file = new File(sourcePath + "/" + fileName);
+            zIn = new SevenZFile(file);
+            SevenZArchiveEntry entry;
+            while ((entry = zIn.getNextEntry()) != null) {
+                if (!entry.isDirectory()) {
+                    File newFile = new File(targetPath, entry.getName());
+                    if (!newFile.exists()) {
+                        new File(newFile.getParent()).mkdirs();
+                    }
+                    OutputStream out = Files.newOutputStream(newFile.toPath());
+                    BufferedOutputStream bos = new BufferedOutputStream(out);
+                    int len = -1;
+                    byte[] buf = new byte[(int) entry.getSize()];
+                    while ((len = zIn.read(buf)) != -1) {
+                        bos.write(buf, 0, len);
+                    }
+                    bos.flush();
+                    bos.close();
+                    out.close();
+                }
+            }
+            flag = true;
+        } catch (Exception e) {
+            log.info("DecompressionUtil.un7z ERROR: {}", e.getMessage());
+        } finally {
+            try {
+                if (zIn != null) zIn.close();
+            } catch (IOException e) {
+                log.info("DecompressionUtil.un7z ERROR: {}", e.getMessage());
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 解压tar
+     *
+     * @param sourcePath 原文件路径
+     * @param targetPath 目标文件路径
+     * @return boolean
+     */
+    public static boolean unTar(String sourcePath, String targetPath, String fileName) {
+        log.info("DecompressionUtil.unTar 文件名: {}, 文件路径: {}, 解压路径: {}", fileName, sourcePath, targetPath);
+        targetPath += "/" + fileName.substring(0, fileName.lastIndexOf("."));
+        boolean flag = false;
+        try (FileInputStream fis = new FileInputStream(sourcePath + "/" + fileName);
+             BufferedInputStream bis = new BufferedInputStream(fis);
+             TarArchiveInputStream tarInputStream = new TarArchiveInputStream(bis)) {
+            TarArchiveEntry tarEntry;
+            while ((tarEntry = tarInputStream.getNextTarEntry()) != null) {
+                File outputFile = new File(targetPath, tarEntry.getName());
+                if (tarEntry.isDirectory()) {
+                    outputFile.mkdirs();
+                } else {
+                    outputFile.getParentFile().mkdirs();
+                    try (OutputStream outputStream = Files.newOutputStream(outputFile.toPath())) {
+                        byte[] buffer = new byte[4096];
+                        int bytesRead;
+                        while ((bytesRead = tarInputStream.read(buffer)) != -1) {
+                            outputStream.write(buffer, 0, bytesRead);
+                        }
+                    }
+                }
+            }
+            flag = true;
+        } catch (IOException e) {
+            log.info("DecompressionUtil.unTar ERROR: {}", e.getMessage());
+        }
+        return flag;
+    }
+
+
+    /**
+     * 解压tar.gz
+     *
+     * @param sourcePath 原文件路径
+     * @param targetPath 目标文件路径
+     * @return boolean
+     */
+    public static boolean unTarGz(String sourcePath, String targetPath, String fileName) {
+        log.info("DecompressionUtil.unTarGz 文件名: {}, 文件路径: {}, 解压路径: {}", fileName, sourcePath, targetPath);
+        targetPath += "/" + fileName.substring(0, fileName.lastIndexOf("."));
+        Path source = Paths.get(sourcePath + "/" + fileName);
+        Path target = Paths.get(targetPath);
+        boolean flag = false;
+        try (InputStream fi = Files.newInputStream(source);
+             BufferedInputStream bi = new BufferedInputStream(fi);
+             GzipCompressorInputStream gzi = new GzipCompressorInputStream(bi);
+             TarArchiveInputStream ti = new TarArchiveInputStream(gzi)) {
+            ArchiveEntry entry;
+            while ((entry = ti.getNextEntry()) != null) {
+                String entryName = entry.getName();
+                Path targetDirResolved = target.resolve(entryName);
+                Path newDestPath = targetDirResolved.normalize();
+                if (entry.isDirectory()) {
+                    newDestPath = Paths.get(targetPath + "/" + entryName);
+                    Files.createDirectories(newDestPath);
+                } else {
+                    Files.copy(ti, newDestPath, StandardCopyOption.REPLACE_EXISTING);
+                }
+            }
+            flag = true;
+        } catch (Exception e) {
+            log.info("DecompressionUtil.unTarGz ERROR: {}", e.getMessage());
+        }
+        return flag;
+    }
+
+    /**
+     * 解压gzip
+     *
+     * @param sourcePath 原文件路径
+     * @param targetPath 目标文件路径
+     * @return boolean
+     */
+    public static boolean unGzip(String sourcePath, String targetPath, String fileName) {
+        log.info("DecompressionUtil.unGzip 文件名: {}, 文件路径: {}, 解压路径: {}", fileName, sourcePath, targetPath);
+        targetPath += "/" + fileName.substring(0, fileName.lastIndexOf("."));
+        boolean flag = false;
+        try {
+            FileInputStream fin = new FileInputStream(sourcePath + "/" + fileName);
+            //建立gzip解压工作流
+            GZIPInputStream gzipInputStream = new GZIPInputStream(fin);
+            //建立解压文件输出流
+            FileOutputStream fileOutputStream = new FileOutputStream(targetPath);
+            int num;
+            byte[] buf = new byte[1024];
+
+            while ((num = gzipInputStream.read(buf, 0, buf.length)) != -1) {
+                fileOutputStream.write(buf, 0, num);
+            }
+            gzipInputStream.close();
+            fileOutputStream.close();
+            fin.close();
+            flag = true;
+        } catch (IOException e) {
+            log.info("DecompressionUtil.unGzip ERROR: {}", e.getMessage());
+        }
+        return flag;
+    }
+
+}

+ 52 - 0
alien-common-core/src/main/java/shop/alien/util/cron/CronUtil.java

@@ -0,0 +1,52 @@
+package shop.alien.util.cron;
+
+import org.quartz.CronExpression;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/2/17 9:40
+ */
+public class CronUtil {
+
+    /**
+     * cron表达式是一种用于指定定时任务的时间表达式,它由6或7个字段组成,每个字段代表一个时间单位。这些字段之间用空格分隔,从左到右依次为:秒(可选)、分、‌小时、‌日期、‌月份、‌星期、‌年份(可选)。
+     * <p>
+     * 在cron表达式中,可以使用一些特殊字符来增加灵活性:
+     * <p>
+     * :代表所有可能的值。例如,在“小时”字段中使用表示“每个小时”。
+     * ,:用于分隔列表中的值。例如,5,10,15在“小时”字段中表示5点、10点和15点。
+     * -:用于指定范围。例如,1-5在“星期”字段中表示星期一到星期五。
+     * /:用于指定步长。例如,在“分钟”字段中使用0/15表示从0分钟开始,每15分钟执行一次。
+     * ?:在“日”和“星期”字段中,?表示不指定值,通常与另一个字段的特定值配合使用。
+     * <p>
+     * ‌示例cron表达式及其含义‌:
+     * <p>
+     * 0 0 * * * ?:每天午夜(00:00:00)执行一次任务。
+     * 0 15 10 ? * *:每天上午10:15执行一次任务。注意这里使用了?来忽略“日”字段。
+     * 0 0/30 * * * ?:每半小时执行一次任务。这里的/30表示从0分钟开始,每30分钟执行一次。
+     * 0 0 9-17 * * MON-FRI:每个工作日(周一至周五)的上午9点到下午5点之间,每小时执行一次任务。
+     * 0 0 0 25 12 ?:每年圣诞节(12月25日)午夜执行一次任务。这里使用了具体的日期和月份。
+     */
+    public static void main(String[] args) throws ParseException {
+        String cronExpression = "0 0/5 14,18 * * ?";
+        cronExpression = "0 0/5 14,18 * * ?";
+        boolean isValid = CronExpression.isValidExpression(cronExpression);
+        System.out.println("Is valid cron expression: " + isValid);
+
+        CronExpression cron = new CronExpression(cronExpression);
+        Date now = new Date();
+        Date nextTime = cron.getNextValidTimeAfter(now);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        System.out.println("Next valid time: " + sdf.format(nextTime));
+
+
+        Date prevTime = cron.getTimeAfter(now);
+        System.out.println("Previous valid time: " + sdf.format(prevTime));
+    }
+
+}

+ 87 - 0
alien-common-core/src/main/java/shop/alien/util/database/Sqlite.java

@@ -0,0 +1,87 @@
+package shop.alien.util.database;
+
+import java.sql.*;
+
+/**
+ * 连接Sqlite并取值
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/4/13 15:59
+ */
+public class Sqlite {
+
+    public static void connect() {
+        //声明数据库连接对象
+        Connection conn = null;
+        try {
+            //定义连接数据库的url(url:访问数据库的URL路径),wc.db为数据库名称
+            String url = "jdbc:sqlite:D:\\SoftWareData\\SQLite\\Test.db";
+            //获取数据库连接
+            conn = DriverManager.getConnection(url);
+            Statement statement = conn.createStatement();
+            ResultSet rs = statement.executeQuery("select * from test");
+            while (rs.next()) {
+                String col1 = rs.getString("id");
+                String col2 = rs.getString("root");
+                String col3 = rs.getString("uuid");
+                System.out.println("col1 = " + col1 + "  col2 = " + col2 + "  col3 = " + col3);
+            }
+        } catch (SQLException e) {
+            System.out.println(e.getMessage());
+        } finally {
+            try {
+                if (conn != null) {
+                    conn.close();
+                }
+            } catch (SQLException ex) {
+                System.out.println(ex.getMessage());
+            }
+        }
+
+    }
+
+
+    public static boolean insert() {
+        boolean flag = false;
+        try {
+            Connection conn = null;
+            String url = "jdbc:sqlite:D:\\SoftWareData\\SQLite\\Test.db";
+            //获取数据库连接
+            conn = DriverManager.getConnection(url);
+            Statement statement = conn.createStatement();
+            boolean execute = statement.execute("insert into test (id,root,uuid) values (5,'4','4')");
+            System.out.println(execute);
+            return execute;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return true;
+    }
+
+    public static boolean update() {
+        boolean flag = false;
+        try {
+            Connection conn = null;
+            String url = "jdbc:sqlite:D:\\SoftWareData\\SQLite\\Test.db";
+            //获取数据库连接
+            conn = DriverManager.getConnection(url);
+            Statement statement = conn.createStatement();
+            int i = statement.executeUpdate("update test set root = '1' where id = 1");
+            System.out.println(i);
+            return flag;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return true;
+    }
+
+
+    public static void main(String[] args) {
+//        connect();
+        insert();
+//        update();
+    }
+}

+ 100 - 0
alien-common-core/src/main/java/shop/alien/util/date/DateUtils.java

@@ -0,0 +1,100 @@
+package shop.alien.util.date;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 日期计算工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2023/5/24 16:03
+ */
+public class DateUtils {
+
+    public static void main(String[] args) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//        Date date = calcHours(new Date(), 1);
+//        System.out.println(sdf.format(date));
+//
+//        boolean b = dateCompare(new Date(), date);
+//        System.out.println(b);
+
+
+        Date date1 = sdf.parse("2024-05-10 19:10:00");
+        Date date2 = sdf.parse("2024-05-10 19:11:00");
+        System.out.println(calcMinute(date1, 1));
+        System.out.println(dateCompare(date1, calcMinute(date1, 1)));
+
+    }
+
+    /**
+     * 天数加减
+     *
+     * @param date 日期
+     * @param days 加减天数
+     * @return 处理后的日期
+     */
+    public static Date calcDays(Date date, Integer days) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.DAY_OF_MONTH, days);
+        return c.getTime();
+    }
+
+    /**
+     * 小时加减
+     *
+     * @param date  日期
+     * @param hours 加减时间
+     * @return 处理后的日期
+     */
+    public static Date calcHours(Date date, Integer hours) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.HOUR_OF_DAY, hours);
+        return c.getTime();
+    }
+
+    /**
+     * 分钟加减
+     *
+     * @param date   日期
+     * @param minute 加减时间
+     * @return 处理后的日期
+     */
+    public static Date calcMinute(Date date, Integer minute) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.MINUTE, minute);
+        return c.getTime();
+    }
+
+    /**
+     * 日期比较
+     *
+     * @param dateOne 日期1
+     * @param dateTwo 日期2
+     * @return 是否大于
+     */
+    public static boolean dateCompare(Date dateOne, Date dateTwo) {
+        return dateOne.compareTo(dateTwo) > 0;
+    }
+
+    /**
+     * 今日剩余秒数
+     *
+     * @return int
+     */
+    public static Integer todayLastSecond() {
+        int daySecond = 86400;
+        LocalDateTime now = LocalDateTime.now();
+        int hour = now.getHour() * 60 * 60;
+        int minute = now.getMinute() * 60;
+        int second = now.getSecond();
+        return daySecond - hour - minute - second;
+    }
+}

+ 133 - 0
alien-common-core/src/main/java/shop/alien/util/encryption/AesCbcUtil.java

@@ -0,0 +1,133 @@
+package shop.alien.util.encryption;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * CBC模式(密码分组链接:Cipher-block chaining)
+ * CBC模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/1/17 11:20
+ */
+public class AesCbcUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AesCbcUtil.class);
+
+    private static final String ENCODING = "GBK";
+
+    private static final String KEY_ALGORITHM = "AES";
+
+    /**
+     * 加解密算法/工作模式/填充方式
+     */
+    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
+
+    /**
+     * 填充向量
+     */
+    private static final String FILL_VECTOR = "1234560405060708";
+
+    /**
+     * 加密字符串
+     *
+     * @param content  字符串
+     * @param password 密钥KEY
+     * @return String
+     */
+    public static String encrypt(String content, String password) {
+        if (StringUtils.isAnyEmpty(content, password)) {
+            LOGGER.error("AES encryption params is null");
+            return null;
+        }
+
+        byte[] raw = hex2byte(password);
+        SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
+        try {
+            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
+            IvParameterSpec iv = new IvParameterSpec(FILL_VECTOR.getBytes());
+            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
+            byte[] anslBytes = content.getBytes(ENCODING);
+            byte[] encrypted = cipher.doFinal(anslBytes);
+            return byte2hex(encrypted).toUpperCase();
+        } catch (Exception e) {
+            LOGGER.error("AES encryption operation has exception,content:{},password:{}", content, password, e);
+        }
+        return null;
+    }
+
+    /**
+     * 解密
+     *
+     * @param content  解密前的字符串
+     * @param password 解密KEY
+     * @return String
+     */
+    public static String decrypt(String content, String password) {
+        if (StringUtils.isAnyEmpty(content, password)) {
+            LOGGER.error("AES decryption params is null");
+            return null;
+        }
+
+        try {
+            byte[] raw = hex2byte(password);
+            SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
+            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
+            IvParameterSpec iv = new IvParameterSpec(FILL_VECTOR.getBytes());
+            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
+            byte[] encrypted1 = hex2byte(content);
+            byte[] original = cipher.doFinal(encrypted1);
+            return new String(original, ENCODING);
+        } catch (Exception e) {
+            LOGGER.error("AES decryption operation has exception,content:{},password:{}", content, password, e);
+        }
+        return null;
+    }
+
+    public static byte[] hex2byte(String strhex) {
+        if (strhex == null) {
+            return null;
+        }
+        int l = strhex.length();
+        if (l % 2 == 1) {
+            return null;
+        }
+        byte[] b = new byte[l / 2];
+        for (int i = 0; i != l / 2; i++) {
+            b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
+        }
+        return b;
+    }
+
+    public static String byte2hex(byte[] b) {
+        StringBuilder hs = new StringBuilder();
+        String stmp;
+        for (byte value : b) {
+            stmp = (Integer.toHexString(value & 0XFF));
+            if (stmp.length() == 1) {
+                hs.append("0").append(stmp);
+            } else {
+                hs.append(stmp);
+            }
+        }
+        return hs.toString().toUpperCase();
+    }
+
+    public static void main(String[] args) throws Exception {
+        String str = "123456";
+        //必须为16位
+        String key = "alien67890982316";
+        //生成加密密钥
+        String key2 = byte2hex(key.getBytes());
+        System.out.println(key2);
+        String encryptStr = encrypt(str, key2);
+        System.out.println(encryptStr);
+        System.out.println(decrypt(encryptStr, key2));
+    }
+}

+ 112 - 0
alien-common-core/src/main/java/shop/alien/util/encryption/AesEcbUtil.java

@@ -0,0 +1,112 @@
+package shop.alien.util.encryption;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * ECB模式(电子密码本模式:Electronic codebook)
+ * ECB是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/1/17 10:24
+ */
+public class AesEcbUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AesEcbUtil.class);
+
+    private static final String KEY_ALGORITHM = "AES";
+
+    private static final String CHAR_SET = "UTF-8";
+
+    /**
+     * AES的密钥长度
+     */
+    private static final Integer SECRET_KEY_LENGTH = 128;
+
+    /**
+     * 加解密算法/工作模式/填充方式
+     */
+    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
+
+    /**
+     * AES加密操作
+     *
+     * @param content  待加密内容
+     * @param password 加密密码
+     * @return 返回Base64转码后的加密数据
+     */
+    public static String encrypt(String content, String password) {
+        if (StringUtils.isAnyEmpty(content, password)) {
+            LOGGER.error("AES encryption params is null");
+            return null;
+        }
+        try {
+            //创建密码器
+            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
+            byte[] byteContent = content.getBytes(CHAR_SET);
+            //初始化为加密密码器
+            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));
+            byte[] encryptByte = cipher.doFinal(byteContent);
+            return Base64.encodeBase64String(encryptByte);
+        } catch (Exception e) {
+            LOGGER.error("AES encryption operation has exception,content:{},password:{}", content, password, e);
+        }
+        return null;
+    }
+
+    /**
+     * AES解密操作
+     *
+     * @param encryptContent 加密的密文
+     * @param password       解密的密钥
+     * @return String
+     */
+    public static String decrypt(String encryptContent, String password) {
+        if (StringUtils.isAnyEmpty(encryptContent, password)) {
+            LOGGER.error("AES decryption params is null");
+            return null;
+        }
+        try {
+            Cipher cipher;
+            cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
+            //设置为解密模式
+            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));
+            //执行解密操作
+            byte[] result = cipher.doFinal(Base64.decodeBase64(encryptContent));
+            return new String(result, CHAR_SET);
+        } catch (Exception e) {
+            LOGGER.error("AES decryption operation has exception,content:{},password:{}", encryptContent, password, e);
+        }
+        return null;
+    }
+
+    private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {
+        //生成指定算法密钥的生成器
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+        keyGenerator.init(SECRET_KEY_LENGTH, new SecureRandom(password.getBytes()));
+        //生成密钥
+        SecretKey secretKey = keyGenerator.generateKey();
+        //转换成AES的密钥
+        return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
+    }
+
+    public static void main(String[] args) throws Exception {
+        String str = "123456";
+        System.out.println("str:" + str);
+        String encryptStr = encrypt(str, "alien");
+        System.out.println("encrypt:" + encryptStr);
+        String decryptStr = decrypt(encryptStr, "alien");
+        System.out.println("decryptStr:" + decryptStr);
+    }
+
+}

+ 41 - 0
alien-common-core/src/main/java/shop/alien/util/encryption/Base64Util.java

@@ -0,0 +1,41 @@
+package shop.alien.util.encryption;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/**
+ * base64加密解密
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/1/17 10:25
+ */
+public class Base64Util {
+
+    /**
+     * base64加密
+     *
+     * @return 加密字符串
+     */
+    public String encode(String str) {
+        return Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * base64解密
+     *
+     * @return 解密字符串
+     */
+    public String decode(String str) {
+        byte[] asBytes = Base64.getDecoder().decode(str);
+        return new String(asBytes, StandardCharsets.UTF_8);
+    }
+
+    public static void main(String[] args) {
+        Base64Util base64Util = new Base64Util();
+        String encodeStr = base64Util.encode("123456");
+        System.out.println("加密:" + encodeStr);
+        System.out.println("解密:" + base64Util.decode(encodeStr));
+    }
+
+}

+ 66 - 0
alien-common-core/src/main/java/shop/alien/util/encryption/Md5Util.java

@@ -0,0 +1,66 @@
+package shop.alien.util.encryption;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * MD5加密后的位数有两种类型:16位与32位,默认使用32位。
+ * 16位实际上是从32位字符串中取中间的第9位到第24位的部分
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/1/17 10:24
+ */
+public class Md5Util {
+
+    /**
+     * 加密转大写
+     *
+     * @param str    加密字符串
+     * @param length 长度
+     * @return String
+     */
+    public String createMd5Str(String str, Integer length) {
+        try {
+            String md5Result;
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            md.update(str.getBytes());
+            byte[] b = md.digest();
+            int i;
+            StringBuilder buf = new StringBuilder();
+            for (byte value : b) {
+                i = value;
+                if (i < 0) i += 256;
+                if (i < 16) buf.append("0");
+                buf.append(Integer.toHexString(i));
+            }
+            if (null != length && length == 16) {
+                md5Result = buf.substring(8, 24);
+            } else {
+                md5Result = buf.toString();
+            }
+            return md5Result;
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("MD5加密出错!!+" + e);
+        }
+    }
+
+    /**
+     * 加密转大写
+     *
+     * @param str    加密字符串
+     * @param length 长度
+     * @return String
+     */
+    public String createMd5StrToUpperCase(String str, Integer length) {
+        return createMd5Str(str, length).toUpperCase();
+    }
+
+    public static void main(String[] args) {
+        Md5Util md5Util = new Md5Util();
+        System.out.println(md5Util.createMd5Str("123456", 32));
+        System.out.println(md5Util.createMd5Str("123456", 16));
+        System.out.println(md5Util.createMd5StrToUpperCase("123456", 32));
+        System.out.println(md5Util.createMd5StrToUpperCase("123456", 16));
+    }
+}

+ 46 - 0
alien-common-core/src/main/java/shop/alien/util/encryption/Sha256Util.java

@@ -0,0 +1,46 @@
+package shop.alien.util.encryption;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * SHA256加密
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/1/17 11:50
+ */
+public class Sha256Util {
+
+    /**
+     * 利用java原生的类实现SHA256加密
+     *
+     * @param str 需要加密的字符串
+     * @return String
+     */
+    public static String getSha256(String str) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-256");
+            messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
+            StringBuilder stringBuilder = new StringBuilder();
+            for (int i = 0; i < messageDigest.digest().length; i++) {
+                String temp;
+                temp = Integer.toHexString(messageDigest.digest()[i] & 0xFF);
+                if (temp.length() == 1) {
+                    //1得到一位的进行补0操作
+                    stringBuilder.append("0");
+                }
+                stringBuilder.append(temp);
+            }
+            return stringBuilder.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("SHA256加密出错!!+" + e);
+        }
+    }
+
+    public static void main(String[] args) {
+        System.out.println(getSha256("123456"));
+    }
+}

+ 261 - 0
alien-common-core/src/main/java/shop/alien/util/excel/EasyExcelUtil.java

@@ -0,0 +1,261 @@
+package shop.alien.util.excel;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.read.listener.ReadListener;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.util.common.WebTool;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * EasyExcel 工具类
+ * 封装常用的 Excel 导入导出功能
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+public class EasyExcelUtil {
+
+    /**
+     * 导出 Excel 文件到 HTTP 响应(自动获取响应对象)
+     *
+     * @param dataList  要导出的数据列表
+     * @param clazz     数据类型的 Class 对象
+     * @param fileName  导出的文件名(不带扩展名)
+     * @param sheetName 工作表名称
+     * @param <T>       数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName, String sheetName) throws IOException {
+        HttpServletResponse response = WebTool.getResponse();
+        exportExcel(response, dataList, clazz, fileName, sheetName);
+    }
+
+    /**
+     * 导出 Excel 文件到 HTTP 响应
+     *
+     * @param response  HttpServletResponse 对象
+     * @param dataList  要导出的数据列表
+     * @param clazz     数据类型的 Class 对象
+     * @param fileName  导出的文件名(不带扩展名)
+     * @param sheetName 工作表名称
+     * @param <T>       数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz,
+                                       String fileName, String sheetName) throws IOException {
+        if (dataList == null || dataList.isEmpty()) {
+            log.warn("[EasyExcelUtil] 导出数据为空: {}", fileName);
+            throw new IllegalArgumentException("导出数据为空");
+        }
+
+        // 设置响应头
+        setupResponse(response, fileName);
+
+        // 导出Excel
+        EasyExcel.write(response.getOutputStream(), clazz)
+                .sheet(sheetName != null ? sheetName : "Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动列宽
+                .doWrite(dataList);
+
+        log.info("[EasyExcelUtil] 导出成功: {}, 共 {} 条数据", fileName, dataList.size());
+    }
+
+    /**
+     * 导出 Excel 文件到 HTTP 响应(使用默认工作表名称)
+     *
+     * @param response HttpServletResponse 对象
+     * @param dataList 要导出的数据列表
+     * @param clazz    数据类型的 Class 对象
+     * @param fileName 导出的文件名(不带扩展名)
+     * @param <T>      数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz, String fileName) throws IOException {
+        exportExcel(response, dataList, clazz, fileName, "Sheet1");
+    }
+
+    /**
+     * 导出 Excel 文件到本地文件
+     *
+     * @param filePath  文件路径
+     * @param dataList  要导出的数据列表
+     * @param clazz     数据类型的 Class 对象
+     * @param sheetName 工作表名称
+     * @param <T>       数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcelToFile(String filePath, List<T> dataList, Class<T> clazz, String sheetName) throws IOException {
+        if (dataList == null || dataList.isEmpty()) {
+            log.warn("[EasyExcelUtil] 导出数据为空: {}", filePath);
+            throw new IllegalArgumentException("导出数据为空");
+        }
+
+        EasyExcel.write(filePath, clazz)
+                .sheet(sheetName != null ? sheetName : "Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                .doWrite(dataList);
+
+        log.info("[EasyExcelUtil] 导出到文件成功: {}, 共 {} 条数据", filePath, dataList.size());
+    }
+
+    /**
+     * 导出 Excel 文件到本地文件(使用默认工作表名称)
+     *
+     * @param filePath 文件路径
+     * @param dataList 要导出的数据列表
+     * @param clazz    数据类型的 Class 对象
+     * @param <T>      数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcelToFile(String filePath, List<T> dataList, Class<T> clazz) throws IOException {
+        exportExcelToFile(filePath, dataList, clazz, "Sheet1");
+    }
+
+    /**
+     * 导出 Excel 文件到输出流
+     *
+     * @param outputStream 输出流
+     * @param dataList     要导出的数据列表
+     * @param clazz        数据类型的 Class 对象
+     * @param sheetName    工作表名称
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void exportExcelToStream(OutputStream outputStream, List<T> dataList, Class<T> clazz, String sheetName) throws IOException {
+        if (dataList == null || dataList.isEmpty()) {
+            log.warn("[EasyExcelUtil] 导出数据为空");
+            throw new IllegalArgumentException("导出数据为空");
+        }
+
+        EasyExcel.write(outputStream, clazz)
+                .sheet(sheetName != null ? sheetName : "Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                .doWrite(dataList);
+
+        log.info("[EasyExcelUtil] 导出到流成功, 共 {} 条数据", dataList.size());
+    }
+
+    /**
+     * 导入 Excel 文件
+     *
+     * @param file         上传的 MultipartFile 文件
+     * @param clazz        数据类型的 Class 对象
+     * @param readListener 读取监听器
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void importExcel(MultipartFile file, Class<T> clazz, ReadListener<T> readListener) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件为空");
+        }
+
+        log.info("[EasyExcelUtil] 开始导入Excel文件: {}", file.getOriginalFilename());
+
+        EasyExcel.read(file.getInputStream(), clazz, readListener)
+                .sheet()
+                .doRead();
+
+        log.info("[EasyExcelUtil] Excel文件导入完成: {}", file.getOriginalFilename());
+    }
+
+    /**
+     * 导入 Excel 文件(指定Sheet)
+     *
+     * @param file         上传的 MultipartFile 文件
+     * @param clazz        数据类型的 Class 对象
+     * @param readListener 读取监听器
+     * @param sheetNo      Sheet索引(从0开始)
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void importExcel(MultipartFile file, Class<T> clazz, ReadListener<T> readListener, int sheetNo) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件为空");
+        }
+
+        log.info("[EasyExcelUtil] 开始导入Excel文件: {}, Sheet: {}", file.getOriginalFilename(), sheetNo);
+
+        EasyExcel.read(file.getInputStream(), clazz, readListener)
+                .sheet(sheetNo)
+                .doRead();
+
+        log.info("[EasyExcelUtil] Excel文件导入完成: {}", file.getOriginalFilename());
+    }
+
+    /**
+     * 导入 Excel 文件(指定Sheet名称)
+     *
+     * @param file         上传的 MultipartFile 文件
+     * @param clazz        数据类型的 Class 对象
+     * @param readListener 读取监听器
+     * @param sheetName    Sheet名称
+     * @param <T>          数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void importExcel(MultipartFile file, Class<T> clazz, ReadListener<T> readListener, String sheetName) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件为空");
+        }
+
+        log.info("[EasyExcelUtil] 开始导入Excel文件: {}, Sheet: {}", file.getOriginalFilename(), sheetName);
+
+        EasyExcel.read(file.getInputStream(), clazz, readListener)
+                .sheet(sheetName)
+                .doRead();
+
+        log.info("[EasyExcelUtil] Excel文件导入完成: {}", file.getOriginalFilename());
+    }
+
+    /**
+     * 下载导入模板(只有表头,无数据)
+     *
+     * @param response HttpServletResponse 对象
+     * @param clazz    数据类型的 Class 对象
+     * @param fileName 文件名(不带扩展名)
+     * @param <T>      数据类型
+     * @throws IOException 如果发生 IO 异常
+     */
+    public static <T> void downloadTemplate(HttpServletResponse response, Class<T> clazz, String fileName) throws IOException {
+        setupResponse(response, fileName);
+
+        // 导出空列表,只有表头
+        EasyExcel.write(response.getOutputStream(), clazz)
+                .sheet("Sheet1")
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                .doWrite(new java.util.ArrayList<>());
+
+        log.info("[EasyExcelUtil] 模板下载成功: {}", fileName);
+    }
+
+    /**
+     * 设置 HTTP 响应头
+     *
+     * @param response HttpServletResponse 对象
+     * @param fileName 文件名(不带扩展名)
+     * @throws IOException 如果发生 IO 异常
+     */
+    private static void setupResponse(HttpServletResponse response, String fileName) throws IOException {
+        response.reset(); // 重置响应,清除之前可能设置的响应头
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+
+        // 使用 RFC 5987 标准格式,更好地支持中文文件名
+        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+        response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + ".xlsx\";filename*=utf-8''" + encodedFileName + ".xlsx");
+
+        // 设置缓存控制
+        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+        response.setHeader("Pragma", "no-cache");
+        response.setDateHeader("Expires", 0);
+    }
+}
+

+ 64 - 0
alien-common-core/src/main/java/shop/alien/util/excel/ExcelWriteTest.java

@@ -0,0 +1,64 @@
+package shop.alien.util.excel;
+
+import com.alibaba.excel.EasyExcel;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * EasyExcel 2.2.10 写入测试
+ * 
+ * @author ssk
+ * @version 2.0
+ * @date 2020/7/21 17:18
+ */
+public class ExcelWriteTest {
+
+    /**
+     * 每行数据是List<String>无表头
+     *
+     * @throws IOException
+     */
+    @Test
+    public void writeWithoutHead() throws IOException {
+        String fileName = "d://withoutHead.xlsx";
+        List<List<String>> data = new ArrayList<>();
+        for (int i = 0; i < 100; i++) {
+            List<String> item = new ArrayList<>();
+            item.add("item0" + i);
+            item.add("item1" + i);
+            item.add("item2" + i);
+            data.add(item);
+        }
+        EasyExcel.write(fileName)
+                .sheet("sheet1")
+                .doWrite(data);
+    }
+
+    /**
+     * 带表头写入
+     *
+     * @throws IOException
+     */
+    @Test
+    public void writeWithHead() throws IOException {
+        String fileName = "D://withHead.xlsx";
+        List<UserExcel> data = new ArrayList<>();
+        for (int i = 0; i < 100; i++) {
+            UserExcel item = new UserExcel();
+            item.name = "name" + i;
+            item.age = "age" + i;
+            item.email = "email" + i;
+            item.address = "address" + i;
+            item.sax = "sax" + i;
+            item.height = "height" + i;
+            item.last = "last" + i;
+            data.add(item);
+        }
+        EasyExcel.write(fileName, UserExcel.class)
+                .sheet("sheet1")
+                .doWrite(data);
+    }
+}

+ 37 - 0
alien-common-core/src/main/java/shop/alien/util/excel/UserExcel.java

@@ -0,0 +1,37 @@
+package shop.alien.util.excel;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+/**
+ * excel导出实体类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/7/21 17:13
+ */
+@Data
+public class UserExcel {
+
+    @ExcelProperty(value = "姓名", index = 0)
+    public String name;
+
+    @ExcelProperty(value = "年龄", index = 1)
+    public String age;
+
+    @ExcelProperty(value = "邮箱", index = 2)
+    public String email;
+
+    @ExcelProperty(value = "地址", index = 3)
+    public String address;
+
+    @ExcelProperty(value = "性别", index = 4)
+    public String sax;
+
+    @ExcelProperty(value = "高度", index = 5)
+    public String height;
+
+    @ExcelProperty(value = "备注", index = 6)
+    public String last;
+
+}

+ 201 - 0
alien-common-core/src/main/java/shop/alien/util/file/FileGetDir.java

@@ -0,0 +1,201 @@
+package shop.alien.util.file;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 获取文件夹下文件
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/2/13 11:25
+ */
+@Slf4j
+public class FileGetDir {
+
+    public static void main(String[] args) {
+//        String filePath = "E:\\Downloads\\ChromeDownloads\\picture";
+//        List<String> fileNames = FileGetDir.getFileNames(filePath);
+//        for (String fileName : fileNames) {
+//            System.out.println(fileName);
+//            //文件夹下所有webp转换为jpg
+//            WebpConvertUtil.webpConvertWithLocalPath(filePath + File.separator + fileName, "jpg");
+//        }
+        System.out.println(getLatestFolder("Z:\\douyin\\待处理图片", "image"));
+    }
+
+    /**
+     * 一个文件夹放多少文件
+     */
+    private static final Integer FOLDER_FILE_COUNT = 1000;
+
+    private static String imageCachePath = "";
+
+    private static String videoCachePath = "";
+
+    /**
+     * 得到文件名称
+     *
+     * @param path 路径
+     * @return List<String>
+     */
+    public static List<String> getFileNames(String path) {
+        File file = new File(path);
+        if (!file.exists()) {
+            return null;
+        }
+        List<String> fileNames = new ArrayList<>();
+        return getFilePath(file, fileNames);
+    }
+
+    /**
+     * 得到文件路径
+     *
+     * @param file      文件
+     * @param fileNames 文件名
+     * @return List<String>
+     */
+    public static List<String> getFilePath(File file, List<String> fileNames) {
+        File[] files = file.listFiles();
+        assert files != null;
+        for (File f : files) {
+            if (f.isDirectory()) {
+                File[] folder = f.listFiles();
+                assert folder != null;
+                if (folder.length == 0) {
+                    f.delete();
+                } else {
+                    getFilePath(f, fileNames);
+                }
+            } else {
+                fileNames.add(f.getAbsolutePath());
+            }
+        }
+        return fileNames;
+    }
+
+    /**
+     * 初始化缓存路径
+     */
+    public static void initCachePath() {
+        imageCachePath = "";
+        videoCachePath = "";
+    }
+
+    /**
+     * 获取可以存储的文件夹
+     *
+     * @param path     可存储的路径(c:/待处理图片/视频)
+     * @param fileType 文件类型
+     * @return 文件夹路径
+     */
+    public static String getLatestFolder(String path, String fileType) {
+        log.info("FileGetDir.getLatestFolder?path={}&fileType={}", path, fileType);
+        try {
+            //1.判断/创建,待处理图片/视频
+            File parentPath = new File(path);
+            if (!parentPath.exists()) {
+                parentPath.mkdir();
+            }
+            //2.判断文件夹数量
+            File[] parentPathFiles = parentPath.listFiles();
+            if (null == parentPathFiles || parentPathFiles.length == 0) {
+                //2.1.主文件夹为空, 创建名为1的文件夹, 缓存
+                String firstFolderName = path + "/" + 1;
+                FileUtil.mkdir(firstFolderName);
+                cacheFolderType(firstFolderName, fileType);
+                return firstFolderName;
+            } else {
+                String resultPath = "";
+                //2.2.1.主文件夹不为空, 从缓存获取, 返回
+                if ("video".equals(fileType) && !videoCachePath.isEmpty()) {
+                    resultPath = videoCachePath;
+                    File file = new File(videoCachePath);
+                    File[] files = file.listFiles();
+                    if (files.length < FOLDER_FILE_COUNT) {
+                        log.info("当前文件夹: {}, 文件夹内容数量: {}", videoCachePath, files.length);
+                        return videoCachePath;
+                    }
+                } else if ("image".equals(fileType) && !imageCachePath.isEmpty()) {
+                    resultPath = imageCachePath;
+                    File file = new File(imageCachePath);
+                    File[] files = file.listFiles();
+                    if (files.length < FOLDER_FILE_COUNT) {
+                        log.info("当前文件夹: {}, 文件夹内容数量: {}", imageCachePath, files.length);
+                        return imageCachePath;
+                    }
+                }
+                //2.2.2.不为空, 缓存为空, 排序, 获取不满1k的文件夹, 缓存
+                if (resultPath.isEmpty()) {
+                    FileUtil.SortFileName(parentPathFiles);
+                    for (File f : parentPathFiles) {
+                        if (f.isDirectory()) {
+                            int length = Objects.requireNonNull(f.list()).length;
+                            log.info("当前文件夹: {}, 文件夹内容数量: {}", f.getAbsolutePath(), length);
+                            if (length < FOLDER_FILE_COUNT) {
+                                resultPath = f.getAbsolutePath();
+                                break;
+                            }
+                        }
+                    }
+                    //主文件夹不为空, 缓存没有文件夹, 所有文件夹都满了
+                    if (resultPath.isEmpty()) {
+                        int size = parentPathFiles.length + 1;
+                        String newPath = path + "/" + size;
+                        cacheFolderType(newPath, fileType);
+                        return newPath;
+                    } else {
+                        cacheFolderType(resultPath, fileType);
+                        return resultPath;
+                    }
+                } else {
+                    //2.2.3.不为空, 缓存不为空, 排序, 所有文件夹都大于1K
+                    //2.2.3.1.缓存最新的路径+1, 缓存
+                    resultPath = resultPath.replace("\\", "/");
+                    String folderNum = resultPath.substring(resultPath.lastIndexOf("/") + 1);
+                    int count = Integer.parseInt(folderNum) + 1;
+                    //c:/待处理图片/latest+1
+                    resultPath = resultPath.substring(0, resultPath.lastIndexOf("/") + 1) + count;
+                    File file = new File(resultPath);
+                    if (file.exists() && file.listFiles().length >= FOLDER_FILE_COUNT) {
+                        //清空缓存的路径, 不然会一直循环
+                        if ("video".equals(fileType)) {
+                            videoCachePath = "";
+                        }
+                        if ("image".equals(fileType)) {
+                            imageCachePath = "";
+                        }
+                        getLatestFolder(path, fileType);
+                    } else {
+                        FileUtil.mkdir(resultPath);
+                        cacheFolderType(resultPath, fileType);
+                        return resultPath;
+                    }
+                }
+            }
+        } catch (RuntimeException e) {
+            log.error("FileGetDir getLatestFolder ERROR: {}", e.getMessage());
+            return null;
+        }
+        return null;
+    }
+
+    /**
+     * 缓存文件夹路径
+     *
+     * @param path     路径
+     * @param fileType 文件类型
+     */
+    public static void cacheFolderType(String path, String fileType) {
+        if ("video".equals(fileType)) {
+            videoCachePath = path;
+        } else if ("image".equals(fileType)) {
+            imageCachePath = path;
+        }
+    }
+
+}

+ 43 - 0
alien-common-core/src/main/java/shop/alien/util/file/FileSizeCalcUtil.java

@@ -0,0 +1,43 @@
+package shop.alien.util.file;
+
+import java.text.DecimalFormat;
+
+/**
+ * 文件大小计算工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/5/7 15:03
+ */
+public class FileSizeCalcUtil {
+
+    /**
+     * 计算文件大小
+     *
+     * @return 返回合适的单位大小
+     */
+    public static String fileSizeCalculation(Double fileSize) {
+        //大于10位转换为GB
+        int gb = 10;
+        //大于7位转换为MB
+        int mb = 7;
+        //取小数点后两位
+        DecimalFormat df = new DecimalFormat("0.000");
+        String lastSize;
+        String sizeToString = df.format(fileSize);
+        //判断文件大小
+        String str = sizeToString.substring(0, sizeToString.indexOf("."));
+        if (str.length() >= gb) {
+            fileSize = fileSize / 1024 / 1024 / 1024;
+            lastSize = df.format(fileSize) + "GB";
+        } else if (str.length() >= mb) {
+            fileSize = fileSize / 1024 / 1024;
+            lastSize = df.format(fileSize) + "MB";
+        } else {
+            //转换为KB
+            fileSize = fileSize / 1024;
+            lastSize = df.format(fileSize) + "KB";
+        }
+        return lastSize;
+    }
+}

+ 514 - 0
alien-common-core/src/main/java/shop/alien/util/file/FileUtil.java

@@ -0,0 +1,514 @@
+package shop.alien.util.file;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 文件工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/5/9 14:34
+ */
+@Slf4j
+public class FileUtil {
+
+    public static void main(String[] args) {
+        String folderName = "1";
+        String path = "Y:\\HHH\\123分类\\" + folderName;
+        String videoPath = "Y:\\HHH\\123分类\\" + folderName + "\\-视频";
+//        updateFileNameByLastModified(path);
+//        updateFileNameByLastModified(videoPath);
+        updateFileNameByLastModified("Z:\\douyin\\视频");
+        updateFileNameByLastModified("Z:\\douyin\\图片");
+    }
+
+    /**
+     * 复制文件
+     *
+     * @param sourcePath 原文件路径
+     * @param targetPath 目标文件路径
+     * @param fileName   文件名
+     */
+    public static boolean copyFile(String sourcePath, String targetPath, String fileName) {
+        try {
+            //创建输入流对象
+            FileInputStream fis = new FileInputStream(sourcePath + File.separator + fileName);
+            //创建输出流对象
+            FileOutputStream fos = new FileOutputStream(targetPath + File.separator + fileName);
+            //创建搬运工具
+            byte[] bytes = new byte[1024 * 8];
+            //创建长度
+            int len;
+            //循环读取数据
+            while ((len = fis.read(bytes)) != -1) {
+                fos.write(bytes, 0, len);
+            }
+            //释放资源
+            fos.close();
+            fis.close();
+            return true;
+        } catch (Exception e) {
+            log.error("FileUtil/copyFile ERROR Msg={}", e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 移动文件
+     *
+     * @param sourcePath 源文件全路径
+     * @param targetPath 目标文件全路径
+     * @return boolean
+     */
+    public static Boolean moveFile(String sourcePath, String targetPath) {
+        sourcePath = sourcePath.replace("\\", "/");
+        targetPath = targetPath.replace("\\", "/");
+        String fileName = sourcePath.substring(sourcePath.lastIndexOf("/") + 1);
+        return moveFile(sourcePath.substring(0, sourcePath.lastIndexOf("/")), targetPath, fileName);
+    }
+
+    /**
+     * 移动文件
+     *
+     * @param sourcePath 原文件路径
+     * @param targetPath 目标文件路径
+     * @param fileName   文件名
+     * @return boolean
+     */
+    public static Boolean moveFile(String sourcePath, String targetPath, String fileName) {
+        try {
+            boolean flag = false;
+            String newFileName = "";
+            //创建输入流对象
+            FileInputStream fis = new FileInputStream(sourcePath + "/" + fileName);
+            File targetFile = new File(targetPath + "/" + fileName);
+            if (targetFile.exists()) {
+                String fileType = fileName.substring(fileName.lastIndexOf("."));
+                String fileRealName = fileName.substring(0, fileName.lastIndexOf("."));
+                int randomMun = (int) (Math.random() * 10000);
+                newFileName = fileRealName + "_" + randomMun + fileType;
+            }
+            //创建输出流对象
+            FileOutputStream fos = new FileOutputStream(targetPath + "/" + newFileName);
+            //创建搬运工具
+            byte[] bytes = new byte[1024 * 8];
+            //创建长度
+            int len;
+            //循环读取数据
+            while ((len = fis.read(bytes)) != -1) {
+                fos.write(bytes, 0, len);
+            }
+            //释放资源
+            fos.close();
+            fis.close();
+            //删除源文件
+            if (new File(sourcePath + "/" + fileName).delete()) {
+                flag = true;
+            }
+            return flag;
+        } catch (Exception e) {
+            log.error("FileUtil/moveFile ERROR Msg={}", e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 使用Apache Commons IO库移动文件
+     *
+     * @param sourcePath 源路径
+     * @param targetPath 目标路径
+     * @param fileName   文件名
+     * @return boolean
+     */
+    public static boolean apacheMoveFile(String sourcePath, String targetPath, String fileName) {
+        try {
+            if (null == sourcePath || sourcePath.isEmpty()) {
+                throw new Exception("移动文件错误, 原路径为空");
+            }
+            if (null == targetPath || targetPath.isEmpty()) {
+                throw new Exception("移动文件错误, 目标路径为空");
+            }
+            if (!new File(targetPath).exists()) {
+                new File(targetPath).mkdirs();
+            }
+            File sourceFile = new File(sourcePath + "/" + fileName);
+            File destFile = new File(targetPath + "/" + fileName);
+            if (destFile.exists()) {
+                String fileType = fileName.substring(fileName.lastIndexOf(".") + 1);
+                String fileRealName = fileName.substring(0, fileName.lastIndexOf("."));
+                int randomMun = (int) (Math.random() * 10000);
+                fileName = fileRealName + "_" + randomMun + fileType;
+                destFile = new File(targetPath + "/" + fileName);
+            }
+            FileUtils.moveFile(sourceFile, destFile);
+            return true;
+        } catch (Exception e) {
+            log.error("FileUtil/apacheMoveFile ERROR Msg={}", e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param filePath 文件路径
+     */
+    public static boolean deleteFile(String filePath) {
+        log.info("FileUtil/deleteFile path: {}", filePath);
+        try {
+            File file = new File(filePath);
+            if (file.exists()) {
+                return file.delete();
+            }
+            return false;
+        } catch (Exception e) {
+            log.error("FileUtil/deleteFile ERROR Msg={}", e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 判断文件夹名称是否合法
+     *
+     * @param folderName 文件夹名称
+     * @return result
+     */
+    public static boolean folderNameTest(String folderName) {
+        boolean result = true;
+        String[] letters = new String[]{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"};
+        for (String letter : letters) {
+            if (folderName.contains(letter)) {
+                result = false;
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 替换文件夹非法字符
+     *
+     * @param folderName 文件夹名称
+     * @return result
+     */
+    public static String folderSymbol(String folderName) {
+        String symbol = "";
+        String[] letters = new String[]{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"};
+        for (String letter : letters) {
+            if (folderName.contains(letter)) {
+                symbol = letter;
+                break;
+            }
+        }
+        return symbol;
+    }
+
+    /**
+     * 根据文件修改时间修改文件名
+     *
+     * @param path 处理路径
+     * @return boolean
+     */
+    public static boolean updateFileNameByLastModified(String path) {
+        boolean flag = false;
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
+            File file = new File(path);
+            List<String> fileNameList = new ArrayList<>();
+            if (file.isDirectory()) {
+                //获取当前文件夹中所有文件名
+                File[] files = file.listFiles();
+                //判断文件夹不为空
+                if (null != files && files.length > 0) {
+                    //给定路径目录进行排序
+                    SortFileName(files);
+                    //存储文件名
+                    for (File oneFile : files) {
+                        fileNameList.add(oneFile.getName());
+                    }
+                    //获取文件名并修改
+                    for (int i = 0; i < files.length; i++) {
+                        String fileName = files[i].getName();
+                        if (files[i].isDirectory()) {
+                            System.err.println("===================================");
+                            System.err.println(fileName + ": 为文件夹, 跳过并继续");
+                            System.err.println("===================================");
+                            continue;
+                        }
+                        long lastModified = files[i].lastModified();
+                        Date date = new Date(lastModified);
+                        String format = sdf.format(date);
+                        System.out.println("原文件名: " + fileName);
+                        String fileType = fileName.substring(fileName.lastIndexOf("."));
+                        //判断文件名是否存在
+                        String lastFileName = renameFile(fileNameList, format, fileType);
+                        System.out.println("新文件名: " + lastFileName);
+                        //修改暂存文件名
+                        fileNameList.set(i, lastFileName);
+                        boolean result = new File(path + File.separator + fileName).renameTo(new File(path + File.separator + lastFileName));
+                        if (!result) {
+                            log.error("FileUtil/updateFileNameByLastModified 修改文件名错误,原文件名: " + fileName);
+                            break;
+                        } else {
+                            log.info("FileUtil/updateFileNameByLastModified: " + fileName + "\t>>>\t" + lastFileName);
+                        }
+                        System.out.println("===================================");
+                    }
+                    flag = true;
+                }
+            }
+            return flag;
+        } catch (Exception e) {
+            log.error("FileUtil/updateFileNameByLastModified ERROR Msg={}", e.getMessage());
+            return flag;
+        }
+    }
+
+    /**
+     * 递归处理文件名
+     *
+     * @param fileNameList 文件名list
+     * @param fileName     文件名
+     * @param fileType     文件类型
+     * @return 文件名
+     */
+    private static String renameFile(List<String> fileNameList, String fileName, String fileType) {
+        String lastFileName = fileName + fileType;
+        if (fileName.length() == 15) {
+            fileName += "_0000";
+            //文件名不存在, 拼接后返回
+            lastFileName = fileName + fileType;
+            renameFile(fileNameList, fileName, fileType);
+        }
+        //判断文件名是否存在,包括0000
+        if (fileNameList.contains(lastFileName) || fileNameList.contains(fileName + "_0000" + fileType)) {
+            //相同时间存在多个,第二个加千位数后缀
+            if (fileName.length() <= 15) {
+                fileName += "_0001";
+                //递归查询是否存在
+                lastFileName = renameFile(fileNameList, fileName, fileType);
+            } else {
+                //三次+后, 获取后缀+1,并格式化为千位
+                String fileStart = fileName.substring(0, 15);
+                String num_str = fileName.substring(16);
+                int num = Integer.parseInt(num_str);
+                num += 1;
+                if (num < 10) {
+                    fileName = fileStart + "_" + "000" + num;
+                } else if (num < 100) {
+                    fileName = fileStart + "_" + "00" + num;
+                } else if (num < 1000) {
+                    fileName = fileStart + "_" + "0" + num;
+                } else {
+                    fileName = fileStart + "_" + num;
+                }
+                //递归查询是否存在
+                lastFileName = renameFile(fileNameList, fileName, fileType);
+            }
+        }
+        return lastFileName;
+    }
+
+    /**
+     * 对某文件夹下的文件名像win里一样排序
+     *
+     * @param fileNames 文件名
+     */
+    private static void SortFileName(String[] fileNames) {
+        Arrays.sort(fileNames, new Comparator<String>() {
+            @Override
+            public int compare(String s1, String s2) {
+                if (returnDouble(s1) < returnDouble(s2)) return -1;
+                else if (returnDouble(s1) > returnDouble(s2)) return 1;
+                else return 0;
+            }
+
+            public double returnDouble(String str) {
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < str.length(); i++) {
+                    if (Character.isDigit(str.charAt(i))) sb.append(str.charAt(i));
+                    else if (str.charAt(i) == '.' && i < str.length() - 1 && Character.isDigit(str.charAt(i + 1)))
+                        sb.append(str.charAt(i));
+                    else break;
+                }
+                if (sb.toString().isEmpty()) return 0;
+                else return Double.parseDouble(sb.toString());
+            }
+
+        });
+    }
+
+    /**
+     * 对某文件夹下的文件名像win里一样排序
+     *
+     * @param fileNames 文件
+     */
+    public static void SortFileName(File[] fileNames) {
+        Arrays.sort(fileNames, new Comparator<File>() {
+            @Override
+            public int compare(File s1, File s2) {
+                if (returnDouble(s1) < returnDouble(s2)) return -1;
+                else if (returnDouble(s1) > returnDouble(s2)) return 1;
+                else return 0;
+            }
+
+            public double returnDouble(File file) {
+                StringBuilder sb = new StringBuilder();
+                String str = file.getName();
+                for (int i = 0; i < str.length(); i++) {
+                    if (Character.isDigit(str.charAt(i))) sb.append(str.charAt(i));
+                    else if (str.charAt(i) == '.' && i < str.length() - 1 && Character.isDigit(str.charAt(i + 1)))
+                        sb.append(str.charAt(i));
+                    else break;
+                }
+                if (sb.toString().isEmpty()) return 0;
+                else return Double.parseDouble(sb.toString());
+            }
+
+        });
+    }
+
+    /**
+     * 递归获取某路径下的所有文件,文件夹,并输出
+     */
+    public static void getFiles(String path) {
+        File file = new File(path);
+        // 如果这个路径是文件夹
+        if (file.isDirectory()) {
+            // 获取路径下的所有文件
+            File[] files = file.listFiles();
+            assert files != null;
+            for (File value : files) {
+                // 如果还是文件夹 递归获取里面的文件 文件夹
+                if (value.isDirectory()) {
+                    System.out.println("目录:" + value.getPath());
+                    getFiles(value.getPath());
+                } else {
+                    System.out.println("文件:" + value.getPath());
+                }
+            }
+        } else {
+            System.out.println("文件:" + file.getPath());
+        }
+    }
+
+    /**
+     * 判断文件夹是否存在
+     *
+     * @param path 文件夹路径
+     * @return 是否存在
+     */
+    public static boolean folderExist(String path) {
+        return new File(path).exists();
+    }
+
+    /**
+     * 创建文件夹
+     *
+     * @param path 文件夹路径
+     * @return 是否成功
+     */
+    public static boolean mkdir(String path) {
+        return new File(path).mkdir();
+    }
+
+    /**
+     * 获取文件大小
+     *
+     * @param filePath 文件路径
+     * @return 字节
+     */
+    public static Long getFileSize(String filePath) {
+        Path path = Paths.get(filePath);
+        try {
+            return Files.size(path);
+        } catch (IOException e) {
+            log.error("FileUtil.getFileSize 获取文件大小失败: {}", e.getMessage());
+            return 0L;
+        }
+    }
+
+    /**
+     * 获取文件大小
+     *
+     * @param file 文件
+     * @return 字节
+     */
+    public static Long getFileSize(File file) {
+        return file.length();
+    }
+
+    /**
+     * 反斜杠转换为正斜杠
+     *
+     * @param filePath 路径
+     * @return 替换后路径
+     */
+    public static String filePathConvert(String filePath) {
+        return filePath.replace("\\", "/");
+    }
+
+
+    /**
+     * 文件转换
+     *
+     * @param multipartFile 前端上传的文件
+     * @return File
+     */
+    public static File convert(MultipartFile multipartFile) {
+        try {
+            // 创建一个临时文件
+            File tempFile = File.createTempFile("upload-", ".tmp");
+            // 将MultipartFile内容转存到临时文件中
+            multipartFile.transferTo(tempFile);
+            return tempFile;
+        } catch (IOException e) {
+            log.error("FileUtil.convert ERROR: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 获取文件名称和类型
+     *
+     * @param multipartFile
+     * @return
+     */
+    public static Map<String, String> getFileNameAndType(MultipartFile multipartFile) {
+        String originalFilename = multipartFile.getOriginalFilename();
+        Map<String, String> map = new HashMap<>();
+        String fileName = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
+        String fileType = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
+        map.put("name", fileName);
+        map.put("type", fileType);
+        return map;
+    }
+
+    /**
+     * 获取文件名称和类型
+     *
+     * @param file
+     * @return
+     */
+    public static Map<String, String> getFileNameAndType(File file) {
+        String originalFilename = file.getName();
+        Map<String, String> map = new HashMap<>();
+        String fileName = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
+        String fileType = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
+        map.put("name", fileName);
+        map.put("type", fileType);
+        return map;
+    }
+}

+ 174 - 0
alien-common-core/src/main/java/shop/alien/util/generator/CodeGenerator.java

@@ -0,0 +1,174 @@
+package shop.alien.util.generator;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.generator.AutoGenerator;
+import com.baomidou.mybatisplus.generator.InjectionConfig;
+import com.baomidou.mybatisplus.generator.config.*;
+import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
+import com.baomidou.mybatisplus.generator.config.po.TableFill;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
+import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
+import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * 代码生成器
+ */
+public class CodeGenerator {
+
+    /**
+     * <p>
+     * 读取控制台内容
+     * </p>
+     */
+    public static String scanner(String tip) {
+        Scanner scanner = new Scanner(System.in);
+        StringBuilder help = new StringBuilder();
+        help.append("请输入" + tip + ":");
+        System.out.println(help.toString());
+        if (scanner.hasNext()) {
+            String ipt = scanner.next();
+            if (StringUtils.isNotEmpty(ipt)) {
+                return ipt;
+            }
+        }
+        throw new MybatisPlusException("请输入正确的" + tip + "!");
+    }
+
+    /**
+     * 自动生成代码
+     */
+    public static void main(String[] args) {
+        // 代码生成器
+        AutoGenerator mpg = new AutoGenerator();
+        // TODO 全局配置
+        GlobalConfig gc = new GlobalConfig();
+        final String projectPath = System.getProperty("user.dir");
+        // 生成文件的输出目录【默认 D 盘根目录】
+        gc.setOutputDir(projectPath + "/src/main/java");
+        // 作者
+        gc.setAuthor("ssk");
+        // 是否打开输出目录
+        gc.setOpen(false);
+        // controller 命名方式,注意 %s 会自动填充表实体属性
+        gc.setControllerName("%sController");
+        // service 命名方式
+        gc.setServiceName("%sService");
+        // serviceImpl 命名方式
+        gc.setServiceImplName("%sServiceImpl");
+        // mapper 命名方式
+        gc.setMapperName("%sMapper");
+        // xml 命名方式
+        gc.setXmlName("%sMapper");
+        // 开启 swagger2 模式
+        gc.setSwagger2(true);
+        // 是否覆盖已有文件
+        gc.setFileOverride(true);
+        // 是否开启 ActiveRecord 模式
+        gc.setActiveRecord(true);
+        // 是否在xml中添加二级缓存配置
+        gc.setEnableCache(false);
+        // 是否开启 BaseResultMap
+        gc.setBaseResultMap(true);
+        // XML columList
+        gc.setBaseColumnList(true);
+        // 全局 相关配置
+        mpg.setGlobalConfig(gc);
+
+        // TODO 数据源配置
+        DataSourceConfig dsc = new MyDataSourceConfig();
+        dsc.setUrl("jdbc:mysql://xiaokuihua.fun:3006/sunflower_assistant?serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false");
+        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
+        dsc.setUsername("root");
+        dsc.setPassword(".,ZyL021405");
+        dsc.setDbType(DbType.MYSQL);
+
+        // 自定义数据库表字段类型转换【可选】
+        dsc.setTypeConvert(new MySqlTypeConvert() {
+            @Override
+            public DbColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
+                // bigint转换成Long
+                if (fieldType.toLowerCase().contains("bigint")) {
+                    return DbColumnType.LONG;
+                }
+                if (fieldType.toLowerCase().contains("datetime")) {
+                    return DbColumnType.DATE;
+                }
+                return (DbColumnType) super.processTypeConvert(globalConfig, fieldType);
+            }
+        });
+        mpg.setDataSource(dsc);
+        // TODO 包配置
+        PackageConfig pc = new PackageConfig();
+        // 父包名。如果为空,将下面子包名必须写全部, 否则就只需写子包名
+        pc.setParent("cc.xiaokuihua.xiaokuihuaentity.generator");
+        // Entity包名
+        pc.setEntity("entity");
+        // Service包名
+        pc.setService("service");
+        // Service Impl包名
+        pc.setServiceImpl("service.impl");
+        mpg.setPackageInfo(pc);
+
+        // TODO 自定义配置
+        InjectionConfig cfg = new InjectionConfig() {
+            @Override
+            public void initMap() {
+                // to do nothing
+            }
+        };
+        // 输出文件配置
+        List<FileOutConfig> focList = new ArrayList<>();
+        focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
+            @Override
+            public String outputFile(TableInfo tableInfo) {
+                // 自定义输入文件名称
+                return projectPath + "xiaokuihua-entity/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper.xml";
+            }
+        });
+
+        // 自定义输出文件
+        cfg.setFileOutConfigList(focList);
+        mpg.setCfg(cfg);
+        mpg.setTemplate(new TemplateConfig().setXml(null));
+
+        // TODO 策略配置
+        StrategyConfig strategy = new StrategyConfig();
+        strategy.setRestControllerStyle(true);
+        // 数据库表映射到实体的命名策略,驼峰原则
+        strategy.setNaming(NamingStrategy.underline_to_camel);
+        // 字数据库表字段映射到实体的命名策略,驼峰原则
+        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
+        // 实体是否生成 serialVersionUID
+        strategy.setEntitySerialVersionUID(true);
+        // 是否生成实体时,生成字段注解
+        strategy.setEntityTableFieldAnnotationEnable(true);
+        // 使用lombok
+        strategy.setEntityLombokModel(true);
+        // 设置逻辑删除键
+        strategy.setLogicDeleteFieldName("delete_flag");
+        List<TableFill> list = new ArrayList<>();
+        TableFill createTime = new TableFill("created_time", FieldFill.INSERT);
+        TableFill updateTime = new TableFill("updated_time", FieldFill.INSERT_UPDATE);
+        list.add(createTime);
+        list.add(updateTime);
+        strategy.setTableFillList(list);
+        // TODO 指定生成的bean的数据库表名
+        strategy.setInclude("msg_timed_remind");
+//        strategy.setTablePrefix("T_");
+        // 驼峰转连字符
+        strategy.setControllerMappingHyphenStyle(true);
+        mpg.setStrategy(strategy);
+        // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
+        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
+        mpg.execute();
+    }
+}
+

+ 24 - 0
alien-common-core/src/main/java/shop/alien/util/generator/MyDMQuery.java

@@ -0,0 +1,24 @@
+package shop.alien.util.generator;
+
+import com.baomidou.mybatisplus.generator.config.querys.DMQuery;
+
+/**
+ * @Author: liwanshan
+ * @Date: 2021/10/15/14:27
+ * @Description:
+ */
+public class MyDMQuery extends DMQuery {
+    private int flag = 1;
+    @Override
+    public String tableName() {
+        if (flag == 1) {
+            flag++;
+            // 有歧义的列名[TABLE_NAME] 解决方案
+            return "T1.TABLE_NAME";
+        } else {
+            // 列无效 解决方案
+            return "TABLE_NAME";
+        }
+    }
+}
+

+ 52 - 0
alien-common-core/src/main/java/shop/alien/util/generator/MyDataSourceConfig.java

@@ -0,0 +1,52 @@
+package shop.alien.util.generator;
+
+
+import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
+import com.baomidou.mybatisplus.generator.config.IDbQuery;
+import com.baomidou.mybatisplus.generator.config.querys.*;
+
+/**
+ * @Author: liwanshan
+ * @Date: 2021/10/15/14:27
+ * @Description:
+ */
+public class MyDataSourceConfig extends DataSourceConfig {
+
+    @Override
+    public IDbQuery getDbQuery() {
+        switch(this.getDbType()) {
+            case ORACLE:
+                setDbQuery(new OracleQuery());
+                break;
+            case SQL_SERVER:
+                setDbQuery(new SqlServerQuery());
+                break;
+            case POSTGRE_SQL:
+                setDbQuery(new PostgreSqlQuery());
+                break;
+            case DB2:
+                setDbQuery(new DB2Query());
+                break;
+            case MARIADB:
+                setDbQuery(new MariadbQuery());
+                break;
+            case H2:
+                setDbQuery(new H2Query());
+                break;
+            case SQLITE:
+                setDbQuery(new SqliteQuery());
+                break;
+            case DM:
+                // 重新指定自定义的DMQuery
+                setDbQuery(new MyDMQuery());
+                break;
+//            case KINGBASE_ES:
+//                setDbQuery(new KingbaseESQuery());
+//                break;
+            default:
+                setDbQuery(new MySqlQuery());
+        }
+        return super.getDbQuery();
+    }
+}
+

+ 127 - 0
alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpGetRequestUtil.java

@@ -0,0 +1,127 @@
+package shop.alien.util.httpsend.apache;
+
+import com.alibaba.fastjson2.JSONObject;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * Get请求
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/4/22 10:37
+ */
+public class HttpGetRequestUtil {
+    public static void main(String[] args) {
+        //请求参数
+        Map<String, String> map = new HashMap<>();
+//        map.put("app_id", "ej6plljgwnwcvl8u");
+//        map.put("app_secret", "UUUrYjhCL3RLQjFpZlBBbGd3cHFKZz09");
+//        String result = getRequest(map, "https://www.mxnzp.com/api/holiday/single/20200422");
+
+//        map.put("msgtype", "text");
+//        map.put("text", "");
+        Map<String, String> headerMap = new HashMap<>();
+//        String url = "https://oapi.dingtalk.com/robot/send?access_token=8c90fb3b08a0126d0181faf1cd7597fb5eae078a522b59de28b0882575eb4196";
+        String url = "https://geoapi.qweather.com/v2/city/lookup";
+        map.put("location", "大连");
+        map.put("key", "109ef16ad7a6448bbf3e670fa38792fa");
+        String result = getRequest(url, map, headerMap);
+        System.out.println(result);
+        JSONObject jsonObject = JSONObject.parseObject(result);
+        System.out.println(jsonObject);
+    }
+
+    /**
+     * Get请求
+     *
+     * @param url       请求url
+     * @param paramMap  请求参数
+     * @param headerMap 请求头
+     * @return 返回结果
+     */
+    public static String getRequest(String url, Map<String, String> paramMap, Map<String, String> headerMap) {
+        String result = "";
+        HttpGet get = new HttpGet(url);
+        try {
+            CloseableHttpClient httpClient = HttpClients.createDefault();
+            List<NameValuePair> params = setHttpParams(paramMap);
+            String param = URLEncodedUtils.format(params, StandardCharsets.UTF_8);
+            get.setURI(URI.create(url + "?" + param));
+            //设置Header
+            if (headerMap.size() > 0) {
+                Set<String> set = headerMap.keySet();
+                for (String next : set) {
+                    get.setHeader(next, headerMap.get(next));
+                }
+            }
+            HttpResponse response = httpClient.execute(get);
+            result = getHttpEntityContent(response);
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+                result = "服务器异常";
+            }
+        } catch (Exception e) {
+            System.out.println("请求异常");
+            throw new RuntimeException(e);
+        } finally {
+            get.abort();
+        }
+        return result;
+    }
+
+    /**
+     * 请求参数处理
+     *
+     * @param paramMap 请求参数
+     * @return 处理后参数
+     */
+    public static List<NameValuePair> setHttpParams(Map<String, String> paramMap) {
+        List<NameValuePair> params = new ArrayList<>();
+        Set<Map.Entry<String, String>> set = paramMap.entrySet();
+        for (Map.Entry<String, String> entry : set) {
+            params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+        }
+        return params;
+    }
+
+    /**
+     * 请求网络
+     *
+     * @param response 请求内容
+     * @return 返回结果
+     * @throws UnsupportedOperationException
+     * @throws IOException
+     */
+    public static String getHttpEntityContent(HttpResponse response) throws UnsupportedOperationException, IOException {
+        String result = "";
+        HttpEntity entity = response.getEntity();
+        if (entity != null) {
+            InputStream in = entity.getContent();
+            BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+            StringBuilder strber = new StringBuilder();
+            String line = null;
+            while ((line = br.readLine()) != null) {
+                strber.append(line + '\n');
+            }
+            br.close();
+            in.close();
+            result = strber.toString();
+        }
+        return result;
+    }
+}

+ 65 - 0
alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpGetWithUrl.java

@@ -0,0 +1,65 @@
+package shop.alien.util.httpsend.apache;
+
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * get请求url传参
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/7/2 14:13
+ */
+public class HttpGetWithUrl {
+
+    public static String GetUrl(String url, String param) {
+        //访问返回结果
+        StringBuilder result = new StringBuilder();
+        //读取访问结果
+        BufferedReader read = null;
+        try {
+            //创建url
+            URL realurl = new URL(url + "?" + param);
+            //打开连接
+            URLConnection connection = realurl.openConnection();
+            // 设置通用的请求属性
+            connection.setRequestProperty("accept", "*/*");
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("user-agent",
+                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            //建立连接
+            connection.connect();
+            // 获取所有响应头字段
+            Map<String, List<String>> map = connection.getHeaderFields();
+            // 遍历所有的响应头字段,获取到cookies等
+            for (String key : map.keySet()) {
+                System.out.println(key + "--->" + map.get(key));
+            }
+            // 定义 BufferedReader输入流来读取URL的响应
+            read = new BufferedReader(new InputStreamReader(
+                    connection.getInputStream(), StandardCharsets.UTF_8));
+            String line;//循环读取
+            while ((line = read.readLine()) != null) {
+                result.append(line);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            //关闭流
+            if (read != null) {
+                try {
+                    read.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return result.toString();
+    }
+}

+ 88 - 0
alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpPostRequestUtil.java

@@ -0,0 +1,88 @@
+package shop.alien.util.httpsend.apache;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Post请求
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/4/22 10:16
+ */
+public class HttpPostRequestUtil {
+
+    public static void main(String[] args) {
+        //请求参数
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("", "");
+        String url = "https://www.mxnzp.com/api";
+        url = url + "/verifycode/code?len=5&type=0";
+        Map<String, String> headerMap = new HashMap<>();
+        headerMap.put("app_id", "ej6plljgwnwcvl8u");
+        headerMap.put("app_secret", "UUUrYjhCL3RLQjFpZlBBbGd3cHFKZz09");
+        HttpPostRequestUtil httpPostRequestUtil = new HttpPostRequestUtil();
+        String result = httpPostRequestUtil.postRequest(url, jsonObject, headerMap);
+        System.out.println(result);
+    }
+
+    /**
+     * Post请求
+     *
+     * @param url        请求url
+     * @param jsonObject 请求参数
+     * @param headerMap  请求头
+     * @return 返回结果
+     */
+    public static String postRequest(String url, JSONObject jsonObject, Map<String, String> headerMap) {
+        String result;
+        HttpPost post = new HttpPost(url);
+        try {
+            CloseableHttpClient httpClient = HttpClients.createDefault();
+            //请求头
+            post.setHeader("Content-Type", "application/json;charset=utf-8");
+            //设置Header
+            if (!headerMap.isEmpty()) {
+                Set<String> set = headerMap.keySet();
+                for (String next : set) {
+                    post.setHeader(next, headerMap.get(next));
+                }
+            }
+            StringEntity postingString = new StringEntity(jsonObject.toString(), StandardCharsets.UTF_8);
+            post.setEntity(postingString);
+            HttpResponse response = httpClient.execute(post);
+            InputStream in = response.getEntity().getContent();
+            BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+            StringBuilder stringBuilder = new StringBuilder();
+            String line;
+            while ((line = br.readLine()) != null) {
+                stringBuilder.append(line).append('\n');
+            }
+            br.close();
+            in.close();
+            result = stringBuilder.toString();
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+                result = "服务器异常";
+            }
+        } catch (Exception e) {
+            System.out.println("请求异常");
+            throw new RuntimeException(e);
+        } finally {
+            post.abort();
+        }
+        return result;
+    }
+}

+ 67 - 0
alien-common-core/src/main/java/shop/alien/util/httpsend/apache/HttpPostWithForm.java

@@ -0,0 +1,67 @@
+package shop.alien.util.httpsend.apache;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 以表单形式发送post请求
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/7/2 14:54
+ */
+public class HttpPostWithForm {
+    /**
+     * 以form表单形式提交数据,发送post请求
+     *
+     * @param url       请求地址
+     * @param paramsMap 具体数据
+     * @return 服务器返回数据
+     */
+    public static String httpPostWithForm(String url, Map<String, String> paramsMap, Map<String, String> headerMap) {
+        // 用于接收返回的结果
+        String resultData = "";
+        try {
+            HttpPost post = new HttpPost(url);
+            List<BasicNameValuePair> pairList = new ArrayList<BasicNameValuePair>();
+            // 迭代Map-->取出key,value放到BasicNameValuePair对象中-->添加到list中
+            for (String key : paramsMap.keySet()) {
+                pairList.add(new BasicNameValuePair(key, paramsMap.get(key)));
+            }
+            UrlEncodedFormEntity uefe = new UrlEncodedFormEntity(pairList, "utf-8");
+            post.setEntity(uefe);
+            //设置Header
+            if (headerMap.size() > 0) {
+                Set<String> set = headerMap.keySet();
+                for (String next : set) {
+                    post.setHeader(next, headerMap.get(next));
+                }
+            }
+            // 创建一个http客户端
+            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+            // 发送post请求
+            HttpResponse response = httpClient.execute(post);
+            // 状态码为:200
+            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                // 返回数据:
+                resultData = EntityUtils.toString(response.getEntity(), "UTF-8");
+            } else {
+                throw new RuntimeException("接口连接失败!");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("接口连接失败!");
+        }
+        return resultData;
+    }
+}

+ 231 - 0
alien-common-core/src/main/java/shop/alien/util/httpsend/spring/GetMethod.java

@@ -0,0 +1,231 @@
+package shop.alien.util.httpsend.spring;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.springframework.stereotype.Component;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Get请求
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2022/8/8 11:02
+ */
+@Component
+@Slf4j
+public class GetMethod {
+
+    /**
+     * Get请求
+     *
+     * @param requestUrl 请求地址
+     * @param param      请求参数(a=a&b=b&c=c)
+     * @return String
+     */
+    public String get(String requestUrl, String param) {
+        //访问返回结果
+        StringBuilder result = new StringBuilder();
+        //读取访问结果
+        BufferedReader read = null;
+        try {
+            //创建url
+            URL url = new URL(requestUrl + "?" + param);
+            //打开连接
+            URLConnection connection = url.openConnection();
+            // 设置通用的请求属性
+            connection.setRequestProperty("accept", "*/*");
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            //建立连接
+            connection.connect();
+            // 获取所有响应头字段
+//            Map<String, List<String>> map = connection.getHeaderFields();
+            // 遍历所有的响应头字段,获取到cookies等
+//            for (String key : map.keySet()) {
+//                System.out.println(key + "--->" + map.get(key));
+//            }
+            // 定义 BufferedReader输入流来读取URL的响应
+            read = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
+            String line;
+            //循环读取
+            while ((line = read.readLine()) != null) {
+                result.append(line);
+            }
+        } catch (Exception e) {
+            log.error("GetMethod: " + e.getMessage());
+            return "9999";
+        } finally {
+            //关闭流
+            if (read != null) {
+                try {
+                    read.close();
+                } catch (Exception e) {
+                    log.error(e.getMessage());
+                }
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Get请求
+     *
+     * @param requestUrl 请求地址
+     * @param paramMap   请求参数(Map<String, String>)
+     * @return String
+     */
+    public String get(String requestUrl, Map<String, String> paramMap) {
+        String result = null;
+        HttpGet get = new HttpGet(requestUrl);
+        try {
+            CloseableHttpClient httpClient = HttpClients.createDefault();
+            List<NameValuePair> params = setHttpParams(paramMap);
+            String param = URLEncodedUtils.format(params, StandardCharsets.UTF_8);
+            get.setURI(URI.create(requestUrl + "?" + param));
+            HttpResponse response = httpClient.execute(get);
+            result = getHttpEntityContent(response);
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK && result == null) {
+                result = "服务器异常";
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        } finally {
+            get.abort();
+        }
+        return result;
+    }
+
+    /**
+     * Get请求
+     *
+     * @param requestUrl 请求地址
+     * @param paramMap   请求参数
+     * @param headerMap  请求头
+     * @return String
+     */
+    public String get(String requestUrl, Map<String, String> paramMap, Map<String, String> headerMap) {
+        String result = null;
+        HttpGet get = new HttpGet(requestUrl);
+        try {
+            CloseableHttpClient httpClient = HttpClients.createDefault();
+            List<NameValuePair> params = setHttpParams(paramMap);
+            String param = URLEncodedUtils.format(params, StandardCharsets.UTF_8);
+            get.setURI(URI.create(requestUrl + "?" + param));
+            //设置Header
+            if (!headerMap.isEmpty()) {
+                Set<String> set = headerMap.keySet();
+                for (String next : set) {
+//                    System.out.println("key为:" + next + ",value为:" + headerMap.get(next));
+                    get.setHeader(next, headerMap.get(next));
+                }
+            }
+            HttpResponse response = httpClient.execute(get);
+            result = getHttpEntityContent(response);
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK && result == null) {
+                result = "服务器异常";
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        } finally {
+            get.abort();
+        }
+        return result;
+    }
+
+
+    /**
+     * Get请求
+     *
+     * @param requestUrl 请求地址
+     * @param headerMap  请求头
+     * @return String
+     */
+    public String getWithHeader(String requestUrl, Map<String, String> headerMap) {
+        String result = null;
+        try {
+            HttpGet get = new HttpGet(requestUrl);
+            CloseableHttpClient httpClient = HttpClients.createDefault();
+            //设置Header
+            if (!headerMap.isEmpty()) {
+                Set<String> set = headerMap.keySet();
+                for (String next : set) {
+//                    System.out.println("key为:" + next + ",value为:" + headerMap.get(next));
+                    get.setHeader(next, headerMap.get(next));
+                }
+            }
+            HttpResponse response = httpClient.execute(get);
+            result = getHttpEntityContent(response);
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK && result == null) {
+                result = "服务器异常";
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 请求参数处理
+     *
+     * @param requestParam 请求参数
+     * @return 处理后参数
+     */
+    public List<NameValuePair> setHttpParams(Map<String, String> requestParam) {
+        List<NameValuePair> params = new ArrayList<>();
+        Set<Map.Entry<String, String>> set = requestParam.entrySet();
+        for (Map.Entry<String, String> entry : set) {
+            params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+        }
+        return params;
+    }
+
+    /**
+     * 请求网络
+     *
+     * @param response 请求内容
+     * @return 返回结果
+     */
+    public String getHttpEntityContent(HttpResponse response) {
+        try {
+            String result = "";
+            HttpEntity entity = response.getEntity();
+            if (entity != null) {
+                InputStream in = entity.getContent();
+                BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+                StringBuilder sb = new StringBuilder();
+                String line;
+                while ((line = br.readLine()) != null) {
+                    sb.append(line).append('\n');
+                }
+                br.close();
+                in.close();
+                result = sb.toString();
+            }
+            return result;
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+        return null;
+    }
+
+}

+ 101 - 0
alien-common-core/src/main/java/shop/alien/util/httpsend/spring/PostMethod.java

@@ -0,0 +1,101 @@
+package shop.alien.util.httpsend.spring;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * post请求
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2022/7/11 14:01
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class PostMethod {
+
+    private final RestTemplate restTemplate;
+
+    /**
+     * 发送post请求
+     *
+     * @param url    请求地址
+     * @param params 请求参数
+     * @return String
+     */
+    public String post(String url, String params) {
+        log.info("restTemplate:" + restTemplate + "url:" + url + "\t content:" + params);
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        HttpEntity<String> request = new HttpEntity<>(params, headers);
+        ResponseEntity<String> postForEntity = null;
+        try {
+            postForEntity = restTemplate.postForEntity(url, request, String.class);
+        } catch (Exception e) {
+            log.error("类:PostMethod 方法:post", e);
+        }
+        if (postForEntity != null) {
+            if (postForEntity.getStatusCode() == HttpStatus.OK) {
+                log.info("postForEntity.getBody()\t" + postForEntity.getBody());
+                return postForEntity.getBody();
+            } else {
+                log.error("http状态:" + postForEntity.getStatusCode());
+                return null;
+            }
+        } else {
+            return null;
+        }
+
+    }
+
+    /**
+     * post请求
+     *
+     * @param url       请求地址
+     * @param params    请求参数
+     * @param headerMap 请求头
+     * @return String
+     */
+    public String post(String url, String params, Map<String, String> headerMap) {
+        log.info("restTemplate:" + restTemplate + "url:" + url + "\t content:" + params);
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        //设置Header
+        if (headerMap.size() > 0) {
+            Set<String> set = headerMap.keySet();
+            for (String next : set) {
+                System.out.println("key为:" + next + ",value为:" + headerMap.get(next));
+                headers.set(next, headerMap.get(next));
+            }
+        }
+        HttpEntity<String> request = new HttpEntity<>(params, headers);
+        ResponseEntity<String> postForEntity = null;
+        try {
+            postForEntity = restTemplate.postForEntity(url, request, String.class);
+        } catch (Exception e) {
+            log.error("类:PostMethod 方法:post", e);
+        }
+        if (postForEntity != null) {
+            if (postForEntity.getStatusCode() == HttpStatus.OK) {
+                log.info("postForEntity.getBody()\t" + postForEntity.getBody());
+                return postForEntity.getBody();
+            } else {
+                log.error("http状态:" + postForEntity.getStatusCode());
+                return null;
+            }
+        } else {
+            return null;
+        }
+
+    }
+
+}

+ 60 - 0
alien-common-core/src/main/java/shop/alien/util/image/ImageUtil.java

@@ -0,0 +1,60 @@
+package shop.alien.util.image;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 图片工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2023/1/17 9:28
+ */
+@Slf4j
+public class ImageUtil {
+
+    public static void main(String[] args) {
+        System.out.println(getImageSizeInfo("C:\\Users\\17238\\Pictures\\Saved Pictures\\4255d6629aa94ff3926fed0bc43684dd_noop.jpg"));
+    }
+
+    /**
+     * 获取图片尺寸信息
+     *
+     * @param path 路径
+     * @return Map
+     */
+    public static Map<String, Object> getImageSizeInfo(String path) {
+        try {
+            if (null != path && !"".equals(path)) {
+                Map<String, Object> map = new HashMap<>();
+                // 文件对象
+                File file = new File(path);
+                // 文件大小;其中file.length()获取的是字节,除以1024可以得到以kb为单位的文件大小
+                double size = file.length() / 1024.0;
+                BigDecimal bigDecimal = new BigDecimal(size);
+                size = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+                map.put("size", size);
+                // 图片对象
+                BufferedImage bufferedImage = ImageIO.read(new FileInputStream(file));
+                // 宽度
+                int width = bufferedImage.getWidth();
+                map.put("width", width);
+                // 高度
+                int height = bufferedImage.getHeight();
+                map.put("height", height);
+                return map;
+            }
+            return null;
+        } catch (Exception e) {
+            log.error("ImageUtil/getImageSizeInfo ERROR Msg = {}", e.getMessage());
+            return null;
+        }
+    }
+}

+ 80 - 0
alien-common-core/src/main/java/shop/alien/util/image/WebpConvertUtil.java

@@ -0,0 +1,80 @@
+package shop.alien.util.image;
+
+import com.luciad.imageio.webp.WebPReadParam;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.FileImageInputStream;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.image.BufferedImage;
+import java.io.*;
+
+/**
+ * webp图片格式转换
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2023/5/6 9:43
+ */
+@Slf4j
+public class WebpConvertUtil {
+
+    public static void main(String[] args) throws IOException {
+        String jpg = webpConvertWithLocalPath("C:\\Users\\Administrator\\Pictures\\Camera Roll\\1.webp", "jpg");
+        System.out.println(jpg);
+    }
+
+    /**
+     * 获取本地webp文件转jpg/png到同文件夹
+     *
+     * @param filePath    文件路径
+     * @param convertType 转换格式(jpg,png)
+     * @return 新文件名
+     */
+    public static String webpConvertWithLocalPath(String filePath, String convertType) {
+        try {
+            //源文件路径+名+.
+            String sourceFilePath = filePath.substring(0, filePath.lastIndexOf(".") + 1);
+            File webpFile = new File(filePath);
+            ImageReader reader = ImageIO.getImageReadersByMIMEType("image/webp").next();
+            WebPReadParam readParam = new WebPReadParam();
+            readParam.setBypassFiltering(true);
+            reader.setInput(new FileImageInputStream(webpFile));
+            BufferedImage image = reader.read(0, readParam);
+            //源文件路径+新格式
+            File targetFile = new File(sourceFilePath + convertType);
+            ImageIO.write(image, convertType, targetFile);
+            return targetFile.getName();
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * 输入流Webp转jpg/png
+     *
+     * @param inputStream 输入流
+     * @param convertType 转换类型
+     * @return InputStream
+     */
+    public static InputStream webpConvertWithStream(InputStream inputStream, String convertType) {
+        try {
+            ByteArrayOutputStream bs = new ByteArrayOutputStream();
+            ImageOutputStream imOut = ImageIO.createImageOutputStream(bs);
+            ImageReader reader = ImageIO.getImageReadersByMIMEType("image/webp").next();
+            WebPReadParam readParam = new WebPReadParam();
+            readParam.setBypassFiltering(true);
+            ImageInputStream iis = ImageIO.createImageInputStream(inputStream);
+            reader.setInput(iis);
+            BufferedImage image = reader.read(0, readParam);
+            ImageIO.write(image, convertType, imOut);
+            return new ByteArrayInputStream(bs.toByteArray());
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+        return null;
+    }
+}

+ 245 - 0
alien-common-core/src/main/java/shop/alien/util/ip/IpAddressUtil.java

@@ -0,0 +1,245 @@
+package shop.alien.util.ip;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 根据IP地址获取详细的地域信息工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2019/12/27 10:50
+ */
+public class IpAddressUtil {
+    /**
+     * 测试
+     *
+     * @param args 主方法参数
+     */
+    public static void main(String[] args) {
+        //广州
+//        String ip = "219.136.134.157";
+        //大连
+//        String ip = "119.113.132.222";
+        //台湾
+        String ip = "34.80.132.13";
+        IpAddressUtil ipAddressUtils = new IpAddressUtil();
+        String address = ipAddressUtils.getAddresses(ip);
+        System.out.println(address);
+    }
+
+    /**
+     * 获取地区
+     *
+     * @param ip ip地址
+     * @return 地区
+     */
+
+    public String getAddresses(String ip) {
+        // 这里调用淘宝的接口
+        String urlStr = "http://ip.taobao.com/service/getIpInfo.php";
+        // 从http://whois.pconline.com.cn取得IP所在的省市区信息
+        ip = "ip=" + ip;
+        String returnStr = this.getResult(urlStr, ip);
+        if (returnStr != null) {
+            //处理返回的省市区信息
+            String[] temp = returnStr.split(",");
+            int size = 3;
+            if (temp.length < size) {
+                //无效IP,局域网测试
+                return "0";
+            }
+            //ip,1
+            String ipNo = "";
+            //国家,2
+            String country = "";
+            //区域,3
+            String area;
+            //省,4
+            String region = "";
+            //市,5
+            String city = "";
+            //地区,6
+            String county;
+            //运营商,bootstrap
+            String isp = "";
+            for (int i = 0; i < temp.length; i++) {
+                switch (i) {
+                    default:
+                        break;
+                    case 1:
+                        ipNo = (temp[i].split(":"))[2].replaceAll("\"", "");
+                        ipNo = decodeUnicode(ipNo);
+                        break;
+                    case 2:
+                        country = (temp[i].split(":"))[1].replaceAll("\"", "");
+                        country = decodeUnicode(country);
+                        break;
+                    case 3:
+                        area = (temp[i].split(":"))[1].replaceAll("\"", "");
+                        area = decodeUnicode(area);
+                        break;
+                    case 4:
+                        region = (temp[i].split(":"))[1].replaceAll("\"", "");
+                        region = decodeUnicode(region);
+                        break;
+                    case 5:
+                        city = (temp[i].split(":"))[1].replaceAll("\"", "");
+                        city = decodeUnicode(city);
+                        break;
+                    case 6:
+                        county = (temp[i].split(":"))[1].replaceAll("\"", "");
+                        county = decodeUnicode(county);
+                        break;
+                    case 7:
+                        isp = (temp[i].split(":"))[1].replaceAll("\"", "");
+                        isp = decodeUnicode(isp);
+                        break;
+                }
+            }
+            return country + "," + region + "," + city + "," + isp + "," + ipNo;
+        }
+        return null;
+    }
+
+    /**
+     * 请求服务
+     *
+     * @param urlStr  请求的地址
+     * @param content 请求的参数 格式为:name=xxx&pwd=xxx
+     * @return json串
+     */
+    public String getResult(String urlStr, String content) {
+        HttpURLConnection connection = null;
+        try {
+            URL url = new URL(urlStr);
+            // 新建连接实例
+            connection = (HttpURLConnection) url.openConnection();
+            // 设置连接超时时间,单位毫秒
+            connection.setConnectTimeout(2000);
+            // 设置读取数据超时时间,单位毫秒
+            connection.setReadTimeout(2000);
+            // 是否打开输出流true|false
+            connection.setDoOutput(true);
+            // 是否打开输入流true|false
+            connection.setDoInput(true);
+            // 提交方法POST|GET
+            connection.setRequestMethod("POST");
+            // 是否缓存true|false
+            connection.setUseCaches(false);
+            // 打开连接端口
+            connection.connect();
+            // 打开输出流往对端服务器写数据
+            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
+            // 写数据,也就是提交你的表单name=xxx&pwd=xxx
+            out.writeBytes(content);
+            // 刷新
+            out.flush();
+            // 关闭输出流
+            out.close();
+            // 往对端写完数据对端服务器返回数据
+            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
+            // 以BufferedReader流来读取
+            StringBuilder buffer = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                buffer.append(line);
+            }
+            reader.close();
+            return buffer.toString();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (connection != null) {
+                // 关闭连接
+                connection.disconnect();
+            }
+        }
+        return null;
+    }
+
+    /**
+     *     * unicode 转换成 中文
+     * <p>
+     *     *
+     * <p>
+     *     * @author fanhui 2007-3-15
+     * <p>
+     *     * @param theString
+     * <p>
+     *     * @return
+     * <p>
+     *    
+     */
+    public static String decodeUnicode(String theString) {
+        char aChar;
+        int len = theString.length();
+        StringBuilder outBuffer = new StringBuilder(len);
+        for (int x = 0; x < len; ) {
+            aChar = theString.charAt(x++);
+            if (aChar == '\\') {
+                aChar = theString.charAt(x++);
+                if (aChar == 'u') {
+                    int value = 0;
+                    int size = 4;
+                    for (int i = 0; i < size; i++) {
+                        aChar = theString.charAt(x++);
+                        switch (aChar) {
+                            case '0':
+                            case '1':
+                            case '2':
+                            case '3':
+                            case '4':
+                            case '5':
+                            case '6':
+                            case '7':
+                            case '8':
+                            case '9':
+                                value = (value << 4) + aChar - '0';
+                                break;
+                            case 'a':
+                            case 'b':
+                            case 'c':
+                            case 'd':
+                            case 'e':
+                            case 'f':
+                                value = (value << 4) + 10 + aChar - 'a';
+                                break;
+                            case 'A':
+                            case 'B':
+                            case 'C':
+                            case 'D':
+                            case 'E':
+                            case 'F':
+                                value = (value << 4) + 10 + aChar - 'A';
+                                break;
+                            default:
+                                throw new IllegalArgumentException("Malformed      encoding.");
+                        }
+                    }
+                    outBuffer.append((char) value);
+                } else {
+                    if (aChar == 't') {
+                        aChar = '\t';
+                    } else if (aChar == 'r') {
+                        aChar = '\r';
+                    } else if (aChar == 'n') {
+                        aChar = '\n';
+                    } else if (aChar == 'f') {
+                        aChar = '\f';
+                    }
+                    outBuffer.append(aChar);
+                }
+            } else {
+                outBuffer.append(aChar);
+            }
+        }
+        return outBuffer.toString();
+    }
+
+}

+ 47 - 0
alien-common-core/src/main/java/shop/alien/util/ip/IpConnectTestUtil.java

@@ -0,0 +1,47 @@
+package shop.alien.util.ip;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * 测试ip连接状态
+ *
+ * @author ssk
+ */
+public class IpConnectTestUtil {
+
+    public static void main(String[] args) {
+        ipTest("34.80.132.13", 443);
+    }
+
+    /**
+     * ip测试
+     *
+     * @param hostName ip地址
+     * @param port     端口号
+     * @return {true:成功;false:失败}
+     */
+    public static Boolean ipTest(String hostName, int port) {
+        Socket socket = new Socket();
+        try {
+            //建立连接
+            socket.connect(new InetSocketAddress(hostName, port), 5 * 1000);
+            //通过现有方法查看连通状态
+            boolean conn = socket.isConnected();
+            //true为连通
+            System.out.println("ip测试状态:" + conn);
+            return conn;
+        } catch (IOException e) {
+            //当连不通时,直接抛异常,异常捕获即可
+            System.err.println("ip测试状态:false");
+            return false;
+        } finally {
+            try {
+                socket.close();
+            } catch (IOException e) {
+                System.err.println("关闭ip测试失败");
+            }
+        }
+    }
+}

+ 369 - 0
alien-common-core/src/main/java/shop/alien/util/lunar/LunarUtils.java

@@ -0,0 +1,369 @@
+package shop.alien.util.lunar;
+
+import com.nlf.calendar.*;
+import com.nlf.calendar.util.HolidayUtil;
+
+import java.time.LocalDate;
+import java.util.*;
+
+/**
+ * 开源日期工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/4/23 11:41
+ */
+public class LunarUtils {
+
+    public static void main(String[] args) {
+//        System.out.println(getFestival("2025-05-12", true));
+
+//        isLeapYear("2024-05-01");
+
+//        getXingZuo("2024-05-01");
+
+//        System.out.println(getOldDate("2024-04-25"));
+//        getDayYiJi("2024-04-25");
+//        getJieQi("2024-04-25");
+//        System.out.println(getNextHoliday("2024-01-02", 1));
+//        String startDate = "2024-05-10-19-10-00";
+//        String endDate = "2024-05-10-19-10-01";
+//        System.out.println(isBefore(startDate, endDate));
+//        List<Holiday> festivalByYear = getFestivalByYear(LocalDate.now().getYear() + "", 0);
+//        for (Holiday holiday : festivalByYear) {
+//            System.out.println(holiday.getDay());
+//            System.out.println(holiday.getName());
+//        }
+//        System.out.println(getNextHoliday("2024-05-13", 0));
+
+//        List<Holiday> holidays = HolidayUtil.getHolidays("2025");
+//        System.out.println(holidays);
+//
+//        SolarDay solarDay = SolarDay.fromYmd(2023, 10, 1);
+//        LegalHoliday holiday = solarDay.getLegalHoliday();
+//        LegalHoliday legalHoliday = new LegalHoliday(2024, 11, 20,"2024-11-20");
+//        LegalHoliday next = legalHoliday.next(1);
+//        System.out.println(next);
+//        HolidayUtil.fix("202501010120250101202501261020250129202501281120250129202501291120250129202501301120250129202501311120250129202502011120250129202502021120250129202502031120250129202502041120250129202502081020250129202504042120250404202504052120250404202504062120250404202504273020250501202505013120250501202505023120250501202505033120250501202505043120250501202505053120250501202505314120250531202506014120250531202506024120250531202509287020251001202510017120251001202510027120251001202510037120251001202510047120251001202510057120251001202510067120251001202510077120251001202510087120251001202510117020251001");
+//        List<Holiday> holidays = HolidayUtil.getHolidays("2025");
+//        System.out.println(holidays);
+//
+//
+//        Map<String, Object> nextHoliday = getNextHoliday("2025-01-01", 0);
+//        System.out.println(nextHoliday);
+        System.out.println(getShuJiu("2024-12-31"));
+    }
+
+    public static Map<String, Object> getDateInfo(String date) {
+        Map<String, Object> resultMap = new HashMap<>();
+        Solar solar = setSolarDateFromYmd(date);
+        resultMap.put("week", solar.getWeek());
+        resultMap.put("week_zh", solar.getWeekInChinese());
+        return resultMap;
+    }
+
+    /**
+     * 时间比较
+     *
+     * @param startDate 开始时间
+     * @param endDate   结束时间
+     * @return boolean
+     */
+    public static boolean isBefore(String startDate, String endDate) {
+        Solar start = setDateFromYmdHms(startDate);
+        Solar end = setDateFromYmdHms(endDate);
+        return start.isBefore(end);
+    }
+
+    /**
+     * 是否闰年
+     *
+     * @param date 日期
+     * @return boolean
+     */
+    public static boolean isLeapYear(String date) {
+        Solar solar = setSolarDateFromYmd(date);
+        return solar.isLeapYear();
+    }
+
+    /**
+     * 获取节日
+     *
+     * @param date           查询日期(yyyy-mm-dd)
+     * @param otherFestivals (true:普通节日, false:其他纪念日)
+     * @return List<String>
+     */
+    public static List<String> getFestival(String date, boolean otherFestivals) {
+        Solar solar = setSolarDateFromYmd(date);
+        List<String> resultList;
+        if (otherFestivals) {
+            //返回节日的数组,包括元旦节、国庆节等,也包括母亲节、父亲节、感恩节、圣诞节等,有可能同一天有多个,也可能没有。
+            resultList = solar.getFestivals();
+        } else {
+            //返回其他纪念日的数组,例如世界抗癌日、香港回归纪念日等,有可能同一天有多个,也可能没有。
+            resultList = solar.getOtherFestivals();
+        }
+        return resultList;
+    }
+
+    /**
+     * 获取星座
+     *
+     * @param date 查询日期
+     * @return String
+     */
+    public static String getXingZuo(String date) {
+        Solar solar = setSolarDateFromYmd(date);
+        return solar.getXingZuo();
+    }
+
+    /**
+     * 获取农历日期信息
+     *
+     * @param date 阳历日期
+     * @return map
+     */
+    public static Map<String, Object> getOldDate(String date) {
+        Map<String, Object> resultMap = new HashMap<>();
+        Solar solar = setSolarDateFromYmd(date);
+        Lunar lunar = solar.getLunar();
+        String[] lunarStr = lunar.toFullString().split(" ");
+        resultMap.put("old_date", lunarStr[0].substring(5));
+        resultMap.put("old_year", lunarStr[1]);
+        return resultMap;
+    }
+
+    /**
+     * 获取每日宜忌
+     *
+     * @param date 阳历日期
+     * @return map
+     */
+    public static Map<String, Object> getDayYiJi(String date) {
+        Map<String, Object> resultMap = new HashMap<>();
+        Solar solar = setSolarDateFromYmd(date);
+        Lunar lunar = solar.getLunar();
+        List<String> dayYi = lunar.getDayYi();
+        resultMap.put("day_yi", dayYi.toString().substring(1, dayYi.toString().length() - 1));
+        List<String> dayJi = lunar.getDayJi();
+        resultMap.put("day_ji", dayJi.toString().substring(1, dayJi.toString().length() - 1));
+        return resultMap;
+    }
+
+    /**
+     * 根据日期获取最近节气
+     *
+     * @param date 日期
+     * @return map
+     */
+    public static Map<String, Object> getJieQi(String date) {
+        Map<String, Object> resultMap = new HashMap<>();
+        Solar solar = setSolarDateFromYmd(date);
+        Lunar lunar = solar.getLunar();
+        JieQi currentJieQi = lunar.getCurrentJieQi();
+        if (currentJieQi != null) {
+            resultMap.put("name", currentJieQi.getName());
+            resultMap.put("day", 0);
+        } else {
+            JieQi nextJieQi = lunar.getNextJieQi();
+            resultMap.put("name", nextJieQi.getName());
+            resultMap.put("day", subtractDate(date, nextJieQi.getSolar().toString()));
+        }
+        return resultMap;
+    }
+
+    /**
+     * 获取下一个节日
+     *
+     * @param date            日期yyyy-MM-dd
+     * @param allFestivalFlag 所有节日标记{0:全部,1:传统节日,2:其他节日,99:不显示节日}
+     * @return map
+     */
+    public static Map<String, Object> getNextHoliday(String date, Integer allFestivalFlag) {
+        int count = 0;
+        Map<String, Object> resultMap = new HashMap<>();
+        //今年所有节假日
+        List<Holiday> all = HolidayUtil.getHolidays(Integer.parseInt(date.substring(0, 4)));
+        if (allFestivalFlag != 99) {
+            List<Holiday> festivals = getFestivalByYear(date.substring(0, 4), allFestivalFlag);
+            //是否重复
+            for (Holiday festival : festivals) {
+                boolean flag = false;
+                for (Holiday holiday : all) {
+                    if (holiday.getDay().equals(festival.getDay())) {
+                        String newName = holiday.getName() + "," + festival.getName();
+                        List<String> listContains = listContains(Arrays.asList(newName.split(",")));
+                        String name = "";
+                        for (String s : listContains) {
+                            name = s + ",";
+                        }
+                        holiday.setName(name.substring(0, name.length() - 1));
+                        flag = true;
+                        break;
+                    }
+                }
+                //不重复的话
+                if (!flag) {
+                    all.add(festival);
+                }
+            }
+        }
+        //升序
+        all.sort(Comparator.comparing(Holiday::getDay).thenComparing(Holiday::getDay));
+        for (Holiday holiday : all) {
+            count++;
+            Solar startDate = setSolarDateFromYmd(date);
+            Solar endDate = setSolarDateFromYmd(holiday.getDay());
+            //排除已过的节日
+            if (date.equals(holiday.getDay()) || startDate.isBefore(endDate)) {
+                resultMap.put("work", holiday.isWork());
+                if (holiday.isWork()) {
+                    resultMap.put("name", holiday.getName() + "调休工作日");
+                } else {
+                    resultMap.put("name", holiday.getName());
+                }
+                int subtractDate = subtractDate(LocalDate.now().toString(), holiday.getDay());
+                resultMap.put("subtract_date", subtractDate);
+                resultMap.put("day", holiday.getDay());
+                return resultMap;
+            } else {
+                if (count == all.size()) {
+                    //今年的循环完毕, 获取明年的
+                    int nextYear = Integer.parseInt(date.substring(0, 4)) + 1;
+                    return getNextHoliday(nextYear + "-01-01", allFestivalFlag);
+                }
+            }
+        }
+        return resultMap;
+    }
+
+    /**
+     * 获取两个日期相差天数
+     *
+     * @param startDate 小日期
+     * @param endDate   大日期
+     * @return int
+     */
+    public static int subtractDate(String startDate, String endDate) {
+        Solar startSolar = setSolarDateFromYmd(startDate);
+        Solar endSolar = setSolarDateFromYmd(endDate);
+        return endSolar.subtract(startSolar);
+    }
+
+    /**
+     * 设置阳历查询日期
+     *
+     * @param date 日期yyyymmdd
+     * @return Solar
+     */
+    private static Solar setSolarDateFromYmd(String date) {
+        String[] dateSplit = date.split("-");
+        return Solar.fromYmd(Integer.parseInt(dateSplit[0]), Integer.parseInt(dateSplit[1]), Integer.parseInt(dateSplit[2]));
+    }
+
+    /**
+     * 设置阴历查询日期
+     *
+     * @param date 日期yyyymmdd
+     * @return Solar
+     */
+    private static Lunar setLunarDateFromYmd(String date) {
+        Solar solar = setSolarDateFromYmd(date);
+        return solar.getLunar();
+    }
+
+    /**
+     * 设置查询日期yyyymmddhhmmss
+     *
+     * @param date 日期
+     * @return Solar
+     */
+    private static Solar setDateFromYmdHms(String date) {
+        String[] dateSplit = date.split("-");
+        return Solar.fromYmdHms(Integer.parseInt(dateSplit[0]), Integer.parseInt(dateSplit[1]), Integer.parseInt(dateSplit[2]), Integer.parseInt(dateSplit[3]), Integer.parseInt(dateSplit[4]), Integer.parseInt(dateSplit[5]));
+    }
+
+    /**
+     * 根据年份获取节日
+     *
+     * @param year            年份
+     * @param allFestivalFlag 所有节日标记{0:全部,1:传统节假期,2:其他节假日}
+     */
+    public static List<Holiday> getFestivalByYear(String year, Integer allFestivalFlag) {
+        List<Holiday> holidays = new ArrayList<>();
+        //闰年
+        boolean leapYear = isLeapYear(year + "-01-01");
+        List<Integer> monthHasDays;
+        if (leapYear) {
+            monthHasDays = Arrays.asList(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
+        } else {
+            monthHasDays = Arrays.asList(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
+        }
+        for (int i = 0; i < monthHasDays.size(); i++) {
+            int month = i + 1;
+            String monthStr = month > 9 ? "" + month : "0" + month;
+            for (int i1 = 0; i1 < monthHasDays.get(i); i1++) {
+                int day = i1 + 1;
+                String dayStr = day > 9 ? "" + day : "0" + day;
+                String yyyyMMdd = year + "-" + monthStr + "-" + dayStr;
+                List<String> festival = new ArrayList<>();
+                if (allFestivalFlag == 1 || allFestivalFlag == 0) {
+                    festival.addAll(getFestival(yyyyMMdd, true));
+                }
+                if (allFestivalFlag == 2 || allFestivalFlag == 0) {
+                    festival.addAll(getFestival(yyyyMMdd, false));
+                }
+                StringBuilder name = new StringBuilder();
+                for (String s : festival) {
+                    name.append(s).append(",");
+                }
+                String sb = name.toString();
+                if (!sb.isEmpty()) {
+                    Holiday holiday = new Holiday();
+                    holiday.setDay(yyyyMMdd);
+                    holiday.setName(sb.substring(0, sb.length() - 1));
+                    Holiday dayInfo = HolidayUtil.getHoliday(Integer.parseInt(yyyyMMdd.substring(0, 4)), Integer.parseInt(yyyyMMdd.substring(5, 7)), Integer.parseInt(yyyyMMdd.substring(8, 10)));
+                    if (dayInfo != null) {
+                        holiday.setWork(dayInfo.isWork());
+                    } else {
+                        holiday.setWork(false);
+                    }
+                    holidays.add(holiday);
+                }
+            }
+        }
+        return holidays;
+    }
+
+    /**
+     * 数九
+     *
+     * @param date 查询日期
+     * @return 结果
+     */
+    public static String getShuJiu(String date) {
+        Lunar lunar = setLunarDateFromYmd(date);
+        ShuJiu shuJiu = lunar.getShuJiu();
+        if (null != shuJiu) {
+            return shuJiu.toFullString();
+        }
+        return "";
+    }
+
+    /**
+     * 自定义去重
+     *
+     * @param list 去重集合
+     */
+    public static List<String> listContains(List<String> list) {
+        //新集合
+        List<String> newList = new ArrayList<>();
+        list.forEach(i -> {
+            if (!newList.contains(i)) {//如果新集合中不存在则插入
+                newList.add(i);
+            }
+        });
+        return newList;
+    }
+
+}

+ 65 - 0
alien-common-core/src/main/java/shop/alien/util/map/DistanceUtil.java

@@ -0,0 +1,65 @@
+package shop.alien.util.map;
+
+import java.text.DecimalFormat;
+
+/**
+ * 两个经纬度距离计算
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/3/14 15:39
+ */
+public class DistanceUtil {
+
+    private static final int EARTH_RADIUS = 6371; // 地球半径,单位为千米
+
+    /**
+     * Haversine公式
+     * <p>
+     * Haversine公式是一种常用的方法,用于计算球面上两个点之间的距离。在此方法中,假设地球是一个完美的球体,忽略了地球的椭球形状。
+     *
+     * @param lon1 经度1
+     * @param lat1 纬度1
+     * @param lon2 经度2
+     * @param lat2 纬度2
+     * @return 两点之间的距离,单位为千米
+     */
+    public static double haversineCalculateDistance(double lon1, double lat1, double lon2, double lat2) {
+        double dLat = Math.toRadians(lat2 - lat1);
+        double dLon = Math.toRadians(lon2 - lon1);
+        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
+                * Math.sin(dLon / 2) * Math.sin(dLon / 2);
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+        DecimalFormat df = new DecimalFormat("#.##");
+        return Double.parseDouble(df.format(EARTH_RADIUS * c));
+    }
+
+    /**
+     * Vincenty公式
+     * <p>
+     * Vincenty公式是一种更准确的计算方法,可以考虑地球的椭球形状。与Haversine公式相比,Vincenty公式的计算结果更准确,但计算量稍大,适用于更高精度的计算。
+     *
+     * @param lon1 经度1
+     * @param lat1 纬度1
+     * @param lon2 经度2
+     * @param lat2 纬度2
+     * @return 两点之间的距离,单位为千米
+     */
+    public static double vincentyCalculateDistance(double lon1, double lat1, double lon2, double lat2) {
+        double phi1 = Math.toRadians(lat1);
+        double lambda1 = Math.toRadians(lon1);
+        double phi2 = Math.toRadians(lat2);
+        double lambda2 = Math.toRadians(lon2);
+
+        double deltaLambda = Math.abs(lambda1 - lambda2);
+        double numerator = Math.sqrt(
+                Math.pow(Math.cos(phi2) * Math.sin(deltaLambda), 2) +
+                        Math.pow(Math.cos(phi1) * Math.sin(phi2) - Math.sin(phi1) * Math.cos(phi2) * Math.cos(deltaLambda), 2)
+        );
+        double denominator = Math.sin(phi1) * Math.sin(phi2) + Math.cos(phi1) * Math.cos(phi2) * Math.cos(deltaLambda);
+        DecimalFormat df = new DecimalFormat("#.##");
+        return Double.parseDouble(df.format(EARTH_RADIUS * Math.atan2(numerator, denominator)));
+    }
+
+}

+ 73 - 0
alien-common-core/src/main/java/shop/alien/util/md5/FileMd5Util.java

@@ -0,0 +1,73 @@
+package shop.alien.util.md5;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 文件计算MD5
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2020/5/22 14:55
+ */
+@Slf4j
+public class FileMd5Util {
+
+    /**
+     * 文件计算MD5
+     *
+     * @param file 文件
+     * @return MD5
+     */
+    public static String fileToMd5(File file) {
+        try {
+            //FileInputStream是InputStream的子类,用于从文件中读取信息
+            FileInputStream fis = new FileInputStream(file);
+            //利用ByteArrayOutputStream将FileInputStream中的文件数据读出来
+            ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
+            byte[] b = new byte[1024];
+            int len = -1;
+            while ((len = fis.read(b)) != -1) {
+                bos.write(b, 0, len);
+            }
+            //利用ByteArrayOutputStream的toByteArray()方法得到文件的字节数组。
+            byte[] fileByte = bos.toByteArray();
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            byte[] digest = md5.digest(fileByte);
+            String md5Hex = new BigInteger(1, digest).toString(16);
+            log.info("fileName={},MD5={}", file.getName(), md5Hex.toUpperCase());
+            //释放资源
+            bos.close();
+            fis.close();
+            return md5Hex.toUpperCase();
+        } catch (IOException | NoSuchAlgorithmException e) {
+            log.error("fileToMd5 ERROR Msg={},Stack={}", e.getMessage(), e.getStackTrace());
+            return null;
+        }
+    }
+
+    /**
+     * 文件计算MD5
+     *
+     * @param uploadBytes 文件转byte数组
+     * @return MD5
+     */
+    public static String fileToMd5(byte[] uploadBytes) {
+        //校验MD5
+        MessageDigest md5 = null;
+        try {
+            md5 = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            log.error("fileToMd5 ERROR Msg={},Stack={}", e.getMessage(), e.getStackTrace());
+        }
+        byte[] digest = md5.digest(uploadBytes);
+        return new BigInteger(1, digest).toString(16);
+    }
+}

+ 644 - 0
alien-common-core/src/main/java/shop/alien/util/myBaticsPlus/QueryBuilder.java

@@ -0,0 +1,644 @@
+package shop.alien.util.myBaticsPlus;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 通用查询构建器
+ * <p>
+ * 功能:根据对象非空字段自动构建查询条件,支持分页、模糊、批量等多种查询方式
+ * <p>
+ * 使用示例:
+ * <pre>{@code
+ * // 1. 简单查询
+ * QueryBuilder.of(query).build().list(service);
+ * 
+ * // 2. 分页查询
+ * QueryBuilder.of(query).page(1, 10).build().page(service);
+ * 
+ * // 3. 批量查询(字段名以_List结尾,如:id_List)
+ * QueryBuilder.of(query).build().list(service);
+ * 
+ * // 4. 范围查询(字段名以_Start/_End结尾,如:createdTime_Start, createdTime_End)
+ * QueryBuilder.of(query).build().list(service);
+ * 
+ * // 5. 模糊查询(字段名以_Like结尾,如:name_Like)
+ * QueryBuilder.of(query).build().list(service);
+ * }</pre>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public class QueryBuilder<T> {
+
+    // ==================== 常量定义 ====================
+    
+    /** 特殊查询字段后缀(统一使用下划线前缀,避免与普通字段冲突) */
+    private static final String SUFFIX_LIST = "_List";
+    private static final String SUFFIX_START = "_Start";
+    private static final String SUFFIX_END = "_End";
+    private static final String SUFFIX_LIKE = "_Like";
+    
+    /** 模糊查询通配符 */
+    private static final String LIKE_WILDCARD = "%";
+    
+    /** 模糊查询模式枚举 */
+    public enum LikeMode {
+        /** 前后模糊:%value% */
+        BOTH,
+        /** 前模糊:%value */
+        LEFT,
+        /** 后模糊:value% */
+        RIGHT
+    }
+
+    // ==================== 成员变量 ====================
+    
+    private final T queryEntity;
+    private final QueryWrapper<T> wrapper;
+    private boolean ignoreEmptyStr = true;
+    private boolean stringFieldLike = false;  // String类型字段是否默认使用模糊查询
+    private final List<String> likeFields = new ArrayList<>();  // 指定哪些字段使用模糊查询
+    private LikeMode likeMode = LikeMode.BOTH;  // 模糊查询模式(默认前后模糊)
+    private Page<T> page;
+
+    // ==================== 构造函数 ====================
+    
+    private QueryBuilder(T queryEntity) {
+        this.queryEntity = queryEntity;
+        this.wrapper = new QueryWrapper<>();
+    }
+
+    // ==================== 公开API ====================
+    
+    /**
+     * 创建查询构建器
+     */
+    public static <T> QueryBuilder<T> of(T queryEntity) {
+        return new QueryBuilder<>(queryEntity);
+    }
+
+    /**
+     * 设置是否忽略空字符串(默认true)
+     */
+    public QueryBuilder<T> ignoreEmptyStr(boolean ignore) {
+        this.ignoreEmptyStr = ignore;
+        return this;
+    }
+
+    /**
+     * 设置String类型字段是否默认使用模糊查询(默认false,即等值查询)
+     */
+    public QueryBuilder<T> stringFieldLike(boolean like) {
+        this.stringFieldLike = like;
+        return this;
+    }
+
+    /**
+     * 指定哪些字段使用模糊查询(可指定多个字段)
+     * 例如:.likeFields("name", "code") 表示 name 和 code 字段使用模糊查询
+     */
+    public QueryBuilder<T> likeFields(String... fieldNames) {
+        if (fieldNames != null) {
+            for (String fieldName : fieldNames) {
+                if (fieldName != null && !fieldName.trim().isEmpty()) {
+                    likeFields.add(fieldName.trim());
+                }
+            }
+        }
+        return this;
+    }
+
+    /**
+     * 设置模糊查询模式(默认前后模糊:%value%)
+     * 
+     * @param mode 模糊查询模式
+     *   - BOTH: 前后模糊 %value%(默认)
+     *   - LEFT: 前模糊 %value
+     *   - RIGHT: 后模糊 value%
+     * 
+     * 使用示例:
+     * <pre>{@code
+     * QueryBuilder.of(query)
+     *     .likeFields("name")
+     *     .likeMode(LikeMode.LEFT)  // 前模糊查询
+     *     .build()
+     * }</pre>
+     */
+    public QueryBuilder<T> likeMode(LikeMode mode) {
+        if (mode != null) {
+            this.likeMode = mode;
+        }
+        return this;
+    }
+
+    /**
+     * 设置分页参数
+     */
+    public QueryBuilder<T> page(int pageNum, int pageSize) {
+        this.page = new Page<>(pageNum, pageSize);
+        return this;
+    }
+
+    /**
+     * 设置分页对象
+     */
+    public QueryBuilder<T> page(Page<T> page) {
+        this.page = page;
+        return this;
+    }
+
+    /**
+     * 构建查询条件
+     */
+    public QueryResult<T> build() {
+        buildBaseQueryConditions();
+        processSpecialQueries();
+        return new QueryResult<>(this.wrapper, this.page);
+    }
+
+    // ==================== 核心构建逻辑 ====================
+    
+    /**
+     * 构建基础查询条件(等值查询)
+     */
+    private void buildBaseQueryConditions() {
+        if (queryEntity == null) {
+            return;
+        }
+
+        List<Field> allFields = getAllFields(queryEntity.getClass());
+
+        for (Field field : allFields) {
+            if (shouldSkipField(field)) {
+                continue;
+            }
+
+            Object value = getFieldValue(field);
+            if (value == null || shouldSkipValue(value)) {
+                continue;
+            }
+
+            String columnName = getColumnName(field);
+            if (columnName == null) {
+                continue;
+            }
+
+            Object serializableValue = ensureSerializable(value);
+            if (serializableValue != null) {
+                String fieldName = field.getName();
+                
+                // 判断是否使用模糊查询
+                boolean shouldUseLike = false;
+                
+                // 1. 检查是否在指定的模糊查询字段列表中
+                if (likeFields.contains(fieldName)) {
+                    shouldUseLike = true;
+                }
+                // 2. 检查是否全局启用String字段模糊查询
+                else if (stringFieldLike && value instanceof String) {
+                    shouldUseLike = true;
+                }
+                
+                if (shouldUseLike && value instanceof String) {
+                    String likeValue = buildLikeValue(serializableValue.toString());
+                    wrapper.like(columnName, likeValue);
+                } else {
+                    wrapper.eq(columnName, serializableValue);
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理特殊查询场景(批量、范围、模糊查询)
+     */
+    private void processSpecialQueries() {
+        if (queryEntity == null) {
+            return;
+        }
+
+        List<Field> allFields = getAllFields(queryEntity.getClass());
+
+        for (Field field : allFields) {
+            if (isStaticField(field)) {
+                continue;
+            }
+
+            Object value = getFieldValue(field);
+            if (value == null) {
+                continue;
+            }
+
+            String fieldName = field.getName();
+
+            // 批量查询:集合类型字段
+            if (value instanceof Collection) {
+                processBatchQuery(fieldName, (Collection<?>) value);
+                continue;
+            }
+
+            // 范围查询:Start/End 后缀
+            if (isRangeQueryField(fieldName)) {
+                processRangeQuery(fieldName, value);
+                continue;
+            }
+
+            // 模糊查询:Like 后缀
+            if (isLikeQueryField(fieldName)) {
+                processLikeQuery(fieldName, value);
+            }
+        }
+    }
+
+    // ==================== 字段过滤判断 ====================
+    
+    /**
+     * 判断是否应该跳过该字段
+     */
+    private boolean shouldSkipField(Field field) {
+        String fieldName = field.getName();
+        
+        // 跳过特殊查询字段
+        if (isSpecialQueryField(fieldName)) {
+            return true;
+        }
+        
+        // 跳过 transient 字段
+        return Modifier.isTransient(field.getModifiers());
+    }
+
+    /**
+     * 判断是否为特殊查询字段(_List/_Start/_End/_Like后缀)
+     */
+    private boolean isSpecialQueryField(String fieldName) {
+        return fieldName.endsWith(SUFFIX_LIST) ||
+               fieldName.endsWith(SUFFIX_START) ||
+               fieldName.endsWith(SUFFIX_END) ||
+               fieldName.endsWith(SUFFIX_LIKE);
+    }
+
+    /**
+     * 判断是否为范围查询字段
+     */
+    private boolean isRangeQueryField(String fieldName) {
+        return fieldName.endsWith(SUFFIX_START) || fieldName.endsWith(SUFFIX_END);
+    }
+
+    /**
+     * 判断是否为模糊查询字段
+     */
+    private boolean isLikeQueryField(String fieldName) {
+        return fieldName.endsWith(SUFFIX_LIKE);
+    }
+
+    /**
+     * 判断是否为静态字段
+     */
+    private boolean isStaticField(Field field) {
+        return Modifier.isStatic(field.getModifiers());
+    }
+
+    /**
+     * 判断是否应该跳过该值
+     */
+    private boolean shouldSkipValue(Object value) {
+        // 空字符串
+        if (ignoreEmptyStr && value instanceof String && ((String) value).trim().isEmpty()) {
+            return true;
+        }
+        
+        // 空集合
+        return value instanceof Collection && ((Collection<?>) value).isEmpty();
+    }
+
+    // ==================== 查询类型处理 ====================
+    
+    /**
+     * 处理批量查询(IN查询)
+     */
+    private void processBatchQuery(String fieldName, Collection<?> collection) {
+        String baseFieldName = removeSuffix(fieldName, SUFFIX_LIST);
+        String columnName = getColumnNameByFieldName(baseFieldName, fieldName);
+        
+        if (columnName != null) {
+            List<Object> serializableList = filterSerializableItems(collection);
+            if (!serializableList.isEmpty()) {
+                wrapper.in(columnName, serializableList);
+            }
+        }
+    }
+
+    /**
+     * 处理范围查询
+     */
+    private void processRangeQuery(String fieldName, Object value) {
+        String baseFieldName = removeSuffix(fieldName, SUFFIX_START, SUFFIX_END);
+        String columnName = getColumnNameByFieldName(baseFieldName);
+        
+        if (columnName != null) {
+            Object serializableValue = ensureSerializable(value);
+            if (serializableValue != null) {
+                if (fieldName.endsWith(SUFFIX_START)) {
+                    wrapper.ge(columnName, serializableValue);
+                } else {
+                    wrapper.le(columnName, serializableValue);
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理模糊查询
+     */
+    private void processLikeQuery(String fieldName, Object value) {
+        String baseFieldName = removeSuffix(fieldName, SUFFIX_LIKE);
+        String columnName = getColumnNameByFieldName(baseFieldName);
+        
+        if (columnName != null && value != null) {
+            String likeValue = buildLikeValue(value);
+            if (likeValue != null) {
+                wrapper.like(columnName, likeValue);
+            }
+        }
+    }
+
+    // ==================== 工具方法 ====================
+    
+    /**
+     * 获取字段值
+     */
+    private Object getFieldValue(Field field) {
+        try {
+            field.setAccessible(true);
+            return field.get(queryEntity);
+        } catch (IllegalAccessException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 获取数据库字段名(通过字段对象)
+     */
+    private String getColumnName(Field field) {
+        TableField tableField = field.getAnnotation(TableField.class);
+        
+        // 检查字段是否标记为不存在
+        if (tableField != null && !tableField.exist()) {
+            return null;
+        }
+        
+        // 优先使用注解指定的字段名
+        if (tableField != null && !tableField.value().isEmpty()) {
+            return tableField.value();
+        }
+        
+        // 默认:驼峰转下划线
+        return camelToUnderscore(field.getName());
+    }
+
+    /**
+     * 获取数据库字段名(通过字段名,支持fallback)
+     */
+    private String getColumnNameByFieldName(String... fieldNames) {
+        for (String fieldName : fieldNames) {
+            try {
+                Field field = findField(queryEntity.getClass(), fieldName);
+                String columnName = getColumnName(field);
+                if (columnName != null) {
+                    return columnName;
+                }
+            } catch (Exception e) {
+                // 继续尝试下一个字段名
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 移除字段名后缀(精确实现:只移除末尾的后缀)
+     * 
+     * 示例:
+     * - removeSuffix("id_List", "_List") → "id"
+     * - removeSuffix("createdTime_Start", "_Start") → "createdTime"
+     * - removeSuffix("name_Like", "_Like") → "name"
+     * - removeSuffix("id_List_List", "_List") → "id_List"(只移除末尾的)
+     * 
+     * @param fieldName 字段名
+     * @param suffixes 要移除的后缀(按顺序尝试,找到第一个匹配的就移除)
+     * @return 移除后缀后的字段名
+     */
+    private String removeSuffix(String fieldName, String... suffixes) {
+        if (fieldName == null || fieldName.isEmpty()) {
+            return fieldName;
+        }
+        
+        // 按顺序尝试每个后缀,找到第一个匹配的就移除
+        for (String suffix : suffixes) {
+            if (suffix != null && !suffix.isEmpty() && fieldName.endsWith(suffix)) {
+                // 只移除末尾的后缀,使用 substring 精确移除
+                return fieldName.substring(0, fieldName.length() - suffix.length());
+            }
+        }
+        
+        // 如果没有匹配的后缀,返回原字段名
+        return fieldName;
+    }
+
+    /**
+     * 构建模糊查询值(根据likeMode模式)
+     * 
+     * 模式说明:
+     * - BOTH: %value%  (前后模糊,默认)
+     * - LEFT: %value   (前模糊)
+     * - RIGHT: value%  (后模糊)
+     */
+    private String buildLikeValue(Object value) {
+        if (value == null) {
+            return null;
+        }
+        
+        String strValue = value instanceof String ? (String) value : value.toString();
+        
+        switch (likeMode) {
+            case LEFT:
+                return LIKE_WILDCARD + strValue;  // %value
+            case RIGHT:
+                return strValue + LIKE_WILDCARD;  // value%
+            case BOTH:
+            default:
+                return LIKE_WILDCARD + strValue + LIKE_WILDCARD;  // %value%
+        }
+    }
+
+    /**
+     * 过滤集合中的可序列化元素
+     */
+    private List<Object> filterSerializableItems(Collection<?> collection) {
+        List<Object> result = new ArrayList<>();
+        for (Object item : collection) {
+            if (item != null && isSerializableType(item)) {
+                result.add(item);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 确保对象是可序列化的类型
+     */
+    private Object ensureSerializable(Object value) {
+        if (value == null) {
+            return null;
+        }
+        
+        if (isSerializableType(value)) {
+            return value;
+        }
+        
+        // 尝试转换为字符串
+        try {
+            return value.toString();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 判断对象是否为可序列化的基本类型
+     */
+    private boolean isSerializableType(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        
+        Class<?> clazz = obj.getClass();
+        
+        return clazz.isPrimitive() ||
+               Number.class.isAssignableFrom(clazz) ||
+               String.class.equals(clazz) ||
+               Boolean.class.equals(clazz) ||
+               Character.class.equals(clazz) ||
+               java.util.Date.class.isAssignableFrom(clazz) ||
+               java.sql.Date.class.isAssignableFrom(clazz) ||
+               java.sql.Timestamp.class.isAssignableFrom(clazz) ||
+               java.time.LocalDate.class.isAssignableFrom(clazz) ||
+               java.time.LocalDateTime.class.isAssignableFrom(clazz);
+    }
+
+    /**
+     * 驼峰命名转下划线命名
+     */
+    private String camelToUnderscore(String camelCase) {
+        if (camelCase == null || camelCase.isEmpty()) {
+            return camelCase;
+        }
+        
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < camelCase.length(); i++) {
+            char c = camelCase.charAt(i);
+            if (Character.isUpperCase(c) && i > 0) {
+                result.append('_');
+            }
+            result.append(Character.toLowerCase(c));
+        }
+        return result.toString();
+    }
+
+    /**
+     * 查找字段(支持父类)
+     */
+    private Field findField(Class<?> clazz, String fieldName) {
+        try {
+            return clazz.getDeclaredField(fieldName);
+        } catch (NoSuchFieldException e) {
+            Class<?> superClass = clazz.getSuperclass();
+            if (superClass != null && superClass != Object.class) {
+                return findField(superClass, fieldName);
+            }
+            throw new RuntimeException("字段不存在: " + fieldName + " in " + clazz.getName(), e);
+        }
+    }
+
+    /**
+     * 获取实体类所有字段(包括父类,排除静态字段)
+     */
+    private List<Field> getAllFields(Class<?> clazz) {
+        List<Field> fieldList = new ArrayList<>();
+        while (clazz != null && clazz != Object.class) {
+            for (Field field : clazz.getDeclaredFields()) {
+                if (!isStaticField(field)) {
+                    fieldList.add(field);
+                }
+            }
+            clazz = clazz.getSuperclass();
+        }
+        return fieldList;
+    }
+
+    // ==================== 内部类 ====================
+    
+    /**
+     * 查询结果包装类
+     */
+    @Data
+    @Accessors(chain = true)
+    public static class QueryResult<T> {
+        private final QueryWrapper<T> wrapper;
+        private final Page<T> page;
+
+        public QueryResult(QueryWrapper<T> wrapper, Page<T> page) {
+            this.wrapper = wrapper;
+            this.page = page;
+        }
+
+        /**
+         * 执行列表查询
+         */
+        public List<T> list(IService<T> service) {
+            return service.list(wrapper);
+        }
+
+        /**
+         * 执行分页查询
+         */
+        public IPage<T> page(IService<T> service) {
+            if (page == null) {
+                throw new IllegalStateException("未设置分页参数,请先调用page()方法");
+            }
+            return service.page(page, wrapper);
+        }
+
+        /**
+         * 执行单条查询
+         */
+        public T one(IService<T> service) {
+            return service.getOne(wrapper);
+        }
+
+        /**
+         * 执行计数查询
+         */
+        public long count(IService<T> service) {
+            return service.count(wrapper);
+        }
+
+        /**
+         * 获取查询条件(用于调试)
+         */
+        public QueryWrapper<T> getWrapper() {
+            return wrapper;
+        }
+    }
+}

+ 33 - 0
alien-common-core/src/main/java/shop/alien/util/ocr/OCRUtil.java

@@ -0,0 +1,33 @@
+package shop.alien.util.ocr;
+
+import net.sourceforge.tess4j.Tesseract;
+import net.sourceforge.tess4j.TesseractException;
+
+import java.io.File;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2024/10/10 13:37
+ */
+public class OCRUtil {
+    public static void main(String[] args) {
+        // 创建Tesseract实例
+        Tesseract tesseract = new Tesseract();
+
+        try {
+            // 设置Tesseract的语言库路径
+            tesseract.setDatapath("D:\\SoftWareProgram\\Tesseract-OCR\\tessdata");
+
+            // 设置识别语言,默认为英文,中文简体设置为"chi_sim"
+            tesseract.setLanguage("chi_sim");
+
+            // 识别图片文件
+            String result = tesseract.doOCR(new File("C:\\Users\\17238\\Pictures\\4A375133A7F453966AF794D0356B80AA.jpg"));
+            System.out.println(result);
+        } catch (TesseractException e) {
+            e.printStackTrace();
+        }
+    }
+
+}

+ 79 - 0
alien-common-core/src/main/java/shop/alien/util/openoffice/LocallinkToPDF.java

@@ -0,0 +1,79 @@
+package shop.alien.util.openoffice;
+
+import com.artofsolving.jodconverter.DocumentConverter;
+import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * 自动启动本地openoffice服务 使doc,xls,ppt转换成PDF
+ * 
+ */
+
+public class LocallinkToPDF {
+	public static int office2PDF(String sourceFile, String destFile) {
+		// 这里是OpenOffice的安装目录,C:\Program Files (x86)\OpenOffice 4
+		System.out.println("寻找服务...");
+		String OpenOffice_HOME = "C:/Program Files (x86)/OpenOffice 4/";
+		// 在我的项目中,为了便于拓展接口,没有直接写成这个样子,但是这样是尽对没题目的
+		// 假如从文件中读取的URL地址最后一个字符不是 '\',则添加'\'
+		if (OpenOffice_HOME.charAt(OpenOffice_HOME.length() - 1) != '/') {
+			OpenOffice_HOME += "/";
+		}
+		Process pro = null;
+
+		try {
+			File inputFile = new File(sourceFile);
+			if (!inputFile.exists()) {
+				return -1;// 找不到源文件, 则返回-1
+			}
+			// 如果目标路径不存在, 则新建该路径
+			File outputFile = new File(destFile);
+			if (!outputFile.getParentFile().exists()) {
+				outputFile.getParentFile().mkdirs();
+			}
+			// 启动OpenOffice的服务
+			String command = OpenOffice_HOME + "program\\soffice.exe -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;StarOffice.ServiceManager\" -nofirststartwizard";
+			System.out.println("启动服务...");
+			pro = Runtime.getRuntime().exec(command);
+			// connect to an OpenOffice.org instance running on port 8100
+			System.out.println("链接服务...");
+			OpenOfficeConnection connection = new SocketOpenOfficeConnection("192.168.3.103", 8100);
+			// OpenOfficeConnection connection = new SocketOpenOfficeConnection(8100);
+			connection.connect();
+
+			// convert
+			DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
+			System.out.println("开始转换");
+			converter.convert(inputFile, outputFile);
+			// converter.convert(inputFile,xml,outputFile,pdf);
+
+			// close the connection
+			connection.disconnect();
+			// 封闭OpenOffice服务的进程
+			pro.destroy();
+
+			return 0;
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+			return -1;
+		} catch (IOException e) {
+			e.printStackTrace();
+		} finally {
+			pro.destroy();
+		}
+
+		return 1;
+	}
+
+	public static void main(String[] args) throws Exception {
+		String sourceFile = "D:\\MyTest\\file\\1.xls";
+		String destFile = "D:\\1.pdf";
+		LocallinkToPDF.office2PDF(sourceFile, destFile);
+		System.out.println("转换完成");
+	}
+}

+ 55 - 0
alien-common-core/src/main/java/shop/alien/util/openoffice/NewOpenOffice.java

@@ -0,0 +1,55 @@
+package shop.alien.util.openoffice;
+
+import com.artofsolving.jodconverter.DocumentConverter;
+import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeException;
+import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.converter.StreamOpenOfficeDocumentConverter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class NewOpenOffice {
+
+	public static void main(String[] args) {
+		String fileName = "1.docx";
+		String tmpdir = "D:\\MyTest\\file\\";
+		File file = new File(tmpdir + fileName);
+		if (!file.exists()) {
+			System.err.println("文件不存在");
+		}
+		Date date = new Date();
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
+		String timesuffix = sdf.format(date);
+		String htmFileName = null;
+		htmFileName = timesuffix + ".pdf";
+		File htmlOutputFile = new File(tmpdir + File.separatorChar + htmFileName);
+		if (htmlOutputFile.exists())
+			htmlOutputFile.delete();
+		try {
+			htmlOutputFile.createNewFile();
+		} catch (IOException e1) {
+			e1.printStackTrace();
+		}
+		/**
+		 * 由fromFileInputStream构建输入文件
+		 */
+		OpenOfficeConnection connection = new SocketOpenOfficeConnection("192.168.3.230", 8100);
+		try {
+			connection.connect();
+		} catch (ConnectException e) {
+			System.err.println("请检查OpenOffice服务是否启动");
+		}
+		DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
+		try {
+			converter.convert(file, htmlOutputFile);
+		} catch (OpenOfficeException ex) {
+			System.err.println("文件转换出错");
+		}
+		connection.disconnect();
+		System.out.println(htmlOutputFile);
+	}
+}

+ 105 - 0
alien-common-core/src/main/java/shop/alien/util/openoffice/OpenOfficeUtil.java

@@ -0,0 +1,105 @@
+package shop.alien.util.openoffice;
+
+import com.artofsolving.jodconverter.DocumentConverter;
+import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.converter.StreamOpenOfficeDocumentConverter;
+import shop.alien.util.ip.IpConnectTestUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * windows下使用openoffice把office文件转换成pdf
+ *
+ * @author ssk
+ */
+public class OpenOfficeUtil {
+
+    public static void main(String[] args) {
+        try {
+            // OpenOffice的安装目录
+            String OpenOfficeHOME = "D:\\software\\openOffice";
+            // 如果从文件中读取的URL地址最后一个字符不是 '\',则添加'\'
+            if (OpenOfficeHOME.charAt(OpenOfficeHOME.length() - 1) != '\\') {
+                OpenOfficeHOME += "\\";
+            }
+            // 启动OpenOffice的服务
+            String command = OpenOfficeHOME + "program\\soffice.exe -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\"";
+            Process pro = Runtime.getRuntime().exec(command);
+        } catch (IOException e) {
+            System.out.println("openoffice:服务开启失败");
+        }
+        List<Boolean> list = new ArrayList<>();
+        int count = 0;
+        list.add(officeToPDF("D:\\MyTest\\1.txt", "D:\\MyTest\\new\\TXT_1.pdf"));
+        list.add(officeToPDF("D:\\MyTest\\test.doc", "D:\\MyTest\\new\\DOC_test.pdf"));
+        list.add(officeToPDF("D:\\MyTest\\test.docx", "D:\\MyTest\\new\\DOCX_test.pdf"));
+        list.add(officeToPDF("D:\\MyTest\\test.xls", "D:\\MyTest\\new\\XLS_test.pdf"));
+        list.add(officeToPDF("D:\\MyTest\\test.xlsx", "D:\\MyTest\\new\\XLSX_test.pdf"));
+        list.add(officeToPDF("D:\\MyTest\\test.ppt", "D:\\MyTest\\new\\PPT_test.pdf"));
+        list.add(officeToPDF("D:\\MyTest\\test.pptx", "D:\\MyTest\\new\\PPTX_test.pdf"));
+        for (int i = 0; i < list.size(); i++) {
+            if (list.get(i) == true) {
+                count++;
+            }
+        }
+        if (list.size() == count) {
+            String srcFilePath = "D:\\MyTest\\new";
+            String destFilePathAndName = srcFilePath;
+//            ZipUtil zipUtil = new ZipUtil();
+//            zipUtil.compress(srcFilePath, destFilePathAndName);
+        } else {
+            System.err.println("有文件未转换成功");
+        }
+    }
+
+    public static boolean officeToPDF(String sourceFile, String destFile) {
+        try {
+            File inputFile = new File(sourceFile);
+            if (!inputFile.exists()) {
+                // 找不到源文件, 则返回false
+                System.err.println("文件不存在!");
+            }
+            // 如果目标路径不存在, 则新建该路径
+            File outputFile = new File(destFile);
+            if (!outputFile.getParentFile().exists()) {
+                outputFile.getParentFile().mkdirs();
+            }
+            // 如果目标文件存在,则删除
+            if (outputFile.exists()) {
+                outputFile.delete();
+            }
+            //测试是否开启
+            IpConnectTestUtil testIpConnectUtil = new IpConnectTestUtil();
+            //等待开启
+            if (!testIpConnectUtil.ipTest("127.0.0.1", 8100)) {
+                //服务未开启,直接停止
+                System.err.println("openoffice:服务未开启!");
+            }
+            System.out.println("openoffice:服务已开启");
+            OpenOfficeConnection connection = new SocketOpenOfficeConnection("127.0.0.1", 8100);
+            connection.connect();
+            // 用于测试openOffice连接时间
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            System.out.println("连接时间:" + sdf.format(new Date()));
+            DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
+            // 传入文件
+            converter.convert(inputFile, outputFile);
+            // 测试word转PDF的转换时间
+            System.out.println("转换时间:" + sdf.format(new Date()));
+            connection.disconnect();
+            return true;
+        } catch (ConnectException e) {
+            System.err.println("openOffice连接失败!请检查IP,端口");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+}

+ 73 - 0
alien-common-core/src/main/java/shop/alien/util/openoffice/RemotelinkToPDF.java

@@ -0,0 +1,73 @@
+package shop.alien.util.openoffice;
+
+import com.artofsolving.jodconverter.DocumentConverter;
+import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
+import com.artofsolving.jodconverter.openoffice.converter.StreamOpenOfficeDocumentConverter;
+
+import java.io.File;
+import java.net.ConnectException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 链接外部openoffice服务 使doc,xls,ppt转换成PDF
+ * 
+ */
+
+public class RemotelinkToPDF {
+
+	public static boolean officeToPDF(String sourceFile, String destFile) {
+		try {
+
+			File inputFile = new File(sourceFile);
+			if (!inputFile.exists()) {
+				// 找不到源文件, 则返回false
+				return false;
+			}
+			// 如果目标路径不存在, 则新建该路径
+			File outputFile = new File(destFile);
+			if (!outputFile.getParentFile().exists()) {
+				outputFile.getParentFile().mkdirs();
+			}
+			// 如果目标文件存在,则删除
+			if (outputFile.exists()) {
+				outputFile.delete();
+			}
+			DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+			// 链接ip,端口号
+			OpenOfficeConnection connection = new SocketOpenOfficeConnection("192.168.3.230", 8100);
+			connection.connect();
+			// 用于测试openOffice连接时间
+			System.out.println("连接时间:" + df.format(new Date()));
+			DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
+			converter.convert(inputFile, outputFile);
+			// 测试word转PDF的转换时间
+			System.out.println("转换时间:" + df.format(new Date()));
+			connection.disconnect();
+			return true;
+		} catch (ConnectException e) {
+			e.printStackTrace();
+			System.err.println("openOffice连接失败!请检查IP,端口");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return false;
+	}
+
+	// doc,xls,ppt转换成PDF(xls文件转换有问题)
+	/*
+	 * public static void main(String[] args) { officeToPDF("F:\\5.ppt",
+	 * "F:\\test.pdf"); }
+	 */
+
+	// doc,xls,ppt转换成html(ppt文件转换有问题)
+	public static void main(String[] args) {
+		// 源文件
+		String sourceFile = "D:\\MyTest\\file\\1.xls";
+		// 目标文件
+		String destFile = "D:\\1.pdf";
+		officeToPDF(sourceFile, destFile);
+	}
+}

+ 58 - 0
alien-common-core/src/main/java/shop/alien/util/pdf/PdfToImgUtil.java

@@ -0,0 +1,58 @@
+package shop.alien.util.pdf;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.PDFRenderer;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * PDF文件转换成图片
+ *
+ * @author ssk
+ */
+public class PdfToImgUtil {
+    public static void main(String[] args) {
+        // pdf存放路径
+        String filePath = "D:\\电商合同范本.pdf";
+        List<String> imageList = pdfToImagePath(filePath);
+        for (String s : imageList) {
+            System.out.println(s);
+        }
+    }
+
+    public static List<String> pdfToImagePath(String filePath) {
+        List<String> list = new ArrayList<>();
+        // 获取去除后缀的文件路径
+        String fileDirectory = filePath.substring(0, filePath.lastIndexOf("."));
+        // 图片存放路径
+        String imagePath;
+        File file = new File(filePath);
+        try {
+            File f = new File(fileDirectory);
+            if (!f.exists()) {
+                f.mkdir();
+            }
+            PDDocument doc = PDDocument.load(file);
+            PDFRenderer renderer = new PDFRenderer(doc);
+            int pageCount = doc.getNumberOfPages();
+            for (int i = 0; i < pageCount; i++) {
+                // 方式1,第二个参数是设置缩放比(即像素)
+                // BufferedImage image = renderer.renderImageWithDPI(i, 296);
+                // 方式2,第二个参数是设置缩放比(即像素)
+                // 第二个参数越大生成图片分辨率越高,转换时间也就越长
+                BufferedImage image = renderer.renderImage(i, 1.25f);
+                imagePath = fileDirectory + "/" + i + ".jpg";
+                ImageIO.write(image, "PNG", new File(imagePath));
+                list.add(imagePath);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return list;
+    }
+}

+ 67 - 0
alien-common-core/src/main/java/shop/alien/util/port/NetUtils.java

@@ -0,0 +1,67 @@
+package shop.alien.util.port;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * 测试端口
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/5/14 10:29
+ */
+@Slf4j
+public class NetUtils {
+
+    /**
+     * 测试本机端口是否被使用
+     *
+     * @param port 端口号
+     * @return 是否使用
+     */
+    public static boolean isLocalPortUsing(int port) {
+        boolean flag = true;
+        try {
+            //如果该端口还在使用则返回true,否则返回false,127.0.0.1代表本机
+            flag = isPortUsing("127.0.0.1", port);
+        } catch (Exception e) {
+        }
+        return flag;
+    }
+
+    /**
+     * 测试主机Host的port端口是否被使用
+     *
+     * @param host 地址
+     * @param port 端口号
+     * @return 是否使用
+     */
+    public static boolean isPortUsing(String host, int port) {
+        boolean flag = false;
+        try {
+            InetAddress Address = InetAddress.getByName(host);
+            Socket socket = new Socket(Address, port);  //建立一个Socket连接
+            flag = true;
+        } catch (IOException e) {
+            //log.info(e.getMessage(),e);
+        }
+        return flag;
+    }
+
+    /**
+     * 由于本机上安装了mysql,采用3306端口去验证
+     *
+     * @param args
+     */
+    public static void main(String[] args) {
+        int testPost = 3306;
+        if (isLocalPortUsing(testPost)) {
+            System.out.println("端口 " + testPost + " 已被使用");
+        } else {
+            System.out.println("端口 " + testPost + "未使用");
+        }
+    }
+}

+ 32 - 0
alien-common-core/src/main/java/shop/alien/util/port/ServerPortUtils.java

@@ -0,0 +1,32 @@
+package shop.alien.util.port;
+
+import java.util.Random;
+
+/**
+ * 选择可用端口
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/5/14 10:32
+ */
+public class ServerPortUtils {
+
+    /**
+     * 获取可用端口
+     *
+     * @return 可用端口号
+     */
+    public static int getAvailablePort(Integer portMin, Integer portMax) {
+        Random random = new Random();
+        int port = random.nextInt(portMax) % (portMax - portMin + 1) + portMin;
+        //查询端口是否被占用
+        boolean using = NetUtils.isLocalPortUsing(port);
+        if (using) {
+            //再次随机
+            return getAvailablePort(portMin, portMax);
+        } else {
+            return port;
+        }
+    }
+
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor