Ver Fonte

feat(zhaozhaonews): 新增项目根POM及通用模块 - common(公共常量/异常/Jackson配置) - model(各业务实体/DTO/VO/通用分页响应) - util(工具类:JWT/加密/文件/ID生成/敏感词等)

WanJL há 3 semanas atrás
pai
commit
cd7b92d6a0
74 ficheiros alterados com 5821 adições e 0 exclusões
  1. 337 0
      zhaozhaonews/pom.xml
  2. 151 0
      zhaozhaonews/zhaozhaonews-common/pom.xml
  3. 20 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/constants/ArticleConstants.java
  4. 45 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/constants/WemediaConstants.java
  5. 40 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/exception/CustomException.java
  6. 61 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/exception/GlobalException.java
  7. 80 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionDeserializer.java
  8. 54 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionDeserializerModifier.java
  9. 76 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionModule.java
  10. 46 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionSerializer.java
  11. 51 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionSerializerModifier.java
  12. 32 0
      zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/InitJacksonConfig.java
  13. 2 0
      zhaozhaonews/zhaozhaonews-common/src/main/resources/META-INF/spring.factories
  14. 11 0
      zhaozhaonews/zhaozhaonews-common/src/main/resources/application.yaml
  15. 53 0
      zhaozhaonews/zhaozhaonews-model/pom.xml
  16. 9 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/AdUserDto.java
  17. 12 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/AuthDto.java
  18. 10 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/ChannelDto.java
  19. 13 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/NewsAuthDto.java
  20. 47 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/entity/AdUser.java
  21. 56 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/entity/ApUserRealname.java
  22. 25 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dto/ArticleHomeDto.java
  23. 29 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dtos/ArticleBehaviorVo.java
  24. 23 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dtos/ArticleDto.java
  25. 29 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dtos/ArticleInfoDto.java
  26. 141 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/entity/ApArticle.java
  27. 76 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/entity/ApArticleConfig.java
  28. 35 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/entity/ApArticleContent.java
  29. 34 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/BehaviorEntryDto.java
  30. 37 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/CollectionBehaviorDto.java
  31. 33 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/LikesBehaviorDto.java
  32. 23 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/ReadBehaviorDto.java
  33. 26 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/UnLikesBehaviorDto.java
  34. 35 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/comment/entity/ApComment.java
  35. 38 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/comment/entity/ApCommentRepay.java
  36. 28 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/annotation/IdEncrypt.java
  37. 21 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/dto/PageRequestDto.java
  38. 44 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/dto/PageResponseResult.java
  39. 168 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/dto/ResponseResult.java
  40. 58 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/enums/AppHttpCodeEnum.java
  41. 35 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/schedule/dtos/Task.java
  42. 26 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/search/entity/ApAssociateWords.java
  43. 29 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/search/entity/ApUserSearch.java
  44. 22 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/dto/LoginDto.java
  45. 10 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/dto/RegisterDto.java
  46. 29 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/dto/UserRelationDto.java
  47. 93 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/entity/ApUser.java
  48. 16 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmLoginDto.java
  49. 24 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmMaterialDto.java
  50. 64 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmNewsDto.java
  51. 39 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmNewsPageReqDto.java
  52. 38 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmChannel.java
  53. 39 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmMaterial.java
  54. 91 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmNews.java
  55. 53 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmNewsMaterial.java
  56. 115 0
      zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmUser.java
  57. 57 0
      zhaozhaonews/zhaozhaonews-util/pom.xml
  58. 120 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/AppJwtUtil.java
  59. 777 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/BCrypt.java
  60. 28 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/Base64Utils.java
  61. 41 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/BurstUtils.java
  62. 109 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/Compute.java
  63. 160 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/DESUtils.java
  64. 470 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/DateUtils.java
  65. 22 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/FileUtils.java
  66. 84 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/IdsUtils.java
  67. 54 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/MD5Utils.java
  68. 351 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/ReflectUtils.java
  69. 123 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/SensitiveWordUtil.java
  70. 177 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/SimHashUtils.java
  71. 145 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/SnowflakeIdWorker.java
  72. 43 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/UrlSignUtils.java
  73. 193 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/ZipUtils.java
  74. 35 0
      zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/thread/WmThreadLocalUtil.java

+ 337 - 0
zhaozhaonews/pom.xml

@@ -0,0 +1,337 @@
+<?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>
+
+    <!-- 继承Spring boot工程 -->
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.9.RELEASE</version>
+    </parent>
+
+
+    <groupId>com.zhaozhaonews</groupId>
+    <artifactId>zhaozhaonews</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>zhaozhaonews-common</module>
+        <module>zhaozhaonews-feign-api</module>
+        <module>zhaozhaonews-gateway</module>
+        <module>zhaozhaonews-model</module>
+        <module>zhaozhaonews-service</module>
+        <module>zhaozhaonews-test</module>
+        <module>zhaozhaonews-util</module>
+        <module>zhaozhaonews-basic</module>
+    </modules>
+
+    <properties>
+        <!-- 项目源码及编译输出的编码 -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <!-- 项目编译JDK版本 -->
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+
+        <!-- 依赖包版本管理 -->
+        <spring.boot.version>2.7.14</spring.boot.version>
+        <commons.io.version>2.6</commons.io.version>
+        <commons.codec.version>1.12</commons.codec.version>
+        <lombok.version>1.18.24</lombok.version>
+        <mysql.version>8.0.33</mysql.version>
+        <mybatis.version>3.5.1</mybatis.version>
+        <mybatis.spring.version>2.0.1</mybatis.spring.version>
+        <mybatis.plus.version>3.4.1</mybatis.plus.version>
+        <lang3.version>3.5</lang3.version>
+        <pagehelper.version>1.2.3</pagehelper.version>
+        <jsoup.version>1.10.2</jsoup.version>
+        <jwt.version>0.9.1</jwt.version>
+        <fastjson.version>1.2.58</fastjson.version>
+        <fastjson.dataformat.version>2.9.9</fastjson.dataformat.version>
+        <spring.jwt.version>1.0.9.RELEASE</spring.jwt.version>
+        <spring.boot.admin.version>2.1.6</spring.boot.admin.version>
+        <spring.cloud.version>Hoxton.SR10</spring.cloud.version>
+        <curator.version>4.2.0</curator.version>
+        <hanlp.version>portable-1.3.4</hanlp.version>
+        <fastdfs.version>1.26.5</fastdfs.version>
+        <baidu.version>4.11.1</baidu.version>
+        <junit.version>4.12</junit.version>
+        <searchbox.version>6.3.1</searchbox.version>
+        <elasticsearch.version>7.2.0</elasticsearch.version>
+        <ali.core.version>4.1.1</ali.core.version>
+        <ali.green.version>3.4.1</ali.green.version>
+        <kafka.version>2.6.6</kafka.version>
+        <kafka.client.version>2.5.1</kafka.client.version>
+        <reflections.version>0.9.11</reflections.version>
+        <swagger.version>2.9.2</swagger.version>
+        <knife4j.version>2.0.2</knife4j.version>
+        <com.alibaba.cloud>2.2.6.RELEASE</com.alibaba.cloud>
+        <xxl.job.version>2.2.0-SNAPSHOT</xxl.job.version>
+    </properties>
+
+    <dependencies>
+        <!-- lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+
+        <dependencies>
+            <!--内部依赖工程-->
+            <dependency>
+                <artifactId>zhaozhaonews-feign-api</artifactId>
+                <groupId>com.zhaozhaonews</groupId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <artifactId>zhaozhaonews-common</artifactId>
+                <groupId>com.zhaozhaonews</groupId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <artifactId>zhaozhaonews-model</artifactId>
+                <groupId>com.zhaozhaonews</groupId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <artifactId>zhaozhaonews-util</artifactId>
+                <groupId>com.zhaozhaonews</groupId>
+                <version>${project.version}</version>
+            </dependency>
+            <!-- Spring boot 特殊处理包 -->
+            <dependency>
+                <groupId>org.reflections</groupId>
+                <artifactId>reflections</artifactId>
+                <version>${reflections.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-web</artifactId>
+                <version>${spring.boot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter</artifactId>
+                <version>${spring.boot.version}</version>
+            </dependency>
+            <!--Apache 工具包包-->
+            <dependency>
+                <groupId>commons-io</groupId>
+                <artifactId>commons-io</artifactId>
+                <version>${commons.io.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>${lang3.version}</version>
+            </dependency>
+            <!--加密处理包-->
+            <dependency>
+                <groupId>commons-codec</groupId>
+                <artifactId>commons-codec</artifactId>
+                <version>${commons.codec.version}</version>
+            </dependency>
+            <!-- Mysql 数据库 -->
+            <dependency>
+                <groupId>com.mysql</groupId>
+                <artifactId>mysql-connector-j</artifactId>
+                <version>${mysql.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.mybatis</groupId>
+                <artifactId>mybatis</artifactId>
+                <version>${mybatis.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.mybatis</groupId>
+                <artifactId>mybatis-spring</artifactId>
+                <version>${mybatis.spring.version}</version>
+            </dependency>
+            <!-- jsoup -->
+            <dependency>
+                <groupId>org.jsoup</groupId>
+                <artifactId>jsoup</artifactId>
+                <version>${jsoup.version}</version>
+            </dependency>
+            <!-- jwt -->
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt</artifactId>
+                <version>${jwt.version}</version>
+            </dependency>
+            <!-- fastjson -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.fasterxml.jackson.dataformat</groupId>
+                <artifactId>jackson-dataformat-cbor</artifactId>
+                <version>${fastjson.dataformat.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.fasterxml.jackson.dataformat</groupId>
+                <artifactId>jackson-dataformat-xml</artifactId>
+                <version>${fastjson.dataformat.version}</version>
+            </dependency>
+            <!-- spring colud -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring.cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!--spring cloud alibaba-->
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${com.alibaba.cloud}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- spring boot admin -->
+            <dependency>
+                <groupId>de.codecentric</groupId>
+                <artifactId>spring-boot-admin-dependencies</artifactId>
+                <version>${spring.boot.admin.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- curator ZK 客户端 -->
+            <dependency>
+                <groupId>org.apache.curator</groupId>
+                <artifactId>curator-framework</artifactId>
+                <version>${curator.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.curator</groupId>
+                <artifactId>curator-recipes</artifactId>
+                <version>${curator.version}</version>
+            </dependency>
+            <!--匹配度工具包-->
+            <dependency>
+                <groupId>com.hankcs</groupId>
+                <artifactId>hanlp</artifactId>
+                <version>${hanlp.version}</version>
+            </dependency>
+            <!-- fastdfs客服端 -->
+            <dependency>
+                <groupId>com.github.tobato</groupId>
+                <artifactId>fastdfs-client</artifactId>
+                <version>${fastdfs.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.aliyun</groupId>
+                <artifactId>aliyun-java-sdk-core</artifactId>
+                <version>${ali.core.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.aliyun</groupId>
+                <artifactId>aliyun-java-sdk-green</artifactId>
+                <version>${ali.green.version}</version>
+            </dependency>
+            <!-- junit -->
+            <dependency>
+                <groupId>junit</groupId>
+                <artifactId>junit</artifactId>
+                <version>${junit.version}</version>
+            </dependency>
+            <!-- kafkfa -->
+            <dependency>
+                <groupId>org.springframework.kafka</groupId>
+                <artifactId>spring-kafka</artifactId>
+                <version>${kafka.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.kafka</groupId>
+                <artifactId>kafka-streams</artifactId>
+                <version>${kafka.client.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>connect-json</artifactId>
+                        <groupId>org.apache.kafka</groupId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.kafka</groupId>
+                <artifactId>kafka-clients</artifactId>
+                <version>${kafka.client.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.xiaoymin</groupId>
+                <artifactId>knife4j-spring-boot-starter</artifactId>
+                <version>${knife4j.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger2</artifactId>
+                <version>${swagger.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger-ui</artifactId>
+                <version>${swagger.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis.plus.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*.*</include>
+                </includes>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <testResources>
+            <testResource>
+                <directory>src/test/resources</directory>
+                <filtering>true</filtering>
+            </testResource>
+        </testResources>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>spring</id>
+            <url>https://maven.aliyun.com/repository/spring</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+
+</project>

+ 151 - 0
zhaozhaonews/zhaozhaonews-common/pom.xml

@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.zhaozhaonews</groupId>
+        <artifactId>zhaozhaonews</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>zhaozhaonews-common</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+
+        <dependency>
+            <groupId>com.zhaozhaonews</groupId>
+            <artifactId>zhaozhaonews-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!--spring data redis & cache-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <!-- redis依赖commons-pool 这个依赖一定要添加 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!--fastdfs-->
+        <dependency>
+            <groupId>com.github.tobato</groupId>
+            <artifactId>fastdfs-client</artifactId>
+        </dependency>
+        <!-- kafkfa -->
+        <dependency>
+            <groupId>org.springframework.kafka</groupId>
+            <artifactId>spring-kafka</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-clients</artifactId>
+        </dependency>
+        <!--kafka stream-->
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-streams</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-green</artifactId>
+        </dependency>
+        <!--HBase-->
+        <dependency>
+            <groupId>org.apache.hbase</groupId>
+            <artifactId>hbase-client</artifactId>
+            <version>2.1.5</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>slf4j-log4j12</artifactId>
+                    <groupId>org.slf4j</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.10.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 20 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/constants/ArticleConstants.java

@@ -0,0 +1,20 @@
+package com.zhaozhaonews.common.constants;
+
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ArticleConstants
+ * @description 文章常量类
+ * @create 2026/4/19
+ */
+public class ArticleConstants {
+    //文章加载类型--初始加载
+    public static final Integer LOAD_TYPE_LOAD = 0;
+    //文章加载类型--更多新闻
+    public static final Integer LOAD_TYPE_MORE = 1;
+    //文章加载类型--最新新闻
+    public static final Integer LOAD_TYPE_NEW = 2;
+    //文章加载频道--推荐频道(所有)
+    public static final String DEFAULT_TAG="__all__";
+}

+ 45 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/constants/WemediaConstants.java

@@ -0,0 +1,45 @@
+package com.zhaozhaonews.common.constants;
+
+/**
+ * 自媒体服务常量类
+ * 定义素材收藏、文章类型、图片类型等相关常量
+ *
+ * @author zhaozhaonews
+ */
+public class WemediaConstants {
+
+    // ==================== 素材收藏相关常量 ====================
+
+    /** 收藏素材 */
+    public static final Short COLLECT_MATERIAL = 1;
+
+    /** 取消收藏素材 */
+    public static final Short CANCEL_COLLECT_MATERIAL = 0;
+
+    // ==================== 文章配图类型常量 ====================
+
+    /** 文章类型:图片文章 */
+    public static final String WM_NEWS_TYPE_IMAGE = "image";
+
+    // ==================== 文章图片数量类型常量 ====================
+
+    /** 无图片(纯文字文章) */
+    public static final Short WM_NEWS_NONE_IMAGE = 0;
+
+    /** 单图文章 */
+    public static final Short WM_NEWS_SINGLE_IMAGE = 1;
+
+    /** 多图文章(三图及以上) */
+    public static final Short WM_NEWS_MANY_IMAGE = 3;
+
+    /** 自动类型(根据内容自动判断配图数量) */
+    public static final Short WM_NEWS_TYPE_AUTO = -1;
+
+    // ==================== 引用类型常量 ====================
+
+    /** 内容引用(引用文章正文内容) */
+    public static final Short WM_CONTENT_REFERENCE = 0;
+
+    /** 封面引用(引用文章封面图) */
+    public static final Short WM_COVER_REFERENCE = 1;
+}

+ 40 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/exception/CustomException.java

@@ -0,0 +1,40 @@
+package com.zhaozhaonews.common.exception;
+
+
+import com.zhaozhaonews.model.common.enums.AppHttpCodeEnum;
+import lombok.Getter;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title CustomException
+ * @description 自定义异常
+ * 这个异常继承 运行时异常,不需要在方法中显式声明异常,也不需要主动处理异常
+ * 而且 触发异常后,Spring事务会自动回滚
+ * 使用场景:
+ *  |-素材引用失效
+ *  |-参数校验不通过
+ *  |-其他的业务规则冲突
+ * @create 2026/5/17
+ */
+@Getter
+public class CustomException extends RuntimeException {
+    /**
+     * 错误码枚举
+     * 包含 code errorMassage
+     * SUCCESS(200,"操作成功"),
+     * MATERIASL_REFERENCE_FAIL(3501,"素材引用失效");
+     */
+    private final AppHttpCodeEnum appHttpCodeEnum;
+
+    /**
+     * 构造方法
+     * @param appHttpCodeEnum
+     */
+    public CustomException(AppHttpCodeEnum appHttpCodeEnum) {
+        //调用父类的构造方法,把错误信息传入
+        super(appHttpCodeEnum.getErrorMessage());
+        //对枚举值进行赋值
+        this.appHttpCodeEnum = appHttpCodeEnum;
+    }
+}

+ 61 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/exception/GlobalException.java

@@ -0,0 +1,61 @@
+package com.zhaozhaonews.common.exception;
+
+
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.common.enums.AppHttpCodeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title GlobalException
+ * @description 全局异常处理器
+ * 工作流程:
+ *      Controller抛出一场
+ *          |-匹配@ExceptionHandler 中的异常类型
+ *              |-执行处理方法,返回ResponseResult
+ *                  |-Spring吧ResponseResult转换为JSON返回给前端。
+ * @create 2026/5/17
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalException {
+    /**
+     * 处理自定义异常CustomException
+     * CustomException是我们自己定义的,自己在Service中抛出的一场。
+     * 包含枚举类型的错误码和描述信息
+     * @param e 自定义业务一场
+     * @return 标准化的错误响应
+     */
+    @ExceptionHandler(value = CustomException.class)
+    public ResponseResult handleCustomException(CustomException e) {
+        //记录异常日志(方便后续排查问题)
+        log.error("业务异常:code={},message={}",
+                e.getAppHttpCodeEnum().getCode(),
+                e.getAppHttpCodeEnum().getErrorMessage()
+                );
+        return ResponseResult.errorResult(e.getAppHttpCodeEnum());
+    }
+
+    /**
+     * 处理其他所有的未捕获的异常---兜底处理
+     * 使用Exception.class匹配所有未指定的异常类型
+     * 防止出现 异常泄露到前端显示 出现500
+     * @param e 未捕获的异常
+     * @return 标准化的错误响应
+     */
+    @ExceptionHandler(value = Exception.class)
+    public ResponseResult handleException(Exception e) {
+        //输出堆栈信息,方便在开发阶段定位问题
+        e.printStackTrace();
+        //记录异常日志
+        log.error("系统异常:",e);
+        //返回通用的服务器错误
+        return ResponseResult.errorResult(
+                AppHttpCodeEnum.SERVER_ERROR,   //系统内部异常
+                e.getMessage()  //错误信息
+        );
+    }
+}

+ 80 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionDeserializer.java

@@ -0,0 +1,80 @@
+package com.zhaozhaonews.common.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.zhaozhaonews.util.common.IdsUtils;
+
+import java.io.IOException;
+
+/**
+ * Jackson 反序列化混淆器
+ *
+ * 用于将前端传递的加密/混淆 ID 字符串在反序列化时还原为原始 Long/Integer 类型。
+ * 支持两种反序列化策略:
+ * 1. 若字段类型为 Long/Integer,直接使用 parseLong/parseInt 转换
+ * 2. 否则使用 IdsUtils.decryptLong() 进行解密还原(应对 ID 加密场景)
+ *
+ * 注意:当前 Long 类型直接解析为字符串数值,未涉及真正的加解密。
+ * 若需要 ID 传输安全,可在 IdsUtils 中补充加密/解密逻辑。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @see ConfusionDeserializerModifier
+ * @see IdsUtils
+ * @since 2026/04/19
+ */
+public class ConfusionDeserializer extends JsonDeserializer<Object> {
+
+    /**
+     * 原始的反序列化器,在解密失败时作为降级方案
+     */
+    JsonDeserializer<Object> deserializer = null;
+
+    /**
+     * 字段的 Java 类型信息,用于判断字段类型(Long/Integer/其他)
+     */
+    JavaType type = null;
+
+    /**
+     * 构造反序列化混淆器
+     *
+     * @param deserializer 原始反序列化器
+     * @param type         字段的 Java 类型
+     */
+    public ConfusionDeserializer(JsonDeserializer<Object> deserializer, JavaType type) {
+        this.deserializer = deserializer;
+        this.type = type;
+    }
+
+    /**
+     * 反序列化方法:将 JSON 字符串还原为目标类型的值
+     *
+     * @param p    JSON 解析器
+     * @param ctxt 反序列化上下文
+     * @return 反序列化后的对象(Long/Integer 或解密后的值)
+     * @throws IOException 反序列化异常
+     */
+    @Override
+    public Object deserialize(JsonParser p, DeserializationContext ctxt)
+            throws IOException {
+        try {
+            if (type != null) {
+                if (type.getTypeName().contains("Long")) {
+                    return Long.valueOf(p.getValueAsString());
+                }
+                if (type.getTypeName().contains("Integer")) {
+                    return Integer.valueOf(p.getValueAsString());
+                }
+            }
+            return IdsUtils.decryptLong(p.getValueAsString());
+        } catch (Exception e) {
+            if (deserializer != null) {
+                return deserializer.deserialize(p, ctxt);
+            } else {
+                return p.getCurrentValue();
+            }
+        }
+    }
+}

+ 54 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionDeserializerModifier.java

@@ -0,0 +1,54 @@
+package com.zhaozhaonews.common.jackson;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
+import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
+import com.zhaozhaonews.model.common.annotation.IdEncrypt;
+
+import java.util.Iterator;
+
+/**
+ * Jackson 反序列化修改器
+ *
+ * 继承 BeanDeserializerModifier,在反序列化过程中遍历所有属性,
+ * 为包含 {@link IdEncrypt} 注解或属性名为 "id" 的字段注入 {@link ConfusionDeserializer},
+ * 使前端传递的字符串类型 ID 能够正确反序列化为后端对应的 Long/Integer 类型。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @see ConfusionDeserializer
+ * @see IdEncrypt
+ * @since 2026/04/19
+ */
+public class ConfusionDeserializerModifier extends BeanDeserializerModifier {
+
+    /**
+     * 修改反序列化属性构建器:为符合条件的字段替换反序列化器
+     *
+     * @param config          反序列化配置
+     * @param beanDescription Bean 描述信息
+     * @param builder         反序列化属性构建器
+     * @return 修改后的属性构建器
+     */
+    @Override
+    public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config,
+                                                  final BeanDescription beanDescription,
+                                                  final BeanDeserializerBuilder builder) {
+        Iterator it = builder.getProperties();
+
+        while (it.hasNext()) {
+            SettableBeanProperty p = (SettableBeanProperty) it.next();
+            // 判断条件:字段上有 @IdEncrypt 注解 或 字段名为 "id"
+            if ((null != p.getAnnotation(IdEncrypt.class) || p.getName().equalsIgnoreCase("id"))) {
+                JsonDeserializer<Object> current = p.getValueDeserializer();
+                builder.addOrReplaceProperty(
+                        p.withValueDeserializer(new ConfusionDeserializer(p.getValueDeserializer(), p.getType())),
+                        true);
+            }
+        }
+        return builder;
+    }
+}

+ 76 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionModule.java

@@ -0,0 +1,76 @@
+package com.zhaozhaonews.common.jackson;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Jackson 混淆加密模块
+ *
+ * 自定义 Jackson Module,用于注册 ID 混淆序列化器和反序列化器。
+ * 自动为带有 {@link com.zhaozhaonews.model.common.annotation.IdEncrypt} 注解的字段
+ * 以及名为 "id" 的字段应用混淆序列化/反序列化处理。
+ *
+ * 该模块通过 {@link ConfusionSerializerModifier} 和 {@link ConfusionDeserializerModifier}
+ * 在 Bean 序列化和反序列化过程中动态替换字段处理器。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+public class ConfusionModule extends Module {
+
+    /**
+     * 模块名称常量
+     */
+    public final static String MODULE_NAME = "jackson-confusion-encryption";
+
+    /**
+     * 模块版本信息(主版本号1.0.0)
+     */
+    public final static Version VERSION = new Version(1, 0, 0, null, "zhaozhaonews", MODULE_NAME);
+
+    /**
+     * 获取模块名称
+     *
+     * @return 模块名称字符串
+     */
+    @Override
+    public String getModuleName() {
+        return MODULE_NAME;
+    }
+
+    /**
+     * 获取模块版本
+     *
+     * @return 版本对象
+     */
+    @Override
+    public Version version() {
+        return VERSION;
+    }
+
+    /**
+     * 模块初始化方法,注册自定义的序列化/反序列化修改器
+     *
+     * @param context Jackson 模块设置上下文
+     */
+    @Override
+    public void setupModule(SetupContext context) {
+        context.addBeanSerializerModifier(new ConfusionSerializerModifier());
+        context.addBeanDeserializerModifier(new ConfusionDeserializerModifier());
+    }
+
+    /**
+     * 注册混淆模块到指定 ObjectMapper 的静态工具方法
+     * 同时关闭 FAIL_ON_UNKNOWN_PROPERTIES 特性,避免未知字段报错
+     *
+     * @param objectMapper 待配置的 ObjectMapper 实例
+     * @return 配置完成后的 ObjectMapper 实例
+     */
+    public static ObjectMapper registerModule(ObjectMapper objectMapper) {
+        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        return objectMapper.registerModule(new ConfusionModule());
+    }
+}

+ 46 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionSerializer.java

@@ -0,0 +1,46 @@
+package com.zhaozhaonews.common.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+/**
+ * Jackson 序列化混淆器
+ *
+ * 用于将 Long/Integer 类型的 ID 字段在序列化为 JSON 时自动转换为字符串。
+ * 主要应用于带有 {@link com.zhaozhaonews.model.common.annotation.IdEncrypt} 注解的字段,
+ * 防止前端 JavaScript 处理大精度 Long 类型时出现精度丢失问题。
+ *
+ * 序列化策略:将 ID 值直接调用 toString() 输出为字符串。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @see ConfusionSerializerModifier
+ * @see ConfusionModule
+ * @since 2026/04/19
+ */
+public class ConfusionSerializer extends JsonSerializer<Object> {
+
+    /**
+     * 序列化方法:将 Object 类型的 ID 值转换为字符串写入 JSON
+     *
+     * @param value          待序列化的字段值
+     * @param jsonGenerator  JSON 生成器,用于输出序列化结果
+     * @param serializers    序列化提供者,用于默认序列化兜底
+     * @throws IOException 序列化异常
+     */
+    @Override
+    public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
+        try {
+            if (value != null) {
+                jsonGenerator.writeString(value.toString());
+                return;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        serializers.defaultSerializeValue(value, jsonGenerator);
+    }
+}

+ 51 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/ConfusionSerializerModifier.java

@@ -0,0 +1,51 @@
+package com.zhaozhaonews.common.jackson;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+import com.zhaozhaonews.model.common.annotation.IdEncrypt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Jackson 序列化修改器
+ *
+ * 继承 BeanSerializerModifier,在序列化过程中遍历所有属性,
+ * 为包含 {@link IdEncrypt} 注解或属性名为 "id" 的字段注入 {@link ConfusionSerializer},
+ * 使这些 ID 字段在输出 JSON 时自动转换为字符串类型。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @see ConfusionSerializer
+ * @see IdEncrypt
+ * @since 2026/04/19
+ */
+public class ConfusionSerializerModifier extends BeanSerializerModifier {
+
+    /**
+     * 修改序列化属性列表:为符合条件的字段替换序列化器
+     *
+     * @param config         序列化配置
+     * @param beanDesc       Bean 描述信息
+     * @param beanProperties 原始的属性写入器列表
+     * @return 修改后的属性写入器列表
+     */
+    @Override
+    public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
+                                                     BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
+        List<BeanPropertyWriter> newWriter = new ArrayList<>();
+        for (BeanPropertyWriter writer : beanProperties) {
+            String name = writer.getType().getTypeName();
+            // 判断条件:字段上有 @IdEncrypt 注解 或 字段名为 "id"
+            if (null == writer.getAnnotation(IdEncrypt.class) && !writer.getName().equalsIgnoreCase("id")) {
+                newWriter.add(writer);
+            } else {
+                writer.assignSerializer(new ConfusionSerializer());
+                newWriter.add(writer);
+            }
+        }
+        return newWriter;
+    }
+}

+ 32 - 0
zhaozhaonews/zhaozhaonews-common/src/main/java/com/zhaozhaonews/common/jackson/InitJacksonConfig.java

@@ -0,0 +1,32 @@
+package com.zhaozhaonews.common.jackson;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Jackson 配置初始化类
+ *
+ * Spring Boot 配置类,用于创建并注册自定义的 ObjectMapper Bean。
+ * 通过 ConfusionModule.registerModule() 将 ID 混淆序列化/反序列化功能
+ * 注入到全局 Jackson 配置中,使所有 API 接口自动支持 ID 混淆处理。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Configuration
+public class InitJacksonConfig {
+
+    /**
+     * 创建配置了 ID 混淆模块的 ObjectMapper 实例
+     *
+     * @return 配置完成的 ObjectMapper Bean
+     */
+    @Bean
+    public ObjectMapper objectMapper() {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper = ConfusionModule.registerModule(objectMapper);
+        return objectMapper;
+    }
+}

+ 2 - 0
zhaozhaonews/zhaozhaonews-common/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.zhaozhaonews.common.jackson.InitJacksonConfig

+ 11 - 0
zhaozhaonews/zhaozhaonews-common/src/main/resources/application.yaml

@@ -0,0 +1,11 @@
+spring:
+  aop:
+    proxy-target-class: true
+server:
+  tomcat:
+    uri-encoding: utf-8
+  servlet:
+    encoding:
+      force: true
+      charset: UTF-8
+      enabled: true

+ 53 - 0
zhaozhaonews/zhaozhaonews-model/pom.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.zhaozhaonews</groupId>
+        <artifactId>zhaozhaonews</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>zhaozhaonews-model</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 9 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/AdUserDto.java

@@ -0,0 +1,9 @@
+package com.zhaozhaonews.model.admin.dto;
+
+import lombok.Data;
+
+@Data
+public class AdUserDto {
+    private String name;
+    private String password;
+}

+ 12 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/AuthDto.java

@@ -0,0 +1,12 @@
+package com.zhaozhaonews.model.admin.dto;
+
+import lombok.Data;
+
+@Data
+public class AuthDto {
+    private Integer id;
+    private String msg;
+    private Integer page;
+    private Integer size;
+    private Integer status;
+}

+ 10 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/ChannelDto.java

@@ -0,0 +1,10 @@
+package com.zhaozhaonews.model.admin.dto;
+
+import lombok.Data;
+
+@Data
+public class ChannelDto {
+    private String name;
+    private Integer page;
+    private Integer size;
+}

+ 13 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/dto/NewsAuthDto.java

@@ -0,0 +1,13 @@
+package com.zhaozhaonews.model.admin.dto;
+
+import lombok.Data;
+
+@Data
+public class NewsAuthDto {
+    private Integer id;
+    private String msg;
+    private Integer page;
+    private Integer size;
+    private Integer status;
+    private String title;
+}

+ 47 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/entity/AdUser.java

@@ -0,0 +1,47 @@
+package com.zhaozhaonews.model.admin.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("ad_user")
+public class AdUser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Integer id;
+
+    @TableField("name")
+    private String name;
+
+    @TableField("password")
+    private String password;
+
+    @TableField("salt")
+    private String salt;
+
+    @TableField("nickname")
+    private String nickname;
+
+    @TableField("image")
+    private String image;
+
+    @TableField("phone")
+    private String phone;
+
+    @TableField("status")
+    private Integer status;
+
+    @TableField("email")
+    private String email;
+
+    @TableField("login_time")
+    private Date loginTime;
+
+    @TableField("created_time")
+    private Date createdTime;
+}

+ 56 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/admin/entity/ApUserRealname.java

@@ -0,0 +1,56 @@
+package com.zhaozhaonews.model.admin.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("ap_user_realname")
+public class ApUserRealname implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    @TableField("name")
+    private String name;
+
+    @TableField("idno")
+    private String idno;
+
+    @TableField("font_image")
+    private String fontImage;
+
+    @TableField("back_image")
+    private String backImage;
+
+    @TableField("hold_image")
+    private String holdImage;
+
+    @TableField("live_image")
+    private String liveImage;
+
+    @TableField("status")
+    private Short status;
+
+    @TableField("reason")
+    private String reason;
+
+    @TableField("created_time")
+    private Date createdTime;
+
+    @TableField("submited_time")
+    private Date submitedTime;
+
+    @TableField("updated_time")
+    private Date updatedTime;
+}

+ 25 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dto/ArticleHomeDto.java

@@ -0,0 +1,25 @@
+package com.zhaozhaonews.model.article.dto;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ArticleHomeDto
+ * @description
+ */
+
+@Data
+public class ArticleHomeDto {
+
+    // 最大时间
+    Date maxBehotTime;
+    // 最小时间
+    Date minBehotTime;
+    // 分页size
+    Integer size;
+    // 频道ID
+    String tag;
+}

+ 29 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dtos/ArticleBehaviorVo.java

@@ -0,0 +1,29 @@
+package com.zhaozhaonews.model.article.dtos;
+
+import lombok.Data;
+
+/**
+ * 文章行为状态值对象
+ *
+ * 封装用户对某篇文章的行为状态,包括是否点赞、是否不喜欢、是否收藏、是否关注作者。
+ * 在文章详情接口中返回,供前端展示对应的交互状态。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class ArticleBehaviorVo {
+
+    /** 当前用户是否已点赞 */
+    private Boolean islike = false;
+
+    /** 当前用户是否已点不喜欢 */
+    private Boolean isunlike = false;
+
+    /** 当前用户是否已收藏 */
+    private Boolean iscollection = false;
+
+    /** 当前用户是否已关注作者 */
+    private Boolean isfollow = false;
+}

+ 23 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dtos/ArticleDto.java

@@ -0,0 +1,23 @@
+package com.zhaozhaonews.model.article.dtos;
+
+import com.zhaozhaonews.model.article.entity.ApArticle;
+import lombok.Data;
+
+/**
+ * 文章 DTO
+ *
+ * 继承 {@link ApArticle} 实体,增加文章内容字段用于传输。
+ * 在文章详情或发布场景中使用,包含完整的文章信息 + 正文内容。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class ArticleDto extends ApArticle {
+
+    /**
+     * 文章正文内容(HTML 或 JSON 格式的富文本内容)
+     */
+    private String content;
+}

+ 29 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/dtos/ArticleInfoDto.java

@@ -0,0 +1,29 @@
+package com.zhaozhaonews.model.article.dtos;
+
+import com.zhaozhaonews.model.common.annotation.IdEncrypt;
+import lombok.Data;
+
+/**
+ * 文章信息查询 DTO
+ *
+ * 用于文章详情/文章行为查询接口的请求参数。
+ * articleId 使用 @IdEncrypt 注解,在 JSON 序列化/反序列化时自动做 ID 混淆处理。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class ArticleInfoDto {
+
+    /**
+     * 文章ID(经过 ID 混淆加密,前后端传输时为字符串)
+     */
+    @IdEncrypt
+    Long articleId;
+
+    /**
+     * 作者ID
+     */
+    Integer authorId;
+}

+ 141 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/entity/ApArticle.java

@@ -0,0 +1,141 @@
+package com.zhaozhaonews.model.article.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ApArticle
+ * @description 文章信息表,存储已发布的文章
+ */
+
+@Data
+@TableName("ap_article")
+public class ApArticle implements Serializable {
+
+    @TableId(value = "id",type = IdType.ID_WORKER)
+    private Long id;
+
+    /**
+     * 标题
+     */
+    private String title;
+
+    /**
+     * 作者id
+     */
+    @TableField("author_id")
+    private Long authorId;
+
+    /**
+     * 作者名称
+     */
+    @TableField("author_name")
+    private String authorName;
+
+    /**
+     * 频道id
+     */
+    @TableField("channel_id")
+    private Integer channelId;
+
+    /**
+     * 频道名称
+     */
+    @TableField("channel_name")
+    private String channelName;
+
+    /**
+     * 文章布局  0 无图文章   1 单图文章    2 多图文章
+     */
+    private Short layout;
+
+    /**
+     * 文章标记  0 普通文章   1 热点文章   2 置顶文章   3 精品文章   4 大V 文章
+     */
+    private Byte flag;
+
+    /**
+     * 文章封面图片 多张逗号分隔
+     */
+    private String images;
+
+    /**
+     * 标签
+     */
+    private String labels;
+
+    /**
+     * 点赞数量
+     */
+    private Integer likes;
+
+    /**
+     * 收藏数量
+     */
+    private Integer collection;
+
+    /**
+     * 评论数量
+     */
+    private Integer comment;
+
+    /**
+     * 阅读数量
+     */
+    private Integer views;
+
+    /**
+     * 省市
+     */
+    @TableField("province_id")
+    private Integer provinceId;
+
+    /**
+     * 市区
+     */
+    @TableField("city_id")
+    private Integer cityId;
+
+    /**
+     * 区县
+     */
+    @TableField("county_id")
+    private Integer countyId;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 发布时间
+     */
+    @TableField("publish_time")
+    private Date publishTime;
+
+    /**
+     * 同步状态
+     */
+    @TableField("sync_status")
+    private Boolean syncStatus;
+
+    /**
+     * 来源
+     */
+    private Boolean origin;
+
+    /**
+     * 静态页面地址
+     */
+    @TableField("static_url")
+    private String staticUrl;
+}

+ 76 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/entity/ApArticleConfig.java

@@ -0,0 +1,76 @@
+package com.zhaozhaonews.model.article.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ApArticleConfig
+ * @description APP已发布文章配置表
+ */
+@Data
+@NoArgsConstructor //无参构造器
+@TableName("ap_article_config")
+public class ApArticleConfig implements Serializable {
+    /**
+     * 有参构造器
+     * 设置如果articleId存在,则传入articleId,完成修改操作
+     * @param articleId
+     */
+    public ApArticleConfig(Long articleId){
+        this.articleId = articleId;
+        this.isComment = true;  //默认可以评论
+        this.isForward = true;  //默认可以转发
+        this.isDelete = false;  //默认没有删除
+        this.isDown = false;    //默认没有下架
+    }
+
+
+    @TableId(value = "id",type = IdType.ID_WORKER)
+    private Long id;
+
+    /**
+     * 文章id
+     */
+    @TableField("article_id")
+    private Long articleId;
+
+    /**
+     * 是否可评论
+     * true: 可以评论   1
+     * false: 不可评论  0
+     */
+    @TableField("is_comment")
+    private Boolean isComment;
+
+    /**
+     * 是否转发
+     * true: 可以转发   1
+     * false: 不可转发  0
+     */
+    @TableField("is_forward")
+    private Boolean isForward;
+
+    /**
+     * 是否下架
+     * true: 下架   1
+     * false: 没有下架  0
+     */
+    @TableField("is_down")
+    private Boolean isDown;
+
+    /**
+     * 是否已删除
+     * true: 删除   1
+     * false: 没有删除  0
+     */
+    @TableField("is_delete")
+    private Boolean isDelete;
+}

+ 35 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/article/entity/ApArticleContent.java

@@ -0,0 +1,35 @@
+package com.zhaozhaonews.model.article.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ApArticleContent
+ * @description 文章内容
+ */
+
+@Data
+@TableName("ap_article_content")
+public class ApArticleContent implements Serializable {
+
+    @TableId(value = "id",type = IdType.ID_WORKER)
+    private Long id;
+
+    /**
+     * 文章id
+     */
+    @TableField("article_id")
+    private Long articleId;
+
+    /**
+     * 文章内容
+     */
+    private String content;
+}

+ 34 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/BehaviorEntryDto.java

@@ -0,0 +1,34 @@
+package com.zhaozhaonews.model.behavior.dtos;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 用户行为记录 DTO
+ *
+ * 封装用户行为数据,用于记录和统计用户对文章的各种操作行为。
+ * 可记录阅读时长、操作类型、操作次数等信息,用于用户画像和推荐算法。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class BehaviorEntryDto implements Serializable {
+
+    /** 用户ID */
+    private Long userId;
+
+    /** 文章ID */
+    private Integer articleId;
+
+    /** 操作类型(点赞/评论/收藏/关注等) */
+    private Integer type;
+
+    /** 操作次数 */
+    private Integer count;
+
+    /** 阅读时长(秒) */
+    private Integer readDuration;
+}

+ 37 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/CollectionBehaviorDto.java

@@ -0,0 +1,37 @@
+package com.zhaozhaonews.model.behavior.dtos;
+
+import lombok.Data;
+import java.util.Date;
+
+/**
+ * 收藏行为 DTO
+ *
+ * 用于用户收藏/取消收藏操作的请求参数。
+ * operation 字段区分收藏(1)和取消收藏(0),
+ * publishedTime 记录被收藏内容的发布时间。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class CollectionBehaviorDto {
+
+    /** 被收藏的内容条目ID */
+    private Long entryId;
+
+    /**
+     * 操作类型
+     * 1: 收藏, 0: 取消收藏
+     */
+    private Short operation;
+
+    /** 被收藏内容的发布时间 */
+    private Date publishedTime;
+
+    /**
+     * 收藏类型
+     * 0: 文章, 其他类型待扩展
+     */
+    private Short type;
+}

+ 33 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/LikesBehaviorDto.java

@@ -0,0 +1,33 @@
+package com.zhaozhaonews.model.behavior.dtos;
+
+import lombok.Data;
+
+/**
+ * 点赞行为 DTO
+ *
+ * 用于用户点赞/取消点赞操作的请求参数。
+ * operation 字段区分点赞(1)和取消点赞(0),
+ * type 字段表示点赞类型(0: 文章, 1: 评论等)。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class LikesBehaviorDto {
+
+    /** 被点赞的文章ID */
+    private Long articleId;
+
+    /**
+     * 操作类型
+     * 1: 点赞, 0: 取消点赞
+     */
+    private Short operation;
+
+    /**
+     * 点赞对象类型
+     * 0: 文章, 1: 评论
+     */
+    private Short type;
+}

+ 23 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/ReadBehaviorDto.java

@@ -0,0 +1,23 @@
+package com.zhaozhaonews.model.behavior.dtos;
+
+import lombok.Data;
+
+/**
+ * 阅读行为 DTO
+ *
+ * 用于记录用户对文章的阅读次数信息。
+ * 可用于更新文章的阅读数统计,以及用户阅读行为分析。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class ReadBehaviorDto {
+
+    /** 被阅读的文章ID */
+    private Long articleId;
+
+    /** 阅读次数 */
+    private Integer count;
+}

+ 26 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/behavior/dtos/UnLikesBehaviorDto.java

@@ -0,0 +1,26 @@
+package com.zhaozhaonews.model.behavior.dtos;
+
+import lombok.Data;
+
+/**
+ * 不喜欢行为 DTO
+ *
+ * 用于用户标记"不感兴趣"操作的请求参数。
+ * 帮助推荐系统了解用户偏好,优化推荐内容质量。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class UnLikesBehaviorDto {
+
+    /** 被标记为"不喜欢"的文章ID */
+    private Long articleId;
+
+    /**
+     * 类型
+     * 0: 文章, 1: 评论等
+     */
+    private Short type;
+}

+ 35 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/comment/entity/ApComment.java

@@ -0,0 +1,35 @@
+package com.zhaozhaonews.model.comment.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("ap_comment")
+public class ApComment implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.ID_WORKER)
+    private Long id;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    @TableField("article_id")
+    private Long articleId;
+
+    @TableField("content")
+    private String content;
+
+    @TableField("likes")
+    private Integer likes;
+
+    @TableField("created_time")
+    private Date createdTime;
+}

+ 38 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/comment/entity/ApCommentRepay.java

@@ -0,0 +1,38 @@
+package com.zhaozhaonews.model.comment.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("ap_comment_repay")
+public class ApCommentRepay implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.ID_WORKER)
+    private Long id;
+
+    @TableField("comment_id")
+    private Long commentId;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    @TableField("target_user_id")
+    private Integer targetUserId;
+
+    @TableField("content")
+    private String content;
+
+    @TableField("likes")
+    private Integer likes;
+
+    @TableField("created_time")
+    private Date createdTime;
+}

+ 28 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/annotation/IdEncrypt.java

@@ -0,0 +1,28 @@
+package com.zhaozhaonews.model.common.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * ID 加密/混淆注解
+ *
+ * 标注在需要做 ID 混淆处理的字段上,用于 Jackson 序列化/反序列化过程中的自动转换。
+ * 序列化时将 Long 类型的 ID 转换为字符串(防止前端精度丢失);
+ * 反序列化时将字符串还原为 Long/Integer 类型。
+ *
+ * 配合 {@link com.zhaozhaonews.common.jackson.ConfusionSerializerModifier} 和
+ * {@link com.zhaozhaonews.common.jackson.ConfusionDeserializerModifier} 使用。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@JacksonAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
+public @interface IdEncrypt {
+}

+ 21 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/dto/PageRequestDto.java

@@ -0,0 +1,21 @@
+package com.zhaozhaonews.model.common.dto;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+@Data
+@Slf4j
+public class PageRequestDto {
+
+    protected Integer size;
+    protected Integer page;
+
+    public void checkParam() {
+        if (this.page == null || this.page < 0) {
+            setPage(1);
+        }
+        if (this.size == null || this.size < 0 || this.size > 100) {
+            setSize(10);
+        }
+    }
+}

+ 44 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/dto/PageResponseResult.java

@@ -0,0 +1,44 @@
+package com.zhaozhaonews.model.common.dto;
+
+import java.io.Serializable;
+
+public class PageResponseResult extends ResponseResult implements Serializable {
+    private Integer currentPage;
+    private Integer size;
+    private Integer total;
+
+    public PageResponseResult(Integer currentPage, Integer size, Integer total) {
+        this.currentPage = currentPage;
+        this.size = size;
+        this.total = total;
+    }
+
+    public PageResponseResult() {
+
+    }
+
+
+    public int getCurrentPage() {
+        return currentPage;
+    }
+
+    public void setCurrentPage(int currentPage) {
+        this.currentPage = currentPage;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public int getTotal() {
+        return total;
+    }
+
+    public void setTotal(int total) {
+        this.total = total;
+    }
+}

+ 168 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/dto/ResponseResult.java

@@ -0,0 +1,168 @@
+package com.zhaozhaonews.model.common.dto;
+
+import com.alibaba.fastjson.JSON;
+import com.zhaozhaonews.model.common.enums.AppHttpCodeEnum;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 通用的结果返回类
+ * @param <T>
+ */
+public class ResponseResult<T> implements Serializable {
+
+    private String host;
+
+    private Integer code;
+
+    private String errorMessage;
+
+    private T data;
+
+    public ResponseResult() {
+        this.code = 200;
+    }
+
+    public ResponseResult(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg, T data) {
+        this.code = code;
+        this.errorMessage = msg;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg) {
+        this.code = code;
+        this.errorMessage = msg;
+    }
+
+    public static ResponseResult errorResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.error(code, msg);
+    }
+
+    public static ResponseResult okResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.ok(code, null, msg);
+    }
+
+    public static ResponseResult okResult(Object data) {
+        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
+        if(data!=null) {
+            result.setData(data);
+        }
+        return result;
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums){
+        return setAppHttpCodeEnum(enums,enums.getErrorMessage());
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
+        return setAppHttpCodeEnum(enums,errorMessage);
+    }
+
+    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
+        return okResult(enums.getCode(),enums.getErrorMessage());
+    }
+
+    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
+        return okResult(enums.getCode(),errorMessage);
+    }
+
+    public ResponseResult<?> error(Integer code, String msg) {
+        this.code = code;
+        this.errorMessage = msg;
+        return this;
+    }
+
+    public ResponseResult<?> ok(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+        return this;
+    }
+
+    public ResponseResult<?> ok(Integer code, T data, String msg) {
+        this.code = code;
+        this.data = data;
+        this.errorMessage = msg;
+        return this;
+    }
+
+    public ResponseResult<?> ok(T data) {
+        this.data = data;
+        return this;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+
+    public static void main(String[] args) {
+        //前置
+        /*AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
+        System.out.println(success.getCode());
+        System.out.println(success.getErrorMessage());*/
+
+        //查询一个对象
+        /*Map map = new HashMap();
+        map.put("name","zhangsan");
+        map.put("age",18);
+        ResponseResult result = ResponseResult.okResult(map);
+        System.out.println(JSON.toJSONString(result));*/
+
+
+        //新增,修改,删除  在项目中统一返回成功即可
+        /*ResponseResult result = ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
+        System.out.println(JSON.toJSONString(result));*/
+
+
+        //根据不用的业务返回不同的提示信息  比如:当前操作需要登录、参数错误
+        /*ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN,"自定义提示信息");
+        System.out.println(JSON.toJSONString(result));*/
+
+        //查询分页信息
+        PageResponseResult responseResult = new PageResponseResult(1,5,50);
+        List list = new ArrayList();
+        list.add("wanjl");
+        list.add("zrrd");
+        responseResult.setData(list);
+        System.out.println(JSON.toJSONString(responseResult));
+
+    }
+
+}

+ 58 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/common/enums/AppHttpCodeEnum.java

@@ -0,0 +1,58 @@
+package com.zhaozhaonews.model.common.enums;
+
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title AppHttpCodeEnum
+ * @description
+ * @create 2025/2/14
+ */
+public enum AppHttpCodeEnum {
+
+    // 成功段固定为200
+    SUCCESS(200,"操作成功"),
+    // 登录段1~50
+    NEED_LOGIN(1,"需要登录后操作"),
+    LOGIN_PASSWORD_ERROR(2,"密码错误"),
+    // TOKEN50~100
+    TOKEN_INVALID(50,"无效的TOKEN"),
+    TOKEN_EXPIRE(51,"TOKEN已过期"),
+    TOKEN_REQUIRE(52,"TOKEN是必须的"),
+    // SIGN验签 100~120
+    SIGN_INVALID(100,"无效的SIGN"),
+    SIG_TIMEOUT(101,"SIGN已过期"),
+    // 参数错误 500~1000
+    PARAM_REQUIRE(500,"缺少参数"),
+    PARAM_INVALID(501,"无效参数"),
+    PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
+    SERVER_ERROR(503,"服务器内部错误"),
+    // 数据错误 1000~2000
+    DATA_EXIST(1000,"数据已经存在"),
+    AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
+    DATA_NOT_EXIST(1002,"数据不存在"),
+    // 数据错误 3000~3500
+    NO_OPERATOR_AUTH(3000,"无权限操作"),
+    NEED_ADMIND(3001,"需要管理员权限"),
+    /** 无权限 */
+    NO_PERMISSION(3002, "无权限"),
+    // 自媒体文章错误 3501~3600
+    MATERIASL_REFERENCE_FAIL(3501,"素材引用失效");
+
+
+    int code;
+    String errorMessage;
+
+    AppHttpCodeEnum(int code, String errorMessage){
+        this.code = code;
+        this.errorMessage = errorMessage;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+}

+ 35 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/schedule/dtos/Task.java

@@ -0,0 +1,35 @@
+package com.zhaozhaonews.model.schedule.dtos;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 延迟任务 DTO
+ *
+ * 封装延迟任务调度的核心信息。
+ * 用于将任务发布到延迟队列中,在指定时间(executeTime)触发执行。
+ * 适用于定时发布文章、延迟消息通知等场景。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class Task implements Serializable {
+
+    /** 任务唯一标识 */
+    private Long taskId;
+
+    /** 任务类型,用于区分不同的业务处理逻辑 */
+    private Integer taskType;
+
+    /** 任务优先级,数值越大优先级越高 */
+    private Integer priority;
+
+    /** 任务执行时间戳(毫秒),到达此时间后任务将被执行 */
+    private long executeTime;
+
+    /** 任务参数(二进制格式),存储业务相关的上下文数据 */
+    private byte[] parameters;
+}

+ 26 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/search/entity/ApAssociateWords.java

@@ -0,0 +1,26 @@
+package com.zhaozhaonews.model.search.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("ap_associate_words")
+public class ApAssociateWords implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("word")
+    private String word;
+
+    @TableField("created_time")
+    private Date createdTime;
+}

+ 29 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/search/entity/ApUserSearch.java

@@ -0,0 +1,29 @@
+package com.zhaozhaonews.model.search.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("ap_user_search")
+public class ApUserSearch implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.ID_WORKER)
+    private Long id;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    @TableField("keyword")
+    private String keyword;
+
+    @TableField("created_time")
+    private Date createdTime;
+}

+ 22 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/dto/LoginDto.java

@@ -0,0 +1,22 @@
+package com.zhaozhaonews.model.user.dto;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title LoginDto
+ * @description
+ * @create 2026/4/12
+ */
+@Data
+@ApiModel("登录参数")
+public class LoginDto {
+    @ApiModelProperty(value = "手机号",required = true)
+    private String phone;
+    @ApiModelProperty(value = "密码",required = true)
+    private String password;
+}

+ 10 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/dto/RegisterDto.java

@@ -0,0 +1,10 @@
+package com.zhaozhaonews.model.user.dto;
+
+import lombok.Data;
+
+@Data
+public class RegisterDto {
+    private String phone;
+    private String code;
+    private String password;
+}

+ 29 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/dto/UserRelationDto.java

@@ -0,0 +1,29 @@
+package com.zhaozhaonews.model.user.dto;
+
+import lombok.Data;
+
+/**
+ * 用户关系操作 DTO
+ *
+ * 封装用户关注/取消关注操作的请求参数。
+ * operation 字段区分关注(1)和取消关注(0)两种操作。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class UserRelationDto {
+
+    /** 文章ID,用于确定关注的作者来源 */
+    private Long articleId;
+
+    /** 作者ID,即被关注/取消关注的用户ID */
+    private Integer authorId;
+
+    /**
+     * 操作类型
+     * 1: 关注, 0: 取消关注
+     */
+    private Short operation;
+}

+ 93 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/user/entity/ApUser.java

@@ -0,0 +1,93 @@
+package com.zhaozhaonews.model.user.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 用户实体类
+ * 表名:ap_user
+ *
+ * @author zhaozhaonews
+ */
+@Data
+@TableName("ap_user")
+public class ApUser {
+
+    /**
+     * 用户ID,自增主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 密码盐值
+     */
+    @TableField("salt")
+    private String salt;
+
+    /**
+     * 用户姓名
+     */
+    @TableField("name")
+    private String name;
+
+    /**
+     * 加密密码
+     */
+    @TableField("password")
+    private String password;
+
+    /**
+     * 手机号
+     */
+    @TableField("phone")
+    private String phone;
+
+    /**
+     * 头像地址
+     */
+    @TableField("image")
+    private String image;
+
+    /**
+     * 性别
+     * 数据库字段:sex tinyint
+     */
+    @TableField("sex")
+    private Boolean sex;
+
+    /**
+     * 是否认证
+     * 数据库字段:is_certification tinyint -> 移除is_ + 驼峰
+     */
+    @TableField("is_certification")
+    private Boolean certification;
+
+    /**
+     * 是否实名认证
+     * 数据库字段:is_identity_authentication tinyint -> 移除is_ + 驼峰
+     */
+    @TableField("is_identity_authentication")
+    private Boolean identityAuthentication;
+
+    /**
+     * 账号状态
+     * 数据库字段:status tinyint
+     */
+    @TableField("status")
+    private Boolean status;
+
+    /**
+     * 逻辑删除标记
+     * 数据库字段:flag tinyint
+     */
+    @TableField("flag")
+    private Boolean flag;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+}

+ 16 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmLoginDto.java

@@ -0,0 +1,16 @@
+package com.zhaozhaonews.model.wemedia.dto;
+
+import lombok.Data;
+
+@Data
+public class WmLoginDto {
+
+    /**
+     * 用户名
+     */
+    private String name;
+    /**
+     * 密码
+     */
+    private String password;
+}

+ 24 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmMaterialDto.java

@@ -0,0 +1,24 @@
+package com.zhaozhaonews.model.wemedia.dto;
+
+import com.zhaozhaonews.model.common.dto.PageRequestDto;
+import lombok.Data;
+
+/**
+ * 自媒体素材查询 DTO
+ *
+ * 继承 {@link PageRequestDto},用于素材管理列表的分页查询。
+ * 支持按收藏状态(isCollection)筛选素材。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Data
+public class WmMaterialDto extends PageRequestDto {
+
+    /**
+     * 收藏状态筛选
+     * 1: 已收藏, 0: 未收藏, null: 全部
+     */
+    private Short isCollection;
+}

+ 64 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmNewsDto.java

@@ -0,0 +1,64 @@
+package com.zhaozhaonews.model.wemedia.dto;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 自媒体文章数据传输对象
+ * 用于前端提交文章信息时的数据封装
+ */
+@Data
+public class WmNewsDto {
+
+    /**
+     * 文章ID(更新时传入,新增时不传)
+     */
+    private Integer id;
+
+    /**
+     * 文章标题
+     */
+    private String title;
+
+    /**
+     * 频道ID(所属栏目)
+     */
+    private Integer channelId;
+
+    /**
+     * 文章标签(多个标签用逗号分隔)
+     */
+    private String labels;
+
+    /**
+     * 发布时间
+     */
+    private Date publishTime;
+
+    /**
+     * 文章内容(富文本HTML)
+     */
+    private String content;
+
+    /**
+     * 文章类型:0-图文,1-视频
+     */
+    private Short type;
+
+    /**
+     * 提交时间
+     */
+    private Date submitedTime;
+
+    /**
+     * 文章状态:0-草稿,1-待审核,2-审核通过,3-审核失败
+     */
+    private Short status;
+
+    /**
+     * 文章图片列表(封面图URL数组)
+     */
+    private List<String> images;
+}

+ 39 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/dto/WmNewsPageReqDto.java

@@ -0,0 +1,39 @@
+package com.zhaozhaonews.model.wemedia.dto;
+
+import com.zhaozhaonews.model.common.dto.PageRequestDto;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 自媒体文章分页查询请求DTO
+ * 继承PageRequestDto获得分页参数(pageNum、pageSize)
+ */
+@Data
+public class WmNewsPageReqDto extends PageRequestDto {
+
+    /**
+     * 文章状态:0-草稿,1-待审核,2-审核通过,3-审核失败(可选)
+     */
+    private Short status;
+
+    /**
+     * 发布开始时间(范围查询起始时间)
+     */
+    private Date beginPubDate;
+
+    /**
+     * 发布结束时间(范围查询结束时间)
+     */
+    private Date endPubDate;
+
+    /**
+     * 频道ID(按栏目筛选)
+     */
+    private Integer channelId;
+
+    /**
+     * 搜索关键词(匹配标题或内容)
+     */
+    private String keyword;
+}

+ 38 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmChannel.java

@@ -0,0 +1,38 @@
+package com.zhaozhaonews.model.wemedia.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("wm_channel")
+public class WmChannel implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("name")
+    private String name;
+
+    @TableField("description")
+    private String description;
+
+    @TableField("is_default")
+    private Boolean isDefault;
+
+    @TableField("status")
+    private Integer status;
+
+    @TableField("ord")
+    private Integer ord;
+
+    @TableField("created_time")
+    private Date createdTime;
+}

+ 39 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmMaterial.java

@@ -0,0 +1,39 @@
+package com.zhaozhaonews.model.wemedia.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("wm_material")
+public class WmMaterial implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    @TableField("url")
+    private String url;
+    /**
+     * 文件类型
+     *  0 图片
+     *  1 视频
+     */
+    @TableField("type")
+    private Short type;
+
+    @TableField("is_collection")
+    private Short isCollection;
+
+    @TableField("created_time")
+    private Date createdTime;
+}

+ 91 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmNews.java

@@ -0,0 +1,91 @@
+package com.zhaozhaonews.model.wemedia.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("wm_news")
+public class WmNews implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    private String title;
+
+    private String content;
+
+    private Short type;
+
+    @TableField("channel_id")
+    private Integer channelId;
+
+    private String labels;
+
+    @TableField("created_time")
+    private Date createdTime;
+
+    @TableField("submited_time")
+    private Date submitedTime;
+
+    private Short status;
+
+    @TableField("publish_time")
+    private Date publishTime;
+
+    private String reason;
+
+    @TableField("article_id")
+    private Long articleId;
+
+    private String images;
+    /**
+     * 上架状态
+     */
+    private Short enable;
+
+    /**
+     * 文章状态 内部枚举类
+     */
+    public enum Status {
+        /*
+            草稿(0)-->待审核(1)-->审核失败(2)
+                        |            |            |-->   管理员审核失败(3)
+                        |            -->管理员审核  -->管理员审核成功(4) -->审核通过-待发布(8)-->已发布(9)
+                        |-------->审核通过-待发布(8)-->已发布(9)
+         */
+        /*草稿*/
+        NORMAL((short) 0),
+        /*待审核*/
+        SUBMIT((short) 1),
+        /*审核失败*/
+        FAIL((short) 2),
+        /*管理员审核失败*/
+        ADMIN_AUTH((short) 3),
+        /*管理员审核成功*/
+        ADMIN_SUCCESS((short) 4),
+        /*审核通过-待发布*/
+        SUCCESS((short) 8),
+        /*已发布*/
+        PUBLISHED((short) 9);
+        private final short code;
+
+        Status(short code) {
+            this.code = code;
+        }
+
+        public short getCode() {
+            return code;
+        }
+    }
+}

+ 53 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmNewsMaterial.java

@@ -0,0 +1,53 @@
+package com.zhaozhaonews.model.wemedia.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 自媒体图文引用素材信息表 实体类
+ *
+ * @author zhaozhaonews
+ * @description 记录图文与素材的引用关系,包括内容引用和封面引用
+ * @create 2026/5/17
+ */
+@Data
+@TableName("wm_news_material")
+public class WmNewsMaterial implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID,自增
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 素材ID,关联素材表
+     */
+    @TableField("material_id")
+    private Integer materialId;
+
+    /**
+     * 图文ID(文章ID),关联 wm_news 表
+     */
+    @TableField("news_id")
+    private Integer newsId;
+
+    /**
+     * 引用类型
+     * - 0:内容引用(文章正文中使用的素材)
+     * - 1:主图引用(封面图片使用的素材)
+     */
+    private Short type;
+
+    /**
+     * 引用排序顺序,用于多图时确定显示顺序
+     */
+    private Integer ord;
+}

+ 115 - 0
zhaozhaonews/zhaozhaonews-model/src/main/java/com/zhaozhaonews/model/wemedia/entity/WmUser.java

@@ -0,0 +1,115 @@
+package com.zhaozhaonews.model.wemedia.entity;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 自媒体用户信息表
+ */
+@Data
+@TableName("wm_user")
+public class WmUser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("ap_user_id")
+    private Integer apUserId;
+
+    @TableField("ap_author_id")
+    private Integer apAuthorId;
+    /**
+     * 登录用户名
+     */
+    @TableField("name")
+    private String name;
+
+    /**
+     * 登录密码
+     */
+    @TableField("password")
+    private String password;
+
+    /**
+     * 盐
+     */
+    @TableField("salt")
+    private String salt;
+
+    /**
+     * 昵称
+     */
+    @TableField("nickname")
+    private String nickname;
+
+    /**
+     * 头像
+     */
+    @TableField("image")
+    private String image;
+
+    /**
+     * 归属地
+     */
+    @TableField("location")
+    private String location;
+
+    /**
+     * 手机号
+     */
+    @TableField("phone")
+    private String phone;
+
+    /**
+     * 状态
+     0 暂时不可用
+     1 永久不可用
+     9 正常可用
+     */
+    @TableField("status")
+    private Integer status;
+
+    /**
+     * 邮箱
+     */
+    @TableField("email")
+    private String email;
+
+    /**
+     * 账号类型
+     0 个人
+     1 企业
+     2 子账号
+     */
+    @TableField("type")
+    private Integer type;
+
+    /**
+     * 运营评分
+     */
+    @TableField("score")
+    private Integer score;
+
+    /**
+     * 最后一次登录时间
+     */
+    @TableField("login_time")
+    private Date loginTime;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private Date createdTime;
+
+}

+ 57 - 0
zhaozhaonews/zhaozhaonews-util/pom.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.zhaozhaonews</groupId>
+        <artifactId>zhaozhaonews</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>zhaozhaonews-util</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.zhaozhaonews</groupId>
+            <artifactId>zhaozhaonews-model</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+        </dependency>
+        <!--匹配度工具包-->
+        <dependency>
+            <groupId>com.hankcs</groupId>
+            <artifactId>hanlp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>1.9.3</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 120 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/AppJwtUtil.java

@@ -0,0 +1,120 @@
+package com.zhaozhaonews.util.common;
+
+import io.jsonwebtoken.*;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.*;
+
+public class AppJwtUtil {
+
+    // TOKEN的有效期一天(S)
+    private static final int TOKEN_TIME_OUT = 3_600;
+    // 加密KEY
+    // 字符串“wanjl”通过MD5加密,加密后内容进行Base64加密生成43位字符串。作为通用加密KEY
+    private static final String TOKEN_ENCRY_KEY = "MzE1NDdjMjJjOTNkNDRjZjRlODg3ZGRhNjIwMGFmNWU";
+    // 最小刷新间隔(S)
+    private static final int REFRESH_TIME = 300;
+
+    // 根据用户ID生成token
+    public static String getToken(Long id){
+        Map<String, Object> claimMaps = new HashMap<>();
+        claimMaps.put("id",id);
+        long currentTime = System.currentTimeMillis();
+        return Jwts.builder()
+                .setId(UUID.randomUUID().toString())
+                .setIssuedAt(new Date(currentTime))  //签发时间
+                .setSubject("system")  //说明
+                .setIssuer("wanjl") //签发者信息
+                .setAudience("app")  //接收用户
+                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
+                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
+                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
+                .addClaims(claimMaps) //cla信息
+                .compact();
+    }
+
+    /**
+     * 获取token中的claims信息
+     *
+     * @param token
+     * @return
+     */
+    private static Jws<Claims> getJws(String token) {
+            return Jwts.parser()
+                    .setSigningKey(generalKey())
+                    .parseClaimsJws(token);
+    }
+
+    /**
+     * 获取payload body信息
+     *
+     * @param token
+     * @return
+     */
+    public static Claims getClaimsBody(String token) {
+        try {
+            return getJws(token).getBody();
+        }catch (ExpiredJwtException e){
+            return null;
+        }
+    }
+
+    /**
+     * 获取hearder body信息
+     *
+     * @param token
+     * @return
+     */
+    public static JwsHeader getHeaderBody(String token) {
+        return getJws(token).getHeader();
+    }
+
+    /**
+     * 是否过期
+     *
+     * @param claims
+     * @return -1:有效,0:有效,1:过期,2:过期
+     */
+    public static int verifyToken(Claims claims) {
+        if(claims==null){
+            return 1;
+        }
+        try {
+            claims.getExpiration()
+                    .before(new Date());
+            // 需要自动刷新TOKEN
+            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
+                return -1;
+            }else {
+                return 0;
+            }
+        } catch (ExpiredJwtException ex) {
+            return 1;
+        }catch (Exception e){
+            return 2;
+        }
+    }
+
+    /**
+     * 由字符串生成加密key
+     *
+     * @return
+     */
+    public static SecretKey generalKey() {
+        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
+        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
+        return key;
+    }
+
+    public static void main(String[] args) {
+       /* Map map = new HashMap();
+        map.put("id","11");*/
+        System.out.println(AppJwtUtil.getToken(1102L));
+        Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQrDIBAA_7LnCO6qq8lvTGLA0BZhDW0o_Xs2h95mGOYLe68wQeDIHi0axMUan5bNzG4jM8YSCYOj4hkGqLnDhDx6pkDWDSDHrLec0svz7iKq7_zaH2r5WNVya8rl0_5nSvdZtSFa-l1VJYAxgwAAAA.3OHb1hwmchluFXRIzMhOY7PDiFVb6MjTDfP-jAv40R9rJGKugvcGoFgubN3ZEzzgcxc0JXVlRAYOeEzIS19NrA");
+        Claims claims = jws.getBody();
+        System.out.println(claims.get("id"));
+
+    }
+
+}

+ 777 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/BCrypt.java

@@ -0,0 +1,777 @@
+// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package com.zhaozhaonews.util.common;
+
+import java.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
+
+/**
+ * BCrypt implements OpenBSD-style Blowfish password hashing using
+ * the scheme described in "A Future-Adaptable Password Scheme" by
+ * Niels Provos and David Mazieres.
+ * <p>
+ * This password hashing system tries to thwart off-line password
+ * cracking using a computationally-intensive hashing algorithm,
+ * based on Bruce Schneier's Blowfish cipher. The work factor of
+ * the algorithm is parameterised, so it can be increased as
+ * computers get faster.
+ * <p>
+ * Usage is really simple. To hash a password for the first time,
+ * call the hashpw method with a random salt, like this:
+ * <p>
+ * <code>
+ * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br />
+ * </code>
+ * <p>
+ * To check whether a plaintext password matches one that has been
+ * hashed previously, use the checkpw method:
+ * <p>
+ * <code>
+ * if (BCrypt.checkpw(candidate_password, stored_hash))<br />
+ * &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It matches");<br />
+ * else<br />
+ * &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br />
+ * </code>
+ * <p>
+ * The gensalt() method takes an optional parameter (log_rounds)
+ * that determines the computational complexity of the hashing:
+ * <p>
+ * <code>
+ * String strong_salt = BCrypt.gensalt(10)<br />
+ * String stronger_salt = BCrypt.gensalt(12)<br />
+ * </code>
+ * <p>
+ * The amount of work increases exponentially (2**log_rounds), so 
+ * each increment is twice as much work. The default log_rounds is
+ * 10, and the valid range is 4 to 30.
+ *
+ * @author Damien Miller
+ * @version 0.2
+ */
+public class BCrypt {
+	// BCrypt parameters
+	private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
+	private static final int BCRYPT_SALT_LEN = 16;
+
+	// Blowfish parameters
+	private static final int BLOWFISH_NUM_ROUNDS = 16;
+
+	// Initial contents of key schedule
+	private static final int P_orig[] = {
+		0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
+		0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
+		0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+		0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
+		0x9216d5d9, 0x8979fb1b
+	};
+	private static final int S_orig[] = {
+		0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
+		0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
+		0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+		0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
+		0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
+		0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+		0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
+		0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
+		0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+		0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
+		0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
+		0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+		0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
+		0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
+		0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+		0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
+		0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
+		0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+		0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
+		0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
+		0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+		0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
+		0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
+		0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+		0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
+		0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
+		0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+		0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
+		0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
+		0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+		0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
+		0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
+		0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+		0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
+		0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
+		0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+		0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
+		0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
+		0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+		0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
+		0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
+		0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+		0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
+		0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
+		0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+		0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
+		0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
+		0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+		0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
+		0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
+		0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+		0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
+		0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
+		0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+		0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
+		0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
+		0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+		0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
+		0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
+		0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+		0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
+		0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
+		0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+		0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
+		0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
+		0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
+		0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+		0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
+		0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
+		0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+		0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
+		0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
+		0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+		0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
+		0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
+		0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+		0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
+		0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
+		0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+		0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
+		0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
+		0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+		0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
+		0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
+		0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+		0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
+		0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
+		0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+		0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
+		0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
+		0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+		0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
+		0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
+		0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+		0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
+		0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
+		0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+		0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
+		0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
+		0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+		0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
+		0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
+		0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+		0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
+		0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
+		0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+		0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
+		0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
+		0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+		0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
+		0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
+		0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+		0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
+		0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
+		0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+		0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
+		0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
+		0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+		0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
+		0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
+		0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+		0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
+		0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
+		0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+		0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
+		0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
+		0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+		0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
+		0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
+		0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
+		0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+		0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
+		0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
+		0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+		0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
+		0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
+		0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+		0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
+		0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
+		0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+		0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
+		0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
+		0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+		0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
+		0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
+		0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+		0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
+		0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
+		0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+		0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
+		0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
+		0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+		0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
+		0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
+		0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+		0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
+		0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
+		0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+		0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
+		0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
+		0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+		0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
+		0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
+		0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+		0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
+		0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
+		0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+		0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
+		0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
+		0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+		0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
+		0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
+		0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+		0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
+		0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
+		0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+		0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
+		0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
+		0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+		0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
+		0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
+		0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+		0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
+		0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
+		0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+		0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
+		0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
+		0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+		0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
+		0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
+		0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+		0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
+		0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
+		0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
+		0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+		0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
+		0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
+		0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+		0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
+		0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
+		0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+		0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
+		0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
+		0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+		0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
+		0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
+		0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+		0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
+		0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
+		0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+		0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
+		0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
+		0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+		0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
+		0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
+		0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+		0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
+		0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
+		0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+		0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
+		0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
+		0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+		0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
+		0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
+		0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+		0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
+		0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
+		0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+		0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
+		0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
+		0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+		0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
+		0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
+		0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+		0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
+		0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
+		0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+		0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
+		0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
+		0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+		0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
+		0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
+		0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+		0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
+		0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
+		0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+		0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
+		0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
+		0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+		0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
+		0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
+		0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+		0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
+		0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
+		0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+		0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
+	};
+
+	// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
+	// this "ciphertext", but it is really plaintext or an IV. We keep
+	// the name to make code comparison easier.
+	static private final int bf_crypt_ciphertext[] = {
+		0x4f727068, 0x65616e42, 0x65686f6c,
+		0x64657253, 0x63727944, 0x6f756274
+	};
+
+	// Table for Base64 encoding
+	static private final char base64_code[] = {
+		'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+		'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+		'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+		'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
+		'6', '7', '8', '9'
+	};
+
+	// Table for Base64 decoding
+	static private final byte index_64[] = {
+		-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+		-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+		-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+		-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+		-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
+		56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
+		-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
+		7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+		17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
+		-1, -1, -1, -1, -1, -1, 28, 29, 30,
+		31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+		41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+		51, 52, 53, -1, -1, -1, -1, -1
+	};
+
+	// Expanded Blowfish key
+	private int P[];
+	private int S[];
+
+	/**
+	 * Encode a byte array using bcrypt's slightly-modified base64
+	 * encoding scheme. Note that this is *not* compatible with
+	 * the standard MIME-base64 encoding.
+	 *
+	 * @param d	the byte array to encode
+	 * @param len	the number of bytes to encode
+	 * @return	base64-encoded string
+	 * @exception IllegalArgumentException if the length is invalid
+	 */
+	private static String encode_base64(byte d[], int len)
+		throws IllegalArgumentException {
+		int off = 0;
+		StringBuffer rs = new StringBuffer();
+		int c1, c2;
+
+		if (len <= 0 || len > d.length)
+			throw new IllegalArgumentException ("Invalid len");
+
+		while (off < len) {
+			c1 = d[off++] & 0xff;
+			rs.append(base64_code[(c1 >> 2) & 0x3f]);
+			c1 = (c1 & 0x03) << 4;
+			if (off >= len) {
+				rs.append(base64_code[c1 & 0x3f]);
+				break;
+			}
+			c2 = d[off++] & 0xff;
+			c1 |= (c2 >> 4) & 0x0f;
+			rs.append(base64_code[c1 & 0x3f]);
+			c1 = (c2 & 0x0f) << 2;
+			if (off >= len) {
+				rs.append(base64_code[c1 & 0x3f]);
+				break;
+			}
+			c2 = d[off++] & 0xff;
+			c1 |= (c2 >> 6) & 0x03;
+			rs.append(base64_code[c1 & 0x3f]);
+			rs.append(base64_code[c2 & 0x3f]);
+		}
+		return rs.toString();
+	}
+
+	/**
+	 * Look up the 3 bits base64-encoded by the specified character,
+	 * range-checking againt conversion table
+	 * @param x	the base64-encoded value
+	 * @return	the decoded value of x
+	 */
+	private static byte char64(char x) {
+		if ((int)x < 0 || (int)x > index_64.length)
+			return -1;
+		return index_64[(int)x];
+	}
+
+	/**
+	 * Decode a string encoded using bcrypt's base64 scheme to a
+	 * byte array. Note that this is *not* compatible with
+	 * the standard MIME-base64 encoding.
+	 * @param s	the string to decode
+	 * @param maxolen	the maximum number of bytes to decode
+	 * @return	an array containing the decoded bytes
+	 * @throws IllegalArgumentException if maxolen is invalid
+	 */
+	private static byte[] decode_base64(String s, int maxolen)
+		throws IllegalArgumentException {
+		StringBuffer rs = new StringBuffer();
+		int off = 0, slen = s.length(), olen = 0;
+		byte ret[];
+		byte c1, c2, c3, c4, o;
+
+		if (maxolen <= 0)
+			throw new IllegalArgumentException ("Invalid maxolen");
+
+		while (off < slen - 1 && olen < maxolen) {
+			c1 = char64(s.charAt(off++));
+			c2 = char64(s.charAt(off++));
+			if (c1 == -1 || c2 == -1)
+				break;
+			o = (byte)(c1 << 2);
+			o |= (c2 & 0x30) >> 4;
+			rs.append((char)o);
+			if (++olen >= maxolen || off >= slen)
+				break;
+			c3 = char64(s.charAt(off++));
+			if (c3 == -1)
+				break;
+			o = (byte)((c2 & 0x0f) << 4);
+			o |= (c3 & 0x3c) >> 2;
+			rs.append((char)o);
+			if (++olen >= maxolen || off >= slen)
+				break;
+			c4 = char64(s.charAt(off++));
+			o = (byte)((c3 & 0x03) << 6);
+			o |= c4;
+			rs.append((char)o);
+			++olen;
+		}
+
+		ret = new byte[olen];
+		for (off = 0; off < olen; off++)
+			ret[off] = (byte)rs.charAt(off);
+		return ret;
+	}
+
+	/**
+	 * Blowfish encipher a single 64-bit block encoded as
+	 * two 32-bit halves
+	 * @param lr	an array containing the two 32-bit half blocks
+	 * @param off	the position in the array of the blocks
+	 */
+	private final void encipher(int lr[], int off) {
+		int i, n, l = lr[off], r = lr[off + 1];
+
+		l ^= P[0];
+		for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
+			// Feistel substitution on left word
+			n = S[(l >> 24) & 0xff];
+			n += S[0x100 | ((l >> 16) & 0xff)];
+			n ^= S[0x200 | ((l >> 8) & 0xff)];
+			n += S[0x300 | (l & 0xff)];
+			r ^= n ^ P[++i];
+
+			// Feistel substitution on right word
+			n = S[(r >> 24) & 0xff];
+			n += S[0x100 | ((r >> 16) & 0xff)];
+			n ^= S[0x200 | ((r >> 8) & 0xff)];
+			n += S[0x300 | (r & 0xff)];
+			l ^= n ^ P[++i];
+		}
+		lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
+		lr[off + 1] = l;
+	}
+
+	/**
+	 * Cycically extract a word of key material
+	 * @param data	the string to extract the data from
+	 * @param offp	a "pointer" (as a one-entry array) to the
+	 * current offset into data
+	 * @return	the next word of material from data
+	 */
+	private static int streamtoword(byte data[], int offp[]) {
+		int i;
+		int word = 0;
+		int off = offp[0];
+
+		for (i = 0; i < 4; i++) {
+			word = (word << 8) | (data[off] & 0xff);
+			off = (off + 1) % data.length;
+		}
+
+		offp[0] = off;
+		return word;
+	}
+
+	/**
+	 * Initialise the Blowfish key schedule
+	 */
+	private void init_key() {
+		P = (int[])P_orig.clone();
+		S = (int[])S_orig.clone();
+	}
+
+	/**
+	 * Key the Blowfish cipher
+	 * @param key	an array containing the key
+	 */
+	private void key(byte key[]) {
+		int i;
+		int koffp[] = { 0 };
+		int lr[] = { 0, 0 };
+		int plen = P.length, slen = S.length;
+
+		for (i = 0; i < plen; i++)
+			P[i] = P[i] ^ streamtoword(key, koffp);
+
+		for (i = 0; i < plen; i += 2) {
+			encipher(lr, 0);
+			P[i] = lr[0];
+			P[i + 1] = lr[1];
+		}
+
+		for (i = 0; i < slen; i += 2) {
+			encipher(lr, 0);
+			S[i] = lr[0];
+			S[i + 1] = lr[1];
+		}
+	}
+
+	/**
+	 * Perform the "enhanced key schedule" step described by
+	 * Provos and Mazieres in "A Future-Adaptable Password Scheme"
+	 * http://www.openbsd.org/papers/bcrypt-paper.ps
+	 * @param data	salt information
+	 * @param key	password information
+	 */
+	private void ekskey(byte data[], byte key[]) {
+		int i;
+		int koffp[] = { 0 }, doffp[] = { 0 };
+		int lr[] = { 0, 0 };
+		int plen = P.length, slen = S.length;
+
+		for (i = 0; i < plen; i++)
+			P[i] = P[i] ^ streamtoword(key, koffp);
+
+		for (i = 0; i < plen; i += 2) {
+			lr[0] ^= streamtoword(data, doffp);
+			lr[1] ^= streamtoword(data, doffp);
+			encipher(lr, 0);
+			P[i] = lr[0];
+			P[i + 1] = lr[1];
+		}
+
+		for (i = 0; i < slen; i += 2) {
+			lr[0] ^= streamtoword(data, doffp);
+			lr[1] ^= streamtoword(data, doffp);
+			encipher(lr, 0);
+			S[i] = lr[0];
+			S[i + 1] = lr[1];
+		}
+	}
+
+	/**
+	 * Perform the central password hashing step in the
+	 * bcrypt scheme
+	 * @param password	the password to hash
+	 * @param salt	the binary salt to hash with the password
+	 * @param log_rounds	the binary logarithm of the number
+	 * of rounds of hashing to apply
+	 * @param cdata         the plaintext to encrypt
+	 * @return	an array containing the binary hashed password
+	 */
+	public byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
+	    int cdata[]) {
+		int rounds, i, j;
+		int clen = cdata.length;
+		byte ret[];
+
+		if (log_rounds < 4 || log_rounds > 30)
+			throw new IllegalArgumentException ("Bad number of rounds");
+		rounds = 1 << log_rounds;
+		if (salt.length != BCRYPT_SALT_LEN)
+			throw new IllegalArgumentException ("Bad salt length");
+
+		init_key();
+		ekskey(salt, password);
+		for (i = 0; i != rounds; i++) {
+			key(password);
+			key(salt);
+		}
+
+		for (i = 0; i < 64; i++) {
+			for (j = 0; j < (clen >> 1); j++)
+				encipher(cdata, j << 1);
+		}
+
+		ret = new byte[clen * 4];
+		for (i = 0, j = 0; i < clen; i++) {
+			ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
+			ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
+			ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
+			ret[j++] = (byte)(cdata[i] & 0xff);
+		}
+		return ret;
+	}
+
+	/**
+	 * Hash a password using the OpenBSD bcrypt scheme
+	 * @param password	the password to hash
+	 * @param salt	the salt to hash with (perhaps generated
+	 * using BCrypt.gensalt)
+	 * @return	the hashed password
+	 */
+	public static String hashpw(String password, String salt) {
+		BCrypt B;
+		String real_salt;
+		byte passwordb[], saltb[], hashed[];
+		char minor = (char)0;
+		int rounds, off = 0;
+		StringBuffer rs = new StringBuffer();
+
+		if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
+			throw new IllegalArgumentException ("Invalid salt version");
+		if (salt.charAt(2) == '$')
+			off = 3;
+		else {
+			minor = salt.charAt(2);
+			if (minor != 'a' || salt.charAt(3) != '$')
+				throw new IllegalArgumentException ("Invalid salt revision");
+			off = 4;
+		}
+
+		// Extract number of rounds
+		if (salt.charAt(off + 2) > '$')
+			throw new IllegalArgumentException ("Missing salt rounds");
+		rounds = Integer.parseInt(salt.substring(off, off + 2));
+
+		real_salt = salt.substring(off + 3, off + 25);
+		try {
+			passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
+		} catch (UnsupportedEncodingException uee) {
+			throw new AssertionError("UTF-8 is not supported");
+		}
+
+		saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
+
+		B = new BCrypt();
+		hashed = B.crypt_raw(passwordb, saltb, rounds,
+		    (int[])bf_crypt_ciphertext.clone());
+
+		rs.append("$2");
+		if (minor >= 'a')
+			rs.append(minor);
+		rs.append("$");
+		if (rounds < 10)
+			rs.append("0");
+		if (rounds > 30) {
+			throw new IllegalArgumentException(
+			    "rounds exceeds maximum (30)");
+		}
+		rs.append(Integer.toString(rounds));
+		rs.append("$");
+		rs.append(encode_base64(saltb, saltb.length));
+		rs.append(encode_base64(hashed,
+		    bf_crypt_ciphertext.length * 4 - 1));
+		return rs.toString();
+	}
+
+	/**
+	 * Generate a salt for use with the BCrypt.hashpw() method
+	 * @param log_rounds	the log2 of the number of rounds of
+	 * hashing to apply - the work factor therefore increases as
+	 * 2**log_rounds.
+	 * @param random		an instance of SecureRandom to use
+	 * @return	an encoded salt value
+	 */
+	public static String gensalt(int log_rounds, SecureRandom random) {
+		StringBuffer rs = new StringBuffer();
+		byte rnd[] = new byte[BCRYPT_SALT_LEN];
+
+		random.nextBytes(rnd);
+
+		rs.append("$2a$");
+		if (log_rounds < 10)
+			rs.append("0");
+		if (log_rounds > 30) {
+			throw new IllegalArgumentException(
+			    "log_rounds exceeds maximum (30)");
+		}
+		rs.append(Integer.toString(log_rounds));
+		rs.append("$");
+		rs.append(encode_base64(rnd, rnd.length));
+		return rs.toString();
+	}
+
+	/**
+	 * Generate a salt for use with the BCrypt.hashpw() method
+	 * @param log_rounds	the log2 of the number of rounds of
+	 * hashing to apply - the work factor therefore increases as
+	 * 2**log_rounds.
+	 * @return	an encoded salt value
+	 */
+	public static String gensalt(int log_rounds) {
+		return gensalt(log_rounds, new SecureRandom());
+	}
+
+	/**
+	 * Generate a salt for use with the BCrypt.hashpw() method,
+	 * selecting a reasonable default for the number of hashing
+	 * rounds to apply
+	 * @return	an encoded salt value
+	 */
+	public static String gensalt() {
+		return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
+	}
+
+	/**
+	 * Check that a plaintext password matches a previously hashed
+	 * one
+	 * @param plaintext	the plaintext password to verify
+	 * @param hashed	the previously-hashed password
+	 * @return	true if the passwords match, false otherwise
+	 */
+	public static boolean checkpw(String plaintext, String hashed) {
+		byte hashed_bytes[];
+		byte try_bytes[];
+		try {
+			String try_pw = hashpw(plaintext, hashed);
+			hashed_bytes = hashed.getBytes("UTF-8");
+			try_bytes = try_pw.getBytes("UTF-8");
+		} catch (UnsupportedEncodingException uee) {
+			return false;
+		}
+		if (hashed_bytes.length != try_bytes.length)
+			return false;
+		byte ret = 0;
+		for (int i = 0; i < try_bytes.length; i++)
+			ret |= hashed_bytes[i] ^ try_bytes[i];
+		return ret == 0;
+	}
+}

+ 28 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/Base64Utils.java

@@ -0,0 +1,28 @@
+package com.zhaozhaonews.util.common;
+
+import java.util.Base64;
+
+public class Base64Utils {
+
+    /**
+     * 解码
+     * @param base64
+     * @return
+     */
+    public static byte[] decode(String base64){
+        try {
+            return Base64.getDecoder().decode(base64);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 编码
+     * @param data
+     * @return
+     */
+    public static String encode(byte[] data) {
+        return Base64.getEncoder().encodeToString(data);
+    }
+}

+ 41 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/BurstUtils.java

@@ -0,0 +1,41 @@
+package com.zhaozhaonews.util.common;
+
+/**
+ * 分片桶字段算法
+ */
+public class BurstUtils {
+
+    public final static String SPLIT_CHAR = "-";
+
+    /**
+     * 用-符号链接
+     * @param fileds
+     * @return
+     */
+    public static String encrypt(Object... fileds){
+        StringBuffer sb  = new StringBuffer();
+        if(fileds!=null&&fileds.length>0) {
+            sb.append(fileds[0]);
+            for (int i = 1; i < fileds.length; i++) {
+                sb.append(SPLIT_CHAR).append(fileds[i]);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 默认第一组
+     * @param fileds
+     * @return
+     */
+    public static String groudOne(Object... fileds){
+        StringBuffer sb  = new StringBuffer();
+        if(fileds!=null&&fileds.length>0) {
+            sb.append("0");
+            for (int i = 0; i < fileds.length; i++) {
+                sb.append(SPLIT_CHAR).append(fileds[i]);
+            }
+        }
+        return sb.toString();
+    }
+}

+ 109 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/Compute.java

@@ -0,0 +1,109 @@
+package com.zhaozhaonews.util.common;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+
+public class Compute {
+
+    public static void main(String[] args) {
+        String content = "最近公司由于业务拓展,需要进行小程序相关的开发,本着朝全栈开发者努力,决定学习下Vue,去年csdn送了一本《Vue.js权威指南》";
+        String title = "VueVueVue";
+        double ss = SimilarDegree(content, title);
+        System.out.println(ss);
+    }
+    /*
+     * 计算相似度
+     * */
+    public static double SimilarDegree(String strA, String strB) {
+        String newStrA = removeSign(strA);
+        String newStrB = removeSign(strB);
+        //用较大的字符串长度作为分母,相似子串作为分子计算出字串相似度
+        int temp = Math.max(newStrA.length(), newStrB.length());
+        int temp2 = longestCommonSubstring(newStrA, newStrB).length();
+        return temp2 * 1.0 / temp;
+    }
+
+
+    /*
+     * 将字符串的所有数据依次写成一行
+     * */
+    public static String removeSign(String str) {
+        StringBuffer sb = new StringBuffer();
+        //遍历字符串str,如果是汉字数字或字母,则追加到ab上面
+        for (char item : str.toCharArray()) {
+            if (charReg(item)) {
+                sb.append(item);
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /*
+     * 判断字符是否为汉字,数字和字母,
+     * 因为对符号进行相似度比较没有实际意义,故符号不加入考虑范围。
+     * */
+    public static boolean charReg(char charValue) {
+        return (charValue >= 0x4E00 && charValue <= 0X9FA5) || (charValue >= 'a' && charValue <= 'z')
+                || (charValue >= 'A' && charValue <= 'Z') || (charValue >= '0' && charValue <= '9');
+    }
+
+
+    /*
+     * 求公共子串,采用动态规划算法。
+     * 其不要求所求得的字符在所给的字符串中是连续的。
+     *
+     * */
+    public static String longestCommonSubstring(String strA, String strB) {
+        char[] chars_strA = strA.toCharArray();
+        char[] chars_strB = strB.toCharArray();
+        int m = chars_strA.length;
+        int n = chars_strB.length;
+
+        /*
+         * 初始化矩阵数据,matrix[0][0]的值为0,
+         * 如果字符数组chars_strA和chars_strB的对应位相同,则matrix[i][j]的值为左上角的值加1,
+         * 否则,matrix[i][j]的值等于左上方最近两个位置的较大值,
+         * 矩阵中其余各点的值为0.
+         */
+        int[][] matrix = new int[m + 1][n + 1];
+        for (int i = 1; i <= m; i++) {
+            for (int j = 1; j <= n; j++) {
+                if (chars_strA[i - 1] == chars_strB[j - 1]) {
+                    matrix[i][j] = matrix[i - 1][j - 1] + 1;
+                } else {
+                    matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
+                }
+
+            }
+        }
+        /*
+         * 矩阵中,如果matrix[m][n]的值不等于matrix[m-1][n]的值也不等于matrix[m][n-1]的值,
+         * 则matrix[m][n]对应的字符为相似字符元,并将其存入result数组中。
+         *
+         */
+        char[] result = new char[matrix[m][n]];
+        int currentIndex = result.length - 1;
+        while (matrix[m][n] != 0) {
+            if (matrix[n] == matrix[n - 1]){
+                n--;
+            } else if (matrix[m][n] == matrix[m - 1][n]){
+                m--;
+            }else {
+                result[currentIndex] = chars_strA[m - 1];
+                currentIndex--;
+                n--;
+                m--;
+            }
+        }
+        return new String(result);
+    }
+
+
+    /*
+     * 结果转换成百分比形式
+     * */
+    public static String similarityResult(double resule) {
+        return NumberFormat.getPercentInstance(new Locale("en ", "US ")).format(resule);
+    }
+}

+ 160 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/DESUtils.java

@@ -0,0 +1,160 @@
+package com.zhaozhaonews.util.common;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+
+public class DESUtils {
+
+    public static final String key = "12345678";
+
+    /**
+     * 加密
+     * @param content
+     * @param keyBytes
+     * @return
+     */
+    private static byte[] encrypt(byte[] content, byte[] keyBytes) {
+        try {
+            DESKeySpec keySpec = new DESKeySpec(keyBytes);
+            String algorithm =  "DES";//指定使什么样的算法
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
+            SecretKey key = keyFactory.generateSecret(keySpec);
+            String transformation = "DES/CBC/PKCS5Padding"; //用什么样的转型方式
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(keySpec.getKey()));
+            byte[] result = cipher.doFinal(content);
+            return result;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 解密
+     * @param content
+     * @param keyBytes
+     * @return
+     */
+    private static byte[] decrypt(byte[] content, byte[] keyBytes) {
+        try {
+            DESKeySpec keySpec = new DESKeySpec(keyBytes);
+            String algorithm = "DES";
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm );
+            SecretKey key = keyFactory.generateSecret(keySpec);
+            String transformation = "DES/CBC/PKCS5Padding";
+            Cipher cipher = Cipher.getInstance(transformation );
+            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(keyBytes));
+            byte[] result = cipher.doFinal(content);
+            return result;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    /**
+     * 二进制转16进制
+     * @param bytes
+     * @return
+     */
+    private static String byteToHexString(byte[] bytes) {
+        StringBuffer sb = new StringBuffer();
+        String sTemp;
+        for (int i = 0; i<bytes.length; i++) {
+            sTemp = Integer.toHexString(0xFF & bytes[i]);
+            if (sTemp.length() < 2) {
+                sb.append(0);
+            }
+            sb.append(sTemp.toUpperCase());
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 16进制字符串转bytes
+     * @param hex
+     * @return
+     */
+    public static byte[] hexStringToByte(String hex) {
+        int len = 0;
+        int num=0;
+        //判断字符串的长度是否是两位
+        if(hex.length()>=2){
+            //判断字符喜欢是否是偶数
+            len=(hex.length() / 2);
+            num = (hex.length() % 2);
+            if (num == 1) {
+                hex = "0" + hex;
+                len=len+1;
+            }
+        }else{
+            hex = "0" + hex;
+            len=1;
+        }
+        byte[] result = new byte[len];
+        char[] achar = hex.toCharArray();
+        for (int i = 0; i < len; i++) {
+            int pos = i * 2;
+            result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
+        }
+        return result;
+    }
+    private static int toByte(char c) {
+        if (c >= 'a')
+            return (c - 'a' + 10) & 0x0f;
+        if (c >= 'A')
+            return (c - 'A' + 10) & 0x0f;
+        return (c - '0') & 0x0f;
+    }
+
+    private static byte[] hexToByteArr(String strIn) {
+        byte[] arrB = strIn.getBytes();
+        int iLen = arrB.length;
+        // 两个字符表示一个字节,所以字节数组长度是字符串长度除以2
+        byte[] arrOut = new byte[iLen / 2];
+        for (int i = 0; i < iLen; i = i + 2) {
+            String strTmp = new String(arrB, i, 2);
+            arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
+        }
+        return arrOut;
+    }
+    /**
+     * 加密
+     * @param pass
+     * @return
+     */
+    public static String encode(String pass){
+        return byteToHexString(encrypt(pass.getBytes(), key.getBytes()));
+    }
+
+    /**
+     * 解密
+     * @param passcode
+     * @return
+     */
+    public static String decode(String passcode){
+        return byteToHexString(decrypt(hexToByteArr(passcode), key.getBytes()));
+    }
+
+    public static void main(String[] args) {
+        String content = "password111111111111111";
+
+        System.out.println("加密前 "+ byteToHexString(content.getBytes()));
+        byte[] encrypted = encrypt(content.getBytes(), key.getBytes());
+        System.out.println("加密后:"+ byteToHexString(encrypted));
+
+        byte[] decrypted=decrypt(encrypted, key.getBytes());
+        System.out.println("解密后:"+ byteToHexString(decrypted));
+
+
+        System.out.println(encode(content));
+        String s = new String(hexStringToByte(decode("159CF72C0BD2A8183D536215768C2E91556D77642F214E34")));
+        System.out.println(s);
+    }
+}

+ 470 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/DateUtils.java

@@ -0,0 +1,470 @@
+package com.zhaozhaonews.util.common;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+public class DateUtils {
+    public static String DATE_FORMAT = "yyyy-MM-dd";
+
+    public static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    public static final String DATE_TIME_STAMP_FORMATE = "yyyyMMddHHmmss";
+
+
+    public static String DATE_FORMAT_CHINESE = "yyyy年M月d日";
+
+    public static String DATE_TIME_FORMAT_CHINESE = "yyyy年M月d日 HH:mm:ss";
+
+
+    /**
+     * 获取当前日期
+     *
+     * @return
+     */
+    public static String getCurrentDate() {
+        String datestr = null;
+        SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_FORMAT);
+        datestr = df.format(new Date());
+        return datestr;
+    }
+
+    /**
+     * 获取当前日期时间
+     *
+     * @return
+     */
+    public static String getCurrentDateTime() {
+        String datestr = null;
+        SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_TIME_FORMAT);
+        datestr = df.format(new Date());
+        return datestr;
+    }
+
+    /**
+     * 获取当前日期时间
+     *
+     * @return
+     */
+    public static String getCurrentDateTime(String Dateformat) {
+        String datestr = null;
+        SimpleDateFormat df = new SimpleDateFormat(Dateformat);
+        datestr = df.format(new Date());
+        return datestr;
+    }
+
+    public static String dateToDateTime(Date date) {
+        String datestr = null;
+        SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_TIME_FORMAT);
+        datestr = df.format(date);
+        return datestr;
+    }
+
+    /**
+     * 将字符串日期转换为日期格式
+     *
+     * @param datestr
+     * @return
+     */
+    public static Date stringToDate(String datestr) {
+
+        if (datestr == null || datestr.equals("")) {
+            return null;
+        }
+        Date date = new Date();
+        SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_FORMAT);
+        try {
+            date = df.parse(datestr);
+        } catch (ParseException e) {
+            date = DateUtils.stringToDate(datestr, "yyyyMMdd");
+        }
+        return date;
+    }
+
+    /**
+     * 将字符串日期转换为日期格式
+     * 自定義格式
+     *
+     * @param datestr
+     * @return
+     */
+    public static Date stringToDate(String datestr, String dateformat) {
+        Date date = new Date();
+        SimpleDateFormat df = new SimpleDateFormat(dateformat);
+        try {
+            date = df.parse(datestr);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return date;
+    }
+
+
+    /**
+     * 将日期格式日期转换为字符串格式
+     *
+     * @param date
+     * @return
+     */
+    public static String dateToString(Date date) {
+        String datestr = null;
+        SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_FORMAT);
+        datestr = df.format(date);
+        return datestr;
+    }
+
+    /**
+     * 将日期格式日期转换为字符串格式 自定義格式
+     *
+     * @param date
+     * @param dateformat
+     * @return
+     */
+    public static String dateToString(Date date, String dateformat) {
+        String datestr = null;
+        SimpleDateFormat df = new SimpleDateFormat(dateformat);
+        datestr = df.format(date);
+        return datestr;
+    }
+
+    /**
+     * 获取日期的DAY值
+     *
+     * @param date 输入日期
+     * @return
+     */
+    public static int getDayOfDate(Date date) {
+        int d = 0;
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        d = cd.get(Calendar.DAY_OF_MONTH);
+        return d;
+    }
+
+    /**
+     * 获取日期的MONTH值
+     *
+     * @param date 输入日期
+     * @return
+     */
+    public static int getMonthOfDate(Date date) {
+        int m = 0;
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        m = cd.get(Calendar.MONTH) + 1;
+        return m;
+    }
+
+    /**
+     * 获取日期的YEAR值
+     *
+     * @param date 输入日期
+     * @return
+     */
+    public static int getYearOfDate(Date date) {
+        int y = 0;
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        y = cd.get(Calendar.YEAR);
+        return y;
+    }
+
+    /**
+     * 获取星期几
+     *
+     * @param date 输入日期
+     * @return
+     */
+    public static int getWeekOfDate(Date date) {
+        int wd = 0;
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        wd = cd.get(Calendar.DAY_OF_WEEK) - 1;
+        return wd;
+    }
+
+    /**
+     * 获取输入日期的当月第一天
+     *
+     * @param date 输入日期
+     * @return
+     */
+    public static Date getFirstDayOfMonth(Date date) {
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        cd.set(Calendar.DAY_OF_MONTH, 1);
+        return cd.getTime();
+    }
+
+    /**
+     * 获得输入日期的当月最后一天
+     *
+     * @param date
+     */
+    public static Date getLastDayOfMonth(Date date) {
+        return DateUtils.addDay(DateUtils.getFirstDayOfMonth(DateUtils.addMonth(date, 1)), -1);
+    }
+
+    /**
+     * 判断是否是闰年
+     *
+     * @param date 输入日期
+     * @return 是true 否false
+     */
+    public static boolean isLeapYEAR(Date date) {
+
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        int year = cd.get(Calendar.YEAR);
+
+        if (year % 4 == 0 && year % 100 != 0 | year % 400 == 0) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 根据整型数表示的年月日,生成日期类型格式
+     *
+     * @param year  年
+     * @param month 月
+     * @param day   日
+     * @return
+     */
+    public static Date getDateByYMD(int year, int month, int day) {
+        Calendar cd = Calendar.getInstance();
+        cd.set(year, month - 1, day);
+        return cd.getTime();
+    }
+
+    /**
+     * 获取年周期对应日
+     *
+     * @param date  输入日期
+     * @param iyear 年数  負數表示之前
+     * @return
+     */
+    public static Date getYearCycleOfDate(Date date, int iyear) {
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+
+        cd.add(Calendar.YEAR, iyear);
+
+        return cd.getTime();
+    }
+
+    /**
+     * 获取月周期对应日
+     *
+     * @param date 输入日期
+     * @param i
+     * @return
+     */
+    public static Date getMonthCycleOfDate(Date date, int i) {
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+
+        cd.add(Calendar.MONTH, i);
+
+        return cd.getTime();
+    }
+
+    /**
+     * 计算 fromDate 到 toDate 相差多少年
+     *
+     * @param fromDate
+     * @param toDate
+     * @return 年数
+     */
+    public static int getYearByMinusDate(Date fromDate, Date toDate) {
+        Calendar df = Calendar.getInstance();
+        df.setTime(fromDate);
+
+        Calendar dt = Calendar.getInstance();
+        dt.setTime(toDate);
+
+        return dt.get(Calendar.YEAR) - df.get(Calendar.YEAR);
+    }
+
+    /**
+     * 计算 fromDate 到 toDate 相差多少个月
+     *
+     * @param fromDate
+     * @param toDate
+     * @return 月数
+     */
+    public static int getMonthByMinusDate(Date fromDate, Date toDate) {
+        Calendar df = Calendar.getInstance();
+        df.setTime(fromDate);
+
+        Calendar dt = Calendar.getInstance();
+        dt.setTime(toDate);
+
+        return dt.get(Calendar.YEAR) * 12 + dt.get(Calendar.MONTH) -
+                (df.get(Calendar.YEAR) * 12 + df.get(Calendar.MONTH));
+    }
+
+    /**
+     * 计算 fromDate 到 toDate 相差多少天
+     *
+     * @param fromDate
+     * @param toDate
+     * @return 天数
+     */
+    public static long getDayByMinusDate(Object fromDate, Object toDate) {
+
+        Date f = DateUtils.chgObject(fromDate);
+
+        Date t = DateUtils.chgObject(toDate);
+
+        long fd = f.getTime();
+        long td = t.getTime();
+
+        return (td - fd) / (24L * 60L * 60L * 1000L);
+    }
+
+    /**
+     * 计算年龄
+     *
+     * @param birthday 生日日期
+     * @param calcDate 要计算的日期点
+     * @return
+     */
+    public static int calcAge(Date birthday, Date calcDate) {
+
+        int cYear = DateUtils.getYearOfDate(calcDate);
+        int cMonth = DateUtils.getMonthOfDate(calcDate);
+        int cDay = DateUtils.getDayOfDate(calcDate);
+        int bYear = DateUtils.getYearOfDate(birthday);
+        int bMonth = DateUtils.getMonthOfDate(birthday);
+        int bDay = DateUtils.getDayOfDate(birthday);
+
+        if (cMonth > bMonth || (cMonth == bMonth && cDay > bDay)) {
+            return cYear - bYear;
+        } else {
+            return cYear - 1 - bYear;
+        }
+    }
+
+    /**
+     * 从身份证中获取出生日期
+     *
+     * @param idno 身份证号码
+     * @return
+     */
+    public static String getBirthDayFromIDCard(String idno) {
+        Calendar cd = Calendar.getInstance();
+        if (idno.length() == 15) {
+            cd.set(Calendar.YEAR, Integer.valueOf("19" + idno.substring(6, 8))
+                    .intValue());
+            cd.set(Calendar.MONTH, Integer.valueOf(idno.substring(8, 10))
+                    .intValue() - 1);
+            cd.set(Calendar.DAY_OF_MONTH,
+                    Integer.valueOf(idno.substring(10, 12)).intValue());
+        } else if (idno.length() == 18) {
+            cd.set(Calendar.YEAR, Integer.valueOf(idno.substring(6, 10))
+                    .intValue());
+            cd.set(Calendar.MONTH, Integer.valueOf(idno.substring(10, 12))
+                    .intValue() - 1);
+            cd.set(Calendar.DAY_OF_MONTH,
+                    Integer.valueOf(idno.substring(12, 14)).intValue());
+        }
+        return DateUtils.dateToString(cd.getTime());
+    }
+
+    /**
+     * 在输入日期上增加(+)或减去(-)天数
+     *
+     * @param date   输入日期
+     * @param iday 要增加或减少的天数
+     */
+    public static Date addDay(Date date, int iday) {
+        Calendar cd = Calendar.getInstance();
+
+        cd.setTime(date);
+
+        cd.add(Calendar.DAY_OF_MONTH, iday);
+
+        return cd.getTime();
+    }
+
+    /**
+     * 在输入日期上增加(+)或减去(-)月份
+     *
+     * @param date   输入日期
+     * @param imonth 要增加或减少的月分数
+     */
+    public static Date addMonth(Date date, int imonth) {
+        Calendar cd = Calendar.getInstance();
+
+        cd.setTime(date);
+
+        cd.add(Calendar.MONTH, imonth);
+
+        return cd.getTime();
+    }
+
+    /**
+     * 在输入日期上增加(+)或减去(-)年份
+     *
+     * @param date   输入日期
+     * @param iyear 要增加或减少的年数
+     */
+    public static Date addYear(Date date, int iyear) {
+        Calendar cd = Calendar.getInstance();
+
+        cd.setTime(date);
+
+        cd.add(Calendar.YEAR, iyear);
+
+        return cd.getTime();
+    }
+
+    /**
+     * 將OBJECT類型轉換為Date
+     *
+     * @param date
+     * @return
+     */
+    public static Date chgObject(Object date) {
+
+        if (date != null && date instanceof Date) {
+            return (Date) date;
+        }
+
+        if (date != null && date instanceof String) {
+            return DateUtils.stringToDate((String) date);
+        }
+
+        return null;
+
+    }
+
+    public static long getAgeByBirthday(String date) {
+
+        Date birthday = stringToDate(date, "yyyy-MM-dd");
+        long sec = new Date().getTime() - birthday.getTime();
+
+        long age = sec / (1000 * 60 * 60 * 24) / 365;
+
+        return age;
+    }
+
+
+    /**
+     * @param args
+     */
+    public static void main(String[] args) {
+        //String temp = DateUtil.dateToString(getLastDayOfMonth(new Date()),
+        ///   DateUtil.DATE_FORMAT_CHINESE);
+        //String s=DateUtil.dateToString(DateUtil.addDay(DateUtil.addYear(new Date(),1),-1));
+
+
+        long s = DateUtils.getDayByMinusDate("2012-01-01", "2012-12-31");
+        System.err.println(s);
+
+
+    }
+}

+ 22 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/FileUtils.java

@@ -0,0 +1,22 @@
+package com.zhaozhaonews.util.common;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class FileUtils {
+
+    /**
+     * 从资源流中读取第一行内容
+     * @param in
+     * @return
+     * @throws IOException
+     */
+    public static String readFristLineFormResource(InputStream in) throws IOException{
+        BufferedReader br=new BufferedReader(new InputStreamReader(in));
+        return br.readLine();
+    }
+
+
+}

+ 84 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/IdsUtils.java

@@ -0,0 +1,84 @@
+package com.zhaozhaonews.util.common;
+
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * 数字ID混淆器,用于前后端数据通信时候的处理
+ */
+public class IdsUtils {
+
+    private static final String KEY_AES = "AES";
+    private static final String KEY_SECART = "12345678901234561234567890123456";
+
+    public static String encryptNumber(Long number) throws Exception{
+        String src = String.format("%d%013d",0,number);
+        return encrypt(src);
+    }
+
+    public static Long decryptLong(String src) throws Exception{
+        String val =decrypt(src);
+        return Long.valueOf(val);
+    }
+
+    public static Integer decryptInt(String src) throws Exception{
+        String val =decrypt(src);
+        return Integer.valueOf(val);
+    }
+
+    private static String encrypt(String src) throws Exception {
+        byte[] raw = KEY_SECART.getBytes();
+        SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_AES);
+        Cipher cipher = Cipher.getInstance(KEY_AES);
+        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
+        byte[] encrypted = cipher.doFinal(src.getBytes());
+        return byte2hex(encrypted);
+    }
+
+    private static String decrypt(String src) throws Exception {
+        byte[] raw = KEY_SECART.getBytes();
+        SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_AES);
+        Cipher cipher = Cipher.getInstance(KEY_AES);
+        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
+        byte[] encrypted1 = hex2byte(src);
+        byte[] original = cipher.doFinal(encrypted1);
+        String originalString = new String(original);
+        return originalString;
+    }
+
+    private 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;
+    }
+
+    private static String byte2hex(byte[] b) {
+        String hs = "";
+        String stmp = "";
+        for (int n = 0; n < b.length; n++) {
+            stmp = (Integer.toHexString(b[n] & 0XFF));
+            if (stmp.length() == 1) {
+                hs = hs + "0" + stmp;
+            } else {
+                hs = hs + stmp;
+            }
+        }
+        return hs.toUpperCase();
+    }
+
+    public static void main(String[] args) throws Exception{
+        System.out.println("========:"+encryptNumber(2l));
+    }
+
+}

+ 54 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/MD5Utils.java

@@ -0,0 +1,54 @@
+package com.zhaozhaonews.util.common;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class MD5Utils {
+
+    /**
+     * MD5加密
+     * @param str
+     * @return
+     */
+    public final static String encode(String str) {
+        try {
+            //创建具有指定算法名称的摘要
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            //使用指定的字节数组更新摘要
+            md.update(str.getBytes());
+            //进行哈希计算并返回一个字节数组
+            byte mdBytes[] = md.digest();
+            String hash = "";
+            //循环字节数组
+            for (int i = 0; i < mdBytes.length; i++) {
+                int temp;
+                //如果有小于0的字节,则转换为正数
+                if (mdBytes[i] < 0)
+                    temp = 256 + mdBytes[i];
+                else
+                    temp = mdBytes[i];
+                if (temp < 16)
+                    hash += "0";
+                //将字节转换为16进制后,转换为字符串
+                hash += Integer.toString(temp, 16);
+            }
+            return hash;
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    public static String encodeWithSalt(String numStr, String salt) {
+        return encode(encode(numStr) + salt);
+    }
+
+    public static String generateSalt() {
+        return String.valueOf((int) ((Math.random() * 899999) + 100000));
+    }
+
+    public static void main(String[] args) {
+        System.out.println(encode("test"));//e10adc3949ba59abbe56e057f20f883e
+        System.out.println(encodeWithSalt("123456","123456"));//5f1d7a84db00d2fce00b31a7fc73224f
+    }
+}

+ 351 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/ReflectUtils.java

@@ -0,0 +1,351 @@
+package com.zhaozhaonews.util.common;
+
+import org.apache.commons.beanutils.ConvertUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class ReflectUtils {
+
+
+    /**
+     * 转换为Map
+     *
+     * @param bean
+     * @return
+     */
+    public static Map<String, Object> beanToMap(Object bean) {
+        PropertyDescriptor[] propertyDescriptorArray = getPropertyDescriptorArray(bean);
+        Map<String, Object> parameterMap = new HashMap<String, Object>();
+        for (PropertyDescriptor propertyDescriptor : propertyDescriptorArray) {
+            Object value = getPropertyDescriptorValue(bean, propertyDescriptor);
+            parameterMap.put(propertyDescriptor.getName(), value);
+        }
+        return parameterMap;
+    }
+
+    /**
+     * 通过反射设置属性
+     *
+     * @param bean
+     * @param key
+     * @param value
+     */
+    public static void setPropertie(Object bean, String key, Object value) {
+        if (null != bean && StringUtils.isNotEmpty(key)) {
+            PropertyDescriptor[] descriptor = getPropertyDescriptorArray(bean);
+            PropertyDescriptor propertyDescriptor = getPropertyDescriptor(descriptor, key);
+            setPropertyDescriptorValue(bean, propertyDescriptor, value);
+        }
+    }
+
+    /**
+     * 通过反射设置属性
+     *
+     * @param bean
+     * @param key
+     * @param value
+     * @param skipExist 是否跳过已存在的属性
+     */
+    public static void setPropertie(Object bean, String key, Object value, boolean skipExist) {
+        if (null != bean && StringUtils.isNotEmpty(key)) {
+            if (skipExist) {
+                Object propValue = getPropertie(bean, key);
+                if (null == propValue) {
+                    setPropertie(bean, key, value);
+                }
+            } else {
+                setPropertie(bean, key, value);
+            }
+        }
+    }
+
+
+    /**
+     * 通过反射将map的key value 映射到实体类中
+     *
+     * @param bean
+     * @param skipExist 是否跳过已存在的属性
+     */
+    public static void setPropertie(Object bean, Map<String, Object> parameterMap, boolean skipExist) {
+        if (null != bean && null != parameterMap && !parameterMap.isEmpty()) {
+            for (Map.Entry<String, Object> entry : parameterMap.entrySet()) {
+                setPropertie(bean, entry.getKey(), entry.getValue());
+            }
+        }
+
+    }
+
+
+    /**
+     * 获取属性的值
+     *
+     * @param bean
+     * @param key
+     * @return
+     */
+    public static Object getPropertie(Object bean, String key) {
+        Object value = null;
+        if (null != bean && StringUtils.isNotEmpty(key)) {
+            PropertyDescriptor[] descriptor = getPropertyDescriptorArray(bean);
+            PropertyDescriptor propertyDescriptor = getPropertyDescriptor(descriptor, key);
+            value = getPropertyDescriptorValue(bean, propertyDescriptor);
+        }
+        return value;
+    }
+
+
+    public static Object getPropertyDescriptorValue(Object bean, PropertyDescriptor propertyDescriptor) {
+        Object value = null;
+        if (null != propertyDescriptor) {
+            Method readMethod = propertyDescriptor.getReadMethod();
+            value = invok(readMethod, bean, propertyDescriptor.getPropertyType(), null);
+        }
+        return value;
+    }
+
+
+    public static void setPropertyDescriptorValue(Object bean, PropertyDescriptor propertyDescriptor, Object value) {
+        if (null != propertyDescriptor) {
+            Method writeMethod = propertyDescriptor.getWriteMethod();
+            invok(writeMethod, bean, propertyDescriptor.getPropertyType(), value);
+        }
+    }
+
+    /**
+     * 获取 PropertyDescriptor 属性
+     *
+     * @param propertyDescriptorArray
+     * @param key
+     * @return
+     */
+    public static PropertyDescriptor getPropertyDescriptor(PropertyDescriptor[] propertyDescriptorArray, String key) {
+        PropertyDescriptor propertyDescriptor = null;
+        for (PropertyDescriptor descriptor : propertyDescriptorArray) {
+            String fieldName = descriptor.getName();
+            if (fieldName.equals(key)) {
+                propertyDescriptor = descriptor;
+                break;
+            }
+        }
+        return propertyDescriptor;
+    }
+
+
+    /**
+     * 获取 PropertyDescriptor 属性
+     *
+     * @param bean
+     * @param key
+     * @return
+     */
+    public static PropertyDescriptor getPropertyDescriptor(Object bean, String key) {
+        PropertyDescriptor[] propertyDescriptorArray = getPropertyDescriptorArray(bean);
+        return getPropertyDescriptor(propertyDescriptorArray, key);
+    }
+
+
+    /**
+     * invok 调用方法
+     *
+     * @param methodName
+     * @param bean
+     * @param targetType
+     * @param value
+     * @return
+     */
+    public static Object invok(String methodName, Object bean, Class<?> targetType, Object value) {
+        Object resultValue = null;
+        if (StringUtils.isNotEmpty(methodName) && null != bean) {
+            Method method = getMethod(bean.getClass(), methodName);
+            if (null != method) {
+                resultValue = invok(method, bean, targetType, value);
+            }
+        }
+        return resultValue;
+    }
+
+    /**
+     * 调用 invok 方法
+     *
+     * @param method
+     * @param bean
+     * @param value
+     */
+    public static Object invok(Method method, Object bean, Class<?> targetType, Object value) {
+        //  System.out.println("method:" + method.getName() + "   bean:" + bean.getClass().getName() + "     " + value);
+        Object resultValue = null;
+        if (null != method && null != bean) {
+            try {
+                int count = method.getParameterCount();
+                if (count >= 1) {
+                    if (null != value) {
+                        value = ConvertUtils.convert(value, targetType);
+                    }
+                    resultValue = method.invoke(bean, value);
+                } else {
+                    resultValue = method.invoke(bean);
+                }
+
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+        }
+        return resultValue;
+    }
+
+    /**
+     * 获取内省的属性
+     *
+     * @param bean
+     * @return
+     */
+    public static PropertyDescriptor[] getPropertyDescriptorArray(Object bean) {
+        BeanInfo beanInfo = null;
+        PropertyDescriptor[] propertyDescriptors = null;
+        try {
+            beanInfo = Introspector.getBeanInfo(bean.getClass());
+        } catch (IntrospectionException e) {
+            e.printStackTrace();
+        }
+        if (null != beanInfo) {
+            propertyDescriptors = beanInfo.getPropertyDescriptors();
+        }
+        return propertyDescriptors;
+    }
+
+    /**
+     * 获取method 方法
+     *
+     * @param clazz
+     * @param methodName
+     * @return
+     */
+    private static Method getMethod(Class clazz, String methodName) {
+        Method method = null;
+        if (null != clazz) {
+            try {
+                method = clazz.getDeclaredMethod(methodName);
+            } catch (NoSuchMethodException e) {
+                e.printStackTrace();
+            }
+        }
+        return method;
+    }
+
+    private static Object getBean(Class clazz) {
+        Object bean = null;
+        if (null != clazz) {
+            try {
+                bean = clazz.newInstance();
+            } catch (InstantiationException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        return bean;
+    }
+
+    /**
+     * 同步 bean 中的数据
+     *
+     * @param oldBean
+     * @param newBean
+     * @param <T>
+     */
+    public static <T> void syncBeanData(T oldBean, T newBean) {
+        PropertyDescriptor[] descriptorArray = getPropertyDescriptorArray(newBean);
+        for (PropertyDescriptor propertyDescriptor : descriptorArray) {
+            Object newValue = getPropertyDescriptorValue(newBean, propertyDescriptor);
+            Object oldValue = getPropertyDescriptorValue(oldBean, propertyDescriptor);
+            if (null == newValue && oldValue != null) {
+                setPropertyDescriptorValue(newBean, propertyDescriptor, oldValue);
+            }
+        }
+    }
+
+
+    /**
+     * 通过反射获取class字节码文件
+     *
+     * @param className
+     * @return
+     */
+    public static Class getClassForName(String className) {
+        Class clazz = null;
+        if (StringUtils.isNotEmpty(className)) {
+            try {
+                clazz = Class.forName(className);
+            } catch (ClassNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        return clazz;
+    }
+
+
+    /**
+     * 通过反射获取对象
+     *
+     * @param className
+     * @return
+     */
+    public static Object getClassForBean(String className) {
+        Object bean = null;
+        Class clazz = getClassForName(className);
+        if (null != clazz) {
+            try {
+                bean = clazz.newInstance();
+            } catch (InstantiationException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        return bean;
+    }
+
+    /**
+     * 获取属性字段的注解属性
+     *
+     * @param bean
+     * @param propertyDescriptor
+     * @return
+     */
+    public static Annotation[] getFieldAnnotations(Object bean, PropertyDescriptor propertyDescriptor) {
+        List<Field> fieldList = Arrays.asList(bean.getClass().getDeclaredFields()).stream().filter(f -> f.getName().equals(propertyDescriptor.getName())).collect(Collectors.toList());
+        if (null != fieldList && fieldList.size() > 0) {
+            return fieldList.get(0).getDeclaredAnnotations();
+        }
+        return null;
+    }
+
+    /**
+     * 获取属性字段的注解属性
+     *
+     * @param bean
+     * @param key
+     * @return
+     */
+    public static Annotation[] getFieldAnnotations(Object bean, String key) {
+        PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean, key);
+        return getFieldAnnotations(bean, propertyDescriptor);
+    }
+
+
+}

+ 123 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/SensitiveWordUtil.java

@@ -0,0 +1,123 @@
+package com.zhaozhaonews.util.common;
+
+
+import java.util.*;
+
+public class SensitiveWordUtil {
+
+    public static Map<String, Object> dictionaryMap = new HashMap<>();
+
+
+    /**
+     * 生成关键词字典库
+     * @param words
+     * @return
+     */
+    public static void initMap(Collection<String> words) {
+        if (words == null) {
+            System.out.println("敏感词列表不能为空");
+            return ;
+        }
+
+        // map初始长度words.size(),整个字典库的入口字数(小于words.size(),因为不同的词可能会有相同的首字)
+        Map<String, Object> map = new HashMap<>(words.size());
+        // 遍历过程中当前层次的数据
+        Map<String, Object> curMap = null;
+        Iterator<String> iterator = words.iterator();
+
+        while (iterator.hasNext()) {
+            String word = iterator.next();
+            curMap = map;
+            int len = word.length();
+            for (int i =0; i < len; i++) {
+                // 遍历每个词的字
+                String key = String.valueOf(word.charAt(i));
+                // 当前字在当前层是否存在, 不存在则新建, 当前层数据指向下一个节点, 继续判断是否存在数据
+                Map<String, Object> wordMap = (Map<String, Object>) curMap.get(key);
+                if (wordMap == null) {
+                    // 每个节点存在两个数据: 下一个节点和isEnd(是否结束标志)
+                    wordMap = new HashMap<>(2);
+                    wordMap.put("isEnd", "0");
+                    curMap.put(key, wordMap);
+                }
+                curMap = wordMap;
+                // 如果当前字是词的最后一个字,则将isEnd标志置1
+                if (i == len -1) {
+                    curMap.put("isEnd", "1");
+                }
+            }
+        }
+
+        dictionaryMap = map;
+    }
+
+    /**
+     * 搜索文本中某个文字是否匹配关键词
+     * @param text
+     * @param beginIndex
+     * @return
+     */
+    private static int checkWord(String text, int beginIndex) {
+        if (dictionaryMap == null) {
+            throw new RuntimeException("字典不能为空");
+        }
+        boolean isEnd = false;
+        int wordLength = 0;
+        Map<String, Object> curMap = dictionaryMap;
+        int len = text.length();
+        // 从文本的第beginIndex开始匹配
+        for (int i = beginIndex; i < len; i++) {
+            String key = String.valueOf(text.charAt(i));
+            // 获取当前key的下一个节点
+            curMap = (Map<String, Object>) curMap.get(key);
+            if (curMap == null) {
+                break;
+            } else {
+                wordLength ++;
+                if ("1".equals(curMap.get("isEnd"))) {
+                    isEnd = true;
+                }
+            }
+        }
+        if (!isEnd) {
+            wordLength = 0;
+        }
+        return wordLength;
+    }
+
+    /**
+     * 获取匹配的关键词和命中次数
+     * @param text
+     * @return
+     */
+    public static Map<String, Integer> matchWords(String text) {
+        Map<String, Integer> wordMap = new HashMap<>();
+        int len = text.length();
+        for (int i = 0; i < len; i++) {
+            int wordLength = checkWord(text, i);
+            if (wordLength > 0) {
+                String word = text.substring(i, i + wordLength);
+                // 添加关键词匹配次数
+                if (wordMap.containsKey(word)) {
+                    wordMap.put(word, wordMap.get(word) + 1);
+                } else {
+                    wordMap.put(word, 1);
+                }
+
+                i += wordLength - 1;
+            }
+        }
+        return wordMap;
+    }
+
+    public static void main(String[] args) {
+        List<String> list = new ArrayList<>();
+        list.add("法轮");
+        list.add("法轮功");
+        list.add("冰毒");
+        initMap(list);
+        String content="我是一个好人,并不会卖冰毒,也不操练法轮功,我真的不卖冰毒";
+        Map<String, Integer> map = matchWords(content);
+        System.out.println(map);
+    }
+}

+ 177 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/SimHashUtils.java

@@ -0,0 +1,177 @@
+package com.zhaozhaonews.util.common;
+
+import com.hankcs.hanlp.seg.common.Term;
+import com.hankcs.hanlp.tokenizer.StandardTokenizer;
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.safety.Whitelist;
+
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("all")
+public class SimHashUtils {
+    /**
+     * 清除html标签
+     * @param content
+     * @return
+     */
+    private static String cleanResume(String content) {
+        // 若输入为HTML,下面会过滤掉所有的HTML的tag
+        content = Jsoup.clean(content, Whitelist.none());
+        content = StringUtils.lowerCase(content);
+        String[] strings = {" ", "\n", "\r", "\t", "\\r", "\\n", "\\t", "&nbsp;"};
+        for (String s : strings) {
+            content = content.replaceAll(s, "");
+        }
+        return content;
+    }
+
+
+    /**
+     * 这个是对整个字符串进行hash计算
+     * @return
+     */
+    private static BigInteger simHash(String token,int hashbits) {
+
+        token = cleanResume(token); // cleanResume 删除一些特殊字符
+
+        int[] v = new int[hashbits];
+
+        List<Term> termList = StandardTokenizer.segment(token); // 对字符串进行分词
+
+        //对分词的一些特殊处理 : 比如: 根据词性添加权重 , 过滤掉标点符号 , 过滤超频词汇等;
+        Map<String, Integer> weightOfNature = new HashMap<String, Integer>(); // 词性的权重
+        weightOfNature.put("n", 2); //给名词的权重是2;
+        Map<String, String> stopNatures = new HashMap<String, String>();//停用的词性 如一些标点符号之类的;
+        stopNatures.put("w", ""); //
+        int overCount = 5; //设定超频词汇的界限 ;
+        Map<String, Integer> wordCount = new HashMap<String, Integer>();
+
+        for (Term term : termList) {
+            String word = term.word; //分词字符串
+
+            String nature = term.nature.toString(); // 分词属性;
+            //  过滤超频词
+            if (wordCount.containsKey(word)) {
+                int count = wordCount.get(word);
+                if (count > overCount) {
+                    continue;
+                }
+                wordCount.put(word, count + 1);
+            } else {
+                wordCount.put(word, 1);
+            }
+
+            // 过滤停用词性
+            if (stopNatures.containsKey(nature)) {
+                continue;
+            }
+
+            // 2、将每一个分词hash为一组固定长度的数列.比如 64bit 的一个整数.
+            BigInteger t = hash(word,hashbits);
+            for (int i = 0; i < hashbits; i++) {
+                BigInteger bitmask = new BigInteger("1").shiftLeft(i);
+                // 3、建立一个长度为64的整数数组(假设要生成64位的数字指纹,也可以是其它数字),
+                // 对每一个分词hash后的数列进行判断,如果是1000...1,那么数组的第一位和末尾一位加1,
+                // 中间的62位减一,也就是说,逢1加1,逢0减1.一直到把所有的分词hash数列全部判断完毕.
+                int weight = 1;  //添加权重
+                if (weightOfNature.containsKey(nature)) {
+                    weight = weightOfNature.get(nature);
+                }
+                if (t.and(bitmask).signum() != 0) {
+                    // 这里是计算整个文档的所有特征的向量和
+                    v[i] += weight;
+                } else {
+                    v[i] -= weight;
+                }
+            }
+        }
+        BigInteger fingerprint = new BigInteger("0");
+        for (int i = 0; i < hashbits; i++) {
+            if (v[i] >= 0) {
+                fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i));
+            }
+        }
+        return fingerprint;
+    }
+
+
+    /**
+     * 对单个的分词进行hash计算;
+     * @param source
+     * @return
+     */
+    private static BigInteger hash(String source,int hashbits) {
+        if (source == null || source.length() == 0) {
+            return new BigInteger("0");
+        } else {
+            /**
+             * 当sourece 的长度过短,会导致hash算法失效,因此需要对过短的词补偿
+             */
+            while (source.length() < 3) {
+                source = source + source.charAt(0);
+            }
+            char[] sourceArray = source.toCharArray();
+            BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7);
+            BigInteger m = new BigInteger("1000003");
+            BigInteger mask = new BigInteger("2").pow(hashbits).subtract(new BigInteger("1"));
+            for (char item : sourceArray) {
+                BigInteger temp = BigInteger.valueOf((long) item);
+                x = x.multiply(m).xor(temp).and(mask);
+            }
+            x = x.xor(new BigInteger(String.valueOf(source.length())));
+            if (x.equals(new BigInteger("-1"))) {
+                x = new BigInteger("-2");
+            }
+            return x;
+        }
+    }
+
+    /**
+     * 计算海明距离,海明距离越小说明越相似;
+     * @param other
+     * @return
+     */
+    private static int hammingDistance(String token1,String token2,int hashbits) {
+        BigInteger m = new BigInteger("3").shiftLeft(hashbits).subtract(
+                new BigInteger("3"));
+        BigInteger x = simHash(token1,hashbits).xor(simHash(token2,hashbits)).and(m);
+        int tot = 0;
+        while (x.signum() != 0) {
+            tot += 1;
+            x = x.and(x.subtract(new BigInteger("3")));
+        }
+        return tot;
+    }
+
+
+    public static double getSemblance(String token1,String token2){
+        double i = (double) hammingDistance(token1,token2, 64);
+        return 1 - i/64 ;
+    }
+
+    public static void main(String[] args) {
+
+        String s1 = "....";
+        String s2 = "最近公司由于业务拓展,需要进行小程序相关的开发,本着朝全栈开发者努力,决定学习下Vue,去年csdn送了一本《Vue.js权威指南》,那就从这本书开始练起来吧。哟吼。一,环境搭建\n" +
+                "今天主要说一下如何搭建环境,以及如何运行。1,npm安装\n" +
+                "brew install npm\n" +
+                "1\n" +
+                "如果brew没有安装的话,大家可以brew如何安装哦,这里就不再详细说明了。本来是有一个Vue的图标的,被我给去掉了,方便后面的调试。\n" +
+                "\n" +
+                "三,Vue.js 权威指南的第一个demo\n" +
+                "一切准备就绪,接下来我们开始练习《Vue.js权威指南》这本书中的demo,在网上找了许久,也没有找到书中的源码,很是遗憾啊。第一个demo的代码保存为jk.vue \n" +
+                "我这边将第一个demo的代码如下:\n" +
+                "--------------------- \n" +
+                "作者:JackLee18 \n" +
+                "来源:CSDN \n" +
+                "原文:https://blog.csdn.net/hanhailong18/article/details/81509952 \n" +
+                "版权声明:本文为博主原创文章,转载请附上博文链接!";
+
+        double semblance = getSemblance(s1, s2);
+        System.out.println(semblance);
+    }
+}

+ 145 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/SnowflakeIdWorker.java

@@ -0,0 +1,145 @@
+package com.zhaozhaonews.util.common;
+
+/**
+ * Twitter_Snowflake<br>
+ * SnowFlake的结构如下(每部分用-分开):<br>
+ * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
+ * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
+ * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
+ * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
+ * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
+ * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
+ * 加起来刚好64位,为一个Long型。<br>
+ * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
+ */
+public class SnowflakeIdWorker {
+
+    // ==============================Fields===========================================
+    /** 开始时间截 (2015-01-01) */
+    private final long twepoch = 1420041600000L;
+
+    /** 机器id所占的位数 */
+    private final long workerIdBits = 5L;
+
+    /** 数据标识id所占的位数 */
+    private final long datacenterIdBits = 5L;
+
+    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
+    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
+
+    /** 支持的最大数据标识id,结果是31 */
+    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
+
+    /** 序列在id中占的位数 */
+    private final long sequenceBits = 12L;
+
+    /** 机器ID向左移12位 */
+    private final long workerIdShift = sequenceBits;
+
+    /** 数据标识id向左移17位(12+5) */
+    private final long datacenterIdShift = sequenceBits + workerIdBits;
+
+    /** 时间截向左移22位(5+5+12) */
+    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+
+    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
+    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
+
+    /** 工作机器ID(0~31) */
+    private long workerId;
+
+    /** 数据中心ID(0~31) */
+    private long datacenterId;
+
+    /** 毫秒内序列(0~4095) */
+    private long sequence = 0L;
+
+    /** 上次生成ID的时间截 */
+    private long lastTimestamp = -1L;
+
+    //==============================Constructors=====================================
+    /**
+     * 构造函数
+     * @param workerId 工作ID (0~31)
+     * @param datacenterId 数据中心ID (0~31)
+     */
+    public SnowflakeIdWorker(long workerId, long datacenterId) {
+        if (workerId > maxWorkerId || workerId < 0) {
+            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
+        }
+        if (datacenterId > maxDatacenterId || datacenterId < 0) {
+            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
+        }
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+    }
+
+    // ==============================Methods==========================================
+    /**
+     * 获得下一个ID (该方法是线程安全的)
+     * @return SnowflakeId
+     */
+    public synchronized long nextId() {
+        long timestamp = timeGen();
+
+        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
+        if (timestamp < lastTimestamp) {
+            throw new RuntimeException(
+                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
+        }
+
+        //如果是同一时间生成的,则进行毫秒内序列
+        if (lastTimestamp == timestamp) {
+            sequence = (sequence + 1) & sequenceMask;
+            //毫秒内序列溢出
+            if (sequence == 0) {
+                //阻塞到下一个毫秒,获得新的时间戳
+                timestamp = tilNextMillis(lastTimestamp);
+            }
+        }
+        //时间戳改变,毫秒内序列重置
+        else {
+            sequence = 0L;
+        }
+
+        //上次生成ID的时间截
+        lastTimestamp = timestamp;
+
+        //移位并通过或运算拼到一起组成64位的ID
+        return ((timestamp - twepoch) << timestampLeftShift) //
+                | (datacenterId << datacenterIdShift) //
+                | (workerId << workerIdShift) //
+                | sequence;
+    }
+
+    /**
+     * 阻塞到下一个毫秒,直到获得新的时间戳
+     * @param lastTimestamp 上次生成ID的时间截
+     * @return 当前时间戳
+     */
+    protected long tilNextMillis(long lastTimestamp) {
+        long timestamp = timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = timeGen();
+        }
+        return timestamp;
+    }
+
+    /**
+     * 返回以毫秒为单位的当前时间
+     * @return 当前时间(毫秒)
+     */
+    protected long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+    //==============================Test=============================================
+    /** 测试 */
+    public static void main(String[] args) {
+        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(10, 10);
+        for (int i = 0; i < 10000; i++) {
+            long id = idWorker.nextId();
+            System.out.println(id);
+        }
+    }
+}

+ 43 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/UrlSignUtils.java

@@ -0,0 +1,43 @@
+package com.zhaozhaonews.util.common;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+import java.util.Map;
+import java.util.SortedMap;
+
+public enum UrlSignUtils {
+
+    getUrlSignUtils;
+    private static final Logger logger = LoggerFactory.getLogger(UrlSignUtils.class);
+
+    /**
+     * @param params 所有的请求参数都会在这里进行排序加密
+     * @return 得到签名
+     */
+    public String getSign(SortedMap<String, String> params) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry entry : params.entrySet()) {
+            if (!entry.getKey().equals("sign")) { //拼装参数,排除sign
+                if (!StringUtils.isEmpty(entry.getKey()) && !StringUtils.isEmpty(entry.getValue()))
+                    sb.append(entry.getKey()).append('=').append(entry.getValue());
+            }
+        }
+        logger.info("Before Sign : {}", sb.toString());
+        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
+    }
+
+    /**
+     * @param params 所有的请求参数都会在这里进行排序加密
+     * @return 验证签名结果
+     */
+    public boolean verifySign(SortedMap<String, String> params) {
+        if (params == null || StringUtils.isEmpty(params.get("sign"))) return false;
+        String sign = getSign(params);
+        logger.info("verify Sign : {}", sign);
+        return !StringUtils.isEmpty(sign) && params.get("sign").equals(sign);
+    }
+
+}

+ 193 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/common/ZipUtils.java

@@ -0,0 +1,193 @@
+package com.zhaozhaonews.util.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Base64;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 字符串压缩
+ */
+public class ZipUtils {
+
+    /**
+     * 使用gzip进行压缩
+     */
+    public static String gzip(String primStr) {
+        if (primStr == null || primStr.length() == 0) {
+            return primStr;
+        }
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        GZIPOutputStream gzip = null;
+        try {
+            gzip = new GZIPOutputStream(out);
+            gzip.write(primStr.getBytes());
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (gzip != null) {
+                try {
+                    gzip.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        return Base64.getEncoder().encodeToString(out.toByteArray());
+    }
+
+    /**
+     * <p>
+     * Description:使用gzip进行解压缩
+     * </p>
+     *
+     * @param compressedStr
+     * @return
+     */
+    public static String gunzip(String compressedStr) {
+        if (compressedStr == null) {
+            return null;
+        }
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ByteArrayInputStream in = null;
+        GZIPInputStream ginzip = null;
+        byte[] compressed = null;
+        String decompressed = null;
+        try {
+            compressed = Base64.getDecoder().decode(compressedStr);
+            in = new ByteArrayInputStream(compressed);
+            ginzip = new GZIPInputStream(in);
+
+            byte[] buffer = new byte[1024];
+            int offset = -1;
+            while ((offset = ginzip.read(buffer)) != -1) {
+                out.write(buffer, 0, offset);
+            }
+            decompressed = out.toString();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (ginzip != null) {
+                try {
+                    ginzip.close();
+                } catch (IOException e) {
+                }
+            }
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+
+        return decompressed;
+    }
+
+    /**
+     * 使用zip进行压缩
+     *
+     * @param str 压缩前的文本
+     * @return 返回压缩后的文本
+     */
+    public static final String zip(String str) {
+        if (str == null)
+            return null;
+        byte[] compressed;
+        ByteArrayOutputStream out = null;
+        ZipOutputStream zout = null;
+        String compressedStr = null;
+        try {
+            out = new ByteArrayOutputStream();
+            zout = new ZipOutputStream(out);
+            zout.putNextEntry(new ZipEntry("0"));
+            zout.write(str.getBytes());
+            zout.closeEntry();
+            compressed = out.toByteArray();
+            compressedStr = Base64.getEncoder().encodeToString(compressed);
+        } catch (IOException e) {
+            compressed = null;
+        } finally {
+            if (zout != null) {
+                try {
+                    zout.close();
+                } catch (IOException e) {
+                }
+            }
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return compressedStr;
+    }
+
+    /**
+     * 使用zip进行解压缩
+     *
+     * @param compressedStr 压缩后的文本
+     * @return 解压后的字符串
+     */
+    public static final String unzip(String compressedStr) {
+        if (compressedStr == null) {
+            return null;
+        }
+
+        ByteArrayOutputStream out = null;
+        ByteArrayInputStream in = null;
+        ZipInputStream zin = null;
+        String decompressed = null;
+        try {
+            byte[] compressed = Base64.getDecoder().decode(compressedStr);
+            out = new ByteArrayOutputStream();
+            in = new ByteArrayInputStream(compressed);
+            zin = new ZipInputStream(in);
+            zin.getNextEntry();
+            byte[] buffer = new byte[1024];
+            int offset = -1;
+            while ((offset = zin.read(buffer)) != -1) {
+                out.write(buffer, 0, offset);
+            }
+            decompressed = out.toString();
+        } catch (IOException e) {
+            decompressed = null;
+        } finally {
+            if (zin != null) {
+                try {
+                    zin.close();
+                } catch (IOException e) {
+                }
+            }
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return decompressed;
+    }
+}

+ 35 - 0
zhaozhaonews/zhaozhaonews-util/src/main/java/com/zhaozhaonews/util/thread/WmThreadLocalUtil.java

@@ -0,0 +1,35 @@
+package com.zhaozhaonews.util.thread;
+
+import com.zhaozhaonews.model.wemedia.entity.WmUser;
+
+/**
+ * 自媒体用户线程上下文工具类
+ *
+ * 基于 ThreadLocal 实现在同一次请求链路中传递自媒体用户(WmUser)信息,
+ * 避免在 Service 层方法调用链中频繁传递用户参数。
+ *
+ * 提供 setUser(设置当前线程用户)、getUser(获取当前线程用户)、
+ * clear(清理,防止内存泄漏)三个静态方法。
+ *
+ * 使用方式:在拦截器(如 WmTokenInterceptor)中解析 Token 后调用 setUser
+ * 存入用户信息,在业务代码中通过 getUser 获取当前用户,请求结束后在
+ * 拦截器的 afterCompletion 中调用 clear 清理。
+ *
+ * @see com.zhaozhaonews.wemedia.interceptor.WmTokenInterceptor
+ */
+public class WmThreadLocalUtil {
+
+    private static final ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();
+
+    public static void setUser(WmUser wmUser) {
+        WM_USER_THREAD_LOCAL.set(wmUser);
+    }
+
+    public static WmUser getUser() {
+        return WM_USER_THREAD_LOCAL.get();
+    }
+
+    public static void clear() {
+        WM_USER_THREAD_LOCAL.remove();
+    }
+}