Ver código fonte

微服务拆分

wangqi49 2 meses atrás
pai
commit
638900a463
100 arquivos alterados com 6452 adições e 57 exclusões
  1. BIN
      .DS_Store
  2. 7 27
      pom.xml
  3. BIN
      resources/.DS_Store
  4. 61 0
      resources/database-sql/webchat-user.sql
  5. 0 0
      resources/webchat.md
  6. BIN
      webchat-admin/.DS_Store
  7. BIN
      webchat-admin/src/.DS_Store
  8. BIN
      webchat-admin/src/main/.DS_Store
  9. BIN
      webchat-aigc/.DS_Store
  10. BIN
      webchat-aigc/src/.DS_Store
  11. BIN
      webchat-aigc/src/main/.DS_Store
  12. 0 13
      webchat-aigc/src/test/java/com/webchat/pgc/WebchatPgcApplicationTests.java
  13. BIN
      webchat-client/.DS_Store
  14. BIN
      webchat-client/src/.DS_Store
  15. BIN
      webchat-client/src/main/.DS_Store
  16. 1 1
      webchat-client/src/main/java/com/webchat/client/WebchatClientApplication.java
  17. 36 0
      webchat-common/pom.xml
  18. 1 1
      webchat-common/src/main/java/com/webchat/common/bean/APIPageResponseBean.java
  19. 1 1
      webchat-common/src/main/java/com/webchat/common/bean/APIResponseBean.java
  20. 1 1
      webchat-common/src/main/java/com/webchat/common/bean/APIResponseBeanUtil.java
  21. 17 0
      webchat-common/src/main/java/com/webchat/common/constants/CookieConstants.java
  22. 60 0
      webchat-common/src/main/java/com/webchat/common/constants/HttpContentType.java
  23. 130 0
      webchat-common/src/main/java/com/webchat/common/constants/LotteryConstants.java
  24. 91 0
      webchat-common/src/main/java/com/webchat/common/constants/MessageConstants.java
  25. 58 0
      webchat-common/src/main/java/com/webchat/common/constants/WebConstant.java
  26. 29 0
      webchat-common/src/main/java/com/webchat/common/enums/APIErrorCommonEnum.java
  27. 32 0
      webchat-common/src/main/java/com/webchat/common/enums/AiFunctionEnum.java
  28. 19 0
      webchat-common/src/main/java/com/webchat/common/enums/ArticleStatusEnum.java
  29. 21 0
      webchat-common/src/main/java/com/webchat/common/enums/ChatMessageTypeEnum.java
  30. 37 0
      webchat-common/src/main/java/com/webchat/common/enums/ClickEvent.java
  31. 82 0
      webchat-common/src/main/java/com/webchat/common/enums/CommonStatusEnum.java
  32. 44 0
      webchat-common/src/main/java/com/webchat/common/enums/ContentConfigEnum.java
  33. 21 0
      webchat-common/src/main/java/com/webchat/common/enums/ESMessageTypeEum.java
  34. 10 0
      webchat-common/src/main/java/com/webchat/common/enums/FileTypeEnum.java
  35. 18 0
      webchat-common/src/main/java/com/webchat/common/enums/FriendStatusEnum.java
  36. 17 0
      webchat-common/src/main/java/com/webchat/common/enums/LlmModelEnum.java
  37. 37 0
      webchat-common/src/main/java/com/webchat/common/enums/LocalCacheKey.java
  38. 53 0
      webchat-common/src/main/java/com/webchat/common/enums/MessageTypeEnum.java
  39. 27 0
      webchat-common/src/main/java/com/webchat/common/enums/PromptTemplateEnum.java
  40. 23 0
      webchat-common/src/main/java/com/webchat/common/enums/RedPacketStatusEnum.java
  41. 21 0
      webchat-common/src/main/java/com/webchat/common/enums/RedPacketTypeEnum.java
  42. 281 0
      webchat-common/src/main/java/com/webchat/common/enums/RedisKeyEnum.java
  43. 20 0
      webchat-common/src/main/java/com/webchat/common/enums/RedisMessageChannelTopicEnum.java
  44. 32 0
      webchat-common/src/main/java/com/webchat/common/enums/ResourceBehaviorTypeEnum.java
  45. 20 0
      webchat-common/src/main/java/com/webchat/common/enums/ResourceTypeEnum.java
  46. 27 0
      webchat-common/src/main/java/com/webchat/common/enums/RoleCodeEnum.java
  47. 9 0
      webchat-common/src/main/java/com/webchat/common/enums/SseEmitterBizEnum.java
  48. 17 0
      webchat-common/src/main/java/com/webchat/common/enums/UserStatusEnum.java
  49. 41 0
      webchat-common/src/main/java/com/webchat/common/enums/WalletTransEventEnum.java
  50. 23 0
      webchat-common/src/main/java/com/webchat/common/enums/WalletTransTypeEnum.java
  51. 23 0
      webchat-common/src/main/java/com/webchat/common/exception/AsyncException.java
  52. 36 0
      webchat-common/src/main/java/com/webchat/common/exception/AuthException.java
  53. 34 0
      webchat-common/src/main/java/com/webchat/common/exception/BusinessException.java
  54. 34 0
      webchat-common/src/main/java/com/webchat/common/exception/NotFoundException.java
  55. 34 0
      webchat-common/src/main/java/com/webchat/common/exception/ParamException.java
  56. 134 0
      webchat-common/src/main/java/com/webchat/common/helper/SessionHelper.java
  57. 108 0
      webchat-common/src/main/java/com/webchat/common/helper/SseEmitterHelper.java
  58. 1065 0
      webchat-common/src/main/java/com/webchat/common/service/RedisService.java
  59. 37 0
      webchat-common/src/main/java/com/webchat/common/util/ArticleUtil.java
  60. 25 0
      webchat-common/src/main/java/com/webchat/common/util/AvatarUtil.java
  61. 31 0
      webchat-common/src/main/java/com/webchat/common/util/CommonUtils.java
  62. 665 0
      webchat-common/src/main/java/com/webchat/common/util/DateUtils.java
  63. 27 0
      webchat-common/src/main/java/com/webchat/common/util/FileConvertUtil.java
  64. 78 0
      webchat-common/src/main/java/com/webchat/common/util/FileUtil.java
  65. 542 0
      webchat-common/src/main/java/com/webchat/common/util/HtmlUtil.java
  66. 456 0
      webchat-common/src/main/java/com/webchat/common/util/HttpClientUtil.java
  67. 128 0
      webchat-common/src/main/java/com/webchat/common/util/HttpsSSLRequestFactory.java
  68. 30 0
      webchat-common/src/main/java/com/webchat/common/util/IDGenerateUtil.java
  69. 19 0
      webchat-common/src/main/java/com/webchat/common/util/JsonExtractorFromMarkdown.java
  70. 118 0
      webchat-common/src/main/java/com/webchat/common/util/JsonUtil.java
  71. 52 0
      webchat-common/src/main/java/com/webchat/common/util/ListUtil.java
  72. 88 0
      webchat-common/src/main/java/com/webchat/common/util/MD5Utils.java
  73. 91 0
      webchat-common/src/main/java/com/webchat/common/util/PicValidCodeUtil.java
  74. 213 0
      webchat-common/src/main/java/com/webchat/common/util/QRCodeUtils.java
  75. 47 0
      webchat-common/src/main/java/com/webchat/common/util/RemoteIpUtil.java
  76. 54 0
      webchat-common/src/main/java/com/webchat/common/util/SpringContextUtil.java
  77. 21 0
      webchat-common/src/main/java/com/webchat/common/util/StringUtil.java
  78. 72 0
      webchat-common/src/main/java/com/webchat/common/util/ThreadPoolExecutorUtil.java
  79. 27 0
      webchat-common/src/main/java/com/webchat/common/util/TransactionSyncManagerUtil.java
  80. 151 0
      webchat-common/src/main/java/com/webchat/common/util/llm/DeepSeekAIClient.java
  81. 151 0
      webchat-common/src/main/java/com/webchat/common/util/llm/MoonShotAIClient.java
  82. 19 0
      webchat-common/src/main/java/com/webchat/common/util/obj/PicValidCodeResult.java
  83. BIN
      webchat-common/target/classes/com/webchat/bean/APIPageResponseBean.class
  84. BIN
      webchat-common/target/classes/com/webchat/bean/APIResponseBean.class
  85. BIN
      webchat-common/target/classes/com/webchat/bean/APIResponseBeanUtil.class
  86. 0 13
      webchat-connect/src/test/java/com/webchat/connect/WebchatPgcApplicationTests.java
  87. 34 0
      webchat-domain/src/main/java/com/webchat/domain/dto/UploadResultDTO.java
  88. 21 0
      webchat-domain/src/main/java/com/webchat/domain/dto/queue/ArticleDelayConsumeMessageDTO.java
  89. 22 0
      webchat-domain/src/main/java/com/webchat/domain/dto/queue/ArticleDelayMessageDTO.java
  90. 22 0
      webchat-domain/src/main/java/com/webchat/domain/dto/queue/BaseDelayNormalQueueDTO.java
  91. 16 0
      webchat-domain/src/main/java/com/webchat/domain/dto/queue/BaseDelayQueueDTO.java
  92. 27 0
      webchat-domain/src/main/java/com/webchat/domain/dto/queue/BaseQueueDTO.java
  93. 28 0
      webchat-domain/src/main/java/com/webchat/domain/dto/queue/LotteryOrderQueueDTO.java
  94. 40 0
      webchat-domain/src/main/java/com/webchat/domain/vo/common/QueryOrderByCondition.java
  95. 18 0
      webchat-domain/src/main/java/com/webchat/domain/vo/common/QueryPageCondition.java
  96. 13 0
      webchat-domain/src/main/java/com/webchat/domain/vo/dto/AbstractBaseEsDTO.java
  97. 50 0
      webchat-domain/src/main/java/com/webchat/domain/vo/dto/AbstractESMessageDTO.java
  98. 56 0
      webchat-domain/src/main/java/com/webchat/domain/vo/dto/BaseMessageDTO.java
  99. 56 0
      webchat-domain/src/main/java/com/webchat/domain/vo/dto/ChatMessageSearchResultDTO.java
  100. 14 0
      webchat-domain/src/main/java/com/webchat/domain/vo/dto/CommentReplyMessageDTO.java

BIN
.DS_Store


+ 7 - 27
pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>3.2.0</version>
+        <version>3.2.2</version>
     </parent>
 
     <groupId>com.webchat</groupId>
@@ -16,6 +16,7 @@
     <description>web chat spring cloud</description>
 
     <modules>
+        <module>webchat-gateway</module>
         <module>webchat-sso</module>
         <module>webchat-domain</module>
         <module>webchat-common</module>
@@ -34,53 +35,32 @@
     <properties>
         <project.build.jdk>17</project.build.jdk>
         <java.version>17</java.version>
-        <lombok.version>1.18.26</lombok.version>
-        <tomcat.version>9.0.33</tomcat.version>
-        <commonslang3.verison>3.8.1</commonslang3.verison>
-        <common.io.version>2.4</common.io.version>
-        <common.codec.version>1.9</common.codec.version>
-        <common.file.version>1.3</common.file.version>
-        <commons.collections.version>3.2.1</commons.collections.version>
-        <httpclient.version>4.5.1</httpclient.version>
-        <httpmime.version>4.5.1</httpmime.version>
-        <fastjson.version>1.2.41</fastjson.version>
-        <httpcore-version>4.4</httpcore-version>
-        <httpclient-version>4.5</httpclient-version>
-        <commons-logging-version>1.1.3</commons-logging-version>
-        <logback-classic-version>1.2.3</logback-classic-version>
-        <log4j-version>1.2.16</log4j-version>
-        <guava-version>20.0</guava-version>
-        <httpcomponents-version>4.5.6</httpcomponents-version>
-        <commons-collections4-version>4.1</commons-collections4-version>
-        <mapper.version>4.0.0</mapper.version>
-        <elasticsearch.version>7.4.2</elasticsearch.version>
-        <!-- 高版本JDK打包 -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <spring-boot.version>3.2.2</spring-boot.version>
+        <spring-cloud.version>2023.0.5</spring-cloud.version>
         <lombok.version>1.18.30</lombok.version>
-        <spring-cloud.version>2024.0.0</spring-cloud.version>
+        <guava-version>20.0</guava-version>
     </properties>
 
     <dependencies>
-
         <!-- Spring Boot Web依赖 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
-
         <!-- Spring Boot与Nacos整合的核心依赖 -->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
             <version>2023.0.1.0</version> <!-- 与Spring Boot 3.2.x兼容 -->
         </dependency>
-
         <!-- 配置管理依赖,如果你需要配置管理功能 -->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
             <version>2023.0.1.0</version>
         </dependency>
-
         <!--提供了引导配置的支持,允许应用程序在启动时从外部配置中心(如 Nacos、Consul、Config Server 等)加载配置。-->
         <dependency>
             <groupId>org.springframework.cloud</groupId>

BIN
resources/.DS_Store


+ 61 - 0
resources/database-sql/webchat-user.sql

@@ -0,0 +1,61 @@
+-- 用户信息主表
+CREATE TABLE webchat_user.`web_chat_user` (
+                                         `ID` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+                                         `USER_ID` char(60) NOT NULL COMMENT '用户ID',
+                                         `USER_NAME` char(30) NOT NULL COMMENT '用户名',
+                                         `PHOTO` varchar(400) NOT NULL COMMENT '用户头像',
+                                         `MOBILE` char(20) NOT NULL COMMENT '手机号',
+                                         `PASSWORD` char(100) NOT NULL COMMENT '密码',
+                                         `SIGNATURE` varchar(500) DEFAULT '暂无签名' COMMENT '签名',
+                                         `STATUS` INT NOT NULL DEFAULT 1 COMMENT '用户状态状态',
+                                         `ROLE_CODE` INT NOT NULL DEFAULT 1 COMMENT '角色',
+                                         `CREATE_BY` char(100) DEFAULT NULL COMMENT '创建人',
+                                         `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                         `UPDATE_BY` char(100) DEFAULT NULL COMMENT '更新人',
+                                         `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间',
+                                         `VERSION` int DEFAULT '0' COMMENT '版本',
+                                         PRIMARY KEY (`ID`),
+                                         KEY `INDEX_USER_ID` (`USER_ID`),
+                                         KEY `INDEX_MOBILE_PASSWORD` (`MOBILE`, `PASSWORD`),
+                                         KEY `INDEX_STATUS` (`STATUS`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户信息主表';
+
+-- 初始化默认管理员账号
+INSERT INTO `webchat_user`.`web_chat_user` (`ID`, `USER_ID`, `USER_NAME`, `PHOTO`, `MOBILE`, `PASSWORD`, `STATUS`, `ROLE_CODE`,
+                                       `CREATE_BY`, `CREATE_DATE`, `UPDATE_BY`, `UPDATE_DATE`, `VERSION`) VALUES
+    (1, 'U_770cce9f632543588b4e8aa6ec43e6a2', '管理员', 'https://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_dd489633f1bb4513a9db81be6e9d692f.png',
+     'admin', '06525f4969c6cf1886ee0db86bef82df', 1, 2, 'U_770cce9f632543588b4e8aa6ec43e6a2',
+     '2022-03-12 05:55:26', NULL, '2022-03-22 10:28:38', 1);
+
+-- 好友关系表
+CREATE TABLE webchat_user.`web_chat_friend` (
+                                           `ID` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+                                           `USER_ID` char(60) NOT NULL COMMENT '用户ID',
+                                           `FRIEND_ID` char(60) NOT NULL COMMENT '好友ID',
+                                           `STATUS` int(11) DEFAULT '0' COMMENT '好友状态',
+                                           `APPLY_DATE` datetime DEFAULT NULL COMMENT '申请时间',
+                                           `HANDLE_DATE` datetime DEFAULT NULL COMMENT '处理时间',
+                                           `VERSION` int DEFAULT '0' COMMENT '版本',
+                                           PRIMARY KEY (`ID`),
+                                           KEY `INDEX_USER_ID` (`USER_ID`),
+                                           KEY `INDEX_FRIEND_ID` (`FRIEND_ID`),
+                                           KEY `INDEX_STATUS` (`STATUS`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='好友关系表';
+
+
+-- 群组用户表
+CREATE TABLE webchat_user.`web_chat_group_user` (
+                                       `ID` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+                                       `GROUP_ID` char(60) NOT NULL COMMENT '群组ID',
+                                       `USER_ID` char(60) NOT NULL COMMENT '用户ID',
+                                       `STATUS` INT NOT NULL DEFAULT 1 COMMENT '用户状态状态',
+                                       `CREATE_BY` char(100) DEFAULT NULL COMMENT '创建人',
+                                       `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                       `UPDATE_BY` char(100) DEFAULT NULL COMMENT '更新人',
+                                       `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间',
+                                       `VERSION` int DEFAULT '0' COMMENT '版本',
+                                       PRIMARY KEY (`ID`),
+                                       KEY `INDEX_GROUP_ID` (`GROUP_ID`),
+                                       KEY `INDEX_USER_ID` (`USER_ID`),
+                                       KEY `INDEX_STATUS` (`STATUS`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='群组用户表';

+ 0 - 0
webchat-domain/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst → resources/webchat.md


BIN
webchat-admin/.DS_Store


BIN
webchat-admin/src/.DS_Store


BIN
webchat-admin/src/main/.DS_Store


BIN
webchat-aigc/.DS_Store


BIN
webchat-aigc/src/.DS_Store


BIN
webchat-aigc/src/main/.DS_Store


+ 0 - 13
webchat-aigc/src/test/java/com/webchat/pgc/WebchatPgcApplicationTests.java

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

BIN
webchat-client/.DS_Store


BIN
webchat-client/src/.DS_Store


BIN
webchat-client/src/main/.DS_Store


+ 1 - 1
webchat-client/src/main/java/com/webchat/client/WebchatClientApplication.java

@@ -1,4 +1,4 @@
-package com.webchat.web;
+package com.webchat.client;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;

+ 36 - 0
webchat-common/pom.xml

@@ -26,5 +26,41 @@
             <artifactId>webchat-domain</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <!-- 添加依赖 -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>2.0.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.reactivex.rxjava2</groupId>
+            <artifactId>rxjava</artifactId>
+            <version>2.2.21</version>
+        </dependency>
+        <!-- 二维码 -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.1.0</version>
+        </dependency>
     </dependencies>
 </project>

+ 1 - 1
webchat-common/src/main/java/com/webchat/bean/APIPageResponseBean.java → webchat-common/src/main/java/com/webchat/common/bean/APIPageResponseBean.java

@@ -1,4 +1,4 @@
-package com.webchat.bean;
+package com.webchat.common.bean;
 
 import lombok.Data;
 

+ 1 - 1
webchat-common/src/main/java/com/webchat/bean/APIResponseBean.java → webchat-common/src/main/java/com/webchat/common/bean/APIResponseBean.java

@@ -1,4 +1,4 @@
-package com.webchat.bean;
+package com.webchat.common.bean;
 
 import lombok.Data;
 

+ 1 - 1
webchat-common/src/main/java/com/webchat/bean/APIResponseBeanUtil.java → webchat-common/src/main/java/com/webchat/common/bean/APIResponseBeanUtil.java

@@ -1,4 +1,4 @@
-package com.webchat.bean;
+package com.webchat.common.bean;
 
 
 import org.apache.commons.lang3.ObjectUtils;

+ 17 - 0
webchat-common/src/main/java/com/webchat/common/constants/CookieConstants.java

@@ -0,0 +1,17 @@
+package com.webchat.common.constants;
+
+/**
+ * @Author: 程序员七七 程序员盒子网站作者
+ */
+public class CookieConstants {
+
+    /***
+     * 用户登录Cookie
+     */
+    public static final String C_U_USER_COOKIE_KEY = "C_U_NAV_USER_SESSION";
+
+    /***
+     * cookie过期时间 秒为单位,5天
+     */
+    public static final int COOKIE_OUT_TIME = 60 * 60 * 24 * 5;
+}

+ 60 - 0
webchat-common/src/main/java/com/webchat/common/constants/HttpContentType.java

@@ -0,0 +1,60 @@
+package com.webchat.common.constants;
+
+import org.apache.http.Consts;
+import org.apache.http.entity.ContentType;
+import org.springframework.http.MediaType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HttpContentType {
+
+    public static final ContentType UTF8_TEXT_PLAIN = ContentType.TEXT_PLAIN.withCharset(Consts.UTF_8);
+
+    public static final ContentType UTF8_APPLICATION_FORM_URLENCODED =
+            ContentType.APPLICATION_FORM_URLENCODED.withCharset(Consts.UTF_8);
+
+    public static final ContentType UTF8_APPLICATION_JSON = ContentType.APPLICATION_JSON.withCharset(Consts.UTF_8);
+
+    public static final ContentType UTF8_APPLICATION_XML = ContentType.APPLICATION_XML.withCharset(Consts.UTF_8);
+
+    public static final ContentType UTF8_MULTIPART_FORM_DATA =
+            ContentType.MULTIPART_FORM_DATA.withCharset(Consts.UTF_8);
+
+    public static final ContentType UTF8_TEXT_HTML = ContentType.TEXT_HTML.withCharset(Consts.UTF_8);
+
+    public static final ContentType UTF8_TEXT_XML = ContentType.TEXT_XML.withCharset(Consts.UTF_8);
+
+    public static boolean equals(ContentType type1, ContentType type2) {
+        return equalsMimeType(type1, type2) && type1.getCharset().equals(type2.getCharset());
+    }
+
+    public static boolean equalsMimeType(ContentType type1, ContentType type2) {
+        if (null == type1 || null == type2) {
+            return false;
+        }
+        return type1.getMimeType().equals(type2.getMimeType());
+    }
+
+    public static List<MediaType> getSupportedMediaTypes() {
+        List<MediaType> supportedMediaTypes = new ArrayList<>();
+        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
+        supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
+        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
+        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
+        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
+        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
+        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
+        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
+        supportedMediaTypes.add(MediaType.APPLICATION_XML);
+        supportedMediaTypes.add(MediaType.IMAGE_GIF);
+        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
+        supportedMediaTypes.add(MediaType.IMAGE_PNG);
+        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
+        supportedMediaTypes.add(MediaType.TEXT_HTML);
+        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
+        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
+        supportedMediaTypes.add(MediaType.TEXT_XML);
+        return supportedMediaTypes;
+    }
+}

+ 130 - 0
webchat-common/src/main/java/com/webchat/common/constants/LotteryConstants.java

@@ -0,0 +1,130 @@
+package com.webchat.common.constants;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 00:14
+ * @description
+ */
+public class LotteryConstants {
+
+    /**
+     * 抽奖活动ID前缀 LotteryActivity: LA
+     */
+    public static final String LOTTERY_ACTIVITY_ID_PREFIX = "LA";
+    /**
+     * 抽奖订单活动ID前缀 LotteryOrder: LO
+     */
+    public static final String LOTTERY_ORDER_ID_PREFIX = "LO";
+    /**
+     * 活动奖品数量应该为8个
+     */
+    public static final int LOTTERY_ITEM_COUNT = 8;
+    /**
+     * 系统
+     */
+    public static final String SYSTEM = "SYSTEM";
+
+    /**
+     * 抽奖活动状态枚举
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum LotteryActivityStatus {
+
+        PREPARATION(1, "筹备中"),
+        RUNNING(2, "进行中"),
+        CLOSED(3, "已结束"),
+        DELETED(4, "已删除");
+
+        private Integer status;
+        private String statusName;
+    }
+
+    /**
+     * 抽奖活动状态枚举
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum LotteryOrderStatus {
+
+        UNCHANGED(1, "未兑换"),
+        EXCHANGED(2, "已兑换"),
+        DELETED(3, "已删除");
+
+        private Integer status;
+        private String statusName;
+    }
+
+    /**
+     * 抽奖奖品类型
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum LotteryItemType {
+
+        INTEGRAL(1, "猿气值"),
+        GOODS(2, "实体商品"),
+        MONEY(3, "现金红包");
+
+        private Integer type;
+        private String typeName;
+    }
+
+    /**
+     * 根据活动状态获取活动中文名称
+     *
+     * @param status
+     * @return
+     */
+    public static String getLotteryActivityStatusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        for (LotteryActivityStatus lotteryActivityStatus : LotteryActivityStatus.values()) {
+            if (lotteryActivityStatus.status.equals(status)) {
+                return lotteryActivityStatus.statusName;
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据活动状态获取活动中文名称
+     *
+     * @param type
+     * @return
+     */
+    public static String getLotteryActivityItemTypeName(Integer type) {
+        if (type == null) {
+            return "";
+        }
+        for (LotteryItemType lotteryItemType : LotteryItemType.values()) {
+            if (lotteryItemType.type.equals(type)) {
+                return lotteryItemType.typeName;
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据订单状态获取中文描述
+     *
+     * @param status
+     * @return
+     */
+    public static String getLotteryOrderStatusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        for (LotteryOrderStatus lotteryOrderStatus : LotteryOrderStatus.values()) {
+            if (lotteryOrderStatus.status.equals(status)) {
+                return lotteryOrderStatus.statusName;
+            }
+        }
+        return "";
+    }
+}

+ 91 - 0
webchat-common/src/main/java/com/webchat/common/constants/MessageConstants.java

@@ -0,0 +1,91 @@
+package com.webchat.common.constants;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/13 00:49
+ * @description
+ */
+public class MessageConstants {
+
+    /**
+     * 系统发送的消息,系统用户
+     */
+    public static final String FROM_SYSTEM = "SYSTEM";
+
+    /**
+     * 消息分类
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum CategoryEnum {
+
+        SYSTEM("系统消息"),
+
+        COMMENT_REPLY("评论与回复"),
+
+        LIKE_COLLECT("点赞与收藏"),
+
+        FOCUS("粉丝关注");
+
+        private String categoryName;
+
+        public String getCategory() {
+            return this.name();
+        }
+    }
+
+    public static String getCategoryName(String category) {
+        if (StringUtils.isBlank(category)) {
+            return "";
+        }
+        for (CategoryEnum categoryEnum : CategoryEnum.values()) {
+            if (categoryEnum.getCategory().equals(category)) {
+                return categoryEnum.getCategoryName();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 消息类型
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum TypeEnum {
+
+        LIKE("点赞"),
+
+        LIKE_COMMENT("点赞"),
+
+        COLLECT("收藏"),
+
+        COMMENT("评论消息"),
+
+        REPLY("回复消息"),
+
+        OTHER("其他");
+
+        private String typeName;
+
+        public String getType() {
+            return this.name();
+        }
+    }
+
+    public static String getTypeName(String type) {
+        if (StringUtils.isBlank(type)) {
+            return "";
+        }
+        for (TypeEnum typeEnum : TypeEnum.values()) {
+            if (typeEnum.getType().equals(type)) {
+                return typeEnum.getTypeName();
+            }
+        }
+        return "";
+    }
+}

+ 58 - 0
webchat-common/src/main/java/com/webchat/common/constants/WebConstant.java

@@ -0,0 +1,58 @@
+package com.webchat.common.constants;
+
+/***
+ * 常量池
+ */
+public class WebConstant {
+
+    /**
+     * redis前缀
+     */
+    public static final String REDIS_KEY_PREFIX = "WEB_CHAT_";
+
+    /**
+     * 缓存的空值
+     */
+    public static final String CACHE_NONE = "none";
+
+    /**
+     * 系统账户
+     */
+    public static final String SYSTEM_WALLET_ID = "U_770cce9f632543588b4e8aa6ec43e6a2";
+
+
+    /**
+     * 系统USERID
+     */
+    public static final String SYSTEM_USER_ID = "SYSTEM";
+
+    /**
+     * MD5 盐值
+     */
+    public static final String MD5_SALT = "SAH(S&0218328jdhaj(**";
+
+    /***
+     * 用户登录SESSION KEY
+     */
+    public static final String SESSION_KEY_USER_LOGIN_INFO = "user_login_info_session";
+
+    /**
+     * & 符号
+     */
+    public static final String AND_SIGN = "&";
+
+    /**
+     * utf-8编码
+     */
+    public static final String ENCODING_UTF8 = "UTF-8";
+
+    /***
+     * 分隔符
+     */
+    public static final String SPLIT_CHAR = ",";
+
+    /***
+     * 图形验证吗session key
+     */
+    public static final String PIC_VALID_CODE_SESSION_KEY = "PIC_VALID_CODE_SESSION";
+}

+ 29 - 0
webchat-common/src/main/java/com/webchat/common/enums/APIErrorCommonEnum.java

@@ -0,0 +1,29 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+/***
+ * 通用接口异常CODE
+ */
+@Getter
+public enum APIErrorCommonEnum {
+
+    /****
+     * 无权限异常CODE:101XXX
+     */
+    UN_AUTH(101400, "无权限!"),
+    USER_UN_LOGIN(101401, "未登录!"),
+    USER_NOT_FOUND(101404, "用户不存在!"),
+    USER_UN_AUTH(101405, "无权限!"),
+    VALID_EXCEPTION(100001, "参数校验失败!"),
+    API_UN_AUTH(101406, "接口调用无权限!"),
+    UN_SAFE_EVENT(999999, "识别存在非安全事件!");
+
+    private Integer code;
+    private String  message;
+
+    APIErrorCommonEnum(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+}

+ 32 - 0
webchat-common/src/main/java/com/webchat/common/enums/AiFunctionEnum.java

@@ -0,0 +1,32 @@
+package com.webchat.common.enums;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+public enum AiFunctionEnum {
+
+    CHAT("对话"),
+
+    IMAGE("文生图");
+
+    private String functionName;
+
+
+    public static String getFunctionName(String function) {
+        if (StringUtils.isBlank(function)) {
+            return "未识别到意图";
+        }
+        for (AiFunctionEnum aiFunctionEnum : AiFunctionEnum.values()) {
+            if (aiFunctionEnum.name().equals(function)) {
+                return aiFunctionEnum.getFunctionName();
+            }
+        }
+        return "未识别到意图";
+    }
+}

+ 19 - 0
webchat-common/src/main/java/com/webchat/common/enums/ArticleStatusEnum.java

@@ -0,0 +1,19 @@
+package com.webchat.common.enums;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum ArticleStatusEnum {
+
+    WAIT_PUSH(1, "待推送"),
+
+    PUSHED(2, "待推送");
+
+    private Integer status;
+    private String statusName;
+}

+ 21 - 0
webchat-common/src/main/java/com/webchat/common/enums/ChatMessageTypeEnum.java

@@ -0,0 +1,21 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum ChatMessageTypeEnum {
+
+    CHAT_TEXT(1, "聊天"),
+    CHAT_FILE(2, "文件"),
+    RED_PACKET(3, "红包"),
+    PUBLIC_ACCOUNT_ARTICLE(4, "公众号推文"),
+    APPLY(5, "申请添加好友"),
+    WALLET_BALANCE(6, "钱包余额");
+
+    private Integer type;
+    private String desc;
+}

+ 37 - 0
webchat-common/src/main/java/com/webchat/common/enums/ClickEvent.java

@@ -0,0 +1,37 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Author: 程序员七七
+ * @Date: 27.11.21 12:27 上午
+ * 点击事件
+ */
+@AllArgsConstructor
+@Getter
+public enum ClickEvent {
+
+
+    RESOURCE_SUPPORT("资源点赞"),
+
+    COMMENT("评论"),
+
+    SUBMIT("提交"),
+
+    REGISTRY("注册"),
+
+    SAVE_ARTICLE("保存文章"),
+
+    LUCK_LOTTERY("幸运抽奖"),
+
+    SEND_RED_PACKET("发红包"),
+
+    OPEN_RED_PACKET("拆红包"),
+
+    CREATE_GROUP("创建群聊"),
+
+    ;
+
+    private String actionName;
+}

+ 82 - 0
webchat-common/src/main/java/com/webchat/common/enums/CommonStatusEnum.java

@@ -0,0 +1,82 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: 程序员王七七 https://www.coderutil.com 网站作者
+ * @Date: 2021-6-14 0014 14:47
+ * @Description: 无描述信息
+ */
+@Getter
+public enum CommonStatusEnum {
+
+    NEW(0, "新建"),
+    PUBLISHED(1, "发布"),
+    BACK(2, "驳回"),
+    DELETED(3, "删除"),
+    WAIT_REVIEW(4, "待审核"),
+    FINISHED(5, "已完成");
+
+    private Integer statusCode;
+    private String statusDesc;
+
+    CommonStatusEnum(Integer statusCode, String statusDesc) {
+        this.statusCode = statusCode;
+        this.statusDesc = statusDesc;
+    }
+
+    public String getStatus() {
+        return this.name();
+    }
+
+    public static String getStatusDescByStatus(String status) {
+        for (CommonStatusEnum commonStatusEnum : CommonStatusEnum.values()) {
+            if (commonStatusEnum.name().equals(status)) {
+                return commonStatusEnum.getStatusDesc();
+            }
+        }
+        return null;
+    }
+
+    public static String getStatusDescByStatus(Integer statusCode) {
+        for (CommonStatusEnum commonStatusEnum : CommonStatusEnum.values()) {
+            if (commonStatusEnum.statusCode.equals(statusCode)) {
+                return commonStatusEnum.getStatusDesc();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 取全部可用状态
+     * @return
+     */
+    public static List<String> getAllEnableStatusDesc() {
+        List<String> statusList = new ArrayList<>();
+        statusList.add(NEW.getStatus());
+        statusList.add(PUBLISHED.getStatus());
+        statusList.add(BACK.getStatus());
+        statusList.add(WAIT_REVIEW.getStatus());
+        return statusList;
+    }
+
+    /**
+     * 取全部状态
+     * @return
+     */
+    public static List<String> getAllStatusDesc() {
+        List<String> statusList = getAllEnableStatusDesc();
+        statusList.add(DELETED.getStatus());
+        return statusList;
+    }
+
+    public static List<String> getAllowSubmitStatus() {
+        List<String> statusList = new ArrayList<>();
+        statusList.add(NEW.getStatus());
+        statusList.add(WAIT_REVIEW.getStatus());
+        return statusList;
+    }
+}

+ 44 - 0
webchat-common/src/main/java/com/webchat/common/enums/ContentConfigEnum.java

@@ -0,0 +1,44 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @Author: 程序员王七七 https://www.coderutil.com 网站作者
+ * @Date: 2021-7-24 0024 23:01
+ * @Description: 无描述信息
+ */
+@Getter
+public enum ContentConfigEnum {
+
+    MESSAGE_MOMENT_COMMENT("动态评论消息", "userName", "bbsContent"),
+    MESSAGE_MOMENT_COMMENT_REPLY("动态下评论回复消息", "userName", "bbsContent", "commentContent");
+
+    /***
+     * 模板名称
+     */
+    private String configName;
+
+    /***
+     * 替换的变量名
+     */
+    private String[] vars;
+
+    ContentConfigEnum(String configName, String ... vars) {
+        this.configName = configName;
+        this.vars = vars;
+    }
+
+    public static String[] getVars(String code) {
+        String[] vars = new String[0];
+        if (StringUtils.isBlank(code)) {
+            return vars;
+        }
+        for (ContentConfigEnum config : ContentConfigEnum.values()) {
+            if (config.name().equals(code)) {
+                return config.vars;
+            }
+        }
+        return vars;
+    }
+}

+ 21 - 0
webchat-common/src/main/java/com/webchat/common/enums/ESMessageTypeEum.java

@@ -0,0 +1,21 @@
+package com.webchat.common.enums;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum ESMessageTypeEum {
+
+    CHAT_MESSAGE(1, "聊天消息"),
+    ACCOUNT(2, "账号"),
+    PUBLIC_ACCOUNT_ARTICLE(3, "公众号文章"),
+    ;
+
+    private Integer type;
+    private String typeName;
+
+}

+ 10 - 0
webchat-common/src/main/java/com/webchat/common/enums/FileTypeEnum.java

@@ -0,0 +1,10 @@
+package com.webchat.common.enums;
+
+/**
+ * @author 程序员王七七 https://www.coderutil.com 网站作者
+ * @date 2024/11/6 22:56
+ */
+public enum FileTypeEnum {
+
+    IMAGE, VIDEO, ZIP,  DOC, EXCEL, OTHER
+}

+ 18 - 0
webchat-common/src/main/java/com/webchat/common/enums/FriendStatusEnum.java

@@ -0,0 +1,18 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum FriendStatusEnum {
+
+    REFUSE(-1, "拒绝"),
+
+    APPLY(0, "申请待处理"),
+    PASS(1, "通过");
+
+    private Integer status;
+
+    private String statusName;
+}

+ 17 - 0
webchat-common/src/main/java/com/webchat/common/enums/LlmModelEnum.java

@@ -0,0 +1,17 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum LlmModelEnum {
+
+    KIMI("kimi"),
+
+    DEEPSEEK("deepseek");
+
+    private String model;
+
+    LlmModelEnum(String model) {
+        this.model = model;
+    }
+}

+ 37 - 0
webchat-common/src/main/java/com/webchat/common/enums/LocalCacheKey.java

@@ -0,0 +1,37 @@
+package com.webchat.common.enums;
+
+/**
+ * @Author: 程序员七七
+ * @Date: 1.4.22 11:12 下午
+ */
+public enum LocalCacheKey {
+
+    /***
+     * 系统配置本地缓存KEY
+     */
+    SYSTEM_CONFIG,
+
+    /***
+     * 搜索引擎配置本地缓存
+     */
+    ENGINE_CONFIG,
+
+    /***
+     * 友情链接
+     */
+    FLINK,
+
+    /***
+     * 核心数据缓存
+     */
+    CORE,
+
+    /***
+     * 用户自定义导航
+     */
+    USER_NAV;
+
+    public String getKey() {
+        return this.name();
+    }
+}

+ 53 - 0
webchat-common/src/main/java/com/webchat/common/enums/MessageTypeEnum.java

@@ -0,0 +1,53 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: 程序员王七七 https://www.coderutil.com 网站作者
+ * @Date: 2021-7-24 0024 21:53
+ * @Description: 无描述信息
+ */
+@Getter
+public enum MessageTypeEnum {
+
+    LIKE_ARTICLE(1000, 1001, "文章点赞消息"),
+    LIKE_COMMENT(1000, 1002, "评论点赞消息"),
+    COMMENT(2000, 2001, "评论消息"),
+    REPLY(2000, 2002, "回复消息"),
+    SYSTEM(3000, 3001, "系统消息"),
+    FOCUS(4000, 4001, "粉丝消息");
+
+    private Integer code1;
+
+    private Integer code2;
+
+    private String desc;
+
+    MessageTypeEnum(Integer code1, Integer code2, String desc) {
+        this.code1 = code1;
+        this.code2 = code2;
+        this.desc = desc;
+    }
+
+    public static Integer getCode1ByCode2(Integer code2) {
+        for (MessageTypeEnum messageTypeEnum : MessageTypeEnum.values()) {
+            if (messageTypeEnum.code2.equals(code2)) {
+                return messageTypeEnum.code1;
+            }
+        }
+        return null;
+    }
+
+    public static List<Integer> getCode2ListByCode1(Integer code1) {
+        List<Integer> code2List = new ArrayList<>();
+        for (MessageTypeEnum messageTypeEnum : MessageTypeEnum.values()) {
+            if (messageTypeEnum.code1.equals(code1)) {
+                code2List.add(messageTypeEnum.code2);
+            }
+        }
+        return code2List;
+    }
+}

+ 27 - 0
webchat-common/src/main/java/com/webchat/common/enums/PromptTemplateEnum.java

@@ -0,0 +1,27 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2024/1/17 00:05
+ * @description
+ */
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum PromptTemplateEnum {
+
+    ROBOT_CHAT("/ftl/ROBOT_CHAT.ftl", "机器人对话"),
+
+    ROBOT_FC("/ftl/ROBOT_FC.ftl", "大模型意图识别"),
+
+    RAG("/ftl/RAG.ftl", "公众号文章RAG问答"),
+    ;
+
+    private String path;
+    private String desc;
+}

+ 23 - 0
webchat-common/src/main/java/com/webchat/common/enums/RedPacketStatusEnum.java

@@ -0,0 +1,23 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author 程序员七七, https://www.coderutil.com网站作者
+ * @date 2024/11/9 04:47
+ */
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum RedPacketStatusEnum {
+
+
+    NORMAL(1, "正常"),
+    END(2, "已结束"),
+    EXPIRED(3, "过期");
+
+    private Integer status;
+    private String statusName;
+}

+ 21 - 0
webchat-common/src/main/java/com/webchat/common/enums/RedPacketTypeEnum.java

@@ -0,0 +1,21 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author 程序员七七, https://www.coderutil.com网站作者
+ * @date 2024/11/9 06:11
+ */
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum RedPacketTypeEnum {
+
+    NORMAL(1, "普通红包"),
+    RANDOM(2, "拼手气红包");
+
+    private Integer type;
+    private String typeName;
+}

+ 281 - 0
webchat-common/src/main/java/com/webchat/common/enums/RedisKeyEnum.java

@@ -0,0 +1,281 @@
+package com.webchat.common.enums;
+
+import com.webchat.common.constants.WebConstant;
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+
+@Getter
+public enum RedisKeyEnum {
+
+    /***
+     * 注册限流,5S内一次
+     */
+    USER_REGISTRY_LIMIT("USER_REGISTRY_LIMIT", 5L),
+
+    /**
+     * 创建群聊限流,10S内一次
+     */
+    CREATE_GROUP_LIMIT("CREATE_GROUP_LIMIT", 10L),
+
+    /**
+     * 创建机器人防重复触发,10S内一次
+     */
+    CREATE_ROBOT_LIMIT("CREATE_ROBOT_LIMIT", 10L),
+
+    /**
+     * 创建公众号防重复触发,10S内一次
+     */
+    CREATE_PUBLIC_ACCOUNT_LIMIT("CREATE_PUBLIC_ACCOUNT_LIMIT", 10L),
+
+    /**
+     * 公众号列表缓存
+     */
+    PUBLIC_ACCOUNT_LIST_CACHE("PUBLIC_ACCOUNT_LIST_CACHE", 24 * 60 * 60L),
+
+    /**
+     * 用户信息缓存 缓存7天
+     */
+    USER_INFO_CACHE("USER_INFO_CACHE", 7 * 24 * 60 * 60L),
+
+    /***
+     * 私信用户列表
+     */
+    MESS_USER_LIST_KEY("MESS_USER_LIST_KEY", -1L),
+
+    /***
+     * 用户私信消息
+     */
+    USER_CHAT_MESS_CACHE_KEY("USER_CHAT_MESS_CACHE_KEY", 7 * 24 * 60 * 60L),
+
+    /**
+     * 用户消息生产 消息队列
+     */
+    QUEUE_WEB_USER_MESSAGE("QUEUE_WEB_USER_MESSAGE", -1L),
+
+    QUEUE_WEB_OPEN_RED_PACKET_MESSAGE("QUEUE_WEB_OPEN_RED_PACKET_MESSAGE", -1L),
+
+    /**
+     * 文章发布消息队列
+     */
+    QUEUE_ARTICLE_PUBLISH_MESSAGE("QUEUE_ARTICLE_PUBLISH_MESSAGE", -1L),
+
+    /**
+     * 文章延迟发布消息队列
+     */
+    QUEUE_ARTICLE_DELAY_PUBLISH_MESSAGE("QUEUE_ARTICLE_DELAY_PUBLISH_MESSAGE", -1L),
+
+    /**
+     * 消息模板
+     */
+    MESSAGE_CONTENT_TEMPLATE_CACHE("MESSAGE_CONTENT_TEMPLATE_CACHE", -1L),
+
+    /**
+     * 消息详情缓存
+     */
+    MESSAGE_DETAIL_CACHE("MESSAGE_DETAIL_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 新消息数缓存
+     */
+    MESSAGE_UNREAD_COUNT_CACHE("MESSAGE_UNREAD_COUNT_CACHE", -1L),
+
+    /**
+     * 消息数量缓存
+     */
+    MESSAGE_USER_UN_READ_COUNT_CACHE("MESSAGE_USER_UN_READ_COUNT_CACHE", -1L),
+
+    /**
+     * 消息列表缓存
+     */
+    MESSAGE_LIST_CACHE("MESSAGE_LIST_CACHE", -1L),
+
+    /***
+     * 私信消息缓存
+     */
+    MESS_DETAIL_CACHE_KEY("MESS_DETAIL_CACHE_KEY", -1L),
+
+    /**
+     * 未读私信数量
+     */
+    UN_READ_MESS_COUNT_CACHE("UN_READ_MESS_COUNT_CACHE", -1L),
+
+    /**
+     * 未读私信人数量
+     */
+    UN_READ_MESS_USER_SET_CACHE("UN_READ_MESS_USER_SET_CACHE", -1L),
+
+    /***
+     * 用户登录Session PREFIX
+     */
+    USER_SESSION_PREFIX("USER_SESSION_PREFIX", 3 * 24 * 60 * 60L),
+
+    /**
+     * 群组下成员用户id列表缓存
+     */
+    GROUP_USER_ID_LIST_CACHE("GROUP_USER_ID_LIST_CACHE", 7 * 24 * 60 * 60L),
+
+    /**
+     * 用户加入的所有群组
+     */
+    USER_ATTEND_GROUP_ID_LIST_CACHE("USER_ATTEND_GROUP_ID_LIST_CACHE", 7 * 24 * 60 * 60L),
+
+    /**
+     * 资源操作的用户列表
+     */
+    RESOURCE_BEHAVIOR_USERS_CACHE("RESOURCE_BEHAVIOR_USERS_CACHE", -1L),
+
+    /**
+     * 资源行为计数缓存
+     */
+    RESOURCE_BEHAVIOR_COUNT_CACHE("RESOURCE_BEHAVIOR_COUNT_CACHE", -1L),
+
+    /***
+     * 用户点赞的资源列表
+     */
+    USER_BEHAVIOR_RESOURCE_CACHE("USER_BEHAVIOR_RESOURCE_CACHE", -1L),
+
+    /**
+     * 社区内容详情缓存
+     */
+    MOMENT_DETAIL_CACHE("MOMENT_DETAIL_CACHE", 7 * 25 * 60* 60L),
+
+    /**
+     * 朋友圈动态列表
+     */
+    MOMENT_TIME_LINE_CACHE("MOMENT_TIME_LINE_CACHE", -1L),
+
+    /**
+     * 外显评论
+     */
+    COMMENT_OUT_LIST_CACHE("COMMENT_OUT_LIST_CACHE", -1L),
+
+    /**
+     * 评论数缓存
+     */
+    COMMENT_COUNT_CACHE("COMMENT_COUNT_CACHE", -1L),
+
+    /**
+     * 红包详情
+     */
+    RED_PACKET_DETAIL_CACHE("RED_PACKET_DETAIL_CACHE", 2 * 24 * 60 * 60L),
+
+    /**
+     * 用户钱包余额
+     */
+    USER_WALLET_BALANCE_CACHE("USER_WALLET_BALANCE_CACHE", 3* 24 * 60 * 60L),
+
+    /**
+     * 用户钱包余额刷新 加锁
+     */
+    REFRESH_USER_WALLET_BALANCE_CACHE_LOCK("USER_WALLET_BALANCE_CACHE_LOCK", -1L),
+
+    /**
+     * 红包累计被抢次数
+     */
+    RED_PACKET_GRAD_COUNT("RED_PACKET_GRAD_COUNT", -1L),
+
+    /**
+     * 红包剩余可拆分金额
+     */
+    RED_PACKET_BALANCE_COUNT("RED_PACKET_BALANCE_COUNT", -1L),
+
+    /**
+     * 红包领取的用户记录
+     */
+    RED_PACKET_RECEIVER_COUNT("RED_PACKET_RECEIVER_COUNT", -1L),
+
+    /**
+     * 红包拆分记录
+     */
+    RED_PACKET_RECEIVER_TIMELINE_COUNT("RED_PACKET_RECEIVER_TIMELINE_COUNT", -1L),
+
+    /**
+     * 公众号单篇文章详情缓存, 3天有效
+     */
+    ARTICLE_DETAIL_CACHE("ARTICLE_DETAIL_CACHE", 3 * 24 * 60 * 60L),
+
+    /**
+     * ES 数据增量同步,最近一次成功同步时间缓存
+     */
+    ES_SYNC_LAST_TIME("ES_SYNC_LAST_TIME", -1L),
+
+    /**
+     * 滑块验证码
+     */
+    SLIDE_VERIFICATION_CACHE("SLIDE_VERIFICATION_CACHE", 30 * 25 * 60* 60L),
+    SLIDE_VERIFICATION_TOKEN_CACHE("SLIDE_VERIFICATION_TOKEN_CACHE", 60L),
+    SLIDE_VERIFICATION_TOKEN_VALIDATE_RESULT_CACHE("SLIDE_VERIFICATION_TOKEN_VALIDATE_RESULT_CACHE", 60L),
+
+    /**
+     * 抽奖活动缓存
+     */
+    LOTTERY_ACTIVITY_DETAIL_CACHE("LOTTERY_ACTIVITY_DETAIL_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 历史抽奖活动缓存
+     */
+    LOTTERY_ACTIVITY_HISTORY_CACHE("LOTTERY_ACTIVITY_HISTORY_CACHE", -1L),
+
+    /**
+     * 最近一期活动ID缓存
+     */
+    LOTTERY_ACTIVITY_LAST_ID_CACHE("LOTTERY_ACTIVITY_LAST_ID_CACHE", -1L),
+
+    /**
+     * 抽奖活动奖品余量缓存
+     */
+    LOTTERY_ACTIVITY_ITEM_STOCK_CACHE("LOTTERY_ACTIVITY_ITEM_STOCK_CACHE", -1L),
+
+    /**
+     * 抽奖商品详情缓存
+     */
+    LOTTERY_ACTIVITY_ITEM_DETAIL_CACHE("LOTTERY_ACTIVITY_ITEM_DETAIL_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 中奖结果缓存
+     */
+    LOTTERY_ACTIVITY_ORDER_CACHE("LOTTERY_ACTIVITY_ORDER_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 订单详情缓存
+     */
+    LOTTERY_ACTIVITY_ORDER_DETAIL_CACHE("LOTTERY_ACTIVITY_ORDER_DETAIL_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 用户中奖结果缓存
+     */
+    LOTTERY_ACTIVITY_USER_ORDER_CACHE("LOTTERY_ACTIVITY_USER_ORDER_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 抽奖结果订单
+     */
+    QUEUE_LOTTERY_ORDER_MESSAGE("QUEUE_LOTTERY_ORDER_MESSAGE", -1L),
+
+
+    ;
+
+
+    RedisKeyEnum(String key, Long expireTime) {
+        this.key = key;
+        this.expireTime = expireTime;
+    }
+
+    private String key;
+
+    /**
+     * 有效时间,单位秒
+     */
+    private long expireTime;
+
+    public String getKey(String... suffix) {
+        StringBuilder tmpSuffix = new StringBuilder();
+        if (suffix != null && suffix.length > 0) {
+            for (String str : suffix) {
+                if (StringUtils.isNotBlank(str)) {
+                    tmpSuffix.append("_").append(str);
+                }
+            }
+        }
+        return WebConstant.REDIS_KEY_PREFIX + this.name() + tmpSuffix;
+    }
+}

+ 20 - 0
webchat-common/src/main/java/com/webchat/common/enums/RedisMessageChannelTopicEnum.java

@@ -0,0 +1,20 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum RedisMessageChannelTopicEnum {
+
+    CHAT,
+    PUSH_ARTICLE,
+    APPLY,
+    GROUP_VIDEO_ONLINE,
+    GROUP_VIDEO_OFFLINE,
+    VIDEO_OFFER_SEND,
+    VIDEO_OFFER_RECEIVER,
+    GROUP_VIDEO_OFFER_SEND;
+
+    public String getChannel() {
+        return this.name();
+    }
+}

+ 32 - 0
webchat-common/src/main/java/com/webchat/common/enums/ResourceBehaviorTypeEnum.java

@@ -0,0 +1,32 @@
+package com.webchat.common.enums;
+
+
+import com.webchat.common.constants.MessageConstants;
+import lombok.Getter;
+
+
+/***
+ * 对资源的操作行为:点赞、收藏、分享、浏览、打赏
+ */
+@Getter
+public enum ResourceBehaviorTypeEnum {
+
+    LIKE("点赞"), VIEW("预览/浏览");
+
+    private String behaviorName;
+
+    ResourceBehaviorTypeEnum(String behaviorName) {
+        this.behaviorName = behaviorName;
+    }
+
+    public String getBehavior() {
+        return this.name();
+    }
+
+    public static MessageConstants.TypeEnum getMessageType(String behaviorType) {
+        if (LIKE.name().equals(behaviorType)) {
+            return MessageConstants.TypeEnum.LIKE;
+        }
+        return null;
+    }
+}

+ 20 - 0
webchat-common/src/main/java/com/webchat/common/enums/ResourceTypeEnum.java

@@ -0,0 +1,20 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum ResourceTypeEnum {
+
+
+    MOMENT("朋友圈动态");
+
+    private String typeName;
+
+    ResourceTypeEnum(String typeName) {
+        this.typeName = typeName;
+    }
+
+    public String getType() {
+        return this.name();
+    }
+}

+ 27 - 0
webchat-common/src/main/java/com/webchat/common/enums/RoleCodeEnum.java

@@ -0,0 +1,27 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum RoleCodeEnum {
+
+    USER(1, "普通用户"),
+
+    ADMIN(2, "管理员"),
+
+    BLACK(3, "黑名单"),
+
+    GROUP(4, "群组"),
+
+    ROBOT(5, "聊天机器人"),
+
+    PUBLIC_ACCOUNT(6, "公众号");
+
+    private Integer code;
+    private String name;
+
+    RoleCodeEnum(Integer code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+}

+ 9 - 0
webchat-common/src/main/java/com/webchat/common/enums/SseEmitterBizEnum.java

@@ -0,0 +1,9 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum SseEmitterBizEnum {
+
+    ROBOT_CHAT;
+}

+ 17 - 0
webchat-common/src/main/java/com/webchat/common/enums/UserStatusEnum.java

@@ -0,0 +1,17 @@
+package com.webchat.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum UserStatusEnum {
+
+    ENABLE(1),
+
+    DISABLE(0);
+
+    private Integer status;
+
+    UserStatusEnum(Integer status) {
+        this.status = status;
+    }
+}

+ 41 - 0
webchat-common/src/main/java/com/webchat/common/enums/WalletTransEventEnum.java

@@ -0,0 +1,41 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author 程序员七七, https://www.coderutil.com网站作者
+ * @date 2024/11/9 03:53
+ */
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum WalletTransEventEnum {
+
+    SYSTEM_GRANT(1, WalletTransTypeEnum.INCOME, "系统发放"),
+
+    SEND_PACKET(2, WalletTransTypeEnum.EXPENSES, "发红包"),
+
+    RECEIVE_PACKET(3, WalletTransTypeEnum.INCOME, "收红包"),
+
+    JOIN_LOTTERY(4, WalletTransTypeEnum.EXPENSES, "参与抽奖"),
+
+    ;
+
+    private Integer transEvent;
+
+    private WalletTransTypeEnum transType;
+
+    private String transEventName;
+
+
+    public static String getEventName(Integer transEvent) {
+        for (WalletTransEventEnum event : WalletTransEventEnum.values()) {
+            if (event.transEvent.equals(transEvent)) {
+                return event.transEventName;
+            }
+        }
+        return "未知";
+    }
+}

+ 23 - 0
webchat-common/src/main/java/com/webchat/common/enums/WalletTransTypeEnum.java

@@ -0,0 +1,23 @@
+package com.webchat.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author 程序员七七, https://www.coderutil.com网站作者
+ * @date 2024/11/9 03:53
+ */
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum WalletTransTypeEnum {
+
+    INCOME(1, "收入"),
+
+    EXPENSES(-1, "支出");
+
+    private Integer transType;
+
+    private String transTypeName;
+}

+ 23 - 0
webchat-common/src/main/java/com/webchat/common/exception/AsyncException.java

@@ -0,0 +1,23 @@
+package com.webchat.common.exception;
+
+/***
+ * 异常处理异常
+ */
+public class AsyncException extends RuntimeException {
+
+    public AsyncException() {
+        super();
+    }
+
+    public AsyncException(String s) {
+        super(s);
+    }
+
+    public AsyncException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public AsyncException(Throwable cause) {
+        super(cause);
+    }
+}

+ 36 - 0
webchat-common/src/main/java/com/webchat/common/exception/AuthException.java

@@ -0,0 +1,36 @@
+package com.webchat.common.exception;
+
+import com.webchat.common.enums.APIErrorCommonEnum;
+
+/***
+ * 认证失败异常
+ */
+public class AuthException extends RuntimeException {
+
+    private int code = APIErrorCommonEnum.UN_AUTH.getCode();
+
+    public AuthException() {
+        super();
+    }
+
+    public AuthException(String s) {
+        super(s);
+    }
+
+    public AuthException(int code, String s) {
+        super(s);
+        this.code = code;
+    }
+
+    public AuthException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public AuthException(Throwable cause) {
+        super(cause);
+    }
+    
+    public int getCode() {
+        return code;
+    }
+}

+ 34 - 0
webchat-common/src/main/java/com/webchat/common/exception/BusinessException.java

@@ -0,0 +1,34 @@
+package com.webchat.common.exception;
+
+import com.webchat.common.enums.APIErrorCommonEnum;
+
+public class BusinessException extends RuntimeException {
+
+    private Integer code = 500;
+
+    public BusinessException() {
+        super();
+    }
+
+    public BusinessException(String msg) {
+        super(msg);
+    }
+
+    public BusinessException(APIErrorCommonEnum apiErrorCommonEnum) {
+        super(apiErrorCommonEnum.getMessage());
+        this.code = apiErrorCommonEnum.getCode();
+    }
+
+    public BusinessException(Integer code, String msg) {
+        super(msg);
+        this.code = code;
+    }
+
+    public BusinessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+}

+ 34 - 0
webchat-common/src/main/java/com/webchat/common/exception/NotFoundException.java

@@ -0,0 +1,34 @@
+package com.webchat.common.exception;
+
+import com.webchat.common.enums.APIErrorCommonEnum;
+
+public class NotFoundException extends RuntimeException {
+
+    private Integer code = 404;
+
+    public NotFoundException() {
+        super();
+    }
+
+    public NotFoundException(String msg) {
+        super(msg);
+    }
+
+    public NotFoundException(APIErrorCommonEnum apiErrorCommonEnum) {
+        super(apiErrorCommonEnum.getMessage());
+        this.code = apiErrorCommonEnum.getCode();
+    }
+
+    public NotFoundException(Integer code, String msg) {
+        super(msg);
+        this.code = code;
+    }
+
+    public NotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+}

+ 34 - 0
webchat-common/src/main/java/com/webchat/common/exception/ParamException.java

@@ -0,0 +1,34 @@
+package com.webchat.common.exception;
+
+import com.webchat.common.enums.APIErrorCommonEnum;
+
+public class ParamException extends RuntimeException {
+
+    private Integer code = 444;
+
+    public ParamException() {
+        super();
+    }
+
+    public ParamException(String msg) {
+        super(msg);
+    }
+
+    public ParamException(APIErrorCommonEnum apiErrorCommonEnum) {
+        super(apiErrorCommonEnum.getMessage());
+        this.code = apiErrorCommonEnum.getCode();
+    }
+
+    public ParamException(Integer code, String msg) {
+        super(msg);
+        this.code = code;
+    }
+
+    public ParamException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+}

+ 134 - 0
webchat-common/src/main/java/com/webchat/common/helper/SessionHelper.java

@@ -0,0 +1,134 @@
+package com.webchat.common.helper;
+
+import com.webchat.common.constants.CookieConstants;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/***
+ * @Author 程序员七七 ====> https://www.coderutil.com 网站作者
+ */
+public class SessionHelper {
+
+    /**
+     * thread local实体类
+     */
+    private static ThreadLocal<ConcurrentHashMap<String, Object>> threadLocalHolder = new ThreadLocal<>();
+
+    /**
+     * IP
+     */
+    private static final String SESSION_KEY_IP = "CURRENT-IP";
+
+    /**
+     * 请求UA信息KEY
+     */
+    private static final String SESSION_KEY_USER_AGENT = "User-Agent";
+
+    /**
+     * TraceId
+     */
+    private static final String SESSION_KEY_TRACE_ID = "TRACE-ID";
+
+    private static final String SESSION_KEY_TASK_ID = "TASK-ID";
+
+
+    public static void setClientUserInfo(String userId, String ip, String taskId) {
+        setUserId(userId);
+        setClientIP(ip);
+        setTaskId(taskId);
+    }
+
+    /**
+     * 设置登录用户,谨慎使用!会覆盖原来的userId,一般在拦截器里使用。
+     */
+    public static void setUserId(String userId) {
+        if (StringUtils.isNotBlank(userId)) {
+            setAttribute(CookieConstants.C_U_USER_COOKIE_KEY, userId);
+        }
+    }
+
+    /***
+     * 设置客户端IP
+     * @param ip
+     */
+    public static void setClientIP(String ip) {
+        if (StringUtils.isNotBlank(ip)) {
+            setAttribute(SESSION_KEY_IP, ip);
+        }
+    }
+
+    /***
+     * 设置TraceId
+     * @param traceId
+     */
+    public static void setTraceId(String traceId) {
+        if (StringUtils.isNotBlank(traceId)) {
+            setAttribute(SESSION_KEY_TRACE_ID, traceId);
+        }
+    }
+
+    /***
+     * 设置任务ID
+     * @param taskId
+     */
+    public static void setTaskId(String taskId) {
+        if (StringUtils.isNotBlank(taskId)) {
+            setAttribute(SESSION_KEY_TASK_ID, taskId);
+        }
+    }
+
+    /**
+     * 获取当前登录USER ID
+     * @return
+     */
+    public static String getCurrentUserId() {
+        Object userIdObj = getAttribute(CookieConstants.C_U_USER_COOKIE_KEY);
+        return userIdObj == null ? null : userIdObj.toString();
+    }
+
+    /***
+     * 获取客户端IP
+     * @return
+     */
+    public static String getCurrentClientIP() {
+        Object userIdObj = getAttribute(SESSION_KEY_IP);
+        return userIdObj == null ? null : userIdObj.toString();
+    }
+
+    /***
+     * 获取客户端UA信息
+     * @return
+     */
+    public static String getCurrentUserAgent() {
+        Object userIdObj = getAttribute(SESSION_KEY_USER_AGENT);
+        return userIdObj == null ? null : userIdObj.toString();
+    }
+
+    /**
+     * 清除本线程保存的数据,不要设置为null,会有内存泄漏
+     */
+    public static void clear() {
+        threadLocalHolder.remove();
+    }
+
+    /**
+     * 获取本地的session,只是一个线程的操作,不会出现多线程问题。
+     * @return
+     */
+    private static Map<String, Object> getLocalSession() {
+        if (threadLocalHolder.get() == null) {
+            threadLocalHolder.set(new ConcurrentHashMap<>());
+        }
+        return threadLocalHolder.get();
+    }
+
+    private static void setAttribute(String key, Object value) {
+        getLocalSession().put(key, value);
+    }
+
+    private static Object getAttribute(String key) {
+        return getLocalSession().get(key);
+    }
+}

+ 108 - 0
webchat-common/src/main/java/com/webchat/common/helper/SseEmitterHelper.java

@@ -0,0 +1,108 @@
+package com.webchat.common.helper;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2024/10/29 23:27
+ * @description
+ */
+@Slf4j
+public class SseEmitterHelper {
+
+    /**
+     * 维护用户对话的SSE
+     */
+    private static ConcurrentHashMap<String, ConcurrentHashMap<String, SseEmitter>> sseEmitterMap = new ConcurrentHashMap<>();
+
+    /**
+     * 获取用户的 SseEmitter 对象,如果不存在重新创建一个
+     *
+     * @param userId
+     * @return
+     */
+    public static SseEmitter get(String biz, String userId) {
+        ConcurrentHashMap<String, SseEmitter> userSseEmitter = sseEmitterMap.get(biz);
+        if (userSseEmitter == null) {
+            userSseEmitter = new ConcurrentHashMap<>();
+        }
+        SseEmitter sseEmitter = userSseEmitter.get(userId);
+        if (sseEmitter == null) {
+            sseEmitter = create(biz, userId);
+        }
+        return sseEmitter;
+    }
+
+    /**
+     * 删除用户 SseEmitter 对象
+     *
+     * @param userId
+     */
+    public static void remove(String biz, String userId) {
+        ConcurrentHashMap<String, SseEmitter> userSseEmitter = sseEmitterMap.get(biz);
+        userSseEmitter.remove(userId);
+    }
+
+    /**
+     * 创建SseEmitter
+     *
+     * @param userId
+     * @return
+     */
+    private static SseEmitter create(String biz, String userId) {
+        SseEmitter sseEmitter = new SseEmitter();
+        ConcurrentHashMap<String, SseEmitter> userSseEmitter = sseEmitterMap.get(biz);
+        if (userSseEmitter == null) {
+            userSseEmitter = new ConcurrentHashMap<>();
+        }
+        userSseEmitter.put(userId, sseEmitter);
+        sseEmitterMap.put(biz, userSseEmitter);
+        sseEmitter.onCompletion(completionCallBack(biz, userId));
+        sseEmitter.onError(errorCallBack(biz, userId));
+        sseEmitter.onTimeout(timeoutCallBack(biz, userId));
+        log.info("创建新链接=====> biz={}, userId={}", biz, userId);
+        return sseEmitter;
+    }
+
+    private static Runnable completionCallBack(String biz, String userId) {
+        return () -> {
+            log.info("结束连接=====> userId={}", userId);
+            remove(biz, userId);
+        };
+    }
+    private static Runnable timeoutCallBack(String biz, String userId){
+        return ()->{
+            log.info("连接超时=====> userId={}", userId);
+            remove(biz, userId);
+        };
+    }
+    private static Consumer<Throwable> errorCallBack(String biz, String userId){
+        return throwable -> {
+            log.info("连接失败=====> userId={}", userId);
+            remove(biz, userId);
+        };
+    }
+
+    /**
+     * sse 消息推送
+     *
+     * @param biz
+     * @param userId
+     * @param message
+     */
+    public static void send(String biz, String userId, String message) {
+        try {
+            SseEmitter sseEmitter = get(biz, userId);
+            sseEmitter.send(message);
+            sseEmitter.send("finished");
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}

+ 1065 - 0
webchat-common/src/main/java/com/webchat/common/service/RedisService.java

@@ -0,0 +1,1065 @@
+package com.webchat.common.service;
+
+import com.alibaba.nacos.common.utils.MapUtil;
+import com.webchat.common.exception.BusinessException;
+import com.webchat.common.util.JsonUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.util.SafeEncoder;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis 工具类
+ */
+@Slf4j
+@Service
+public class RedisService {
+
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    /**
+     * get取值
+     *
+     * @param key
+     *
+     * @return
+     */
+    public String get(final String key) {
+        String result = null;
+        try {
+            ValueOperations<String, String> operations = redisTemplate.opsForValue();
+            result = operations.get(key);
+        } catch (Exception e) {
+            log.error("redis get error! key:{}", key, e);
+        }
+        return result;
+    }
+
+    /***
+     * 批量Get
+     * @param keys
+     *
+     * [1,2,3]
+     * [u1,null,u3]
+     *
+     * @return
+     */
+    public List<String> mget(final List<String> keys) {
+        List<String> result = new ArrayList<>();
+        try {
+            ValueOperations<String, String> operations = this.redisTemplate.opsForValue();
+            return operations.multiGet(keys);
+        } catch (Exception e) {
+            log.error("redis get error! key:{}", JsonUtil.toJsonString(keys), e);
+        }
+        return result;
+    }
+
+    public Map<String, String> mgetAndParseMap(final List<String> keys) {
+        Map<String, String> resultMap = new HashMap<>(keys.size());
+        try {
+            List<String> result = this.mget(keys);
+            for (int i = 0; i < keys.size(); i++) {
+                resultMap.put(keys.get(i), result.get(i));
+            }
+            return resultMap;
+        } catch (Exception e) {
+            log.error("redis get error! key:{}", JsonUtil.toJsonString(keys), e);
+        }
+        return resultMap;
+    }
+
+    /***
+     * 获取定时任务的锁, liveTime毫秒
+     * @param key
+     * @param requestId
+     * @param liveTime
+     * @return
+     */
+    public boolean installLockForMS(String key, String requestId, long liveTime) {
+        if (org.springframework.util.StringUtils.isEmpty(key) || org.springframework.util.StringUtils.isEmpty(requestId)) {
+            return false;
+        }
+        return setNxPx(key, requestId, liveTime);
+    }
+
+    /***
+     * @param key
+     * @param value
+     * @param exptime
+     * @return
+     *
+     * 1 ---> setnx(key, none , 2000) return true;
+     * 2 ---> setnx return false;
+     */
+
+    public boolean setNxPx(final String key, final String value, final long exptime) {
+        Boolean result = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
+            RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
+            RedisSerializer keySerializer = redisTemplate.getKeySerializer();
+            Object obj = connection.execute("set", keySerializer.serialize(key),
+                    valueSerializer.serialize(value),
+                    SafeEncoder.encode("NX"),
+                    SafeEncoder.encode("PX"),
+                    Protocol.toByteArray(exptime));
+            return obj != null && "OK".equals(new String((byte[]) obj));
+        });
+        return result;
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @param exptime
+     *
+     * @return
+     */
+    public boolean setNxEx(final String key, final String value, final long exptime) {
+        if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
+            return false;
+        }
+        Boolean result = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
+            RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
+            RedisSerializer keySerializer = redisTemplate.getKeySerializer();
+            Object obj = connection.execute("set", keySerializer.serialize(key),
+                    valueSerializer.serialize(value),
+                    SafeEncoder.encode("NX"),
+                    SafeEncoder.encode("EX"),
+                    Protocol.toByteArray(exptime));
+            return obj != null && "OK".equals(new String((byte[]) obj));
+        });
+        return result;
+    }
+
+    /**
+     * 带有过期时间的set传值
+     *
+     * @param key
+     * @param value
+     *
+     * @return
+     */
+    public boolean set(final String key, final String value, long expireTime) {
+        boolean result = false;
+        try {
+            ValueOperations<String, String> operations = redisTemplate.opsForValue();
+            if (expireTime > 0) {
+                operations.set(key, value, expireTime, TimeUnit.SECONDS);
+            } else {
+                operations.set(key, value);
+            }
+            result = true;
+        } catch (Exception e) {
+            log.error("redis set error! key:{}, value:{}", key, value, e);
+        }
+        return result;
+    }
+
+    /**
+     * set传值
+     *
+     * @param key
+     * @param value
+     *
+     * @return
+     */
+    public boolean set(final String key, String value) {
+        boolean result = false;
+        try {
+            ValueOperations<String, String> operations = redisTemplate.opsForValue();
+            operations.set(key, value);
+            result = true;
+        } catch (Exception e) {
+            log.error("redis set error! key:{}, value:{}", key, value);
+        }
+        return result;
+    }
+
+    /**
+     * @param key
+     * @param value
+     *
+     * @return
+     */
+    public boolean sadd(String key, String... value) {
+        boolean result = false;
+        try {
+            SetOperations<String, String> set = redisTemplate.opsForSet();
+            set.add(key, value);
+            result = true;
+        } catch (Exception e) {
+            log.error("redis set error! key:{}, value:{}", key, value, e);
+        }
+        return result;
+    }
+
+    /**
+     * @param key
+     * @param setValue
+     */
+    public void sadd(String key, Set<String> setValue) {
+        try {
+            SetOperations<String, String> set = redisTemplate.opsForSet();
+            set.add(key, setValue.toArray(new String[0]));
+        } catch (Exception e) {
+            log.error("sadd error! key:{}", key, e);
+        }
+    }
+
+    /**
+     * @param key
+     *
+     * @return
+     */
+    public Set<String> smembers(String key) {
+        try {
+            SetOperations<String, String> set = redisTemplate.opsForSet();
+            return set.members(key);
+        } catch (Exception e) {
+            log.error("redis set error! key:{}", key, e);
+            return Collections.emptySet();
+        }
+    }
+
+    public Long ssize(String key) {
+        Long size = null;
+        try {
+            size = redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            log.error("redis ssize error! key:{}", key, e);
+        }
+        return size;
+    }
+
+    /**
+     * @param key
+     * @param values
+     *
+     * @return
+     */
+    public Long sremove(String key, Object... values) {
+        try {
+            SetOperations<String, String> set = redisTemplate.opsForSet();
+            return set.remove(key, values);
+        } catch (Exception e) {
+            log.error("redis set error! key:{}", key, e);
+            return null;
+        }
+    }
+
+    /**
+     * 元素是否在set集合中
+     * @param key
+     * @param value
+     * @return
+     */
+    public Boolean sIsMember(String key, String value) {
+        try {
+            SetOperations<String, String> set = redisTemplate.opsForSet();
+            return set.isMember(key, value);
+        } catch (Exception ex) {
+            log.error("redis sIsMember error! key:{}", key, ex);
+            return false;
+        }
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @param score
+     *
+     * @return
+     */
+    public boolean zadd(String key, String value, long score) {
+        boolean result = false;
+        try {
+            ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+            zset.add(key, value, score);
+            result = true;
+        } catch (Exception e) {
+            log.error("redis set error! key:{}, value:{}", key, value, e);
+        }
+        return result;
+    }
+
+    /***
+     * 判断zset中是否包含元素
+     * @param key
+     * @param value
+     * @return
+     */
+    public boolean zContains(String key, String value) {
+        try {
+            ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+            Long rank = zset.rank(key, value);
+            return rank != null && rank >= 0;
+        } catch (Exception e) {
+            log.error("redis zContains error! key:{}, value:{}", key, value, e);
+        }
+        return false;
+    }
+
+
+    /**
+     * @param key
+     * @param value
+     * @param score
+     * @param liveTime
+     *
+     * @return
+     */
+    public boolean zadd(String key, String value, long score, long liveTime) {
+        boolean result = false;
+        try {
+            ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+            zset.add(key, value, score);
+            if (liveTime > 0) {
+                expire(key, liveTime);
+            }
+            result = true;
+        } catch (Exception e) {
+            log.error("redis set error! key:{}, value:{}", key, value, e);
+        }
+        return result;
+    }
+
+    /**
+     * @param key
+     * @param tuples
+     * @param liveTime
+     *
+     * @return
+     */
+    public boolean zadd(String key, Set<ZSetOperations.TypedTuple<String>> tuples, long liveTime) {
+        boolean result = false;
+        try {
+            ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+            zset.add(key, tuples);
+            if (liveTime > 0) {
+                expire(key, liveTime);
+            }
+            result = true;
+        } catch (Exception e) {
+            log.error("redis set error! key:{}, value:{}", key, tuples, e);
+        }
+        return result;
+    }
+
+    /**
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<String> zrange(String key, long start, long end) {
+        ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+        try {
+            Set<String> result = zset.range(key, start, end - 1L);
+            return result;
+        } catch (Exception e) {
+            log.error("redis zrange error,key:{}", key, e);
+        }
+        return null;
+    }
+
+    /***
+     * 获取Score
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long zscore(String key, String value) {
+        Double score = redisTemplate.opsForZSet().score(key, value);
+        if (score == null) {
+            return 0L;
+        }
+        return score.longValue();
+    }
+
+    /**
+     * @param key
+     * @param start
+     * @param end
+     *
+     * @return
+     */
+    public Set<String> zreverseRange(String key, long start, long end) {
+        ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+        try {
+            Set<String> result = zset.reverseRange(key, start, end - 1L);
+            return result;
+        } catch (Exception e) {
+            log.error("redis zrange error,errorMessage:{}", e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * 获取ZSet分数区间中前几位的元素
+     * @param key
+     * @param maxScore
+     * @param offset
+     * @param count
+     * @return
+     */
+    public Set<String> zreverseRangeByScore(String key, double maxScore, long offset, long count) {
+        ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+        try {
+            Set<String> result = zset.reverseRangeByScore(key, 1 - Double.MAX_VALUE, maxScore, offset, count);
+            return result;
+        } catch (Exception e) {
+            log.error("redis zrangeByScoreWithScores error,errorMessage:{}",
+                    e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * 获取ZSet分数区间中前几位的元素
+     * @param key
+     * @param minScore
+     * @param maxScore
+     * @param offset
+     * @param count
+     * @return
+     */
+    public Set<String> zreverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {
+        ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+        try {
+            Set<String> result = zset.reverseRangeByScore(key, minScore, maxScore, offset, count);
+            return result;
+        } catch (Exception e) {
+            log.error("redis zrangeByScoreWithScores error,errorMessage:{}",
+                    e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * 获取ZSet中所有元素总数
+     * @param key
+     */
+    public long zSetSize(String key) {
+        if (StringUtils.isEmpty(key)) {
+            return 0;
+        }
+        try {
+            return redisTemplate.opsForZSet().size(key);
+        } catch (Exception ex) {
+            log.error("zSetSize error", ex);
+            return 0;
+        }
+    }
+
+    /**
+     * 获取ZSet中所有分数区间元素总数
+     * @param key
+     */
+    public long zSetCount(String key, double minScore, double maxScore) {
+        if (StringUtils.isEmpty(key)) {
+            return 0;
+        }
+        try {
+            return redisTemplate.opsForZSet().count(key, minScore, maxScore);
+        } catch (Exception ex) {
+            log.error("zSetCount error", ex);
+            return 0;
+        }
+    }
+
+    /**
+     * 获取ZSet中所有元素
+     * @param key
+     */
+    public Set<String> zSetGetAll(String key) {
+        if (StringUtils.isEmpty(key)) {
+            return null;
+        }
+        try {
+            return redisTemplate.opsForZSet().reverseRange(key, 0, -1);
+        } catch (Exception ex) {
+            log.error("zSetGetAll error", ex);
+            return null;
+        }
+    }
+
+
+    /**
+     * @param key
+     * @param start
+     * @param end
+     *
+     * @return
+     */
+    public Set<String> zrangeByScore(String key, long start, long end) {
+        ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+        try {
+            Set<String> result = zset.rangeByScore(key, start, end);
+            return result;
+        } catch (Exception e) {
+            log.error("redis zrangeByScore error,errorMessage:{}", e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * 按照分数范围删除
+     *
+     * @param key
+     * @param start
+     * @param end
+     */
+    public void zremoveRangeByScore(String key, long start, long end) {
+        ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+        try {
+            zset.removeRangeByScore(key, start, end);
+        } catch (Exception e) {
+            log.error("redis zremoveRangeByScore error,errorMessage:{}", e.getMessage());
+        }
+    }
+
+    /**
+     * 删除ZSet中某一元素
+     * @param key
+     * @param value
+     */
+    public void zremove(String key, String value) {
+        if (StringUtils.isEmpty(key)) {
+            return;
+        }
+        try {
+            redisTemplate.opsForZSet().remove(key, value);
+        } catch (Exception ex) {
+            log.error("zSetAdd error", ex);
+        }
+    }
+
+    /**
+     * Zset取交集
+     * {key} 与 {otherKey} 交集赋值给 {destKey}
+     */
+    public void zintersectAndStore(String key, String otherKey, String destKey) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(otherKey) || StringUtils.isEmpty(destKey)) {
+            return;
+        }
+        try {
+            redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey);
+        } catch (Exception ex) {
+            log.error("zSetAdd error", ex);
+        }
+    }
+
+    /**
+     * Zset取交集(使用lua的方式)
+     * {key} 与 {otherKey} 交集赋值给 {destKey},分数取最小的值
+     * @param key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long zinterstoreMin(String key, String otherKey, String destKey) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(otherKey) || StringUtils.isEmpty(destKey)) {
+            return null;
+        }
+        List<String> keys = Arrays.asList(destKey, key, otherKey);
+        String script = " return redis.call('zinterstore', KEYS[1], '2', KEYS[2], KEYS[3], 'aggregate', 'min') ";
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
+        try {
+            Object res = redisTemplate.execute(redisScript, keys);
+            return (Long) res;
+        } catch (Exception ex) {
+            log.error("zinterstoreMin error, key:{}, otherKey:{}, destKey:{}", key, otherKey, destKey, ex);
+        }
+        return null;
+    }
+
+    /**
+     * zset复制数据
+     * 将{key}的数据复制到{destKey}上
+     * @param key
+     * @param destKey
+     * @return
+     */
+    public Long zunionstore(String key, String destKey) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(destKey)) {
+            return null;
+        }
+        List<String> keys = Arrays.asList(destKey, key);
+        String script = " return redis.call('zunionstore', KEYS[1], '1', KEYS[2]) ";
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
+        try {
+            Object res = redisTemplate.execute(redisScript, keys);
+            return (Long) res;
+        } catch (Exception ex) {
+            log.error("zunionstore error, key:{}, destKey:{}", key, destKey, ex);
+        }
+        return null;
+    }
+
+
+    /**
+     * @param key
+     *
+     * @return
+     */
+    public Long zsize(String key) {
+        ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
+        try {
+            Long result = zset.size(key);
+            return result;
+        } catch (Exception e) {
+            log.error("redis zrange error,errorMessage:{}", e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * hset
+     *
+     * @param key
+     * @param field
+     * @param value
+     *
+     * @return
+     */
+    public boolean hset(String key, String field, String value, long... expire) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field) || StringUtils.isEmpty(value)) {
+            return false;
+        }
+        boolean result = false;
+        try {
+            final byte[] rawKey = redisTemplate.getStringSerializer().serialize(key);
+            final byte[] rawField = redisTemplate.getStringSerializer().serialize(field);
+            final byte[] rawValue = redisTemplate.getStringSerializer().serialize(value);
+            result = redisTemplate.execute(connection -> {
+                boolean ret = connection.hSet(rawKey, rawField, rawValue);
+                if (expire.length > 0 && expire[0] > 0) {
+                    connection.expire(rawKey, expire[0]);
+                }
+                return ret;
+            }, true);
+        } catch (Exception ex) {
+            log.error("hset error", ex);
+        }
+        return result;
+    }
+
+    /**
+     * hget
+     *
+     * @param key
+     * @param field
+     *
+     * @return
+     */
+    public String hget(String key, String field) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
+            return null;
+        }
+        final byte[] rawKey = redisTemplate.getStringSerializer().serialize(key);
+        final byte[] rawField = redisTemplate.getStringSerializer().serialize(field);
+        final byte[] rawValue = redisTemplate.execute(connection -> connection.hGet(rawKey, rawField), true);
+        return redisTemplate.getStringSerializer().deserialize(rawValue);
+    }
+
+    /**
+     * @param key
+     * @param field
+     *
+     * @return
+     */
+    public Long hdel(String key, String field) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
+            return null;
+        }
+        long result = redisTemplate.opsForHash().delete(key, field);
+        return result;
+    }
+
+    /**
+     * @param key
+     * @param queryFields
+     *
+     * @return
+     */
+    public List<String> hmget(String key, List<String> queryFields) {
+        HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
+        return hashOperations.multiGet(key, queryFields);
+    }
+
+    /**
+     * @param key
+     * @param map
+     *
+     * @return
+     */
+    public void hmSet(String key, Map<Long, Long> map) {
+        if (StringUtils.isEmpty(key) || MapUtil.isEmpty(map)) {
+            return;
+        }
+        redisTemplate.opsForHash().putAll(key, map);
+    }
+
+    /**
+     * @param key
+     * @param map
+     */
+    public void hmSet(String key, Map<String, String> map, long... expire) {
+        if (StringUtils.isEmpty(key) || MapUtil.isEmpty(map)) {
+            return;
+        }
+        redisTemplate.opsForHash().putAll(key, map);
+    }
+
+    /**
+     * @param key
+     * @param field
+     *
+     * @return
+     */
+    public Long hincrex(String key, String field) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
+            return null;
+        }
+        Long result = redisTemplate.opsForHash().increment(key, field, 1);
+        return result;
+    }
+
+    public Long hIncrementVal(String key, String field, long val) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
+            return null;
+        }
+        Long result = redisTemplate.opsForHash().increment(key, field, val);
+        return result;
+    }
+
+    /**
+     * @param key
+     * @param field
+     *
+     * @return
+     */
+    public Long hdecrex(String key, String field) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
+            return null;
+        }
+        Long result = redisTemplate.opsForHash().increment(key, field, -1);
+        return result;
+    }
+
+    public void lleftPush(String key, String value) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
+            return;
+        }
+        redisTemplate.opsForList().leftPush(key, value);
+    }
+
+    public void lleftPushAll(String key, Collection values) {
+        if (StringUtils.isEmpty(key) || CollectionUtils.isEmpty(values)) {
+            return;
+        }
+        redisTemplate.opsForList().leftPushAll(key, values);
+    }
+
+    public void lrightPush(String key, String value) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
+            return;
+        }
+        redisTemplate.opsForList().rightPush(key, value);
+    }
+
+    public void lrightPushAll(String key, List<String> valueList) {
+        if (StringUtils.isEmpty(key) || CollectionUtils.isEmpty(valueList)) {
+            return;
+        }
+        redisTemplate.opsForList().rightPushAll(key, valueList);
+    }
+
+    public void lleftPushAll(String key, List<String> valueList) {
+        if (StringUtils.isEmpty(key) || CollectionUtils.isEmpty(valueList)) {
+            return;
+        }
+        redisTemplate.opsForList().leftPushAll(key, valueList);
+    }
+
+    public Long lsize(String key) {
+        if (StringUtils.isEmpty(key)) {
+            return null;
+        }
+        return redisTemplate.opsForList().size(key);
+    }
+
+    /**
+     * @param key
+     * @param start
+     * @param end
+     */
+    public List<String> lrange(String key, long start, long end) {
+        if (StringUtils.isEmpty(key)) {
+            return null;
+        }
+        return redisTemplate.opsForList().range(key, start, end);
+    }
+
+    /**
+     * @param key
+     *
+     * @return
+     */
+    public String lleftPop(String key) {
+        if (StringUtils.isEmpty(key)) {
+            return null;
+        }
+        return redisTemplate.opsForList().leftPop(key);
+    }
+
+    /**
+     * @param key
+     * @param time
+     * @param timeUnit
+     * @return
+     */
+    public String lleftPop(String key, long time, TimeUnit timeUnit) {
+        if (StringUtils.isEmpty(key)) {
+            return null;
+        }
+        try {
+            return redisTemplate.opsForList().leftPop(key, time, timeUnit);
+        } catch (Exception e) {
+            log.error("redis lleftPop error! key:{}", key, e);
+        }
+        return null;
+    }
+
+    /**
+     * @param key
+     * @param time
+     * @param timeUnit
+     * @return
+     */
+    public String lrightPop(String key, long time, TimeUnit timeUnit) {
+        if (StringUtils.isEmpty(key)) {
+            return null;
+        }
+        try {
+            return redisTemplate.opsForList().rightPop(key, time, timeUnit);
+        } catch (Exception e) {
+            log.error("redis lrightPop error! key:{}", key, e);
+        }
+        return null;
+    }
+
+    /**
+     * 带失效时间的值原子自增
+     *
+     * @param key
+     * @param sec
+     *
+     * @return
+     */
+    public Long increx(String key, long sec) {
+        Long result = null;
+        try {
+            ValueOperations<String, String> operations = redisTemplate.opsForValue();
+            result = operations.increment(key, 1);
+            // 第一次设置,设置有效时间
+            if (result == 1) {
+                expire(key, sec);
+            }
+        } catch (Exception e) {
+            log.error("redis get error! key:{}", key, e);
+        }
+        return result;
+    }
+
+    /**
+     * 带失效时间的值原子自减
+     *
+     * @param key
+     * @param sec
+     *
+     * @return
+     */
+    public Long decrex(String key, long sec) {
+        Long result = null;
+        try {
+            ValueOperations<String, String> operations = redisTemplate.opsForValue();
+            result = operations.increment(key, -1);
+            // 第一次设置,设置有效时间
+            if (result == -1) {
+                expire(key, sec);
+            }
+        } catch (Exception e) {
+            log.error("redis get error! key:{}", key, e);
+        }
+        return result;
+    }
+
+    /**
+     * 获取定时任务的锁(非集群方式)
+     *
+     * @param key       键值
+     * @param requestId 锁ID,通过此来判断是哪个实例的锁
+     * @param liveTime  过期时间
+     *
+     * @return
+     */
+    public boolean installDistributedLock(String key, String requestId, long liveTime) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(requestId)) {
+            return false;
+        }
+        return setNxEx(key, requestId, liveTime);
+    }
+
+    /**
+     * 释放定时任务的锁
+     *
+     * @param key       键值
+     * @param requestId 锁ID
+     */
+    public void releaseDistributedLock(String key, String requestId) {
+        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(requestId)) {
+            return;
+        }
+        try {
+            String redisId = redisTemplate.opsForValue().get(key);
+            // 保证只解自己拿到的锁
+            if (requestId.equals(redisId)) {
+                redisTemplate.delete(key);
+            }
+        } catch (Exception e) {
+            log.error("release distributed lock error,requestId:{},errorMessage:{}",
+                    requestId, e.getMessage());
+            throw new BusinessException("release distributed lock error");
+        }
+    }
+
+    /**
+     * 删除对应的value
+     *
+     * @param key
+     */
+    public void remove(final String key) {
+        redisTemplate.delete(key);
+    }
+
+    /**
+     * 判断缓存中是否有对应的value
+     *
+     * @param key
+     *
+     * @return
+     */
+    public Boolean exists(final String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 设置过期时间
+     *
+     * @param key
+     * @param livetime
+     */
+    public void expire(String key, long livetime) {
+        redisTemplate.expire(key, livetime, TimeUnit.SECONDS);
+    }
+
+    /***
+     * 求两个set的交集
+     */
+    public Set<String> intersect(String key1, String key2) {
+        try {
+            SetOperations<String, String> set = redisTemplate.opsForSet();
+            return set.intersect(key1, key2);
+        } catch (Exception e) {
+            log.error("redis intersect error! key1:{}, key2:{}", key1, key2, e);
+            return Collections.emptySet();
+        }
+    }
+
+    /***
+     * 求两个set的并集
+     */
+    public Set<String> union(String key1, String key2) {
+        try {
+            SetOperations<String, String> set = redisTemplate.opsForSet();
+            return set.union(key1, key2);
+        } catch (Exception e) {
+            log.error("redis union error! key1:{}, key2:{}", key1, key2, e);
+            return Collections.emptySet();
+        }
+    }
+
+    public Long size(String key) {
+        SetOperations<String, String> set = redisTemplate.opsForSet();
+        try {
+            Long result = set.size(key);
+            return result;
+        } catch (Exception e) {
+            log.error("redis size error, key:{}, errorMessage:{}",key, e.getMessage());
+        }
+        return null;
+    }
+
+    /***
+     * 查询剩余存活时间
+     * @param key
+     * @return
+     */
+    public Long getKeysExpireTime(String key) {
+        Set<String> keys = redisTemplate.keys(key);
+        return redisTemplate.opsForValue().getOperations().getExpire(key);
+    }
+
+    /***
+     * 根据前缀查询所有key的剩余存活时间
+     * @return
+     */
+    public Map<String, Long> batchGetKeysExpireTime(String keyPrefix) {
+        Set<String> keys = redisTemplate.keys(keyPrefix + "*");
+        Map<String, Long> keyExpireMap = new HashMap<>();
+        for (String key : keys) {
+            Long expireTime = redisTemplate.opsForValue().getOperations().getExpire(key);
+            keyExpireMap.put(key, expireTime);
+        }
+        return keyExpireMap;
+    }
+
+    /**
+     * @param start
+     * @param end
+     * @return
+     */
+    public Map<Long, Set<DefaultTypedTuple>> zreverseRangeWithScoreByPipelined(List<String> keys,
+                                                                               List<Long> indexs,
+                                                                               long start, long end) {
+        if (CollectionUtils.isEmpty(keys)) {
+            return new HashMap<>();
+        }
+        List<Object> redisResult = redisTemplate.executePipelined((RedisCallback<List<Object>>) connection ->  {
+            for (String key : keys) {
+                byte[] rewKey = redisTemplate.getStringSerializer().serialize(key);
+                connection.zRevRangeWithScores(rewKey, start, end - 1);
+            }
+            return null;
+        });
+        Map<Long, Set<DefaultTypedTuple>> resultMap = new HashMap<>();
+        if (!CollectionUtils.isEmpty(redisResult)) {
+            for (int i = 0; i < indexs.size(); i++) {
+                Set<DefaultTypedTuple> result = (Set<DefaultTypedTuple>) redisResult.get(i);
+                resultMap.put(indexs.get(i), result);
+            }
+        }
+        return resultMap;
+    }
+}

+ 37 - 0
webchat-common/src/main/java/com/webchat/common/util/ArticleUtil.java

@@ -0,0 +1,37 @@
+package com.webchat.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Author: 程序员七七
+ * @Date: 27.3.22 5:21 下午
+ */
+public class ArticleUtil {
+
+    /***
+     * 提取html中所有的图片
+     * @param html
+     * @return
+     */
+    public static List<String> getImagesFromHtml(String html) {
+        List<String> images = new ArrayList<>();
+        if (StringUtils.isBlank(html)) {
+            return images;
+        }
+        Pattern p = Pattern.compile("<img\\b[^>]*\\bsrc\\b\\s*=\\s*('|\")?([^'\"\n\r\f>]+(\\.jpg|\\.bmp|\\.eps|\\.gif|\\.mif|\\.miff|\\.png|\\.tif|\\.tiff|\\.svg|\\.wmf|\\.jpe|\\.jpeg|\\.dib|\\.ico|\\.tga|\\.cut|\\.pic)\\b)[^>]*>", Pattern.CASE_INSENSITIVE);
+        Matcher m = p.matcher(html);
+        String quote;
+        String src;
+        while (m.find()) {
+            quote = m.group(1);
+            src = (quote == null || quote.trim().length() == 0) ? m.group(2).split("\\s+")[0] : m.group(2);
+            images.add(src.replaceAll("&quot;", ""));
+        }
+        return images;
+    }
+}

+ 25 - 0
webchat-common/src/main/java/com/webchat/common/util/AvatarUtil.java

@@ -0,0 +1,25 @@
+package com.webchat.common.util;
+
+import java.util.Random;
+
+/**
+ * @Author: 程序员王七七 https://www.coderutil.com 网站作者
+ * @Date: 2021-7-13 0013 23:42
+ * @Description: 无描述信息
+ */
+
+public class AvatarUtil {
+
+
+    private static final String PHOTO_PREFIX = "/image/photo/%s.png";
+
+    /**
+     * 随机获取一个头像
+     *
+     * @return
+     */
+    public static String getRandomAvatar() {
+        int name = new Random().nextInt(9) + 1;
+        return String.format(PHOTO_PREFIX, name);
+    }
+}

+ 31 - 0
webchat-common/src/main/java/com/webchat/common/util/CommonUtils.java

@@ -0,0 +1,31 @@
+package com.webchat.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class CommonUtils {
+
+    // 手机号码前三后四脱敏
+    public static String mobileEncrypt(String mobile) {
+        if (StringUtils.isEmpty(mobile) || (mobile.length() != 11)) {
+            return mobile;
+        }
+        return mobile.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
+    }
+
+    //身份证前三后四脱敏
+    public static String idEncrypt(String id) {
+        if (StringUtils.isEmpty(id) || (id.length() < 8)) {
+            return id;
+        }
+        return id.replaceAll("(?<=\\w{3})\\w(?=\\w{4})", "*");
+    }
+
+    //护照前2后3位脱敏,护照一般为8或9位
+    public static String idPassport(String id) {
+        if (StringUtils.isEmpty(id) || (id.length() < 8)) {
+            return id;
+        }
+        return id.substring(0, 2) + new String(new char[id.length() - 5]).replace("\0", "*") + id.substring(id.length() - 3);
+    }
+
+}

+ 665 - 0
webchat-common/src/main/java/com/webchat/common/util/DateUtils.java

@@ -0,0 +1,665 @@
+package com.webchat.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.*;
+
+/**
+ * 日期的工具类
+ */
+public final class DateUtils {
+
+    public static final String YYYY_MM_DD_T_HH_MM_SS_Z = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+    public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
+    public static final String YYYY_MM_DD = "yyyy-MM-dd";
+    public static final String YYYY_MM = "yyyy-MM";
+    public static final String YYYY = "yyyy";
+    public static final String MM_DD_HH_MM_SS = "MM-dd HH:mm:ss";
+
+    public static final String YYYYMMDDHHMMSSSS = "yyyyMMddHHmmssSS";
+    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+    public static final String YYYYMMDDHHMM = "yyyyMMddHHmm";
+    public static final String YYYYMMDDHH = "yyyyMMddHH";
+    public static final String YYYYMMDD = "yyyyMMdd";
+    public static final String YYMMDD = "yyMMdd";
+    public static final String HHMM = "HH:mm";
+
+    public static final String YYYYMMDDHHMM_SPLASH = "yyyy/MM/dd HH:mm";
+    public static final String MM_DD_YYYY = "MM/dd/yyyy";
+
+    public static final String YYYYMMDD_CN = "yyyy年MM月dd日";
+    public static final String YYYYMMDD_CN_SP = "yyyy 年 MM 月 dd 日";
+    public static final String MM_DD_CN = "M月d日";
+
+    public static final String DATE_BEGIN = " 00:00:00";
+    public static final String DATE_END = " 23:59:59";
+
+    public static String today;
+    public static String yesterday;
+
+    private DateUtils() {
+    }
+
+    public static Date getCurrentDate(String... format) {
+        Date date = new Date();
+
+        if (format != null && format.length > 0) {
+            return getString2Date(getCurrentFormatDate(format[0]));
+        }
+
+        return date;
+    }
+
+    public static Date getYesterdayDate() {
+        return new Date(getCurrentTimeMillis() - 0x5265c00L);
+    }
+
+    public static long getCurrentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    public static String getCurrentFormatDate(String formatDate) {
+        Date date = getCurrentDate();
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formatDate);
+
+        return simpleDateFormat.format(date);
+    }
+
+    public static String getCurrentFormatDate() {
+        Date date = getCurrentDate();
+        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date);
+    }
+
+    public static void resetToday() {
+        String todayStr = getDate2String("yyyy-MM-dd", getCurrentDate());
+
+        if ((today == null) || !today.equals(todayStr)) {
+            today = todayStr;
+            yesterday = getDate2String("yyyy-MM-dd", getYesterdayDate());
+        }
+    }
+
+    public static final String getDate2String(String format, Date date) {
+        if (date != null) {
+            if (StringUtils.isEmpty(format)) {
+                format = YYYY_MM_DD_HH_MM_SS;
+            }
+
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
+
+            return simpleDateFormat.format(date);
+        } else {
+            return "";
+        }
+    }
+
+    public static final String getTodayDateString() {
+
+        return new SimpleDateFormat(YYYY_MM_DD).format(new Date());
+    }
+
+    public static String getDate2String(Date date) {
+        return getDate2String("", date);
+    }
+
+    public static String getLong2ShortString(long l, String formatDate) {
+        Date date = getLong2Date(l);
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formatDate);
+
+        return simpleDateFormat.format(date);
+    }
+
+    public static Date getString2Date(String format, String str) {
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
+        ParsePosition parseposition = new ParsePosition(0);
+
+        return simpleDateFormat.parse(str, parseposition);
+    }
+
+    public static Date getString2Date(String str) {
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(YYYY_MM_DD);
+        ParsePosition parseposition = new ParsePosition(0);
+        return simpleDateFormat.parse(str, parseposition);
+    }
+
+    public static Date getString2Date(String format, Locale locale, String dateStr) {
+        SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
+
+        try {
+            return sdf.parse(dateStr);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    public static Date getLong2Date(long l) {
+        return new Date(l);
+    }
+
+    public static int getOffDays(long l) {
+        return getOffDays(l, getCurrentTimeMillis());
+    }
+
+    public static int getOffDays(long from, long to) {
+        return getOffMinutes(from, to) / 1440;
+    }
+
+    public static int getOffMinutes(long l) {
+        return getOffMinutes(l, getCurrentTimeMillis());
+    }
+
+    public static int getOffMinutes(long from, long to) {
+        return (int) ((to - from) / 60000L);
+    }
+
+    public static String getLastNQuarterBeginDate(int n) {
+        return getLastNQuarterBeginDate(n, new SimpleDateFormat("yyyy-MM-dd"));
+    }
+
+    public static String getLastNQuarterBeginDate(int n, DateFormat format) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+        c.add(Calendar.MONTH, n * 3);
+
+        int q = getQuarter(c.get(Calendar.MONTH));
+
+        c.set(Calendar.MONTH, 3 * (q - 1));
+        c.set(Calendar.DATE, c.getActualMinimum(Calendar.DATE));
+        return format.format(c.getTime());
+    }
+
+    public static String getLastNQuarterEndDate(int n) {
+        return getLastNQuarterEndDate(n, new SimpleDateFormat("yyyy-MM-dd"));
+    }
+
+    private static int getQuarter(int month) {
+        if (month <= 2 && month >= 0) {
+            return 1;
+        }
+        if (month <= 5 && month >= 3) {
+            return 2;
+        }
+        if (month <= 8 && month >= 6) {
+            return 3;
+        }
+        if (month <= 11 && month >= 9) {
+            return 4;
+        }
+        throw new IllegalStateException("月份错误");
+    }
+
+    public static String getLastNQuarterEndDate(int n, DateFormat format) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+        c.add(Calendar.MONTH, n * 3);
+
+        int q = getQuarter(c.get(Calendar.MONTH));
+
+        c.set(Calendar.MONTH, 2 + 3 * (q - 1));
+
+        c.set(Calendar.DATE, c.getMaximum(Calendar.DAY_OF_MONTH));
+
+        return format.format(c.getTime());
+    }
+
+    public static String getLastNMonthBeginDate(int n, DateFormat format) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+        c.add(Calendar.MONTH, n);
+        c.set(Calendar.DATE, c.getMinimum(Calendar.DAY_OF_MONTH));
+        return format.format(c.getTime());
+    }
+
+    public static String getLastNMonthBeginDate(int n) {
+        return getLastNMonthBeginDate(n, new SimpleDateFormat("yyyy-MM-dd"));
+    }
+
+    public static String getLastNMonthEndDate(int n) {
+        return getLastNMonthEndDate(n, new SimpleDateFormat("yyyy-MM-dd"));
+    }
+
+    public static String getLastNMonthEndDate(int n, DateFormat format) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+        c.add(Calendar.MONTH, n);
+        c.set(Calendar.DATE, c.getActualMaximum(Calendar.DAY_OF_MONTH));
+
+        return format.format(c.getTime());
+    }
+
+    public static Date getBeforeNDayDate(long n, Date... date) {
+        Date tmpDate = null;
+        if (date != null && date.length > 0) {
+            tmpDate = date[0];
+        } else {
+            tmpDate = getCurrentDate();
+        }
+
+        Long dateTime = tmpDate.getTime() - n * 24 * 60 * 60 * 1000L;
+
+        return new Date(dateTime);
+    }
+
+    public static Date getNSeconBefore(long n, Date... date) {
+        Date tmpDate = null;
+        if (date != null && date.length > 0) {
+            tmpDate = date[0];
+        } else {
+            tmpDate = getCurrentDate();
+        }
+
+        Long dateTime = tmpDate.getTime() - n * 1000;
+        return new Date(dateTime);
+    }
+
+    public static String getLastNDayDate(int n, Date... date) {
+        Date tmpDate = null;
+        if (date != null && date.length > 0) {
+            tmpDate = date[0];
+        } else {
+            tmpDate = getCurrentDate();
+        }
+
+        Calendar c = Calendar.getInstance();
+        c.setTime(tmpDate);
+        c.add(Calendar.DAY_OF_YEAR, n);
+
+        return getDate2String(c.getTime());
+    }
+
+    public static String getLastNDayDate(int n, String date) {
+        return getLastNDayDate(n, getString2Date(date));
+    }
+
+    public static String getLastNMonthDate(int n, Date... date) {
+        Date tmpDate = null;
+        if (date != null && date.length > 0) {
+            tmpDate = date[0];
+        } else {
+            tmpDate = getCurrentDate();
+        }
+
+        Calendar c = Calendar.getInstance();
+        c.setTime(tmpDate);
+        c.add(Calendar.MONTH, n);
+
+        return getDate2String(c.getTime());
+    }
+
+    public static String getLastNMonthDate(int n, String date) {
+        if (StringUtils.isNotBlank(date)) {
+            return getLastNMonthDate(n, getString2Date(date));
+        } else {
+            return getLastNMonthDate(n);
+        }
+    }
+
+    public static String getLastNYearBeginDate(int n) {
+        return getLastNYearBeginDate(n, new SimpleDateFormat("yyyy-MM-dd"));
+    }
+
+    public static String getLastNYearBeginDate(int n, DateFormat format) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+        c.add(Calendar.YEAR, n);
+        c.set(c.get(Calendar.YEAR), 0, 1);
+        return format.format(c.getTime());
+    }
+
+    public static String getLastNYearEndDate(int n) {
+        return getLastNYearEndDate(n, new SimpleDateFormat("yyyy-MM-dd"));
+    }
+
+    public static String getLastNYearEndDate(int n, DateFormat format) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+        c.add(Calendar.YEAR, n);
+        c.set(c.get(Calendar.YEAR), 11, 31);
+        return format.format(c.getTime());
+    }
+
+    public static String getCurrentWeekDay() {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+
+        int weekDay = c.get(Calendar.DAY_OF_WEEK);
+        String weekDayStr = null;
+        switch (weekDay) {
+            case 1:
+                weekDayStr = "星期日";
+                break;
+            case 2:
+                weekDayStr = "星期一";
+                break;
+            case 3:
+                weekDayStr = "星期二";
+                break;
+            case 4:
+                weekDayStr = "星期 三";
+                break;
+            case 5:
+                weekDayStr = "星期四";
+                break;
+            case 6:
+                weekDayStr = "星期五";
+                break;
+            case 7:
+                weekDayStr = "星期六";
+                break;
+        }
+
+        return weekDayStr;
+    }
+
+    public static String getCurrentTimeDesc() {
+        Date now = new Date();
+        SimpleDateFormat format = new SimpleDateFormat("HH");
+        int h = Integer.parseInt(format.format(now));
+        if (h >= 0 && h <= 6) {
+            return "凌晨";
+        }
+        if (h > 6 && h <= 12) {
+            return "上午";
+        }
+        if (h > 12 && h <= 13) {
+            return "中午";
+        }
+        if (h > 13 && h <= 18) {
+            return "下午";
+        }
+        if (h > 18 && h <= 24) {
+            return "晚上";
+        }
+        return null;
+    }
+
+    public static int getCurrentHourOfDay() {
+        Calendar c = Calendar.getInstance();
+        c.setTime(new Date());
+
+        return c.get(Calendar.HOUR_OF_DAY);
+    }
+
+    public static Date getCurrentYearDate() {
+        Calendar date = Calendar.getInstance();
+        String year = date.get(Calendar.YEAR) + "-01-01";
+        return getString2Date(year);
+    }
+
+    public static Date getCurrentYearDate(Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        String year = cal.get(Calendar.YEAR) + "-01-01 00:00:00";
+        return getString2Date(YYYY_MM_DD_HH_MM_SS, year);
+    }
+
+    public static Date getCurrentYearDate(String yearStr) {
+        String year = yearStr + "-01-01 00:00:00";
+        return getString2Date(YYYY_MM_DD_HH_MM_SS, year);
+    }
+
+    public static Date getNextYearDate(Date date) {
+        Calendar calendar = new GregorianCalendar();
+        calendar.setTime(date);
+        calendar.add(Calendar.YEAR, 1); // 把日期往后增加一年.整数往后推,负数往前移动
+        return calendar.getTime();
+    }
+
+    public static String getAMPM() {
+        int hour = getCurrentHourOfDay();
+        String amPM = "";
+        if (hour <= 12) {
+            amPM = "上午";
+        } else if (12 < hour && hour <= 18) {
+            amPM = "下午";
+        } else if (18 < hour && hour < 24) {
+            amPM = "晚上";
+        }
+
+        return amPM;
+    }
+
+    public static Date formatDate(Date date, String... format) {
+        String tmpFormat = YYYY_MM_DD;
+        if (format != null && format.length > 0) {
+            tmpFormat = format[0];
+        }
+
+        String dateStr = getDate2String(tmpFormat, date);
+
+        return getString2Date(tmpFormat, dateStr);
+    }
+
+    public static long handleDateTimeForZadd(Date date) {
+        if (date == null) {
+            throw new RuntimeException("zadd中转换日期不能为空!");
+        }
+        String tmpFormat = YYYY_MM_DD_HH_MM_SS;
+        Date realDate = formatDate(date, tmpFormat);
+        return realDate.getTime();
+    }
+
+    public static long getDifferDays(Date end, Date begin) {
+        long quot = 0;
+        quot = end.getTime() - begin.getTime();
+        quot = quot / 1000 / 60 / 60 / 24;
+        return quot;
+    }
+
+    public static String getString2String(String str, String formatSrc, String format) {
+        return getDate2String(format, getString2Date(formatSrc, str));
+    }
+
+    public static long getBetweenDay(String beginDate, Date... endDate) {
+        return getBetweenDay(getString2Date(beginDate), endDate);
+    }
+
+    public static long getBetweenDay(Date beginDate, Date... endDate) {
+        Date tmpEndDate = getCurrentDate(YYYY_MM_DD);
+        if (endDate != null && endDate.length > 0) {
+            tmpEndDate = formatDate(endDate[0], YYYY_MM_DD);
+        }
+
+        beginDate = formatDate(beginDate, YYYY_MM_DD);
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(beginDate);
+        long time1 = cal.getTimeInMillis();
+        cal.setTime(tmpEndDate);
+        long time2 = cal.getTimeInMillis();
+        long betweenDays = (time2 - time1) / (1000 * 3600 * 24);
+
+        return betweenDays;
+    }
+
+    public static Date dateAdd(Date srcDate, int amount) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(srcDate);
+
+        c.add(Calendar.DAY_OF_YEAR, amount);
+        return c.getTime();
+    }
+
+    public static Date dateAddHour(Date srcDate, int hour) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(srcDate);
+
+        c.add(Calendar.HOUR, hour);
+        return c.getTime();
+
+    }
+
+    public static Date getTodayZeroTime() {
+        Date now = new Date();
+        Calendar c = Calendar.getInstance();
+        c.setTime(now);
+        c.set(Calendar.HOUR_OF_DAY, 0);
+        c.set(Calendar.MINUTE, 0);
+        c.set(Calendar.SECOND, 0);
+        c.set(Calendar.MILLISECOND, 0);
+        return c.getTime();
+    }
+
+    public static Map<String, Date> getCurrentDateRangeMap(Date... date) {
+        Map<String, Date> dateMap = new HashMap<String, Date>();
+        Date tmpDate = DateUtils.getCurrentDate();
+        if (date != null && date.length > 0) {
+            tmpDate = date[0];
+        }
+
+        String operateTime = DateUtils.getDate2String(DateUtils.YYYY_MM_DD, tmpDate);
+        Date beginDate = DateUtils.getString2Date(DateUtils.YYYY_MM_DD_HH_MM_SS, operateTime + DateUtils.DATE_BEGIN);
+        Date endDate = DateUtils.getString2Date(DateUtils.YYYY_MM_DD_HH_MM_SS, operateTime + DateUtils.DATE_END);
+
+        dateMap.put("beginDate", beginDate);
+        dateMap.put("endDate", endDate);
+
+        return dateMap;
+    }
+
+    public static Map<String, Date> getYesterdayRangeMap() {
+        Map<String, Date> dateMap = new HashMap<String, Date>();
+        Date yesterday = DateUtils.getBeforeNDayDate(1L, DateUtils.getCurrentDate());
+        String operateTime = DateUtils.getDate2String(DateUtils.YYYY_MM_DD, yesterday);
+        Date beginDate = DateUtils.getString2Date(DateUtils.YYYY_MM_DD_HH_MM_SS, operateTime + DateUtils.DATE_BEGIN);
+        Date endDate = DateUtils.getString2Date(DateUtils.YYYY_MM_DD_HH_MM_SS, operateTime + DateUtils.DATE_END);
+
+        dateMap.put("beginDate", beginDate);
+        dateMap.put("endDate", endDate);
+
+        return dateMap;
+    }
+
+    public static Map<String, Date> getNHourBeforeRangeMap(int hours) {
+        if (hours < 0) {
+            hours = 1;
+        }
+        Map<String, Date> dateMap = new HashMap<String, Date>();
+        Date beginDate = DateUtils.getNSeconBefore(Long.valueOf(hours * 60 * 60)); // n 小时前
+        Date endDate = DateUtils.getCurrentDate();
+
+        dateMap.put("beginDate", beginDate);
+        dateMap.put("endDate", endDate);
+
+        return dateMap;
+    }
+
+    public static String getDateYear(Date... date) {
+        String year = "";
+        Date tmpDate = getCurrentDate();
+        if ((date != null) && (date.length > 0)) {
+            tmpDate = date[0];
+        }
+        Calendar c = Calendar.getInstance();
+        c.setTime(tmpDate);
+
+        year = String.valueOf(c.get(Calendar.YEAR));
+
+        return year;
+    }
+
+    public static String getYearByDay(Date now) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
+        return sdf.format(now);
+    }
+
+    public static boolean checkParam(Object param) {
+        if (null == param || "".equals(param)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public static Date addDays(Date date, int days) {
+        if (checkParam(date)) {
+            return null;
+        }
+        if (0 == days) {
+            return date;
+        }
+        Locale locale = Locale.getDefault();
+        Calendar calendar = new GregorianCalendar(locale);
+        calendar.setTime(date);
+        calendar.add(Calendar.DAY_OF_MONTH, days);
+        return calendar.getTime();
+    }
+
+    public static Long getDistanceReduceNow(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return date.getTime() - getCurrentTimeMillis();
+    }
+
+    public static Date getEndOfDay(Date date) {
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),
+                ZoneId.systemDefault());
+        LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
+        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    public static Date getStartOfDay(Date date) {
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),
+                ZoneId.systemDefault());
+        LocalDateTime endOfDay = localDateTime.with(LocalTime.MIN);
+        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    public static int getSecondTimestamp(Date date) {
+        if (null == date) {
+            return 0;
+        }
+        String timestamp = String.valueOf(date.getTime());
+        int length = timestamp.length();
+        if (length > 3) {
+            return Integer.valueOf(timestamp.substring(0, length - 3));
+        } else {
+            return 0;
+        }
+    }
+
+    public static Date getCurrentHourTime() {
+        return getHourTime(new Date(), 0, "=");
+    }
+
+    public static Date getLastHourTime(Date date, int n) {
+        return getHourTime(date, n, "-");
+    }
+
+    public static Date getNextHourTime(Date date, int n) {
+        return getHourTime(date, n, "+");
+    }
+
+    public static Date getHourTime(Date date, int n, String direction) {
+        Calendar ca = Calendar.getInstance();
+        ca.setTime(date);
+        ca.set(Calendar.MINUTE, 0);
+        ca.set(Calendar.SECOND, 0);
+        switch (direction) {
+            case "+":
+                ca.set(Calendar.HOUR_OF_DAY, ca.get(Calendar.HOUR_OF_DAY) + n);
+                break;
+            case "-":
+                ca.set(Calendar.HOUR_OF_DAY, ca.get(Calendar.HOUR_OF_DAY) - n);
+                break;
+            case "=":
+                ca.set(Calendar.HOUR_OF_DAY, ca.get(Calendar.HOUR_OF_DAY));
+                break;
+            default:
+                ca.set(Calendar.HOUR_OF_DAY, ca.get(Calendar.HOUR_OF_DAY));
+        }
+
+        date = ca.getTime();
+        return date;
+    }
+
+}

+ 27 - 0
webchat-common/src/main/java/com/webchat/common/util/FileConvertUtil.java

@@ -0,0 +1,27 @@
+package com.webchat.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+
+@Slf4j
+public class FileConvertUtil {
+
+    /***
+     * MultipartFile转换为二进制
+     * @param multipartFile
+     * @return
+     * @throws IOException
+     */
+    public static byte[] convertMultipartFileToByteArray(MultipartFile multipartFile) {
+        byte[] binary = new byte[0];
+        try {
+            binary = multipartFile.getBytes();
+        } catch (Exception e) {
+            log.error("convert MultipartFile to byte array error.", e);
+        }
+        return binary;
+    }
+}

+ 78 - 0
webchat-common/src/main/java/com/webchat/common/util/FileUtil.java

@@ -0,0 +1,78 @@
+package com.webchat.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.net.FileNameMap;
+import java.net.URLConnection;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+public class FileUtil {
+
+    private static FileNameMap fileNameMap = null;
+
+    /***
+     * 生成上传文件的文件名称
+     * @param oriFileName
+     * @return
+     */
+    public static String generateFileName(String oriFileName) {
+        StringBuffer bf = new StringBuffer();
+        bf.append(UUID.randomUUID().toString().replaceAll("-", ""));
+        String type = null;
+        if (StringUtils.isNotBlank(oriFileName)) {
+            String[] fileNames = oriFileName.split("\\.");
+            bf.append("_");
+            bf.append(stringFilter(fileNames[0]));
+            if (fileNames.length > 1) {
+                type = "." + fileNames[1];
+            }
+        }
+
+        String fileName = bf.toString() + "/" + type;
+        fileName = fileName.replaceAll("/", "");
+        fileName = fileName.replaceAll("\n", "");
+        return fileName;
+
+    }
+
+    public static String stringFilter(String str) throws PatternSyntaxException {
+        // 清除掉所有特殊字符
+        String regEx = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
+        Pattern p = Pattern.compile(regEx);
+        Matcher m = p.matcher(str);
+        return m.replaceAll("").trim();
+    }
+
+    public static String getMimeType(String fileName) {
+        if (StringUtils.isBlank(fileName)) {
+            return null;
+        }
+        return getFileNameMap().getContentTypeFor(fileName);
+    }
+
+    /**
+     * 获得文件URL,集群环境,拼装相对主机名的URL
+     *
+     * @param fileURL
+     * @param remoteHost
+     *
+     * @return
+     */
+    public static String getFileURL(String fileURL, String remoteHost) {
+        if (StringUtils.isNotBlank(fileURL) && !fileURL.contains("http")) {
+            fileURL = "http://" + remoteHost + ":" + fileURL;
+        }
+
+        return fileURL;
+    }
+
+    private static FileNameMap getFileNameMap() {
+        if (fileNameMap == null) {
+            fileNameMap = URLConnection.getFileNameMap();
+        }
+        return fileNameMap;
+    }
+}

+ 542 - 0
webchat-common/src/main/java/com/webchat/common/util/HtmlUtil.java

@@ -0,0 +1,542 @@
+package com.webchat.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class HtmlUtil {
+
+    private static final String REGXP_FOR_HTML = "<([^>]*)>"; // 过滤所有以<开头以>结尾的标签
+    private static final String PICTURE = "[图片]";
+
+    /**
+     * @param fString
+     *
+     * @return
+     */
+    public static String htmlEncode(String fString) {
+        fString = fString.replaceAll(" <", "&lt;");
+        fString = fString.replaceAll(">", "&gt;");
+        fString = fString.replaceAll(new String(new char[] {32}), "&nbsp;");
+        fString = fString.replaceAll(new String(new char[] {9}), "&nbsp;");
+        fString = fString.replaceAll(new String(new char[] {34}), "&quot;");
+        fString = fString.replaceAll(new String(new char[] {39}), "&#39;");
+        fString = fString.replaceAll(new String(new char[] {13}), "");
+        fString = fString.replaceAll(new String(new char[] {10, 10}), " </p> <p>");
+        fString = fString.replaceAll(new String(new char[] {10}), " <br>");
+        return fString;
+    }
+
+    /**
+     * xss escape
+     */
+    public static String xssEscape(String input) {
+        return input == null ? null : input.replaceAll("<", "&lt;")
+                .replaceAll(">", "&gt;");
+    }
+
+    /**
+     * 除指定标签之外的html标签编码
+     *
+     * @param str
+     * @param tag
+     *
+     * @return
+     */
+    public static String xssEscapeExceptTag(String str, String tag) {
+        String replaceTag = "@" + tag + "@";
+        str = str.replaceAll("<" + tag, replaceTag);
+        str = xssEscape(str);
+        str = str.replaceAll(replaceTag, "<" + tag);
+
+        return str;
+    }
+
+    /**
+     * 过滤一下字符串,连同前后< xxx >yyy< / xxx >全部消除。
+     * 不区分大小写、空格可识别
+     * <br>"function", "window\\.", "javascript:", "script",
+     * <br>"js:", "about:", "file:", "document\\.", "vbs:", "frame",
+     * <br>"cookie", "onclick", "onfinish", "onmouse", "onexit=",
+     * <br>"onerror", "onclick", "onkey", "onload", "onfocus", "onblur"
+     *
+     * @param htmlStr
+     *
+     * @return
+     */
+    public static String filterSafe(String htmlStr) {
+        Pattern p = null; // 正则表达式
+        Matcher m = null; // 操作的字符串
+        StringBuffer tmp = null;
+        String str = "";
+        boolean isHave = false;
+        String[] rstr = {"meta", "script", "object", "embed"};
+        if (htmlStr == null || !(htmlStr.length() > 0)) {
+            return "";
+        }
+        str = htmlStr.toLowerCase();
+        for (int i = 0; i < rstr.length; i++) {
+            p = Pattern.compile("<" + rstr[i] + "(.[^>])*>");
+            m = p.matcher(str);
+            tmp = new StringBuffer();
+            if (m.find()) {
+                m.appendReplacement(tmp, "<" + rstr[i] + ">");
+                while (m.find()) {
+                    m.appendReplacement(tmp, "<" + rstr[i] + ">");
+                }
+                isHave = true;
+            }
+
+            m.appendTail(tmp);
+            str = tmp.toString();
+
+            p = Pattern.compile("</" + rstr[i] + "(.[^>])*>");
+            m = p.matcher(str);
+            tmp = new StringBuffer();
+            if (m.find()) {
+                m.appendReplacement(tmp, "</" + rstr[i] + ">");
+                while (m.find()) {
+                    m.appendReplacement(tmp, "</" + rstr[i] + ">");
+                }
+                isHave = true;
+            }
+            m.appendTail(tmp);
+            str = tmp.toString();
+
+        }
+
+        String[] rstr1 = {"function", "window\\.", "javascript:", "script",
+                "js:", "about:", "file:", "document\\.", "vbs:", "frame",
+                "cookie", "onclick", "onfinish", "onmouse", "onexit=",
+                "onerror", "onclick", "onkey", "onload", "onfocus", "onblur"};
+
+        for (int i = 0; i < rstr1.length; i++) {
+            p = Pattern.compile("<([^<>])*" + rstr1[i] + "([^<>])*>([^<>])*</([^<>])*>");
+
+            m = p.matcher(str);
+            tmp = new StringBuffer();
+            if (m.find()) {
+                m.appendReplacement(tmp, "");
+                while (m.find()) {
+                    m.appendReplacement(tmp, "");
+                }
+                isHave = true;
+            }
+            m.appendTail(tmp);
+            str = tmp.toString();
+        }
+
+        if (isHave) {
+            htmlStr = str;
+        }
+
+        htmlStr = htmlStr.replaceAll("%3C", "<");
+        htmlStr = htmlStr.replaceAll("%3E", ">");
+        htmlStr = htmlStr.replaceAll("%2F", "");
+        htmlStr = htmlStr.replaceAll("&#", "&ltb>&#</b>");
+        return htmlStr;
+    }
+
+    /**
+     * @param input
+     *
+     * @return
+     */
+    public static String filter(String input) {
+        if (!hasSpecialChars(input)) {
+            return input;
+        }
+        StringBuffer filtered = new StringBuffer(input.length());
+        char c;
+        for (int i = 0; i <= input.length() - 1; i++) {
+            c = input.charAt(i);
+            switch (c) {
+                case '<':
+                    filtered.append("&lt;");
+                    break;
+                case '>':
+                    filtered.append("&gt;");
+                    break;
+                case '"':
+                    filtered.append("&uot;");
+                    break;
+                case '&':
+                    filtered.append("&amp;");
+                    break;
+                default:
+                    filtered.append(c);
+                    break;
+            }
+        }
+        return (filtered.toString());
+    }
+
+    public static boolean hasSpecialChars(String input) {
+        boolean flag = false;
+        if ((input != null) && (input.length() > 0)) {
+            char c;
+            for (int i = 0; i <= input.length() - 1; i++) {
+                c = input.charAt(i);
+                switch (c) {
+                    case '>':
+                        flag = true;
+                        break;
+                    case '<':
+                        flag = true;
+                        break;
+                    case '"':
+                        flag = true;
+                        break;
+                    case '&':
+                        flag = true;
+                        break;
+                    default:
+                        flag = false;
+                        break;
+
+                }
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 基本功能:过滤所有以"<"开头以">"结尾的标签
+     * <p>
+     *
+     * @param str
+     *
+     * @return String
+     */
+    public static String filterHtml(String str) {
+        if (StringUtils.isBlank(str)) {
+            return str;
+        }
+        Pattern pattern = Pattern.compile(REGXP_FOR_HTML);
+        Matcher matcher = pattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        boolean result1 = matcher.find();
+        while (result1) {
+            matcher.appendReplacement(sb, "");
+            result1 = matcher.find();
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    /**
+     * 过滤除指定tag之外的html标签
+     *
+     * @param str
+     * @param tag
+     *
+     * @return
+     */
+    public static String filterHtmlExceptTag(String str, String tag) {
+        if (StringUtils.isBlank(str)) {
+            return str;
+        }
+        String replaceTag = "@" + tag + "@";
+        str = str.replaceAll("<" + tag, replaceTag);
+        str = filterHtml(str);
+        str = str.replaceAll(replaceTag, "<" + tag);
+
+        return str;
+    }
+
+    /***
+     * 删除指定标签及标签的内容
+     * 如:<tag>value</tag>, 连同value内容也会清理掉
+     * @param content
+     * @param tag
+     * @return
+     */
+    public static String deleteHtmlTagAndTagValue(String content, String tag) {
+        if (StringUtils.isBlank(content)) {
+            return null;
+        }
+        Pattern imgPattern = Pattern.compile("<" + tag + ".*?>.*</" + tag + ">");
+        Matcher tagMatcher = imgPattern.matcher(content);
+        while (tagMatcher.find()) {
+            String matchGroup = tagMatcher.group();
+            content = content.replace(matchGroup, "");
+        }
+        return content;
+    }
+
+    /***
+     * 清理动态、评论内的@结构
+     * 如:<a>@value</a>, 连同value内容也会清理掉
+     * @param content
+     * @return
+     */
+    public static String deleteAtTagAndValue(String content) {
+        if (StringUtils.isBlank(content)) {
+            return null;
+        }
+        Pattern imgPattern = Pattern.compile("<a.*?>@.*</a>");
+        Matcher tagMatcher = imgPattern.matcher(content);
+        while (tagMatcher.find()) {
+            String matchGroup = tagMatcher.group();
+            content = content.replace(matchGroup, "");
+        }
+        return content;
+    }
+
+    /**
+     * 基本功能:过滤指定标签
+     * <p>
+     *
+     * @param str
+     * @param tag 指定标签
+     *
+     * @return String
+     */
+    public static String fiterHtmlTag(String str, String tag) {
+        String regxp = "<\\s*" + tag + "\\s+([^>]*)\\s*>";
+        Pattern pattern = Pattern.compile(regxp);
+        Matcher matcher = pattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        boolean result1 = matcher.find();
+        while (result1) {
+            matcher.appendReplacement(sb, "");
+            result1 = matcher.find();
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    /**
+     * 基本功能:替换指定的标签
+     *
+     * @param str
+     * @param beforeTag 要替换的标签
+     * @param tagAttrib 要替换的标签属性值
+     * @param startTag  新标签开始标记
+     * @param endTag    新标签结束标记
+     *
+     * @return String
+     *
+     * @如:替换img标签的src属性值为[img]属性值[/img]
+     */
+    public static String replaceHtmlTag(String str, String beforeTag,
+                                        String tagAttrib, String startTag, String endTag) {
+        String regxpForTag = "<\\s*" + beforeTag + "\\s+([^>]*)\\s*>";
+        String regxpForTagAttrib = tagAttrib + "=\"([^\"]+)\"";
+        Pattern patternForTag = Pattern.compile(regxpForTag);
+        Pattern patternForAttrib = Pattern.compile(regxpForTagAttrib);
+        Matcher matcherForTag = patternForTag.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        boolean result = matcherForTag.find();
+        while (result) {
+            StringBuffer sbreplace = new StringBuffer();
+            Matcher matcherForAttrib = patternForAttrib.matcher(matcherForTag.group(1));
+            if (matcherForAttrib.find()) {
+                matcherForAttrib.appendReplacement(sbreplace, startTag + matcherForAttrib.group(1) + endTag);
+            }
+            matcherForTag.appendReplacement(sb, sbreplace.toString());
+            result = matcherForTag.find();
+        }
+        matcherForTag.appendTail(sb);
+        return sb.toString();
+    }
+
+    /**
+     * html标签替换为指定字符
+     *
+     * @param str
+     * @param tag
+     * @param text
+     *
+     * @return
+     */
+    public static String replaceHtmlTagOfText(String str, String tag, String text) {
+        String regxp = "<\\s*" + tag + "\\s+([^>]*)\\s*>";
+        Pattern pattern = Pattern.compile(regxp);
+        Matcher matcher = pattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        boolean result1 = matcher.find();
+        while (result1) {
+            matcher.appendReplacement(sb, text);
+            result1 = matcher.find();
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    /**
+     * 获取指定HTML标签的指定属性的值
+     *
+     * @param source  要匹配的源文本
+     * @param element 标签名称
+     * @param attr    标签的属性名称
+     *
+     * @return 属性值列表
+     */
+    public static List<String> match(String source, String element, String attr) {
+        List<String> result = new ArrayList<>();
+        String reg = "<" + element + "[^<>]*?\\s" + attr + "=['\"]?(.*?)['\"]?\\s.*?>";
+        Matcher m = Pattern.compile(reg).matcher(source);
+        while (m.find()) {
+            String r = m.group(1);
+            result.add(r);
+        }
+        return result;
+    }
+
+    /**
+     * @param html
+     *
+     * @return
+     */
+    public static List<String> getImgHTML(String html) {
+        List<String> resultList = new ArrayList<>();
+        Pattern p = Pattern.compile("<img ([^>]*)"); // <img开头>结尾
+        Matcher m = p.matcher(html); // 开始编译
+        while (m.find()) {
+            resultList.add("<img " + m.group(1) + ">"); // 获取匹配的部分
+        }
+
+        return resultList;
+    }
+
+    public static List<String> getImgSrc(String htmlStr) {
+        String img = "";
+        Pattern pImage;
+        Matcher mImage;
+        List<String> pics = new ArrayList<>();
+        String regExImg = "<img.*src=(.*?)[^>]*?>"; // 图片链接地址
+        pImage = Pattern.compile(regExImg, Pattern.CASE_INSENSITIVE);
+        mImage = pImage.matcher(htmlStr);
+        while (mImage.find()) {
+            img = mImage.group();
+            Matcher m = Pattern.compile("src=\"?(.*?)(\"|>|\\s+)").matcher(img); // 匹配src
+            while (m.find()) {
+                pics.add(m.group(1));
+            }
+        }
+        return pics;
+    }
+
+    public static List<String> getImgAlt(String htmlStr) {
+        String img = "";
+        Pattern pImage;
+        Matcher mImage;
+        List<String> alts = new ArrayList<>();
+
+        String regExImg = "<img.*src=(.*?)[^>]*?>"; // 图片链接地址
+        pImage = Pattern.compile(regExImg, Pattern.CASE_INSENSITIVE);
+        mImage = pImage.matcher(htmlStr);
+        while (mImage.find()) {
+            img = mImage.group();
+            Matcher m = Pattern.compile("alt=\"?(.*?)(\"|>|\\s+)").matcher(img); // 匹配src
+            while (m.find()) {
+                alts.add(m.group(1));
+            }
+        }
+        return alts;
+    }
+
+    /**
+     * 基本功能:过滤所有以"<"开头以">"结尾的标签,但是替换为空格
+     * <p>
+     *
+     * @param str
+     *
+     * @return String
+     */
+    public static String filterHtmlWithSapce(String str) {
+        if (StringUtils.isBlank(str)) {
+            return str;
+        }
+        Pattern pattern = Pattern.compile(REGXP_FOR_HTML);
+        Matcher matcher = pattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        boolean result1 = matcher.find();
+        while (result1) {
+            matcher.appendReplacement(sb, " ");
+            result1 = matcher.find();
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    /**
+     * 获取纯文本内容
+     *
+     * @param content
+     *
+     * @return
+     */
+    public static String getTxtContent(String content) {
+        String txtcontent = "";
+        if (StringUtils.isNotBlank(content)) {
+            txtcontent = content.replaceAll("</?[^>]+>", ""); // 剔出<html>的标签
+            txtcontent = txtcontent.replaceAll("<a>\\s*|\t|\r|\n</a>", "");
+            txtcontent = txtcontent.replaceAll("\\s*|\t|\r|\n", "");
+        }
+        return txtcontent;
+    }
+
+    /**
+     * 清楚文本中的HTML等标签
+     * @param htmlStr
+     * @return
+     */
+    public static String delHTMLTag(String htmlStr) {
+        if (StringUtils.isBlank(htmlStr)) {
+            return null;
+        }
+        // 定义script的正则表达式
+        String regExScript = "<script[^>]*?>[\\s\\S]*?<\\/script>";
+        // 定义style的正则表达式
+        String regExStyle = "<style[^>]*?>[\\s\\S]*?<\\/style>";
+        // 定义HTML标签的正则表达式
+        String regExHtml = "<[^>]+>";
+        // 过滤script标签
+        Pattern pScript = Pattern.compile(regExScript, Pattern.CASE_INSENSITIVE);
+        Matcher mScript = pScript.matcher(htmlStr);
+        htmlStr = mScript.replaceAll("");
+        // 过滤style标签
+        Pattern pStyle = Pattern.compile(regExStyle, Pattern.CASE_INSENSITIVE);
+        Matcher mStyle = pStyle.matcher(htmlStr);
+        htmlStr = mStyle.replaceAll("");
+        // 过滤html标签
+        Pattern pHtml = Pattern.compile(regExHtml, Pattern.CASE_INSENSITIVE);
+        Matcher mHtml = pHtml.matcher(htmlStr);
+        htmlStr = mHtml.replaceAll("");
+        return htmlStr.trim();
+    }
+
+    public static List<String> getATagLink(String html) {
+        if (StringUtils.isBlank(html)) {
+            return null;
+        }
+        List<String> aLinkList = new ArrayList<>();
+        Pattern aLinkPattern;
+        Matcher aLinkMatcher;
+        // A标签
+        String regATag = "<a.*href=(.*?)[^>]*?>";
+        aLinkPattern = Pattern.compile(regATag, Pattern.CASE_INSENSITIVE);
+        aLinkMatcher = aLinkPattern.matcher(html);
+        while (aLinkMatcher.find()) {
+            // 匹配A标签href
+            Matcher m = Pattern.compile("href=\"?(.*?)(\"|>|\\s+)").matcher(aLinkMatcher.group());
+            while (m.find()) {
+                aLinkList.add(m.group(1));
+            }
+        }
+        return aLinkList;
+    }
+
+    /**
+     * xss escape back
+     */
+    public static String xssEscapeBack(String input) {
+        return input == null ? null : input.replaceAll("&lt;", "<")
+                .replaceAll("&gt;", ">");
+    }
+}

+ 456 - 0
webchat-common/src/main/java/com/webchat/common/util/HttpClientUtil.java

@@ -0,0 +1,456 @@
+package com.webchat.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.tomcat.util.http.fileupload.IOUtils;
+import org.springframework.http.*;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.util.*;
+
+@Slf4j
+public class HttpClientUtil {
+
+    public static int defaultRetryTimes = 1;
+
+    public static int defaultReadTimeOut = 100000; // 默认超时时间,毫秒
+
+    public static <T extends Object> T getObjectFromUrl(String url, Class<T> type) throws URISyntaxException {
+        return getObjectFromUrl(new URI(url), type);
+    }
+
+    public static <T extends Object> T getObjectFromUrl(URI url, Class<T> type ) {
+        return getObjectFromUrl(url, type, defaultRetryTimes, defaultReadTimeOut);
+    }
+
+    public static ResponseEntity<?> getObjectFromUrl(String url, Map<String, String> headers, Class<?> type) {
+        return getObjectFromUrl(url, headers, type, defaultRetryTimes, defaultReadTimeOut);
+    }
+
+    public static <T extends Object> T postObjectForUrl(URI url, Object requestBody,  Class<T> type, boolean ...ssl) {
+        return postObjectForUrl(url, requestBody, null, type, defaultRetryTimes, defaultReadTimeOut, ssl);
+    }
+
+    public static <T extends Object> T postObjectForUrl(String url, Map<String, String> headers, Object requestBody,  Class<T> type, boolean ...ssl)
+            throws URISyntaxException {
+        return postObjectForUrl(new URI(url), requestBody, headers, type, defaultRetryTimes, defaultReadTimeOut, ssl);
+    }
+
+    public static <T extends Object> T postObjectForUrl(String url, Object requestBody, Class<T> type, boolean ...ssl)
+            throws URISyntaxException {
+        return postObjectForUrl(new URI(url), requestBody, null, type, defaultRetryTimes,
+                defaultReadTimeOut, ssl);
+    }
+
+    public static <T extends Object> T postObjectForUrl(String url, Object requestBody, Class<T> type,
+                                                        int retryTimes, int defaultReadTimeOut)
+            throws URISyntaxException {
+        return postObjectForUrl(new URI(url), requestBody, null, type, retryTimes, defaultReadTimeOut);
+    }
+
+    public static <T extends Object> T postObjectForUrl(String url, Object requestBody, Map<String, String> headers,
+                                                        Class<T> type, boolean ...ssl)throws URISyntaxException {
+        return postObjectForUrl(new URI(url), requestBody, headers, type, defaultRetryTimes, defaultReadTimeOut, ssl);
+    }
+
+    public static void putObjectFormUrl(String url, Object requestBody, Map<String, String> headers)
+            throws URISyntaxException {
+        putObjectFormUrl(new URI(url), requestBody, headers, defaultRetryTimes, defaultReadTimeOut);
+    }
+
+    public static ResponseEntity<?> putObjectFormUrl(String url, Object requestBody, Map<String, String> headers,
+                                                     Class<?> type) throws URISyntaxException {
+        return putObjectFormUrl(new URI(url), requestBody, headers, type, defaultRetryTimes, defaultReadTimeOut);
+    }
+
+    public static ResponseEntity<?> deleteObjectFormUrl(String url, Object requestBody, Map<String, String> headers,
+                                                        Class<?> type) throws URISyntaxException {
+        return deleteObjectFormUrl(url, requestBody, headers, type, defaultRetryTimes, defaultReadTimeOut);
+    }
+
+
+    public static <T extends Object> T getObjectFromUrl(URI url, Class<T> type, int retryTimes, int readTimeOut) {
+        RestTemplate restTemplate =  getRestTemplate(readTimeOut);
+        boolean success = false;
+        T result = null;
+        while (!success && retryTimes >= 0) {
+            try {
+                result = restTemplate.getForObject(url, type);
+                success = true;
+            } catch (Exception ex) {
+                log.error("rest template getObjectFromUrl error: ", ex);
+                retryTimes--;
+                if (retryTimes == 0) {
+                    throw ex;
+                }
+            }
+        }
+        return result;
+    }
+
+    public static ResponseEntity<?> getObjectFromUrl(String url,  Map<String, String> headerMap,
+                                                     Class<?> responseType, int retryTimes, int readTimeOut) {
+        HttpHeaders httpHeaders = setHttpHeaders(headerMap);
+        if (!httpHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            httpHeaders.add(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE);
+
+        }
+        HttpEntity<Object> formEntity = new HttpEntity<>(null, httpHeaders);
+        RestTemplate restTemplate =  getRestTemplate(readTimeOut);
+        boolean success = false;
+        ResponseEntity<?> result = null;
+        while (!success && retryTimes >= 0) {
+            try {
+                result = restTemplate.exchange(url, HttpMethod.GET, formEntity, responseType);
+                success = true;
+            } catch (Exception ex) {
+                log.error("rest template postObjectForUrl error: ", ex);
+                retryTimes--;
+                if (retryTimes == 0) {
+                    throw ex;
+                }
+            }
+        }
+        return result;
+    }
+
+    public static <T extends Object> T postObjectForUrl(URI  url, Object requestBody, Map<String, String> headerMap,
+                                                        Class<T> type, int retryTimes, int readTimeOut,
+                                                        boolean ...ssl) {
+        HttpHeaders httpHeaders = setHttpHeaders(headerMap);
+        if (!httpHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            httpHeaders.add(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE);
+
+        }
+        HttpEntity<Object> formEntity = new HttpEntity<Object>(requestBody, httpHeaders);
+        RestTemplate restTemplate =  getRestTemplate(readTimeOut, ssl);
+        boolean success = false;
+        T result = null;
+        while (!success && retryTimes >= 0) {
+            try {
+                result = restTemplate.postForObject(url, formEntity, type);
+                success = true;
+            } catch (Exception ex) {
+                log.error("rest template postObjectForUrl error: ", ex);
+                retryTimes--;
+                if (retryTimes == 0) {
+                    throw ex;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @param url
+     * @param formData
+     * @param type
+     * @param <T>
+     *
+     * @return
+     */
+    public static <T extends Object> T  postFormForUrl(String url, Map<String, String> formData, Class<T> type ) {
+        return postFormForUrl(url, formData, null, type);
+    }
+
+    /**
+     * @param url
+     * @param formData
+     * @param type
+     * @return
+     *
+     */
+    public static <T extends Object> T postFormForUrl(String url, Map<String, String> formData,  Map<String, String>
+            headerMap, Class<T> type) {
+        HttpHeaders headers = setHttpHeaders(headerMap);
+        if (!headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        }
+        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
+        for (String key : formData.keySet()) {
+            map.add(key, formData.get(key));
+        }
+        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
+        RestTemplate restTemplate =  getRestTemplate(defaultReadTimeOut);
+        return  restTemplate.postForObject(url, request , type);
+    }
+
+    /**
+     * @param url
+     * @param requestBody
+     * @param headerMap
+     * @param retryTimes
+     * @param readTimeOut
+     */
+    public static void putObjectFormUrl(URI url, Object requestBody, Map<String, String> headerMap, int retryTimes,
+                                        int readTimeOut) {
+        HttpHeaders httpHeaders = setHttpHeaders(headerMap);
+        if (!httpHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            httpHeaders.add(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE);
+
+        }
+        HttpEntity<Object> formEntity = new HttpEntity<>(requestBody, httpHeaders);
+        RestTemplate restTemplate =  getRestTemplate(readTimeOut);
+        boolean success = false;
+        while (!success && retryTimes >= 0) {
+            try {
+                restTemplate.put(url, formEntity);
+                success = true;
+            } catch (Exception ex) {
+                log.error("rest template postObjectForUrl error: ", ex);
+                retryTimes--;
+                if (retryTimes == 0) {
+                    throw ex;
+                }
+            }
+        }
+    }
+
+    /**
+     * @param url
+     * @param requestBody
+     * @param headerMap
+     * @param responseType
+     * @param retryTimes
+     * @param readTimeOut
+     *
+     * @return
+     */
+    public static ResponseEntity<?> deleteObjectFormUrl(String url, Object requestBody, Map<String, String> headerMap,
+                                                        Class<?> responseType, int retryTimes, int readTimeOut) {
+        HttpHeaders httpHeaders = setHttpHeaders(headerMap);
+        if (!httpHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            httpHeaders.add(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE);
+
+        }
+        HttpEntity<Object> formEntity = new HttpEntity<>(requestBody, httpHeaders);
+        RestTemplate restTemplate =  getRestTemplate(readTimeOut);
+        boolean success = false;
+        ResponseEntity<?> result = null;
+        while (!success && retryTimes >= 0) {
+            try {
+                result = restTemplate.exchange(url, HttpMethod.DELETE, formEntity, responseType);
+                success = true;
+            } catch (Exception ex) {
+                log.error("rest template postObjectForUrl error: ", ex);
+                retryTimes--;
+                if (retryTimes == 0) {
+                    throw ex;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @param url
+     * @param requestBody
+     * @param headerMap
+     * @param responseType
+     * @param retryTimes
+     * @param readTimeOut
+     *
+     * @return
+     */
+    public static ResponseEntity<?> putObjectFormUrl(URI url, Object requestBody, Map<String, String> headerMap,
+                                                     Class<?> responseType, int retryTimes, int readTimeOut) {
+        HttpHeaders httpHeaders = setHttpHeaders(headerMap);
+        if (!httpHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            httpHeaders.add(HttpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE);
+
+        }
+        HttpEntity<Object> formEntity = new HttpEntity<Object>(requestBody, httpHeaders);
+        RestTemplate restTemplate =  getRestTemplate(readTimeOut);
+        boolean success = false;
+        ResponseEntity<?> result = null;
+        while (!success && retryTimes >= 0) {
+            try {
+                result = restTemplate.exchange(url, HttpMethod.PUT, formEntity, responseType);
+                success = true;
+            } catch (Exception ex) {
+                log.error("rest template postObjectForUrl error: ", ex);
+                retryTimes--;
+                if (retryTimes == 0) {
+                    throw ex;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @param headerMap
+     *
+     * @return
+     */
+    private static HttpHeaders setHttpHeaders(Map<String, String> headerMap) {
+        HttpHeaders httpHeaders = new HttpHeaders();
+        if (headerMap != null) {
+            Set<String> keySet = headerMap.keySet();
+            for (Iterator<String> it = keySet.iterator(); it.hasNext();) {
+                String key = it.next();
+                String value = headerMap.get(key);
+                httpHeaders.add(key, value);
+            }
+        }
+        return  httpHeaders;
+    }
+
+    /**
+     * @param timeout
+     *
+     * @return
+     */
+    public static RestTemplate getRestTemplate(int timeout, boolean ...ssl) {
+        RestTemplate restTemplate = null;
+        if (ssl.length > 0 && ssl[0]) {
+            HttpsSSLRequestFactory factory = new HttpsSSLRequestFactory();
+            factory.setReadTimeout(timeout);
+            factory.setConnectTimeout(defaultReadTimeOut);
+            restTemplate = new RestTemplate(factory);
+        } else {
+            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
+            factory.setConnectionRequestTimeout(timeout);
+            factory.setConnectTimeout(defaultReadTimeOut);
+            restTemplate = new RestTemplate(factory);
+        }
+        restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
+        return  restTemplate;
+    }
+
+    public static List<String> getAsStringList(String url) {
+        HttpClient httpClient = HttpClients.createDefault();
+        HttpGet httpGet = new HttpGet(url);
+        for (int i = 0; i < defaultRetryTimes; ++i) {
+            HttpResponse response = null;
+
+            try {
+                response = httpClient.execute(httpGet);
+            } catch (IOException e) {
+                log.error("Failed to download file,url: " + url, e);
+            }
+
+            if (response != null && response.getStatusLine().getStatusCode() == 200) {
+                InputStreamReader reader = null;
+
+                try {
+                    reader = new InputStreamReader(response.getEntity().getContent());
+                } catch (IOException var23) {
+                    continue;
+                }
+                BufferedReader bufferedReader = new BufferedReader(reader);
+                String line = "";
+                ArrayList list = new ArrayList();
+                try {
+                    while ((line = bufferedReader.readLine()) != null) {
+                        list.add(line.trim());
+                    }
+                } catch (IOException e) {
+                    log.error("bufferedReader.readLine error", e);
+                } finally {
+                    try {
+                        reader.close();
+                        bufferedReader.close();
+                    } catch (IOException var19) {
+                        var19.printStackTrace();
+                    }
+                }
+                return list;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 根据URL下载文件
+     * @param url
+     * @param headers
+     * @param paramMap
+     * @param file
+     */
+    public static void downloadByResourceUrl(String url, Map<String, String> headers, Map<String, String> paramMap,
+                                             File file) {
+        HttpClient httpClient = HttpClients.createDefault();
+        InputStream in = null;
+        try {
+            if (paramMap != null) {
+                StringBuilder buffer = new StringBuilder(url);
+                for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+                    appendParam(buffer, entry.getKey(), entry.getValue());
+                }
+                url = buffer.toString();
+            }
+            HttpGet httpGet = new HttpGet(url);
+            if (headers != null && headers.size() > 0) {
+                for (String key : headers.keySet()) {
+                    httpGet.addHeader(key, headers.get(key));
+                }
+            }
+            HttpResponse response = httpClient.execute(httpGet);
+            FileOutputStream out = new FileOutputStream(file);
+            int code = response.getStatusLine().getStatusCode();
+            org.apache.http.HttpEntity entity = response.getEntity();
+            in = entity == null ? null : entity.getContent();
+            if (HttpStatus.SC_OK == code && in != null && entity != null) {
+                byte[] data = readInputStream(entity.getContent());
+                out.write(data);
+                out.close();
+            } else {
+                log.error("downloadByResourceUrl error code. code:{}, url:{}", code, url);
+                return;
+            }
+        } catch (Exception e) {
+            log.error("downloadByResourceUrl error. url:{} ", url, e);
+            return;
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    private static void appendParam(StringBuilder buffer, String name, String value) {
+        if (!buffer.toString().contains("?")) {
+            buffer.append("?");
+        }
+        if (!(buffer.charAt(buffer.length() - 1) == '?')) {
+            buffer.append("&");
+        }
+        try {
+            buffer.append(URLEncoder.encode(name, "UTF-8"));
+            buffer.append("=");
+            buffer.append(URLEncoder.encode(value, "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /***
+     * 读取输入流
+     * @param inputStream
+     * @return
+     * @throws IOException
+     */
+    private static byte[] readInputStream(InputStream inputStream) throws IOException {
+        byte[] buffer = new byte[1024];
+        int len;
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        while ((len = inputStream.read(buffer)) != -1) {
+            bos.write(buffer, 0, len);
+        }
+        bos.close();
+        return bos.toByteArray();
+    }
+}

+ 128 - 0
webchat-common/src/main/java/com/webchat/common/util/HttpsSSLRequestFactory.java

@@ -0,0 +1,128 @@
+package com.webchat.common.util;
+
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+
+import javax.net.ssl.*;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.cert.X509Certificate;
+
+/**
+ * SSL 请求工厂
+ */
+public class HttpsSSLRequestFactory extends SimpleClientHttpRequestFactory {
+
+    @Override
+    protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
+        try {
+            if (!(connection instanceof HttpsURLConnection)) {
+                throw new RuntimeException("An instance of HttpsURLConnection is expected");
+            }
+
+            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
+
+            TrustManager[] trustAllCerts = new TrustManager[]{
+                    new X509TrustManager() {
+                        @Override
+                        public X509Certificate[] getAcceptedIssuers() {
+                            X509Certificate[] x509Certificates = new X509Certificate[0];
+                            return x509Certificates;
+                        }
+
+                        @Override
+                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
+                        }
+
+                        @Override
+                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
+                        }
+
+                    }
+            };
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+            httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
+
+            httpsConnection.setHostnameVerifier((s, sslSession) -> true);
+
+            super.prepareConnection(httpsConnection, httpMethod);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
+     * see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
+     */
+    // SSLSocketFactory用于创建 SSLSockets
+    private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
+
+        private final SSLSocketFactory delegate;
+
+        public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
+            this.delegate = delegate;
+        }
+
+        // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
+        // 这些默认的服务的最低质量要求保密保护和服务器身份验证
+        @Override
+        public String[] getDefaultCipherSuites() {
+            return delegate.getDefaultCipherSuites();
+        }
+
+        // 返回的密码套件可用于SSL连接启用的名字
+        @Override
+        public String[] getSupportedCipherSuites() {
+            return delegate.getSupportedCipherSuites();
+        }
+
+
+        @Override
+        public Socket createSocket(final Socket socket, final String host, final int port,
+                                   final boolean autoClose) throws IOException {
+            final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
+            return overrideProtocol(underlyingSocket);
+        }
+
+
+        @Override
+        public Socket createSocket(final String host, final int port) throws IOException {
+            final Socket underlyingSocket = delegate.createSocket(host, port);
+            return overrideProtocol(underlyingSocket);
+        }
+
+        @Override
+        public Socket createSocket(final String host, final int port, final InetAddress localAddress,
+                                   final int localPort) throws
+                IOException {
+            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
+            return overrideProtocol(underlyingSocket);
+        }
+
+        @Override
+        public Socket createSocket(final InetAddress host, final int port) throws IOException {
+            final Socket underlyingSocket = delegate.createSocket(host, port);
+            return overrideProtocol(underlyingSocket);
+        }
+
+        @Override
+        public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
+                                   final int localPort) throws
+                IOException {
+            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
+            return overrideProtocol(underlyingSocket);
+        }
+
+        private Socket overrideProtocol(final Socket socket) {
+            if (!(socket instanceof SSLSocket)) {
+                throw new RuntimeException("An instance of SSLSocket is expected");
+            }
+//            ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1"});
+            ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"});
+            return socket;
+        }
+    }
+}

+ 30 - 0
webchat-common/src/main/java/com/webchat/common/util/IDGenerateUtil.java

@@ -0,0 +1,30 @@
+package com.webchat.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Random;
+import java.util.UUID;
+
+/***
+ * ID 生成器
+ */
+public class IDGenerateUtil {
+
+    public static String createId(String prefix) {
+        if (StringUtils.isNotBlank(prefix)) {
+            return prefix.concat("_").concat(uuid());
+        }
+        return uuid();
+    }
+    /**
+     * 生成验证码, 生成4为长度验证码
+     * @return
+     */
+    public static String createCode() {
+        return String.format("%04d",new Random().nextInt(9999));
+    }
+
+    public static String uuid() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+}

+ 19 - 0
webchat-common/src/main/java/com/webchat/common/util/JsonExtractorFromMarkdown.java

@@ -0,0 +1,19 @@
+package com.webchat.common.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JsonExtractorFromMarkdown {
+
+
+    public static String getJson(String markdownText) {
+        // 正则表达式用于匹配```json```标记的JSON代码块
+        String regex = "```json\\s*\\n(.*?)\\n```";
+        Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
+        Matcher matcher = pattern.matcher(markdownText);
+        if (matcher.find()) {
+            return matcher.group(1).trim();
+        }
+        return null;
+    }
+}

+ 118 - 0
webchat-common/src/main/java/com/webchat/common/util/JsonUtil.java

@@ -0,0 +1,118 @@
+package com.webchat.common.util;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+@Slf4j
+public class JsonUtil {
+
+    private static ObjectMapper om = new ObjectMapper();
+
+    private static JsonFactory factory = new JsonFactory();
+
+    static {
+        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
+    }
+
+    /**
+     * @param jsonStr
+     * @param clazz
+     * @param <T>
+     *
+     * @return
+     */
+    public static <T> T fromJson(String jsonStr, Class<T> clazz) {
+        T value = null;
+        try {
+            value = om.readValue(jsonStr, clazz);
+        } catch (Exception e) {
+            log.error("fromJson error,jsonStr:" + jsonStr + ",class:" + clazz.getName() + ",exception:"
+                    + e.getMessage());
+            return null;
+        }
+        return value;
+    }
+
+    /**
+     * @param jsonStr
+     * @param valueTypeRef
+     * @param <T>
+     *
+     * @return
+     */
+    public static <T> T fromJson(String jsonStr, TypeReference<?> valueTypeRef) {
+        T value = null;
+        try {
+            value = (T) om.readValue(jsonStr, valueTypeRef);
+        } catch (Exception e) {
+            log.error("fromJson error,jsonStr:" + jsonStr + ",class:" + valueTypeRef.getType() + ",exception:"
+                    + e.getMessage());
+            throw new RuntimeException(e);
+        }
+        return value;
+    }
+
+    /**
+     * @param obj
+     * @param prettyPrinter
+     *
+     * @return
+     */
+    public static String toJson(Object obj, boolean prettyPrinter) {
+        if (obj == null) {
+            return null;
+        }
+        StringWriter sw = new StringWriter();
+        try {
+            JsonGenerator jg = factory.createGenerator(sw);
+            if (prettyPrinter) {
+                jg.useDefaultPrettyPrinter();
+            }
+            om.writeValue(jg, obj);
+        } catch (IOException e) {
+            log.error("toJson error,jsonObj:" + obj.getClass().getName() + ",prettyPrinter:" + prettyPrinter
+                    + ",exception:" + e.getMessage());
+            throw new RuntimeException(e);
+        }
+        return sw.toString();
+    }
+
+    /**
+     * @param object
+     *
+     * @return
+     */
+    public static String toJsonString(Object object) {
+        try {
+            String valueAsString = om.writeValueAsString(object);
+            return valueAsString;
+        } catch (Exception e) {
+            log.error("toJson error,jsonObj:" + object.getClass().getName() + ",exception:" + e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @param object
+     *
+     * @return
+     */
+    public static String toJsonStringWithDefaultPrettyPrinter(Object object) {
+        try {
+            String valueAsString = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
+            return valueAsString;
+        } catch (Exception e) {
+            log.error("toJson error,jsonObj:" + object.getClass().getName() + ",exception:" + e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 52 - 0
webchat-common/src/main/java/com/webchat/common/util/ListUtil.java

@@ -0,0 +1,52 @@
+package com.webchat.common.util;
+
+import com.google.common.base.Joiner;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+/**
+ * @Author: 程序员七七
+ * @Dae: 3.6.22 10:53 下午
+ */
+public class ListUtil {
+
+    public static void shuffle(List list) {
+        int size = list.size();
+        Random random = new Random();
+        for(int i = 0; i < size; i++) {
+            // 获取随机位置
+            int randomPos = random.nextInt(size);
+            // 当前元素与随机元素交换
+            Object temp = list.get(i);
+            list.set(i, list.get(randomPos));
+            list.set(randomPos, temp);
+        }
+    }
+
+    public static String list2String(List<Long> list, String separator) {
+        return Joiner.on(separator).join(list);
+    }
+
+    public static String stringList2String(List<String> list, String separator) {
+        return Joiner.on(separator).join(list);
+    }
+
+    public static List<Long> string2LongList(String str) {
+        if (StringUtils.isBlank(str)) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(str.split(",")).map(Long::valueOf).collect(Collectors.toList());
+    }
+
+    public static List<String> string2List(String str) {
+        if (StringUtils.isBlank(str)) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(str.split(",")).collect(Collectors.toList());
+    }
+}

+ 88 - 0
webchat-common/src/main/java/com/webchat/common/util/MD5Utils.java

@@ -0,0 +1,88 @@
+package com.webchat.common.util;
+
+import com.webchat.common.constants.WebConstant;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.UUID;
+
+public class MD5Utils {
+
+    /**
+     * 随机产生一个MD5 16进制的字符串 跟date uuid有关。
+     *
+     * @param params
+     *
+     * @return
+     */
+    public static String randomMD5HexString(String... params) {
+        StringBuffer bf = new StringBuffer();
+        bf.append(DateUtils.getCurrentFormatDate(DateUtils.YYYYMMDDHHMMSSSS));
+
+        if (params != null && params.length > 0) {
+            for (String param : params) {
+                bf.append(param);
+            }
+        }
+        bf.append(UUID.randomUUID().toString());
+        MessageDigest messageDigest = null;
+        try {
+            messageDigest = MessageDigest.getInstance("MD5");
+            byte[] result = messageDigest.digest(bf.toString().getBytes("UTF-8"));
+            return str2HexStr(result);
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static void main(String[] args) {
+        System.out.println("生成密码:"+MD5Utils.md5("admin666666".concat(WebConstant.MD5_SALT)));
+    }
+
+    public static String md5(String src) {
+        if (StringUtils.isBlank(src)) {
+            return null;
+        }
+        MessageDigest messageDigest = null;
+        try {
+            messageDigest = MessageDigest.getInstance("MD5");
+            byte[] result = messageDigest.digest(src.getBytes("UTF-8"));
+            return str2HexStr(result);
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static String md5(byte[] bytes) {
+        MessageDigest messageDigest = null;
+        try {
+            messageDigest = MessageDigest.getInstance("MD5");
+            byte[] result = messageDigest.digest(bytes);
+            return str2HexStr(result);
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private static String str2HexStr(byte[] bs) {
+        StringBuilder sb = new StringBuilder();
+        char[] chars = "0123456789abcdef".toCharArray();
+        int bit;
+        for (int i = 0; i < bs.length; i++) {
+            bit = (bs[i] & 0x0f0) >> 4;
+            sb.append(chars[bit]);
+            bit = bs[i] & 0x0f;
+            sb.append(chars[bit]);
+        }
+        return sb.toString();
+    }
+}

+ 91 - 0
webchat-common/src/main/java/com/webchat/common/util/PicValidCodeUtil.java

@@ -0,0 +1,91 @@
+package com.webchat.common.util;
+
+import com.webchat.common.util.obj.PicValidCodeResult;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Random;
+
+/**
+ * @Author: 程序员七七
+ * @Date: 12.12.21 1:09 上午
+ *
+ * 图形验证码
+ */
+public class PicValidCodeUtil {
+
+    /***
+     * 定义图片的width
+     */
+    private static int width = 90;
+    /***
+     * 定义图片的height
+     */
+    private static int height = 20;
+    /***
+     * 定义图片上显示验证码的个数
+     */
+    private static int codeCount = 4;
+    private static int xx = 15;
+    private static int fontHeight = 18;
+    private static  int codeY = 16;
+    private static char[] codeSequence = { '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' };
+    private static char[] numberCodeSequence = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
+
+    /**
+     * 生成一个map集合
+     * code为生成的验证码
+     * codePic为生成的验证码BufferedImage对象
+     * onlyNumber true 生成纯数字的验证吗,false 生成字母 + 数字
+     * @return
+     */
+    public static PicValidCodeResult generateCodeAndPic(boolean onlyNumber) {
+        // 定义图像buffer
+        BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        Graphics gd = buffImg.getGraphics();
+        // 创建一个随机数生成器类
+        Random random = new Random();
+        // 将图像填充为白色
+        gd.setColor(Color.WHITE);
+        gd.fillRect(0, 0, width, height);
+        // 创建字体,字体的大小应该根据图片的高度来定。
+        Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
+        // 设置字体。
+        gd.setFont(font);
+        // 画边框。
+//        gd.setColor(Color.BLACK);
+        gd.drawRect(0, 0, width - 1, height - 1);
+        // 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
+        gd.setColor(Color.BLACK);
+        for (int i = 0; i < 30; i++) {
+            int x = random.nextInt(width);
+            int y = random.nextInt(height);
+            int xl = random.nextInt(12);
+            int yl = random.nextInt(12);
+            gd.drawLine(x, y, x + xl, y + yl);
+        }
+        // randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
+        StringBuffer randomCode = new StringBuffer();
+        int red = 0, green = 0, blue = 0;
+        // 随机产生codeCount数字的验证码。
+        for (int i = 0; i < codeCount; i++) {
+            // 得到随机产生的验证码数字。
+            String code = "";
+            if (onlyNumber) {
+                code = String.valueOf(numberCodeSequence[random.nextInt(10)]);
+            } else {
+                code = String.valueOf(codeSequence[random.nextInt(26)]);
+            }
+            // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
+            red = random.nextInt(255);
+            green = random.nextInt(255);
+            blue = random.nextInt(255);
+            // 用随机产生的颜色将验证码绘制到图像中。
+            gd.setColor(new Color(red, green, blue));
+            gd.drawString(code, (i + 1) * xx, codeY);
+            // 将产生的四个随机数组合在一起。
+            randomCode.append(code);
+        }
+        return new PicValidCodeResult(randomCode.toString(), buffImg);
+    }
+}

+ 213 - 0
webchat-common/src/main/java/com/webchat/common/util/QRCodeUtils.java

@@ -0,0 +1,213 @@
+package com.webchat.common.util;
+
+import com.google.zxing.*;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.OutputStream;
+import java.util.Hashtable;
+
+public class QRCodeUtils {
+
+    private static final String CHARSET = "utf-8";
+
+    private static final String FORMAT_NAME = "jpg";
+
+    // 二维码尺寸    
+    private static final int QRCODE_SIZE = 300;
+    // LOGO宽度    
+    private static final int WIDTH = 60;
+    // LOGO高度    
+    private static final int HEIGHT = 60;
+
+    private static BufferedImage createImage(String content, String imgPath,
+                                             boolean needCompress) throws Exception {
+        Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
+        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
+        hints.put(EncodeHintType.MARGIN, 1);
+        BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
+                BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
+        int width = bitMatrix.getWidth();
+        int height = bitMatrix.getHeight();
+        BufferedImage image = new BufferedImage(width, height,
+                BufferedImage.TYPE_INT_RGB);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000
+                        : 0xFFFFFFFF);
+            }
+        }
+        if (imgPath == null || "".equals(imgPath)) {
+            return image;
+        }
+        // 插入图片    
+        QRCodeUtils.insertImage(image, imgPath, needCompress);
+        return image;
+    }
+
+    /**
+     * 插入LOGO  
+     *
+     * @param source
+     *            二维码图片  
+     * @param imgPath
+     *            LOGO图片地址  
+     * @param needCompress
+     *            是否压缩  
+     * @throws Exception
+     */
+    private static void insertImage(BufferedImage source, String imgPath,
+                                    boolean needCompress) throws Exception {
+        File file = new File(imgPath);
+        if (!file.exists()) {
+            System.err.println(""+imgPath+"   该文件不存在!");
+            return;
+        }
+        Image src = ImageIO.read(new File(imgPath));
+        int width = src.getWidth(null);
+        int height = src.getHeight(null);
+        if (needCompress) { // 压缩LOGO    
+            if (width > WIDTH) {
+                width = WIDTH;
+            }
+            if (height > HEIGHT) {
+                height = HEIGHT;
+            }
+            Image image = src.getScaledInstance(width, height,
+                    Image.SCALE_SMOOTH);
+            BufferedImage tag = new BufferedImage(width, height,
+                    BufferedImage.TYPE_INT_RGB);
+            Graphics g = tag.getGraphics();
+            g.drawImage(image, 0, 0, null); // 绘制缩小后的图    
+            g.dispose();
+            src = image;
+        }
+        // 插入LOGO    
+        Graphics2D graph = source.createGraphics();
+        int x = (QRCODE_SIZE - width) / 2;
+        int y = (QRCODE_SIZE - height) / 2;
+        graph.drawImage(src, x, y, width, height, null);
+        Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
+        graph.setStroke(new BasicStroke(3f));
+        graph.draw(shape);
+        graph.dispose();
+    }
+
+    /**
+     * 生成二维码(内嵌LOGO)  
+     *
+     * @param content
+     *            内容  
+     * @param imgPath
+     *            LOGO地址  
+     * @param destPath
+     *            存放目录  
+     * @param needCompress
+     *            是否压缩LOGO  
+     * @throws Exception
+     */
+    public static String encode(String content, String imgPath, String destPath, String qrName,
+                              boolean needCompress) throws Exception {
+        BufferedImage image = QRCodeUtils.createImage(content, imgPath, needCompress);
+        mkdirs(destPath);
+        String file = destPath+"/"+qrName;
+        ImageIO.write(image, FORMAT_NAME, new File(file));
+        return file;
+    }
+
+    public static String encode(String content, String imgPath, String destPath, String qrName) throws Exception {
+        return encode(content, imgPath, destPath, qrName, true);
+    }
+
+    /**
+     * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)  
+     * @date 2013-12-11 上午10:16:36  
+     * @param destPath 存放目录  
+     */
+    public static void mkdirs(String destPath) {
+        File file = new File(destPath);
+        //当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)    
+        if (!file.exists() && !file.isDirectory()) {
+            file.mkdirs();
+        }
+    }
+
+    /**
+     * 生成二维码(内嵌LOGO)  
+     *
+     * @param content
+     *            内容  
+     * @param imgPath
+     *            LOGO地址  
+     * @param output
+     *            输出流  
+     * @param needCompress
+     *            是否压缩LOGO  
+     * @throws Exception
+     */
+    public static void encode(String content, String imgPath,
+                              OutputStream output, boolean needCompress) throws Exception {
+        BufferedImage image = QRCodeUtils.createImage(content, imgPath,
+                needCompress);
+        ImageIO.write(image, FORMAT_NAME, output);
+    }
+
+    /**
+     * 生成二维码  
+     *
+     * @param content
+     *            内容  
+     * @param output
+     *            输出流  
+     * @throws Exception
+     */
+    public static void encode(String content, OutputStream output)
+            throws Exception {
+        QRCodeUtils.encode(content, null, output, false);
+    }
+
+    /**
+     * 解析二维码  
+     *
+     * @param file
+     *            二维码图片  
+     * @return
+     * @throws Exception
+     */
+    public static String decode(File file) throws Exception {
+        BufferedImage image;
+        image = ImageIO.read(file);
+        if (image == null) {
+            return null;
+        }
+        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(
+                image);
+        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+        Result result;
+        Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();
+        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
+        result = new MultiFormatReader().decode(bitmap, hints);
+        String resultStr = result.getText();
+        return resultStr;
+    }
+
+    /**
+     * 解析二维码  
+     *
+     * @param path
+     *            二维码图片地址  
+     * @return
+     * @throws Exception
+     */
+    public static String decode(String path) throws Exception {
+        return QRCodeUtils.decode(new File(path));
+    }
+}  

+ 47 - 0
webchat-common/src/main/java/com/webchat/common/util/RemoteIpUtil.java

@@ -0,0 +1,47 @@
+package com.webchat.common.util;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.commons.lang3.StringUtils;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class RemoteIpUtil {
+
+    public static final String COMMA = ",";
+
+    private static final String REMOTE_IP_HEADER_FLAG = "x-forwarded-for";
+
+    /**
+     * get Real RemoteIp
+     *
+     * @param request
+     * @return
+     */
+    public static String getRemoteIpByRequest(HttpServletRequest request) {
+        String requestIp = request.getHeader(REMOTE_IP_HEADER_FLAG);
+
+        if (StringUtils.isBlank(requestIp)) {
+            return request.getRemoteAddr();
+        }
+
+        int lastIndex = requestIp.lastIndexOf(COMMA);
+        if (lastIndex < 0) {
+            return requestIp;
+        }
+        return requestIp.substring(lastIndex + 1);
+    }
+
+    /**
+     * 获取本地的HostName
+     *
+     * @return
+     */
+    public static String getLocalHostname() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 54 - 0
webchat-common/src/main/java/com/webchat/common/util/SpringContextUtil.java

@@ -0,0 +1,54 @@
+package com.webchat.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class SpringContextUtil implements ApplicationContextAware, InitializingBean {
+
+    // Spring应用上下文环境
+    private static ApplicationContext applicationContext;
+
+    public void afterPropertiesSet() throws Exception {
+        log.debug("after Properties Set, applicationContext:{},currentThread:{}",
+                applicationContext, Thread.currentThread());
+
+    }
+
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringContextUtil.applicationContext = applicationContext;
+        log.debug("set Application Context, applicationContext:{},currentThread:{}",
+                applicationContext, Thread.currentThread());
+
+    }
+
+    /**
+     * 获取对象 这里重写了bean方法,起主要作用
+     *
+     * @param name
+     * @return Object 一个以所给名字注册的bean的实例
+     * @throws BeansException
+     */
+    public static Object getBeanName(String name) throws BeansException {
+        return applicationContext.getBean(name);
+    }
+
+    public static <T> T getBean(Class<T> clazz) throws BeansException {
+        return applicationContext.getBean(clazz);
+    }
+
+    public static <T> T getBeanType(Class<T> requiredType) throws BeansException {
+        return applicationContext.getBean(requiredType);
+    }
+
+    public static Resource getResource(String location) {
+        return applicationContext.getResource(location);
+    }
+
+}

+ 21 - 0
webchat-common/src/main/java/com/webchat/common/util/StringUtil.java

@@ -0,0 +1,21 @@
+package com.webchat.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @Author: 程序员七七 : www.coderutil.com网站作者
+ * @Date: 22.3.22 11:23 下午
+ */
+public class StringUtil {
+
+
+    public static String handleSpecialHtmlTag(String content) {
+        if (StringUtils.isBlank(content)) {
+            return content;
+        }
+        content = content.replaceAll("&lt;br&gt;", "<br>");
+        content = content.replaceAll("&lt;b&gt;", "<b>");
+        return content;
+    }
+
+}

+ 72 - 0
webchat-common/src/main/java/com/webchat/common/util/ThreadPoolExecutorUtil.java

@@ -0,0 +1,72 @@
+package com.webchat.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.*;
+
+/**
+ * Created by 程序员七七 on 2021/12/3.
+ */
+@Slf4j
+public class ThreadPoolExecutorUtil {
+
+    /**
+     * 核心线程数
+     */
+    private static int corePoolSize = 20;
+
+    /**
+     * 最大线程数
+     */
+    private static int maximumPoolSize = corePoolSize * 5;
+
+    /**
+     * 空闲等待时间
+     */
+    private static long keepAliveTime = 60;
+
+    /**
+     * 等待队列大小
+     */
+    private static int queueSize = 1024 * 2;
+
+    /**
+     * 如果poolSize < coreSize, 线程优先加入corePool中;
+     * 如果poolSize >= coreSize, 线程加入到queue中;
+     * 如果queue满了, 就创建新线程, 直到maxPool大小;
+     * 如果poolSize > coreSize, 大于部分的线程会在keepAliveTime没有接到工作任务后销毁。
+     **/
+    private static ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
+            TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueSize), new ThreadPoolExecutor.CallerRunsPolicy());
+
+    /**
+     * 执行task
+     *
+     * @param task
+     */
+    public static void execute(Runnable task) {
+        pool.execute(task);
+    }
+
+    public static <T> Future<T> submit(Callable<T> task) {
+        return pool.submit(task);
+    }
+
+    /**
+     * 线程池状态
+     */
+    public static void printStatus() {
+        int active = pool.getActiveCount();
+        int queue = pool.getQueue().size();
+        long complete = pool.getCompletedTaskCount();
+        long task = pool.getTaskCount();
+        if (active > 0 || queue > 0) {
+            log.debug("[busy] ThreadPool active:" + active + ",queue:" + queue + ",complete:" + complete + ",task:"
+                    + task);
+        } else {
+            log.debug("[free] ThreadPool is empty. active:" + active + ",queue:" + queue + ",complete:" + complete
+                    + ",task:" + task);
+        }
+    }
+}
+

+ 27 - 0
webchat-common/src/main/java/com/webchat/common/util/TransactionSyncManagerUtil.java

@@ -0,0 +1,27 @@
+package com.webchat.common.util;
+
+import org.springframework.transaction.support.TransactionSynchronizationAdapter;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 事务后置处理
+ */
+public class TransactionSyncManagerUtil {
+
+    private static ExecutorService executorService = Executors.newFixedThreadPool(10);
+
+
+    public static void registerSynchronization(final Runnable task) {
+
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
+            @Override
+            public void afterCommit() {
+                executorService.execute(task);
+            }
+        });
+    }
+
+}

+ 151 - 0
webchat-common/src/main/java/com/webchat/common/util/llm/DeepSeekAIClient.java

@@ -0,0 +1,151 @@
+package com.webchat.common.util.llm;
+
+import com.google.gson.Gson;
+import com.webchat.domain.vo.llm.ChatCompletionRequest;
+import com.webchat.domain.vo.llm.ChatCompletionResponse;
+import com.webchat.domain.vo.llm.ChatCompletionStreamResponse;
+import com.webchat.domain.vo.llm.ModelsList;
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import okhttp3.OkHttpClient;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2024/3/21 01:34
+ * @description
+ */
+public class DeepSeekAIClient {
+
+    private static final String DEFAULT_BASE_URL = "https://api.deepseek.com";
+
+    private static final String CHAT_COMPLETION_SUFFIX = "/chat/completions";
+    private static final String MODELS_SUFFIX = "/models";
+    private static final String FILES_SUFFIX = "/files";
+
+    private String baseUrl;
+    private String apiKey;
+
+    public DeepSeekAIClient(String apiKey) {
+        this(apiKey, DEFAULT_BASE_URL);
+    }
+
+    public DeepSeekAIClient(String apiKey, String baseUrl) {
+        this.apiKey = apiKey;
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        this.baseUrl = baseUrl;
+    }
+
+    public String getChatCompletionUrl() {
+        return baseUrl + CHAT_COMPLETION_SUFFIX;
+    }
+
+    public String getModelsUrl() {
+        return baseUrl + MODELS_SUFFIX;
+    }
+
+    public String getFilesUrl() {
+        return baseUrl + FILES_SUFFIX;
+    }
+
+    public String getApiKey() {
+        return apiKey;
+    }
+
+    public ModelsList ListModels() throws IOException {
+        OkHttpClient client = new OkHttpClient();
+        okhttp3.Request request = new okhttp3.Request.Builder()
+                .url(getModelsUrl())
+                .addHeader("Authorization", "Bearer " + getApiKey())
+                .build();
+        try {
+            okhttp3.Response response = client.newCall(request).execute();
+            String body = response.body().string();
+            Gson gson = new Gson();
+            return gson.fromJson(body, ModelsList.class);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw e;
+        }
+    }
+
+
+    public ChatCompletionResponse ChatCompletion(ChatCompletionRequest request) throws IOException {
+        request.stream = false;
+        OkHttpClient client = new OkHttpClient.Builder()
+                .connectTimeout(2, TimeUnit.MINUTES)
+                .readTimeout(2, TimeUnit.MINUTES)
+                .writeTimeout(2, TimeUnit.MINUTES)
+                .build();
+        okhttp3.MediaType mediaType = okhttp3.MediaType.parse("application/json");
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, new Gson().toJson(request));
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(getChatCompletionUrl())
+                .addHeader("Authorization", "Bearer " + getApiKey())
+                .addHeader("Content-Type", "application/json")
+                .post(body)
+                .build();
+        try {
+            okhttp3.Response response = client.newCall(httpRequest).execute();
+            String responseBody = response.body().string();
+            System.out.println(responseBody);
+            Gson gson = new Gson();
+            return gson.fromJson(responseBody, ChatCompletionResponse.class);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw e;
+        }
+    }
+
+    // return a stream of ChatCompletionStreamResponse
+    public Flowable<ChatCompletionStreamResponse> ChatCompletionStream(ChatCompletionRequest request) throws IOException {
+        request.stream = true;
+        OkHttpClient client = new OkHttpClient();
+        okhttp3.MediaType mediaType = okhttp3.MediaType.parse("application/json");
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, new Gson().toJson(request));
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(getChatCompletionUrl())
+                .addHeader("Authorization", "Bearer " + getApiKey())
+                .addHeader("Content-Type", "application/json")
+                .post(body)
+                .build();
+        okhttp3.Response response = client.newCall(httpRequest).execute();
+        if (response.code() != 200) {
+            throw new RuntimeException("Failed to start stream: " + response.body().string());
+        }
+
+        // get response body line by line
+        return Flowable.create(emitter -> {
+            okhttp3.ResponseBody responseBody = response.body();
+            if (responseBody == null) {
+                emitter.onError(new RuntimeException("Response body is null"));
+                return;
+            }
+            String line;
+            while ((line = responseBody.source().readUtf8Line()) != null) {
+                if (line.startsWith("data:")) {
+                    line = line.substring(5);
+                    line = line.trim();
+                }
+                if (Objects.equals(line, "[DONE]")) {
+                    emitter.onComplete();
+                    return;
+                }
+                line = line.trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+                Gson gson = new Gson();
+                ChatCompletionStreamResponse streamResponse = gson.fromJson(line, ChatCompletionStreamResponse.class);
+                emitter.onNext(streamResponse);
+            }
+            emitter.onComplete();
+        }, BackpressureStrategy.BUFFER);
+    }
+}

+ 151 - 0
webchat-common/src/main/java/com/webchat/common/util/llm/MoonShotAIClient.java

@@ -0,0 +1,151 @@
+package com.webchat.common.util.llm;
+
+import com.google.gson.Gson;
+import com.webchat.domain.vo.llm.ChatCompletionRequest;
+import com.webchat.domain.vo.llm.ChatCompletionResponse;
+import com.webchat.domain.vo.llm.ChatCompletionStreamResponse;
+import com.webchat.domain.vo.llm.ModelsList;
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import okhttp3.OkHttpClient;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2024/3/21 01:34
+ * @description
+ */
+public class MoonShotAIClient {
+
+    private static final String DEFAULT_BASE_URL = "https://api.moonshot.cn/v1";
+
+    private static final String CHAT_COMPLETION_SUFFIX = "/chat/completions";
+    private static final String MODELS_SUFFIX = "/models";
+    private static final String FILES_SUFFIX = "/files";
+
+    private String baseUrl;
+    private String apiKey;
+
+    public MoonShotAIClient(String apiKey) {
+        this(apiKey, DEFAULT_BASE_URL);
+    }
+
+    public MoonShotAIClient(String apiKey, String baseUrl) {
+        this.apiKey = apiKey;
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        this.baseUrl = baseUrl;
+    }
+
+    public String getChatCompletionUrl() {
+        return baseUrl + CHAT_COMPLETION_SUFFIX;
+    }
+
+    public String getModelsUrl() {
+        return baseUrl + MODELS_SUFFIX;
+    }
+
+    public String getFilesUrl() {
+        return baseUrl + FILES_SUFFIX;
+    }
+
+    public String getApiKey() {
+        return apiKey;
+    }
+
+    public ModelsList ListModels() throws IOException {
+        okhttp3.OkHttpClient client = new okhttp3.OkHttpClient();
+        okhttp3.Request request = new okhttp3.Request.Builder()
+                .url(getModelsUrl())
+                .addHeader("Authorization", "Bearer " + getApiKey())
+                .build();
+        try {
+            okhttp3.Response response = client.newCall(request).execute();
+            String body = response.body().string();
+            Gson gson = new Gson();
+            return gson.fromJson(body, ModelsList.class);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw e;
+        }
+    }
+
+
+    public ChatCompletionResponse ChatCompletion(ChatCompletionRequest request) throws IOException {
+        request.stream = false;
+        okhttp3.OkHttpClient client = new OkHttpClient.Builder()
+                .connectTimeout(2, TimeUnit.MINUTES)
+                .readTimeout(2, TimeUnit.MINUTES)
+                .writeTimeout(2, TimeUnit.MINUTES)
+                .build();
+        okhttp3.MediaType mediaType = okhttp3.MediaType.parse("application/json");
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, new Gson().toJson(request));
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(getChatCompletionUrl())
+                .addHeader("Authorization", "Bearer " + getApiKey())
+                .addHeader("Content-Type", "application/json")
+                .post(body)
+                .build();
+        try {
+            okhttp3.Response response = client.newCall(httpRequest).execute();
+            String responseBody = response.body().string();
+            System.out.println(responseBody);
+            Gson gson = new Gson();
+            return gson.fromJson(responseBody, ChatCompletionResponse.class);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw e;
+        }
+    }
+
+    // return a stream of ChatCompletionStreamResponse
+    public Flowable<ChatCompletionStreamResponse> ChatCompletionStream(ChatCompletionRequest request) throws IOException {
+        request.stream = true;
+        okhttp3.OkHttpClient client = new okhttp3.OkHttpClient();
+        okhttp3.MediaType mediaType = okhttp3.MediaType.parse("application/json");
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, new Gson().toJson(request));
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(getChatCompletionUrl())
+                .addHeader("Authorization", "Bearer " + getApiKey())
+                .addHeader("Content-Type", "application/json")
+                .post(body)
+                .build();
+        okhttp3.Response response = client.newCall(httpRequest).execute();
+        if (response.code() != 200) {
+            throw new RuntimeException("Failed to start stream: " + response.body().string());
+        }
+
+        // get response body line by line
+        return Flowable.create(emitter -> {
+            okhttp3.ResponseBody responseBody = response.body();
+            if (responseBody == null) {
+                emitter.onError(new RuntimeException("Response body is null"));
+                return;
+            }
+            String line;
+            while ((line = responseBody.source().readUtf8Line()) != null) {
+                if (line.startsWith("data:")) {
+                    line = line.substring(5);
+                    line = line.trim();
+                }
+                if (Objects.equals(line, "[DONE]")) {
+                    emitter.onComplete();
+                    return;
+                }
+                line = line.trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+                Gson gson = new Gson();
+                ChatCompletionStreamResponse streamResponse = gson.fromJson(line, ChatCompletionStreamResponse.class);
+                emitter.onNext(streamResponse);
+            }
+            emitter.onComplete();
+        }, BackpressureStrategy.BUFFER);
+    }
+}

+ 19 - 0
webchat-common/src/main/java/com/webchat/common/util/obj/PicValidCodeResult.java

@@ -0,0 +1,19 @@
+package com.webchat.common.util.obj;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.awt.image.BufferedImage;
+
+/**
+ * 验证码生成结果
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PicValidCodeResult {
+
+    private String code;
+    private BufferedImage image;
+}

BIN
webchat-common/target/classes/com/webchat/bean/APIPageResponseBean.class


BIN
webchat-common/target/classes/com/webchat/bean/APIResponseBean.class


BIN
webchat-common/target/classes/com/webchat/bean/APIResponseBeanUtil.class


+ 0 - 13
webchat-connect/src/test/java/com/webchat/connect/WebchatPgcApplicationTests.java

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

+ 34 - 0
webchat-domain/src/main/java/com/webchat/domain/dto/UploadResultDTO.java

@@ -0,0 +1,34 @@
+package com.webchat.domain.dto;
+
+import lombok.Data;
+
+@Data
+public class UploadResultDTO {
+
+    private Boolean success;
+
+    /***
+     * 上传OSS URL
+     */
+    private String url;
+
+    /**
+     * 上传OSS后的随机文件名称
+     */
+    private String fileName;
+
+    /***
+     * 文件大小, KB
+     */
+    private Long fileSize;
+
+    private String extName;
+
+    public UploadResultDTO() {
+        this.success = true;
+    }
+
+    public UploadResultDTO(boolean success) {
+        this.success = success;
+    }
+}

+ 21 - 0
webchat-domain/src/main/java/com/webchat/domain/dto/queue/ArticleDelayConsumeMessageDTO.java

@@ -0,0 +1,21 @@
+package com.webchat.domain.dto.queue;
+
+import lombok.Data;
+
+/**
+ * @author 77
+ * @date 2024/11/13 00:00
+ */
+@Data
+public class ArticleDelayConsumeMessageDTO extends BaseQueueDTO {
+
+    /**
+     * 要推的具体文章
+     */
+    private Long articleId;
+
+    /**
+     * 公众号
+     */
+    private String publicAccount;
+}

+ 22 - 0
webchat-domain/src/main/java/com/webchat/domain/dto/queue/ArticleDelayMessageDTO.java

@@ -0,0 +1,22 @@
+package com.webchat.domain.dto.queue;
+
+import lombok.Data;
+
+/**
+ * @author 77
+ * @date 2024/11/13 00:00
+ */
+@Data
+public class ArticleDelayMessageDTO extends BaseDelayQueueDTO {
+
+
+    /**
+     * 公众号id
+     */
+    private String publicAccount;
+
+    /**
+     * 文章id
+     */
+    private Long articleId;
+}

+ 22 - 0
webchat-domain/src/main/java/com/webchat/domain/dto/queue/BaseDelayNormalQueueDTO.java

@@ -0,0 +1,22 @@
+package com.webchat.domain.dto.queue;
+
+import lombok.Data;
+
+import java.util.Set;
+
+/**
+ * @author 77
+ * @date 2024/11/12 23:38
+ */
+@Data
+public class BaseDelayNormalQueueDTO extends BaseQueueDTO {
+
+    private Set<String> messages;
+
+
+    public static BaseDelayNormalQueueDTO of(Set<String> messages) {
+        BaseDelayNormalQueueDTO mess = new BaseDelayNormalQueueDTO();
+        mess.setMessages(messages);
+        return mess;
+    }
+}

+ 16 - 0
webchat-domain/src/main/java/com/webchat/domain/dto/queue/BaseDelayQueueDTO.java

@@ -0,0 +1,16 @@
+package com.webchat.domain.dto.queue;
+
+import lombok.Data;
+
+/**
+ * Redis数据传输实体
+ */
+@Data
+public class BaseDelayQueueDTO extends BaseQueueDTO {
+
+    /**
+     * 消费实现
+     * 作为延迟队列zset的score字段,13位时间戳
+     */
+    private long time;
+}

+ 27 - 0
webchat-domain/src/main/java/com/webchat/domain/dto/queue/BaseQueueDTO.java

@@ -0,0 +1,27 @@
+package com.webchat.domain.dto.queue;
+
+import lombok.Data;
+
+import java.util.UUID;
+
+/**
+ * Redis数据传输实体
+ */
+@Data
+public class BaseQueueDTO {
+
+    /**
+     * 任务ID
+     */
+    private String taskId = UUID.randomUUID().toString().replaceAll("-", "");;
+
+    /**
+     * 被执行次数
+     */
+    private int retryTimes;
+
+    /**
+     * 重回队列的最大次数
+     */
+    private int backQueueTimes;
+}

+ 28 - 0
webchat-domain/src/main/java/com/webchat/domain/dto/queue/LotteryOrderQueueDTO.java

@@ -0,0 +1,28 @@
+package com.webchat.domain.dto.queue;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 00:41
+ * @description
+ */
+@Data
+public class LotteryOrderQueueDTO extends BaseQueueDTO {
+
+    /**
+     * 中奖用户id
+     */
+    private String userId;
+
+    /**
+     * 中奖商品id
+     */
+    private Long itemId;
+
+    /**
+     * 活动id
+     */
+    private String activityId;
+}

+ 40 - 0
webchat-domain/src/main/java/com/webchat/domain/vo/common/QueryOrderByCondition.java

@@ -0,0 +1,40 @@
+package com.webchat.domain.vo.common;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: 程序员王七七 https://www.coderutil.com 网站作者
+ * @Date: 2021-6-19 0019 17:53
+ * @Description: 无描述信息
+ */
+@Data
+public class QueryOrderByCondition {
+
+    /**
+     * 排序字段
+     */
+    private String field;
+
+    /**
+     * 排序类型:ASC / DESC
+     */
+    private String orderType;
+
+    public QueryOrderByCondition of(String field, String orderType) {
+        this.field = field;
+        this.orderType = orderType;
+        return this;
+    }
+
+    public List<QueryOrderByCondition> addAll(QueryOrderByCondition ... orders) {
+        List<QueryOrderByCondition> queryOrderByConditionList = new ArrayList<>();
+        for (QueryOrderByCondition order : orders) {
+            queryOrderByConditionList.add(order);
+        }
+        return queryOrderByConditionList;
+    }
+
+}

+ 18 - 0
webchat-domain/src/main/java/com/webchat/domain/vo/common/QueryPageCondition.java

@@ -0,0 +1,18 @@
+package com.webchat.domain.vo.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * @Author: 程序员王七七 https://www.coderutil.com 网站作者
+ * @Date: 2021-6-19 0019 17:54
+ * @Description: 无描述信息
+ */
+@Data
+@AllArgsConstructor
+public class QueryPageCondition {
+
+    private Integer pageNo = 1;
+
+    private Integer pageSize = 10;
+}

+ 13 - 0
webchat-domain/src/main/java/com/webchat/domain/vo/dto/AbstractBaseEsDTO.java

@@ -0,0 +1,13 @@
+package com.webchat.domain.vo.dto;
+
+import lombok.Data;
+
+
+/**
+ * ES 数据同步抽象消息结构
+ */
+@Data
+public class AbstractBaseEsDTO {
+
+    private Integer messageType;
+}

+ 50 - 0
webchat-domain/src/main/java/com/webchat/domain/vo/dto/AbstractESMessageDTO.java

@@ -0,0 +1,50 @@
+package com.webchat.domain.vo.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+
+/**
+ * ES 数据同步抽象消息结构
+ */
+@Data
+public class AbstractESMessageDTO {
+
+
+    private String id;
+
+    /**
+     * 消息分类
+     */
+    @JsonProperty("type")
+    private Integer type;
+
+    @JsonProperty("sender_id")
+    private String senderId;
+
+    @JsonProperty("proxy_sender_id")
+    private String proxySenderId;
+
+    @JsonProperty("receiver_id")
+    private String receiverId;
+
+    @JsonProperty("biz_id")
+    private String bizId;
+
+    @JsonProperty("content")
+    private String content;
+
+    @JsonProperty("data")
+    private String data;
+
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @JsonProperty("create_date")
+    private Date createDate;
+
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @JsonProperty("update_date")
+    private Date updateDate;
+}

+ 56 - 0
webchat-domain/src/main/java/com/webchat/domain/vo/dto/BaseMessageDTO.java

@@ -0,0 +1,56 @@
+package com.webchat.domain.vo.dto;
+
+import com.webchat.domain.dto.queue.BaseQueueDTO;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/13 00:35
+ * @description 基础消息
+ */
+@Data
+public class BaseMessageDTO extends BaseQueueDTO {
+
+    /**
+     * 消息分类
+     */
+    private String category;
+
+    /**
+     * 消息类型
+     */
+    private String type;
+
+    /***
+     * 消息正文
+     */
+    private String content;
+
+    /**
+     * 消息发送人
+     */
+    private String fromUserId;
+
+    /**
+     * 消息接收人
+     */
+    private String toUserId;
+
+    /**
+     * 是否读取
+     */
+    private Boolean isRead = false;
+
+    /**
+     * 消息发送时间
+     */
+    private Date createDate;
+
+    /**
+     * 消息读取时间
+     */
+    private Date readDate;
+}

+ 56 - 0
webchat-domain/src/main/java/com/webchat/domain/vo/dto/ChatMessageSearchResultDTO.java

@@ -0,0 +1,56 @@
+package com.webchat.domain.vo.dto;
+
+import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
+import com.webchat.domain.vo.response.publicaccount.ArticleBaseResponseVO;
+import lombok.Data;
+
+
+/**
+ * 聊天消息搜索结果
+ *
+ */
+@Data
+public class ChatMessageSearchResultDTO extends AbstractBaseEsDTO{
+
+    private Long id;
+
+    /***
+     * 发送人
+     */
+    private String sender;
+    private UserBaseResponseInfoVO senderUser;
+
+    /***
+     * 代理发送人(群聊真实消息发送人)点对点消息,proxySender为空
+     */
+    private String proxySender;
+    private UserBaseResponseInfoVO proxySenderUser;
+
+    /***
+     * 接收人
+     */
+    private String receiver;
+    private UserBaseResponseInfoVO receiverUser;
+
+    /**
+     * 消息类型
+     *
+     * @see com.webchat.common.enums.ChatMessageTypeEnum
+     */
+    private Integer type;
+
+    private ArticleBaseResponseVO article;
+
+    /***
+     * 正文
+     */
+    private String message;
+
+    /***
+     * 发送时间
+     */
+    private Long sendDate;
+
+    private Long updateDate;
+
+}

+ 14 - 0
webchat-domain/src/main/java/com/webchat/domain/vo/dto/CommentReplyMessageDTO.java

@@ -0,0 +1,14 @@
+package com.webchat.domain.vo.dto;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/13 01:00
+ * @description 评论回复消息体
+ */
+@Data
+public class CommentReplyMessageDTO extends ResourceMessageDTO {
+
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff