微信公众号订阅号(未认证)接入chatgpt最新gpt-4o模型实现AI聊天详细教程(免费体验)
前言
众说周知,微信未经过认证的订阅号在接口权限上面有非常大的限制,例如:只能回复用户消息而不能主动推送;回复消息只能在三次微信推送的15秒内;回复用户消息有字符限制等等。这里主要是为了平衡国内调用openai接口的速度和微信公众号限制。
可关注公众号体验
原文地址:微信公众号订阅号(未认证)接入gpt-4o模型实现AI聊天详细教程(免费体验)
一、准备工作
- 申请一个个人订阅号(很简单,不说了)
- 到微信公众号的管理界面,点击 设置与开发 —> 基本配置开启服务器配置,自定义令牌,选择明文模式。
- 如下所示,填写到配置文件中
#wechatmpwechatmp:#这里就是服务器配置中自己填写的 令牌(Token)token: xxxxxxxxxxxx
#chatgptchatgpt:model: gpt-4o
# openAI 的接口apikey:- sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#oepnai 接口基础地址 https://openai.xxx.com/ 或者使用自己的代理地址baseUrl: https://openai.xxxx.com/
二、验证服务器配置中的服务器URL
当我们填写服务器配置的URL时候,是需要验证URL地址的,验证代码如下:
- Controller
@GetMapping("")publicResponseEntity<Object>checkSignature(WeChatBean weChatBean){//验证是否为微信消息String signatureHashcode = weChatService.checkWeChatSignature(weChatBean);if(!signatureHashcode.equals(weChatBean.getSignature())){returnResponseEntity.ok("非法数据");}//微信公众号接口认证if(StringUtils.isNotBlank(weChatBean.getEchostr())){returnResponseEntity.ok(weChatBean.getEchostr());}returnResponseEntity.ok(null);}
- Service
@OverridepublicStringcheckWeChatSignature(WeChatBean weChatBean){String hashSignature =null;if(StringUtils.isBlank(weChatBean.getTimestamp())||StringUtils.isBlank(weChatBean.getNonce())){return hashSignature;}
hashSignature =SignatureUtils.generateEventMessageSignature(wechatMpConfig.getToken(),
weChatBean.getTimestamp(), weChatBean.getNonce());return hashSignature;}
三、 接受订阅号用户对话,调用openai接口
1、微信回复的处理
- 过滤掉不是文本的对话信息(暂时只处理文本对话)
if(!params.get("MsgType").equals("text")){returngetReplyWeChat(weChatBean.getOpenid(), params.get("ToUserName"),"暂时只支持接收文本信息");}
对于处理ChatGPT接口回复逻辑,这里主要分为两方面:
- 当前对话是第一次调用,即:问问题
//调用chatgptfinalString msgKey =String.format(CommonConstant.CHAT_WX_USER_MSG_REPLY_KEY, msgId);if(!redisCacheUtils.hasKey(msgKey)){
redisCacheUtils.setCacheObject(msgKey, success,30,TimeUnit.SECONDS);AsyncManager.me().execute(newTimerTask(){@Overridepublicvoidrun(){if(StringUtils.isNotBlank(content)){
redisCacheUtils.deleteObject(waitKey);
chatgptService.singleChatStreamToWX(weChatBean.getOpenid(), msgId, content);}}});}while(messageCountMap.containsKey(msgId)){String replay =checkMessageCountMap(msgId, currentMsgCount, start, toUserName, weChatBean);if(null!= replay){return replay;}Object o = redisCacheUtils.getCacheObject(msgKey);if(!success.equals(String.valueOf(o))){
messageCountMap.remove(msgId);
redisCacheUtils.deleteObject(Arrays.asList(msgKey, waitKey));returngetReplyWeChat(weChatBean.getOpenid(), toUserName,String.valueOf(o));}}
- 当前对话是为了取出openai的回答,即:输入 “继续”
if(StringUtils.isNotBlank(content)&& content.equals("继续")){while(messageCountMap.containsKey(msgId)){String replay =checkMessageCountMap(msgId, currentMsgCount, start, toUserName, weChatBean);if(null!= replay){return replay;}if(redisCacheUtils.hasKey(waitKey)){Object o = redisCacheUtils.getCacheObject(waitKey);Integer contentLength =getByteSize(String.valueOf(o));
messageCountMap.remove(msgId);if(contentLength <2048){
redisCacheUtils.deleteObject(waitKey);returngetReplyWeChat(weChatBean.getOpenid(), toUserName,String.valueOf(o));}else{String replyContent =String.valueOf(o).substring(0,580);
redisCacheUtils.setCacheObject(waitKey,String.valueOf(o).replace(replyContent,""),60,TimeUnit.MINUTES);returngetReplyWeChat(weChatBean.getOpenid(), toUserName, replyContent +"\n (公众号回复字符限制,输入\"继续\"查看后续内容)");}}}return success;}
这里两处的 while循环操作,主要是为了尽可能的在三次微信消息推送的15秒内给出ChatGPT的回答,而不是让用户多次输入继续。
2、调用openai接口处理
- 调用接口分为单轮对话和多轮对话(这里使用了开源的openai调用sdk)
<dependency><groupId>com.unfbx</groupId><artifactId>chatgpt-java</artifactId><version>1.1.5</version></dependency>
/**
* 多轮会话
* @param openId 用户openid
* @param msgId 消息id
* @param content 问话内容
*/@OverridepublicvoidmultiChatStreamToWX(String openId,String msgId,String content){OpenAiStreamClient streamClient =getStreamClient();WeChatEventSourceListener weChatEventSourceListener =newWeChatEventSourceListener(openId, msgId);//获取历史会话记录List<Message> messages =getWxMessageList(openId, content);ChatCompletion chatCompletion =ChatCompletion.builder().stream(true).messages(messages).build();
streamClient.streamChatCompletion(chatCompletion, weChatEventSourceListener);}/**
* 单轮会话
* @param openId 用户openid
* @param msgId 消息id
* @param content 问话内容
*/@OverridepublicvoidsingleChatStreamToWX(String openId,String msgId,String content){OpenAiStreamClient streamClient =getStreamClient();WeChatEventSourceListener weChatEventSourceListener =newWeChatEventSourceListener(openId, msgId);Message message =Message.builder().role(BaseMessage.Role.USER).content(content).build();ChatCompletion chatCompletion =ChatCompletion.builder().stream(true).messages(Arrays.asList(message)).build();
streamClient.streamChatCompletion(chatCompletion, weChatEventSourceListener);}
- 这里处理opeAI接口接口返回,都是使用sse的形式,因此所有的结果处理都是在WeChatEventSourceListener中
publicWeChatEventSourceListener(String openId,String msgId){this.openId = openId;this.msgId = msgId;this.sb =newStringBuffer();}@OverridepublicvoidonClosed(@NotNullEventSource eventSource){
log.info("OpenAI关闭sse连接...");//缓存回复到redis
redisCacheUtils.setCacheObject(waitKey, sb.toString(),60,TimeUnit.MINUTES);
redisCacheUtils.setCacheObject(msgKey, sb.toString(),60,TimeUnit.SECONDS);
eventSource.cancel();}@OverridepublicvoidonEvent(@NotNullEventSource eventSource,@NullableString id,@NullableString type,@NotNullString data){
log.debug("OpenAI返回数据:{}", data);if(!"[DONE]".equals(data)){ChatCompletionResponse response =JSON.parseObject(data,ChatCompletionResponse.class);if(null== response.getChoices().get(0).getFinishReason()){String content = response.getChoices().get(0).getDelta().getContent();
sb.append(content);}}else{
log.info("OpenAI返回数据结束了");}}
结束语
以上只是实现了简单的订阅号对话openai,下面是本项目开源地址
项目开源地址: wechatgpt 微信订阅号对接gpt-4o模型
版权归原作者 取个好一点的名字真的是太难了 所有, 如有侵权,请联系我们删除。