diff --git a/README.md b/README.md index 276a666..0bc4fd2 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,28 @@ 运行环境:Java8,maven - ChatGPT接入企业微信成为聊天机器人 -- 羊了个羊无限通关,通关教程在公众号**卷福同学** 最新文章里,所用工具包在公众号里发送`羊了个羊`获取 +- 微信客服接入ChatGPT - 有脚本运行问题可公众号内询问 ![公众号二维码](https://raw.githubusercontent.com/longbig/multi_function_github/main/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7.png) ## 脚本使用 -### ChatGPT 3.5接入企业微信 +### 微信客服接入ChatGPT,外部群可用 +- 可在企业微信外部群使用,非企业人员也能用,只支持私聊 +- 使用教程:[【奶奶看了也不会】微信群聊(微信客服)接入ChatGPT教程](https://longbig.github.io/2023/06/05/%E5%BE%AE%E4%BF%A1%E5%AE%A2%E6%9C%8D%E6%8E%A5%E5%85%A5ChatGPT%E6%95%99%E7%A8%8B-%E5%A4%96%E9%83%A8%E7%BE%A4%E8%81%8A%E5%8F%AF%E7%94%A8/) + + +### ChatGPT 3.5 / 4接入企业微信 - 修改application.properties文件的chatgpt开头 和wechat开头的配置,配置内容看注释 - 默认访问超时时间是30s,如需修改,可自行修改`OkHttpUtils`中`DEFAULT_TIME_OUT`的值 - 使用教程:[ChatGPT3.5接入企业微信且支持连续对话](https://longbig.github.io/2023/03/05/ChatGPT3-5%E6%8E%A5%E5%85%A5%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E4%B8%94%E6%94%AF%E6%8C%81%E8%BF%9E%E7%BB%AD%E5%AF%B9%E8%AF%9D/) - 发送`开始连续对话`进入连续对话模式,注意连续对话模式下,chatGPT账号额度消耗是累加(每次发消息,会累加这次和过去所有对话的额度消耗) - 发送`结束连续对话`关闭连续对话模式 +- 发送`开始GPT4`使用GPT4模型,发送`结束GPT4`关闭GPT4模型 + - 可修改application.properties文件里的`chatgpt.flow.num`配置,自行修改最大对话次数(不建议太大,官方限制的消息最大长度是4096 token,大概20次对话之后就会超了) ### 其他脚本使用 -- 羊了个羊:代码在YangService下,启动程序后,浏览器访问`http://localhost:8080/doc.html#/default/yang-service/getYangUsingGET`, - 在参数cookie里写入t=【替换为抓包工具抓到的t值】,直接请求即可,抓包教程见公众号最新文章 - 京东脚本:多个账号使用时,修改resources目录下的jd_cookie.txt文件,每行为pt_key,pt_pin的格式 - 掘金脚本:修改application.properties文件的juejin.Cookie为你的掘金cookie @@ -40,6 +45,8 @@ - 运行`docker-compose up`运行上一步构建的镜像即可 ## 更新 +- 2023年06月04日 增加,微信客服对接ChatGPT的功能,支持企业外部微信群使用 +- 2023年05月06日 更新,加上GPT4模型对话,修复ChatGPT消息长度过长,被微信截断的bug - 2023年03月05日 更新,使用GPT3.5接入企业微信,且新增连续对话功能 - 2023年02月27日 修复ChatGPT接入企业微信未设置超时时间的bug - 2023年02月22日 修复ChatGPT接入企业微信agentId的bug diff --git a/pom.xml b/pom.xml index 191914c..7fd7dad 100644 --- a/pom.xml +++ b/pom.xml @@ -162,6 +162,13 @@ 1.10 + + dom4j + dom4j + 1.6.1 + + + diff --git a/src/main/java/com/longbig/multifunction/api/ChatGPTController.java b/src/main/java/com/longbig/multifunction/api/ChatGPTController.java index 652c62a..8222913 100644 --- a/src/main/java/com/longbig/multifunction/api/ChatGPTController.java +++ b/src/main/java/com/longbig/multifunction/api/ChatGPTController.java @@ -3,16 +3,22 @@ import com.longbig.multifunction.config.BaseConfig; import com.longbig.multifunction.config.BaseConstant; import com.longbig.multifunction.dto.WechatXmlDTO; +import com.longbig.multifunction.model.wechat.aes.AesException; import com.longbig.multifunction.model.wechat.aes.WXBizMsgCrypt; +import com.longbig.multifunction.model.wechat.kf.*; import com.longbig.multifunction.service.ChatGptService; import com.longbig.multifunction.service.WeChatService; import com.longbig.multifunction.utils.CacheHelper; +import com.longbig.multifunction.utils.XmlHelper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -36,6 +42,22 @@ public class ChatGPTController { private Executor executor = Executors.newCachedThreadPool(); + @GetMapping("/chatGpt") + public String chatGpt(@RequestParam("text") String text) throws Exception { + log.info("chatGpt, text:{}", text); + String result = chatGptService.openAiComplete(text); + return result; + } + + /** + * 企业微信三方应用验证消息接口 + * @param msg_signature + * @param timestamp + * @param nonce + * @param echostr + * @return + * @throws Exception + */ @GetMapping("/receiveMsgFromWechat") public String receiveMsgFromDd(@RequestParam("msg_signature") String msg_signature, @RequestParam("timestamp") String timestamp, @@ -43,24 +65,19 @@ public String receiveMsgFromDd(@RequestParam("msg_signature") String msg_signatu @RequestParam("echostr") String echostr) throws Exception { log.info("receiveMsgFromDd, msg_signature:{}, timestamp:{}, nonce:{}, echostr:{}", msg_signature, timestamp, nonce, echostr); - WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(baseConfig.getSToken(), baseConfig.getSEncodingAESKey(), - baseConfig.getSCorpID()); - - String sEchoStr = null; - try { - sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp, - nonce, echostr); - log.info("verifyurl echostr: " + sEchoStr); - // 验证URL成功,将sEchoStr返回 - return sEchoStr; - } catch (Exception e) { - //验证URL失败,错误原因请查看异常 - log.error("verifyurl error,e={}", e); - return ""; - } + return validateMessage(msg_signature, timestamp, nonce, echostr); } + /** + * 企业微信三方应用接收消息接口 + * @param msg_signature + * @param timestamp + * @param nonce + * @param body + * @return + * @throws Exception + */ @PostMapping(value = "/receiveMsgFromWechat", consumes = {"application/xml", "text/xml"}, produces = "application/xml;charset=utf-8") @@ -82,9 +99,9 @@ public String receiveMsgFromDd(@RequestParam("msg_signature") String msg_signatu @Override public void run() { String fromUser = StringUtils.substringBetween(xmlcontent, ""); - //是否开启连续对话 - if (BaseConstant.CHAT_FLOW_OPEN.equals(data) || BaseConstant.CHAT_FLOW_CLOSE.equals(data)) { - ChatFlowhandler(data, fromUser); + //是否开启连续对话,GPT4 + if (BaseConstant.isInChatArray(data)) { + ChatFlowhandler(data, fromUser, null); return; } // 调openai @@ -101,21 +118,134 @@ public void run() { } } - private void ChatFlowhandler(String data, String fromUser) { + + /** + * 微信客服API验证消息接口 + * @param msg_signature + * @param timestamp + * @param nonce + * @param echostr + * @return + * @throws Exception + */ + @GetMapping("/receiveMsgFromWechatKf") + public String receiveMsgFromWechatKf(@RequestParam("msg_signature") String msg_signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("echostr") String echostr) throws Exception { + log.info("receiveMsgFromWechatKf, msg_signature:{}, timestamp:{}, nonce:{}, echostr:{}", + msg_signature, timestamp, nonce, echostr); + return validateMessage(msg_signature, timestamp, nonce, echostr); + } + + + /** + * 微信客服API接收消息接口 + * @param msg_signature + * @param timestamp + * @param nonce + * @param body + * @return + * @throws Exception + */ + @PostMapping(value = "/receiveMsgFromWechatKf", + consumes = {"application/xml", "text/xml"}, + produces = "application/xml;charset=utf-8") + public String receiveMsgFromWechatKf(@RequestParam("msg_signature") String msg_signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestBody WechatXmlDTO body) throws Exception { + WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(baseConfig.getSToken(), baseConfig.getSEncodingAESKey(), + baseConfig.getSCorpID()); + + String sEchoStr = null; + try { + String msg = body.getEncrypt(); + String xmlcontent = wxcpt.decrypt(msg); + log.info("xml content msg: " + xmlcontent); + KefuNoticeDTO kefuNoticeDTO = XmlHelper.parseXmlToObject(xmlcontent, KefuNoticeDTO.class); + log.info("kefuDataDTO:{}", kefuNoticeDTO); + KefuHandleDTO kefuHandleDTO = weChatService.readKfReceiveMsg(kefuNoticeDTO); + + executor.execute(new Runnable() { + @SneakyThrows + @Override + public void run() { + String data = kefuHandleDTO.getData(); + String fromUser = kefuHandleDTO.getFromUser(); + + //是否开启连续对话,GPT4 + if (BaseConstant.isInChatArray(data)) { + ChatFlowhandler(data, fromUser, kefuHandleDTO); + return; + } + // 调openai + String result = chatGptService.gptNewComplete(data, fromUser); + kefuHandleDTO.setChatGptData(result); + //给微信客服发消息 + String send = weChatService.sendKfMsg(kefuHandleDTO); + } + }); + return kefuHandleDTO.getData(); + } catch (Exception e) { + //验证URL失败,错误原因请查看异常 + log.error("DecryptMsg msg error,e={}", e); + return ""; + } + } + + /** + * + * @param data + * @param fromUser + * @param kefuHandleDTO 是否客服消息通道 + */ + private void ChatFlowhandler(String data, String fromUser, KefuHandleDTO kefuHandleDTO) { String result = ""; if (BaseConstant.CHAT_FLOW_OPEN.equals(data)) { CacheHelper.setUserChatFlowOpen(fromUser); - result = "连续对话开启,有效期10分钟,连续对话超过" + baseConfig.getChatGptFlowNum() + "次后自动关闭"; + result = "连续对话开启,有效期30分钟,连续对话超过" + baseConfig.getChatGptFlowNum() + "次后自动关闭"; } else if (BaseConstant.CHAT_FLOW_CLOSE.equals(data)) { CacheHelper.setUserChatFlowClose(fromUser); result = "连续对话关闭"; + } else if (BaseConstant.CHAT_GPT_4_OPEN.equals(data)) { + CacheHelper.setUserChatGpt4Open(fromUser); + result = "GPT4对话开启,有效期3小时"; + } else if (BaseConstant.CHAT_GPT_4_CLOSE.equals(data)) { + CacheHelper.setUserChatGpt4Close(fromUser); + result = "GPT4对话关闭"; } try { - String send = weChatService.sendMsg(result, fromUser); + if (Objects.isNull(kefuHandleDTO)) { + String send = weChatService.sendMsg(result, fromUser); + } else { + kefuHandleDTO.setChatGptData(result); + //给微信客服发消息 + String send = weChatService.sendKfMsg(kefuHandleDTO); + } } catch (Exception e) { log.error("weChatService.sendMsg error,e={}", e); } } + + private String validateMessage(String msg_signature, String timestamp, String nonce, String echostr) + throws AesException { + WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(baseConfig.getSToken(), baseConfig.getSEncodingAESKey(), + baseConfig.getSCorpID()); + + String sEchoStr = null; + try { + sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp, + nonce, echostr); + log.info("validateMessage echostr: " + sEchoStr); + // 验证URL成功,将sEchoStr返回 + return sEchoStr; + } catch (Exception e) { + //验证URL失败,错误原因请查看异常 + log.error("validateMessage error,e={}", e); + return ""; + } + } } diff --git a/src/main/java/com/longbig/multifunction/config/BaseConfig.java b/src/main/java/com/longbig/multifunction/config/BaseConfig.java index 709ef6b..58ef412 100644 --- a/src/main/java/com/longbig/multifunction/config/BaseConfig.java +++ b/src/main/java/com/longbig/multifunction/config/BaseConfig.java @@ -32,4 +32,7 @@ public class BaseConfig { @Value("${chatgpt.flow.num}") private Integer chatGptFlowNum; + @Value("${wechat.kfsecret}") + private String kfsecret; + } diff --git a/src/main/java/com/longbig/multifunction/config/BaseConstant.java b/src/main/java/com/longbig/multifunction/config/BaseConstant.java index 861b9ea..ab49778 100644 --- a/src/main/java/com/longbig/multifunction/config/BaseConstant.java +++ b/src/main/java/com/longbig/multifunction/config/BaseConstant.java @@ -1,5 +1,9 @@ package com.longbig.multifunction.config; +import com.google.common.collect.Lists; + +import java.util.List; + /** * @author yuyunlong * @date 2023年3月5日 10:26 @@ -11,4 +15,23 @@ public class BaseConstant { public static final String CHAT_FLOW_CLOSE = "结束连续对话"; + public static final String CHAT_GPT_4_OPEN = "开始GPT4"; + + public static final String CHAT_GPT_4_CLOSE = "结束GPT4"; + + public static List chatArrayList = Lists.newArrayList(); + + static { + chatArrayList.add(CHAT_FLOW_CLOSE); + chatArrayList.add(CHAT_FLOW_OPEN); + chatArrayList.add(CHAT_GPT_4_OPEN); + chatArrayList.add(CHAT_GPT_4_CLOSE); + } + + public static Boolean isInChatArray(String message) { + return chatArrayList.contains(message); + } + + + } diff --git a/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuDataDTO.java b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuDataDTO.java new file mode 100644 index 0000000..83542e0 --- /dev/null +++ b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuDataDTO.java @@ -0,0 +1,59 @@ +package com.longbig.multifunction.model.wechat.kf; + +import lombok.ToString; + +import java.util.List; + +/** + * @author yuyunlong + * @date 2023年6月3日 21:48 + * @description 微信客服读取接收消息文档:https://developer.work.weixin.qq.com/document/path/94670 + */ +@ToString +public class KefuDataDTO { + private Integer errcode; + private String errmsg; + private String next_cursor; + private Integer has_more; + private List msg_list; + + public Integer getErrcode() { + return errcode; + } + + public void setErrcode(Integer errcode) { + this.errcode = errcode; + } + + public String getErrmsg() { + return errmsg; + } + + public void setErrmsg(String errmsg) { + this.errmsg = errmsg; + } + + public String getNext_cursor() { + return next_cursor; + } + + public void setNext_cursor(String next_cursor) { + this.next_cursor = next_cursor; + } + + public Integer getHas_more() { + return has_more; + } + + public void setHas_more(Integer has_more) { + this.has_more = has_more; + } + + public List getMsg_list() { + return msg_list; + } + + public void setMsg_list(List msg_list) { + this.msg_list = msg_list; + } +} diff --git a/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuDataMsgDTO.java b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuDataMsgDTO.java new file mode 100644 index 0000000..181fe9b --- /dev/null +++ b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuDataMsgDTO.java @@ -0,0 +1,87 @@ +package com.longbig.multifunction.model.wechat.kf; + +import lombok.ToString; + +/** + * @author yuyunlong + * @date 2023年6月3日 21:50 + * @description + */ +@ToString +public class KefuDataMsgDTO { + /** + * 字段文档:https://developer.work.weixin.qq.com/document/path/94670 + */ + private String msgid; + private String open_kfid; + private String external_userid; + private Long send_time; + private Integer origin; + private String servicer_userid; + private String msgtype; + private KefuTextDTO text; + + public String getMsgid() { + return msgid; + } + + public void setMsgid(String msgid) { + this.msgid = msgid; + } + + public String getOpen_kfid() { + return open_kfid; + } + + public void setOpen_kfid(String open_kfid) { + this.open_kfid = open_kfid; + } + + public String getExternal_userid() { + return external_userid; + } + + public void setExternal_userid(String external_userid) { + this.external_userid = external_userid; + } + + public Long getSend_time() { + return send_time; + } + + public void setSend_time(Long send_time) { + this.send_time = send_time; + } + + public Integer getOrigin() { + return origin; + } + + public void setOrigin(Integer origin) { + this.origin = origin; + } + + public String getServicer_userid() { + return servicer_userid; + } + + public void setServicer_userid(String servicer_userid) { + this.servicer_userid = servicer_userid; + } + + public String getMsgtype() { + return msgtype; + } + + public void setMsgtype(String msgtype) { + this.msgtype = msgtype; + } + + public KefuTextDTO getText() { + return text; + } + + public void setText(KefuTextDTO text) { + this.text = text; + } +} diff --git a/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuHandleDTO.java b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuHandleDTO.java new file mode 100644 index 0000000..c57e9c2 --- /dev/null +++ b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuHandleDTO.java @@ -0,0 +1,48 @@ +package com.longbig.multifunction.model.wechat.kf; + +import lombok.ToString; + +/** + * @author yuyunlong + * @date 2023年6月4日 00:23 + * @description + */ +@ToString +public class KefuHandleDTO { + private String data; + private String fromUser; + private String openKfid; + private String chatGptData; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getFromUser() { + return fromUser; + } + + public void setFromUser(String fromUser) { + this.fromUser = fromUser; + } + + public String getOpenKfid() { + return openKfid; + } + + public void setOpenKfid(String openKfid) { + this.openKfid = openKfid; + } + + public String getChatGptData() { + return chatGptData; + } + + public void setChatGptData(String chatGptData) { + this.chatGptData = chatGptData; + } +} diff --git a/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuNoticeDTO.java b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuNoticeDTO.java new file mode 100644 index 0000000..5815bf4 --- /dev/null +++ b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuNoticeDTO.java @@ -0,0 +1,66 @@ +package com.longbig.multifunction.model.wechat.kf; + +import lombok.ToString; + +/** + * @author yuyunlong + * @date 2023年6月3日 20:58 + * @description 微信客服接收消息触发的xml data + */ +@ToString +public class KefuNoticeDTO { + private String ToUserName; + private String CreateTime; + private String MsgType; + private String Event; + private String Token; + private String OpenKfId; + + public String getToUserName() { + return ToUserName; + } + + public void setToUserName(String toUserName) { + ToUserName = toUserName; + } + + public String getCreateTime() { + return CreateTime; + } + + public void setCreateTime(String createTime) { + CreateTime = createTime; + } + + public String getMsgType() { + return MsgType; + } + + public void setMsgType(String msgType) { + MsgType = msgType; + } + + public String getEvent() { + return Event; + } + + public void setEvent(String event) { + Event = event; + } + + public String getToken() { + return Token; + } + + public void setToken(String token) { + Token = token; + } + + public String getOpenKfId() { + return OpenKfId; + } + + public void setOpenKfId(String openKfId) { + OpenKfId = openKfId; + } +} diff --git a/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuSendTextDTO.java b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuSendTextDTO.java new file mode 100644 index 0000000..512ec8d --- /dev/null +++ b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuSendTextDTO.java @@ -0,0 +1,60 @@ +package com.longbig.multifunction.model.wechat.kf; + +import lombok.ToString; + +/** + * @author yuyunlong + * @date 2023年6月4日 00:31 + * @description 字段说明:https://developer.work.weixin.qq.com/document/path/94677#%E6%96%87%E6%9C%AC%E6%B6%88%E6%81%AF + */ +@ToString +public class KefuSendTextDTO { + /** + * 字段文档:https://developer.work.weixin.qq.com/document/path/94677#文本消息 + */ + private String touser; + private String open_kfid; + private String msgid; + private String msgtype = "text"; + private TextContentDTO text; + + public String getTouser() { + return touser; + } + + public void setTouser(String touser) { + this.touser = touser; + } + + public String getOpen_kfid() { + return open_kfid; + } + + public void setOpen_kfid(String open_kfid) { + this.open_kfid = open_kfid; + } + + public String getMsgid() { + return msgid; + } + + public void setMsgid(String msgid) { + this.msgid = msgid; + } + + public String getMsgtype() { + return msgtype; + } + + public void setMsgtype(String msgtype) { + this.msgtype = msgtype; + } + + public TextContentDTO getText() { + return text; + } + + public void setText(TextContentDTO text) { + this.text = text; + } +} diff --git a/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuTextDTO.java b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuTextDTO.java new file mode 100644 index 0000000..958c206 --- /dev/null +++ b/src/main/java/com/longbig/multifunction/model/wechat/kf/KefuTextDTO.java @@ -0,0 +1,30 @@ +package com.longbig.multifunction.model.wechat.kf; + +import lombok.ToString; + +/** + * @author yuyunlong + * @date 2023年6月4日 00:02 + * @description + */ +@ToString +public class KefuTextDTO { + private String content; + private String menu_id; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getMenu_id() { + return menu_id; + } + + public void setMenu_id(String menu_id) { + this.menu_id = menu_id; + } +} diff --git a/src/main/java/com/longbig/multifunction/model/wechat/kf/TextContentDTO.java b/src/main/java/com/longbig/multifunction/model/wechat/kf/TextContentDTO.java new file mode 100644 index 0000000..4332915 --- /dev/null +++ b/src/main/java/com/longbig/multifunction/model/wechat/kf/TextContentDTO.java @@ -0,0 +1,21 @@ +package com.longbig.multifunction.model.wechat.kf; + +import lombok.ToString; + +/** + * @author yuyunlong + * @date 2023年6月4日 00:32 + * @description + */ +@ToString +public class TextContentDTO { + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/com/longbig/multifunction/service/ChatGptService.java b/src/main/java/com/longbig/multifunction/service/ChatGptService.java index 496a00b..f4da8f5 100644 --- a/src/main/java/com/longbig/multifunction/service/ChatGptService.java +++ b/src/main/java/com/longbig/multifunction/service/ChatGptService.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; /** * @author yuyunlong @@ -70,7 +71,6 @@ public String openAiComplete(String text) throws Exception { * @return */ public String gptNewComplete(String text, String fromUser) { - log.info("调用GPT3.5模型对话,text:{}", text); Map header = Maps.newHashMap(); String drawUrl = "https://api.openai.com/v1/chat/completions"; String cookie = ""; @@ -84,9 +84,14 @@ public String gptNewComplete(String text, String fromUser) { gptMessageDto.setRole("user"); gptMessageDto.setContent(text); msgs.add(gptMessageDto); - body.put("model", "gpt-3.5-turbo"); + if (CacheHelper.getUserChatGpt4Switch(fromUser)) { + body.put("model", "gpt-4"); + log.info("调用GPT4模型对话,text:{}", text); + } else { + body.put("model", "gpt-3.5-turbo"); + log.info("调用GPT3.5模型对话,text:{}", text); + } body.put("messages", msgs); - body.put("max_tokens", 1024); body.put("temperature", 1); MediaType JSON1 = MediaType.parse("application/json;charset=utf-8"); RequestBody requestBody = RequestBody.create(JSON1, JSON.toJSONString(body)); @@ -102,6 +107,11 @@ public String gptNewComplete(String text, String fromUser) { return "访问超时"; } JSONObject jsonObject = JSONObject.parseObject(response); + JSONObject errorJsonObject = (JSONObject) jsonObject.get("error"); + if (Objects.nonNull(errorJsonObject)) { + String errorMsg = (String) errorJsonObject.get("message"); + return errorMsg; + } JSONArray jsonArray = jsonObject.getJSONArray("choices"); JSONObject jsonObject1 = (JSONObject) jsonArray.get(0); JSONObject jsonObject2 = (JSONObject) jsonObject1.get("message"); diff --git a/src/main/java/com/longbig/multifunction/service/WeChatService.java b/src/main/java/com/longbig/multifunction/service/WeChatService.java index 475ee47..d9454b1 100644 --- a/src/main/java/com/longbig/multifunction/service/WeChatService.java +++ b/src/main/java/com/longbig/multifunction/service/WeChatService.java @@ -1,9 +1,13 @@ package com.longbig.multifunction.service; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.longbig.multifunction.config.BaseConfig; +import com.longbig.multifunction.model.wechat.kf.*; import com.longbig.multifunction.utils.CacheHelper; +import com.longbig.multifunction.utils.JsonHelper; import com.longbig.multifunction.utils.OkHttpUtils; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; @@ -11,6 +15,10 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Map; /** * @author yuyunlong @@ -24,53 +32,172 @@ public class WeChatService { @Autowired private BaseConfig baseConfig; - private String WECHAT_TOKEN = "WECHAT_TOKEN"; + /** + * 企业微信自建三方应用accessToken名 + */ + private String WECHAT_TOKEN = "WECHAT_ROBOT_TOKEN"; + /** + * 微信客服accessToken名 + */ + private String KF_TOKEN = "KF_TOKEN"; + + private String cursorKey = "cursor"; - public String getAccessToken() throws Exception { - String data = CacheHelper.get(WECHAT_TOKEN); + private String getAccessToken(String tokenName, String corpsecret) throws Exception { + + String data = CacheHelper.get(tokenName); if (StringUtils.isNotEmpty(data)) { - log.info("cache data:{}", data); +// log.info("cache data:{}", data); return data; } String corpid = baseConfig.getSCorpID(); - String corpsecret = baseConfig.getCorpsecret(); String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + corpid + "&corpsecret=" + corpsecret; String jsonData = OkHttpUtils.get(url); JSONObject jsonObject = JSONObject.parseObject(jsonData); String accessToken = jsonObject.getString("access_token"); - CacheHelper.set(WECHAT_TOKEN, accessToken); + CacheHelper.set(tokenName, accessToken); return accessToken; } public String sendMsg(String msg, String touser) throws Exception { String accessToken = null; + List msgList = Lists.newArrayList(); try { - accessToken = getAccessToken(); + String corpsecret = baseConfig.getCorpsecret(); + accessToken = getAccessToken(WECHAT_TOKEN, corpsecret); } catch (Exception e) { log.error("sendMsg getAccessToken error,e={}", e); return "fail"; } String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; - String body = "{\n" + - " \"touser\" : \"" + touser + "\",\n" + - " \"msgtype\" : \"text\",\n" + - " \"agentid\" : " + baseConfig.getAgentId() + ",\n" + - " \"text\" : {\n" + - " \"content\" : \"" + msg + "\"\n" + - " },\n" + - " \"safe\":0,\n" + - " \"enable_id_trans\": 0,\n" + - " \"enable_duplicate_check\": 0,\n" + - " \"duplicate_check_interval\": 1800\n" + - "}"; - MediaType JSON1 = MediaType.parse("application/json;charset=utf-8"); - RequestBody requestBody = RequestBody.create(JSON1, body); - log.info("send msg:{}", requestBody); - OkHttpUtils.post(url, "", requestBody, Maps.newHashMap()); + if (msg.length()> 2048) { + int count = msg.length() / 2048 + 1; + int beginIndex = 0, endIndex = 2048; + for (int i = 0; i < count; i++) { + String temp = msg.substring(beginIndex, endIndex); + msgList.add(temp); + beginIndex = endIndex; + endIndex += 2048; + endIndex = endIndex> msg.length() ? msg.length() : endIndex; + } + } else { + msgList.add(msg); + } + + for (String s : msgList) { + String body = "{\n" + + " \"touser\" : \"" + touser + "\",\n" + + " \"msgtype\" : \"text\",\n" + + " \"agentid\" : " + baseConfig.getAgentId() + ",\n" + + " \"text\" : {\n" + + " \"content\" : \"" + s + "\"\n" + + " },\n" + + " \"safe\":0,\n" + + " \"enable_id_trans\": 0,\n" + + " \"enable_duplicate_check\": 0,\n" + + " \"duplicate_check_interval\": 1800\n" + + "}"; + MediaType JSON1 = MediaType.parse("application/json;charset=utf-8"); + RequestBody requestBody = RequestBody.create(JSON1, body); + log.info("send msg:{}", requestBody); + OkHttpUtils.post(url, "", requestBody, Maps.newHashMap()); + } return "success;"; } + public String sendKfMsg(KefuHandleDTO kefuHandleDTO) throws Exception { + String accessToken = null; + List msgList = Lists.newArrayList(); + try { + String corpsecret = baseConfig.getKfsecret(); + accessToken = getAccessToken(KF_TOKEN, corpsecret); + } catch (Exception e) { + log.error("sendKfMsg getAccessToken error,e={}", e); + return "fail"; + } + String url = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=" + accessToken; + String msg = kefuHandleDTO.getChatGptData(); + if (msg.length()> 2048) { + int count = msg.length() / 2048 + 1; + int beginIndex = 0, endIndex = 2048; + for (int i = 0; i < count; i++) { + String temp = msg.substring(beginIndex, endIndex); + msgList.add(temp); + beginIndex = endIndex; + endIndex += 2048; + endIndex = endIndex> msg.length() ? msg.length() : endIndex; + } + } else { + msgList.add(msg); + } + + for (String s : msgList) { + String body = "{\n" + + " \"touser\" : \"" + kefuHandleDTO.getFromUser() + "\",\n" + + " \"msgtype\" : \"text\",\n" + + " \"text\" : {\n" + + " \"content\" : \"" + s + "\"\n" + + " },\n" + + " \"open_kfid\": \"" + kefuHandleDTO.getOpenKfid() + "\"\n" + + "}"; + MediaType JSON1 = MediaType.parse("application/json;charset=utf-8"); + RequestBody requestBody = RequestBody.create(JSON1, body); + log.info("sendKfMsg:{}", requestBody); + OkHttpUtils.post(url, "", requestBody, Maps.newHashMap()); + } + return "success"; + } + + /** + * 获取客服接收的消息 + * @return + */ + public KefuHandleDTO readKfReceiveMsg(KefuNoticeDTO kefuNoticeDTO) { + String kfsecret = baseConfig.getKfsecret(); + try { + String accessToken = getAccessToken(KF_TOKEN, kfsecret); + String cursorCache = CacheHelper.getWechatCache(cursorKey); + + String url = "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=" + accessToken; + Map body = Maps.newHashMap(); + body.put("cursor", cursorCache); + body.put("token", kefuNoticeDTO.getToken()); + body.put("limit", 1000); + body.put("voice_format", 0); + body.put("open_kfid", kefuNoticeDTO.getOpenKfId()); + MediaType JSON1 = MediaType.parse("application/json;charset=utf-8"); + RequestBody requestBody = RequestBody.create(JSON1, JSON.toJSONString(body)); + String msg = OkHttpUtils.post(url, "", requestBody, Maps.newHashMap()); + if (StringUtils.isBlank(msg)) { + return null; + } + KefuDataDTO kefuDataDTO = JsonHelper.parseJsonToObject(msg, KefuDataDTO.class); + String cursorNew = kefuDataDTO.getNext_cursor(); + CacheHelper.setWechatCache(cursorKey, cursorNew); + + KefuHandleDTO kefuHandleDTO = new KefuHandleDTO(); + List kefuDataMsgDTOS = kefuDataDTO.getMsg_list(); + //TODO 这里包含event和text类型数据,看看怎么处理 + if (!CollectionUtils.isEmpty(kefuDataMsgDTOS)) { + for (KefuDataMsgDTO kefuDataMsgDTO : kefuDataMsgDTOS) { + if (kefuDataMsgDTO.getMsgtype().equals("text")) { + kefuHandleDTO.setData(kefuDataMsgDTO.getText().getContent()); + kefuHandleDTO.setFromUser(kefuDataMsgDTO.getExternal_userid()); + kefuHandleDTO.setOpenKfid(kefuDataMsgDTO.getOpen_kfid()); + break; + } + } + + } + return kefuHandleDTO; + + } catch (Exception e) { + log.error("readKfReceiveMsg error, e={}", e); + } + return null; + } + } diff --git a/src/main/java/com/longbig/multifunction/utils/CacheHelper.java b/src/main/java/com/longbig/multifunction/utils/CacheHelper.java index 5482fd0..daa0a71 100644 --- a/src/main/java/com/longbig/multifunction/utils/CacheHelper.java +++ b/src/main/java/com/longbig/multifunction/utils/CacheHelper.java @@ -23,6 +23,10 @@ public class CacheHelper { //用户连续对话开关 private static Cache userChatFlowSwitch; + //用户GPT4对话开关 + private static Cache userChatGpt4Switch; + + private static Cache wechatCache; static { cache = CacheBuilder.newBuilder() @@ -30,14 +34,30 @@ public class CacheHelper { .build(); chatGptCache = CacheBuilder.newBuilder() - .expireAfterWrite(10, TimeUnit.MINUTES) + .expireAfterWrite(30, TimeUnit.MINUTES) .build(); userChatFlowSwitch = CacheBuilder.newBuilder() - .expireAfterWrite(15, TimeUnit.MINUTES) + .expireAfterWrite(30, TimeUnit.MINUTES) + .build(); + + userChatGpt4Switch = CacheBuilder.newBuilder() + .expireAfterWrite(3, TimeUnit.HOURS) + .build(); + + wechatCache = CacheBuilder.newBuilder() + .expireAfterWrite(3, TimeUnit.DAYS) .build(); } + public static void setWechatCache(String key, String value) { + wechatCache.put(key, value); + } + + public static String getWechatCache(String key) { + return wechatCache.getIfPresent(key); + } + public static void set(String key, String value) { cache.put(key, value); } @@ -74,4 +94,20 @@ public static Boolean getUserChatFlowSwitch(String username) { } return result; } + + public static void setUserChatGpt4Open(String username) { + userChatGpt4Switch.put(username, true); + } + + public static void setUserChatGpt4Close(String username) { + userChatGpt4Switch.put(username, false); + } + + public static Boolean getUserChatGpt4Switch(String username) { + Boolean result = userChatGpt4Switch.getIfPresent(username); + if (Objects.isNull(result)) { + return false; + } + return result; + } } diff --git a/src/main/java/com/longbig/multifunction/utils/JsonHelper.java b/src/main/java/com/longbig/multifunction/utils/JsonHelper.java new file mode 100644 index 0000000..bba1bc8 --- /dev/null +++ b/src/main/java/com/longbig/multifunction/utils/JsonHelper.java @@ -0,0 +1,22 @@ +package com.longbig.multifunction.utils; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +/** + * @author yuyunlong + * @date 2023年6月3日 23:55 + * @description + */ +@Slf4j +public class JsonHelper { + + public static T parseJsonToObject(String jsonData, Class clazz) { + try { + return JSON.parseObject(jsonData, clazz); + } catch (Exception e) { + log.error("parseJsonToObject error,e={}", e); + } + return null; + } +} diff --git a/src/main/java/com/longbig/multifunction/utils/OkHttpUtils.java b/src/main/java/com/longbig/multifunction/utils/OkHttpUtils.java index b63090e..883e565 100644 --- a/src/main/java/com/longbig/multifunction/utils/OkHttpUtils.java +++ b/src/main/java/com/longbig/multifunction/utils/OkHttpUtils.java @@ -16,7 +16,7 @@ @Slf4j public class OkHttpUtils { - private static Long DEFAULT_TIME_OUT = 30L; + private static Long DEFAULT_TIME_OUT = 40L; public static String post(String url, String cookie, RequestBody requestBody, Map header) throws Exception { diff --git a/src/main/java/com/longbig/multifunction/utils/XmlHelper.java b/src/main/java/com/longbig/multifunction/utils/XmlHelper.java new file mode 100644 index 0000000..7fc3e6d --- /dev/null +++ b/src/main/java/com/longbig/multifunction/utils/XmlHelper.java @@ -0,0 +1,55 @@ +package com.longbig.multifunction.utils; + +import lombok.extern.slf4j.Slf4j; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; + +/** + * @author yuyunlong + * @date 2023年6月3日 20:47 + * @description + */ +@Slf4j +public class XmlHelper { + + /** + * xml格式数据转Java对象 + * + * @param xmlData + * @param clazz + * @param + * @return + */ + public static T parseXmlToObject(String xmlData, Class clazz) { + try { + Document document = DocumentHelper.parseText(xmlData); + Element root = document.getRootElement(); + + T object = clazz.newInstance(); + + for (java.lang.reflect.Field field : clazz.getDeclaredFields()) { + String tagName = field.getName(); + String value = getElementValue(root, tagName); + if (value != null) { + field.setAccessible(true); + field.set(object, value); + } + } + + return object; + } catch (DocumentException | InstantiationException | IllegalAccessException | IllegalArgumentException e) { + log.error("parseXmlToObject error, e = {}", e); + } + return null; + } + + private static String getElementValue(Element parent, String tagName) { + Element element = parent.element(tagName); + if (element != null) { + return element.getTextTrim(); + } + return null; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f4f2f9e..9c10d3a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -16,7 +16,7 @@ juejin.Cookie=你的掘金cookie chatgpt.apiKey=【替换为你的ChatGPT账号API key】 #一次连续对话中,最大的对话次数限制,可自行修改 -chatgpt.flow.num=15 +chatgpt.flow.num=20 #wechat微信应用配置 @@ -30,4 +30,5 @@ wechat.corpsecret=【自建应用的secret】 wechat.agentId=【自建应用的agentId】 # 企业微信管理后台-我的企业-企业信息-最下面的企业ID wechat.sCorpID=【企业ID】 - +# 微信客服-Secret +wechat.kfsecret=【微信客服secret】

AltStyle によって変換されたページ (->オリジナル) /