From 1041dfa909c3dd3dc86dd4d4561e808ce33791ea Mon Sep 17 00:00:00 2001 From: Yutou <583819556@qq.com> Date: Tue, 4 Feb 2025 17:13:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(bot):=20=E5=A2=9E=E5=8A=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=97=A5=E5=BF=97=E7=B3=BB=E7=BB=9F=20-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20textToImage=20=E5=92=8C=20imageToText=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=AE=9E=E7=8E=B0=E6=96=87=E6=9C=AC=E4=B8=8E?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E7=9A=84=E7=9B=B8=E4=BA=92=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=20-=20=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8=20log4j2=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95-=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20BaiduGPTManager=20=E7=B1=BB=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=A4=9A=E7=BA=BF=E7=A8=8B=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=92=8C=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=20-=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20MessageHandleBuild=20=E7=B1=BB=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20message=5Fid=20=E5=8F=82=E6=95=B0=20-=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=83=A8=E5=88=86=E5=8A=9F=E8=83=BD=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E9=94=99=E8=AF=AF=EF=BC=8C=E6=8F=90=E9=AB=98=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 38 +++ .../napcat/handle/MessageHandleBuild.java | 10 +- .../com/yutou/napcat/http/MessageAPI.java | 4 +- .../com/yutou/qqbot/data/baidu/Message.java | 4 +- .../qqbot/models/BiliBili/BiliVideo.java | 3 +- .../yutou/qqbot/models/Commands/BaiduGPT.java | 59 +++-- .../java/com/yutou/qqbot/models/Model.java | 36 ++- .../com/yutou/qqbot/models/setu/QQSetu.java | 9 +- .../yutou/qqbot/utlis/BaiduGPTManager.java | 239 +++++++++++++----- .../com/yutou/qqbot/utlis/ConfigTools.java | 1 + .../yutou/qqbot/utlis/DateFormatUtils.java | 134 ++++++++++ .../com/yutou/qqbot/utlis/DynamicLogFile.java | 124 +++++++++ src/main/java/com/yutou/qqbot/utlis/Log.java | 83 ++++-- src/main/resources/application.properties | 5 +- src/main/resources/log4j2.xml | 39 +++ 15 files changed, 679 insertions(+), 109 deletions(-) create mode 100644 src/main/java/com/yutou/qqbot/utlis/DateFormatUtils.java create mode 100644 src/main/java/com/yutou/qqbot/utlis/DynamicLogFile.java create mode 100644 src/main/resources/log4j2.xml diff --git a/pom.xml b/pom.xml index 87bcf7a..92f5470 100644 --- a/pom.xml +++ b/pom.xml @@ -164,6 +164,44 @@ jsoup 1.17.2 + + com.baidubce + qianfan + 0.1.1 + + + + org.apache.logging.log4j + log4j-core + 2.24.1 + + + org.apache.logging.log4j + log4j-bom + 2.24.1 + import + pom + + + org.apache.logging.log4j + log4j-api + 2.24.1 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + diff --git a/src/main/java/com/yutou/napcat/handle/MessageHandleBuild.java b/src/main/java/com/yutou/napcat/handle/MessageHandleBuild.java index ff782fd..c91f67e 100644 --- a/src/main/java/com/yutou/napcat/handle/MessageHandleBuild.java +++ b/src/main/java/com/yutou/napcat/handle/MessageHandleBuild.java @@ -3,6 +3,7 @@ package com.yutou.napcat.handle; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.yutou.okhttp.BaseBean; +import com.yutou.qqbot.utlis.StringUtils; import lombok.Data; import java.util.ArrayList; @@ -13,6 +14,7 @@ public class MessageHandleBuild { private long qq; private boolean autoEscape; private boolean isGroup; + private String messageId; public static MessageHandleBuild create() { return new MessageHandleBuild(); @@ -41,7 +43,10 @@ public class MessageHandleBuild { isGroup = group; return this; } - + public MessageHandleBuild setMessageId(String messageId) { + this.messageId = messageId; + return this; + } public JSONObject build() { JSONObject json=new JSONObject(); if(isGroup){ @@ -51,6 +56,9 @@ public class MessageHandleBuild { } json.put("auto_escape", autoEscape); json.put("message", msgList); + if(!StringUtils.isEmpty(messageId)){ + json.put("message_id",messageId); + } return json; } } diff --git a/src/main/java/com/yutou/napcat/http/MessageAPI.java b/src/main/java/com/yutou/napcat/http/MessageAPI.java index 7ee7735..8ab2e71 100644 --- a/src/main/java/com/yutou/napcat/http/MessageAPI.java +++ b/src/main/java/com/yutou/napcat/http/MessageAPI.java @@ -44,9 +44,9 @@ public interface MessageAPI { @Field("message_id") long messageId ); - @FormUrlEncoded @POST("/get_msg") Call> getMessage( - @Field("message_id") long messageId + @Body + JSONObject message ); } diff --git a/src/main/java/com/yutou/qqbot/data/baidu/Message.java b/src/main/java/com/yutou/qqbot/data/baidu/Message.java index 0ff2d6a..fc5d53e 100644 --- a/src/main/java/com/yutou/qqbot/data/baidu/Message.java +++ b/src/main/java/com/yutou/qqbot/data/baidu/Message.java @@ -22,9 +22,7 @@ public class Message { public static Message create(String message, boolean isGTP) { Message msg = new Message(); msg.content = message; - if (isGTP) { - msg.role = "assistant"; - } + msg.role = isGTP ? "assistant" : "user"; return msg; } } diff --git a/src/main/java/com/yutou/qqbot/models/BiliBili/BiliVideo.java b/src/main/java/com/yutou/qqbot/models/BiliBili/BiliVideo.java index c558774..390cf33 100644 --- a/src/main/java/com/yutou/qqbot/models/BiliBili/BiliVideo.java +++ b/src/main/java/com/yutou/qqbot/models/BiliBili/BiliVideo.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.yutou.napcat.enums.MessageEnum; import com.yutou.napcat.event.MessageEvent; +import com.yutou.napcat.handle.MessageHandleBuild; import com.yutou.napcat.handle.OtherHandle; import com.yutou.napcat.handle.Reply; import com.yutou.napcat.http.NapCatApi; @@ -78,7 +79,7 @@ public class BiliVideo extends Model { private String onAIVideo(long id) { try { - Response> execute = NapCatApi.getMessageApi().getMessage(id).execute(); + Response> execute = NapCatApi.getMessageApi().getMessage(MessageHandleBuild.create().setMessageId(String.valueOf(id)).build()).execute(); if(execute.body()==null){ return "省流失败"; } diff --git a/src/main/java/com/yutou/qqbot/models/Commands/BaiduGPT.java b/src/main/java/com/yutou/qqbot/models/Commands/BaiduGPT.java index c6d37ab..2c78f6f 100644 --- a/src/main/java/com/yutou/qqbot/models/Commands/BaiduGPT.java +++ b/src/main/java/com/yutou/qqbot/models/Commands/BaiduGPT.java @@ -1,20 +1,24 @@ package com.yutou.qqbot.models.Commands; import com.yutou.napcat.QQDatabase; -import com.yutou.napcat.handle.At; -import com.yutou.napcat.handle.BaseHandle; -import com.yutou.napcat.handle.Text; +import com.yutou.napcat.handle.*; +import com.yutou.napcat.http.NapCatApi; import com.yutou.qqbot.Annotations.UseModel; +import com.yutou.qqbot.QQBotApplication; import com.yutou.qqbot.QQBotManager; +import com.yutou.qqbot.data.baidu.Message; import com.yutou.qqbot.data.baidu.ResponseMessage; +import com.yutou.qqbot.interfaces.DownloadInterface; import com.yutou.qqbot.models.Model; import com.yutou.qqbot.utlis.BaiduGPTManager; import com.yutou.napcat.event.MessageEvent; import com.yutou.qqbot.utlis.ConfigTools; +import com.yutou.qqbot.utlis.HttpTools; import com.yutou.qqbot.utlis.StringUtils; import lombok.val; import org.apache.catalina.valves.JsonErrorReportValve; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -42,16 +46,6 @@ public class BaiduGPT extends Model { @Override public void onMessage(Long qq, MessageEvent event, boolean isGroup) { super.onMessage(qq, event, isGroup); - String version = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION, String.class); - if (StringUtils.isEmpty(version)) { - version = "3.5"; - BaiduGPTManager.getManager().setModelFor35(); - } - if ("3.5".equals(version)) { - BaiduGPTManager.getManager().setModelFor35(); - } else if ("4.0".equals(version)) { - BaiduGPTManager.getManager().setModelFor40(); - } if (event.getTextMessage().equals(QQGroupCommands.GPT_CLEAR)) { BaiduGPTManager.getManager().clear(); QQBotManager.getInstance().sendMessage(event.isUser(), qq, new Text("已经失忆捏")); @@ -87,17 +81,52 @@ public class BaiduGPT extends Model { QQBotManager.getInstance().sendMessage(event.isUser(), qq, list); } return; + }else if(event.getTextMessage().contains("画画")){ + val file = BaiduGPTManager.getManager().textToImage(String.valueOf(qq), event.getTextMessage().replace("@" + QQDatabase.getMe().getUserId(), "").replace("画画", "").trim()); + if(file==null){ + QQBotManager.getInstance().sendMessage(event.isUser(), qq, new Text("画不出")); + }else{ + QQBotManager.getInstance().sendMessage(file,qq, event.getMessageId().toString(), "好嘞"); + } + return; } - ResponseMessage message = BaiduGPTManager.getManager().sendMessage( + if(checkImage()) { + parseImage(event, qq); + return; + + } + Message message = BaiduGPTManager.getManager().sendMessage( String.valueOf(qq), event.getTextMessage().replace("@" + QQDatabase.getMe().getUserId(), "").trim()); String sb = "调用版本:" + BaiduGPTManager.getManager().getGPTVersion() + "\n" + - message.getResult(); + message.getContent(); QQBotManager.getInstance().sendMessage(event.isUser(), qq, new Text(sb)); } } + private void parseImage(MessageEvent event, Long qq) { + Image imageHandle = event.findType(Image.class); + val replyHandle = event.findType(Reply.class); + if (replyHandle != null &&imageHandle==null) { + imageHandle = getReply(replyHandle.getData().getId()).findType(Image.class); + } + if(imageHandle==null){ + return; + } + HttpTools.download(imageHandle.getData().getUrl(), "gpt_parse_image.png", new DownloadInterface() { + @Override + public void onDownload(File file) { + super.onDownload(file); + if(file==null){ + return; + } + val text = BaiduGPTManager.getManager().imageToText(String.valueOf(qq), file); + QQBotManager.getInstance().sendMessage(event.isUser(),qq,new Reply(event.getMessageId()),new Text(text)); + } + }); + + } public static void main(String[] args) { System.out.println(ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION)); diff --git a/src/main/java/com/yutou/qqbot/models/Model.java b/src/main/java/com/yutou/qqbot/models/Model.java index 1cf7318..684a89d 100644 --- a/src/main/java/com/yutou/qqbot/models/Model.java +++ b/src/main/java/com/yutou/qqbot/models/Model.java @@ -2,12 +2,18 @@ package com.yutou.qqbot.models; import com.yutou.napcat.event.GroupMessageEvent; import com.yutou.napcat.event.MessageEvent; -import com.yutou.napcat.handle.At; +import com.yutou.napcat.handle.*; +import com.yutou.napcat.http.NapCatApi; +import com.yutou.napcat.model.MessageBean; +import com.yutou.okhttp.HttpBody; import com.yutou.qqbot.QQNumberManager; import com.yutou.qqbot.interfaces.ModelInterface; import com.yutou.qqbot.utlis.ConfigTools; +import com.yutou.qqbot.utlis.Log; +import lombok.val; +import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -129,6 +135,34 @@ public abstract class Model implements ModelInterface { public boolean isAt() { return msg.contains("@" + ConfigTools.load(ConfigTools.CONFIG, ConfigTools.QQ_NUMBER, String.class)); } + public boolean checkImage(){ + val image = event.findType(Image.class); + if(image!=null){ + return true; + } + val reply = event.findType(Reply.class); + if(reply!=null){ + val replyEvent = getReply(reply.getData().getId()); + if(replyEvent!=null){ + return replyEvent.findType(Image.class)!=null; + } + } + return false; + } + public MessageEvent getReply(long replyId){ + HttpBody body = null; + try { + body = NapCatApi.getMessageApi().getMessage(MessageHandleBuild.create().setMessageId(String.valueOf(replyId)).build()).execute().body(); + if (body == null) { + return null; + } + return MessageEvent.parseHandleHttp(body.getSrc()); + } catch (IOException e) { + Log.e(e); + } + return null; + + } public boolean isAdmin() { return user == 583819556L; diff --git a/src/main/java/com/yutou/qqbot/models/setu/QQSetu.java b/src/main/java/com/yutou/qqbot/models/setu/QQSetu.java index 9b4463c..628d9fa 100644 --- a/src/main/java/com/yutou/qqbot/models/setu/QQSetu.java +++ b/src/main/java/com/yutou/qqbot/models/setu/QQSetu.java @@ -7,10 +7,7 @@ import com.alibaba.fastjson2.JSONObject; import com.yutou.napcat.QQDatabase; import com.yutou.napcat.enums.MessageEnum; import com.yutou.napcat.event.GroupMessageEvent; -import com.yutou.napcat.handle.At; -import com.yutou.napcat.handle.BaseHandle; -import com.yutou.napcat.handle.Image; -import com.yutou.napcat.handle.Text; +import com.yutou.napcat.handle.*; import com.yutou.napcat.http.NapCatApi; import com.yutou.napcat.model.MessageBean; import com.yutou.okhttp.HttpBody; @@ -70,7 +67,9 @@ public class QQSetu extends Model { RedisTools.set(db_print, redisKey, json.toJSONString()); JSONObject info = setu.getJSONObject("info"); JSONObject score = setu.getJSONObject("score"); - HttpBody body = NapCatApi.getMessageApi().getMessage(info.getLong("id")).execute().body(); + HttpBody body = NapCatApi.getMessageApi().getMessage( + MessageHandleBuild.create().setMessageId(String.valueOf(info.getLong("id"))).build() + ).execute().body(); List> sendList = new ArrayList<>(); if (body == null) { sendList.add(new Text("[图片获取失败]")); diff --git a/src/main/java/com/yutou/qqbot/utlis/BaiduGPTManager.java b/src/main/java/com/yutou/qqbot/utlis/BaiduGPTManager.java index 26ea5f4..8f12d45 100644 --- a/src/main/java/com/yutou/qqbot/utlis/BaiduGPTManager.java +++ b/src/main/java/com/yutou/qqbot/utlis/BaiduGPTManager.java @@ -1,104 +1,225 @@ package com.yutou.qqbot.utlis; -import com.alibaba.fastjson2.JSONObject; +import com.baidubce.qianfan.Qianfan; +import com.baidubce.qianfan.model.chat.ChatResponse; +import com.baidubce.qianfan.model.image.Image2TextResponse; +import com.baidubce.qianfan.model.image.Text2ImageResponse; import com.yutou.qqbot.data.baidu.Message; -import com.yutou.qqbot.data.baidu.ResponseMessage; +import lombok.val; -import java.nio.charset.StandardCharsets; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Base64; +import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; public class BaiduGPTManager { - private static int MAX_MESSAGE = 5; - private static BaiduGPTManager manager; - private static final String url_3_5 = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"; - //4.0 - private static final String url_4_0 = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro"; - private static String url = url_3_5; + private static final AtomicInteger MAX_MESSAGE = new AtomicInteger(20); private static final String AppID = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_APPID, String.class); private static final String ApiKey = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_API_KEY, String.class); + //ConfigTools.load操作可以确保获取到相关参数,所以无需关心 + private static final String AccessKey = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_ACCESS_KEY, String.class); private static final String SecretKey = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_SECRET_KEY, String.class); - private static Map> msgMap; + private final ConcurrentHashMap> msgMap; + private final static String modelFor40 = "ERNIE-4.0-8K"; + private final static String modelFor35 = "ERNIE-3.5-8K"; + private String model = modelFor35; + // 新增锁映射表 + private final ConcurrentHashMap userLocks = new ConcurrentHashMap<>(); + private final Qianfan qianfan; private BaiduGPTManager() { - msgMap = new HashMap<>(); + msgMap = new ConcurrentHashMap<>(); + qianfan = new Qianfan(AccessKey, SecretKey); + String savedVersion = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION, String.class); + if (StringUtils.isEmpty(savedVersion) || (!"3.5".equals(savedVersion) && !"4.0".equals(savedVersion))) { + savedVersion = "3.5"; + ConfigTools.save(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION, savedVersion); + } + model = "3.5".equals(savedVersion) ? modelFor35 : modelFor40; } + private static volatile BaiduGPTManager manager; + public static BaiduGPTManager getManager() { if (manager == null) { - manager = new BaiduGPTManager(); + synchronized (BaiduGPTManager.class) { + if (manager == null) { + manager = new BaiduGPTManager(); + } + } } return manager; } public int setMaxMessageCount(int count) { - MAX_MESSAGE = count; - return MAX_MESSAGE; + MAX_MESSAGE.set(count); + return count; } - public void setModelFor40() { - url = url_4_0; + public synchronized void setModelFor40() { + model = modelFor40; ConfigTools.save(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION, "4.0"); } - public void setModelFor35() { - url = url_3_5; + public synchronized void setModelFor35() { + model = modelFor35; ConfigTools.save(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION, "3.5"); } - public void clear() { + /** + * 这里确实是需要清空所有数据 + */ + public synchronized void clear() { // 添加同步 msgMap.clear(); + for (AtomicBoolean value : userLocks.values()) { + value.set(false); + } + userLocks.forEachValue(1, atomicBoolean -> atomicBoolean.set(false)); + userLocks.clear(); } - private String getToken() { - String _url = String.format("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s" - , ApiKey - , SecretKey - ); - String get = HttpTools.get(_url); - JSONObject response = JSONObject.parseObject(get); - return response.getString("access_token"); + + // 这个是官方的示例代码,表示连续对话 + private static void exampleChat() { + Qianfan qianfan = new Qianfan(); + ChatResponse response = qianfan.chatCompletion() + // 设置需要使用的模型,与endpoint同时只能设置一种 + .model("ERNIE-Bot") + // 通过传入历史对话记录来实现多轮对话 + .addMessage("user", "你好!你叫什么名字?") + .addMessage("assistant", "你好!我是文心一言,英文名是ERNIE Bot。") + // 传入本轮对话的用户输入 + .addMessage("user", "刚刚我的问题是什么?") + .execute(); + System.out.println("输出内容:" + response.getResult()); } - public ResponseMessage sendMessage(String user, String message) { - List messages = msgMap.getOrDefault(user, new ArrayList<>()); - if (messages.size() > MAX_MESSAGE * 2) { - messages.remove(0); - messages.remove(1); + public Message sendMessage(String user, String message) { + // 获取或创建用户锁 + AtomicBoolean lock = userLocks.computeIfAbsent(user, k -> new AtomicBoolean(false)); + // 尝试加锁(如果已被锁定则立即返回提示) + if (!lock.compareAndSet(false, true)) { + return Message.create("您有请求正在处理中,请稍后再试", true); } - messages.add(Message.create(message)); - - JSONObject json = new JSONObject(); - json.put("messages", messages); - System.out.println("json = " + json); - - Map map = new HashMap<>(); - map.put("Content-Type", "application/json"); - map.put("Content-Length", String.valueOf(json.toJSONString().getBytes(StandardCharsets.UTF_8).length)); - String post = HttpTools.http_post(url + "?access_token=" + getToken() - , json.toJSONString().getBytes(StandardCharsets.UTF_8), 0, map); - System.out.println("post = " + post); - if (StringUtils.isEmpty(post)) { - clear(); - return sendMessage(user, message); + try { + List list = msgMap.computeIfAbsent(user, k -> Collections.synchronizedList(new ArrayList<>())); + // 限制历史消息的最大数量 + synchronized (list) { + if (list.size() >= MAX_MESSAGE.get()) { + int removeCount = list.size() - MAX_MESSAGE.get() + 1; // 腾出空间给新消息 + list.subList(0, removeCount).clear(); + } + list.add(Message.create(message)); + } + val builder = qianfan.chatCompletion() + .model(model); + for (Message msg : list) { + builder.addMessage(msg.getRole(), msg.getContent()); + } + ChatResponse chatResponse = builder.execute(); + Message response = Message.create(chatResponse.getResult(), true); + synchronized (list) { + list.add(response); + if (list.size() > MAX_MESSAGE.get()) { + int overflow = list.size() - MAX_MESSAGE.get(); + list.subList(0, overflow).clear(); + } + } +// msgMap.put(user, list); + return response; + } catch (Exception e) { + Log.e(e, message); + return Message.create("请求失败,请重试", true); + } finally { + lock.set(false); + userLocks.remove(user, lock); } - ResponseMessage response = JSONObject.parseObject(post, ResponseMessage.class); - messages.add(Message.create(response.getResult(), true)); - msgMap.put(user, messages); - System.out.println("\n\n"); - return response; + + } + + /** + * 将文本转换为图像文件 + * 该方法使用预训练的AI模型将给定的文本转换为图像,并将其保存为文件 + * + * @param user 用户标识符,用于为生成的图像文件命名 + * @param text 要转换为图像的文本 + * @return 返回生成的图像文件对象,如果转换过程中发生错误,则返回null + */ + public File textToImage(String user, String text) { + // 使用QianFan的text2Image方法将文本转换为图像数据 + Text2ImageResponse response = qianfan.text2Image() + .prompt(text) + .execute(); + // 获取转换后的图像数据,以Base64编码的图像字符串形式 + val b64Image = response.getData().get(0).getB64Image(); + // 将Base64编码的图像数据转换为图像文件 + // 创建一个临时目录下的图像文件,文件名包含用户标识符和当前时间戳,以确保唯一性 + val imageFile = new File("tmp" + File.separator + user + "_" + System.currentTimeMillis() + ".png"); + try (val inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(b64Image))) { + // 将解码后的图像数据复制到图像文件中,替换现有文件 + Files.copy(inputStream, imageFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + return imageFile; + } catch (Exception e) { + // 如果在图像文件生成过程中发生错误,记录错误信息 + Log.e(e); + } + // 如果发生错误,返回null + return null; + } + + /** + * 将图片转换为文本描述 + * + * @param user 使用该功能的用户标识 + * @param file 要转换的图片文件 + * @return 转换后的文本描述,如果转换失败则返回null + */ + public String imageToText(String user, File file) { + // 将file文件转换成base64的代码 + try { + // 读取文件内容并转换为Base64编码 + val base64 = Base64.getEncoder().encodeToString(Files.readAllBytes(file.toPath())); + + // 调用图像转文本的API + Image2TextResponse response = qianfan.image2Text() + .image(base64) + .prompt("分析图片,如果图中有文本则加上文本内容") + .execute(); + + // 获取API返回的结果 + String result = response.getResult(); + + // 如果结果不是中文,通过sendMessage函数尝试将其翻译成中文 + result = sendMessage("bot", "你是一个语言翻译专家,如果这段内容不是中文,请翻译成中文,如果已经是中文则不需要翻译:" + result).getContent(); + + // 返回最终的中文描述结果 + return result; + } catch (Exception e) { + // 异常处理:记录错误日志 + Log.e(e); + } + // 如果发生异常,返回null + return null; } public String getGPTVersion() { - return (url.equals(url_3_5) ? "3.5" : "4.0"); + return (model.equals(modelFor35) ? "3.5" : "4.0"); } public static void main(String[] args) throws Exception { - ResponseMessage message = BaiduGPTManager.getManager().sendMessage("test", "现在假设小猪等于1,小猴等于2"); - System.out.println(message.getResult()); - message = BaiduGPTManager.getManager().sendMessage("test", "那么小猪加上小猴等于多少?"); - System.out.println(message.getResult()); +// BaiduGPTManager.getManager().textToImage("user","画一个猫娘,用二次元动画画风,她是粉色头发,坐在地上"); +// BaiduGPTManager.getManager().imageToText("user",new File("test.png")); +// Message message = BaiduGPTManager.getManager().sendMessage("user", "现在假设小猪等于1,小猴等于2"); +// System.out.println(message.getContent()); +// message = BaiduGPTManager.getManager().sendMessage("user", "那么小猪加上小猴等于多少?"); +// System.out.println(message.getContent()); + System.out.println(BaiduGPTManager.getManager().sendMessage("user", "分析这个网页链接的页面内容,而非链接本身:https://www.bilibili.com/video/BV1TTf5YrESz/").getContent()); } } diff --git a/src/main/java/com/yutou/qqbot/utlis/ConfigTools.java b/src/main/java/com/yutou/qqbot/utlis/ConfigTools.java index ecdd0de..ed308bb 100644 --- a/src/main/java/com/yutou/qqbot/utlis/ConfigTools.java +++ b/src/main/java/com/yutou/qqbot/utlis/ConfigTools.java @@ -30,6 +30,7 @@ public class ConfigTools { public static final String BAIDU_GPT_VERSION = "baidu.gpt.version"; public static final String BAIDU_GPT_APPID = "baidu.gpt.appid"; public static final String BAIDU_GPT_API_KEY = "baidu.gpt.apikey"; + public static final String BAIDU_GPT_ACCESS_KEY = "baidu.gpt.accessKey"; public static final String BAIDU_GPT_SECRET_KEY = "baidu.gpt.SecretKey"; public static final String TURNIP_PROPHET_SERVER = "turnip.server"; public static final String TURNIP_PROPHET_SEND_TMP_GROUP = "turnip.send.tmp.group"; diff --git a/src/main/java/com/yutou/qqbot/utlis/DateFormatUtils.java b/src/main/java/com/yutou/qqbot/utlis/DateFormatUtils.java new file mode 100644 index 0000000..f6827fd --- /dev/null +++ b/src/main/java/com/yutou/qqbot/utlis/DateFormatUtils.java @@ -0,0 +1,134 @@ +package com.yutou.qqbot.utlis; + +import org.springframework.util.StringUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DateFormatUtils { + private final Map> formats = new ConcurrentHashMap<>(); + private static volatile DateFormatUtils utils; + public static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; + + private DateFormatUtils() {} + + public static DateFormatUtils getInstance() { + if (utils == null) { + synchronized (DateFormatUtils.class) { + if (utils == null) { + utils = new DateFormatUtils(); + } + } + } + return utils; + } + + public String format(Date date, String format) { + getFormat(format).applyPattern(format); + return getFormat(format).format(date); + } + + public String format(long time, String format) { + return getFormat(format).format(new Date(time)); + } + + public String format(long time) { + if (time < 1000000000) { + time *= 1000; + } + return getFormat().format(new Date(time)); + } + + public String format(Date date) { + return getFormat().format(date); + } + + public String format() { + return getFormat().format(new Date()); + } + + public Date parseTimer(String date) { + return parse(date, "HH:mm:ss"); + } + + public Date parse(String date, String format) { + try { + if(date.startsWith("1")){ + return new Date(Long.parseLong(date)); + } + return getFormat(format).parse(date); + } catch (ParseException e) { + System.err.println("Error parsing date: " + e.getMessage()); + return null; + } + } + + public String parseString(String date, String format) { + Date time = parse(date, format); + return format(time, format); + } + public String convertSeconds(long totalSeconds) { + // 计算总小时数 + long hours = totalSeconds / 3600; + // 剩余的秒数 + long remainingSecondsAfterHours = totalSeconds % 3600; + // 计算分钟数 + long minutes = remainingSecondsAfterHours / 60; + // 最后剩余的秒数 + long seconds = remainingSecondsAfterHours % 60; + return String.format("%d小时%d分%d秒", hours, minutes, seconds); + } + public String formatMillis(long millis) { + Duration duration = Duration.ofMillis(millis); + int seconds = (int) (duration.getSeconds() % 60); + int minutes = (int) ((duration.getSeconds() / 60) % 60); + int hours = (int) (duration.getSeconds() / 3600); + + return String.format("%02d:%02d:%02d", hours, minutes, seconds); + } + public boolean checkTime(List weeks, String recordDate) { + if (!StringUtils.hasText(recordDate)) { + recordDate = "00:00:00 - 23:59:59"; + } + String[] parts = recordDate.split(" - "); + LocalTime startTime = LocalTime.parse(parts[0], DateTimeFormatter.ofPattern("HH:mm:ss")); + LocalTime endTime = LocalTime.parse(parts[1], DateTimeFormatter.ofPattern("HH:mm:ss")); + + + // 获取当前时间 + LocalTime currentTime = LocalTime.now(); + LocalDate currentDate = LocalDate.now(); + + + // 获取当前日期对应的星期几(1-7分别对应周一到周日) + int currentWeekDay = currentDate.getDayOfWeek().getValue(); + + // 判断当前日期是否在指定的星期列表中 + boolean isSpecifiedWeekDay; + if (weeks == null) { + isSpecifiedWeekDay = true; + } else { + isSpecifiedWeekDay = weeks.contains(String.valueOf(currentWeekDay)); + } + + // 判断当前时间是否在指定的时间范围内 + boolean isWithinRange = (currentTime.isAfter(startTime) || currentTime.equals(startTime)) && + (currentTime.isBefore(endTime) || currentTime.equals(endTime)); + return isWithinRange && isSpecifiedWeekDay; + } + private SimpleDateFormat getFormat() { + return getFormat(DEFAULT_PATTERN); + } + + private SimpleDateFormat getFormat(String format) { + return formats.computeIfAbsent(format, key -> ThreadLocal.withInitial(() -> new SimpleDateFormat(key))).get(); + } +} diff --git a/src/main/java/com/yutou/qqbot/utlis/DynamicLogFile.java b/src/main/java/com/yutou/qqbot/utlis/DynamicLogFile.java new file mode 100644 index 0000000..39805d9 --- /dev/null +++ b/src/main/java/com/yutou/qqbot/utlis/DynamicLogFile.java @@ -0,0 +1,124 @@ +package com.yutou.qqbot.utlis; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.layout.PatternLayout; + +import java.util.Date; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class DynamicLogFile { + // 创建一个缓存,用于存储Logger对象,最大容量为1000,过期时间为10分钟 + static Cache cache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterAccess(10, TimeUnit.MINUTES) + .removalListener(it -> { + if (it.wasEvicted()) { + if (it.getKey() != null) { + String loggerName = (String) it.getKey(); + remove(loggerName, true); + } + } + }) + .build(); + + // 根据loggerName获取Logger对象,如果缓存中不存在,则创建一个新的Logger对象并放入缓存 + public static Logger getLogger(String loggerName) { + try { + return cache.get(loggerName, () -> { + configureLogger(loggerName); + return LogManager.getLogger(loggerName); + }); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + // 配置Logger对象 + private static void configureLogger(String loggerName) { + + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + + // 创建日志格式 + Layout layout = PatternLayout.newBuilder() + .withPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %-5p [%thread] (%F:%L) : %m%n") + .build(); + + // 创建时间触发策略 + TimeBasedTriggeringPolicy timePolicy = TimeBasedTriggeringPolicy.newBuilder() + .build(); + + // 创建文件大小触发策略 + SizeBasedTriggeringPolicy sizePolicy = SizeBasedTriggeringPolicy.createPolicy("100 MB"); + + // 创建组合触发策略 + CompositeTriggeringPolicy triggeringPolicy = CompositeTriggeringPolicy.createPolicy(timePolicy, sizePolicy); + + + // 创建滚动文件Appender + RollingFileAppender appender = RollingFileAppender.newBuilder() + .setName(loggerName) + .withFileName("logs" + "/" + DateFormatUtils.getInstance().format(new Date(), "yyyy-MM-dd") + "/" + loggerName + ".log") + .withFilePattern("logs" + "/" + "%d{yyyy-MM-dd}" + "/" + loggerName + "-%i.log.gz") + .setLayout(layout) + .setImmediateFlush(true) + .withAppend(true) + .setIgnoreExceptions(false) + .withPolicy(triggeringPolicy) + .build(); + + appender.start(); + config.addAppender(appender); + + // 获取Logger对象 + org.apache.logging.log4j.core.Logger coreLogger = context.getLogger(loggerName); + if (coreLogger == null) { + throw new IllegalStateException("Logger with name " + loggerName + " does not exist."); + } + + // 将Appender添加到Logger对象中 + coreLogger.addAppender(appender); + coreLogger.setLevel(Level.ALL); + coreLogger.setAdditive(false); + + // 更新Logger对象 + context.updateLoggers(); + + } + + // 移除Logger对象 + public static void remove(String loggerName) { + remove(loggerName, false); + } + + // 私有方法,移除Logger对象,isAuto参数用于判断是否是自动移除 + private static void remove(String loggerName, boolean isAuto) { + if (!isAuto) { + cache.invalidate(loggerName); + } + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + org.apache.logging.log4j.core.Logger coreLogger = context.getLogger(loggerName); + Appender appender = config.getAppender(loggerName); + if (appender != null) { + appender.stop(); + coreLogger.removeAppender(appender); + } + config.getAppenders().remove(loggerName); + context.updateLoggers(); + } + +} diff --git a/src/main/java/com/yutou/qqbot/utlis/Log.java b/src/main/java/com/yutou/qqbot/utlis/Log.java index 7126c48..a21d6bd 100644 --- a/src/main/java/com/yutou/qqbot/utlis/Log.java +++ b/src/main/java/com/yutou/qqbot/utlis/Log.java @@ -1,33 +1,74 @@ package com.yutou.qqbot.utlis; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.StackLocatorUtil; + + public class Log { - public static void i(String tag, Object log) { - i('[' + tag + ']' + log); + + public static void i() { + System.out.println(); } - public static void i(Object log) { - if (ConfigTools.load(ConfigTools.CONFIG, ConfigTools.SERVICE_LOG, boolean.class, false)) { - System.out.printf("[%s]%s%n", - AppTools.getToDayNowTimeToString(), - log - ); + public static Logger getDynamicLogger(String loggerName) { + return DynamicLogFile.getLogger(loggerName); + } + + public static void removeDynamicLogger(String loggerName) { + DynamicLogFile.remove(loggerName); + } + + public static void i(Object... log) { + if (!((boolean) ConfigTools.load(ConfigTools.CONFIG, ConfigTools.SERVICE_LOG))) { + return; } + LogManager.getLogger(getStackTrace()).info(buildLog(log)); } - public static void e(String tag, Exception e) { - System.err.printf("[%s]%s - %s%n", - AppTools.getToDayNowTimeToString(), - tag, - AppTools.getExceptionString(e) - ); - } - - public static void i(Object tag, Object log) { - if (tag instanceof String) { - i("[" + tag + "]" + log); - } else { - i(tag.getClass().getSimpleName(), log); + public static void e(Object... log) { + if (!ConfigTools.load(ConfigTools.CONFIG, ConfigTools.SERVICE_LOG, Boolean.class)) { + return; } + LogManager.getLogger(getStackTrace()).error(buildLog(log)); + } + + public static void e(Throwable e, Object... log) { + if (!ConfigTools.load(ConfigTools.CONFIG, ConfigTools.SERVICE_LOG, Boolean.class)) { + return; + } + LogManager.getLogger(getStackTrace()).error(buildLog(log), e); + } + + public static void e(Throwable e) { + if (!ConfigTools.load(ConfigTools.CONFIG, ConfigTools.SERVICE_LOG, Boolean.class)) { + return; + } + LogManager.getLogger().error(getStackTrace(), e); + } + + + public static void d(Object... log) { + if (!ConfigTools.load(ConfigTools.CONFIG, ConfigTools.SERVICE_LOG, Boolean.class)) { + return; + } + LogManager.getLogger().debug(buildLog(log)); + } + + private static String getStackTrace() { + StackTraceElement element = StackLocatorUtil.getStackTraceElement(3); + return "(" + element.getFileName() + ":" + element.getLineNumber() + ")"; + } + + private static String buildLog(Object... log) { + StringBuilder sb = new StringBuilder(); + for (Object obj : log) { + if (!sb.isEmpty()) { + sb.append("|"); + } + sb.append(obj); + } + return sb.toString(); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3c76dd8..8d07152 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,6 @@ server.port=8002 spring.servlet.multipart.max-file-size=10MB -spring.servlet.multipart.max-request-size=100MB \ No newline at end of file +spring.servlet.multipart.max-request-size=100MB +logging.config=classpath:log4j2.xml +logging.file.path=./logs +logging.level.com.log.controller = trace \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..801c1d5 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + %d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %-5p [%thread] (%F:%L) : %m%n%throwable + + + + + + + + + + + + + + + + + + + + + + +