diff --git a/README.md b/README.md index f31cf3b..9706d19 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## 项目介绍: - 本项目是一个微信公众号项目,需配合微信公众号使用,在微信公众号配置本项目运行的服务器域名,用户关注公众号后,向公众号发送任意信息,公众号会根据用户发送的内容自动回复。 +本项目是一个微信公众号项目,需配合微信公众号使用,在微信公众号配置本项目运行的服务器域名,用户关注公众号后,向公众号发送任意信息,公众号会根据用户发送的内容自动回复。 ## 涉及框架及技术 @@ -25,20 +25,33 @@ _Tips:1.2版本开始使用Vert.x替换SpringBoot_ ## 支持的功能 + [x] 自定义关键字回复内容 -+ [x] 调用ChatGPT接口回复内容(需配置启动参数或者环境变量:`OPENAI_API_KEY`) -+ [x] 调用OPENAI规范的接口回复内容(需配置启动参数或者环境变量:`OPENAI_SERVER_URL`) -+ [x] 调用图灵机器人(V2)接口回复内容(需配置启动参数或者环境变量:`TULING_API_KEY`) - -## 使用说明: - -1. 使用之前需要有微信公众号的帐号,没有的请戳[微信公众号申请](https://mp.weixin.qq.com/cgi-bin/readtemplate?t=register/step1_tmpl&lang=zh_CN) -2. 如果需要使用图灵机器人的回复内容则需要[注册图灵机器人帐号](http://tuling123.com/register/email.jhtml)获取相应的ApiKey并配置在启动参数或者环境变量中 -3. 如果需要使用ChatGPT的回复内容则需要[创建OpenAI的API Key](https://platform.openai.com/account/api-keys)并配置在启动参数或者环境变量中 -4. 可以通过配置启动参数或者环境变量`OPENAI_SERVER_URL`指定访问OpenAI服务的baseUrl -5. 可以通过配置启动参数或者环境变量`OPENAI_BASE_DOMAIN`更换访问OpenAI的域名 -6. 可以通过配置启动参数或者环境变量`OPENAI_PROXY`使用代理服务访问OpenAI -7. 内容响应来源的优先级`自定义关键 > ChatGPT > 图灵机器人` -8. 在微信公众号后台配置回调URL为,其中`wechatrobot.doodl6.com`是你自己的域名,token与`config.yml`里面配置的保持一致即可 ++ [x] 调用OpenAI接口回复内容(配置启动参数或者环境变量:`OPENAI_API_KEY`) ++ [x] 修改OpenAI的接口地址(配置启动参数或者环境变量:`OPENAI_SERVER_URL`) ++ [x] 调用通义千问接口回复内容(配置启动参数或者环境变量:`DASHSCOPE_API_KEY`) ++ [x] 调用图灵机器人(V2)接口回复内容(配置启动参数或者环境变量:`TULING_API_KEY`) + +## 使用说明 + +需要有微信公众号的帐号,没有的请戳[微信公众号申请](https://mp.weixin.qq.com/cgi-bin/readtemplate?t=register/step1_tmpl&lang=zh_CN) + +内容响应来源的优先级`自定义关键 > OpenAI > 通义千问 > 图灵机器人` + +在微信公众号后台配置回调URL为,其中`wechatrobot.doodl6.com`是你自己的域名,token与`config.yml`里面配置的保持一致即可 + +### OpenAI + +1. 如果需要使用OpenAI的回复内容则需要[创建OpenAI的API Key](https://platform.openai.com/account/api-keys)并配置在启动参数或者环境变量中 +2. 可以通过配置启动参数或者环境变量`OPENAI_SERVER_URL`指定访问OpenAI服务的baseUrl +3. 可以通过配置启动参数或者环境变量`OPENAI_BASE_DOMAIN`更换访问OpenAI的域名(优先级低于`OPENAI_SERVER_URL`) +4. 可以通过配置启动参数或者环境变量`OPENAI_PROXY`使用代理服务访问OpenAI + +### 通义千问 + +如果需要使用通义千问的回复内容则需要[创建通义千问的API Key](https://bailian.console.aliyun.com/#/api_key)并配置在启动参数或者环境变量中 + +### 图灵机器人 + +如果需要使用图灵机器人的回复内容则需要[注册图灵机器人帐号](http://tuling123.com/register/email.jhtml)获取相应的ApiKey并配置在启动参数或者环境变量中 ## 开发部署 diff --git a/pom.xml b/pom.xml index f2ee9f0..0c83dd9 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ - 1.2 + 1.3 UTF-8 UTF-8 1.8 @@ -22,14 +22,15 @@ 3.12.0 2.11.0 + 2.14.4 32.1.1-jre 2.15.2 1.6.20 - 1.3.8 1.18.30 4.10.0 0.12.0 1.0.3 + 1.7.21 4.4.4 @@ -55,6 +56,17 @@ ${lombok.version} + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + com.google.guava guava @@ -118,6 +130,13 @@ ${kotlin-stdlib.version} + + + com.alibaba + dashscope-sdk-java + ${dashscope.version} + + diff --git a/robot-common/pom.xml b/robot-common/pom.xml index 90b5e3c..05c0870 100644 --- a/robot-common/pom.xml +++ b/robot-common/pom.xml @@ -16,6 +16,15 @@ guava + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + org.apache.commons commons-lang3 diff --git a/robot-common/src/main/java/com/doodl6/wechatrobot/util/HttpUtil.java b/robot-common/src/main/java/com/doodl6/wechatrobot/util/HttpUtil.java index 9dfe96a..5956079 100644 --- a/robot-common/src/main/java/com/doodl6/wechatrobot/util/HttpUtil.java +++ b/robot-common/src/main/java/com/doodl6/wechatrobot/util/HttpUtil.java @@ -1,5 +1,6 @@ package com.doodl6.wechatrobot.util; +import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -7,13 +8,9 @@ import okhttp3.Response; import okhttp3.ResponseBody; -import java.util.logging.Level; -import java.util.logging.Logger; - +@Slf4j public final class HttpUtil { - private static final Logger LOGGER = Logger.getLogger(HttpUtil.class.getName()); - public static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient(); @@ -43,7 +40,7 @@ private static String executeRequest(Request request) { return responseBody.string(); } } catch (Exception e) { - LOGGER.log(Level.SEVERE, "api调用异常,url:" + request.url(), e); + log.error("api调用异常,url:{}", request.url(), e); throw new RuntimeException(e); } } diff --git a/robot-common/src/main/java/com/doodl6/wechatrobot/util/JsonUtil.java b/robot-common/src/main/java/com/doodl6/wechatrobot/util/JsonUtil.java index e7e0b1d..2c05755 100644 --- a/robot-common/src/main/java/com/doodl6/wechatrobot/util/JsonUtil.java +++ b/robot-common/src/main/java/com/doodl6/wechatrobot/util/JsonUtil.java @@ -5,17 +5,15 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; -import java.util.logging.Level; -import java.util.logging.Logger; +@Slf4j public class JsonUtil { - private static final Logger LOGGER = Logger.getLogger(JsonUtil.class.getName()); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; @@ -37,7 +35,7 @@ public static T jsonToObj(String jsonStr, Class clazz) { try { return OBJECT_MAPPER.readValue(jsonStr, clazz); } catch (IOException e) { - LOGGER.log(Level.SEVERE, "json转对象异常", e); + log.error("json转对象异常", e); throw new IllegalArgumentException(e.getMessage()); } } @@ -46,7 +44,7 @@ public static T jsonToObj(InputStream inputStream, Class clazz) { try { return OBJECT_MAPPER.readValue(inputStream, clazz); } catch (IOException e) { - LOGGER.log(Level.SEVERE, "json转对象异常", e); + log.error("json转对象异常", e); throw new IllegalArgumentException(e.getMessage()); } } @@ -55,7 +53,7 @@ public static String objToJson(Object obj) { try { return OBJECT_MAPPER.writeValueAsString(obj); } catch (JsonProcessingException e) { - LOGGER.log(Level.SEVERE, "对象转json异常", e); + log.error("对象转json异常", e); throw new IllegalArgumentException(e.getMessage()); } } diff --git a/robot-common/src/main/java/com/doodl6/wechatrobot/util/LogUtil.java b/robot-common/src/main/java/com/doodl6/wechatrobot/util/LogUtil.java deleted file mode 100644 index 7265077..0000000 --- a/robot-common/src/main/java/com/doodl6/wechatrobot/util/LogUtil.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.doodl6.wechatrobot.util; - -/** - * 日志工具类 - */ -public final class LogUtil { - - private LogUtil() { - } - - /** - * 构建日志字符串 - */ - public static String buildLog(Object... objArray) { - StringBuilder logBuilder = new StringBuilder(); - - for (Object obj : objArray) { - if (logBuilder.length() > 0) { - logBuilder.append(" | "); - } - - if (obj instanceof String) { - logBuilder.append(obj); - } else { - logBuilder.append(JsonUtil.objToJson(obj)); - } - } - - return logBuilder.toString(); - } -} diff --git a/robot-common/src/main/java/com/doodl6/wechatrobot/util/XmlUtil.java b/robot-common/src/main/java/com/doodl6/wechatrobot/util/XmlUtil.java index 29f7fba..9ed77b7 100644 --- a/robot-common/src/main/java/com/doodl6/wechatrobot/util/XmlUtil.java +++ b/robot-common/src/main/java/com/doodl6/wechatrobot/util/XmlUtil.java @@ -1,22 +1,20 @@ package com.doodl6.wechatrobot.util; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import lombok.extern.slf4j.Slf4j; import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; +@Slf4j public class XmlUtil { - private static final Logger LOGGER = Logger.getLogger(XmlUtil.class.getName()); - private static final XmlMapper XML_MAPPER = new XmlMapper(); public static T xmlToObj(String xml, Class clazz) { try { return XML_MAPPER.readValue(xml, clazz); } catch (IOException e) { - LOGGER.log(Level.SEVERE, "xml转clazz异常", e); + log.error("xml转clazz异常", e); throw new IllegalArgumentException(e.getMessage()); } } @@ -25,7 +23,7 @@ public static String objToXml(Object obj) { try { return XML_MAPPER.writeValueAsString(obj); } catch (IOException e) { - LOGGER.log(Level.SEVERE, "obj转xml异常", e); + log.error("obj转xml异常", e); throw new IllegalArgumentException(e.getMessage()); } } diff --git a/robot-web/pom.xml b/robot-web/pom.xml index 1c2b9a1..086d2f7 100644 --- a/robot-web/pom.xml +++ b/robot-web/pom.xml @@ -30,6 +30,11 @@ service + + com.alibaba + dashscope-sdk-java + + diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/MainVerticle.java b/robot-web/src/main/java/com/doodl6/wechatrobot/MainVerticle.java index 95822f6..49ec254 100644 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/MainVerticle.java +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/MainVerticle.java @@ -14,16 +14,14 @@ import io.vertx.ext.web.Route; import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.StaticHandler; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; +@Slf4j public class MainVerticle extends AbstractVerticle { - private static final Logger LOGGER = Logger.getLogger(MainVerticle.class.getName()); - private static final String CONFIG_KEY = "config"; @Override @@ -43,7 +41,7 @@ public void start() { .requestHandler(router) .listen(port, listen -> { if (listen.succeeded()) { - LOGGER.log(Level.INFO,"HTTP server started on port " + port); + log.info("HTTP server started on port: {}", port); } else { listen.cause().printStackTrace(); System.exit(1); @@ -60,10 +58,10 @@ public static void main(String[] args) { configPath = "config.yml"; } - VertxOptions options = new VertxOptions(); - // 设置事件循环线程的最大执行时间 - options.setMaxEventLoopExecuteTime(TimeUnit.SECONDS.toNanos(10)); - Vertx vertx = Vertx.vertx(options); + VertxOptions options = new VertxOptions(); + // 设置事件循环线程的最大执行时间 + options.setMaxEventLoopExecuteTime(TimeUnit.SECONDS.toNanos(10)); + Vertx vertx = Vertx.vertx(options); ConfigStoreOptions storeOptions = new ConfigStoreOptions() .setType("file") .setFormat("yaml") @@ -74,7 +72,7 @@ public static void main(String[] args) { ConfigRetriever retriever = ConfigRetriever.create(vertx, retrieverOptions); retriever.getConfig() .onSuccess(config -> { - System.out.println("load config success:" + config.toString()); + log.info("load config success:{}", config.toString()); DeploymentOptions deploymentOptions = new DeploymentOptions().setConfig(config); vertx.deployVerticle(new MainVerticle(), deploymentOptions); }) diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/handler/MainHandler.java b/robot-web/src/main/java/com/doodl6/wechatrobot/handler/MainHandler.java index 9173968..30f3bfa 100644 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/handler/MainHandler.java +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/handler/MainHandler.java @@ -5,7 +5,6 @@ import com.doodl6.wechatrobot.processor.EventMessageProcessor; import com.doodl6.wechatrobot.processor.TextMessageProcessor; import com.doodl6.wechatrobot.processor.WeChatMessageProcessor; -import com.doodl6.wechatrobot.service.KeywordService; import com.doodl6.wechatrobot.service.WeChatService; import com.doodl6.wechatrobot.util.XmlUtil; import com.google.common.collect.Lists; @@ -15,16 +14,14 @@ import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerRequest; import io.vertx.ext.web.RoutingContext; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; +@Slf4j public class MainHandler implements Handler { - private static final Logger LOGGER = Logger.getLogger(KeywordService.class.getName()); - private final WeChatService weChatService; public MainHandler(Vertx vertx, WechatConfig wechatConfig, KeywordConfig keywordConfig) { @@ -50,17 +47,18 @@ public void handle(RoutingContext context) { } } else { request.bodyHandler(body -> { + long start = System.currentTimeMillis(); String requestBody = body.toString(); Object result; try { result = weChatService.processReceived(requestBody); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "获取来自微信的消息异常", e); + log.error("获取来自微信的消息异常", e); result = StringUtils.EMPTY; } this.responseXml(context, result); + log.info("响应耗时:{}ms", (System.currentTimeMillis() - start)); }); - } } diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/processor/EventMessageProcessor.java b/robot-web/src/main/java/com/doodl6/wechatrobot/processor/EventMessageProcessor.java index 5d2f6e6..3f2b8c9 100644 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/processor/EventMessageProcessor.java +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/processor/EventMessageProcessor.java @@ -5,16 +5,14 @@ import com.doodl6.wechatrobot.enums.WeChatMsgType; import com.doodl6.wechatrobot.response.BaseMessage; import com.doodl6.wechatrobot.response.TextMessage; - -import java.util.logging.Logger; +import lombok.extern.slf4j.Slf4j; /** * 事件类型消息处理类 */ +@Slf4j public class EventMessageProcessor implements WeChatMessageProcessor { - private static final Logger LOGGER = Logger.getLogger(EventMessageProcessor.class.getName()); - @Override public WeChatMsgType getMsgType() { return WeChatMsgType.EVENT; @@ -29,7 +27,7 @@ public BaseMessage processMessage(WeChatMessage weChatMessage) { if (eventType == WeChatEventType.SUBSCRIBE) { return new TextMessage(toUserName, fromUserName, "谢谢关注!可以开始跟我聊天啦😁"); } else if (eventType == WeChatEventType.UNSUBSCRIBE) { - LOGGER.info("用户[" + weChatMessage.getFromUserName() + "]取消了订阅"); + log.info("用户[{}]取消了订阅", weChatMessage.getFromUserName()); } return new TextMessage(toUserName, fromUserName, "bye!"); diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/processor/TextMessageProcessor.java b/robot-web/src/main/java/com/doodl6/wechatrobot/processor/TextMessageProcessor.java index d706f34..eead37d 100644 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/processor/TextMessageProcessor.java +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/processor/TextMessageProcessor.java @@ -5,31 +5,32 @@ import com.doodl6.wechatrobot.enums.WeChatMsgType; import com.doodl6.wechatrobot.response.BaseMessage; import com.doodl6.wechatrobot.response.TextMessage; -import com.doodl6.wechatrobot.service.OpenAIService; -import com.doodl6.wechatrobot.service.KeywordService; -import com.doodl6.wechatrobot.service.TulingService; -import com.doodl6.wechatrobot.util.LogUtil; +import com.doodl6.wechatrobot.service.assistant.AssistantService; +import com.doodl6.wechatrobot.service.assistant.impl.DashscopeService; +import com.doodl6.wechatrobot.service.assistant.impl.KeywordService; +import com.doodl6.wechatrobot.service.assistant.impl.OpenAIService; +import com.doodl6.wechatrobot.service.assistant.impl.TulingService; +import com.doodl6.wechatrobot.util.JsonUtil; +import com.google.common.collect.Lists; import io.vertx.core.Vertx; +import lombok.extern.slf4j.Slf4j; -import java.util.logging.Logger; +import java.util.List; /** * 文本类型消息处理类 */ +@Slf4j public class TextMessageProcessor implements WeChatMessageProcessor { - private static final Logger LOGGER = Logger.getLogger(TextMessageProcessor.class.getName()); - - private final KeywordService keywordService; - - private final OpenAIService openAIService; - - private final TulingService tulingService; + private final List assistantServiceList; public TextMessageProcessor(Vertx vertx, KeywordConfig keywordConfig) { - this.keywordService = new KeywordService(vertx, keywordConfig); - this.openAIService = new OpenAIService(); - this.tulingService = new TulingService(); + this.assistantServiceList = Lists.newLinkedList(); + assistantServiceList.add(new KeywordService(vertx, keywordConfig)); + assistantServiceList.add(new OpenAIService()); + assistantServiceList.add(new DashscopeService()); + assistantServiceList.add(new TulingService()); } @Override @@ -40,23 +41,17 @@ public WeChatMsgType getMsgType() { @Override public BaseMessage processMessage(WeChatMessage weChatMessage) { - LOGGER.info(LogUtil.buildLog("收到用户文本信息", weChatMessage)); + log.info("收到用户文本信息: {}", JsonUtil.objToJson(weChatMessage)); String fromUserName = weChatMessage.getFromUserName(); String toUserName = weChatMessage.getToUserName(); String content = weChatMessage.getContent(); - - //优先查找关键字配置 - BaseMessage message = keywordService.getMessageByKeyword(content); - - //再尝试从GPT获取响应 - if (message == null) { - message = openAIService.getResponse(content); - } - - //再尝试从图灵机器人获取响应 - if (message == null) { - message = tulingService.getResponse(content, fromUserName); + BaseMessage message = null; + for (AssistantService assistantService : assistantServiceList) { + message = assistantService.processText(content, fromUserName); + if (message != null) { + break; + } } //最后返回收到的文本信息作为兜底 diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/service/OpenAIService.java b/robot-web/src/main/java/com/doodl6/wechatrobot/service/OpenAIService.java deleted file mode 100644 index 3f25ff4..0000000 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/service/OpenAIService.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.doodl6.wechatrobot.service; - -import com.doodl6.wechatrobot.response.BaseMessage; -import com.doodl6.wechatrobot.response.TextMessage; -import com.doodl6.wechatrobot.util.AddressUtil; -import com.doodl6.wechatrobot.util.PropertyUtil; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.theokanning.openai.OpenAiApi; -import com.theokanning.openai.completion.chat.ChatCompletionChoice; -import com.theokanning.openai.completion.chat.ChatCompletionRequest; -import com.theokanning.openai.completion.chat.ChatMessage; -import com.theokanning.openai.service.OpenAiService; -import okhttp3.OkHttpClient; -import org.apache.commons.lang3.StringUtils; -import retrofit2.Retrofit; - -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.time.Duration; -import java.util.Collections; -import java.util.List; - -public class OpenAIService { - - private static final String OPENAI_API_KEY = "OPENAI_API_KEY"; - - private static final String OPENAI_BASE_DOMAIN = "OPENAI_BASE_DOMAIN"; - - private static final String OPENAI_SERVER_URL = "OPENAI_SERVER_URL"; - - private static final String OPENAI_PROXY = "OPENAI_PROXY"; - - private static final String MODEL = "gpt-3.5-turbo"; - - private static OpenAiService openAiService; - - public OpenAIService() { - String apiKey = PropertyUtil.getProperty(OPENAI_API_KEY); - if (StringUtils.isBlank(apiKey)) { - return; - } - - String proxyAddress = PropertyUtil.getProperty(OPENAI_PROXY); - OkHttpClient.Builder clientBuilder = OpenAiService.defaultClient(apiKey, Duration.ofSeconds(10)).newBuilder(); - if (StringUtils.isNotBlank(proxyAddress)) { - boolean valid = AddressUtil.validateAddress(proxyAddress); - if (!valid) { - throw new RuntimeException("OPENAI_PROXY is not valid, value:" + proxyAddress); - } - - String[] parts = proxyAddress.split(":"); - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(parts[0], parts.length == 2 ? Integer.parseInt(parts[1]) : 80)); - clientBuilder.proxy(proxy); - } - - OkHttpClient client = clientBuilder.build(); - ObjectMapper mapper = OpenAiService.defaultObjectMapper(); - Retrofit.Builder retrofitBuilder = OpenAiService.defaultRetrofit(client, mapper).newBuilder(); - - String serverUrl = PropertyUtil.getProperty(OPENAI_SERVER_URL); - if (StringUtils.isNotBlank(serverUrl)) { - retrofitBuilder.baseUrl(serverUrl); - } else { - String baseDomain = PropertyUtil.getProperty(OPENAI_BASE_DOMAIN); - if (StringUtils.isNotBlank(baseDomain)) { - boolean valid = AddressUtil.validateAddress(baseDomain); - if (!valid) { - throw new RuntimeException("OPENAI_BASE_DOMAIN is not valid, value:" + baseDomain); - } - - retrofitBuilder.baseUrl("https://" + baseDomain + "/"); - } - } - - Retrofit retrofit = retrofitBuilder.build(); - OpenAiApi api = retrofit.create(OpenAiApi.class); - openAiService = new OpenAiService(api); - } - - /** - * 获取消息响应 - */ - public BaseMessage getResponse(String content) { - if (openAiService == null) { - return null; - } - - ChatMessage chatMessage = new ChatMessage(); - chatMessage.setRole("user"); - chatMessage.setContent(content); - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() - .model(MODEL) - .messages(Collections.singletonList(chatMessage)) - .build(); - List choiceList = openAiService.createChatCompletion(chatCompletionRequest).getChoices(); - choiceList.forEach(System.out::println); - String result = choiceList.get(0).getMessage().getContent(); - return new TextMessage(result); - } -} diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/service/WeChatService.java b/robot-web/src/main/java/com/doodl6/wechatrobot/service/WeChatService.java index a1d998b..3541ece 100644 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/service/WeChatService.java +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/service/WeChatService.java @@ -7,6 +7,7 @@ import com.doodl6.wechatrobot.response.BaseMessage; import com.doodl6.wechatrobot.response.TextMessage; import com.doodl6.wechatrobot.util.XmlUtil; +import lombok.extern.slf4j.Slf4j; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -14,17 +15,14 @@ import java.util.Formatter; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; /** * 微信服务类 */ +@Slf4j public class WeChatService { - private static final Logger LOGGER = Logger.getLogger(WeChatService.class.getName()); - private final WechatConfig wechatConfig; private final Map messageHandleMap; @@ -55,7 +53,7 @@ public boolean checkSignature(String signature, String timestamp, String nonce) tmpStr = byteToHex(digest); return tmpStr.equals(signature); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "校验签名异常", e); + log.error("校验签名异常", e); } return false; } @@ -77,7 +75,7 @@ public BaseMessage processReceived(String message) { resultMessage = weChatMessageHandle.processMessage(weChatMessage); } } catch (Exception e) { - LOGGER.log(Level.SEVERE, "处理来至微信服务器的消息出现错误", e); + log.error("处理来至微信服务器的消息出现错误", e); resultMessage = new TextMessage(toUserName, fromUserName, "我竟无言以对!"); } diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/AssistantService.java b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/AssistantService.java new file mode 100644 index 0000000..c7f3c96 --- /dev/null +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/AssistantService.java @@ -0,0 +1,9 @@ +package com.doodl6.wechatrobot.service.assistant; + +import com.doodl6.wechatrobot.response.BaseMessage; + +public interface AssistantService { + + BaseMessage processText(String content, String fromUserName); + +} diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/DashscopeService.java b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/DashscopeService.java new file mode 100644 index 0000000..906981e --- /dev/null +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/DashscopeService.java @@ -0,0 +1,65 @@ +package com.doodl6.wechatrobot.service.assistant.impl; + +import com.alibaba.dashscope.aigc.generation.Generation; +import com.alibaba.dashscope.aigc.generation.GenerationOutput; +import com.alibaba.dashscope.aigc.generation.GenerationParam; +import com.alibaba.dashscope.aigc.generation.GenerationResult; +import com.alibaba.dashscope.common.Message; +import com.alibaba.dashscope.common.Role; +import com.alibaba.dashscope.exception.NoApiKeyException; +import com.alibaba.dashscope.utils.ApiKey; +import com.doodl6.wechatrobot.response.BaseMessage; +import com.doodl6.wechatrobot.response.TextMessage; +import com.doodl6.wechatrobot.service.assistant.AssistantService; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class DashscopeService implements AssistantService { + + private boolean enable; + + public DashscopeService() { + try { + ApiKey.getApiKey(null); + this.enable = true; + } catch (NoApiKeyException ignore) { + } + } + + @Override + public BaseMessage processText(String content, String fromUserName) { + if (!enable) { + return null; + } + + try { + Generation gen = new Generation(); + Message systemMsg = Message.builder().role(Role.SYSTEM.getValue()).content("你是一个AI助手,保持回复内容尽量简短").build(); + Message userMsg = Message.builder().role(Role.USER.getValue()).content(content).build(); + GenerationParam param = + GenerationParam.builder().model(Generation.Models.QWEN_TURBO).messages(Lists.newArrayList(systemMsg,userMsg)) + .resultFormat(GenerationParam.ResultFormat.TEXT) + .build(); + GenerationResult result = gen.call(param); + String responseText = result.getOutput().getText(); + if (responseText == null) { + List choiceList = result.getOutput().getChoices(); + if (choiceList != null && !choiceList.isEmpty()) { + responseText = choiceList.get(0).getMessage().getContent(); + } + } + System.out.println("responseText=" + responseText); + + if (responseText != null) { + return new TextMessage(responseText); + } + } catch (Exception e) { + log.error("调用通义千问接口异常", e); + } + + return null; + } +} diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/service/KeywordService.java b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/KeywordService.java similarity index 89% rename from robot-web/src/main/java/com/doodl6/wechatrobot/service/KeywordService.java rename to robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/KeywordService.java index 2e44ccd..6a8acc2 100644 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/service/KeywordService.java +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/KeywordService.java @@ -1,14 +1,16 @@ -package com.doodl6.wechatrobot.service; +package com.doodl6.wechatrobot.service.assistant.impl; import com.doodl6.wechatrobot.config.KeywordConfig; import com.doodl6.wechatrobot.response.BaseMessage; import com.doodl6.wechatrobot.response.NewsMessage; import com.doodl6.wechatrobot.response.TextMessage; +import com.doodl6.wechatrobot.service.assistant.AssistantService; import com.doodl6.wechatrobot.util.HttpUtil; import com.doodl6.wechatrobot.util.JsonUtil; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Maps; import io.vertx.core.Vertx; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.nio.charset.StandardCharsets; @@ -19,15 +21,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; /** * 关键字服务类 */ -public class KeywordService { - - private static final Logger LOGGER = Logger.getLogger(KeywordService.class.getName()); +@Slf4j +public class KeywordService implements AssistantService { /** * 当前配置的版本 @@ -62,7 +61,8 @@ public KeywordService(Vertx vertx, KeywordConfig keywordConfig) { return thread; }); - public BaseMessage getMessageByKeyword(String keyword) { + @Override + public BaseMessage processText(String keyword, String fromUserName) { JsonNode messageJsonNode = keywordMessageMap.get(keyword); if (messageJsonNode == null) { return null; @@ -108,7 +108,7 @@ private synchronized void loadByHttp(String versionLocation, String messageLocat replaceKeywordMessageMap(newVersion, messageConfigStr); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "根据Http请求获取关键字配置信息异常", e); + log.error("根据Http请求获取关键字配置信息异常", e); } } @@ -121,7 +121,7 @@ private static void replaceKeywordMessageMap(String newVersion, String messageCo String keyword = entry.getKey(); JsonNode messageJsonNode = entry.getValue(); newKeywordMessageMap.put(keyword, messageJsonNode); - LOGGER.log(Level.INFO,"初始化关键字map,{0} : {1}", new Object[]{keyword, messageJsonNode.toString()}); + log.info("初始化关键字map,{} : {}", keyword, messageJsonNode.toString()); } currentConfigVersion = newVersion; keywordMessageMap = newKeywordMessageMap; diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/OpenAIService.java b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/OpenAIService.java new file mode 100644 index 0000000..8d6e1af --- /dev/null +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/OpenAIService.java @@ -0,0 +1,102 @@ +package com.doodl6.wechatrobot.service.assistant.impl; + +import com.doodl6.wechatrobot.response.BaseMessage; +import com.doodl6.wechatrobot.response.TextMessage; +import com.doodl6.wechatrobot.service.assistant.AssistantService; +import com.doodl6.wechatrobot.util.AddressUtil; +import com.doodl6.wechatrobot.util.PropertyUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.theokanning.openai.OpenAiApi; +import com.theokanning.openai.completion.chat.ChatCompletionChoice; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatMessage; +import com.theokanning.openai.service.OpenAiService; +import okhttp3.OkHttpClient; +import org.apache.commons.lang3.StringUtils; +import retrofit2.Retrofit; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.time.Duration; +import java.util.Collections; +import java.util.List; + +public class OpenAIService implements AssistantService { + + private static final String OPENAI_API_KEY = "OPENAI_API_KEY"; + + private static final String OPENAI_BASE_DOMAIN = "OPENAI_BASE_DOMAIN"; + + private static final String OPENAI_SERVER_URL = "OPENAI_SERVER_URL"; + + private static final String OPENAI_PROXY = "OPENAI_PROXY"; + + private static final String MODEL = "gpt-3.5-turbo"; + + private static OpenAiService openAiService; + + public OpenAIService() { + String apiKey = PropertyUtil.getProperty(OPENAI_API_KEY); + if (StringUtils.isBlank(apiKey)) { + return; + } + + String proxyAddress = PropertyUtil.getProperty(OPENAI_PROXY); + OkHttpClient.Builder clientBuilder = OpenAiService.defaultClient(apiKey, Duration.ofSeconds(10)).newBuilder(); + if (StringUtils.isNotBlank(proxyAddress)) { + boolean valid = AddressUtil.validateAddress(proxyAddress); + if (!valid) { + throw new RuntimeException("OPENAI_PROXY is not valid, value:" + proxyAddress); + } + + String[] parts = proxyAddress.split(":"); + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(parts[0], parts.length == 2 ? Integer.parseInt(parts[1]) : 80)); + clientBuilder.proxy(proxy); + } + + OkHttpClient client = clientBuilder.build(); + ObjectMapper mapper = OpenAiService.defaultObjectMapper(); + Retrofit.Builder retrofitBuilder = OpenAiService.defaultRetrofit(client, mapper).newBuilder(); + + String serverUrl = PropertyUtil.getProperty(OPENAI_SERVER_URL); + if (StringUtils.isNotBlank(serverUrl)) { + retrofitBuilder.baseUrl(serverUrl); + } else { + String baseDomain = PropertyUtil.getProperty(OPENAI_BASE_DOMAIN); + if (StringUtils.isNotBlank(baseDomain)) { + boolean valid = AddressUtil.validateAddress(baseDomain); + if (!valid) { + throw new RuntimeException("OPENAI_BASE_DOMAIN is not valid, value:" + baseDomain); + } + + retrofitBuilder.baseUrl("https://" + baseDomain + "/"); + } + } + + Retrofit retrofit = retrofitBuilder.build(); + OpenAiApi api = retrofit.create(OpenAiApi.class); + openAiService = new OpenAiService(api); + } + + /** + * 获取消息响应 + */ + @Override + public BaseMessage processText(String content, String fromUserName) { + if (openAiService == null) { + return null; + } + + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setRole("user"); + chatMessage.setContent(content); + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() + .model(MODEL) + .messages(Collections.singletonList(chatMessage)) + .build(); + List choiceList = openAiService.createChatCompletion(chatCompletionRequest).getChoices(); + choiceList.forEach(System.out::println); + String result = choiceList.get(0).getMessage().getContent(); + return new TextMessage(result); + } +} diff --git a/robot-web/src/main/java/com/doodl6/wechatrobot/service/TulingService.java b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/TulingService.java similarity index 92% rename from robot-web/src/main/java/com/doodl6/wechatrobot/service/TulingService.java rename to robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/TulingService.java index 914cff8..4f8bb6f 100644 --- a/robot-web/src/main/java/com/doodl6/wechatrobot/service/TulingService.java +++ b/robot-web/src/main/java/com/doodl6/wechatrobot/service/assistant/impl/TulingService.java @@ -1,9 +1,10 @@ -package com.doodl6.wechatrobot.service; +package com.doodl6.wechatrobot.service.assistant.impl; import com.doodl6.wechatrobot.constant.TulingConstants; import com.doodl6.wechatrobot.domain.TulingTextReq; import com.doodl6.wechatrobot.response.BaseMessage; import com.doodl6.wechatrobot.response.TextMessage; +import com.doodl6.wechatrobot.service.assistant.AssistantService; import com.doodl6.wechatrobot.util.HttpUtil; import com.doodl6.wechatrobot.util.JsonUtil; import com.fasterxml.jackson.databind.JsonNode; @@ -16,7 +17,7 @@ /** * 图灵服务类 */ -public class TulingService { +public class TulingService implements AssistantService { private static final String API_URL = "http://openapi.turingapi.com/openapi/api/v2"; @@ -34,7 +35,8 @@ public TulingService() { /** * 获取消息响应 */ - public BaseMessage getResponse(String content, String fromUserName) { + @Override + public BaseMessage processText(String content, String fromUserName) { if (StringUtils.isBlank(API_KEY)) { return null; } diff --git a/robot-web/src/main/resources/simplelogger.properties b/robot-web/src/main/resources/simplelogger.properties new file mode 100644 index 0000000..397e376 --- /dev/null +++ b/robot-web/src/main/resources/simplelogger.properties @@ -0,0 +1,14 @@ +# \u5168\u5C40\u65E5\u5FD7\u9ED8\u8BA4\u7EA7\u522B +org.slf4j.simpleLogger.defaultLogLevel=INFO +# \u663E\u793A\u65F6\u95F4 +org.slf4j.simpleLogger.showDateTime=true +# \u65F6\u95F4\u683C\u5F0F +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss +# \u7EBF\u7A0B\u540D + org.slf4j.simpleLogger.showThreadName=true +# \u5305\u8DEF\u5F84 +org.slf4j.simpleLogger.showLogName=true +# \u77ED\u7684\u5305\u8DEF\u5F84 +org.slf4j.simpleLogger.showShortLogName=true +# \u8F93\u51FA\u65B9\u5F0F +org.slf4j.simpleLogger.logFile=System.out \ No newline at end of file