当前位置:   article > 正文

微信公众号对接openai详细教程,手把手教你搭建自己的gpt智能助手_微信公众号接入chatgpt

微信公众号接入chatgpt

前言

众说周知,微信未经过认证的订阅号在接口权限上面有非常大的限制,例如:只能回复用户消息而不能主动推送;回复消息只能在三次微信推送的15秒内;回复用户消息有字符限制等等。这里主要是为了平衡国内调用openai接口的速度和微信公众号限制。

可先关注公众号免费体验,回复口令还可以免费领取openai密钥(不限制次数,可用于学习和测试)。

个人公众号

一、准备工作

  • 申请一个个人订阅号(很简单,不说了)
  • 到微信公众号的管理界面,点击 设置与开发 —> 基本配置
    开启服务器配置,自定义令牌,选择明文模式。
    微信公众号服务器配置
  • 如下所示,填写到配置文件中
#wechatmp
wechatmp:
  #这里就是服务器配置中自己填写的 令牌(Token)
  token: xxxxxxxxxxxx

#chatgpt
chatgpt:
  model: gpt-4o
  # openAI 的接口
  apikey:
    - sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  #oepnai 接口基础地址 https://openai.xxx.com/ 或者使用自己的代理地址
  baseUrl: https://openai.xxxx.com/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

二、验证服务器配置中的服务器URL

当我们填写服务器配置的URL时候,是需要验证URL地址的,验证代码如下:

  • Controller
    @GetMapping("")
    public ResponseEntity<Object> checkSignature(WeChatBean weChatBean) {
        //验证是否为微信消息
        String signatureHashcode = weChatService.checkWeChatSignature(weChatBean);
        if (!signatureHashcode.equals(weChatBean.getSignature())) {
            return ResponseEntity.ok("非法数据");
        }
        //微信公众号接口认证
        if (StringUtils.isNotBlank(weChatBean.getEchostr())) {
            return ResponseEntity.ok(weChatBean.getEchostr());
        }
        return ResponseEntity.ok(null);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • Service
    @Override
    public String checkWeChatSignature(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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

三、 接受订阅号用户对话,调用openai接口

1、微信回复的处理

  • 过滤掉不是文本的对话信息(暂时只处理文本对话)
if (!params.get("MsgType").equals("text")) {
    return getReplyWeChat(weChatBean.getOpenid(), params.get("ToUserName"), "暂时只支持接收文本信息");
}
  • 1
  • 2
  • 3

对于处理openai接口回复逻辑,这里主要分为两方面:

  1. 当前对话是第一次调用,即:问问题
        //调用chatgpt
        final String 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(new TimerTask() {
                @Override
                public void run() {
                    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));
                return getReplyWeChat(weChatBean.getOpenid(), toUserName, String.valueOf(o));
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  1. 当前对话是为了取出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);
                        return getReplyWeChat(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);
                        return getReplyWeChat(weChatBean.getOpenid(), toUserName, replyContent + "\n  (公众号回复字符限制,输入\"继续\"查看后续内容)");
                    }
                }
            }
            return success;
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

这里两处的 while循环操作,主要是为了尽可能的在三次微信消息推送的15秒内给出openai的回答,而不是让用户多次输入继续。

2、调用openai接口处理

  • 调用接口分为单轮对话和多轮对话
    /**
     * 多轮会话
     * @param openId 用户openid 
     * @param msgId 消息id
     * @param content 问话内容
     */
    @Override
    public void multiChatStreamToWX(String openId, String msgId, String content) {
        OpenAiStreamClient streamClient = getStreamClient();
        WeChatEventSourceListener weChatEventSourceListener = new WeChatEventSourceListener(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 问话内容
     */
    @Override
    public void singleChatStreamToWX(String openId, String msgId, String content) {
        OpenAiStreamClient streamClient = getStreamClient();
        WeChatEventSourceListener weChatEventSourceListener = new WeChatEventSourceListener(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);
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 这里处理openai接口接口返回,都是使用sse的形式,因此所有的结果处理都是在WeChatEventSourceListener中
    public WeChatEventSourceListener(String openId, String msgId) {
        this.openId = openId;
        this.msgId = msgId;
        this.sb = new StringBuffer();
    }

    @Override
    public void onClosed(@NotNull EventSource eventSource) {
        log.info("OpenAI关闭sse连接...");
        //缓存回复到redis
        redisCacheUtils.setCacheObject(waitKey, sb.toString(),  60, TimeUnit.MINUTES);
        redisCacheUtils.setCacheObject(msgKey, sb.toString(), 60, TimeUnit.SECONDS);
        eventSource.cancel();
    }

    @Override
    public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String 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返回数据结束了");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

结束语

以上只是实现了简单的订阅号对话openai,下面是本项目开源地址

项目开源地址 项目开源地址

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/768101
推荐阅读
相关标签
  

闽ICP备14008679号