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

文章目录

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

前言

我在前一段时间突发奇想,就使用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一下!!! 感谢大家了(≥▽≤)/(≥▽≤)/

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nodejs+vue 智能餐厅菜品厨位分配管理系统

系统功能主要介绍以下几点&#xff1a; 本智能餐厅管理系统主要包括三大功能模块&#xff0c;即用户功能模块和管理员功能模块、厨房功能模块。 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户是管理员&#xff0c;管理员登录后&#xff0c;通过管理员功能来…

基于matlab使用 CSI-RS 的 NR 下行链路发射端波束细化

一、前言 此示例演示了使用 5G 工具箱中的信道状态信息参考信号 &#xff08;CSI-RS&#xff09; 的下行链路发射端波束细化过程。该示例展示了如何在散射环境中向不同方向传输多个CSI-RS资源&#xff0c;以及如何根据参考信号接收功率&#xff08;RSRP&#xff09;测量结果选择…

编写高质量代码:改善Java程序的151个建议(数组和集合)

集合中的元素必须做到compareTo和equals同步 实现了Comparable接口的元素就可以排序&#xff0c;compareTo方法是Comparable接口要求必须实现的&#xff0c;它与equals方法有关系吗?有关系&#xff0c;在compareTo的返回为0时&#xff0c;它表示的是 进行比较的两个元素时相等…

某医院网络安全分析案例

背景 我们已将NetInside流量分析系统部署到某市医院的机房内&#xff0c;使用流量分析系统提供实时和历史原始流量。本次分析重点针对网络流量安全进行分析&#xff0c;以供安全取证、网络质量监测以及深层网络分析。 分析时间 报告分析时间范围为&#xff1a;2023-04-12 16…

Cloud Kernel SIG月度动态:发布 Anolis 8.8 镜像、kABI 社区共建流程

Cloud Kernel SIG&#xff08;Special Interest Group&#xff09;&#xff1a;支撑龙蜥内核版本的研发、发布和服务&#xff0c;提供生产可用的高性价比内核产品。 01 SIG 整体进展 Anolis 8.8 镜像发布&#xff0c;默认搭载 ANCK 5.10-013 版本。 Anolis 23 滚动内核更新至…

ai智能文章生成器-ai论文写作

在数字时代&#xff0c;营销推广策略已经向数字化方向发展。今天我们要介绍的是一款名为“智能ai写作免费”的软件&#xff0c;它可以让营销人员轻松地创作新的内容&#xff0c;并且其中不需要过多的技术知识或文学背景。这款软件可以为许多企业和机构带来创造性的帮助。 智能A…

@Async异步线程:Spring 自带的异步解决方案

前言 在项目应用中&#xff0c;使用MQ异步调用来实现系统性能优化&#xff0c;完成服务间数据同步是常用的技术手段。如果是在同一台服务器内部&#xff0c;不涉及到分布式系统&#xff0c;单纯的想实现部分业务的异步执行&#xff0c;这里介绍一个更简单的异步方法调用。 对于…

电脑端(PC)按键精灵——5.找色/找图命令

电脑端(PC)按键精灵——5.找色/找图命令 注&#xff1a;说了键盘、鼠标、其他、控制命令还有安装内容&#xff0c;现在说下颜色/图形命令&#xff0c;这一节相当重要 按键精灵小白入门详细教程&#xff1a; 电脑端(PC)按键精灵—小白入门 详细教程 命令介绍 1.GetPixelCol…

【C++类】

目录 前言一、类的定义二、类的访问限定符及封装2.1访问限定符2.2封装 三、类的大小3.1为什么需要内存对齐3.2为什么成员函数不占用类的内存&#xff1f;3.3为什么空类的大小是1个字节&#xff1f; 四、this指针4.1this指针的引入4.2this指针的特性 五、类的6个默认成员函数5.1…

飞书接入ChatGPT - 将ChatGPT集成到飞书机器人,直接拉满效率 【飞书ChatGPT机器人】

文章目录 前言环境列表视频教程1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话,在下面操作步骤中,使…

6.4 一阶方程组与高阶方程的数值解法

学习目标&#xff1a; 学习一阶方程组与高阶方程的数值解法的目标可以分为以下几个方面&#xff1a; 掌握一阶方程组和高阶方程的基本概念和求解方法&#xff1b;理解数值解法的概念和原理&#xff0c;了解常见的数值解法&#xff1b;掌握欧拉方法、改进欧拉方法和龙格-库塔方…

深入探讨Linux驱动开发:Linux设备树

文章目录 一、设备树介绍二、设备树框架1.设备树框架2.节点基本格式3.节点部分属性简介 总结 一、设备树介绍 设备树&#xff08;Device Tree&#xff0c;简称 DT&#xff09;是一种在嵌入式系统中描述硬件设备的一种数据结构和编程语言。它用于将硬件设备的配置信息以树形结构…

Springboot 中快速完成文件上传,整合多平台神器

哈喽&#xff0c;大家好~ 又是做好人好事的一天&#xff0c;有个小可爱私下问我有没有好用的springboot文件上传工具&#xff0c;这不巧了嘛&#xff0c;正好我私藏了一个好东西&#xff0c;顺便给小伙伴们也分享一下&#xff0c;demo地址放在文末了。 文件上传在平常不过的一…

最新,有8本SCIE期刊被剔除,4月SCIESSCI期刊目录更新(附最新目录下载)

2023年4月18日&#xff0c;科睿唯安更新了WOS期刊目录&#xff0c;继上次3月WOS期刊目录更新大变动之后&#xff0c;此次4月更新又有8本SCIE期刊发生变动&#xff0c;其中有4本期刊被剔出SCIE数据库&#xff0c;4本期刊更改了名称和ISSN号。更新后的最新SCIE期刊目录共有9505本…

Flask 与 Django 先学哪个呢

本文把 Flask 和 Django 做一个比对&#xff0c;因为我对这两个 Python Web 框架都有实际的开发经验。希望我可以帮助您选择学习哪个框架&#xff0c;因为学习一个框架可能会非常耗时 —— 当然也很有趣&#xff01; 相似之处 让我们从相似之处开始。 No. 1 Flask 和 Djang…

【ctfshow】命令执行->web29-web44

前言 半夜网抑云听歌听emo了 z 刷会儿题不然睡不着了呜呜呜 红中(hong_zh0) CSDN内容合伙人、2023年新星计划web安全方向导师、 华为MindSpore截至目前最年轻的优秀开发者、IK&N战队队长、 吉林师范大学网安大一的一名普通学生、搞网安论文拿了回大挑校二、 阿里云专家博…

数据结构复习题(包含答案)

第一章 概论 一、选择题 1、研究数据结构就是研究&#xff08; D &#xff09;。 A. 数据的逻辑结构 B. 数据的存储结构 C. 数据的逻辑结构和存储结构 D. 数据的逻辑结构、存储结构及其基本操作 2、算法分析的两个主要方面是&#xff08; A …

【小技巧】word文档编辑技巧(一)

文章目录 一、显示显示导航显示所有字符 二、格式格式-三级目录格式-文本格式-图格式-表格式-公式格式-参考文献 三、小技巧交叉引用连续交叉引用表/图目录等自动更新分节符设置页眉/页码word转pdf带导航 一、显示 显示导航 开启导航&#xff1a;视图->显示框->导航窗格…

【Python从入门到进阶】16、文件的打开和关闭

接上篇《15、函数的定义和使用》 上一篇我们学习了Python中函数的定义和使用&#xff0c;包括函数的参数、返回值、局部变量和全景变量等操作。从本篇开始我们来开始学习Python对文件的一些操作&#xff0c;本篇我们主要讲解如何使用Python打开和关闭文件。 一、打开/创建文件…

【SVN】windows SVN安装使用教程(服务器4.3.4版本/客户端1.11.0版本)

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…