使用chatgpt實(shí)現(xiàn)微信聊天小程序的代碼示例
前言
前一段時(shí)間使用java來(lái)調(diào)用chatgpt的接口,然后寫了一個(gè)簡(jiǎn)單小程序,java調(diào)用chatgpt接口,實(shí)現(xiàn)專屬于自己的人工智能助手,事實(shí)上,這個(gè)程序毛病挺多的,最不能讓人接受的一點(diǎn)就是返回速度非常緩慢(即使使用非常好的外網(wǎng)服務(wù)器)。
現(xiàn)在,我改進(jìn)了一下程序,使用異步請(qǐng)求的方式,基本可以實(shí)現(xiàn)秒回復(fù)。并且還基于webSocket編寫了一個(gè)微信小程序來(lái)進(jìn)行交互,可以直接使用微信小程序來(lái)進(jìn)行體驗(yàn)。
效果展示
部分截圖如下
原理說(shuō)明
在 java調(diào)用chatgpt接口,實(shí)現(xiàn)專屬于自己的人工智能助手 我說(shuō)明了java調(diào)用chatgpt的基本原理,這里的代碼就是對(duì)這個(gè)代碼的改進(jìn),使用異步請(qǐng)求的方式來(lái)進(jìn)行。
注意看官方文檔,我們?cè)谡?qǐng)求時(shí)可以提供一個(gè)參數(shù)stream,然后就可以實(shí)現(xiàn)按照流的形式進(jìn)行返回,這種方式基本可以做到?jīng)]有延遲就給出答案。
由于這次改進(jìn)的思路主要就是將請(qǐng)求改為了異步,其他的基本一樣,所以就不做解釋,直接給出代碼了,代碼上面都有注釋
/** * 這個(gè)方法用于測(cè)試的,可以在控制臺(tái)打印輸出結(jié)果 * * @param chatGptRequestParameter 請(qǐng)求的參數(shù) * @param question 問(wèn)題 */ public void printAnswer(ChatRequestParameter chatGptRequestParameter, String question) { asyncClient.start(); // 創(chuàng)建一個(gè)post請(qǐng)求 AsyncRequestBuilder asyncRequest = AsyncRequestBuilder.post(url); // 設(shè)置請(qǐng)求參數(shù) chatGptRequestParameter.addMessages(new ChatMessage("user", question)); // 請(qǐng)求的參數(shù)轉(zhuǎn)換為字符串 String valueAsString = null; try { valueAsString = objectMapper.writeValueAsString(chatGptRequestParameter); } catch (JsonProcessingException e) { e.printStackTrace(); } // 設(shè)置編碼和請(qǐng)求參數(shù) ContentType contentType = ContentType.create("text/plain", charset); asyncRequest.setEntity(valueAsString, contentType); asyncRequest.setCharset(charset); // 設(shè)置請(qǐng)求頭 asyncRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); // 設(shè)置登錄憑證 asyncRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey); // 下面就是生產(chǎn)者消費(fèi)者模型 CountDownLatch latch = new CountDownLatch(1); // 用于記錄返回的答案 StringBuilder sb = new StringBuilder(); // 消費(fèi)者 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 { // 收到一個(gè)請(qǐng)求就進(jìn)行處理 String ss = src.toString(); // 通過(guò)data:進(jìn)行分割,如果不進(jìn)行此步,可能返回的答案會(huì)少一些內(nèi)容 for (String s : ss.split("data:")) { // 去除掉data: if (s.startsWith("data:")) { s = s.substring(5); } // 返回的數(shù)據(jù)可能是(DONE) if (s.length() > 8) { // 轉(zhuǎn)換為對(duì)象 ChatResponseParameter responseParameter = objectMapper.readValue(s, ChatResponseParameter.class); // 處理結(jié)果 for (Choice choice : responseParameter.getChoices()) { String content = choice.getDelta().getContent(); if (content != null && !"".equals(content)) { // 保存結(jié)果 sb.append(content); // 將結(jié)果使用webSocket傳送過(guò)去 System.out.print(content); } } } } } @Override protected HttpResponse buildResult() throws IOException { return response; } @Override public void releaseResources() { } }; // 執(zhí)行請(qǐng)求 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("回答結(jié)束?。?!"); } @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(); } }
大家代碼可以直接不看,反正最終的效果就是可以實(shí)現(xiàn)問(wèn)了問(wèn)題就返回結(jié)果。運(yùn)行效果如下
可以發(fā)現(xiàn),輸出就類似于官方的那種效果,一個(gè)字一個(gè)字的輸出
服務(wù)器端代碼說(shuō)明
我使用java搭建了一個(gè)簡(jiǎn)單的服務(wù)器端程序,提供最基礎(chǔ)的用戶登錄校驗(yàn)功能,以及提供了WebSocket通信。
用戶校驗(yàn)的代碼
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("密碼錯(cuò)誤"); 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("用戶校驗(yàn)異常"); 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 { /** * 靜態(tài)變量,用來(lái)記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。 */ private static int onlineCount = 0; /** * concurrent包的線程安全Map,用來(lái)存放每個(gè)客戶端對(duì)應(yīng)的MyWebSocket對(duì)象。 */ private static ConcurrentHashMap<String, ChatWebSocketServer> chatWebSocketMap = new ConcurrentHashMap<>(); /** * 與某個(gè)客戶端的連接會(huì)話,需要通過(guò)它來(lái)給客戶端發(fā)送數(shù)據(jù) */ 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 會(huì)話 * @param username 連接用戶名稱 */ @OnOpen public void onOpen(Session session, @PathParam("username") String username) { this.session = session; this.username = username; this.userLog = new UserLog(); // 這里的用戶id不可能為null,出現(xiàn)null,那么就是非法請(qǐng)求 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(); // 這里就會(huì)返回結(jié)果 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); } }
我們只需要編寫簡(jiǎn)單的前端代碼,就可以實(shí)現(xiàn)和后端的socket通信。對(duì)于后端,我們只需要改一下apiKey和數(shù)據(jù)庫(kù)配置就可以直接運(yùn)行了。
微信小程序代碼說(shuō)明
我寫了一個(gè)簡(jiǎn)單微信小程序來(lái)和后端進(jìn)行通信,界面如下
大家只需要下載源代碼,然將程序中的ip改為自己服務(wù)器的ip即可
代碼鏈接
github的地址為 https://github.com/c-ttpfx/chatgpt-java-wx
可以直接使用 git clone https://github.com/c-ttpfx/chatgpt-java-wx.git 下載代碼到本地
我在github里面說(shuō)明了安裝使用的基本步驟,大家按照步驟使用即可
總結(jié)
上面聊天小程序就是我花2天寫出來(lái)的,可能會(huì)有一些bug,我自己測(cè)試的時(shí)候倒是沒(méi)有怎么遇到bug,聊天和登錄功能都能正常使用。
對(duì)于微信小程序,由于我不是專業(yè)搞前端的,就只東拼西湊實(shí)現(xiàn)了最基本的功能(登錄、聊天),大家可以自己寫一個(gè),反正后端接口都提供好了嘛,也不是很難,不想寫也可以將就使用我的。
更新日志
對(duì)代碼進(jìn)行了重構(gòu),最新的代碼已經(jīng)支持代理,通過(guò)在application.yaml里面進(jìn)行簡(jiǎn)單配置即可使用
gpt: proxy: host: 127.0.0.1 port: 7890
以上就是使用chatgpt實(shí)現(xiàn)微信聊天小程序的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于chatgpt微信聊天小程序的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java之Spring認(rèn)證使用Profile配置運(yùn)行環(huán)境講解
這篇文章主要介紹了Java之Spring認(rèn)證使用Profile配置運(yùn)行環(huán)境講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07Java實(shí)現(xiàn)監(jiān)控多個(gè)線程狀態(tài)的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇Java實(shí)現(xiàn)監(jiān)控多個(gè)線程狀態(tài)的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03SpringMVC統(tǒng)一異常處理實(shí)例代碼
這篇文章主要介紹了SpringMVC統(tǒng)一異常處理實(shí)例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11SpringMVC數(shù)據(jù)頁(yè)響應(yīng)ModelAndView實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)
本文主要介紹了SpringMVC數(shù)據(jù)頁(yè)響應(yīng)ModelAndView實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Java中對(duì)象的創(chuàng)建和銷毀過(guò)程詳析
這篇文章主要介紹了Java中對(duì)象的創(chuàng)建和銷毀過(guò)程,對(duì)象的創(chuàng)建過(guò)程包括類加載檢查、內(nèi)存分配、初始化零值內(nèi)存、設(shè)置對(duì)象頭和執(zhí)行init方法,對(duì)象的銷毀過(guò)程由垃圾回收機(jī)制負(fù)責(zé),文中介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02微信公眾號(hào)開(kāi)發(fā)之設(shè)置自定義菜單實(shí)例代碼【java版】
這篇文章主要介紹了微信公眾號(hào)開(kāi)發(fā)之設(shè)置自定義菜單實(shí)例代碼,本實(shí)例是為了實(shí)現(xiàn)在管理后臺(tái)實(shí)現(xiàn)微信菜單的添加刪除管理。需要的朋友可以參考下2018-06-06