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

打印 上一主题 下一主题

主题 578|帖子 578|积分 1734

媒介

我在前一段时间突发奇想,就使用java来调用chatgpt的接口,然后写了一个简单小程序,也上了热榜第一,java调用chatgpt接口,实现专属于本身的人工智能助手,事实上,这个程序毛病挺多的,最不能让人接受的一点就是返回速度非常缓慢(即使使用非常好的外网服务器)。
如今,我改进了一下程序,使用异步哀求的方式,基本可以实现秒回复。而且还基于webSocket编写了一个微信小程序来进行交互,可以直接使用微信小程序来进行体验。
如今我将所有代码都上传了github(链接在文章结尾),各人可以clone下来,部署到服务器上,真正实现本身的聊天呆板人!!!

结果展示

部分截图如下






原理说明

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

注意看官方文档,我们在哀求时可以提供一个参数stream,然后就可以实现按照流的形式进行返回,这种方式基本可以做到没有延迟就给出答案。
由于这次改进的思路主要就是将哀求改为了异步,其他的基本一样,以是就不做解释,直接给出代码了,代码上面都有注释
  1.     /**
  2.      * 这个方法用于测试的,可以在控制台打印输出结果
  3.      *
  4.      * @param chatGptRequestParameter 请求的参数
  5.      * @param question                问题
  6.      */
  7.     public void printAnswer(ChatRequestParameter chatGptRequestParameter, String question) {
  8.         asyncClient.start();
  9.         // 创建一个post请求
  10.         AsyncRequestBuilder asyncRequest = AsyncRequestBuilder.post(url);
  11.         // 设置请求参数
  12.         chatGptRequestParameter.addMessages(new ChatMessage("user", question));
  13.         // 请求的参数转换为字符串
  14.         String valueAsString = null;
  15.         try {
  16.             valueAsString = objectMapper.writeValueAsString(chatGptRequestParameter);
  17.         } catch (JsonProcessingException e) {
  18.             e.printStackTrace();
  19.         }
  20.         // 设置编码和请求参数
  21.         ContentType contentType = ContentType.create("text/plain", charset);
  22.         asyncRequest.setEntity(valueAsString, contentType);
  23.         asyncRequest.setCharset(charset);
  24.         // 设置请求头
  25.         asyncRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
  26.         // 设置登录凭证
  27.         asyncRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey);
  28.         // 下面就是生产者消费者模型
  29.         CountDownLatch latch = new CountDownLatch(1);
  30.         // 用于记录返回的答案
  31.         StringBuilder sb = new StringBuilder();
  32.         // 消费者
  33.         AbstractCharResponseConsumer<HttpResponse> consumer = new AbstractCharResponseConsumer<HttpResponse>() {
  34.             HttpResponse response;
  35.             @Override
  36.             protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException {
  37.                 setCharset(charset);
  38.                 this.response = response;
  39.             }
  40.             @Override
  41.             protected int capacityIncrement() {
  42.                 return Integer.MAX_VALUE;
  43.             }
  44.             @Override
  45.             protected void data(CharBuffer src, boolean endOfStream) throws IOException {
  46.                 // 收到一个请求就进行处理
  47.                 String ss = src.toString();
  48.                 // 通过data:进行分割,如果不进行此步,可能返回的答案会少一些内容
  49.                 for (String s : ss.split("data:")) {
  50.                     // 去除掉data:
  51.                     if (s.startsWith("data:")) {
  52.                         s = s.substring(5);
  53.                     }
  54.                     // 返回的数据可能是(DONE)
  55.                     if (s.length() > 8) {
  56.                         // 转换为对象
  57.                         ChatResponseParameter responseParameter = objectMapper.readValue(s, ChatResponseParameter.class);
  58.                         // 处理结果
  59.                         for (Choice choice : responseParameter.getChoices()) {
  60.                             String content = choice.getDelta().getContent();
  61.                             if (content != null && !"".equals(content)) {
  62.                                 // 保存结果
  63.                                 sb.append(content);
  64.                                 // 将结果使用webSocket传送过去
  65.                                 System.out.print(content);
  66.                             }
  67.                         }
  68.                     }
  69.                 }
  70.             }
  71.             @Override
  72.             protected HttpResponse buildResult() throws IOException {
  73.                 return response;
  74.             }
  75.             @Override
  76.             public void releaseResources() {
  77.             }
  78.         };
  79.         // 执行请求
  80.         asyncClient.execute(asyncRequest.build(), consumer, new FutureCallback<HttpResponse>() {
  81.             @Override
  82.             public void completed(HttpResponse response) {
  83.                 latch.countDown();
  84.                 chatGptRequestParameter.addMessages(new ChatMessage("assistant", sb.toString()));
  85.                 System.out.println("回答结束!!!");
  86.             }
  87.             @Override
  88.             public void failed(Exception ex) {
  89.                 latch.countDown();
  90.                 System.out.println("failed");
  91.                 ex.printStackTrace();
  92.             }
  93.             @Override
  94.             public void cancelled() {
  95.                 latch.countDown();
  96.                 System.out.println("cancelled");
  97.             }
  98.         });
  99.         try {
  100.             latch.await();
  101.         } catch (InterruptedException e) {
  102.             e.printStackTrace();
  103.         }
  104.     }
复制代码
  各人代码可以直接不看,反正终极的结果就是可以实现问了题目就返回结果。运行结果如下
  


   可以发现,输出就雷同于官方的那种结果,一个字一个字的输出
  
服务器端代码说明

我使用java搭建了一个简单的服务器端程序,提供最底子的用户登录校验功能,以及提供了WebSocket通信。
用户校验的代码
  1. package com.ttpfx.controller;
  2. import com.ttpfx.entity.User;
  3. import com.ttpfx.service.UserService;
  4. import com.ttpfx.utils.R;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import javax.annotation.Resource;
  8. import java.util.Objects;
  9. import java.util.concurrent.ConcurrentHashMap;
  10. /**
  11. * @author ttpfx
  12. * @date 2023/3/29
  13. */
  14. @RestController
  15. @RequestMapping("/user")
  16. public class UserController {
  17.     @Resource
  18.     private UserService userService;
  19.     public static ConcurrentHashMap<String, User> loginUser = new ConcurrentHashMap<>();
  20.     public static ConcurrentHashMap<String, Long> loginUserKey = new ConcurrentHashMap<>();
  21.     @RequestMapping("/login")
  22.     public R login(String username, String password) {
  23.         if (username == null) return R.fail("必须填写用户名");
  24.         User user = userService.queryByName(username);
  25.         if (user == null) return R.fail("用户名不存在");
  26.         String targetPassword = user.getPassword();
  27.         if (targetPassword == null) return R.fail("用户密码异常");
  28.         if (!targetPassword.equals(password)) return R.fail("密码错误");
  29.         loginUser.put(username, user);
  30.         loginUserKey.put(username, System.currentTimeMillis());
  31.         return R.ok(String.valueOf(loginUserKey.get(username)));
  32.     }
  33.     @RequestMapping("/logout")
  34.     public R logout(String username) {
  35.         loginUser.remove(username);
  36.         loginUserKey.remove(username);
  37.         return R.ok();
  38.     }
  39.     @RequestMapping("/checkUserKey")
  40.     public R checkUserKey(String username, Long key){
  41.         if (username==null || key == null)return R.fail("用户校验异常");
  42.         if (!Objects.equals(loginUserKey.get(username), key)){
  43.             return R.fail("用户在其他地方登录!!!");
  44.         }
  45.         return R.ok();
  46.     }
  47.     @RequestMapping("/loginUser")
  48.     public R loginUser(){
  49.         return R.ok("success",loginUser.keySet());
  50.     }
  51. }
复制代码
基于webSocket通信的代码
  1. package com.ttpfx.server;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import com.ttpfx.entity.UserLog;
  4. import com.ttpfx.model.ChatModel;
  5. import com.ttpfx.service.UserLogService;
  6. import com.ttpfx.service.UserService;
  7. import com.ttpfx.vo.chat.ChatRequestParameter;
  8. import org.springframework.stereotype.Component;
  9. import javax.annotation.Resource;
  10. import javax.websocket.*;
  11. import javax.websocket.server.PathParam;
  12. import javax.websocket.server.ServerEndpoint;
  13. import java.io.IOException;
  14. import java.time.LocalDateTime;
  15. import java.util.concurrent.ConcurrentHashMap;
  16. /**
  17. * @author ttpfx
  18. * @date 2023/3/28
  19. */
  20. @Component
  21. @ServerEndpoint("/chatWebSocket/{username}")
  22. public class ChatWebSocketServer {
  23.     /**
  24.      * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  25.      */
  26.     private static int onlineCount = 0;
  27.     /**
  28.      * concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象。
  29.      */
  30.     private static ConcurrentHashMap<String, ChatWebSocketServer> chatWebSocketMap = new ConcurrentHashMap<>();
  31.     /**
  32.      * 与某个客户端的连接会话,需要通过它来给客户端发送数据
  33.      */
  34.     private Session session;
  35.     /**
  36.      * 接收的username
  37.      */
  38.     private String username = "";
  39.     private UserLog userLog;
  40.     private static UserService userService;
  41.     private static UserLogService userLogService;
  42.     @Resource
  43.     public void setUserService(UserService userService) {
  44.         ChatWebSocketServer.userService = userService;
  45.     }
  46.     @Resource
  47.     public void setUserLogService(UserLogService userLogService) {
  48.         ChatWebSocketServer.userLogService = userLogService;
  49.     }
  50.     private ObjectMapper objectMapper = new ObjectMapper();
  51.     private static ChatModel chatModel;
  52.     @Resource
  53.     public void setChatModel(ChatModel chatModel) {
  54.         ChatWebSocketServer.chatModel = chatModel;
  55.     }
  56.     ChatRequestParameter chatRequestParameter = new ChatRequestParameter();
  57.     /**
  58.      * 建立连接
  59.      * @param session 会话
  60.      * @param username 连接用户名称
  61.      */
  62.     @OnOpen
  63.     public void onOpen(Session session, @PathParam("username") String username) {
  64.         this.session = session;
  65.         this.username = username;
  66.         this.userLog = new UserLog();
  67.         // 这里的用户id不可能为null,出现null,那么就是非法请求
  68.         try {
  69.             this.userLog.setUserId(userService.queryByName(username).getId());
  70.         } catch (Exception e) {
  71.             e.printStackTrace();
  72.             try {
  73.                 session.close();
  74.             } catch (IOException ex) {
  75.                 ex.printStackTrace();
  76.             }
  77.         }
  78.         this.userLog.setUsername(username);
  79.         chatWebSocketMap.put(username, this);
  80.         onlineCount++;
  81.         System.out.println(username + "--open");
  82.     }
  83.     @OnClose
  84.     public void onClose() {
  85.         chatWebSocketMap.remove(username);
  86.         System.out.println(username + "--close");
  87.     }
  88.     @OnMessage
  89.     public void onMessage(String message, Session session) {
  90.         System.out.println(username + "--" + message);
  91.         // 记录日志
  92.         this.userLog.setDateTime(LocalDateTime.now());
  93.         this.userLog.setPreLogId(this.userLog.getLogId() == null ? -1 : this.userLog.getLogId());
  94.         this.userLog.setLogId(null);
  95.         this.userLog.setQuestion(message);
  96.         long start = System.currentTimeMillis();
  97.         // 这里就会返回结果
  98.         String answer = chatModel.getAnswer(session, chatRequestParameter, message);
  99.         long end = System.currentTimeMillis();
  100.         this.userLog.setConsumeTime(end - start);
  101.         this.userLog.setAnswer(answer);
  102.         userLogService.save(userLog);
  103.     }
  104.     @OnError
  105.     public void onError(Session session, Throwable error) {
  106.         error.printStackTrace();
  107.     }
  108.     public void sendMessage(String message) throws IOException {
  109.         this.session.getBasicRemote().sendText(message);
  110.     }
  111.     public static void sendInfo(String message, String toUserId) throws IOException {
  112.         chatWebSocketMap.get(toUserId).sendMessage(message);
  113.     }
  114. }
复制代码
  我们只需要编写简单的前端代码,就可以实现和后端的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一下!!! 感谢各人了(≥▽≤)/(≥▽≤)/
更新日记

2023/5/13 14:42更新

对代码进行了重构,最新的代码已经支持代理,通过在application.yaml里面进行简单配置即可使用
  1. gpt:
  2.   proxy:
  3.     host: 127.0.0.1
  4.     port: 7890
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表