使用chatgpt實(shí)現(xiàn)微信聊天小程序的代碼示例
前言
前一段時(shí)間使用java來調(diào)用chatgpt的接口,然后寫了一個(gè)簡單小程序,java調(diào)用chatgpt接口,實(shí)現(xiàn)專屬于自己的人工智能助手,事實(shí)上,這個(gè)程序毛病挺多的,最不能讓人接受的一點(diǎn)就是返回速度非常緩慢(即使使用非常好的外網(wǎng)服務(wù)器)。
現(xiàn)在,我改進(jìn)了一下程序,使用異步請求的方式,基本可以實(shí)現(xiàn)秒回復(fù)。并且還基于webSocket編寫了一個(gè)微信小程序來進(jìn)行交互,可以直接使用微信小程序來進(jìn)行體驗(yàn)。
效果展示
部分截圖如下





原理說明
在 java調(diào)用chatgpt接口,實(shí)現(xiàn)專屬于自己的人工智能助手 我說明了java調(diào)用chatgpt的基本原理,這里的代碼就是對這個(gè)代碼的改進(jìn),使用異步請求的方式來進(jìn)行。

注意看官方文檔,我們在請求時(shí)可以提供一個(gè)參數(shù)stream,然后就可以實(shí)現(xiàn)按照流的形式進(jìn)行返回,這種方式基本可以做到?jīng)]有延遲就給出答案。
由于這次改進(jìn)的思路主要就是將請求改為了異步,其他的基本一樣,所以就不做解釋,直接給出代碼了,代碼上面都有注釋
/**
* 這個(gè)方法用于測試的,可以在控制臺打印輸出結(jié)果
*
* @param chatGptRequestParameter 請求的參數(shù)
* @param question 問題
*/
public void printAnswer(ChatRequestParameter chatGptRequestParameter, String question) {
asyncClient.start();
// 創(chuàng)建一個(gè)post請求
AsyncRequestBuilder asyncRequest = AsyncRequestBuilder.post(url);
// 設(shè)置請求參數(shù)
chatGptRequestParameter.addMessages(new ChatMessage("user", question));
// 請求的參數(shù)轉(zhuǎn)換為字符串
String valueAsString = null;
try {
valueAsString = objectMapper.writeValueAsString(chatGptRequestParameter);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// 設(shè)置編碼和請求參數(shù)
ContentType contentType = ContentType.create("text/plain", charset);
asyncRequest.setEntity(valueAsString, contentType);
asyncRequest.setCharset(charset);
// 設(shè)置請求頭
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è)請求就進(jìn)行處理
String ss = src.toString();
// 通過data:進(jìn)行分割,如果不進(jìn)行此步,可能返回的答案會少一些內(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)換為對象
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傳送過去
System.out.print(content);
}
}
}
}
}
@Override
protected HttpResponse buildResult() throws IOException {
return response;
}
@Override
public void releaseResources() {
}
};
// 執(zhí)行請求
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)問了問題就返回結(jié)果。運(yùn)行效果如下


可以發(fā)現(xiàn),輸出就類似于官方的那種效果,一個(gè)字一個(gè)字的輸出
服務(wù)器端代碼說明
我使用java搭建了一個(gè)簡單的服務(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)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的線程安全Map,用來存放每個(gè)客戶端對應(yīng)的MyWebSocket對象。
*/
private static ConcurrentHashMap<String, ChatWebSocketServer> chatWebSocketMap = new ConcurrentHashMap<>();
/**
* 與某個(gè)客戶端的連接會話,需要通過它來給客戶端發(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 會話
* @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,那么就是非法請求
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();
// 這里就會返回結(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);
}
}
我們只需要編寫簡單的前端代碼,就可以實(shí)現(xiàn)和后端的socket通信。對于后端,我們只需要改一下apiKey和數(shù)據(jù)庫配置就可以直接運(yùn)行了。
微信小程序代碼說明
我寫了一個(gè)簡單微信小程序來和后端進(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里面說明了安裝使用的基本步驟,大家按照步驟使用即可
總結(jié)
上面聊天小程序就是我花2天寫出來的,可能會有一些bug,我自己測試的時(shí)候倒是沒有怎么遇到bug,聊天和登錄功能都能正常使用。
對于微信小程序,由于我不是專業(yè)搞前端的,就只東拼西湊實(shí)現(xiàn)了最基本的功能(登錄、聊天),大家可以自己寫一個(gè),反正后端接口都提供好了嘛,也不是很難,不想寫也可以將就使用我的。
更新日志
對代碼進(jìn)行了重構(gòu),最新的代碼已經(jīng)支持代理,通過在application.yaml里面進(jìn)行簡單配置即可使用
gpt:
proxy:
host: 127.0.0.1
port: 7890
以上就是使用chatgpt實(shí)現(xiàn)微信聊天小程序的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于chatgpt微信聊天小程序的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java之Spring認(rèn)證使用Profile配置運(yùn)行環(huán)境講解
這篇文章主要介紹了Java之Spring認(rèn)證使用Profile配置運(yùn)行環(huán)境講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
Java實(shí)現(xiàn)監(jiān)控多個(gè)線程狀態(tài)的簡單實(shí)例
下面小編就為大家?guī)硪黄狫ava實(shí)現(xiàn)監(jiān)控多個(gè)線程狀態(tài)的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03
SpringMVC統(tǒng)一異常處理實(shí)例代碼
這篇文章主要介紹了SpringMVC統(tǒng)一異常處理實(shí)例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
SpringMVC數(shù)據(jù)頁響應(yīng)ModelAndView實(shí)現(xiàn)頁面跳轉(zhuǎn)
本文主要介紹了SpringMVC數(shù)據(jù)頁響應(yīng)ModelAndView實(shí)現(xiàn)頁面跳轉(zhuǎn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
微信公眾號開發(fā)之設(shè)置自定義菜單實(shí)例代碼【java版】
這篇文章主要介紹了微信公眾號開發(fā)之設(shè)置自定義菜單實(shí)例代碼,本實(shí)例是為了實(shí)現(xiàn)在管理后臺實(shí)現(xiàn)微信菜單的添加刪除管理。需要的朋友可以參考下2018-06-06

