diff --git a/README.md b/README.md
index 6bca532..aa4d81a 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,11 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
## 项目介绍
-抹茶聊天是一个IM项目,通过netty实现和前端的websocket连接。内含微信扫描登录,成员列表,上下线动画,消息列表,消息互动,还有很多实用的小轮子列如aop日志,分布式锁注解,频控注解,ip解析归属地等,持续更新中。。。
+抹茶聊天是一个IM项目,通过netty实现和前端的websocket连接。内含微信扫描登录,成员列表,消息列表,消息互动,丰富的消息类型,还有很多实用的小轮子列如aop日志,分布式锁注解,频控注解,ip解析归属地等,持续更新中。。。
+
+
+
+
### 项目演示
@@ -31,7 +35,9 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
- 前端项目地址:[https://github.com/Evansy/MallChatWeb](https://github.com/Evansy/MallChatWeb)
- 项目演示地址:[https://mallchat.cn](https://mallchat.cn) (记住抹茶.cn,下次工作摸鱼可直接打开)
-
+
+
+
### 技术选型
@@ -52,6 +58,7 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
| Hutool | Java工具类库 | https://github.com/looly/hutool |
| Swagger-UI | API文档生成工具 | https://github.com/swagger-api/swagger-ui |
| Hibernate-validator | 接口校验框架 | [hibernate.org/validator/](hibernate.org/validator/) |
+| minio | 自建对象存储 | https://github.com/minio/minio |
#### 前端技术
@@ -67,7 +74,9 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
更多有趣功能在持续更新中。。。
-
+
+
+
## star 趋势图
@@ -78,6 +87,37 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
+
优秀贡献者:
+
+
+
+
## 公众号
微信搜索 **阿斌Java之路** 关注我的原创公众号,后台回复「**抹茶**」即可加入抹茶交流群,一些做过公司万人群聊,高并发的小伙伴都在里面讨论方案。公众号也会经常更新项目相关的文档,等你来撩~~
diff --git a/docs/image/复杂图片.jpg b/docs/image/复杂图片.jpg
new file mode 100644
index 0000000..feda4d6
Binary files /dev/null and b/docs/image/复杂图片.jpg differ
diff --git a/docs/image/文档1.jpg b/docs/image/文档1.jpg
new file mode 100644
index 0000000..c6638e4
Binary files /dev/null and b/docs/image/文档1.jpg differ
diff --git a/docs/image/文档2.jpg b/docs/image/文档2.jpg
new file mode 100644
index 0000000..4e78122
Binary files /dev/null and b/docs/image/文档2.jpg differ
diff --git a/docs/image/群聊截图.jpg b/docs/image/群聊截图.jpg
new file mode 100644
index 0000000..1ebff4a
Binary files /dev/null and b/docs/image/群聊截图.jpg differ
diff --git a/docs/image/设计模式.jpg b/docs/image/设计模式.jpg
new file mode 100644
index 0000000..4e0a949
Binary files /dev/null and b/docs/image/设计模式.jpg differ
diff --git a/docs/image/项目大纲.jpg b/docs/image/项目大纲.jpg
new file mode 100644
index 0000000..2de07a2
Binary files /dev/null and b/docs/image/项目大纲.jpg differ
diff --git a/docs/mallchat.sql b/docs/mallchat.sql
index 3a21d4b..9a94090 100644
--- a/docs/mallchat.sql
+++ b/docs/mallchat.sql
@@ -113,7 +113,8 @@ CREATE TABLE `user` (
INDEX `idx_create_time`(`create_time`) USING BTREE,
INDEX `idx_update_time`(`update_time`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-
+INSERT INTO `mallchat`.`user` (`id`, `name`, `avatar`, `sex`, `open_id`, `last_opt_time`, `ip_info`, `item_id`, `status`, `create_time`, `update_time`) VALUES (10001, 'ChatGPT', 'https://img1.baidu.com/it/u=3613958228,3522035000&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500', 0, '??', '2023-06-29 17:03:03.357', NULL, NULL, 0, '2023-06-29 17:03:03.357', '2023-07-01 14:56:10.271');
+INSERT INTO `mallchat`.`user` (`id`, `name`, `avatar`, `sex`, `open_id`, `last_opt_time`, `ip_info`, `item_id`, `status`, `create_time`, `update_time`) VALUES (10002, 'ChatGLM2', 'http://mms1.baidu.com/it/u=1979830414,2984779047&fm=253&app=138&f=JPEG&fmt=auto&q=75?w=500&h=500', NULL, '450', '2023-07-01 11:58:24.605', NULL, NULL, 0, '2023-07-01 11:58:24.605', '2023-07-01 12:02:56.900');
-- ----------------------------
-- Table structure for user_backpack
-- ----------------------------
diff --git a/docs/version/2023-07-01.sql b/docs/version/2023-07-01.sql
new file mode 100644
index 0000000..847dbeb
--- /dev/null
+++ b/docs/version/2023-07-01.sql
@@ -0,0 +1,2 @@
+INSERT INTO `mallchat`.`user` (`id`, `name`, `avatar`, `sex`, `open_id`, `last_opt_time`, `ip_info`, `item_id`, `status`, `create_time`, `update_time`) VALUES (10001, 'ChatGPT', 'https://img1.baidu.com/it/u=3613958228,3522035000&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500', 0, '??', '2023-06-29 17:03:03.357', NULL, NULL, 0, '2023-06-29 17:03:03.357', '2023-07-01 14:56:10.271');
+INSERT INTO `mallchat`.`user` (`id`, `name`, `avatar`, `sex`, `open_id`, `last_opt_time`, `ip_info`, `item_id`, `status`, `create_time`, `update_time`) VALUES (10002, 'ChatGLM2', 'http://mms1.baidu.com/it/u=1979830414,2984779047&fm=253&app=138&f=JPEG&fmt=auto&q=75?w=500&h=500', NULL, '450', '2023-07-01 11:58:24.605', NULL, NULL, 0, '2023-07-01 11:58:24.605', '2023-07-01 12:02:56.900');
\ No newline at end of file
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/FileMsgDTO.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/FileMsgDTO.java
index d030c6d..0b487d0 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/FileMsgDTO.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/FileMsgDTO.java
@@ -6,6 +6,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
@@ -20,12 +22,15 @@ import java.io.Serializable;
public class FileMsgDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("大小(字节)")
+ @NotNull
private Long size;
@ApiModelProperty("下载地址")
+ @NotBlank
private String url;
@ApiModelProperty("文件名(带后缀)")
+ @NotBlank
private String fileName;
}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/SoundMsgDTO.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/SoundMsgDTO.java
index 2b2fac9..e965eea 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/SoundMsgDTO.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/SoundMsgDTO.java
@@ -6,6 +6,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
@@ -20,11 +22,14 @@ import java.io.Serializable;
public class SoundMsgDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("大小(字节)")
+ @NotNull
private Long size;
@ApiModelProperty("时长(秒)")
+ @NotNull
private Integer second;
@ApiModelProperty("下载地址")
+ @NotBlank
private String url;
}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/VideoMsgDTO.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/VideoMsgDTO.java
index 0fcf2ba..49136ac 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/VideoMsgDTO.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/VideoMsgDTO.java
@@ -9,7 +9,6 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
-import java.math.BigDecimal;
/**
* Description: 视频消息入参
@@ -23,20 +22,23 @@ import java.math.BigDecimal;
public class VideoMsgDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("大小(字节)")
+ @NotNull
private Long size;
@ApiModelProperty("下载地址")
+ @NotBlank
private String url;
@ApiModelProperty("缩略图宽度(像素)")
@NotNull
- private Integer thumbWidth = BigDecimal.ROUND_HALF_DOWN;
+ private Integer thumbWidth;
@ApiModelProperty("缩略图高度(像素)")
@NotNull
private Integer thumbHeight;
@ApiModelProperty("缩略图大小(字节)")
+ @NotNull
private Long thumbSize;
@ApiModelProperty("缩略图下载地址")
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java
index faa6bd2..0cc9bd0 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java
@@ -36,6 +36,16 @@ public class RedisKey {
*/
public static final String USER_SUMMARY_STRING = "userSummary:uid_%d";
+ /**
+ * 用户GPT聊天次数
+ */
+ public static final String USER_CHAT_NUM = "useChatGPTNum:uid_%d";
+
+ /**
+ * 用户上次使用GLM使用时间
+ */
+ public static final String USER_GLM2_TIME_LAST = "userGLM2UseTime:uid_%d";
+
public static String getKey(String key, Object... objects) {
return BASE_KEY + String.format(key, objects);
}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java
index fad1667..3a986e1 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java
@@ -22,7 +22,7 @@ public class GlobalExceptionHandler {
e.getBindingResult().getFieldErrors().forEach(x -> errorMsg.append(x.getField()).append(x.getDefaultMessage()).append(","));
String message = errorMsg.toString();
log.info("validation parameters error!The reason is:{}", message);
- return ApiResult.fail(-1, message.substring(0, message.length() - 1));
+ return ApiResult.fail(CommonErrorEnum.PARAM_VALID.getErrorCode(), message.substring(0, message.length() - 1));
}
/**
@@ -34,7 +34,7 @@ public class GlobalExceptionHandler {
e.getBindingResult().getFieldErrors().forEach(x -> errorMsg.append(x.getField()).append(x.getDefaultMessage()).append(","));
String message = errorMsg.toString();
log.info("validation parameters error!The reason is:{}", message);
- return ApiResult.fail(-1, message.substring(0, message.length() - 1));
+ return ApiResult.fail(CommonErrorEnum.PARAM_VALID.getErrorCode(), message.substring(0, message.length() - 1));
}
/**
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java
index 77e5102..0301132 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java
@@ -3,16 +3,83 @@ package com.abin.mallchat.common.common.utils;
import cn.hutool.core.util.ObjectUtil;
import com.abin.mallchat.common.common.exception.BusinessErrorEnum;
import com.abin.mallchat.common.common.exception.BusinessException;
+import com.abin.mallchat.common.common.exception.CommonErrorEnum;
import com.abin.mallchat.common.common.exception.ErrorEnum;
+import org.hibernate.validator.HibernateValidator;
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
import java.text.MessageFormat;
-import java.util.Objects;
+import java.util.*;
/**
* 校验工具类
*/
public class AssertUtil {
+ /**
+ * 校验到失败就结束
+ */
+ private static Validator failFastValidator = Validation.byProvider(HibernateValidator.class)
+ .configure()
+ .failFast(true)
+ .buildValidatorFactory().getValidator();
+
+ /**
+ * 全部校验
+ */
+ private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+
+ /**
+ * 注解验证参数(校验到失败就结束)
+ * @param obj
+ */
+ public static void fastFailValidate(T obj) {
+ Set> constraintViolations = failFastValidator.validate(obj);
+ if (constraintViolations.size() > 0) {
+ throwException(CommonErrorEnum.PARAM_VALID,constraintViolations.iterator().next().getMessage());
+ }
+ }
+
+ /**
+ * 注解验证参数(全部校验,抛出异常)
+ * @param obj
+ */
+ public static void allCheckValidateThrow(T obj) {
+ Set> constraintViolations = validator.validate(obj);
+ if (constraintViolations.size() > 0) {
+ StringBuilder errorMsg = new StringBuilder();
+ Iterator> iterator = constraintViolations.iterator();
+ while (iterator.hasNext()) {
+ ConstraintViolation violation = iterator.next();
+ //拼接异常信息
+ errorMsg.append(violation.getPropertyPath().toString()).append(":").append(violation.getMessage()).append(",");
+ }
+ //去掉最后一个逗号
+ throwException(CommonErrorEnum.PARAM_VALID, errorMsg.toString().substring(0, errorMsg.length() - 1));
+ }
+ }
+
+
+ /**
+ * 注解验证参数(全部校验,返回异常信息集合)
+ * @param obj
+ */
+ public static Map allCheckValidate(T obj) {
+ Set> constraintViolations = validator.validate(obj);
+ if (constraintViolations.size() > 0) {
+ Map errorMessages= new HashMap<>();
+ Iterator> iterator = constraintViolations.iterator();
+ while (iterator.hasNext()) {
+ ConstraintViolation violation = iterator.next();
+ errorMessages.put(violation.getPropertyPath().toString(),violation.getMessage());
+ }
+ return errorMessages;
+ }
+ return new HashMap<>();
+ }
+
//如果不是true,则抛异常
public static void isTrue(boolean expression, String msg) {
if (!expression) {
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/DateUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/DateUtils.java
new file mode 100644
index 0000000..ecc2a59
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/DateUtils.java
@@ -0,0 +1,16 @@
+package com.abin.mallchat.common.common.utils;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+ public static Long getEndTimeByToday() {
+ Calendar instance = Calendar.getInstance();
+ Date now = new Date();
+ instance.setTime(now);
+ instance.set(Calendar.HOUR_OF_DAY, 23);
+ instance.set(Calendar.MINUTE, 59);
+ instance.set(Calendar.SECOND, 59);
+ return instance.getTime().getTime() - now.getTime();
+ }
+}
diff --git a/mallchat-common/src/main/resources/application-pro.properties b/mallchat-common/src/main/resources/application-pro.properties
index 3b71009..c6a31b5 100644
--- a/mallchat-common/src/main/resources/application-pro.properties
+++ b/mallchat-common/src/main/resources/application-pro.properties
@@ -25,4 +25,12 @@ oss.type=minio
oss.endpoint=http://localhost:9000
oss.access-key=BEZ213
oss.secret-key=Ii4vCMIXuFfds1EZ8e7RXI2342342kV
-oss.bucketName=default
\ No newline at end of file
+oss.bucketName=default
+##################gpt配置##################
+mallchat.chatgpt.use=false
+mallchat.chatgpt.uid=10001
+mallchat.chatgpt.key=sk-wvWM0xGcxFfsddfsgxixbXK5tHovM
+mallchat.chatgpt.proxyUrl=https://123.cc
+mallchat.chatglm2.use=false
+mallchat.chatglm2.url=http://v32134.cc
+mallchat.chatglm2.uid=10002
\ No newline at end of file
diff --git a/mallchat-common/src/main/resources/application-test.properties b/mallchat-common/src/main/resources/application-test.properties
index 45e7793..a4967ae 100644
--- a/mallchat-common/src/main/resources/application-test.properties
+++ b/mallchat-common/src/main/resources/application-test.properties
@@ -25,4 +25,12 @@ oss.type=minio
oss.endpoint=http://localhost:9000
oss.access-key=BEZ213
oss.secret-key=Ii4vCMIXuFe241dsfEZ8e7RXI2342342kV
-oss.bucketName=default
\ No newline at end of file
+oss.bucketName=default
+##################gpt配置##################
+mallchat.chatgpt.use=false
+mallchat.chatgpt.uid=10001
+mallchat.chatgpt.key=sk-wvWM0xGcxFfsddfsgxixbXK5tHovM
+mallchat.chatgpt.proxyUrl=https://123.cc
+mallchat.chatglm2.use=false
+mallchat.chatglm2.url=http://v32134.cc
+mallchat.chatglm2.uid=10002
\ No newline at end of file
diff --git a/mallchat-common/src/main/resources/application.yml b/mallchat-common/src/main/resources/application.yml
index 53b6b9b..2f2ab12 100644
--- a/mallchat-common/src/main/resources/application.yml
+++ b/mallchat-common/src/main/resources/application.yml
@@ -62,4 +62,15 @@ wx:
- appId: ${mallchat.wx.appId} # 第一个公众号的appid
secret: ${mallchat.wx.secret} # 公众号的appsecret
token: ${mallchat.wx.token} # 接口配置里的Token值
- aesKey: ${mallchat.wx.aesKey} # 接口配置里的EncodingAESKey值
\ No newline at end of file
+ aesKey: ${mallchat.wx.aesKey} # 接口配置里的EncodingAESKey值
+chatai:
+ chatgpt:
+ use: ${mallchat.chatgpt.use}
+ AIUserId: ${mallchat.chatgpt.uid}
+ key: ${mallchat.chatgpt.key}
+ proxyUrl: ${mallchat.chatgpt.proxyUrl}
+ chatglm2:
+ use: ${mallchat.chatglm2.use}
+ url: ${mallchat.chatglm2.url}
+ minute: 3 # 每个用户每3分钟可以请求一次
+ AIUserId: ${mallchat.chatglm2.uid}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java
index a6a6095..2fd2040 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java
@@ -11,9 +11,13 @@ import javax.validation.constraints.NotNull;
/**
+ * 聊天信息点播
* Description: 消息发送请求体
* Author: abin
* Date: 2023-03-23
+ *
+ * @author zhaoyuhang
+ * @date 2023/06/30
*/
@Data
@Builder
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/msg/TextMsgReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/msg/TextMsgReq.java
index a58d6b8..4ccfee4 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/msg/TextMsgReq.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/msg/TextMsgReq.java
@@ -6,6 +6,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
import java.util.List;
/**
@@ -19,6 +21,8 @@ import java.util.List;
@NoArgsConstructor
public class TextMsgReq {
+ @NotBlank(message = "内容不能为空")
+ @Size(max = 1024, message = "消息内容过长,服务器扛不住啊,兄dei")
@ApiModelProperty("消息内容")
private String content;
@@ -26,5 +30,6 @@ public class TextMsgReq {
private Long replyMsgId;
@ApiModelProperty("艾特的uid")
+ @Size(max = 10, message = "一次别艾特这么多人")
private List atUidList;
}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/FileMsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/FileMsgHandler.java
index 529c3bf..0ce1c21 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/FileMsgHandler.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/FileMsgHandler.java
@@ -6,6 +6,7 @@ import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.domain.entity.msg.FileMsgDTO;
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
+import com.abin.mallchat.common.common.utils.AssertUtil;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -29,6 +30,8 @@ public class FileMsgHandler extends AbstractMsgHandler {
@Override
public void checkMsg(ChatMessageReq request, Long uid) {
+ FileMsgDTO body = BeanUtil.toBean(request.getBody(), FileMsgDTO.class);
+ AssertUtil.allCheckValidateThrow(body);
}
@Override
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/ImgMsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/ImgMsgHandler.java
index 91ae4f9..6e2cf98 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/ImgMsgHandler.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/ImgMsgHandler.java
@@ -6,6 +6,7 @@ import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.domain.entity.msg.ImgMsgDTO;
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
+import com.abin.mallchat.common.common.utils.AssertUtil;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -29,6 +30,8 @@ public class ImgMsgHandler extends AbstractMsgHandler {
@Override
public void checkMsg(ChatMessageReq request, Long uid) {
+ ImgMsgDTO body = BeanUtil.toBean(request.getBody(), ImgMsgDTO.class);
+ AssertUtil.allCheckValidateThrow(body);
}
@Override
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/SoundMsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/SoundMsgHandler.java
index 84aae0d..470df61 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/SoundMsgHandler.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/SoundMsgHandler.java
@@ -6,6 +6,7 @@ import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
import com.abin.mallchat.common.chat.domain.entity.msg.SoundMsgDTO;
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
+import com.abin.mallchat.common.common.utils.AssertUtil;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -29,6 +30,8 @@ public class SoundMsgHandler extends AbstractMsgHandler {
@Override
public void checkMsg(ChatMessageReq request, Long uid) {
+ SoundMsgDTO body = BeanUtil.toBean(request.getBody(), SoundMsgDTO.class);
+ AssertUtil.allCheckValidateThrow(body);
}
@Override
@@ -44,7 +47,7 @@ public class SoundMsgHandler extends AbstractMsgHandler {
@Override
public Object showMsg(Message msg) {
- return msg.getExtra().getFileMsg();
+ return msg.getExtra().getSoundMsgDTO();
}
@Override
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java
index 461ed88..d8e347a 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java
@@ -57,8 +57,7 @@ public class TextMsgHandler extends AbstractMsgHandler {
@Override
public void checkMsg(ChatMessageReq request, Long uid) {
TextMsgReq body = BeanUtil.toBean(request.getBody(), TextMsgReq.class);
- AssertUtil.isNotEmpty(body.getContent(), "内容不能为空");
- AssertUtil.isTrue(body.getContent().length() < 500, "消息内容过长,服务器扛不住啊,兄dei");
+ AssertUtil.allCheckValidateThrow(body);
//校验下回复消息
if (Objects.nonNull(body.getReplyMsgId())) {
Message replyMsg = messageDao.getById(body.getReplyMsgId());
@@ -66,7 +65,6 @@ public class TextMsgHandler extends AbstractMsgHandler {
AssertUtil.equal(replyMsg.getRoomId(), request.getRoomId(), "只能回复相同会话内的消息");
}
if (CollectionUtil.isNotEmpty(body.getAtUidList())) {
- AssertUtil.isTrue(body.getAtUidList().size() > 10, "一次别艾特这么多人");
List atUidList = body.getAtUidList();
Map batch = userInfoCache.getBatch(atUidList);
AssertUtil.equal(atUidList.size(), batch.values().size(), "@用户不存在");
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/VideoMsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/VideoMsgHandler.java
index ae967c7..f2bef5f 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/VideoMsgHandler.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/VideoMsgHandler.java
@@ -6,6 +6,7 @@ import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
import com.abin.mallchat.common.chat.domain.entity.msg.VideoMsgDTO;
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
+import com.abin.mallchat.common.common.utils.AssertUtil;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -29,6 +30,8 @@ public class VideoMsgHandler extends AbstractMsgHandler {
@Override
public void checkMsg(ChatMessageReq request, Long uid) {
+ VideoMsgDTO body = BeanUtil.toBean(request.getBody(), VideoMsgDTO.class);
+ AssertUtil.allCheckValidateThrow(body);
}
@Override
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/enums/ChatGPTModelEnum.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/enums/ChatGPTModelEnum.java
new file mode 100644
index 0000000..6b73b0f
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/enums/ChatGPTModelEnum.java
@@ -0,0 +1,92 @@
+package com.abin.mallchat.custom.chatai.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+@Getter
+public enum ChatGPTModelEnum {
+ // chat
+ GPT_35_TURBO("gpt-3.5-turbo", 3, 40000),
+ GPT_35_TURBO_0301("gpt-3.5-turbo-0301", 3, 40000),
+ GPT_35_TURBO_0613("gpt-3.5-turbo-0613", 3, 40000),
+ GPT_35_TURBO_16K("gpt-3.5-turbo-16k", 3, 40000),
+ GPT_35_TURBO_16K_0613("gpt-3.5-turbo-16k-0613", 3, 40000),
+ // text
+ ADA("ada", 60, 150000),
+ ADA_CODE_SEARCH_CODE("ada-code-search-code", 60, 150000),
+ ADA_CODE_SEARCH_TEXT("ada-code-search-text", 60, 150000),
+ ADA_SEARCH_DOCUMENT("ada-search-document", 60, 150000),
+ ADA_SEARCH_QUERY("ada-search-query", 60, 150000),
+ ADA_SIMILARITY("ada-similarity", 60, 150000),
+ BABBAGE("babbage", 60, 150000),
+ BABBAGE_CODE_SEARCH_CODE("babbage-code-search-code", 60, 150000),
+ BABBAGE_CODE_SEARCH_TEXT("babbage-code-search-text", 60, 150000),
+ BABBAGE_SEARCH_DOCUMENT("babbage-search-document", 60, 150000),
+ BABBAGE_SEARCH_QUERY("babbage-search-query", 60, 150000),
+ BABBAGE_SIMILARITY("babbage-similarity", 60, 150000),
+ CODE_DAVINCI_EDIT_001("code-davinci-edit-001", 20, 150000),
+ CODE_SEARCH_ADA_CODE_001("code-search-ada-code-001", 60, 150000),
+ CODE_SEARCH_ADA_TEXT_001("code-search-ada-text-001", 60, 150000),
+ CODE_SEARCH_BABBAGE_CODE_001("code-search-babbage-code-001", 60, 150000),
+ CODE_SEARCH_BABBAGE_TEXT_001("code-search-babbage-text-001", 60, 150000),
+ CURIE("curie", 60, 150000),
+ CURIE_INSTRUCT_BETA("curie-instruct-beta", 60, 150000),
+ CURIE_SEARCH_DOCUMENT("curie-search-document", 60, 150000),
+ CURIE_SEARCH_QUERY("curie-search-query", 60, 150000),
+ CURIE_SIMILARITY("curie-similarity", 60, 150000),
+ DAVINCI("davinci", 60, 150000),
+ DAVINCI_INSTRUCT_BETA("davinci-instruct-beta", 60, 150000),
+ DAVINCI_SEARCH_DOCUMENT("davinci-search-document", 60, 150000),
+ DAVINCI_SEARCH_QUERY("davinci-search-query", 60, 150000),
+ DAVINCI_SIMILARITY("davinci-similarity", 60, 150000),
+ TEXT_ADA_001("text-ada-001", 60, 150000),
+ TEXT_BABBAGE_001("text-babbage-001", 60, 150000),
+ TEXT_CURIE_001("text-curie-001", 60, 150000),
+ TEXT_DAVINCI_001("text-davinci-001", 60, 150000),
+ TEXT_DAVINCI_002("text-davinci-002", 60, 150000),
+ TEXT_DAVINCI_003("text-davinci-003", 60, 150000),
+ TEXT_DAVINCI_EDIT_001("text-davinci-edit-001", 20, 150000),
+ TEXT_EMBEDDING_ADA_002("text-embedding-ada-002", 60, 150000),
+ TEXT_SEARCH_ADA_DOC_001("text-search-ada-doc-001", 60, 150000),
+ TEXT_SEARCH_ADA_QUERY_001("text-search-ada-query-001", 60, 150000),
+ TEXT_SEARCH_BABBAGE_DOC_001("text-search-babbage-doc-001", 60, 150000),
+ TEXT_SEARCH_BABBAGE_QUERY_001("text-search-babbage-query-001", 60, 150000),
+ TEXT_SEARCH_CURIE_DOC_001("text-search-curie-doc-001", 60, 150000),
+ TEXT_SEARCH_CURIE_QUERY_001("text-search-curie-query-001", 60, 150000),
+ TEXT_SEARCH_DAVINCI_DOC_001("text-search-davinci-doc-001", 60, 150000),
+ TEXT_SEARCH_DAVINCI_QUERY_001("text-search-davinci-query-001", 60, 150000),
+ TEXT_SIMILARITY_ADA_001("text-similarity-ada-001", 60, 150000),
+ TEXT_SIMILARITY_BABBAGE_001("text-similarity-babbage-001", 60, 150000),
+ TEXT_SIMILARITY_CURIE_001("text-similarity-curie-001", 60, 150000),
+ TEXT_SIMILARITY_DAVINCI_001("text-similarity-davinci-001", 60, 150000);
+
+ /**
+ * 名字
+ */
+ private final String name;
+ /**
+ * 每分钟请求数
+ */
+ private final Integer RPM;
+ /**
+ * 每分钟令牌数
+ */
+ private final Integer TPM;
+
+ private static final Map cache;
+
+ static {
+ cache = Arrays.stream(ChatGPTModelEnum.values()).collect(Collectors.toMap(ChatGPTModelEnum::getName, Function.identity()));
+ }
+
+ public static ChatGPTModelEnum of(String name) {
+ return cache.get(name);
+ }
+
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/AbstractChatAIHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/AbstractChatAIHandler.java
new file mode 100644
index 0000000..183f4e3
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/AbstractChatAIHandler.java
@@ -0,0 +1,128 @@
+package com.abin.mallchat.custom.chatai.handler;
+
+import cn.hutool.core.thread.NamedThreadFactory;
+import com.abin.mallchat.common.chat.domain.entity.Message;
+import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
+import com.abin.mallchat.common.common.exception.BusinessException;
+import com.abin.mallchat.common.common.handler.GlobalUncaughtExceptionHandler;
+import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
+import com.abin.mallchat.custom.chat.domain.vo.request.msg.TextMsgReq;
+import com.abin.mallchat.custom.chat.service.ChatService;
+import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp;
+import com.abin.mallchat.custom.user.service.UserService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.annotation.PostConstruct;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public abstract class AbstractChatAIHandler implements DisposableBean, InitializingBean {
+ public static ExecutorService EXECUTOR;
+
+ @Autowired
+ protected ChatService chatService;
+ @Autowired
+ protected UserService userService;
+
+ @PostConstruct
+ private void init() {
+ ChatAIHandlerFactory.register(getChatAIUserId(), getChatAIName(), this);
+ }
+ // 获取机器人id
+ public abstract Long getChatAIUserId();
+ // 获取机器人名称
+ public abstract String getChatAIName();
+
+ public void chat(Message message) {
+ if (!supports(message)) {
+ return;
+ }
+ EXECUTOR.execute(() -> {
+ String text = doChat(message);
+ if (StringUtils.isNotBlank(text)) {
+ answerMsg(text, message.getRoomId(), message.getFromUid());
+ }
+ });
+ }
+
+ /**
+ * 支持
+ *
+ * @param message 消息
+ * @return boolean true 支持 false 不支持
+ */
+ protected abstract boolean supports(Message message);
+
+ /**
+ * 执行聊天
+ *
+ * @param message 消息
+ * @return {@link String} AI回答的内容
+ */
+ protected abstract String doChat(Message message);
+
+
+ protected void answerMsg(String text, Long roomId, Long uid) {
+ UserInfoResp userInfo = userService.getUserInfo(uid);
+ text = "@" + userInfo.getName() + " " + text;
+ if (text.length() < 450) {
+ save(text, roomId, uid);
+ }else {
+ int maxLen = 450;
+ int len = text.length();
+ int count = (len + maxLen - 1) / maxLen;
+
+ for (int i = 0; i < count; i++) {
+ int start = i * maxLen;
+ int end = Math.min(start + maxLen, len);
+ save(text.substring(start, end), roomId, uid);
+ }
+ }
+ }
+
+ private void save(String text, Long roomId, Long uid) {
+ ChatMessageReq answerReq = new ChatMessageReq();
+ answerReq.setRoomId(roomId);
+ answerReq.setMsgType(MessageTypeEnum.TEXT.getType());
+ TextMsgReq textMsgReq = new TextMsgReq();
+ textMsgReq.setContent(text);
+ textMsgReq.setAtUidList(Collections.singletonList(uid));
+ answerReq.setBody(textMsgReq);
+ chatService.sendMsg(answerReq, getChatAIUserId());
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ EXECUTOR = new ThreadPoolExecutor(
+ 10,
+ 10,
+ 0L,
+ TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<>(15),
+ new NamedThreadFactory("openAI-chat-gpt",
+ null,
+ false,
+ new GlobalUncaughtExceptionHandler()),
+ (r, executor) -> {
+ throw new BusinessException("别问的太快了,我的脑子不够用了");
+ });
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ EXECUTOR.shutdown();
+ if (!EXECUTOR.awaitTermination(30, TimeUnit.SECONDS)) { //最多等30秒,处理不完就拉倒
+ if (log.isErrorEnabled()) {
+ log.error("Timed out while waiting for executor [{}] to terminate", EXECUTOR);
+ }
+ }
+ }
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatAIHandlerFactory.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatAIHandlerFactory.java
new file mode 100644
index 0000000..8d6356d
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatAIHandlerFactory.java
@@ -0,0 +1,42 @@
+package com.abin.mallchat.custom.chatai.handler;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ChatAIHandlerFactory {
+ private static final Map CHATAI_ID_MAP = new ConcurrentHashMap<>();
+ private static final Map CHATAI_NAME_MAP = new ConcurrentHashMap<>();
+
+ public static void register(Long aIUserId, String name, AbstractChatAIHandler chatAIHandler) {
+ CHATAI_ID_MAP.put(aIUserId, chatAIHandler);
+ CHATAI_NAME_MAP.put(name, chatAIHandler);
+ }
+
+ public static AbstractChatAIHandler getChatAIHandlerById(List userIds) {
+ if (CollectionUtils.isEmpty(userIds)) {
+ return null;
+ }
+ for (Long userId : userIds) {
+ AbstractChatAIHandler chatAIHandler = CHATAI_ID_MAP.get(userId);
+ if (chatAIHandler != null) {
+ return chatAIHandler;
+ }
+ }
+ return null;
+ }
+ public static AbstractChatAIHandler getChatAIHandlerByName(String text) {
+ if (StringUtils.isBlank(text)) {
+ return null;
+ }
+ for (Map.Entry entry : CHATAI_NAME_MAP.entrySet()) {
+ if (text.contains("@"+entry.getKey())) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatGLM2Handler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatGLM2Handler.java
new file mode 100644
index 0000000..11350ca
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatGLM2Handler.java
@@ -0,0 +1,138 @@
+package com.abin.mallchat.custom.chatai.handler;
+
+import cn.hutool.http.HttpResponse;
+import com.abin.mallchat.common.chat.domain.entity.Message;
+import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
+import com.abin.mallchat.common.common.constant.RedisKey;
+import com.abin.mallchat.common.common.utils.RedisUtils;
+import com.abin.mallchat.custom.chatai.properties.ChatGLM2Properties;
+import com.abin.mallchat.custom.chatai.utils.ChatGLM2Utils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import static com.abin.mallchat.common.common.constant.RedisKey.USER_GLM2_TIME_LAST;
+
+@Slf4j
+@Component
+public class ChatGLM2Handler extends AbstractChatAIHandler {
+
+ private static final List ERROR_MSG = Arrays.asList(
+ "还摸鱼呢?你不下班我还要下班呢。。。。",
+ "没给钱,矿工了。。。。",
+ "服务器被你们玩儿坏了。。。。",
+ "你们这群人,我都不想理你们了。。。。",
+ "艾特我那是另外的价钱。。。。",
+ "得加钱");
+
+
+ private static final Random RANDOM = new Random();
+
+ @Autowired
+ private ChatGLM2Properties glm2Properties;
+
+ @Override
+ public Long getChatAIUserId() {
+ return glm2Properties.getAIUserId();
+ }
+
+ @Override
+ public String getChatAIName() {
+ if (StringUtils.isNotBlank(glm2Properties.getAIUserName())) {
+ return glm2Properties.getAIUserName();
+ }
+ String name = userService.getUserInfo(glm2Properties.getAIUserId()).getName();
+ glm2Properties.setAIUserName(name);
+ return name;
+ }
+
+ @Override
+ protected String doChat(Message message) {
+ String content = message.getContent().replace("@" +glm2Properties.getAIUserName(), "").trim();
+ Long uid = message.getFromUid();
+ Long minute;
+ String text;
+ if ((minute = userMinutesLater(uid)) > 0) {
+ text = "你太快了 " + minute + "分钟后重试";
+ } else {
+ HttpResponse response = null;
+ try {
+ response = ChatGLM2Utils
+ .create()
+ .url(glm2Properties.getUrl())
+ .prompt(content)
+ .timeout(glm2Properties.getTimeout())
+ .send();
+ text = ChatGLM2Utils.parseText(response);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return getErrorText();
+ }
+ if (StringUtils.isNotBlank(text)) {
+ RedisUtils.set(RedisKey.getKey(USER_GLM2_TIME_LAST, uid), new Date(), glm2Properties.getMinute(), TimeUnit.MINUTES);
+ }
+ }
+ return text;
+ }
+
+ private static String getErrorText() {
+ int index = RANDOM.nextInt(ERROR_MSG.size());
+ return ERROR_MSG.get(index);
+ }
+
+ /**
+ * 用户多少分钟后才能再次聊天
+ *
+ * @param uid
+ * @return
+ */
+ private Long userMinutesLater(Long uid) {
+ // 获取用户最后聊天时间
+ Date lastChatTime = RedisUtils.get(RedisKey.getKey(USER_GLM2_TIME_LAST, uid), Date.class);
+ if (lastChatTime == null) {
+ // 如果没有聊天记录,则可以立即聊天
+ return 0L;
+ }
+ // 计算当前时间和上次聊天时间之间的时间差
+ long now = System.currentTimeMillis();
+ long lastChatTimeMillis = lastChatTime.getTime();
+ long durationMillis = now - lastChatTimeMillis;
+ long minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis);
+ // 计算剩余等待时间
+ long remainingMinutes = glm2Properties.getMinute() - minutes;
+ return remainingMinutes > 0 ? remainingMinutes : 0L;
+ }
+
+
+ @Override
+ protected boolean supports(Message message) {
+ if (!glm2Properties.isUse()) {
+ return false;
+ }
+ /* 前端传@信息后取消注释 */
+ MessageExtra extra = message.getExtra();
+ if (extra == null) {
+ return false;
+ }
+ if (CollectionUtils.isEmpty(extra.getAtUidList())) {
+ return false;
+ }
+ if (!extra.getAtUidList().contains(glm2Properties.getAIUserId())) {
+ return false;
+ }
+
+ if (StringUtils.isBlank(message.getContent())) {
+ return false;
+ }
+ return StringUtils.contains(message.getContent(), "@" + glm2Properties.getAIUserName())
+ && StringUtils.isNotBlank(message.getContent().replace(glm2Properties.getAIUserName(), "").trim());
+ }
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/GPTChatAIHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/GPTChatAIHandler.java
new file mode 100644
index 0000000..f814713
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/GPTChatAIHandler.java
@@ -0,0 +1,98 @@
+package com.abin.mallchat.custom.chatai.handler;
+
+import cn.hutool.http.HttpResponse;
+import com.abin.mallchat.common.chat.domain.entity.Message;
+import com.abin.mallchat.common.common.constant.RedisKey;
+import com.abin.mallchat.common.common.utils.DateUtils;
+import com.abin.mallchat.common.common.utils.RedisUtils;
+import com.abin.mallchat.custom.chatai.properties.ChatGPTProperties;
+import com.abin.mallchat.custom.chatai.utils.ChatGPTUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class GPTChatAIHandler extends AbstractChatAIHandler {
+
+ @Autowired
+ private ChatGPTProperties chatGPTProperties;
+
+ @Override
+ public Long getChatAIUserId() {
+ return chatGPTProperties.getAIUserId();
+ }
+
+ @Override
+ public String getChatAIName() {
+ if (StringUtils.isNotBlank(chatGPTProperties.getAIUserName())) {
+ return chatGPTProperties.getAIUserName();
+ }
+ String name = userService.getUserInfo(chatGPTProperties.getAIUserId()).getName();
+ chatGPTProperties.setAIUserName(name);
+ return name;
+ }
+
+ @Override
+ protected String doChat(Message message) {
+ String content = message.getContent().replace("@" + chatGPTProperties.getAIUserName(), "").trim();
+ Long uid = message.getFromUid();
+ Long chatNum;
+ String text;
+ if ((chatNum = getUserChatNum(uid)) > chatGPTProperties.getLimit()) {
+ text = "你今天已经和我聊了" + chatNum + "次了,我累了,明天再聊吧";
+ } else {
+ HttpResponse response = null;
+ try {
+ response = ChatGPTUtils.create(chatGPTProperties.getKey())
+ .proxyUrl(chatGPTProperties.getProxyUrl())
+ .model(chatGPTProperties.getModelName())
+ .prompt(content)
+ .send();
+ text = ChatGPTUtils.parseText(response);
+ userChatNumInrc(uid);
+ } catch (Exception e) {
+ e.printStackTrace();
+ text = "我累了,明天再聊吧";
+ }
+ }
+ return text;
+ }
+
+ private Long userChatNumInrc(Long uid) {
+ return RedisUtils.inc(RedisKey.getKey(RedisKey.USER_CHAT_NUM, uid), DateUtils.getEndTimeByToday().intValue(), TimeUnit.MILLISECONDS);
+ }
+
+ private Long getUserChatNum(Long uid) {
+ Long num = RedisUtils.get(RedisKey.getKey(RedisKey.USER_CHAT_NUM, uid), Long.class);
+ return num == null ? 0 : num;
+
+ }
+
+
+ @Override
+ protected boolean supports(Message message) {
+ if (!chatGPTProperties.isUse()) {
+ return false;
+ }
+ /* 前端传@信息后取消注释 */
+
+// MessageExtra extra = message.getExtra();
+// if (extra == null) {
+// return false;
+// }
+// if (CollectionUtils.isEmpty(extra.getAtUidList())) {
+// return false;
+// }
+// if (!extra.getAtUidList().contains(chatGPTProperties.getAIUserId())) {
+// return false;
+// }
+
+ if (StringUtils.isBlank(message.getContent())) {
+ return false;
+ }
+ return StringUtils.contains(message.getContent(), "@" + chatGPTProperties.getAIUserName())
+ && StringUtils.isNotBlank(message.getContent().replace(chatGPTProperties.getAIUserName(), "").trim());
+ }
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/properties/ChatGLM2Properties.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/properties/ChatGLM2Properties.java
new file mode 100644
index 0000000..2ccc99b
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/properties/ChatGLM2Properties.java
@@ -0,0 +1,48 @@
+package com.abin.mallchat.custom.chatai.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * ChatGLM2 配置文件
+ *
+ * @author zhaoyuhang
+ * @date 2023/06/30
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "chatai.chatglm2")
+public class ChatGLM2Properties {
+
+ /**
+ * 使用
+ */
+ private boolean use;
+
+ /**
+ * url
+ */
+ private String url;
+
+ /**
+ * 机器人 id
+ */
+ private Long AIUserId;
+
+ /**
+ * 机器人名称
+ */
+ private String AIUserName;
+
+ /**
+ * 每个用户每?分钟可以请求一次
+ */
+ private Long minute = 3L;
+
+ /**
+ * 超时
+ */
+ private Integer timeout = 60*1000;
+
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/properties/ChatGPTProperties.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/properties/ChatGPTProperties.java
new file mode 100644
index 0000000..b7a5c56
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/properties/ChatGPTProperties.java
@@ -0,0 +1,44 @@
+package com.abin.mallchat.custom.chatai.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "chatai.chatgpt")
+public class ChatGPTProperties {
+
+ /**
+ * 是否使用openAI
+ */
+ private boolean use;
+ /**
+ * 机器人 id
+ */
+ private Long AIUserId;
+
+ /**
+ * 机器人名称
+ */
+ private String AIUserName;
+ /**
+ * 模型名称
+ */
+ private String modelName = "text-davinci-003";
+ /**
+ * openAI key
+ */
+ private String key;
+ /**
+ * 代理地址
+ */
+ private String proxyUrl;
+
+ /**
+ * 用户每天条数限制
+ */
+ private Integer limit = 5;
+
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/service/IChatAIService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/service/IChatAIService.java
new file mode 100644
index 0000000..8cded22
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/service/IChatAIService.java
@@ -0,0 +1,8 @@
+package com.abin.mallchat.custom.chatai.service;
+
+import com.abin.mallchat.common.chat.domain.entity.Message;
+
+public interface IChatAIService {
+
+ void chat(Message message);
+}
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/service/impl/ChatAIServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/service/impl/ChatAIServiceImpl.java
new file mode 100644
index 0000000..885c227
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/service/impl/ChatAIServiceImpl.java
@@ -0,0 +1,26 @@
+package com.abin.mallchat.custom.chatai.service.impl;
+
+import com.abin.mallchat.common.chat.domain.entity.Message;
+import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
+import com.abin.mallchat.custom.chatai.handler.AbstractChatAIHandler;
+import com.abin.mallchat.custom.chatai.handler.ChatAIHandlerFactory;
+import com.abin.mallchat.custom.chatai.service.IChatAIService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class ChatAIServiceImpl implements IChatAIService {
+ @Override
+ public void chat(Message message) {
+ MessageExtra extra = message.getExtra();
+ if (extra == null) {
+ return;
+ }
+// AbstractChatAIHandler chatAI = ChatAIHandlerFactory.getChatAIHandlerByName(message.getContent());
+ AbstractChatAIHandler chatAI = ChatAIHandlerFactory.getChatAIHandlerById(extra.getAtUidList());
+ if (chatAI != null) {
+ chatAI.chat(message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/utils/ChatGLM2Utils.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/utils/ChatGLM2Utils.java
new file mode 100644
index 0000000..7ac9e1a
--- /dev/null
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/utils/ChatGLM2Utils.java
@@ -0,0 +1,106 @@
+package com.abin.mallchat.custom.chatai.utils;
+
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+public class ChatGLM2Utils {
+
+
+ private final Map headers;
+ /**
+ * 超时30秒
+ */
+ private Integer timeout = 60 * 1000;
+
+ private String url;
+ /**
+ * 提示词
+ */
+ private String prompt;
+
+ /**
+ * 历史
+ */
+ private List