Browse Source

initialize

ken 4 weeks ago
commit
11494fbfb0
100 changed files with 4729 additions and 0 deletions
  1. 8 0
      .gitignore
  2. 51 0
      README.md
  3. 94 0
      api.md
  4. 22 0
      common/common-core/pom.xml
  5. 11 0
      common/common-core/src/main/java/cn/poyee/common/base/BasePageQuery.java
  6. 53 0
      common/common-core/src/main/java/cn/poyee/common/base/IBaseEnum.java
  7. 5 0
      common/common-core/src/main/java/cn/poyee/common/constraint/Constant.java
  8. 5 0
      common/common-core/src/main/java/cn/poyee/common/constraint/RoleConstant.java
  9. 31 0
      common/common-core/src/main/java/cn/poyee/common/constraint/ThreadPoolConstant.java
  10. 6 0
      common/common-core/src/main/java/cn/poyee/common/result/IResultCode.java
  11. 77 0
      common/common-core/src/main/java/cn/poyee/common/result/Result.java
  12. 101 0
      common/common-core/src/main/java/cn/poyee/common/result/ResultCode.java
  13. 62 0
      common/common-core/src/main/java/cn/poyee/common/util/DateUtil.java
  14. 114 0
      common/common-core/src/main/java/cn/poyee/common/util/IdWorker.java
  15. 112 0
      common/common-core/src/main/java/cn/poyee/common/util/JsonUtil.java
  16. 37 0
      common/common-core/src/main/java/cn/poyee/common/util/NetworkUtil.java
  17. 25 0
      common/common-core/src/main/java/cn/poyee/common/util/StrUtil.java
  18. 40 0
      common/common-logback/pom.xml
  19. 10 0
      common/common-logback/src/main/java/cn/poyee/common/logback/util/CanonicalHostNamePropertyDefiner.java
  20. 78 0
      common/common-logback/src/main/java/cn/poyee/common/logback/util/NetworkAddressUtil.java
  21. 0 0
      common/common-logback/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  22. 23 0
      common/common-logback/src/main/resources/logback-fluentd.xml
  23. 42 0
      common/common-logback/src/main/resources/logback.xml
  24. 48 0
      common/common-mqtt/pom.xml
  25. 150 0
      common/common-mqtt/src/main/java/cn/poyee/common/mqtt/config/MqttConfig.java
  26. 59 0
      common/common-mqtt/src/main/java/cn/poyee/common/mqtt/config/MqttGatewayComponent.java
  27. 26 0
      common/common-mqtt/src/main/java/cn/poyee/common/mqtt/handle/AbstractMqttMessageHandler.java
  28. 30 0
      common/common-mqtt/src/main/java/cn/poyee/common/mqtt/properties/MqttProperties.java
  29. 3 0
      common/common-mqtt/src/main/resources/META-INF/spring.factories
  30. 36 0
      common/common-mybatis/pom.xml
  31. 23 0
      common/common-mybatis/src/main/java/cn/poyee/common/mybatis/config/MybatisPlusConfig.java
  32. 57 0
      common/common-redis/pom.xml
  33. 73 0
      common/common-redis/src/main/java/cn/poyee/common/redis/cache/CacheKeyGenerator.java
  34. 39 0
      common/common-redis/src/main/java/cn/poyee/common/redis/cache/CustomRedisCacheManager.java
  35. 37 0
      common/common-redis/src/main/java/cn/poyee/common/redis/cache/RedissonConfig.java
  36. 71 0
      common/common-redis/src/main/java/cn/poyee/common/redis/kryo/KryoRedisSerializer.java
  37. 236 0
      common/common-redis/src/main/java/cn/poyee/common/redis/service/RedisService.java
  38. 2 0
      common/common-redis/src/main/resources/META-INF/spring.factories
  39. 40 0
      common/common-web/pom.xml
  40. 89 0
      common/common-web/src/main/java/cn/poyee/common/web/config/OkHttpConfiguration.java
  41. 28 0
      common/common-web/src/main/java/cn/poyee/common/web/exception/BusinessException.java
  42. 110 0
      common/common-web/src/main/java/cn/poyee/common/web/exception/GlobalExceptionHandler.java
  43. 164 0
      common/common-web/src/main/java/cn/poyee/common/web/util/OkHttpUtils.java
  44. 148 0
      common/common-web/src/main/java/cn/poyee/common/web/util/RandomUtil.java
  45. 4 0
      common/common-web/src/main/resources/META-INF/spring.factories
  46. 45 0
      common/pom.xml
  47. 135 0
      im-web/pom.xml
  48. 14 0
      im-web/src/main/java/cn/poyee/IMWebApplication.java
  49. 43 0
      im-web/src/main/java/cn/poyee/config/AMQPConfig.java
  50. 27 0
      im-web/src/main/java/cn/poyee/config/HandlerThreadPoolConfig.java
  51. 49 0
      im-web/src/main/java/cn/poyee/config/MetricsConfig.java
  52. 29 0
      im-web/src/main/java/cn/poyee/config/RabbitConsumer.java
  53. 55 0
      im-web/src/main/java/cn/poyee/config/SchedulerConfig.java
  54. 16 0
      im-web/src/main/java/cn/poyee/config/StompPrincipal.java
  55. 96 0
      im-web/src/main/java/cn/poyee/config/StompSocketConfig.java
  56. 48 0
      im-web/src/main/java/cn/poyee/config/ThreadPoolExecutorShutdownDefinition.java
  57. 52 0
      im-web/src/main/java/cn/poyee/config/bean/ContentCensorConfig.java
  58. 78 0
      im-web/src/main/java/cn/poyee/config/bean/RabbitMessageConfirmationHandler.java
  59. 55 0
      im-web/src/main/java/cn/poyee/config/bean/RabbitMultiTemplateConfig.java
  60. 27 0
      im-web/src/main/java/cn/poyee/config/cache/ReactiveRedisConfig.java
  61. 16 0
      im-web/src/main/java/cn/poyee/config/properties/JwtProperties.java
  62. 14 0
      im-web/src/main/java/cn/poyee/config/properties/RabbitProperties.java
  63. 14 0
      im-web/src/main/java/cn/poyee/constant/MessageChildType.java
  64. 36 0
      im-web/src/main/java/cn/poyee/constant/RedisConstants.java
  65. 9 0
      im-web/src/main/java/cn/poyee/constant/TempConstants.java
  66. 11 0
      im-web/src/main/java/cn/poyee/dto/model/ChatToBroadcast.java
  67. 12 0
      im-web/src/main/java/cn/poyee/dto/model/ChatToMerchant.java
  68. 14 0
      im-web/src/main/java/cn/poyee/dto/model/ChatToPersonal.java
  69. 14 0
      im-web/src/main/java/cn/poyee/dto/model/ChatToRoom.java
  70. 6 0
      im-web/src/main/java/cn/poyee/dto/model/ContentModel.java
  71. 14 0
      im-web/src/main/java/cn/poyee/dto/model/NoRecord.java
  72. 47 0
      im-web/src/main/java/cn/poyee/dto/processor/MessageConverter.java
  73. 48 0
      im-web/src/main/java/cn/poyee/dto/processor/MessageProcessor.java
  74. 36 0
      im-web/src/main/java/cn/poyee/dto/processor/MessageTypeRegistry.java
  75. 15 0
      im-web/src/main/java/cn/poyee/endpoint/ActuatorController.java
  76. 78 0
      im-web/src/main/java/cn/poyee/endpoint/ChatP2PController.java
  77. 48 0
      im-web/src/main/java/cn/poyee/endpoint/ChatRelationsController.java
  78. 54 0
      im-web/src/main/java/cn/poyee/endpoint/ChatRoomController.java
  79. 27 0
      im-web/src/main/java/cn/poyee/endpoint/CheckListController.java
  80. 181 0
      im-web/src/main/java/cn/poyee/endpoint/LiveChatController.java
  81. 70 0
      im-web/src/main/java/cn/poyee/endpoint/MessageController.java
  82. 32 0
      im-web/src/main/java/cn/poyee/endpoint/ProhibitionController.java
  83. 46 0
      im-web/src/main/java/cn/poyee/endpoint/RabbitController.java
  84. 47 0
      im-web/src/main/java/cn/poyee/endpoint/TaskController.java
  85. 109 0
      im-web/src/main/java/cn/poyee/endpoint/ToolController.java
  86. 38 0
      im-web/src/main/java/cn/poyee/endpoint/UnifiedMessageHandler.java
  87. 69 0
      im-web/src/main/java/cn/poyee/endpoint/UserController.java
  88. 42 0
      im-web/src/main/java/cn/poyee/entity/ChatP2PHistory.java
  89. 27 0
      im-web/src/main/java/cn/poyee/entity/ChatProhibition.java
  90. 46 0
      im-web/src/main/java/cn/poyee/entity/ChatRelations.java
  91. 29 0
      im-web/src/main/java/cn/poyee/entity/ChatRoomHistory.java
  92. 48 0
      im-web/src/main/java/cn/poyee/entity/CheckList.java
  93. 22 0
      im-web/src/main/java/cn/poyee/entity/LiveMessage.java
  94. 52 0
      im-web/src/main/java/cn/poyee/entity/Message.java
  95. 21 0
      im-web/src/main/java/cn/poyee/entity/MessageRead.java
  96. 18 0
      im-web/src/main/java/cn/poyee/entity/MessageScope.java
  97. 21 0
      im-web/src/main/java/cn/poyee/entity/Task.java
  98. 22 0
      im-web/src/main/java/cn/poyee/entity/TaskInfo.java
  99. 15 0
      im-web/src/main/java/cn/poyee/entity/UserSetting.java
  100. 12 0
      im-web/src/main/java/cn/poyee/entity/dto/ChatMessageParam.java

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+target/
+.idea
+*.iml
+out
+gen
+target
+*.log
+application-dev.yml

+ 51 - 0
README.md

@@ -0,0 +1,51 @@
+### 待办事项:
+- [ ] 网关增加MQTT连接的身份校验
+- [ ] 好友列表没有历史聊天时,前端不依赖payload
+- [ ] 直播间聊天改为MQTT协议
+- [ ] 取消WebSocket协议
+- [ ] 团队会议
+    - [ ] 准备演示材料
+    - [x] 预约会议室
+
+### git提交前缀说明
+- feat: 新功能的添加
+- update: 对现有功能的更新或增强
+- fix: 修复BUG
+- refactor: 代码重构,无其他操作
+
+### MQTT消息的级别
+```
+QoS0: 至多一次,消息最多送达一次	最低	最高	无关紧要的数据传输,丢失消息无关紧要
+QoS1: 至少一次,消息至少送达一次	中等	中等	需要确保消息至少到达一次,重复消息可以容忍
+QoS2: 仅一次,消息确保只送达一次	最高	最低	对消息准确性要求高,重复消息不可容忍
+```
+
+### 个人对个人消息
+```json
+{
+  "code":"chat",
+  "messageType":"chat_to_personal",
+  "ChatMessageParam":{
+    "receiver":"2477",
+    "messageId":"mid",
+    "payload":{
+      "contentType":"TEXT",
+      "sender":"2477",
+      "content":"你好!",
+      "extra":"{}"
+    }
+  }
+}
+```
+
+### 设备在线
+```json
+{
+    "code": "state",
+    "messageType": "online",
+    "ChatMessageParam": {
+        "value": true,
+        "userId":1
+    }
+}
+```

+ 94 - 0
api.md

@@ -0,0 +1,94 @@
+# IM接口文档
+
+### 目录
+- [商家禁言](#商家禁言)
+- [校验用户是否禁言](#校验用户是否禁言)
+
+
+## 商家禁言
+
+**接口描述**:新商家禁言接口
+
+- **请求方法**:`PUT`
+- **Content-Type**:`application/json;`
+- **请求URL**:`/api/livechat/forbidden`
+
+**请求参数**
+
+| 参数名称         | 参数       | 类型     | 必填        | 描述                       |
+|--------------|----------|--------|-----------|--------------------------|
+| sender       | avatar   | string | 否         | 头像                       |
+|              | nickname | string | 否         | 昵称                       |
+|              | id       | int    | 是         | 用户编号                     |
+| type         |          | int    | 是         | 禁言类型: 1:直播间禁言  2:商家按时间禁言 |
+| content      |          | string | 是         | 用户发言内容                   |
+| speakingTime |          | string | 是         | 用户发言时间                   |
+| roomId       |          | int    | 是         | 直播间ID                    |
+| roomCode     |          | string | 否         | 直播间编号                    |
+| deadlineHour |          | int    | type为2时必填 | 禁言时长(小时)                 |                                          |
+
+**请求示例**
+
+```text
+直播间禁言:
+{
+    "sender": {
+        "avatar": "https://static.public.hobbystock.cn/avatar/development/15161/1758260609488",
+        "nickname": "1610",
+        "id": 15161
+    },
+    "type": 1,
+    "content": "11",
+    "speakingTime": "2025-10-21 14:16:08",
+    "roomCode": "",
+    "roomId": 2777
+}
+
+商家禁言:
+{
+    "sender": {
+        "avatar": "https://static.public.hobbystock.cn/avatar/development/15161/1758260609488",
+        "nickname": "1610",
+        "id": 15161
+    },
+    "type": 2,
+    "roomId": 2777,
+    "deadlineHour": 24
+}
+```
+
+
+## 校验用户是否禁言
+
+**接口描述**:用户发言时,校验是否被禁言
+
+- **请求方法**:`POST`
+- **Content-Type**:`application/json;`
+- **请求URL**:`/api/livechat/dispatch`
+
+**请求参数**
+
+| 参数名称       | 参数       | 类型     | 必填 | 描述                                                  |
+|------------|----------|--------|----|-----------------------------------------------------|
+| sender     | avatar   | string | 否  | 头像                                                  |
+|            | nickname | string | 否  | 昵称                                                  |
+|            | id       | int    | 是  | 用户编号                                                |
+| merchantId |          | int    | 是  | 商家编号 <font color='red'>* 前端先传,后期后台通过 roomId取</font> |
+| content    |          | string | 是  | 用户发言内容                                              |
+| roomId     |          | int    | 是  | 直播间ID                                               |
+
+**请求示例**
+
+```text
+直播间禁言:
+{
+    "sender": {
+        "avatar": "https://static.public.hobbystock.cn/avatar/development/15161/1758260609488",
+        "nickname": "1610",
+        "id": 15161
+    },
+    "content": "你好",
+    "roomId": 2777,
+    "merchantId": 233
+}
+```

+ 22 - 0
common/common-core/pom.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>common</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>common-core</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 11 - 0
common/common-core/src/main/java/cn/poyee/common/base/BasePageQuery.java

@@ -0,0 +1,11 @@
+package cn.poyee.common.base;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class BasePageQuery implements Serializable {
+    private int pageNum = 1;
+    private int pageSize = 10;
+}

+ 53 - 0
common/common-core/src/main/java/cn/poyee/common/base/IBaseEnum.java

@@ -0,0 +1,53 @@
+package cn.poyee.common.base;
+
+import java.util.EnumSet;
+import java.util.Objects;
+
+public interface IBaseEnum<T> {
+
+    T getValue();
+
+    String getLabel();
+
+    static <E extends Enum<E> & IBaseEnum> E getEnumByValue(Object value, Class<E> clazz) {
+        Objects.requireNonNull(value);
+        EnumSet<E> allEnums = EnumSet.allOf(clazz);
+        E matchEnum = allEnums.stream()
+                .filter(e -> Objects.equals(e.getValue(), value))
+                .findFirst()
+                .orElse(null);
+        return matchEnum;
+    }
+
+    static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
+        Objects.requireNonNull(value);
+        EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
+        E matchEnum = allEnums.stream()
+                .filter(e -> Objects.equals(e.getValue(), value))
+                .findFirst()
+                .orElse(null);
+
+        String label = null;
+        if (matchEnum != null) {
+            label = matchEnum.getLabel();
+        }
+        return label;
+    }
+
+    static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
+        Objects.requireNonNull(label);
+        EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
+        String finalLabel = label;
+        E matchEnum = allEnums.stream()
+                .filter(e -> Objects.equals(e.getLabel(), finalLabel))
+                .findFirst()
+                .orElse(null);
+
+        Object value = null;
+        if (matchEnum != null) {
+            value = matchEnum.getValue();
+        }
+        return value;
+    }
+
+}

+ 5 - 0
common/common-core/src/main/java/cn/poyee/common/constraint/Constant.java

@@ -0,0 +1,5 @@
+package cn.poyee.common.constraint;
+
+public class Constant {
+    public final static Integer TRUE = 1;
+}

+ 5 - 0
common/common-core/src/main/java/cn/poyee/common/constraint/RoleConstant.java

@@ -0,0 +1,5 @@
+package cn.poyee.common.constraint;
+
+public class RoleConstant {
+    public final static String ADMIN = "admin";
+}

+ 31 - 0
common/common-core/src/main/java/cn/poyee/common/constraint/ThreadPoolConstant.java

@@ -0,0 +1,31 @@
+package cn.poyee.common.constraint;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class ThreadPoolConstant {
+
+
+    /**
+     * small
+     */
+    public static final Integer SINGLE_CORE_POOL_SIZE = 1;
+    public static final Integer SINGLE_MAX_POOL_SIZE = 1;
+    public static final Integer SMALL_KEEP_LIVE_TIME = 10;
+
+    /**
+     * medium
+     */
+    public static final Integer COMMON_CORE_POOL_SIZE = 20;
+    public static final Integer COMMON_MAX_POOL_SIZE = 150;
+    public static final Integer COMMON_KEEP_LIVE_TIME = 60;
+    public static final Integer COMMON_QUEUE_SIZE = 128;
+
+
+    /**
+     * big
+     */
+    public static final Integer BIG_QUEUE_SIZE = 1024;
+    public static final BlockingQueue BIG_BLOCKING_QUEUE = new LinkedBlockingQueue(BIG_QUEUE_SIZE);
+
+}

+ 6 - 0
common/common-core/src/main/java/cn/poyee/common/result/IResultCode.java

@@ -0,0 +1,6 @@
+package cn.poyee.common.result;
+
+public interface IResultCode {
+    Integer getCode();
+    String getMsg();
+}

+ 77 - 0
common/common-core/src/main/java/cn/poyee/common/result/Result.java

@@ -0,0 +1,77 @@
+package cn.poyee.common.result;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class Result<T> implements Serializable {
+
+    private Integer code;
+
+    private T data;
+
+    private String msg;
+    private Long total;
+
+    public static <T> Result<T> success() {
+        return success(null);
+    }
+
+    public static <T> Result<T> success(T data) {
+        ResultCode rce = ResultCode.SUCCESS;
+        if (data instanceof Boolean && Boolean.FALSE.equals(data)) {
+            rce = ResultCode.SYSTEM_EXECUTION_ERROR;
+        }
+        return result(rce, data);
+    }
+
+    public static <T> Result<T> success(T data, Long total) {
+        Result<T> result = new Result<>();
+        result.setCode(ResultCode.SUCCESS.getCode());
+        result.setMsg(ResultCode.SUCCESS.getMsg());
+        result.setData(data);
+        result.setTotal(total);
+        return result;
+    }
+
+    public static <T> Result<T> failed() {
+        return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), ResultCode.SYSTEM_EXECUTION_ERROR.getMsg(), null);
+    }
+
+    public static <T> Result<T> failed(String msg) {
+        return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), msg, null);
+    }
+
+    public static <T> Result<T> judge(boolean status) {
+        if (status) {
+            return success();
+        } else {
+            return failed();
+        }
+    }
+
+    public static <T> Result<T> failed(IResultCode resultCode) {
+        return result(resultCode.getCode(), resultCode.getMsg(), null);
+    }
+
+    public static <T> Result<T> failed(IResultCode resultCode, String msg) {
+        return result(resultCode.getCode(), msg, null);
+    }
+
+    private static <T> Result<T> result(IResultCode resultCode, T data) {
+        return result(resultCode.getCode(), resultCode.getMsg(), data);
+    }
+
+    private static <T> Result<T> result(Integer code, String msg, T data) {
+        Result<T> result = new Result<>();
+        result.setCode(code);
+        result.setData(data);
+        result.setMsg(msg);
+        return result;
+    }
+
+    public static boolean isSuccess(Result<?> result) {
+        return result != null && ResultCode.SUCCESS.getCode().equals(result.getCode());
+    }
+}

+ 101 - 0
common/common-core/src/main/java/cn/poyee/common/result/ResultCode.java

@@ -0,0 +1,101 @@
+package cn.poyee.common.result;
+
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@AllArgsConstructor
+@NoArgsConstructor
+public enum ResultCode implements IResultCode, Serializable {
+
+    THE_LIVE_BROADCAST_HAS_ENDED(100, "直播已结束"),
+
+    INTERFACE_NOT_EXIST(113, "接口不存在"),
+    MESSAGE_SERVICE_ERROR(120, "消息服务出错"),
+    MESSAGE_DELIVERY_ERROR(121, "请求参数有误"),
+    MESSAGE_CONSUMPTION_ERROR(122, "消息消费出错"),
+    MESSAGE_SUBSCRIPTION_ERROR(123, "消息订阅出错"),
+    MESSAGE_GROUP_NOT_FOUND(124, "消息分组未查到"),
+
+    SUCCESS(200, "SUCCESS"),
+    LOGIC_LAYER_ERROR(200, "逻辑层异常,请联系开发人员"),
+    AFFECTED_ROWS_IS_EMPTY(200, "执行成功,受影响行数为空"),
+    USER_LOGIN_ERROR(200, "用户登录异常"),
+    USER_NOT_EXIST(201, "用户不存在"),
+    USER_ACCOUNT_LOCKED(202, "用户账户被冻结"),
+    USER_ACCOUNT_INVALID(203, "用户账户已作废"),
+    FLOW_LIMITING(204, "系统限流"),
+    DEGRADATION(205, "系统功能降级"),
+    PAY_ERROR(206, "支付异常"),
+    USERNAME_OR_PASSWORD_ERROR(210, "用户名或密码错误"),
+    PASSWORD_ENTER_EXCEED_LIMIT(211, "用户输入密码次数超限"),
+    CLIENT_AUTHENTICATION_FAILED(212, "客户端认证失败"),
+    TOKEN_INVALID_OR_EXPIRED(230, "token无效或已过期"),
+    TOKEN_ACCESS_FORBIDDEN(231, "token已被禁止访问"),
+    SMS_CODE_TIMEOUT(251, "验证码已过期"),
+    SMS_CODE_ERROR(252, "验证码错误"),
+
+    AUTHORIZED_ERROR(300, "权限异常"),
+    ACCESS_UNAUTHORIZED(301, "访问未授权"),
+    FORBIDDEN_OPERATION(302, "演示环境禁止修改、删除重要数据,请本地部署后测试"),
+    SYSTEM_RESOURCE_ERROR(303, "系统资源异常"),
+    SYSTEM_RESOURCE_EXHAUSTION(304, "系统资源耗尽"),
+    SYSTEM_RESOURCE_ACCESS_ERROR(305, "系统资源访问异常"),
+    SYSTEM_READ_DISK_FILE_ERROR(306, "系统读取磁盘文件失败"),
+    DATABASE_ERROR(307, "数据库服务出错"),
+    REPEAT_OPERATION(308, "重复操作"),
+    DATABASE_TABLE_NOT_EXIST(311, "表不存在"),
+    DATABASE_COLUMN_NOT_EXIST(312, "列不存在"),
+    DATABASE_DUPLICATE_COLUMN_NAME(321, "多表关联中存在多个相同名称的列"),
+    DATABASE_DEADLOCK(331, "数据库死锁"),
+    DATABASE_PRIMARY_KEY_CONFLICT(341, "主键冲突"),
+
+    PARAM_ERROR(400, "用户请求参数错误"),
+    RESOURCE_NOT_FOUND(401, "请求资源不存在"),
+    INVALID_OPERATION(402, "无效操作,与业务逻辑不符"),
+    COLLECTION_IS_EMPTY(403, "集合为空"),
+    CONTEXT_IS_NULL(404, "流程上下文为空"),
+    PARAM_IS_NULL(410, "请求必填参数为空"),
+
+    MOBILE_FORMAT_ERROR(450, "手机号格式错误"),
+
+    SYSTEM_EXECUTION_ERROR(500, "系统执行出错"),
+    SYSTEM_EXECUTION_TIMEOUT(501, "系统执行超时"),
+    SYSTEM_ORDER_PROCESSING_TIMEOUT(502, "系统订单处理超时"),
+    REQUEST_UNEXPECTED_INTERRUPTION(503, "客户端请求意外中断"),
+    CALL_THIRD_PARTY_SERVICE_ERROR(504, "调用第三方服务出错"),
+    MIDDLEWARE_SERVICE_ERROR(505, "中间件服务出错");
+
+    @Override
+    public Integer getCode() {
+        return code;
+    }
+
+    @Override
+    public String getMsg() {
+        return msg;
+    }
+
+    private int code;
+
+    private String msg;
+
+    @Override
+    public String toString() {
+        return "{" +
+                "\"code\":\"" + code + '\"' +
+                ", \"msg\":\"" + msg + '\"' +
+                '}';
+    }
+
+
+    public static ResultCode getValue(int code) {
+        for (ResultCode value : values()) {
+            if (value.getCode() == code) {
+                return value;
+            }
+        }
+        return SYSTEM_EXECUTION_ERROR;
+    }
+}

+ 62 - 0
common/common-core/src/main/java/cn/poyee/common/util/DateUtil.java

@@ -0,0 +1,62 @@
+package cn.poyee.common.util;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DateUtil {
+
+    public static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    private static final ThreadLocal<Map<String, DateFormat>> dateFormatThreadLocal = new ThreadLocal<>();
+
+    private static DateFormat getDateFormat(String pattern) {
+        if (pattern == null || pattern.trim().length() == 0) {
+            throw new IllegalArgumentException("pattern cannot be empty.");
+        }
+
+        Map<String, DateFormat> dateFormatMap = dateFormatThreadLocal.get();
+        if (dateFormatMap != null && dateFormatMap.containsKey(pattern)) {
+            return dateFormatMap.get(pattern);
+        }
+
+        synchronized (dateFormatThreadLocal) {
+            if (dateFormatMap == null) {
+                dateFormatMap = new HashMap<>();
+            }
+            dateFormatMap.put(pattern, new SimpleDateFormat(pattern));
+            dateFormatThreadLocal.set(dateFormatMap);
+        }
+
+        return dateFormatMap.get(pattern);
+    }
+
+    public static Date parse(String date) {
+        try {
+            SimpleDateFormat format = new SimpleDateFormat(DATETIME_FORMAT);
+            return format.parse(date);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static long differentTimeStamp(long startTimeStamp, long endTimeStamp) {
+
+        try {
+            Instant instant1 = Instant.ofEpochSecond(startTimeStamp);
+            Instant instant2 = Instant.ofEpochSecond(endTimeStamp);
+            return Duration.between(instant1, instant2).getSeconds();
+        } catch (Exception e) {
+            throw new RuntimeException();
+        }
+    }
+
+    public static String format(Date date, String patten) {
+        return getDateFormat(patten).format(date);
+    }
+}

+ 114 - 0
common/common-core/src/main/java/cn/poyee/common/util/IdWorker.java

@@ -0,0 +1,114 @@
+package cn.poyee.common.util;
+
+import java.util.Random;
+
+public class IdWorker {
+
+    private long workerId;
+    private long datacenterId;
+    private long sequence;
+
+    public IdWorker(long workerId, long datacenterId, long sequence) {
+        if (workerId > maxWorkerId || workerId < 0) {
+            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
+        }
+        if (datacenterId > maxDatacenterId || datacenterId < 0) {
+            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
+        }
+        System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
+                timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
+
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+        this.sequence = sequence;
+    }
+
+    private long twepoch = 1288834974657L;
+    private long workerIdBits = 5L;
+    private long datacenterIdBits = 5L;
+    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
+    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
+    private long sequenceBits = 12L;
+    private long sequenceMask = -1L ^ (-1L << sequenceBits);
+    private long workerIdShift = sequenceBits;
+    private long datacenterIdShift = sequenceBits + workerIdBits;
+    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+    private long lastTimestamp = -1L;
+
+    public synchronized long nextId() {
+        long timestamp = timeGen();
+
+        if (timestamp < lastTimestamp) {
+            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
+                    lastTimestamp - timestamp));
+        }
+
+        if (lastTimestamp == timestamp) {
+            sequence = (sequence + 1) & sequenceMask;
+            if (sequence == 0) {
+                timestamp = tilNextMillis(lastTimestamp);
+            }
+        } else {
+            sequence = 0;
+        }
+
+        //将上次时间戳值刷新
+        lastTimestamp = timestamp;
+
+        /**
+         * 返回结果:
+         * (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数
+         * (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
+         * (workerId << workerIdShift) 表示将工作id左移相应位数
+         * | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。
+         * 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id
+         */
+        return ((timestamp - twepoch) << timestampLeftShift) |
+                (datacenterId << datacenterIdShift) |
+                (workerId << workerIdShift) |
+                sequence;
+    }
+
+    /**
+     * 获取时间戳,并与上次时间戳比较
+     *
+     * @param lastTimestamp
+     * @return
+     */
+    private long tilNextMillis(long lastTimestamp) {
+        long timestamp = timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = timeGen();
+        }
+        return timestamp;
+    }
+
+    /**
+     * 获取系统时间戳
+     */
+    private long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * 这里简单实现,通过随机数生成工作ID、数据ID
+     * 不生成重复id要通过datacenterId和workerId来做区分
+     */
+    private static class SingletonClassInstance {
+        static Random random = new Random();
+        private static final IdWorker instance = new IdWorker(random.nextInt(31), random.nextInt(31), 1);
+    }
+
+    /**
+     * 单例调用入口
+     */
+    public static IdWorker getInstance() {
+        return SingletonClassInstance.instance;
+    }
+
+    public static void main(String[] args) {
+        for (int i = 0; i < 30; i++) {
+            System.out.println(IdWorker.getInstance().nextId());
+        }
+    }
+}

+ 112 - 0
common/common-core/src/main/java/cn/poyee/common/util/JsonUtil.java

@@ -0,0 +1,112 @@
+package cn.poyee.common.util;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.json.JsonReadFeature;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import lombok.NonNull;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Map;
+
+public class JsonUtil {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    static {
+        // 对象的所有字段全部列入
+        mapper.setDefaultPropertyInclusion(JsonInclude.Include.ALWAYS);
+        // 取消默认转换timestamps形式
+        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+        // 忽略空Bean转json的错误
+        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+        // 所有的日期格式都统一为以下的样式(yyyy-MM-dd HH:mm:ss)
+        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+        // 忽略在json字符串中存在,但是在java对象中不存在对应属性的情况。防止错误
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+        mapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true);
+
+        mapper.registerModule(new JavaTimeModule());
+    }
+
+    public static ObjectNode createObjectNode() {
+        return mapper.createObjectNode();
+    }
+
+    public static boolean isValid(String json) {
+        try {
+            mapper.readTree(json);
+        } catch (JacksonException e) {
+            return false;
+        }
+        return true;
+    }
+
+    public static JsonNode toNode(String json) {
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            mapper.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
+            mapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
+            return mapper.readTree(json.replace("\\\\\"", "\\\""));
+        } catch (IOException e) {
+            throw new RuntimeException("JSON解析失败", e);
+        }
+    }
+
+    public static <T> Map<String, T> toMap(String json, Class<T> clz) {
+        try {
+            return mapper.readValue(json, collectionType(Map.class, String.class, clz));
+        } catch (JsonProcessingException e) {
+            return null;
+        }
+    }
+
+    public static String toStr(@NonNull Object o) {
+        try {
+            return format(mapper.writeValueAsString(o));
+        } catch (JsonProcessingException e) {
+            return null;
+        }
+    }
+
+    public static JsonNode safeGet(ObjectNode node, String... fields) {
+        JsonNode current = node;
+        for (String field : fields) {
+            if (current == null) return null;
+            current = current.get(field);
+        }
+        return current;
+    }
+
+    public static <T> T toBean(String json, Class<T> clz) {
+        try {
+            return mapper.readValue(format(json), clz);
+        } catch (JsonProcessingException e) {
+            return null;
+        }
+    }
+
+    public static <T> T toBean(byte[] bytes, Class<T> clz) {
+        try {
+            return mapper.readValue(bytes, clz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static JavaType collectionType(Class<?> collectionClz, Class<?>... elementClz) {
+        return mapper.getTypeFactory().constructParametricType(collectionClz, elementClz);
+    }
+
+    private static String format(String str) {
+        if (null == str) {
+            throw new RuntimeException("JsonUtil.toBean.format转换内容不能为空");
+        }
+        return str.replaceAll("\r|\n", "");
+    }
+}

+ 37 - 0
common/common-core/src/main/java/cn/poyee/common/util/NetworkUtil.java

@@ -0,0 +1,37 @@
+package cn.poyee.common.util;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+public class NetworkUtil {
+
+    public static String getLocalHostName() throws UnknownHostException, SocketException {
+        try {
+            InetAddress localhost = InetAddress.getLocalHost();
+            return localhost.getHostName();
+        } catch (UnknownHostException e) {
+            return getLocalAddressAsString();
+        }
+    }
+
+    private static String getLocalAddressAsString() throws UnknownHostException, SocketException {
+        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+        while (interfaces.hasMoreElements()) {
+            Enumeration<InetAddress> addresses = interfaces.nextElement().getInetAddresses();
+            while (addresses.hasMoreElements()) {
+                InetAddress address = addresses.nextElement();
+                if (acceptableAddress(address)) {
+                    return address.getHostAddress();
+                }
+            }
+        }
+        throw new UnknownHostException();
+    }
+
+    private static boolean acceptableAddress(InetAddress address) {
+        return address != null && !address.isLoopbackAddress() && !address.isAnyLocalAddress() && !address.isLinkLocalAddress();
+    }
+}

+ 25 - 0
common/common-core/src/main/java/cn/poyee/common/util/StrUtil.java

@@ -0,0 +1,25 @@
+package cn.poyee.common.util;
+
+import java.util.Arrays;
+
+public class StrUtil {
+
+    public static String[] sort(String... args) {
+        Arrays.sort(args);
+        return args;
+    }
+
+    public static String assemble(String[] args, String split) {
+        Arrays.sort(args);
+        return String.join(split, args);
+    }
+
+    public static boolean isInteger(String str) {
+        try {
+            Integer.parseInt(str);
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+}

+ 40 - 0
common/common-logback/pom.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>common</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>common-logback</artifactId>
+
+    <dependencies>
+        <!-- logback fluentd -->
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>janino</artifactId>
+            <version>3.0.12</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sndyuk</groupId>
+            <artifactId>logback-more-appenders</artifactId>
+            <version>1.8.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.fluentd</groupId>
+            <artifactId>fluent-logger</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>

+ 10 - 0
common/common-logback/src/main/java/cn/poyee/common/logback/util/CanonicalHostNamePropertyDefiner.java

@@ -0,0 +1,10 @@
+package cn.poyee.common.logback.util;
+
+import ch.qos.logback.core.PropertyDefinerBase;
+
+public class CanonicalHostNamePropertyDefiner extends PropertyDefinerBase {
+    @Override
+    public String getPropertyValue() {
+        return new NetworkAddressUtil(getContext()).safelyGetCanonicalLocalHostName();
+    }
+}

+ 78 - 0
common/common-logback/src/main/java/cn/poyee/common/logback/util/NetworkAddressUtil.java

@@ -0,0 +1,78 @@
+package cn.poyee.common.logback.util;
+
+import ch.qos.logback.core.Context;
+import ch.qos.logback.core.CoreConstants;
+import ch.qos.logback.core.spi.ContextAwareBase;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+public class NetworkAddressUtil extends ContextAwareBase {
+
+    public NetworkAddressUtil(Context context) {
+        setContext(context);
+    }
+
+    public static String getLocalHostName() throws UnknownHostException, SocketException {
+        try {
+            InetAddress localhost = InetAddress.getLocalHost();
+            return localhost.getHostName();
+        } catch (UnknownHostException e) {
+            return getLocalAddressAsString();
+        }
+    }
+
+    public static String getCanonicalLocalHostName() throws UnknownHostException, SocketException {
+        try {
+            InetAddress localhost = InetAddress.getLocalHost();
+            return localhost.getCanonicalHostName();
+        } catch (UnknownHostException e) {
+            return getLocalAddressAsString();
+        }
+    }
+
+    private static String getLocalAddressAsString() throws UnknownHostException, SocketException {
+        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+        while (interfaces != null && interfaces.hasMoreElements()) {
+            Enumeration<InetAddress> addresses = interfaces.nextElement().getInetAddresses();
+            while (addresses != null && addresses.hasMoreElements()) {
+                InetAddress address = addresses.nextElement();
+                if (acceptableAddress(address)) {
+                    return address.getHostAddress();
+                }
+            }
+        }
+        throw new UnknownHostException();
+    }
+
+    private static boolean acceptableAddress(InetAddress address) {
+        return address != null && !address.isLoopbackAddress() && !address.isAnyLocalAddress() && !address.isLinkLocalAddress();
+    }
+
+    /**
+     * Add the local host's name as a property
+     */
+    public String safelyGetLocalHostName() {
+        try {
+            String localhostName = getLocalHostName();
+            return localhostName;
+        } catch (UnknownHostException | SocketException | SecurityException e) {
+            addError("Failed to get local hostname", e);
+        }
+        return CoreConstants.UNKNOWN_LOCALHOST;
+    }
+
+    public String safelyGetCanonicalLocalHostName() {
+        try {
+            String localhostName = getCanonicalLocalHostName();
+            return localhostName;
+        } catch (UnknownHostException | SocketException | SecurityException e) {
+            addError("Failed to get canonical local hostname", e);
+        }
+        return CoreConstants.UNKNOWN_LOCALHOST;
+
+    }
+}

+ 0 - 0
common/common-logback/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports


+ 23 - 0
common/common-logback/src/main/resources/logback-fluentd.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<included>
+    <appender name="FLUENT_SYNC" class="ch.qos.logback.more.appenders.DataFluentAppender">
+        <tag>logback</tag>
+        <label>${spring.application.name}.${hostname}</label>
+        <remoteHost>${logging.fluentd.host}</remoteHost>
+        <port>${logging.fluentd.port}</port>
+
+        <encoder charset="UTF-8">
+            <pattern>%logger{15}:%L - %msg</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+
+    </appender>
+
+    <appender name="FLUENT" class="ch.qos.logback.classic.AsyncAppender">
+        <queueSize>1000</queueSize>
+        <neverBlock>true</neverBlock>
+        <maxFlushTime>15000</maxFlushTime>
+        <includeCallerData>false</includeCallerData>
+        <appender-ref ref="FLUENT_SYNC" />
+    </appender>
+</included>

+ 42 - 0
common/common-logback/src/main/resources/logback.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="false" debug="true">
+    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+    <springProperty scope="context" name="spring.application.name" source="spring.application.name"/>
+    <springProperty scope="context" name="logging.console.enabled" source="logging.console.enabled"/>
+    <springProperty scope="context" name="logging.fluentd.enabled" source="logging.fluentd.enabled"/>
+    <springProperty scope="context" name="logging.fluentd.host" source="logging.fluentd.host"/>
+    <springProperty scope="context" name="logging.fluentd.port" source="logging.fluentd.port"/>
+
+    <define name="hostname" class="cn.poyee.common.logback.util.CanonicalHostNamePropertyDefiner"/>
+
+    <!-- 控制台输出 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <if condition='p("logging.fluentd.enabled").equals("true")'>
+        <then>
+            <include resource="logback-fluentd.xml"/>
+        </then>
+    </if>
+
+    <!-- Spring日志级别控制  -->
+    <logger name="org.springframework" level="WARN"/>
+    <!--系统操作日志-->
+    <if condition='p("logging.fluentd.enabled").equals("true")'>
+        <then>
+            <root level="INFO">
+                <appender-ref ref="FLUENT" />
+            </root>
+        </then>
+        <else>
+            <root level="INFO">
+                <appender-ref ref="CONSOLE" />
+            </root>
+        </else>
+    </if>
+
+</configuration>

+ 48 - 0
common/common-mqtt/pom.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>common</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>common-mqtt</artifactId>
+
+    <dependencies>
+        <!-- mqtt -->
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-amqp</artifactId>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+
+
+        <dependency>
+            <groupId>jakarta.annotation</groupId>
+            <artifactId>jakarta.annotation-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>

+ 150 - 0
common/common-mqtt/src/main/java/cn/poyee/common/mqtt/config/MqttConfig.java

@@ -0,0 +1,150 @@
+package cn.poyee.common.mqtt.config;
+
+import cn.poyee.common.mqtt.handle.AbstractMqttMessageHandler;
+import cn.poyee.common.mqtt.properties.MqttProperties;
+import cn.poyee.common.util.NetworkUtil;
+
+import javax.annotation.Resource;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.core.MessageProducer;
+import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
+import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import static cn.poyee.common.mqtt.properties.MqttProperties.INPUT_CHANNEL;
+import static cn.poyee.common.mqtt.properties.MqttProperties.OUTBOUND_CHANNEL;
+
+@Slf4j
+@Configuration
+@ConditionalOnProperty(name = "mqtt.pattern", havingValue = "mqtt")
+public class MqttConfig {
+
+    @Resource
+    private MqttGatewayComponent mqttGatewayComponent;
+    @Resource
+    private MqttProperties prop;
+
+    private final AbstractMqttMessageHandler mqttMessageHandler;
+
+    // 通过构造函数注入用户自定义的实现
+    public MqttConfig(AbstractMqttMessageHandler mqttMessageHandler) {
+        this.mqttMessageHandler = mqttMessageHandler;
+    }
+
+    @Bean
+    public MqttPahoClientFactory clientFactory() {
+        MqttConnectOptions options = new MqttConnectOptions();
+        options.setAutomaticReconnect(true);
+        options.setCleanSession(false);
+        options.setServerURIs(prop.getUrl().split(","));
+        options.setUserName(prop.getUsername());
+        options.setPassword(prop.getPassword().toCharArray());
+
+        // String willMessage = "{\"msg\":\"device " + NetworkUtil.getLocalHostName() + " loss connection " +System.currentTimeMillis()+ "\"}";
+        // String willTopic = "/last-will";
+        // options.setWill(willTopic, willMessage.getBytes(), 1, true);
+        // options.setConnectionTimeout(30);     // 连接超时(秒)
+        // 设置心跳的频率
+        options.setKeepAliveInterval(60);
+        options.setMaxInflight(200); // 1000
+
+        log.error("mqtt.url:{}", prop.getUrl());
+        log.error("mqtt.username:{}", prop.getUsername());
+        log.error("mqtt.password:{}", prop.getPassword());
+
+        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
+        factory.setConnectionOptions(options);
+        return factory;
+    }
+
+    /**
+     * mqtt出站通道
+     *
+     * @return {@link MessageChannel}
+     */
+    @Bean(value = OUTBOUND_CHANNEL)
+    public MessageChannel mqttOutboundChannel() {
+        return new DirectChannel();
+    }
+
+    /**
+     * mqtt出站handler
+     *
+     * @return {@link MessageHandler}
+     */
+    @Bean
+    @ServiceActivator(inputChannel = OUTBOUND_CHANNEL)
+    public MessageHandler mqttOutboundHandler() throws SocketException, UnknownHostException {
+        MqttPahoMessageHandler handler = new MqttPahoMessageHandler(NetworkUtil.getLocalHostName(), clientFactory());
+        handler.setDefaultQos(2);                       // QoS 2 确保消息仅发送一次
+        handler.setDefaultRetained(false);              // 关闭 Retained,确保所有消息都能收到,而不是只存储最后一条
+        handler.setDefaultTopic(prop.getPubTopic());
+        handler.setAsync(true);                         // 允许异步发送,提高吞吐量
+        handler.setAsyncEvents(true);                   // 允许异步事件,提高吞吐量
+
+        return handler;
+    }
+
+
+    /**
+     * mqtt输入通道
+     *
+     * @return {@link MessageChannel}
+     */
+    @Bean
+    public MessageChannel mqttInputChannel() {
+        return new DirectChannel();
+    }
+
+    /**
+     * 入站
+     */
+    @Bean
+    public MessageProducer inbound() throws SocketException, UnknownHostException {
+        // 配置订阅端
+        MqttPahoMessageDrivenChannelAdapter adapter =
+                new MqttPahoMessageDrivenChannelAdapter("im_svc", clientFactory(), prop.getSubTopic().split(","));
+        // 设置超时时间
+        adapter.setCompletionTimeout(3000);
+        // 设置默认的消息转换类
+        adapter.setConverter(new DefaultPahoMessageConverter());
+        //设置qos级别
+        adapter.setQos(1);
+        // 设置入站管道
+        adapter.setOutputChannel(mqttInputChannel());
+
+        return adapter;
+    }
+
+    /**
+     * 消息处理程序
+     *
+     * @return {@link MessageHandler}
+     */
+    @Bean
+    @ServiceActivator(inputChannel = INPUT_CHANNEL)
+    public MessageHandler messageHandler() {
+        return message -> {
+            // 从消息中获取主题和内容
+            String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
+            Object payload = message.getPayload();
+            // 调用用户实现的 handleMessage 方法进行处理
+            // log.debug("messageHandler.payload:{}", payload);
+            mqttMessageHandler.handleMessage(topic, payload);
+        };
+    }
+}

+ 59 - 0
common/common-mqtt/src/main/java/cn/poyee/common/mqtt/config/MqttGatewayComponent.java

@@ -0,0 +1,59 @@
+package cn.poyee.common.mqtt.config;
+
+import cn.poyee.common.mqtt.properties.MqttProperties;
+import org.springframework.integration.annotation.MessagingGateway;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * MQTT发送网关
+ */
+@Component
+@MessagingGateway(defaultRequestChannel = MqttProperties.OUTBOUND_CHANNEL)
+public interface MqttGatewayComponent {
+    /**
+     * 发送到mqtt
+     *
+     * @param payload 有效载荷
+     */
+    void sendToMqtt(String payload);
+
+    /**
+     * 发送到mqtt
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     */
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
+
+    /**
+     * 发送到mqtt
+     *
+     * @param topic   主题
+     * @param qos     qos
+     * @param payload 消息内容
+     */
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
+
+    /**
+     * 发送到mqtt(带过期时间)
+     *
+     * @param topic           主题
+     * @param qos             QoS等级
+     * @param payload         消息内容
+     * @param expiryInterval  过期时间(毫秒)
+     */
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
+                    @Header(MqttHeaders.QOS) int qos,
+                    String payload,
+                    @Header(MqttHeaders.MESSAGE_EXPIRY_INTERVAL) long expiryInterval);
+
+    /**
+     * 发送到mqtt
+     *
+     * @param message 消息
+     */
+    void sendToMqtt(Message<?> message);
+}

+ 26 - 0
common/common-mqtt/src/main/java/cn/poyee/common/mqtt/handle/AbstractMqttMessageHandler.java

@@ -0,0 +1,26 @@
+package cn.poyee.common.mqtt.handle;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class AbstractMqttMessageHandler {
+
+
+    /**
+     * 处理接收到的消息
+     * 这个方法是抽象的,子类必须实现它
+     *
+     * @param topic   主题
+     * @param payload 消息内容
+     */
+    public abstract void handleMessage(String topic, Object payload);
+
+    /**
+     * 可选的辅助方法:处理某些业务逻辑,如消息转换等
+     * 子类可以重写这个方法
+     */
+    protected Object preProcess(Object payload) {
+        // 默认的处理方法(如果需要)——可以在此进行一些通用的预处理
+        return payload;
+    }
+}

+ 30 - 0
common/common-mqtt/src/main/java/cn/poyee/common/mqtt/properties/MqttProperties.java

@@ -0,0 +1,30 @@
+package cn.poyee.common.mqtt.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Getter
+@Setter
+@Component
+@ConfigurationProperties(prefix = "mqtt")
+public class MqttProperties {
+
+    // 模式: amqp、mqtt
+    private String pattern;
+    // 主机url
+    private String url;
+    // 用户名
+    private String username;
+    // 密码
+    private String password;
+    // 发布的主题
+    private String pubTopic;
+    // 订阅的主题
+    private String subTopic;
+    // 出站通道
+    public static final String OUTBOUND_CHANNEL = "mqttOutboundChannel";
+    // 输入通道
+    public static final String INPUT_CHANNEL = "mqttInputChannel";
+}

+ 3 - 0
common/common-mqtt/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  cn.poyee.common.web.config.OkHttpConfiguration,\
+  cn.poyee.common.web.util.OkHttpUtils

+ 36 - 0
common/common-mybatis/pom.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>common</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>common-mybatis</artifactId>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.32</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>1.2.15</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.2</version>
+        </dependency>
+    </dependencies>
+</project>

+ 23 - 0
common/common-mybatis/src/main/java/cn/poyee/common/mybatis/config/MybatisPlusConfig.java

@@ -0,0 +1,23 @@
+package cn.poyee.common.mybatis.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+
+@Configuration
+@RequiredArgsConstructor
+@EnableTransactionManagement
+public class MybatisPlusConfig {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+}

+ 57 - 0
common/common-redis/pom.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>common</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>common-redis</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>com.esotericsoftware</groupId>
+            <artifactId>kryo</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+            <version>3.18.0</version>
+        </dependency>
+    </dependencies>
+</project>

+ 73 - 0
common/common-redis/src/main/java/cn/poyee/common/redis/cache/CacheKeyGenerator.java

@@ -0,0 +1,73 @@
+package cn.poyee.common.redis.cache;
+
+import cn.poyee.common.util.JsonUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+
+//@Configuration
+//@EnableCaching
+public class CacheKeyGenerator extends CachingConfigurerSupport {
+
+    @Autowired
+    private RedisConnectionFactory redisConnectionFactory;
+
+    @Bean
+    public RedisCacheManager redisCacheManager() {
+        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
+        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
+        return new CustomRedisCacheManager(redisCacheWriter, redisCacheConfiguration);
+    }
+
+    /**
+     * 公告列表
+     * @return
+     */
+    @Bean
+    public KeyGenerator message() {
+        return (target, method, params) -> {
+            JsonNode paramNode = JsonUtil.toBean(JsonUtil.toStr(params[0]), JsonNode.class);
+            return paramNode.get("title").asText()
+                    .concat("_")
+                    .concat(paramNode.get("status").asText())
+                    .concat("_")
+                    .concat(paramNode.get("type").asText())
+                    .concat("_")
+                    .concat(paramNode.get("merchantId").asText())
+                    .concat("_")
+                    .concat(paramNode.get("pageSize").asText())
+                    .concat("_")
+                    .concat(paramNode.get("pageNum").asText())
+                    .hashCode();
+        };
+    }
+
+    @Bean
+    public KeyGenerator read() {
+        return (target, method, params) -> {
+            JsonNode paramNode = JsonUtil.toBean(JsonUtil.toStr(params[0]), JsonNode.class);
+            return paramNode.get("messageId").asText();
+        };
+    }
+
+    @Bean
+    public KeyGenerator chatRoomHistory() {
+        return (target, method, params) -> {
+
+            JsonNode paramNode = JsonUtil.toBean(JsonUtil.toStr(params[0]), JsonNode.class);
+            return paramNode.get("roomId").asText()
+                    .concat("_")
+                    .concat(paramNode.get("pageNum").asText())
+                    .concat("_")
+                    .hashCode();
+        };
+    }
+}

+ 39 - 0
common/common-redis/src/main/java/cn/poyee/common/redis/cache/CustomRedisCacheManager.java

@@ -0,0 +1,39 @@
+package cn.poyee.common.redis.cache;
+
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.util.StringUtils;
+
+import java.time.Duration;
+import java.util.Objects;
+
+public class CustomRedisCacheManager extends RedisCacheManager {
+
+    public CustomRedisCacheManager(RedisCacheWriter cacheWriter,
+                                   RedisCacheConfiguration defaultCacheConfiguration) {
+
+        super(cacheWriter, defaultCacheConfiguration);
+    }
+
+    @Override
+    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
+
+        String[] cells = StringUtils.delimitedListToStringArray(name, "#");
+        if (0 == cells.length || 1 == cells.length) {
+            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(-1));
+        } else if (cells.length > 2) {
+            throw new RuntimeException("Cacheable注解中不能包含两个#号");
+        } else {
+            name = cells[0];
+            if (Objects.isNull(cells[1])) {
+                throw new RuntimeException("Cacheable注解中#后必须要有整数型的过期时间值(或更换value的值)");
+            }
+            long ttl = Long.parseLong(cells[1]);
+            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
+        }
+
+        return super.createRedisCache(name, cacheConfig);
+    }
+}

+ 37 - 0
common/common-redis/src/main/java/cn/poyee/common/redis/cache/RedissonConfig.java

@@ -0,0 +1,37 @@
+package cn.poyee.common.redis.cache;
+
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RedissonConfig {
+
+    private final RedisProperties redisProperties;
+
+    public RedissonConfig(RedisProperties redisProperties) {
+        this.redisProperties = redisProperties;
+    }
+
+    @Bean(destroyMethod = "shutdown")
+    public RedissonClient redissonClient() {
+        Config config = new Config();
+        RedisProperties.Sentinel sentinel = redisProperties.getSentinel();
+
+        String[] nodes = sentinel.getNodes().stream()
+                .map(node -> "redis://" + node)
+                .toArray(String[]::new);
+
+        config.useSentinelServers()
+                .setMasterName(sentinel.getMaster())
+                .addSentinelAddress(nodes)
+                .setCheckSentinelsList(false) // 关键:禁用哨兵数量检查
+                .setPassword(redisProperties.getPassword())
+                .setDatabase(redisProperties.getDatabase());
+
+        return Redisson.create(config);
+    }
+}

+ 71 - 0
common/common-redis/src/main/java/cn/poyee/common/redis/kryo/KryoRedisSerializer.java

@@ -0,0 +1,71 @@
+package cn.poyee.common.redis.kryo;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.io.ByteArrayOutputStream;
+
+@Slf4j
+public class KryoRedisSerializer<T> implements RedisSerializer<T> {
+
+    public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    private Class<T> clazz;
+
+    private final ThreadLocal<Kryo> kryoLocal = ThreadLocal.withInitial(() -> {
+        Kryo kryo = new Kryo();
+        // 检测循环依赖,默认值为true,避免版本变化显式设置
+        kryo.setReferences(true);
+        // 默认值为true,避免版本变化显式设置
+        kryo.setRegistrationRequired(false);
+        kryo.register(clazz);
+        return kryo;
+    });
+
+    public KryoRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
+            return EMPTY_BYTE_ARRAY;
+        }
+
+        Kryo kryo = getKryo();
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+             Output output = new Output(baos)) {
+            kryo.writeClassAndObject(output, t);
+            output.flush();
+            return baos.toByteArray();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return EMPTY_BYTE_ARRAY;
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException {
+
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+
+        Kryo kryo = getKryo();
+        try (Input input = new Input(bytes)) {
+            return (T) kryo.readClassAndObject(input);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    private Kryo getKryo() {
+        return kryoLocal.get();
+    }
+}

+ 236 - 0
common/common-redis/src/main/java/cn/poyee/common/redis/service/RedisService.java

@@ -0,0 +1,236 @@
+package cn.poyee.common.redis.service;
+
+import cn.poyee.common.constraint.Constant;
+import cn.poyee.common.util.JsonUtil;
+
+import javax.annotation.Resource;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+public class RedisService {
+
+    @Resource
+    private StringRedisTemplate template;
+
+    public void set(String key, Object object, long exp, TimeUnit unit) {
+        template.opsForValue().set(key, Objects.requireNonNull(JsonUtil.toStr(object)), exp, unit);
+    }
+
+    public void set(String key, Object object) {
+        template.opsForValue().set(key, Objects.requireNonNull(JsonUtil.toStr(object)));
+    }
+
+    public <T> T get(String category, String key, Class<T> clazz) {
+        String v = template.opsForValue().get(category.concat(key));
+        T t = JsonUtil.toBean(v, clazz);
+        return t;
+    }
+
+    /**
+     * lpush 方法 并指定过期时间
+     */
+    public void lPush(String key, String value, Long seconds) {
+        try {
+            template.executePipelined((RedisCallback<String>) connection -> {
+                connection.lPush(key.getBytes(), value.getBytes());
+                connection.expire(key.getBytes(), seconds);
+                return null;
+            });
+        } catch (Exception ex) {
+            log.error("RedisUtils#pipelineSetEx fail! e:{}", ex.getMessage());
+        }
+    }
+
+    public void pipelineSetEx(Map<String, String> keyValues, Long seconds) {
+        try {
+            template.executePipelined((RedisCallback<String>) connection -> {
+                for (Map.Entry<String, String> entry : keyValues.entrySet()) {
+                    connection.setEx(entry.getKey().getBytes(), seconds,
+                            entry.getValue().getBytes());
+                }
+                return null;
+            });
+        } catch (Exception ex) {
+            log.error("RedisUtils#pipelineSetEx fail! e:{}", ex.getMessage());
+        }
+    }
+
+    public Long lastIndexOf(String key, String value) {
+        return template.opsForList().lastIndexOf(key, value);
+    }
+
+    public Long listRemove(String key, long count, String value) {
+        return template.opsForList().remove(key, count, value);
+    }
+
+    public Long listSize(String key) {
+        return template.opsForList().size(key);
+    }
+
+    public <T> T get(String key, Class<T> clazz) {
+        String v = template.opsForValue().get(key);
+        if (null == v) {
+            return null;
+        }
+        T t = JsonUtil.toBean(v, clazz);
+        return t;
+    }
+
+    public String get(String category, String key) {
+        return template.opsForValue().get(category.concat(key));
+    }
+
+    //
+    public void delete(String key) {
+        template.delete(key);
+    }
+
+    public void delete(Collection<String> keys) {
+        template.delete(keys);
+    }
+
+    public void delete(List<String> keys) {
+        template.delete(keys);
+    }
+
+    public Set<String> keys(String pattern) {
+        return template.keys(pattern);
+    }
+
+    public Set<String> keys(String category, String pattern) {
+        return template.keys(category + pattern);
+    }
+
+    public List<String> multiGet(Set<String> pattern) {
+        return template.opsForValue().multiGet(pattern);
+    }
+
+    public void multiSet(Map<String, String> pattern) {
+        template.opsForValue().multiSet(pattern);
+    }
+
+    public Map<Object, Object> hmget(String key) {
+        template.opsForHash().entries(key);
+        return template.opsForHash().entries(key);
+    }
+
+    public String hmget(String key, String hmkey) {
+        Object obj = template.opsForHash().get(key, hmkey);
+
+        return String.valueOf(obj);
+    }
+
+    public Set<Object> hmkeys(String key) {
+        return template.opsForHash().keys(key);
+    }
+
+    public boolean putAll(String key, Object object) {
+        try {
+            set(key, object);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    public long hmdel(String key, String hkey) {
+        return template.opsForHash().delete(key, hkey);
+    }
+
+    public boolean hmhas(String key, String hkey) {
+        return template.opsForHash().hasKey(key, hkey);
+    }
+
+    public long leftPush(String key, String values) {
+        return template.opsForList().leftPush(key, values);
+
+    }
+
+    public Boolean hasKey(String key) {
+        return template.hasKey(key);
+    }
+
+    /**
+     * 执行指定的lua脚本返回执行结果
+     * --KEYS[1]: 限流 key
+     * --ARGV[1]: 限流窗口
+     * --ARGV[2]: 当前时间戳(作为score)
+     * --ARGV[3]: 阈值
+     * --ARGV[4]: score 对应的唯一value
+     *
+     * @param redisScript
+     * @param keys
+     * @param args
+     * @return
+     */
+    public Boolean execLimitLua(RedisScript<Long> redisScript, List<String> keys, String... args) {
+
+        try {
+            Long execute = template.execute(redisScript, keys, args);
+            return Constant.TRUE.equals(execute.intValue());
+        } catch (Exception e) {
+            log.error("redis execLimitLua fail! e:{}", e.getMessage());
+        }
+
+        return false;
+    }
+
+    public Long setRemove(String key, String... v) {
+        return template.opsForSet().remove(key, v);
+    }
+
+    public Long setAdd(String key, String... v) {
+        return template.opsForSet().add(key, v);
+    }
+
+    public Long setSize(String key) {
+        return template.opsForSet().size(key);
+    }
+
+    public Set<String> members(String key) {
+        return template.opsForSet().members(key);
+    }
+
+    public boolean expire(String key, long exp, TimeUnit unit) {
+        return Boolean.TRUE.equals(template.expire(key, exp, unit));
+    }
+
+
+    public Long removeRange(String key, long start, long end) {
+        return template.opsForZSet().removeRange(key, start, end);
+    }
+
+    public Set reverseRange(String key, long start, long end) {
+        return template.opsForZSet().reverseRange(key, start, end);
+    }
+
+    public Long increment(String key, final long timeout, final TimeUnit unit) {
+        final Long increment = template.opsForValue().increment(key);
+        template.expire(key, timeout, unit);
+        return increment;
+    }
+
+    public Long decrement(String key, final long timeout, final TimeUnit unit) {
+        final Long decrement = template.opsForValue().decrement(key);
+        template.expire(key, timeout, unit);
+        return decrement;
+    }
+
+    public boolean setBit(String key, int idx, boolean bool) {
+        return template.opsForValue().setBit(key, idx, bool);
+    }
+
+    public boolean getBit(String key, int idx) {
+        return template.opsForValue().getBit(key, idx);
+    }
+}

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

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  cn.poyee.common.redis.cache.RedissonConfig

+ 40 - 0
common/common-web/pom.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>common</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>common-web</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.10.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 89 - 0
common/common-web/src/main/java/cn/poyee/common/web/config/OkHttpConfiguration.java

@@ -0,0 +1,89 @@
+package cn.poyee.common.web.config;
+
+
+import okhttp3.ConnectionPool;
+import okhttp3.OkHttpClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class OkHttpConfiguration {
+
+    @Value("${okhttp.connect-timeout:30}")
+    private Integer connectTimeout;
+
+    @Value("${okhttp.read-timeout:30}")
+    private Integer readTimeout;
+
+    @Value("${okhttp.write-timeout:30}")
+    private Integer writeTimeout;
+
+    @Value("${okhttp.max-idle-connections:200}")
+    private Integer maxIdleConnections;
+
+    @Value("${okhttp.keep-alive-duration:300}")
+    private Long keepAliveDuration;
+
+    @Bean
+    public OkHttpClient okHttpClient() {
+        return new OkHttpClient.Builder()
+                .sslSocketFactory(sslSocketFactory(), x509TrustManager())
+                .retryOnConnectionFailure(false)
+                .connectionPool(pool())
+                .connectTimeout(connectTimeout, TimeUnit.SECONDS)
+                .readTimeout(readTimeout, TimeUnit.SECONDS)
+                .writeTimeout(writeTimeout, TimeUnit.SECONDS)
+                .hostnameVerifier((hostname, session) -> true)
+                .build();
+    }
+
+    @Bean
+    public X509TrustManager x509TrustManager() {
+        return new X509TrustManager() {
+            @Override
+            public void checkClientTrusted(X509Certificate[] chain, String authType)
+                    throws CertificateException {
+            }
+
+            @Override
+            public void checkServerTrusted(X509Certificate[] chain, String authType)
+                    throws CertificateException {
+            }
+
+            @Override
+            public X509Certificate[] getAcceptedIssuers() {
+                return new X509Certificate[0];
+            }
+        };
+    }
+
+    @Bean
+    public SSLSocketFactory sslSocketFactory() {
+        try {
+            // 信任任何链接
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
+            return sslContext.getSocketFactory();
+        } catch (NoSuchAlgorithmException | KeyManagementException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    @Bean
+    public ConnectionPool pool() {
+        return new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.SECONDS);
+    }
+}

+ 28 - 0
common/common-web/src/main/java/cn/poyee/common/web/exception/BusinessException.java

@@ -0,0 +1,28 @@
+package cn.poyee.common.web.exception;
+
+import cn.poyee.common.result.IResultCode;
+import lombok.Getter;
+
+@Getter
+public class BusinessException extends RuntimeException {
+
+    public IResultCode resultCode;
+
+    public BusinessException(IResultCode errorCode) {
+        super(errorCode.getMsg());
+        this.resultCode = errorCode;
+    }
+
+    public BusinessException(String message) {
+        super(message);
+    }
+
+    public BusinessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BusinessException(Throwable cause) {
+        super(cause);
+    }
+
+}

+ 110 - 0
common/common-web/src/main/java/cn/poyee/common/web/exception/GlobalExceptionHandler.java

@@ -0,0 +1,110 @@
+package cn.poyee.common.web.exception;
+
+import cn.poyee.common.result.Result;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.core.NestedRuntimeException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.servlet.ServletException;
+import java.util.concurrent.CompletionException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    /**
+     * ServletException
+     */
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(ServletException.class)
+    public <T> Result<T> processException(ServletException e) {
+        log.error(e.getMessage(), e);
+        return Result.failed(e.getMessage());
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(IllegalArgumentException.class)
+    public <T> Result<T> handleIllegalArgumentException(IllegalArgumentException e) {
+        log.error("非法参数异常,异常原因:{}", e.getMessage(), e);
+        return Result.failed(e.getMessage());
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(JsonProcessingException.class)
+    public <T> Result<T> handleJsonProcessingException(JsonProcessingException e) {
+        log.error("Json转换异常,异常原因:{}", e.getMessage(), e);
+        return Result.failed(e.getMessage());
+    }
+
+    /**
+     * HttpMessageNotReadableException
+     */
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    public <T> Result<T> processException(HttpMessageNotReadableException e) {
+        log.error(e.getMessage(), e);
+        String errorMessage = "请求体不可为空";
+        Throwable cause = e.getCause();
+        if (cause != null) {
+            errorMessage = convertMessage(cause);
+        }
+        return Result.failed(errorMessage);
+    }
+
+    /**
+     * TypeMismatchException
+     */
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(TypeMismatchException.class)
+    public <T> Result<T> processException(TypeMismatchException e) {
+        log.error(e.getMessage(), e);
+        return Result.failed(e.getMessage());
+    }
+
+    @ResponseStatus(HttpStatus.ACCEPTED)
+    @ExceptionHandler(BusinessException.class)
+    public <T> Result<T> handleBizException(BusinessException e) {
+        log.error("业务异常,异常原因:{}", e.getMessage(), e);
+        if (e.getResultCode() != null) {
+            return Result.failed(e.getResultCode());
+        }
+        return Result.failed(e.getMessage());
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(NestedRuntimeException.class)
+    public <T> Result<T> nestedRuntimeException(CompletionException e) {
+        log.error(e.getMessage(), e);
+        return Result.failed(e.getMessage());
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(Exception.class)
+    public <T> Result<T> handleException(Exception ex) {
+        log.error(ex.getLocalizedMessage());
+        return Result.failed(ex.getLocalizedMessage());
+    }
+
+    private String convertMessage(Throwable throwable) {
+        String error = throwable.toString();
+        String regulation = "\\[\"(.*?)\"]+";
+        Pattern pattern = Pattern.compile(regulation);
+        Matcher matcher = pattern.matcher(error);
+        String group = "";
+        if (matcher.find()) {
+            String matchString = matcher.group();
+            matchString = matchString.replace("[", "").replace("]", "");
+            matchString = matchString.replaceAll("\\\"", "") + "字段类型错误";
+            group += matchString;
+        }
+        return group;
+    }
+}

+ 164 - 0
common/common-web/src/main/java/cn/poyee/common/web/util/OkHttpUtils.java

@@ -0,0 +1,164 @@
+package cn.poyee.common.web.util;
+
+import javax.annotation.Resource;
+
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class OkHttpUtils {
+    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+    private static final MediaType XML = MediaType.parse("application/xml; charset=utf-8");
+
+    @Resource
+    private OkHttpClient okHttpClient;
+
+    /**
+     * get 请求
+     *
+     * @param url 请求url地址
+     * @return string
+     */
+    public String doGet(String url) {
+        return doGet(url, null, null);
+    }
+
+    public String doGet(String url, Map<String, Object> params) {
+        return doGet(url, params, null);
+    }
+
+    public int getCode(String url, Map<String, String> headers) {
+        Request.Builder builder = getBuilderWithHeaders(headers);
+        Request request = builder.url(url).build();
+        try (Response response = okHttpClient.newCall(request).execute()) {
+            return response.code();
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+        return 500;
+    }
+
+    public String doGetWithHeaders(String url, Map<String, String> headers) {
+        return doGet(url, null, headers);
+    }
+
+    /**
+     * get 请求
+     *
+     * @param url     请求url地址
+     * @param params  请求参数 map
+     * @param headers 请求头字段 {k1, v1 k2, v2, ...}
+     * @return string
+     */
+    public String doGet(String url, Map<String, Object> params, Map<String, String> headers) {
+        StringBuilder sb = new StringBuilder(url);
+        if (params != null && params.keySet().size() > 0) {
+            boolean firstFlag = true;
+            for (String key : params.keySet()) {
+                if (firstFlag) {
+                    sb.append("?").append(key).append("=").append(params.get(key));
+                    firstFlag = false;
+                } else {
+                    sb.append("&").append(key).append("=").append(params.get(key));
+                }
+            }
+        }
+        Request.Builder builder = getBuilderWithHeaders(headers);
+        Request request = builder.url(sb.toString()).build();
+        return execute(request);
+    }
+
+    /**
+     * post 请求
+     *
+     * @param url     请求url地址
+     * @param params  请求参数 map
+     * @param headers 请求头字段 {k1, v1 k2, v2, ...}
+     * @return string
+     */
+    public String doPost(String url, Map<String, String> params, Map<String, String> headers) {
+        FormBody.Builder formBuilder = new FormBody.Builder();
+
+        if (params != null && params.keySet().size() > 0) {
+            for (String key : params.keySet()) {
+                formBuilder.add(key, params.get(key));
+            }
+        }
+        Request.Builder builder = getBuilderWithHeaders(headers);
+
+        Request request = builder.url(url).post(formBuilder.build()).build();
+        // log.info("do post request and url[{}]", url);
+
+        return execute(request);
+    }
+
+
+    /**
+     * 获取request Builder
+     *
+     * @param headers 请求头字段 {k1, v1 k2, v2, ...}
+     * @return
+     */
+    private Request.Builder getBuilderWithHeaders(Map<String, String> headers) {
+        Request.Builder builder = new Request.Builder();
+        if (null != headers && !headers.isEmpty()) {
+            for (Map.Entry<String, String> entry : headers.entrySet()) {
+                builder.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        return builder;
+    }
+
+
+    /**
+     * post 请求, 请求数据为 json 的字符串
+     *
+     * @param url  请求url地址
+     * @param json 请求数据, json 字符串
+     * @return string
+     */
+    public String doPostJson(String url, String json) {
+        return executePost(url, json, JSON, null);
+    }
+
+    /**
+     * post 请求, 请求数据为 json 的字符串
+     *
+     * @param url     请求url地址
+     * @param json    请求数据, json 字符串
+     * @param headers 请求头字段 {k1, v1 k2, v2, ...}
+     * @return string
+     */
+    public String doPostJsonWithHeaders(String url, String json, Map<String, String> headers) {
+//        log.info("do post request and url[{}]", url);
+        return executePost(url, json, JSON, headers);
+    }
+
+    private String executePost(String url, String data, MediaType contentType, Map<String, String> headers) {
+        RequestBody requestBody = RequestBody.create(contentType, data.getBytes(StandardCharsets.UTF_8));
+        Request.Builder builder = getBuilderWithHeaders(headers);
+        Request request = builder.url(url).post(requestBody).build();
+
+        return execute(request);
+    }
+
+    private String execute(Request request) {
+        try (Response response = okHttpClient.newCall(request).execute()) {
+            // final Integer code = response.code();
+            // log.debug("url:{} code:{}",request.url(), code);
+            if (response.isSuccessful()) {
+                return response.body().string();
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+        return null;
+    }
+
+}
+

+ 148 - 0
common/common-web/src/main/java/cn/poyee/common/web/util/RandomUtil.java

@@ -0,0 +1,148 @@
+package cn.poyee.common.web.util;
+
+import cn.poyee.common.util.NetworkUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.security.SecureRandom;
+import java.util.Objects;
+
+/**
+ * 增强型随机数工具类(支持字母数字随机字符串)
+ */
+@Slf4j
+@Component
+public class RandomUtil {
+    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
+    private static final char[] ALPHANUMERIC_CHARS =
+            "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
+
+    private final SecureRandom secureRandom;
+    private final byte[] buffer;
+    private int position;
+    private static final int BUFFER_SIZE = 1024; // 1KB缓冲
+
+    public RandomUtil() throws SocketException, UnknownHostException {
+        String hostname = NetworkUtil.getLocalHostName();
+        // log.debug("RandomUtil.hostname:{}", hostname);
+        Objects.requireNonNull(hostname, "Hostname不能为null");
+
+        try {
+            this.secureRandom = SecureRandom.getInstanceStrong();
+
+            byte[] hostnameBytes = hostname.getBytes();
+            byte[] seed = new byte[32];
+            new SecureRandom().nextBytes(seed);
+            System.arraycopy(hostnameBytes, 0, seed, 16,
+                    Math.min(hostnameBytes.length, 16));
+            secureRandom.setSeed(seed);
+
+            this.buffer = new byte[BUFFER_SIZE];
+            refill();
+        } catch (Exception e) {
+            throw new RuntimeException("随机数缓冲初始化失败", e);
+        }
+    }
+
+    private synchronized void refill() {
+        secureRandom.nextBytes(buffer);
+        position = 0;
+    }
+
+    public synchronized void nextBytes(byte[] bytes) {
+        if (bytes == null || bytes.length == 0) {
+            throw new IllegalArgumentException("字节数组不能为空");
+        }
+
+        if (position + bytes.length > BUFFER_SIZE) {
+            refill();
+        }
+
+        System.arraycopy(buffer, position, bytes, 0, bytes.length);
+        position += bytes.length;
+    }
+
+    public synchronized int nextInt() {
+        byte[] bytes = new byte[4];
+        nextBytes(bytes);
+        return ((bytes[0] & 0xFF) << 24) |
+                ((bytes[1] & 0xFF) << 16) |
+                ((bytes[2] & 0xFF) << 8) |
+                (bytes[3] & 0xFF);
+    }
+
+    public int nextInt(int bound) {
+        if (bound <= 0) {
+            throw new IllegalArgumentException("范围必须为正数");
+        }
+        int r = nextInt() >>> 1;
+        return r % bound;
+    }
+
+    /**
+     * 生成类UUID格式的随机字符串(32字符,包含字母数字)
+     * 示例:x5A7fE29b3C1d8G4h6J2k9L0m3N5o7P9
+     */
+    public String nextAlphanumericUUID() {
+        byte[] randomBytes = new byte[16];
+        nextBytes(randomBytes);
+
+        char[] result = new char[32];
+        for (int i = 0; i < 16; i++) {
+            // 每个字节转换为2个字符
+            int v = randomBytes[i] & 0xFF;
+            result[i*2] = ALPHANUMERIC_CHARS[v % ALPHANUMERIC_CHARS.length];
+            result[i*2+1] = ALPHANUMERIC_CHARS[(v + i) % ALPHANUMERIC_CHARS.length];
+        }
+        return new String(result);
+    }
+
+    /**
+     * 生成标准UUID格式的字符串(带连字符)
+     * 示例:550e8400-e29b-41d4-a716-446655440000
+     */
+    public String nextStandardUUID() {
+        byte[] randomBytes = new byte[16];
+        nextBytes(randomBytes);
+
+        // 设置版本和变体
+        randomBytes[6] &= 0x0f;
+        randomBytes[6] |= 0x40; // 版本4
+        randomBytes[8] &= 0x3f;
+        randomBytes[8] |= 0x80; // IETF变体
+
+        char[] buf = new char[36];
+        int j = 0;
+        for (int i = 0; i < 16; i++) {
+            if (i == 4 || i == 6 || i == 8 || i == 10) {
+                buf[j++] = '-';
+            }
+            int v = randomBytes[i] & 0xFF;
+            buf[j++] = HEX_CHARS[v >>> 4];
+            buf[j++] = HEX_CHARS[v & 0x0f];
+        }
+        return new String(buf);
+    }
+
+    /**
+     * 生成指定长度的字母数字随机字符串
+     * @param length 要生成的字符串长度
+     */
+    public String nextAlphanumeric(int length) {
+        if (length <= 0) {
+            throw new IllegalArgumentException("长度必须为正数");
+        }
+
+        char[] result = new char[length];
+        byte[] randomBytes = new byte[length];
+        nextBytes(randomBytes);
+
+        for (int i = 0; i < length; i++) {
+            int index = (randomBytes[i] & 0xFF) % ALPHANUMERIC_CHARS.length;
+            result[i] = ALPHANUMERIC_CHARS[index];
+        }
+        return new String(result);
+    }
+}

+ 4 - 0
common/common-web/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,4 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  cn.poyee.common.web.config.OkHttpConfiguration,\
+  cn.poyee.common.web.util.OkHttpUtils,\
+  cn.poyee.common.web.util.RandomUtil

+ 45 - 0
common/pom.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>im</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>common</artifactId>
+    <packaging>pom</packaging>
+
+    <properties>
+        <kryo.version>5.3.0</kryo.version>
+    </properties>
+
+    <modules>
+        <module>common-core</module>
+        <module>common-web</module>
+        <module>common-redis</module>
+        <module>common-mybatis</module>
+        <module>common-logback</module>
+        <module>common-mqtt</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+
+            <dependency>
+                <groupId>com.esotericsoftware</groupId>
+                <artifactId>kryo</artifactId>
+                <version>${kryo.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>cn.poyee</groupId>
+                <artifactId>common-core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>

+ 135 - 0
im-web/pom.xml

@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.poyee</groupId>
+        <artifactId>im</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>im-web</artifactId>
+
+    <dependencies>
+
+        <!-- websocket -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- rabbit -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-reactor-netty</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-web</artifactId>
+        </dependency>
+
+        <!--  redis dependency -->
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-redis</artifactId>
+        </dependency>
+
+        <!--  mybatis dependency -->
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.dynamictp</groupId>
+            <artifactId>dynamic-tp-core</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- logback fluentd -->
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-logback</artifactId>
+        </dependency>
+
+        <!-- flyway -->
+        <dependency>
+            <groupId>org.flywaydb</groupId>
+            <artifactId>flyway-core</artifactId>
+            <version>7.15.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.59</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+
+        <!-- qiniu SDK -->
+        <dependency>
+            <groupId>com.qiniu</groupId>
+            <artifactId>qiniu-java-sdk</artifactId>
+            <version>7.8.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.poyee</groupId>
+            <artifactId>common-mqtt</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-clean-plugin</artifactId>
+                <version>3.2.0</version>
+            </plugin>
+
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 14 - 0
im-web/src/main/java/cn/poyee/IMWebApplication.java

@@ -0,0 +1,14 @@
+package cn.poyee;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@Slf4j
+public class IMWebApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(IMWebApplication.class, args);
+        log.info("IM启动成功");
+    }
+}

+ 43 - 0
im-web/src/main/java/cn/poyee/config/AMQPConfig.java

@@ -0,0 +1,43 @@
+package cn.poyee.config;
+
+import org.springframework.amqp.core.*;
+import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnProperty(name = "mqtt.pattern", havingValue = "amqp")
+public class AMQPConfig {
+    public static final String QUEUE_NAME = "svc.queue";
+    public static final String ROUTING_SVC_KEY = ".svc";
+    public static final String ROUTING_LAST_WILL_KEY = ".will";
+
+    @Bean
+    public Queue svcQueue() {
+        return new Queue(QUEUE_NAME, true);
+    }
+
+    @Bean
+    public Binding svcBinding(Queue svcQueue) {
+        return BindingBuilder.bind(svcQueue)
+                .to(new TopicExchange("amq.topic"))
+                .with(ROUTING_SVC_KEY);
+    }
+
+    @Bean
+    public Binding lastWillBinding(Queue svcQueue) {
+        return BindingBuilder.bind(svcQueue)
+                .to(new TopicExchange("amq.topic"))
+                .with(ROUTING_LAST_WILL_KEY);
+    }
+
+    @Bean("ampqContainerFactory")
+    public SimpleRabbitListenerContainerFactory ampqContainerFactory(ConnectionFactory connectionFactory) {
+        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
+        factory.setConnectionFactory(connectionFactory);
+        factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
+        return factory;
+    }
+}

+ 27 - 0
im-web/src/main/java/cn/poyee/config/HandlerThreadPoolConfig.java

@@ -0,0 +1,27 @@
+package cn.poyee.config;
+
+import cn.poyee.common.constraint.ThreadPoolConstant;
+import com.dtp.common.em.QueueTypeEnum;
+import com.dtp.common.em.RejectedTypeEnum;
+import com.dtp.core.support.ThreadPoolBuilder;
+import com.dtp.core.thread.DtpExecutor;
+
+import java.util.concurrent.TimeUnit;
+
+public class HandlerThreadPoolConfig {
+
+    private static final String PRE_FIX = "im.";
+
+    public static DtpExecutor getExecutor(String groupId) {
+        return ThreadPoolBuilder.newBuilder()
+                .threadPoolName(PRE_FIX + groupId)
+                .corePoolSize(ThreadPoolConstant.COMMON_CORE_POOL_SIZE)
+                .maximumPoolSize(ThreadPoolConstant.COMMON_MAX_POOL_SIZE)
+                .keepAliveTime(ThreadPoolConstant.COMMON_KEEP_LIVE_TIME)
+                .timeUnit(TimeUnit.SECONDS)
+                .rejectedExecutionHandler(RejectedTypeEnum.CALLER_RUNS_POLICY.getName())
+                .allowCoreThreadTimeOut(false)
+                .workQueue(QueueTypeEnum.VARIABLE_LINKED_BLOCKING_QUEUE.getName(), ThreadPoolConstant.COMMON_QUEUE_SIZE, false)
+                .buildDynamic();
+    }
+}

+ 49 - 0
im-web/src/main/java/cn/poyee/config/MetricsConfig.java

@@ -0,0 +1,49 @@
+package cn.poyee.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import io.micrometer.core.instrument.Clock;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.logging.LoggingMeterRegistry;
+import io.micrometer.core.instrument.logging.LoggingRegistryConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.time.Duration;
+
+@Configuration
+public class MetricsConfig {
+    @Resource
+    private DruidDataSource druidDataSource;
+    @Resource
+    private MeterRegistry meterRegistry;
+    @Autowired
+    public void configureMetrics(MeterRegistry registry) {
+        registry.config().commonTags(
+                "application", "poyee-im"
+        );
+    }
+    @PostConstruct
+    public void init() {
+        meterRegistry.gauge("druid.activeCount", druidDataSource, DruidDataSource::getActiveCount);
+        meterRegistry.gauge("druid.poolingCount", druidDataSource, DruidDataSource::getPoolingCount);
+        meterRegistry.gauge("druid.connectCount", druidDataSource, DruidDataSource::getConnectCount);
+        meterRegistry.gauge("druid.closeCount", druidDataSource, DruidDataSource::getCloseCount);
+        meterRegistry.gauge("druid.recycleErrorCount", druidDataSource, DruidDataSource::getRecycleErrorCount);
+        meterRegistry.gauge("druid.connectErrorCount", druidDataSource, DruidDataSource::getConnectErrorCount);
+        meterRegistry.gauge("druid.recycleCount", druidDataSource, DruidDataSource::getRecycleCount);
+        meterRegistry.gauge("druid.removeAbandonedCount", druidDataSource, DruidDataSource::getRemoveAbandonedCount);
+        meterRegistry.gauge("druid.notEmptyWaitCount", druidDataSource, DruidDataSource::getNotEmptyWaitCount);
+        meterRegistry.gauge("druid.notEmptySignalCount", druidDataSource, DruidDataSource::getNotEmptySignalCount);
+        meterRegistry.gauge("druid.notEmptyWaitNanos", druidDataSource, DruidDataSource::getNotEmptyWaitNanos);
+        meterRegistry.gauge("druid.activePeak", druidDataSource, DruidDataSource::getActivePeak);
+        meterRegistry.gauge("druid.poolingPeak", druidDataSource, DruidDataSource::getPoolingPeak);
+        meterRegistry.gauge("druid.discardCount", druidDataSource, DruidDataSource::getDiscardCount);
+        meterRegistry.gauge("druid.notEmptyWaitThreadCount", druidDataSource, DruidDataSource::getNotEmptyWaitThreadCount);
+        meterRegistry.gauge("druid.notEmptyWaitThreadPeak", druidDataSource, DruidDataSource::getNotEmptyWaitThreadPeak);
+    }
+}

+ 29 - 0
im-web/src/main/java/cn/poyee/config/RabbitConsumer.java

@@ -0,0 +1,29 @@
+package cn.poyee.config;
+
+import cn.poyee.service.UnifiedMessageService;
+import org.springframework.amqp.AmqpRejectAndDontRequeueException;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConditionalOnProperty(name = "mqtt.pattern", havingValue = "amqp")
+public class RabbitConsumer {
+
+    @Autowired
+    private UnifiedMessageService unifiedMessageService;
+
+    @RabbitListener(queues = AMQPConfig.QUEUE_NAME, containerFactory = "ampqContainerFactory")
+    public void serialNumberMessage(String payload) {
+        try {
+            byte[] bytes = payload.getBytes();
+            Message<byte[]> genericMessage = new GenericMessage<>(bytes);
+            unifiedMessageService.chatHandler(genericMessage);
+        } catch (Exception ex) {
+            throw new AmqpRejectAndDontRequeueException("消息格式错误: " + ex.getMessage() + payload);
+        }
+    }
+}

+ 55 - 0
im-web/src/main/java/cn/poyee/config/SchedulerConfig.java

@@ -0,0 +1,55 @@
+package cn.poyee.config;
+
+import cn.poyee.entity.Task;
+import cn.poyee.scheduled.TaskExecutor;
+import cn.poyee.service.ChatRoomService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Configuration
+@AllArgsConstructor
+public class SchedulerConfig {
+
+    private final ChatRoomService chatRoomService;
+
+    @Bean
+    public TaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+        scheduler.setPoolSize(2);
+        scheduler.setThreadNamePrefix("task-scheduler-");
+        scheduler.initialize();
+        return scheduler;
+    }
+
+    @Component("taskType1Executor")
+    public class TaskType1Executor implements TaskExecutor {
+        @Override
+        public void executeTask(Task task) {
+            try {
+                chatRoomService.synchronizePeople2App();
+            } catch (Exception ex) {
+                log.error("任务 synchronizePeople2App {} 执行异常:{}", task.getTaskName(), ex.getMessage());
+            }
+        }
+    }
+
+    @Component("taskType2Executor")
+    public class TaskType2Executor implements TaskExecutor {
+        @Override
+        public void executeTask(Task task) {
+            try {
+                 // chatRoomService.synchronizeTerminalDetails();
+                chatRoomService.synchronizedIncrementalPeople();
+            } catch (Exception ex) {
+                log.error("任务 synchronizeTerminalDetails {} 执行异常:{}", task.getTaskName(), ex.getMessage());
+            }
+        }
+    }
+
+}

+ 16 - 0
im-web/src/main/java/cn/poyee/config/StompPrincipal.java

@@ -0,0 +1,16 @@
+package cn.poyee.config;
+
+import java.security.Principal;
+
+public class StompPrincipal implements Principal {
+    private final String name;
+
+    public StompPrincipal(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}

+ 96 - 0
im-web/src/main/java/cn/poyee/config/StompSocketConfig.java

@@ -0,0 +1,96 @@
+package cn.poyee.config;
+
+import cn.poyee.handler.HeaderParamInterceptor;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.ChannelRegistration;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
+
+@Slf4j
+@Configuration
+@EnableWebSocketMessageBroker
+@AllArgsConstructor
+public class StompSocketConfig implements WebSocketMessageBrokerConfigurer {
+
+    public static final String DEFAULT_TOPIC = "/topic/";
+    public static final String DEFAULT_QUEUE = "/queue/";
+
+    private final RabbitProperties rabbitProperties;
+    private final HeaderParamInterceptor headerParamInterceptor;
+
+    @Override
+    public void registerStompEndpoints(StompEndpointRegistry registry) {
+        int taskNum = 10;
+        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
+        taskScheduler.setPoolSize(taskNum);
+        taskScheduler.setThreadNamePrefix("task-");
+        taskScheduler.afterPropertiesSet();
+        registry.addEndpoint("/stomp")
+                .setAllowedOriginPatterns("*")
+                .withSockJS()
+                .setStreamBytesLimit(512 * 1024)
+                .setTaskScheduler(taskScheduler)
+                .setHttpMessageCacheSize(1000)
+                .setDisconnectDelay(30 * 1000);
+
+        registry.addEndpoint("/ws")
+                .setAllowedOriginPatterns("*");
+    }
+
+    @Override
+    public void configureMessageBroker(MessageBrokerRegistry registry) {
+
+        int DEFAULT_RELAY_PORT = 61613;
+        String DESTINATION_PREFIXES = "/app";
+        String[] STOMP_BROKER_RELAY = {DEFAULT_QUEUE, DEFAULT_TOPIC};
+        registry.setApplicationDestinationPrefixes(DESTINATION_PREFIXES)
+                .enableStompBrokerRelay(STOMP_BROKER_RELAY)
+                // 使用客户端心跳配置
+                .setSystemHeartbeatReceiveInterval(10000)
+                .setSystemHeartbeatSendInterval(0)
+                .setVirtualHost(rabbitProperties.getVirtualHost())
+                .setRelayHost(rabbitProperties.getHost())
+                .setRelayPort(DEFAULT_RELAY_PORT)
+                .setClientLogin(rabbitProperties.getUsername())
+                .setClientPasscode(rabbitProperties.getPassword())
+                .setSystemLogin(rabbitProperties.getUsername())
+                .setSystemPasscode(rabbitProperties.getPassword());
+    }
+
+    @Override
+    public void configureClientInboundChannel(ChannelRegistration registration) {
+        registration
+                .interceptors(headerParamInterceptor)
+                .taskExecutor()
+                .corePoolSize(4)
+                .maxPoolSize(10)
+                .queueCapacity(1000)
+                .keepAliveSeconds(60);
+    }
+
+    @Override
+    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
+        registration
+                .setMessageSizeLimit(128 * 1024)            // 128KB 消息大小限制
+                .setSendBufferSizeLimit(512 * 1024)         // 512KB 发送缓冲区限制
+                .setSendTimeLimit(20 * 1000)                // 20秒发送超时
+                .setTimeToFirstMessage(60 * 1000);          // 60秒首条消息超时
+    }
+
+    @Override
+    public void configureClientOutboundChannel(ChannelRegistration registration) {
+        // 配置出站通道的线程池
+        registration.taskExecutor()
+                .corePoolSize(4)
+                .maxPoolSize(10)
+                .queueCapacity(1000)
+                .keepAliveSeconds(60);
+    }
+}

+ 48 - 0
im-web/src/main/java/cn/poyee/config/ThreadPoolExecutorShutdownDefinition.java

@@ -0,0 +1,48 @@
+package cn.poyee.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Component
+@Slf4j
+public class ThreadPoolExecutorShutdownDefinition implements ApplicationListener<ContextClosedEvent> {
+
+    private final List<ExecutorService> POOLS = Collections.synchronizedList(new ArrayList<>(12));
+    private final long AWAIT_TERMINATION = 20;
+    private final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
+
+    public void registryExecutor(ExecutorService executor) {
+        POOLS.add(executor);
+    }
+
+    @Override
+    public void onApplicationEvent(ContextClosedEvent event) {
+        if (CollectionUtils.isEmpty(POOLS)) {
+            return;
+        }
+        for (ExecutorService pool : POOLS) {
+            pool.shutdown();
+            try {
+                if (!pool.awaitTermination(AWAIT_TERMINATION, TIME_UNIT)) {
+                    if (log.isWarnEnabled()) {
+                        log.warn("Timed out while waiting for executor [{}] to terminate", pool);
+                    }
+                }
+            } catch (InterruptedException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn("Timed out while waiting for executor [{}] to terminate", pool);
+                }
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+}

+ 52 - 0
im-web/src/main/java/cn/poyee/config/bean/ContentCensorConfig.java

@@ -0,0 +1,52 @@
+package cn.poyee.config.bean;
+
+import cn.poyee.service.censor.BaiduImageCensor;
+import cn.poyee.service.censor.BaiduTextCensor;
+import cn.poyee.service.censor.NonOpTextCensor;
+import cn.poyee.service.censor.QiNiuTextCensor;
+import cn.poyee.util.BaiduAuth;
+import com.qiniu.util.Auth;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+@Configuration
+public class ContentCensorConfig {
+
+    @Bean
+    @Primary
+    BaiduAuth baiduAuth(@Value("${hobbystock.baidu.apiKey}") String clientId, @Value("${hobbystock.baidu.secretKey}") String clientSecret) {
+        return BaiduAuth.create(clientId, clientSecret);
+    }
+
+    @ConditionalOnProperty(prefix = "hobbystock.baidu.censor.text", name = "enabled", havingValue = "true", matchIfMissing = true)
+    @Bean
+    @Primary
+    BaiduTextCensor baiduCensor(BaiduAuth auth) {
+        return new BaiduTextCensor(auth);
+    }
+
+    @ConditionalOnProperty(prefix = "hobbystock.baidu.censor.text", name = "enabled", havingValue = "false")
+    @Bean
+    @Primary
+    NonOpTextCensor nonopTextCensor() { return new NonOpTextCensor(); }
+
+    @Bean
+    @Primary
+    BaiduImageCensor baiduImageCensor(BaiduAuth auth) {
+        return new BaiduImageCensor(auth);
+    }
+
+    @Bean
+    Auth qiniuAuth(@Value("${hobbystock.qiniu.accessKey}") String accessKey,
+                   @Value("${hobbystock.qiniu.secretKey}") String secretKey) {
+        return Auth.create(accessKey, secretKey);
+    }
+
+    @Bean
+    QiNiuTextCensor qiniuCensor(Auth auth) {
+        return new QiNiuTextCensor(auth);
+    }
+}

+ 78 - 0
im-web/src/main/java/cn/poyee/config/bean/RabbitMessageConfirmationHandler.java

@@ -0,0 +1,78 @@
+package cn.poyee.config.bean;
+
+import cn.poyee.common.util.JsonUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.ReturnedMessage;
+import org.springframework.amqp.rabbit.connection.CorrelationData;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+@Slf4j
+@Component
+public class RabbitMessageConfirmationHandler {
+
+//    @Autowired
+//    private Hand messageLogRepository;
+
+    // 确认回调处理
+    @Async
+    public void handleConfirm(CorrelationData correlationData, boolean ack, String cause) {
+        if (Objects.isNull(correlationData) || Objects.isNull(correlationData.getId())) return;
+
+        if (ack) {
+            // log.info("消息:{}发送成功", correlationData.getId());
+            // messageLogRepository.updateStatus(correlationData.getId(), "DELIVERED");
+        } else {
+            log.error("消息:{}发送失败", correlationData.getId());
+            // messageLogRepository.updateStatus(correlationData.getId(), "FAILED", cause);
+            // 可以添加重试逻辑
+        }
+    }
+
+    // 返回回调处理
+    @Async
+    public void handleReturn(ReturnedMessage returned) {
+        String messageStr = new String(returned.getMessage().getBody());
+        ObjectNode messageBean = JsonUtil.toBean(messageStr, ObjectNode.class);
+        JsonNode messageParam = messageBean.get("messageParam");
+        String messageId = messageParam.get("messageId").asText();
+
+        // log.info("回调messageId:{}", messageId);
+
+        // 1. 记录原始消息
+//        messageLogRepository.save(new MessageLog(
+//                messageId,
+//                new String(returned.getMessage().getBody()),
+//                "UNDELIVERED",
+//                returned.getExchange(),
+//                returned.getRoutingKey(),
+//                returned.getReplyText()
+//        ));
+
+        // 2. 根据重试次数决定处理方式
+//        int retryCount = messageLogRepository.getRetryCount(messageId);
+//        if (retryCount < 3) {
+//            // 延迟重试
+//            rabbitTemplate.convertAndSend(
+//                    RETRY_EXCHANGE,
+//                    returned.getRoutingKey(),
+//                    returned.getMessage(),
+//                    msg -> {
+//                        msg.getMessageProperties().setDelay(5000 * (retryCount + 1));
+//                        return msg;
+//                    }
+//            );
+//        } else {
+//            // 最终进入死信队列
+//            rabbitTemplate.convertAndSend(
+//                    DLX_EXCHANGE,
+//                    "undelivered",
+//                    returned.getMessage()
+//            );
+//        }
+    }
+}

+ 55 - 0
im-web/src/main/java/cn/poyee/config/bean/RabbitMultiTemplateConfig.java

@@ -0,0 +1,55 @@
+package cn.poyee.config.bean;
+
+import cn.poyee.config.properties.JwtProperties;
+import cn.poyee.config.properties.RabbitProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+
+@Slf4j
+@Configuration
+public class RabbitMultiTemplateConfig {
+
+    @Resource
+    private RabbitProperties rabbitProp;
+
+    @Bean
+    public CachingConnectionFactory connectionFactory() {
+        CachingConnectionFactory factory = new CachingConnectionFactory();
+        factory.setHost(rabbitProp.host);
+        factory.setUsername(rabbitProp.username);
+        factory.setPassword(rabbitProp.password);
+        // 启用确认机制
+        factory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
+        // 启用消息返回机制
+        factory.setPublisherReturns(true);
+
+        return factory;
+    }
+
+    @Bean(name = "rabbitTemplate")
+    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
+
+        RabbitTemplate template = new RabbitTemplate(connectionFactory);
+        template.setMandatory(false);
+        return template;
+    }
+
+    @Bean(name = "confirmRabbitTemplate")
+    public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory,
+                                                RabbitMessageConfirmationHandler handler) {
+        RabbitTemplate template = new RabbitTemplate(connectionFactory);
+        template.setMandatory(true);
+
+        template.setConfirmCallback(handler::handleConfirm);
+        template.setReturnsCallback(handler::handleReturn);
+
+        return template;
+    }
+}

+ 27 - 0
im-web/src/main/java/cn/poyee/config/cache/ReactiveRedisConfig.java

@@ -0,0 +1,27 @@
+package cn.poyee.config.cache;
+
+import cn.poyee.entity.LiveMessage;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class ReactiveRedisConfig {
+
+    @Bean("genericRedisTemplate")
+    public ReactiveRedisTemplate<String, Object> genericRedisTemplate(ReactiveRedisConnectionFactory cf) {
+
+        Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer(Object.class);
+        RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
+                .<String, LiveMessage>newSerializationContext()
+                .key(new StringRedisSerializer())
+                .value(valueSerializer)
+                .hashKey(valueSerializer)
+                .hashValue(valueSerializer).build();
+        return new ReactiveRedisTemplate<>(cf, serializationContext);
+    }
+}

+ 16 - 0
im-web/src/main/java/cn/poyee/config/properties/JwtProperties.java

@@ -0,0 +1,16 @@
+package cn.poyee.config.properties;
+
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Setter
+@Configuration
+@ConfigurationProperties(prefix = "jwt")
+public class JwtProperties {
+    public Boolean verify;
+    public String coreUrl;
+    public String pyAppUrl;
+    public String pyOrderUrl;
+//    public String pySvcName;
+}

+ 14 - 0
im-web/src/main/java/cn/poyee/config/properties/RabbitProperties.java

@@ -0,0 +1,14 @@
+package cn.poyee.config.properties;
+
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Setter
+@Configuration
+@ConfigurationProperties(prefix = "spring.rabbitmq")
+public class RabbitProperties {
+    public String host;
+    public String username;
+    public String password;
+}

+ 14 - 0
im-web/src/main/java/cn/poyee/constant/MessageChildType.java

@@ -0,0 +1,14 @@
+package cn.poyee.constant;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class MessageChildType {
+
+    public static Set<String> requiredCases = new HashSet<>(Arrays.asList(
+            "mallUpadteAddress",
+            "refundOrder",
+            "reviewGroupBuying"
+    ));
+}

+ 36 - 0
im-web/src/main/java/cn/poyee/constant/RedisConstants.java

@@ -0,0 +1,36 @@
+package cn.poyee.constant;
+
+public interface RedisConstants {
+    // 标记直播间人数是否变更过
+    String CHAT_ROOM_PEOPLE_CHANGE = "CHAT_ROOM_PEOPLE_CHANGE";
+
+    String CHAT_ROOM_PEOPLE_NUM = "CHAT_ROOM_PEOPLE_NUM::";
+    String CHAT_P2P_RECORD_LAST = "CHAT_P2P_RECORD_LAST::";
+    String CHAT_P2P_NO_READ = "CHAT_P2P_NO_READ::";
+    // 非读条数
+    String CHAT_P2P_NO_READ_NUM = "CHAT_P2P_NO_READ_NUM::";
+
+    String CHAT_RELATIONS = "CHAT_RELATIONS::";
+
+    // 公告已读
+    String MESSAGE_READ = "MESSAGE_READ::";
+
+    // MQTT用户在线用户
+     String MQTT_PEOPLE_ONLINE = "MQTT_PEOPLE_ONLINE";
+    // MQTT直播间在线用户
+     String MQTT_ROOM_PEOPLE = "MQTT_ROOM_PEOPLE_NUM::";
+
+    // 用户信息
+    String USER_ROLE_IDENTITY = "USER_ROLE_IDENTITY::";
+
+    // 用户信息列表
+    String USER_DETAIL = "USER_DETAIL::";
+    String MERCHANT_LIST = "MERCHANT_LIST";
+
+    // 用户与商家的映射
+    String USER_MERCHANT = "USER_MERCHANT::";
+    // 通过昵称记录IDENTITY, 如 yyds -> mid_13
+    // String NICK_NAME_IDENTITY = "NICK_NAME_IDENTITY::";
+    String STOMP_TOKEN_PREFIX = "STOMP_TOKEN_PREFIX::";
+
+}

+ 9 - 0
im-web/src/main/java/cn/poyee/constant/TempConstants.java

@@ -0,0 +1,9 @@
+package cn.poyee.constant;
+// 可删除
+public class TempConstants {
+
+    // 可删除
+  public static   String spn = "0";
+    public static  String rpn = "0";
+
+}

+ 11 - 0
im-web/src/main/java/cn/poyee/dto/model/ChatToBroadcast.java

@@ -0,0 +1,11 @@
+package cn.poyee.dto.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ChatToBroadcast extends ContentModel {
+    private String contentType;
+    private String content;
+}

+ 12 - 0
im-web/src/main/java/cn/poyee/dto/model/ChatToMerchant.java

@@ -0,0 +1,12 @@
+package cn.poyee.dto.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ChatToMerchant extends ContentModel {
+    private String contentType;
+    private String sender;
+    private String content;
+}

+ 14 - 0
im-web/src/main/java/cn/poyee/dto/model/ChatToPersonal.java

@@ -0,0 +1,14 @@
+package cn.poyee.dto.model;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ChatToPersonal extends ContentModel {
+    private String contentType;
+    private String sender;
+    private String content;
+    private JsonNode extra;
+}

+ 14 - 0
im-web/src/main/java/cn/poyee/dto/model/ChatToRoom.java

@@ -0,0 +1,14 @@
+package cn.poyee.dto.model;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ChatToRoom extends ContentModel {
+    private String contentType;
+    private String sender;
+    private String content;
+    private JsonNode extra;
+}

+ 6 - 0
im-web/src/main/java/cn/poyee/dto/model/ContentModel.java

@@ -0,0 +1,6 @@
+package cn.poyee.dto.model;
+
+import java.io.Serializable;
+
+public class ContentModel implements Serializable {
+}

+ 14 - 0
im-web/src/main/java/cn/poyee/dto/model/NoRecord.java

@@ -0,0 +1,14 @@
+package cn.poyee.dto.model;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class NoRecord extends ContentModel {
+    private String contentType;
+    private String sender;
+    private String content;
+    private JsonNode extra;
+}

+ 47 - 0
im-web/src/main/java/cn/poyee/dto/processor/MessageConverter.java

@@ -0,0 +1,47 @@
+package cn.poyee.dto.processor;
+
+import cn.poyee.entity.dto.MessageRequest;
+import cn.poyee.pipeline.ProcessModel;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.messaging.MessagingException;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+
+@Slf4j
+@Service
+public class MessageConverter {
+
+    private final ObjectMapper objectMapper;
+    private final MessageTypeRegistry typeRegistry;
+
+    public MessageConverter(ObjectMapper objectMapper, MessageTypeRegistry typeRegistry) {
+        this.objectMapper = objectMapper;
+        this.typeRegistry = typeRegistry;
+    }
+
+    public ProcessModel convert(byte[] payload) {
+        try {
+
+            // 1. 先解析基本结构
+            MessageRequest request = objectMapper.readValue(payload, MessageRequest.class);
+
+            // 2. 获取对应的模型类
+            Class<? extends ProcessModel> modelClass = typeRegistry.getModelClass(
+                    request.getCode(),
+                    request.getMessageType()
+            );
+
+//            if (modelClass == null) {
+//                throw new MessagingException("Unknown message type: code=" + request.getCode() + " , messageType= " + request.getMessageType());
+//            }
+
+            // 3. 转换为具体模型
+            return objectMapper.convertValue(request, modelClass);
+        } catch (MessagingException | IOException e) {
+            log.error("MessageConverter.convert err:{} 错误信息:{}", new String(payload),e.getMessage());
+            return null;
+        }
+    }
+}

+ 48 - 0
im-web/src/main/java/cn/poyee/dto/processor/MessageProcessor.java

@@ -0,0 +1,48 @@
+package cn.poyee.dto.processor;
+
+import cn.poyee.common.result.ResultCode;
+import cn.poyee.common.web.exception.BusinessException;
+import cn.poyee.enums.BusinessCode;
+import cn.poyee.pending.Task;
+import cn.poyee.pending.TaskPendingHolder;
+import cn.poyee.pipeline.ProcessModel;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.ExecutorService;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class MessageProcessor {
+    private final ApplicationContext applicationContext;
+    private final TaskPendingHolder taskPendingHolder;
+
+    public Mono<Void> process(ProcessModel model) {
+        return Mono.just(model)
+                .map(this::determineRouteKey)
+                .flatMap(routeKey -> submitTask(model, routeKey))
+                .onErrorMap(e -> new BusinessException(ResultCode.MESSAGE_CONSUMPTION_ERROR.getMsg(), e));
+    }
+
+    private String determineRouteKey(ProcessModel model) {
+        return BusinessCode.STATE.getCodeEn().equals(model.getCode())
+                ? model.getCode()
+                : model.getMessageType();
+    }
+
+    private Mono<Void> submitTask(ProcessModel model, String routeKey) {
+        return Mono.fromRunnable(() -> {
+            try {
+                Task<ProcessModel> task = Task.of(model, applicationContext);
+                ExecutorService executor = taskPendingHolder.route(routeKey);
+                executor.execute(task);
+            } catch (Exception ex) {
+                log.error("submitTask.error:{}", ex.getMessage());
+            }
+        });
+    }
+}

+ 36 - 0
im-web/src/main/java/cn/poyee/dto/processor/MessageTypeRegistry.java

@@ -0,0 +1,36 @@
+package cn.poyee.dto.processor;
+
+import cn.poyee.entity.dto.ChatModel;
+import cn.poyee.entity.dto.StateModel;
+import cn.poyee.pipeline.ProcessModel;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class MessageTypeRegistry {
+    private final Map<String, Class<? extends ProcessModel>> modelMap = new HashMap<>();
+
+    public MessageTypeRegistry() {
+        register("chat", "chat_to_room", ChatModel.class);
+        register("chat", "chat_to_personal", ChatModel.class);
+        register("chat", "chat_to_merchant", ChatModel.class);
+        register("chat", "chat_to_broadcast", ChatModel.class);
+        register("chat", "no_record", ChatModel.class);
+        register("state", "in_room", StateModel.class);
+        register("state", "online", StateModel.class);
+    }
+
+    public void register(String code, String messageType, Class<? extends ProcessModel> modelClass) {
+        modelMap.put(getKey(code, messageType), modelClass);
+    }
+
+    public Class<? extends ProcessModel> getModelClass(String code, String messageType) {
+        return modelMap.get(getKey(code, messageType));
+    }
+
+    private String getKey(String code, String messageType) {
+        return code + ":" + messageType;
+    }
+}

+ 15 - 0
im-web/src/main/java/cn/poyee/endpoint/ActuatorController.java

@@ -0,0 +1,15 @@
+package cn.poyee.endpoint;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/actuator")
+public class ActuatorController {
+
+    @RequestMapping("/health")
+    public String health() {
+        return "ok";
+    }
+
+}

+ 78 - 0
im-web/src/main/java/cn/poyee/endpoint/ChatP2PController.java

@@ -0,0 +1,78 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.entity.ChatP2PHistory;
+import cn.poyee.entity.query.ChatP2PHistoryQuery;
+import cn.poyee.service.ChatP2PService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/api/chat/p2p")
+public class ChatP2PController {
+
+    private final ChatP2PService historyService;
+
+    @GetMapping("/v1/paging")
+    public Result<Object> pagingV1(ChatP2PHistoryQuery query) {
+        Page<ChatP2PHistory> result = historyService.pagingV1(query);
+        return Result.success(result.getRecords(), result.getTotal());
+    }
+
+    @GetMapping("/hist")
+    public Result<Page<ChatP2PHistory>> historyList(ChatP2PHistoryQuery query) {
+        return Result.success(historyService.pageHistory(query));
+    }
+
+    @PutMapping("/batch-update")
+    public Result<String> updateBatch(String[] mid) {
+        historyService.updateBatch(mid);
+        return Result.success();
+    }
+
+    @PutMapping
+    public Result<Boolean> updateHistory(ChatP2PHistory entity) {
+        return Result.success(historyService.updateHistory(entity));
+    }
+
+    @GetMapping("/read-all")
+    public Result<Boolean> readAll(String sender) {
+        // 为了请求的方式统一
+        // 这里的sender其实是要被修改的接收者
+        return Result.success(historyService.readAll(sender));
+    }
+
+    @DeleteMapping
+    public Result<Boolean> deleteHistory(String sender) {
+        return Result.success(historyService.deleteHistory(sender));
+    }
+
+    @Deprecated
+    @GetMapping("/all-hist")
+    public Result<List<ChatP2PHistory>> allHist() {
+        return Result.success(historyService.allHist());
+    }
+
+    @Deprecated(since = "消息丢失确认")
+    @PostMapping
+    public Result<Boolean> messageCheck(@RequestBody ObjectNode message) {
+        return Result.success(historyService.messageCheck(message));
+    }
+
+    @Deprecated
+    @GetMapping
+    public Result<List<String>> messageCheckGet(String key, Integer start, Integer end) {
+        return Result.success(historyService.messageCheckGet(key, start, end));
+    }
+
+    @Deprecated
+    @GetMapping("/message-check-count")
+    public Result<Long> messageCheckCount(String key) {
+        return Result.success(historyService.messageCheckCount(key));
+    }
+}

+ 48 - 0
im-web/src/main/java/cn/poyee/endpoint/ChatRelationsController.java

@@ -0,0 +1,48 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.entity.ChatRelations;
+import cn.poyee.entity.query.ChatRelationsQuery;
+import cn.poyee.service.ChatRelationsService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping(value = "/api/chat/relation")
+public class ChatRelationsController {
+
+    private final ChatRelationsService chatRelationsService;
+
+    @GetMapping("/v1/paging")
+    public Result<Object> relationPagingV1(ChatRelationsQuery query) {
+        Page<ChatRelations> result = chatRelationsService.pagingV1(query);
+        return Result.success(result.getRecords(), result.getTotal());
+    }
+
+    @Deprecated
+    @GetMapping("/paging")
+    public Result<Object> relationPaging(ChatRelationsQuery query) {
+        Page<ChatRelations> result = chatRelationsService.paging(query);
+        return Result.success(result.getRecords(), result.getTotal());
+    }
+
+    @Deprecated
+    @GetMapping("/all")
+    public Result<Object> all(ChatRelationsQuery query) {
+        List<ChatRelations> result = chatRelationsService.all(query);
+        return Result.success(result);
+    }
+
+    @Deprecated
+    @DeleteMapping("/delete")
+    public int delete(ChatRelationsQuery query) {
+        return chatRelationsService.delete(query);
+    }
+}

+ 54 - 0
im-web/src/main/java/cn/poyee/endpoint/ChatRoomController.java

@@ -0,0 +1,54 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.entity.ChatRoomHistory;
+import cn.poyee.entity.query.ChatRoomHistoryQuery;
+import cn.poyee.service.ChatRoomService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+import java.util.Set;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping(value = "/api/chat/room")
+public class ChatRoomController {
+
+    private final ChatRoomService roomService;
+
+    @GetMapping("/hist")
+    public Result<Page<ChatRoomHistory>> historyList(ChatRoomHistoryQuery query) {
+        return Result.success(roomService.pageHistory(query));
+    }
+
+    @GetMapping("people-num")
+    public Mono<Result<Integer>> peopleNum(String roomId) {
+        return roomService.peopleNum(roomId)
+                .map(Result::success);
+    }
+
+    @GetMapping("people-list")
+    public Mono<Result<Set<String>>> peopleList(String roomId) {
+        return roomService.peopleList(roomId)
+                .map(Result::success);
+    }
+
+    @Deprecated
+    @GetMapping("close")
+    public Result<Boolean> close(String roomId) {
+        return Result.success();
+    }
+
+    /**
+     * 非公开,排查问题用
+     */
+    @Deprecated
+    @GetMapping("/all")
+    public Result<Page<ChatRoomHistory>> catRoomHistory(ChatRoomHistoryQuery query) {
+        return Result.success(roomService.all(query));
+    }
+}

+ 27 - 0
im-web/src/main/java/cn/poyee/endpoint/CheckListController.java

@@ -0,0 +1,27 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.entity.CheckList;
+import cn.poyee.entity.query.CheckListQuery;
+import cn.poyee.service.CheckListService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@Slf4j
+@RestController
+@RequestMapping("/api/check-click")
+@AllArgsConstructor
+public class CheckListController {
+
+    private final CheckListService checkListService;
+
+    @GetMapping
+    public Result<List<CheckList>> listCheckList(CheckListQuery clq) {
+        return Result.success(checkListService.listCheckList(clq));
+    }
+}

+ 181 - 0
im-web/src/main/java/cn/poyee/endpoint/LiveChatController.java

@@ -0,0 +1,181 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.common.util.JsonUtil;
+import cn.poyee.service.livechat.ForbiddenGlobal;
+import cn.poyee.service.livechat.ForbiddenRoom;
+import cn.poyee.service.livechat.LiveChatDispatcher;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Mono;
+
+import java.util.Base64;
+
+@Slf4j
+@RestController
+@RequestMapping("/api/livechat")
+@AllArgsConstructor
+public class LiveChatController {
+
+    private final LiveChatDispatcher liveMessage;
+    private final ForbiddenRoom forbiddenRoom;
+    private final ForbiddenGlobal forbiddenGlobal;
+
+    @PostMapping(value = "/{roomId}")
+    public Mono<ResponseEntity<?>> dispatch(@PathVariable("roomId") String roomId,
+                                            @RequestHeader(value = "X-USER-BASE64", required = false) String xBaseUser,
+                                            @RequestBody MsgReq body) {
+
+        return liveMessage.dispatch(body.content, String.valueOf(body.sender.getId()), roomId)
+                .map(status -> ResponseEntity.ok().body(new MsgResp(status.getSuggestion())));
+    }
+
+    @PutMapping(value = "/{roomId}/forbiddenrepo")
+    public Mono<ResponseEntity<?>> forbid(@PathVariable("roomId") String roomId,
+                                          @RequestHeader("X-USER-BASE64") String xBaseUser,
+                                          @RequestBody ProhibitionReq pr) {
+
+        UserInfo userInfo = JsonUtil.toBean(new String(Base64.getDecoder().decode(xBaseUser)), UserInfo.class);
+        return this.forbiddenRoom.block(userInfo, roomId, String.valueOf(pr.getSender().getId()), pr)
+                .map(success -> {
+                    if (success)
+                        return ResponseEntity.noContent().build();
+                    return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+                });
+    }
+
+    @PostMapping(value = "/dispatch")
+    public Mono<ResponseEntity<?>> dispatch(@RequestHeader(value = "X-USER-BASE64", required = false) String xBaseUser,
+                                            @RequestBody ObjectNode body) {
+
+        String content = body.get("content").asText();
+        String roomId = body.get("roomId").asText();
+        String merchantId = body.get("merchantId").asText();
+        String sender = body.get("sender").get("id").asText();
+        return liveMessage.dispatch(content, sender, roomId, merchantId)
+                .map(status -> ResponseEntity.ok().body(new MsgResp(status.getSuggestion())));
+    }
+
+    /**
+     * 新逻辑
+     * 用户禁言, 支持直播间、24H禁言、永久禁言
+     *
+     * @return 执行结果
+     */
+    @PutMapping(value = "/forbidden")
+    public Mono<ResponseEntity<?>> forbidden(@RequestHeader("X-USER-BASE64") String xBaseUser,
+                                             @RequestBody ProhibitionReq pr) {
+
+        UserInfo owner = JsonUtil.toBean(new String(Base64.getDecoder().decode(xBaseUser)), UserInfo.class);
+        return this.forbiddenRoom.block(owner, String.valueOf(pr.getSender().getId()), pr)
+                .map(success -> {
+                    if (success)
+                        return ResponseEntity.ok(Result.success("禁言成功"));
+                    return ResponseEntity.status(HttpStatus.FORBIDDEN)
+                            .body(Result.failed("禁言异常"));
+                });
+    }
+
+    @DeleteMapping(value = "/{roomId}/forbiddenrepo")
+    public Mono<ResponseEntity<?>> release(@PathVariable("roomId") String roomId,
+                                           @RequestHeader("X-USER-BASE64") String xBaseUser,
+                                           @RequestBody Sender sender) {
+
+        UserInfo userInfo = JsonUtil.toBean(new String(Base64.getDecoder().decode(xBaseUser)), UserInfo.class);
+        return this.forbiddenRoom.release(userInfo, roomId, String.valueOf(sender.getId()))
+                .map(success -> {
+                    if (success)
+                        return ResponseEntity.noContent().build();
+                    return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+                });
+    }
+
+    @GetMapping(value = "/{roomId}/blocklist")
+    public Mono<ResponseEntity<?>> blockList(@PathVariable("roomId") String roomId) {
+        return this.forbiddenRoom.blockList(roomId)
+                .collectList()
+                .map(list -> ResponseEntity.ok().body(list));
+    }
+
+    @PutMapping(value = "/blocklist")
+    public Mono<ResponseEntity<?>> forbid(@RequestHeader("X-USER-BASE64") String xBaseUser,
+                                          @RequestParam(value = "hour", required = false, defaultValue = "24") int hour,
+                                          @RequestBody Sender sender) {
+
+        UserInfo userInfo = JsonUtil.toBean(new String(Base64.getDecoder().decode(xBaseUser)), UserInfo.class);
+        return forbiddenGlobal.block(userInfo, sender, hour)
+                .map(success -> {
+                    if (success)
+                        return ResponseEntity.noContent().build();
+                    return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+                });
+    }
+
+    @DeleteMapping(value = "/blocklist")
+    public Mono<ResponseEntity<?>> cancel(@RequestHeader("X-USER-BASE64") String xBaseUser,
+                                          @RequestBody Sender sender) {
+
+        UserInfo userInfo = JsonUtil.toBean(new String(Base64.getDecoder().decode(xBaseUser)), UserInfo.class);
+        return forbiddenGlobal.release(userInfo, sender)
+                .map(success -> {
+                    if (success)
+                        return ResponseEntity.noContent().build();
+                    return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+                });
+    }
+
+    @GetMapping(value = "/blocklist")
+    public Mono<ResponseEntity<?>> blockList() {
+        return this.forbiddenGlobal.blockList()
+                .collectList()
+                .map(list -> ResponseEntity.ok().body(list));
+    }
+
+    @Getter
+    static class MsgReq {
+        private Sender sender;
+        private String content;
+    }
+
+    @Getter
+    @Setter
+    public static class ProhibitionReq extends MsgReq {
+        private Integer type = 1;
+        private String roomId;
+        private Integer deadlineHour;
+
+        private String roomCode;
+        private String speakingTime;
+    }
+
+    @Getter
+    @Setter
+    public static class Sender {
+        private int id;
+        private String nickname;
+        private String avatar;
+    }
+
+    @Getter
+    @AllArgsConstructor
+    static class MsgResp {
+        private String suggestion;
+    }
+
+    @Getter
+    @Setter
+    public static class UserInfo {
+        private String sub;
+        private String nickName;
+        private String roleCode;
+        private String avatar;
+        private int id;
+        private int merchantId;
+    }
+}

+ 70 - 0
im-web/src/main/java/cn/poyee/endpoint/MessageController.java

@@ -0,0 +1,70 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.entity.Message;
+import cn.poyee.entity.MessageRead;
+import cn.poyee.entity.query.MessageQuery;
+import cn.poyee.service.MessageService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.AllArgsConstructor;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/api/msg")
+@AllArgsConstructor
+public class MessageController {
+
+    private final MessageService messageService;
+
+    @GetMapping("/paging")
+    public Result<List<Message>> paging(MessageQuery query) {
+        IPage<Message> result = messageService.paging(query);
+        return Result.success(result.getRecords(), result.getTotal());
+    }
+
+    @GetMapping
+    public Result<List<Message>> listMessage(MessageQuery query) {
+        return Result.success(messageService.listMessage(query));
+    }
+
+    @PostMapping
+    public Result<Boolean> addMessage(@RequestBody Message message) {
+        return Result.judge(messageService.addMessage(message));
+    }
+
+    @GetMapping("/{id}")
+    public Result<Message> byId(@PathVariable Integer id) {
+        return Result.success(messageService.getById(id));
+    }
+
+    @PutMapping
+    public Result<Boolean> updateMessage(@RequestBody Message message) {
+        Assert.isTrue(Objects.nonNull(message.getId()), "缺少必填字段");
+        return Result.success(messageService.updateMessage(message));
+    }
+
+    @DeleteMapping("/{id}")
+    public Result<Boolean> deleteMessage(@PathVariable Integer id) {
+        return Result.success(messageService.deleteMessage(id));
+    }
+
+    @PostMapping("/read")
+    public Result<Object> read(@RequestParam Map<String, String> param) {
+        return Result.success(messageService.read(param));
+    }
+
+    @GetMapping("/unread-count")
+    public Result<Integer> unreadCount(String messageType) {
+        return Result.success(messageService.unreadCount(messageType));
+    }
+
+    @GetMapping("/read/{messageId}")
+    public Result<List<MessageRead>> readRecord(@PathVariable Long messageId) {
+        return Result.success(messageService.readRecord(messageId));
+    }
+}

+ 32 - 0
im-web/src/main/java/cn/poyee/endpoint/ProhibitionController.java

@@ -0,0 +1,32 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.entity.ChatProhibition;
+import cn.poyee.entity.query.ChatProhibitionQuery;
+import cn.poyee.service.ChatProhibitionService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/api/chat/prohibition")
+@RequiredArgsConstructor
+public class ProhibitionController {
+
+    private final ChatProhibitionService imChatProhibitionService;
+
+    @GetMapping("/paging")
+    public Result<Object> paging(ChatProhibitionQuery query) {
+
+        LambdaQueryWrapper<ChatProhibition> queryWrapper = new LambdaQueryWrapper<ChatProhibition>()
+                .eq(Objects.nonNull(query.getRoomId()), ChatProhibition::getRoomId, query.getRoomId())
+                .orderByDesc(ChatProhibition::getCreateTime);
+        Page<ChatProhibition> result = imChatProhibitionService.page(new Page<>(query.getPageNum(), query.getPageSize()), queryWrapper);
+        return Result.success(result.getRecords(), result.getTotal());
+    }
+}

+ 46 - 0
im-web/src/main/java/cn/poyee/endpoint/RabbitController.java

@@ -0,0 +1,46 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.service.RabbitMQManagerService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Slf4j
+@RestController
+@RequestMapping("/rb")
+public class RabbitController {
+
+    @Resource
+    private RabbitMQManagerService rabbitMQManagerService;
+
+    @GetMapping("/testGetTopic")
+    public Mono<Map<String, List<Integer>>> testGetTopic() {
+        return rabbitMQManagerService.numberOfRoomPeople();
+    }
+
+    @GetMapping("/num-platform-user")
+    public Mono<Set<Integer>> numOfPlatformUser() {
+        return rabbitMQManagerService.numOfPlatformUser();
+    }
+
+    @GetMapping("/queues")
+    public Mono<List<String>> queueList(Integer page, Integer pageSize) {
+        return rabbitMQManagerService.queueList(page,pageSize);
+    }
+
+    @DeleteMapping("/queues")
+    public Mono<List<String>> deleteQueue(@RequestBody Set<String> queueName) {
+        return rabbitMQManagerService.deleteQueue(queueName);
+    }
+
+    @DeleteMapping("/all_queues")
+    public Mono<List<String>> deleteAllQueue() {
+        return rabbitMQManagerService.deleteAllQueue();
+    }
+
+}

+ 47 - 0
im-web/src/main/java/cn/poyee/endpoint/TaskController.java

@@ -0,0 +1,47 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.entity.Task;
+import cn.poyee.entity.query.TaskQuery;
+import cn.poyee.service.TaskService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/task")
+public class TaskController {
+
+    private final TaskService taskService;
+
+    @GetMapping("/paging")
+    public Result<Page<Task>> paging(TaskQuery taskQuery) {
+        return Result.success(taskService.paging(taskQuery));
+    }
+
+    @PostMapping
+    public Result<Boolean> createTask(@RequestBody Task task) {
+        return Result.judge(taskService.createTask(task));
+    }
+
+    @DeleteMapping("/{id}")
+    public Result<Boolean> deleteTask(@PathVariable Long id) {
+        return Result.success(taskService.deleteTask(id));
+    }
+
+    @PutMapping("/{id}")
+    public Result<Boolean> updateTask(@PathVariable Long id, @RequestBody Task task) {
+        return Result.success(taskService.updateTask(id, task));
+    }
+
+    @PutMapping("/{id}/start")
+    public Result<Boolean> startTask(@PathVariable Long id) {
+        return Result.success(taskService.startTask(id));
+    }
+
+    @PutMapping("/{id}/stop")
+    public Result<Boolean> stopTask(@PathVariable Long id) {
+        return Result.success(taskService.stopTask(id));
+    }
+}

+ 109 - 0
im-web/src/main/java/cn/poyee/endpoint/ToolController.java

@@ -0,0 +1,109 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.service.ChatRelationsService;
+import cn.poyee.service.RabbitMQManagerService;
+import cn.poyee.service.ToolService;
+import cn.poyee.service.UnifiedMessageService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping("/tool")
+public class ToolController {
+
+    @Resource
+    private ToolService toolService;
+    private final WebClient webClient;
+    @Resource
+    private UnifiedMessageService unifiedMessageService;
+    @Resource
+    private ChatRelationsService chatRelationsService;
+    @Resource
+    private RabbitMQManagerService rabbitMQManagerService;
+
+    public ToolController(WebClient.Builder webClientBuilder) {
+        this.webClient = webClientBuilder.baseUrl("http://poyee-app").build();
+    }
+
+    @Deprecated
+    @GetMapping("/testGetTopic")
+    public Mono<Map<String, List<Integer>>> testGetTopic() {
+        return rabbitMQManagerService.numberOfRoomPeople();
+    }
+
+
+    @GetMapping("/sendAMQP")
+    public void getMerAppUserId(String routingKey, String msg) {
+        toolService.sendAMQP(routingKey, msg);
+    }
+
+    @PostMapping("/fill")
+    public Mono<String> fillRoomPeople(@RequestBody Map<String, Object> payload) {
+        return toolService.fillRoomPeople(payload);
+    }
+
+    @GetMapping("/rds/set/get")
+    public Mono<Result<List<String>>> setGet(String key) {
+        return toolService.setGet(key)
+                .collectList()
+                .map(Result::success);
+    }
+
+    @GetMapping("/rds/get")
+    public Mono<Result<Object>> rdsGet(String key) {
+        return toolService.rdsGet(key)
+                .map(Result::success);
+    }
+
+    @DeleteMapping("/rds/del")
+    public Mono<Result<Object>> rdsDel(String key, String scope) {
+        return toolService.rdsDel(key, scope)
+                .map(Result::success);
+    }
+
+    @DeleteMapping("/rds/all")
+    public Mono<Long> getAllCache() {
+        return toolService.rdsDelAll();
+    }
+
+    @GetMapping("/keys")
+    public Mono<Object> keys(String key) {
+        return toolService.keys(key)
+                .collectList()
+                .map(Result::success);
+    }
+
+    @Deprecated
+    @GetMapping("/rb-queues")
+    public Mono<Object> rbQueues(String key) {
+        return toolService.rbQueues(key)
+                .collectList()
+                .map(Result::success);
+    }
+
+    @Deprecated
+    @DeleteMapping("/cleanUser")
+    public String cleanUser() {
+        return toolService.cleanUser();
+    }
+
+    @Deprecated
+    @DeleteMapping("/cleanRecordLast")
+    public String cleanRecordLast() {
+        return toolService.cleanRecordLast();
+    }
+
+    @Deprecated
+    @PostMapping("/room_num_enable")
+    public String roomNumEnable(String val) {
+        return toolService.roomNumEnable(val);
+    }
+}

+ 38 - 0
im-web/src/main/java/cn/poyee/endpoint/UnifiedMessageHandler.java

@@ -0,0 +1,38 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.ResultCode;
+import cn.poyee.common.util.JsonUtil;
+import cn.poyee.common.web.exception.BusinessException;
+import cn.poyee.service.UnifiedMessageService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+import java.util.Optional;
+
+@Slf4j
+@RestController
+@AllArgsConstructor
+public class UnifiedMessageHandler {
+
+    private final UnifiedMessageService ums;
+
+    @MessageMapping("/chat/handler")
+    public void chatHandler(Message<?> message) {
+        ums.chatHandler(message);
+    }
+
+    @PostMapping("/chat/handler")
+    public void chatHandler(@RequestBody Map<String, Object> payload) {
+        String payloadStr = Optional.ofNullable(JsonUtil.toStr(payload))
+                .orElseThrow(() -> new BusinessException(ResultCode.PARAM_ERROR));
+        ums.chatHandler(new GenericMessage<>(payloadStr.getBytes()));
+    }
+
+}

+ 69 - 0
im-web/src/main/java/cn/poyee/endpoint/UserController.java

@@ -0,0 +1,69 @@
+package cn.poyee.endpoint;
+
+import cn.poyee.common.result.Result;
+import cn.poyee.common.result.ResultCode;
+import cn.poyee.common.util.JsonUtil;
+import cn.poyee.common.web.exception.BusinessException;
+import cn.poyee.common.web.util.OkHttpUtils;
+import cn.poyee.config.properties.JwtProperties;
+import cn.poyee.entity.UserSetting;
+import cn.poyee.service.UserService;
+import cn.poyee.service.UserSettingService;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping("/api/user")
+@AllArgsConstructor
+public class UserController {
+
+    private final JwtProperties jwtProp;
+    private final UserService userService;
+    private final UserSettingService userSettingService;
+    private final OkHttpUtils httpUtils;
+    private final String temp = "{\"data\":{\"merchantId\":%s}}";
+
+    @GetMapping
+    public Result<JsonNode> userInfo(String type, String userId) {
+
+        if ("base".equals(type)) {
+            if (null == userId) {
+                throw new BusinessException(ResultCode.PARAM_IS_NULL);
+            }
+            return Result.success(userService.coreSvc(userId));
+        } else {
+            Map<String, Object> merchantMap = userService.userInfo();
+            String merchantId = merchantMap.get("merchantId").toString();
+            String uri = jwtProp.pyAppUrl.concat("api/v2.2/merchant/getMerAppUserId");
+            String responseText = httpUtils.doPostJson(uri, String.format(temp, merchantId));
+            JsonNode jsonNode = JsonUtil.toBean(responseText, JsonNode.class);
+            return Result.success(jsonNode.get("data"));
+        }
+    }
+
+    /**
+     * 获取用户身份
+     */
+    @GetMapping("/identity")
+    public Result<String> userIdentity() {
+        return Result.success(userService.userIdentity());
+    }
+
+    @PostMapping("/setting")
+    public Result<Boolean> addSetting(@RequestBody UserSetting param) {
+        return Result.success(userSettingService.update(param));
+    }
+
+    @GetMapping("/setting")
+    public Result<List<Long>> getSetting() {
+        return Result.success(userSettingService.get());
+    }
+
+
+}

+ 42 - 0
im-web/src/main/java/cn/poyee/entity/ChatP2PHistory.java

@@ -0,0 +1,42 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@TableName(value = "im_chat_p2p_history")
+public class ChatP2PHistory {
+
+    private String messageId;
+    private String contentType;
+    private String sender;
+    private String receiver;
+
+    private String senderN;
+    private String receiverN;
+    private String combinationKeyN;
+
+    private String extra;
+    private String content;
+    private Integer isRead;
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    private String combinationKey;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
+    private Date createTime;
+    @TableField(exist = false)
+    private String nickname;
+    @TableField(exist = false)
+    private String avatar;
+}

+ 27 - 0
im-web/src/main/java/cn/poyee/entity/ChatProhibition.java

@@ -0,0 +1,27 @@
+package cn.poyee.entity;
+
+import cn.poyee.dto.model.ContentModel;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder
+@TableName(value = "im_chat_prohibition")
+public class ChatProhibition extends ContentModel {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String roomId;
+    private String userId;
+    private String nickName;
+    private String roomCode;
+    private String content;
+    private LocalDateTime speakingTime;
+    private LocalDateTime createTime;
+}

+ 46 - 0
im-web/src/main/java/cn/poyee/entity/ChatRelations.java

@@ -0,0 +1,46 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@TableName(value = "im_chat_relations")
+public class ChatRelations {
+
+    private String sender;
+    private String receiver;
+
+    private String senderN;
+    private String receiverN;
+    private String combinationKeyN;
+
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    private String combinationKey;
+    @TableField(exist = false)
+    private String nickname;
+    @TableField(exist = false)
+    private String avatar;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
+    private Date createTime;
+    @TableField(exist = false)
+    private String recordLast;
+    @TableField(exist = false)
+    private Boolean hasNoRead;
+    @TableField(exist = false)
+    private Integer noReadNum;
+    // 用户状态是否正常
+    @TableField(exist = false)
+    private Boolean delFlg = false;
+//    @TableField(exist = false)
+//    private String roleCode;
+
+}

+ 29 - 0
im-web/src/main/java/cn/poyee/entity/ChatRoomHistory.java

@@ -0,0 +1,29 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@TableName(value = "im_chat_room_history")
+public class ChatRoomHistory implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String contentType;
+    private String sender;
+    private String roomId;
+    private String content;
+    private String extra;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
+    private Date createTime;
+}

+ 48 - 0
im-web/src/main/java/cn/poyee/entity/CheckList.java

@@ -0,0 +1,48 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@TableName(value = "im_check_list")
+public class CheckList {
+
+    private String code;
+    private String year;
+    private String sport;
+    private String manufacturer;
+    private String sets;
+    private String displayName;
+    private String setsVersion;
+    private String rangePrice;
+    private String guideBoxPrice;
+    // 规格限制(盒)
+    private String specificationLimit;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
+    private Date createTime;
+
+    @Override
+    public String toString() {
+
+        return year +
+                " " +
+                sport +
+                " " +
+                manufacturer +
+                " " +
+                sets +
+                " " +
+                setsVersion +
+                " " +
+                displayName;
+    }
+}

+ 22 - 0
im-web/src/main/java/cn/poyee/entity/LiveMessage.java

@@ -0,0 +1,22 @@
+package cn.poyee.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+
+import java.time.Instant;
+
+@Getter
+@Setter
+public class LiveMessage {
+
+    public LiveMessage() {
+    }
+
+    @Id
+    private int id;
+    private String roomId;
+    private String sender;
+    private String content;
+    private Instant createAt;
+}

+ 52 - 0
im-web/src/main/java/cn/poyee/entity/Message.java

@@ -0,0 +1,52 @@
+package cn.poyee.entity;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@TableName(value = "im_message")
+public class Message implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String title;
+    private String content;
+    // 消息类型: 1:公告 2:站内信
+    private String type;
+    private String createBy;
+    private Date updateTime;
+    private Date createTime;
+    // 0:待发布 1:已发布
+    private String status;
+    private boolean isTop;
+    // 是否强制查看
+    private boolean forceView;
+    // 浏览次数
+    private Long viewCount;
+    // 是否已读,只显示
+    @TableField(exist = false)
+    private boolean isRead;
+    // 右上角已读
+    @TableField(exist = false)
+    private boolean rightRead;
+    @TableField(exist = false)
+    private Long[] scope;
+    @TableField(exist = false)
+    private CheckList checkList;
+    private String viewScope;
+//    // 用户条款 如: "我已阅读并知晓..."
+//    private Boolean userAgreement = false;
+}

+ 21 - 0
im-web/src/main/java/cn/poyee/entity/MessageRead.java

@@ -0,0 +1,21 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@TableName(value = "im_message_read")
+public class MessageRead {
+    private Long messageId;
+    private String entityId;
+    private String entityContent;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
+    private Date createTime;
+}

+ 18 - 0
im-web/src/main/java/cn/poyee/entity/MessageScope.java

@@ -0,0 +1,18 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@TableName(value = "im_message_scope")
+public class MessageScope {
+
+    private Long messageId;
+    private String entityId;
+    // 1:商家
+    private String type;
+}

+ 21 - 0
im-web/src/main/java/cn/poyee/entity/Task.java

@@ -0,0 +1,21 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.springframework.data.annotation.Id;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class Task {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String taskName;
+    private String cronExpression;
+    private Integer isRunning;
+    private String type;
+
+}

+ 22 - 0
im-web/src/main/java/cn/poyee/entity/TaskInfo.java

@@ -0,0 +1,22 @@
+package cn.poyee.entity;
+
+import cn.poyee.dto.model.ContentModel;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class TaskInfo {
+    private String messageId;
+    private String protocol;
+    private String receiver;
+    private String messageType;
+    private ContentModel contentModel;
+    private String time;
+}

+ 15 - 0
im-web/src/main/java/cn/poyee/entity/UserSetting.java

@@ -0,0 +1,15 @@
+package cn.poyee.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+@TableName(value = "im_user_setting")
+public class UserSetting {
+    private Long userId;
+    private Long messageId;
+}

+ 12 - 0
im-web/src/main/java/cn/poyee/entity/dto/ChatMessageParam.java

@@ -0,0 +1,12 @@
+package cn.poyee.entity.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ChatMessageParam {
+    private String messageId;
+    private String receiver;
+    private ChatPayload payload;
+}

Some files were not shown because too many files changed in this diff