mirror of
https://github.com/zongzibinbin/MallChat.git
synced 2026-04-07 08:37:35 +00:00
46
README.md
46
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://github.com/Evansy/MallChatWeb](https://github.com/Evansy/MallChatWeb)
|
||||||
- 项目演示地址:[https://mallchat.cn](https://mallchat.cn) (记住抹茶.cn,下次工作摸鱼可直接打开)
|
- 项目演示地址:[https://mallchat.cn](https://mallchat.cn) (记住抹茶.cn,下次工作摸鱼可直接打开)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### 技术选型
|
### 技术选型
|
||||||
|
|
||||||
@@ -52,6 +58,7 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
|
|||||||
| Hutool | Java工具类库 | https://github.com/looly/hutool |
|
| Hutool | Java工具类库 | https://github.com/looly/hutool |
|
||||||
| Swagger-UI | API文档生成工具 | https://github.com/swagger-api/swagger-ui |
|
| Swagger-UI | API文档生成工具 | https://github.com/swagger-api/swagger-ui |
|
||||||
| Hibernate-validator | 接口校验框架 | [hibernate.org/validator/](hibernate.org/validator/) |
|
| Hibernate-validator | 接口校验框架 | [hibernate.org/validator/](hibernate.org/validator/) |
|
||||||
|
| minio | 自建对象存储 | https://github.com/minio/minio |
|
||||||
|
|
||||||
#### 前端技术
|
#### 前端技术
|
||||||
|
|
||||||
@@ -67,7 +74,9 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
|
|||||||
|
|
||||||
更多有趣功能在持续更新中。。。
|
更多有趣功能在持续更新中。。。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## star 趋势图
|
## star 趋势图
|
||||||
|
|
||||||
@@ -78,6 +87,37 @@ MallChat的后端项目,是一个既能购物又能即时聊天的电商系统
|
|||||||
<img src="https://contrib.rocks/image?repo=zongzibinbin/MallChat" />
|
<img src="https://contrib.rocks/image?repo=zongzibinbin/MallChat" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<h4>优秀贡献者:</h4>
|
||||||
|
<table>
|
||||||
|
<tr border>
|
||||||
|
<th width="30%">用户</th>
|
||||||
|
<th>贡献模块</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" rowspan="3"><a href="https://github.com/LIjiAngChen8"><img src="https://avatars.githubusercontent.com/u/48879481?v=4" style="border-radius: 50%; width: 100px; height: 100px;"></a></td>
|
||||||
|
<td><a href="https://github.com/Evansy/MallChatWeb/pull/72">前端 图片,语音,文件类型消息</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://github.com/Evansy/MallChatWeb/pull/50">前端 撤回消息</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://github.com/Evansy/MallChatWeb/pull/28">前端 消息点赞</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" rowspan="2"><a href="https://github.com/1045078399"><img src="https://avatars.githubusercontent.com/u/82020261?v=4" style="border-radius: 50%; width: 100px; height: 100px;"></a></td>
|
||||||
|
<td><a href="https://github.com/zongzibinbin/MallChat/pull/31">DFA敏感词检测</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://github.com/zongzibinbin/MallChat/pull/64">OpenAI聊天机器人</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/xiaocairush"><img src="https://avatars.githubusercontent.com/u/6416523?v=4" style="border-radius: 50%; width: 100px; height: 100px;"></a></td>
|
||||||
|
<td><a href="https://github.com/zongzibinbin/MallChat/pull/42">ac自动机敏感词检测</a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 公众号
|
## 公众号
|
||||||
|
|
||||||
微信搜索 **阿斌Java之路** 关注我的原创公众号,后台回复「**抹茶**」即可加入抹茶交流群,一些做过公司万人群聊,高并发的小伙伴都在里面讨论方案。公众号也会经常更新项目相关的文档,等你来撩~~
|
微信搜索 **阿斌Java之路** 关注我的原创公众号,后台回复「**抹茶**」即可加入抹茶交流群,一些做过公司万人群聊,高并发的小伙伴都在里面讨论方案。公众号也会经常更新项目相关的文档,等你来撩~~
|
||||||
|
|||||||
BIN
docs/image/复杂图片.jpg
Normal file
BIN
docs/image/复杂图片.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 668 KiB |
BIN
docs/image/文档1.jpg
Normal file
BIN
docs/image/文档1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
docs/image/文档2.jpg
Normal file
BIN
docs/image/文档2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/image/群聊截图.jpg
Normal file
BIN
docs/image/群聊截图.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 KiB |
BIN
docs/image/设计模式.jpg
Normal file
BIN
docs/image/设计模式.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/image/项目大纲.jpg
Normal file
BIN
docs/image/项目大纲.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
@@ -113,7 +113,8 @@ CREATE TABLE `user` (
|
|||||||
INDEX `idx_create_time`(`create_time`) USING BTREE,
|
INDEX `idx_create_time`(`create_time`) USING BTREE,
|
||||||
INDEX `idx_update_time`(`update_time`) USING BTREE
|
INDEX `idx_update_time`(`update_time`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
|
) 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
|
-- Table structure for user_backpack
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
|||||||
2
docs/version/2023-07-01.sql
Normal file
2
docs/version/2023-07-01.sql
Normal file
@@ -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');
|
||||||
@@ -6,6 +6,8 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,12 +22,15 @@ import java.io.Serializable;
|
|||||||
public class FileMsgDTO implements Serializable {
|
public class FileMsgDTO implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@ApiModelProperty("大小(字节)")
|
@ApiModelProperty("大小(字节)")
|
||||||
|
@NotNull
|
||||||
private Long size;
|
private Long size;
|
||||||
|
|
||||||
@ApiModelProperty("下载地址")
|
@ApiModelProperty("下载地址")
|
||||||
|
@NotBlank
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
@ApiModelProperty("文件名(带后缀)")
|
@ApiModelProperty("文件名(带后缀)")
|
||||||
|
@NotBlank
|
||||||
private String fileName;
|
private String fileName;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,11 +22,14 @@ import java.io.Serializable;
|
|||||||
public class SoundMsgDTO implements Serializable {
|
public class SoundMsgDTO implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@ApiModelProperty("大小(字节)")
|
@ApiModelProperty("大小(字节)")
|
||||||
|
@NotNull
|
||||||
private Long size;
|
private Long size;
|
||||||
|
|
||||||
@ApiModelProperty("时长(秒)")
|
@ApiModelProperty("时长(秒)")
|
||||||
|
@NotNull
|
||||||
private Integer second;
|
private Integer second;
|
||||||
|
|
||||||
@ApiModelProperty("下载地址")
|
@ApiModelProperty("下载地址")
|
||||||
|
@NotBlank
|
||||||
private String url;
|
private String url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import lombok.NoArgsConstructor;
|
|||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description: 视频消息入参
|
* Description: 视频消息入参
|
||||||
@@ -23,20 +22,23 @@ import java.math.BigDecimal;
|
|||||||
public class VideoMsgDTO implements Serializable {
|
public class VideoMsgDTO implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@ApiModelProperty("大小(字节)")
|
@ApiModelProperty("大小(字节)")
|
||||||
|
@NotNull
|
||||||
private Long size;
|
private Long size;
|
||||||
|
|
||||||
@ApiModelProperty("下载地址")
|
@ApiModelProperty("下载地址")
|
||||||
|
@NotBlank
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
@ApiModelProperty("缩略图宽度(像素)")
|
@ApiModelProperty("缩略图宽度(像素)")
|
||||||
@NotNull
|
@NotNull
|
||||||
private Integer thumbWidth = BigDecimal.ROUND_HALF_DOWN;
|
private Integer thumbWidth;
|
||||||
|
|
||||||
@ApiModelProperty("缩略图高度(像素)")
|
@ApiModelProperty("缩略图高度(像素)")
|
||||||
@NotNull
|
@NotNull
|
||||||
private Integer thumbHeight;
|
private Integer thumbHeight;
|
||||||
|
|
||||||
@ApiModelProperty("缩略图大小(字节)")
|
@ApiModelProperty("缩略图大小(字节)")
|
||||||
|
@NotNull
|
||||||
private Long thumbSize;
|
private Long thumbSize;
|
||||||
|
|
||||||
@ApiModelProperty("缩略图下载地址")
|
@ApiModelProperty("缩略图下载地址")
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ public class RedisKey {
|
|||||||
*/
|
*/
|
||||||
public static final String USER_SUMMARY_STRING = "userSummary:uid_%d";
|
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) {
|
public static String getKey(String key, Object... objects) {
|
||||||
return BASE_KEY + String.format(key, objects);
|
return BASE_KEY + String.format(key, objects);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class GlobalExceptionHandler {
|
|||||||
e.getBindingResult().getFieldErrors().forEach(x -> errorMsg.append(x.getField()).append(x.getDefaultMessage()).append(","));
|
e.getBindingResult().getFieldErrors().forEach(x -> errorMsg.append(x.getField()).append(x.getDefaultMessage()).append(","));
|
||||||
String message = errorMsg.toString();
|
String message = errorMsg.toString();
|
||||||
log.info("validation parameters error!The reason is:{}", message);
|
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(","));
|
e.getBindingResult().getFieldErrors().forEach(x -> errorMsg.append(x.getField()).append(x.getDefaultMessage()).append(","));
|
||||||
String message = errorMsg.toString();
|
String message = errorMsg.toString();
|
||||||
log.info("validation parameters error!The reason is:{}", message);
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,16 +3,83 @@ package com.abin.mallchat.common.common.utils;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.abin.mallchat.common.common.exception.BusinessErrorEnum;
|
import com.abin.mallchat.common.common.exception.BusinessErrorEnum;
|
||||||
import com.abin.mallchat.common.common.exception.BusinessException;
|
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 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.text.MessageFormat;
|
||||||
import java.util.Objects;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验工具类
|
* 校验工具类
|
||||||
*/
|
*/
|
||||||
public class AssertUtil {
|
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 <T> void fastFailValidate(T obj) {
|
||||||
|
Set<ConstraintViolation<T>> constraintViolations = failFastValidator.validate(obj);
|
||||||
|
if (constraintViolations.size() > 0) {
|
||||||
|
throwException(CommonErrorEnum.PARAM_VALID,constraintViolations.iterator().next().getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注解验证参数(全部校验,抛出异常)
|
||||||
|
* @param obj
|
||||||
|
*/
|
||||||
|
public static <T> void allCheckValidateThrow(T obj) {
|
||||||
|
Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj);
|
||||||
|
if (constraintViolations.size() > 0) {
|
||||||
|
StringBuilder errorMsg = new StringBuilder();
|
||||||
|
Iterator<ConstraintViolation<T>> iterator = constraintViolations.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
ConstraintViolation<T> 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 <T> Map<String,String> allCheckValidate(T obj) {
|
||||||
|
Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj);
|
||||||
|
if (constraintViolations.size() > 0) {
|
||||||
|
Map<String,String> errorMessages= new HashMap<>();
|
||||||
|
Iterator<ConstraintViolation<T>> iterator = constraintViolations.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
ConstraintViolation<T> violation = iterator.next();
|
||||||
|
errorMessages.put(violation.getPropertyPath().toString(),violation.getMessage());
|
||||||
|
}
|
||||||
|
return errorMessages;
|
||||||
|
}
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
//如果不是true,则抛异常
|
//如果不是true,则抛异常
|
||||||
public static void isTrue(boolean expression, String msg) {
|
public static void isTrue(boolean expression, String msg) {
|
||||||
if (!expression) {
|
if (!expression) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,3 +26,11 @@ oss.endpoint=http://localhost:9000
|
|||||||
oss.access-key=BEZ213
|
oss.access-key=BEZ213
|
||||||
oss.secret-key=Ii4vCMIXuFfds1EZ8e7RXI2342342kV
|
oss.secret-key=Ii4vCMIXuFfds1EZ8e7RXI2342342kV
|
||||||
oss.bucketName=default
|
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
|
||||||
@@ -26,3 +26,11 @@ oss.endpoint=http://localhost:9000
|
|||||||
oss.access-key=BEZ213
|
oss.access-key=BEZ213
|
||||||
oss.secret-key=Ii4vCMIXuFe241dsfEZ8e7RXI2342342kV
|
oss.secret-key=Ii4vCMIXuFe241dsfEZ8e7RXI2342342kV
|
||||||
oss.bucketName=default
|
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
|
||||||
@@ -63,3 +63,14 @@ wx:
|
|||||||
secret: ${mallchat.wx.secret} # 公众号的appsecret
|
secret: ${mallchat.wx.secret} # 公众号的appsecret
|
||||||
token: ${mallchat.wx.token} # 接口配置里的Token值
|
token: ${mallchat.wx.token} # 接口配置里的Token值
|
||||||
aesKey: ${mallchat.wx.aesKey} # 接口配置里的EncodingAESKey值
|
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}
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ import javax.validation.constraints.NotNull;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 聊天信息点播
|
||||||
* Description: 消息发送请求体
|
* Description: 消息发送请求体
|
||||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||||
* Date: 2023-03-23
|
* Date: 2023-03-23
|
||||||
|
*
|
||||||
|
* @author zhaoyuhang
|
||||||
|
* @date 2023/06/30
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +21,8 @@ import java.util.List;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class TextMsgReq {
|
public class TextMsgReq {
|
||||||
|
|
||||||
|
@NotBlank(message = "内容不能为空")
|
||||||
|
@Size(max = 1024, message = "消息内容过长,服务器扛不住啊,兄dei")
|
||||||
@ApiModelProperty("消息内容")
|
@ApiModelProperty("消息内容")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@@ -26,5 +30,6 @@ public class TextMsgReq {
|
|||||||
private Long replyMsgId;
|
private Long replyMsgId;
|
||||||
|
|
||||||
@ApiModelProperty("艾特的uid")
|
@ApiModelProperty("艾特的uid")
|
||||||
|
@Size(max = 10, message = "一次别艾特这么多人")
|
||||||
private List<Long> atUidList;
|
private List<Long> atUidList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.FileMsgDTO;
|
||||||
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
|
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
|
||||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
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 com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -29,6 +30,8 @@ public class FileMsgHandler extends AbstractMsgHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkMsg(ChatMessageReq request, Long uid) {
|
public void checkMsg(ChatMessageReq request, Long uid) {
|
||||||
|
FileMsgDTO body = BeanUtil.toBean(request.getBody(), FileMsgDTO.class);
|
||||||
|
AssertUtil.allCheckValidateThrow(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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.ImgMsgDTO;
|
||||||
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
|
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
|
||||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
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 com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -29,6 +30,8 @@ public class ImgMsgHandler extends AbstractMsgHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkMsg(ChatMessageReq request, Long uid) {
|
public void checkMsg(ChatMessageReq request, Long uid) {
|
||||||
|
ImgMsgDTO body = BeanUtil.toBean(request.getBody(), ImgMsgDTO.class);
|
||||||
|
AssertUtil.allCheckValidateThrow(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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.MessageExtra;
|
||||||
import com.abin.mallchat.common.chat.domain.entity.msg.SoundMsgDTO;
|
import com.abin.mallchat.common.chat.domain.entity.msg.SoundMsgDTO;
|
||||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
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 com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -29,6 +30,8 @@ public class SoundMsgHandler extends AbstractMsgHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkMsg(ChatMessageReq request, Long uid) {
|
public void checkMsg(ChatMessageReq request, Long uid) {
|
||||||
|
SoundMsgDTO body = BeanUtil.toBean(request.getBody(), SoundMsgDTO.class);
|
||||||
|
AssertUtil.allCheckValidateThrow(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,7 +47,7 @@ public class SoundMsgHandler extends AbstractMsgHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object showMsg(Message msg) {
|
public Object showMsg(Message msg) {
|
||||||
return msg.getExtra().getFileMsg();
|
return msg.getExtra().getSoundMsgDTO();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ public class TextMsgHandler extends AbstractMsgHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void checkMsg(ChatMessageReq request, Long uid) {
|
public void checkMsg(ChatMessageReq request, Long uid) {
|
||||||
TextMsgReq body = BeanUtil.toBean(request.getBody(), TextMsgReq.class);
|
TextMsgReq body = BeanUtil.toBean(request.getBody(), TextMsgReq.class);
|
||||||
AssertUtil.isNotEmpty(body.getContent(), "内容不能为空");
|
AssertUtil.allCheckValidateThrow(body);
|
||||||
AssertUtil.isTrue(body.getContent().length() < 500, "消息内容过长,服务器扛不住啊,兄dei");
|
|
||||||
//校验下回复消息
|
//校验下回复消息
|
||||||
if (Objects.nonNull(body.getReplyMsgId())) {
|
if (Objects.nonNull(body.getReplyMsgId())) {
|
||||||
Message replyMsg = messageDao.getById(body.getReplyMsgId());
|
Message replyMsg = messageDao.getById(body.getReplyMsgId());
|
||||||
@@ -66,7 +65,6 @@ public class TextMsgHandler extends AbstractMsgHandler {
|
|||||||
AssertUtil.equal(replyMsg.getRoomId(), request.getRoomId(), "只能回复相同会话内的消息");
|
AssertUtil.equal(replyMsg.getRoomId(), request.getRoomId(), "只能回复相同会话内的消息");
|
||||||
}
|
}
|
||||||
if (CollectionUtil.isNotEmpty(body.getAtUidList())) {
|
if (CollectionUtil.isNotEmpty(body.getAtUidList())) {
|
||||||
AssertUtil.isTrue(body.getAtUidList().size() > 10, "一次别艾特这么多人");
|
|
||||||
List<Long> atUidList = body.getAtUidList();
|
List<Long> atUidList = body.getAtUidList();
|
||||||
Map<Long, User> batch = userInfoCache.getBatch(atUidList);
|
Map<Long, User> batch = userInfoCache.getBatch(atUidList);
|
||||||
AssertUtil.equal(atUidList.size(), batch.values().size(), "@用户不存在");
|
AssertUtil.equal(atUidList.size(), batch.values().size(), "@用户不存在");
|
||||||
|
|||||||
@@ -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.MessageExtra;
|
||||||
import com.abin.mallchat.common.chat.domain.entity.msg.VideoMsgDTO;
|
import com.abin.mallchat.common.chat.domain.entity.msg.VideoMsgDTO;
|
||||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
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 com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -29,6 +30,8 @@ public class VideoMsgHandler extends AbstractMsgHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkMsg(ChatMessageReq request, Long uid) {
|
public void checkMsg(ChatMessageReq request, Long uid) {
|
||||||
|
VideoMsgDTO body = BeanUtil.toBean(request.getBody(), VideoMsgDTO.class);
|
||||||
|
AssertUtil.allCheckValidateThrow(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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<String, ChatGPTModelEnum> cache;
|
||||||
|
|
||||||
|
static {
|
||||||
|
cache = Arrays.stream(ChatGPTModelEnum.values()).collect(Collectors.toMap(ChatGPTModelEnum::getName, Function.identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatGPTModelEnum of(String name) {
|
||||||
|
return cache.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Long, AbstractChatAIHandler> CHATAI_ID_MAP = new ConcurrentHashMap<>();
|
||||||
|
private static final Map<String, AbstractChatAIHandler> 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<Long> 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<String, AbstractChatAIHandler> entry : CHATAI_NAME_MAP.entrySet()) {
|
||||||
|
if (text.contains("@"+entry.getKey())) {
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, String> headers;
|
||||||
|
/**
|
||||||
|
* 超时30秒
|
||||||
|
*/
|
||||||
|
private Integer timeout = 60 * 1000;
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
/**
|
||||||
|
* 提示词
|
||||||
|
*/
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史
|
||||||
|
*/
|
||||||
|
private List<Object> history;
|
||||||
|
|
||||||
|
|
||||||
|
public ChatGLM2Utils() {
|
||||||
|
HashMap<String, String> _headers_ = new HashMap<>();
|
||||||
|
_headers_.put("Content-Type", "application/json");
|
||||||
|
this.headers = _headers_;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatGLM2Utils create() {
|
||||||
|
return new ChatGLM2Utils();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChatGLM2Utils url(String url) {
|
||||||
|
this.url = url;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChatGLM2Utils timeout(int timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGLM2Utils prompt(String prompt) {
|
||||||
|
this.prompt = prompt;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponse send() {
|
||||||
|
JSONObject param = new JSONObject();
|
||||||
|
param.set("prompt", prompt);
|
||||||
|
log.info("headers >>> " + headers);
|
||||||
|
log.info("param >>> " + param);
|
||||||
|
return HttpUtil.createPost(url)
|
||||||
|
.addHeaders(headers)
|
||||||
|
.body(param.toString())
|
||||||
|
.timeout(timeout)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parseText(String body) {
|
||||||
|
log.info("body >>> " + body);
|
||||||
|
JSONObject jsonObj = new JSONObject(body);
|
||||||
|
if (200 != jsonObj.getInt("status")) {
|
||||||
|
log.error("status >>> " + jsonObj.getInt("status"));
|
||||||
|
return "闹脾气了,等会再试试吧~";
|
||||||
|
}
|
||||||
|
return jsonObj.getStr("response");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parseText(HttpResponse response) {
|
||||||
|
return parseText(response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HttpResponse send = null;
|
||||||
|
try {
|
||||||
|
send = ChatGLM2Utils
|
||||||
|
.create()
|
||||||
|
.url("http://vastmiao.natapp1.cc")
|
||||||
|
.timeout(60 * 1000)
|
||||||
|
.prompt("Spring的启动流程是什么")
|
||||||
|
.send();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
System.out.println("send = " + send);
|
||||||
|
|
||||||
|
System.out.println("parseText(send) = " + parseText(send));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.abin.mallchat.custom.chatai.utils;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.json.JSONArray;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import com.abin.mallchat.common.common.exception.BusinessException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ChatGPTUtils {
|
||||||
|
|
||||||
|
private static final String URL = "https://api.openai.com/v1/completions";
|
||||||
|
|
||||||
|
private String model = "text-davinci-003";
|
||||||
|
|
||||||
|
private final Map<String, String> headers;
|
||||||
|
/**
|
||||||
|
* 超时30秒
|
||||||
|
*/
|
||||||
|
private Integer timeout = 30 * 1000;
|
||||||
|
/**
|
||||||
|
* 参数用于指定生成文本的最大长度。
|
||||||
|
* 它表示生成的文本中最多包含多少个 token。一个 token 可以是一个单词、一个标点符号或一个空格。
|
||||||
|
*/
|
||||||
|
private int maxTokens = 2048;
|
||||||
|
/**
|
||||||
|
* 用于控制生成文本的多样性。
|
||||||
|
* 较高的温度会导致更多的随机性和多样性,但可能会降低生成文本的质量。默认值为 1,建议在 0.7 到 1.3 之间调整。
|
||||||
|
*/
|
||||||
|
private Object temperature = 1;
|
||||||
|
/**
|
||||||
|
* 用于控制生成文本的多样性。
|
||||||
|
* 它会根据概率选择最高的几个单词,而不是选择概率最高的单词。默认值为 1,建议在 0.7 到 0.9 之间调整。
|
||||||
|
*/
|
||||||
|
private Object topP = 0.9;
|
||||||
|
/**
|
||||||
|
* 用于控制生成文本中重复单词的数量。
|
||||||
|
* 较高的惩罚值会导致更少的重复单词,但可能会降低生成文本的流畅性。默认值为 0,建议在 0 到 2 之间调整。
|
||||||
|
*/
|
||||||
|
private Object frequencyPenalty = 0.0;
|
||||||
|
/**
|
||||||
|
* 用于控制生成文本中出现特定单词的数量。
|
||||||
|
* 较高的惩罚值会导致更少的特定单词,但可能会降低生成文本的流畅性。默认值为 0,建议在 0 到 2 之间调整。
|
||||||
|
*/
|
||||||
|
private Object presencePenalty = 0.6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提示词
|
||||||
|
*/
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
private String proxyUrl;
|
||||||
|
|
||||||
|
public ChatGPTUtils(String key) {
|
||||||
|
HashMap<String, String> _headers_ = new HashMap<>();
|
||||||
|
_headers_.put("Content-Type", "application/json");
|
||||||
|
if (StringUtils.isBlank(key)) {
|
||||||
|
throw new BusinessException("openAi key is blank");
|
||||||
|
}
|
||||||
|
_headers_.put("Authorization", "Bearer " + key);
|
||||||
|
this.headers = _headers_;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatGPTUtils create(String key) {
|
||||||
|
return new ChatGPTUtils(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parseText(HttpResponse response) {
|
||||||
|
return parseText(response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parseText(String body) {
|
||||||
|
log.info("body >>> " + body);
|
||||||
|
JSONObject jsonObj = new JSONObject(body);
|
||||||
|
JSONObject error = jsonObj.getJSONObject("error");
|
||||||
|
if (error != null) {
|
||||||
|
log.error("error >>> " + error);
|
||||||
|
return "闹脾气了,等会再试试吧~";
|
||||||
|
}
|
||||||
|
JSONArray choicesArr = jsonObj.getJSONArray("choices");
|
||||||
|
JSONObject choiceObj = choicesArr.getJSONObject(0);
|
||||||
|
return choiceObj.getStr("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils model(String model) {
|
||||||
|
this.model = model;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils timeout(int timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils maxTokens(int maxTokens) {
|
||||||
|
this.maxTokens = maxTokens;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils temperature(int temperature) {
|
||||||
|
this.temperature = temperature;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils topP(int topP) {
|
||||||
|
this.topP = topP;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils frequencyPenalty(int frequencyPenalty) {
|
||||||
|
this.frequencyPenalty = frequencyPenalty;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils presencePenalty(int presencePenalty) {
|
||||||
|
this.presencePenalty = presencePenalty;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils prompt(String prompt) {
|
||||||
|
this.prompt = prompt;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatGPTUtils proxyUrl(String proxyUrl) {
|
||||||
|
this.proxyUrl = proxyUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponse send() {
|
||||||
|
JSONObject param = new JSONObject();
|
||||||
|
param.set("model", model);
|
||||||
|
param.set("prompt", prompt);
|
||||||
|
param.set("max_tokens", maxTokens);
|
||||||
|
param.set("temperature", temperature);
|
||||||
|
param.set("top_p", topP);
|
||||||
|
param.set("frequency_penalty", frequencyPenalty);
|
||||||
|
param.set("presence_penalty", presencePenalty);
|
||||||
|
log.info("headers >>> " + headers);
|
||||||
|
log.info("param >>> " + param);
|
||||||
|
return HttpUtil.createPost(StringUtils.isNotBlank(proxyUrl) ? proxyUrl : URL)
|
||||||
|
.addHeaders(headers)
|
||||||
|
.body(param.toString())
|
||||||
|
.timeout(timeout)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HttpResponse send = ChatGPTUtils.create("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")
|
||||||
|
.timeout(30 * 1000)
|
||||||
|
.prompt("Spring的启动流程是什么")
|
||||||
|
.send();
|
||||||
|
System.out.println("send = " + send);
|
||||||
|
// JSON 数据
|
||||||
|
// JSON 数据
|
||||||
|
JSONObject jsonObj = new JSONObject(send.body());
|
||||||
|
JSONArray choicesArr = jsonObj.getJSONArray("choices");
|
||||||
|
JSONObject choiceObj = choicesArr.getJSONObject(0);
|
||||||
|
String text = choiceObj.getStr("text");
|
||||||
|
System.out.println("text = " + text);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,11 @@ import com.abin.mallchat.common.chat.domain.entity.Message;
|
|||||||
import com.abin.mallchat.common.common.event.MessageSendEvent;
|
import com.abin.mallchat.common.common.event.MessageSendEvent;
|
||||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||||
|
import com.abin.mallchat.custom.chatai.service.IChatAIService;
|
||||||
import com.abin.mallchat.custom.user.service.WebSocketService;
|
import com.abin.mallchat.custom.user.service.WebSocketService;
|
||||||
import com.abin.mallchat.custom.user.service.adapter.WSAdapter;
|
import com.abin.mallchat.custom.user.service.adapter.WSAdapter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -27,6 +29,8 @@ public class MessageSendListener {
|
|||||||
private ChatService chatService;
|
private ChatService chatService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private MessageDao messageDao;
|
private MessageDao messageDao;
|
||||||
|
@Autowired
|
||||||
|
private IChatAIService openAIService;
|
||||||
|
|
||||||
@Async
|
@Async
|
||||||
@TransactionalEventListener(classes = MessageSendEvent.class, fallbackExecution = true)
|
@TransactionalEventListener(classes = MessageSendEvent.class, fallbackExecution = true)
|
||||||
@@ -36,4 +40,10 @@ public class MessageSendListener {
|
|||||||
webSocketService.sendToAllOnline(WSAdapter.buildMsgSend(msgResp), message.getFromUid());
|
webSocketService.sendToAllOnline(WSAdapter.buildMsgSend(msgResp), message.getFromUid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TransactionalEventListener(classes = MessageSendEvent.class, fallbackExecution = true)
|
||||||
|
public void handlerMsg(@NotNull MessageSendEvent event) {
|
||||||
|
Message message = messageDao.getById(event.getMsgId());
|
||||||
|
openAIService.chat(message);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user