使用chatgpt实现微信聊天小程序(秒回复),github开源(附带链接)

news2024/12/26 10:40:21

文章目录

  • 前言
  • 效果展示
  • 原理说明
  • 服务器端代码说明
  • 微信小程序代码说明
  • 代码链接
  • 总结

前言

我在前一段时间突发奇想,就使用java来调用chatgpt的接口,然后写了一个简单小程序,也上了热榜第一,java调用chatgpt接口,实现专属于自己的人工智能助手,事实上,这个程序毛病挺多的,最不能让人接受的一点就是返回速度非常缓慢(即使使用非常好的外网服务器)。

现在,我改进了一下程序,使用异步请求的方式,基本可以实现秒回复。并且还基于webSocket编写了一个微信小程序来进行交互,可以直接使用微信小程序来进行体验。

现在我将所有代码都上传了github(链接在文章结尾),大家可以clone下来,部署到服务器上,真正实现自己的聊天机器人!!!


效果展示

部分截图如下
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


原理说明

在 java调用chatgpt接口,实现专属于自己的人工智能助手 我说明了java调用chatgpt的基本原理,这里的代码就是对这个代码的改进,使用异步请求的方式来进行。

在这里插入图片描述

注意看官方文档,我们在请求时可以提供一个参数stream,然后就可以实现按照流的形式进行返回,这种方式基本可以做到没有延迟就给出答案。

由于这次改进的思路主要就是将请求改为了异步,其他的基本一样,所以就不做解释,直接给出代码了,代码上面都有注释

    /**
     * 这个方法用于测试的,可以在控制台打印输出结果
     *
     * @param chatGptRequestParameter 请求的参数
     * @param question                问题
     */
    public void printAnswer(ChatRequestParameter chatGptRequestParameter, String question) {
        asyncClient.start();
        // 创建一个post请求
        AsyncRequestBuilder asyncRequest = AsyncRequestBuilder.post(url);

        // 设置请求参数
        chatGptRequestParameter.addMessages(new ChatMessage("user", question));

        // 请求的参数转换为字符串
        String valueAsString = null;
        try {
            valueAsString = objectMapper.writeValueAsString(chatGptRequestParameter);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        // 设置编码和请求参数
        ContentType contentType = ContentType.create("text/plain", charset);
        asyncRequest.setEntity(valueAsString, contentType);
        asyncRequest.setCharset(charset);

        // 设置请求头
        asyncRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
        // 设置登录凭证
        asyncRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey);

        // 下面就是生产者消费者模型
        CountDownLatch latch = new CountDownLatch(1);
        // 用于记录返回的答案
        StringBuilder sb = new StringBuilder();
        // 消费者
        AbstractCharResponseConsumer<HttpResponse> consumer = new AbstractCharResponseConsumer<HttpResponse>() {
            HttpResponse response;

            @Override
            protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException {
                setCharset(charset);
                this.response = response;
            }

            @Override
            protected int capacityIncrement() {
                return Integer.MAX_VALUE;
            }

            @Override
            protected void data(CharBuffer src, boolean endOfStream) throws IOException {
                // 收到一个请求就进行处理
                String ss = src.toString();
                // 通过data:进行分割,如果不进行此步,可能返回的答案会少一些内容
                for (String s : ss.split("data:")) {
                    // 去除掉data:
                    if (s.startsWith("data:")) {
                        s = s.substring(5);
                    }
                    // 返回的数据可能是(DONE)
                    if (s.length() > 8) {
                        // 转换为对象
                        ChatResponseParameter responseParameter = objectMapper.readValue(s, ChatResponseParameter.class);
                        // 处理结果
                        for (Choice choice : responseParameter.getChoices()) {
                            String content = choice.getDelta().getContent();
                            if (content != null && !"".equals(content)) {
                                // 保存结果
                                sb.append(content);
                                // 将结果使用webSocket传送过去
                                System.out.print(content);
                            }
                        }
                    }
                }
            }

            @Override
            protected HttpResponse buildResult() throws IOException {
                return response;
            }

            @Override
            public void releaseResources() {
            }
        };

        // 执行请求
        asyncClient.execute(asyncRequest.build(), consumer, new FutureCallback<HttpResponse>() {

            @Override
            public void completed(HttpResponse response) {
                latch.countDown();
                chatGptRequestParameter.addMessages(new ChatMessage("assistant", sb.toString()));
                System.out.println("回答结束!!!");
            }

            @Override
            public void failed(Exception ex) {
                latch.countDown();
                System.out.println("failed");
                ex.printStackTrace();
            }

            @Override
            public void cancelled() {
                latch.countDown();
                System.out.println("cancelled");
            }

        });
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

大家代码可以直接不看,反正最终的效果就是可以实现问了问题就返回结果。运行效果如下

在这里插入图片描述
在这里插入图片描述

可以发现,输出就类似于官方的那种效果,一个字一个字的输出


服务器端代码说明

我使用java搭建了一个简单的服务器端程序,提供最基础的用户登录校验功能,以及提供了WebSocket通信。

用户校验的代码

package com.ttpfx.controller;

import com.ttpfx.entity.User;
import com.ttpfx.service.UserService;
import com.ttpfx.utils.R;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ttpfx
 * @date 2023/3/29
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    public static ConcurrentHashMap<String, User> loginUser = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<String, Long> loginUserKey = new ConcurrentHashMap<>();
    @RequestMapping("/login")
    public R login(String username, String password) {
        if (username == null) return R.fail("必须填写用户名");


        User user = userService.queryByName(username);
        if (user == null) return R.fail("用户名不存在");
        String targetPassword = user.getPassword();
        if (targetPassword == null) return R.fail("用户密码异常");
        if (!targetPassword.equals(password)) return R.fail("密码错误");

        loginUser.put(username, user);
        loginUserKey.put(username, System.currentTimeMillis());
        return R.ok(String.valueOf(loginUserKey.get(username)));
    }

    @RequestMapping("/logout")
    public R logout(String username) {
        loginUser.remove(username);
        loginUserKey.remove(username);
        return R.ok();
    }

    @RequestMapping("/checkUserKey")
    public R checkUserKey(String username, Long key){
        if (username==null || key == null)return R.fail("用户校验异常");
        if (!Objects.equals(loginUserKey.get(username), key)){
            return R.fail("用户在其他地方登录!!!");
        }
        return R.ok();
    }

    @RequestMapping("/loginUser")
    public R loginUser(){
        return R.ok("success",loginUser.keySet());
    }
}

基于webSocket通信的代码

package com.ttpfx.server;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ttpfx.entity.UserLog;
import com.ttpfx.model.ChatModel;
import com.ttpfx.service.UserLogService;
import com.ttpfx.service.UserService;
import com.ttpfx.vo.chat.ChatRequestParameter;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ttpfx
 * @date 2023/3/28
 */
@Component
@ServerEndpoint("/chatWebSocket/{username}")
public class ChatWebSocketServer {

    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;
    /**
     * concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, ChatWebSocketServer> chatWebSocketMap = new ConcurrentHashMap<>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收的username
     */
    private String username = "";

    private UserLog userLog;

    private static UserService userService;
    private static UserLogService userLogService;

    @Resource
    public void setUserService(UserService userService) {
        ChatWebSocketServer.userService = userService;
    }

    @Resource
    public void setUserLogService(UserLogService userLogService) {
        ChatWebSocketServer.userLogService = userLogService;
    }

    private ObjectMapper objectMapper = new ObjectMapper();
    private static ChatModel chatModel;

    @Resource
    public void setChatModel(ChatModel chatModel) {
        ChatWebSocketServer.chatModel = chatModel;
    }

    ChatRequestParameter chatRequestParameter = new ChatRequestParameter();

    /**
     * 建立连接
     * @param session 会话
     * @param username 连接用户名称
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        this.session = session;
        this.username = username;
        this.userLog = new UserLog();
        // 这里的用户id不可能为null,出现null,那么就是非法请求
        try {
            this.userLog.setUserId(userService.queryByName(username).getId());
        } catch (Exception e) {
            e.printStackTrace();
            try {
                session.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        this.userLog.setUsername(username);
        chatWebSocketMap.put(username, this);
        onlineCount++;
        System.out.println(username + "--open");
    }

    @OnClose
    public void onClose() {
        chatWebSocketMap.remove(username);
        System.out.println(username + "--close");
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println(username + "--" + message);
        // 记录日志
        this.userLog.setDateTime(LocalDateTime.now());
        this.userLog.setPreLogId(this.userLog.getLogId() == null ? -1 : this.userLog.getLogId());
        this.userLog.setLogId(null);
        this.userLog.setQuestion(message);
        long start = System.currentTimeMillis();
        // 这里就会返回结果
        String answer = chatModel.getAnswer(session, chatRequestParameter, message);
        long end = System.currentTimeMillis();
        this.userLog.setConsumeTime(end - start);
        this.userLog.setAnswer(answer);
        userLogService.save(userLog);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    public static void sendInfo(String message, String toUserId) throws IOException {
        chatWebSocketMap.get(toUserId).sendMessage(message);
    }
}

我们只需要编写简单的前端代码,就可以实现和后端的socket通信。对于后端,我们只需要改一下apiKey和数据库配置就可以直接运行了。


微信小程序代码说明

我写了一个简单微信小程序来和后端进行通信,界面如下

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

大家只需要下载源代码,然将程序中的ip改为自己服务器的ip即可


代码链接

github的地址为 https://github.com/c-ttpfx/chatgpt-java-wx
可以直接使用 git clone https://github.com/c-ttpfx/chatgpt-java-wx.git 下载代码到本地

我在github里面说明了安装使用的基本步骤,大家按照步骤使用即可

总结

上面聊天小程序就是我花2天写出来的,可能会有一些bug,我自己测试的时候倒是没有怎么遇到bug,聊天和登录功能都能正常使用。

对于微信小程序,由于我不是专业搞前端的,就只东拼西凑实现了最基本的功能(登录、聊天),大家可以自己写一个,反正后端接口都提供好了嘛,也不是很难,不想写也可以将就使用我的。

最后,也是最重要的,大家帮我的代码star一下!!! 感谢大家了(≥▽≤)/(≥▽≤)/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/424732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

select 排序qsort排序

目录 1.希尔排序的时间复杂度 3.有技巧的选择排序&#xff1a;堆排序 4.排序的种类 5.直接插入排序和冒泡排序 6.快速排序 7.希尔排序 堆排序 和快排的区别 8.为什么相遇位置一定比key小 9.快排的优化 11.快排递归写法的不足 12.快排的非递归解法 1.希尔排序的时间复杂…

C++11新特性(上)

357089 文章目录1. 统一的列表初始化1.1 &#xff5b;&#xff5d;初始化1.2 std::initializer_list2. decltype3. 右值引用和移动语义3.1 左值引用和右值引用3.2 左值引用与右值引用比较3.3 右值引用使用场景和意义3.4 右值引用引用左值及更深入的使用场景3.5 完美转发4. 新的…

“编程式 WebSocket” 实现简易 online QQ在线聊天项目

目录 一、需求分析与演示 1.1、需求分析 1.2、效果演示 二、客户端、服务器开发 2.1、客户端开发 2.2、服务器开发 一、需求分析与演示 1.1、需求分析 需求&#xff1a;实现一个 online QQ在线聊天项目&#xff0c;当用户登录上自己的账号后&#xff0c;将会显示在线&…

我用nodejs和electron实现了一个简单的聊天软件-----chat 开源

翎&#x1f3a5;项目演示地址 &#x1f517;https://www.bilibili.com/video/BV1Fg4y1u76d/ 希望观众老爷给个免费的三连支持一下新人up主 ♻️项目基本介绍 翎是基于electron(vue2)和nodejs实现的简单聊天软件,其中用websocket和http进行通讯传递,数据库使用了mysql数据库,…

二进制插入与查找组成一个偶数最接近的两个素数

二进制插入 链接&#xff1a;二进制插入_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a;给定两个32位整数n和m&#xff0c;同时给定i和j&#xff0c;将m的二进制数位插入到n的二进制的第j到第i位,保证n的第j到第i位均为零&#xff0c;且m的二进制位数小于等于i-j1&#xff…

Qt Quick - Popup

Qt Quick - Popup使用总结一、概述二、Popup 的布局三、弹出分级四、弹出定位五、定制化一、概述 Popup是类似弹出式用户界面控件的基本类型。它可以与Window或ApplicationWindow一起使用。 import QtQuick.Window 2.2import QtQuick.Controls 2.12ApplicationWindow {id: win…

力推美团企业版 美团究竟意欲何为?

已经拥有930万活跃商家的美团公司&#xff0c;正在充分整合自身的“供应链”优势&#xff0c;冲向B端市场。 3月31日&#xff0c;据36氪消息显示&#xff0c;美团将于近期正式上线面向To B市场的业务“美团企业版”&#xff0c;定位企业消费赛道。美团企业版会为企业客户提供消…

ZeroTier 内网穿透

ZeroTier 内网穿透 官网注册账号&#xff0c;创建自己的局域网段, 登录官网 创建网络&#xff1a; 点击创建好的网络&#xff0c;进入设置界面进行设置, 选择 public 模式,点击入设置页面 地址随便选择 说明没有设备链接 下载客户端 &#xff0c;下载 安装客户端&#xf…

高级数据结构与算法 | 三元搜索树(Ternary Search Tree)

文章目录TernarySearchTree基本概念介绍原理插入查找删除代码实现TernarySearchTree 基本概念 介绍 Ternary Search Tree&#xff08;三元搜索树&#xff09;&#xff0c;它是由 Bentley 和 Sedgewick 在 1997 年提出的一种基于 Trie 的思想改良的一种数据结构&#xff0c;其…

【GCU体验】基于PyTorch + GCU跑通ResNet50模型并测试GCU性能

一、环境 地址&#xff1a;启智社区:https://openi.pcl.ac.cn/ 二、计算卡介绍 云燧T20是基于邃思2.0芯片打造的面向数据中心的第二代人工智能训练加速卡&#xff0c;具有模型覆盖面广、性能强、软件生态开放等特点&#xff0c;可支持多种人工智能训练场景。同时具备灵活的可…

win10 64位 环境下安装CUDA 11.8和 cuDNN v8.6.0

win10 64位 环境下安装CUDA 11.8和 cuDNN v8.6.0 1 安装 NVIDIA 显卡驱动程序 下载地址&#xff1a;http://www.nvidia.cn/Download/index.aspx?langcn ​​​​​​ 下载文件&#xff1a;531.41-desktop-win10-win11-64bit-international-nsd-dch-whql 选择适合自己电脑的显…

DeepFM论文翻译

1.摘要 为了最大化推荐系统的CTR&#xff0c;学习用户行为的复杂交叉特征很关键。 尽管有很大进步&#xff0c;现有的方法无论对低阶还是高阶的交叉特征&#xff0c;似乎还是有很强的bias, 或者需要专门的特征工程。 本文&#xff0c;我们证明了得出一个能强化高阶和低阶交叉特…

前端实现自动化测试

什么是前端测试 我们经常说的单元测试其实只是前端测试的一种。前端测试分为单元测试&#xff0c;UI 测试&#xff0c;集成测试和端到端测试。 ● 单元测试&#xff1a;是指对软件中的最小可测试单元进行检查和验证&#xff0c;通常指的是独立测试单个函数。 ● UI 测试&#…

2023美赛Y题二手帆船价格--成品论文、思路、数据、代码

2023美赛Y题二手帆船价格 第一时间在CSDN分享 最新进度在文章最下方卡片&#xff0c;加入获取一手资源&#xff1a;2023美赛Y题二手帆船价格–成品论文、思路、数据、代码 可以提供关于帆船特性的信息: BoatTrader (https://www.boattrader.com/):一个网站&#xff0c;允许您根…

WindowsGUI自动化测试项目实战+辛酸过程+经验分享

WindowsGUI自动化测试项目实战辛酸过程经验分享一、前言⚜ 起因⚜ 项目要求⚜ 预研过程⚜⚜ 框架选型⚜⚜ 关于UIaotumation框架⚜ 预研成果二、项目介绍&#x1f493; 测试对象&#x1f493; 技术栈&#x1f493; 项目框架说明三、项目展示&#x1f923; 界面实现效果&#x1…

【深度学习】windows10环境配置详细教程

【深度学习】windows10环境配置详细教程 文章目录【深度学习】windows10环境配置详细教程Anaconda31.安装Anaconda32.卸载Anaconda33.修改Anaconda3安装虚拟环境的默认位置安装cuda/cudnn1.安装合适的CUDA2.安装对应的CUDNN3.卸载CUDA/CUDNNconda虚拟环境独立安装cuda/cudnn1.搭…

随想录Day55--动态规划: 392.判断子序列 , 115.不同的子序列

392.判断子序列 思路 &#xff08;这道题也可以用双指针的思路来实现&#xff0c;时间复杂度也是O(n)&#xff09; 动态规划五部曲分析如下&#xff1a; 1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i][j] 表示以下标i-1为结尾的字符串s&#xff0c;和…

基线配置管理在网络中的重要性

在网络环境中&#xff0c;配置通常被认为具有不可估量的价值&#xff0c;因为设备配置的微小变化可以在几分钟内成就或破坏整个网络基础设施。 这些配置分为两部分&#xff1a;启动配置和运行配置。在网络设备中&#xff0c;默认情况下&#xff0c;第一个配置版本被视为运行和…

el-input-number的精度问题

前言 el-input-number 饿了么的数字输入框组件&#xff0c;在项目中听常用的。而这个组件比较常用的属性就是精度设置&#xff0c;给组件添加属性precision 。 其实吧&#xff0c;之前一直没怎么研究&#xff0c;保留几位小数就直接填几就好了&#xff0c;比如保留两位小数&am…

4.mysql内置函数

目录 日期函数 字符串函数 数学函数 其它函数 日期函数 获得当前年月日: