?目录
一、介绍1、使用技术2、效果 二、代码1、前端代码2、后端代码2.1、maven依赖2.2、model2.2.1、请求接口的格式2.2.2、响应数据对象 2.3、工具类2.3.1、?使用WebClient调用`chatgpt`方法2.3.2、? webSocket连接对话方法 2.4、Controller
一、介绍
通过java实现对chatGPT的API接口实现websocket流式输出以及接口调用两种方式代码
1、使用技术
使用到的技术包括WebClient
、webSocket
加thymeleaf
WebClient
:客户端的使用可以开 ?java http客户端webSocket
:可以看?webSokcet 使用thymeleaf
:可以网上直接找教程,我使用的比较简单会掉用就行,或者使用html静态页面也可以 2、效果
websocket的具体实现效果如下,非专业前端,页面比较简陋,可以自行优化页面接口调用
二、代码
1、前端代码
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>Java后端WebSocket的Tomcat实现</title> <script type="text/javascript" src="js/jquery.min.js"></script></head><body><strong>ChatGPT使用</strong> <br /> <input id="text" type="text" onkeydown="checkEnter(event)"/><button onclick="send()">发送消息</button><hr /><button onclick="closeWebSocket()">关闭WebSocket连接</button><hr /><div id="message"></div></body><script type="text/javascript"> var websocket = null; //判断当前浏览器是否支持WebSocket if ('WebSocket' in window) { //改成你的地址 websocket = new WebSocket("ws://localhost:8088/websocket/100"); } else { alert('当前浏览器 Not support websocket') } //连接发生错误的回调方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket连接发生错误"); }; //连接成功建立的回调方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket连接成功"); } var U01data, Uidata, Usdata //接收到消息的回调方法 websocket.onmessage = function (event) { console.log(event); if (event.data !== "conn_success") { setMessageInnerHTML(event.data); // setMessageInnerHTML(event); setechart() } } //连接关闭的回调方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket连接关闭"); } // //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { closeWebSocket(); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML ; } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); } //发送消息 function send() { var message = document.getElementById('text').value; websocket.send('{"msg":"' + message + '"}'); setMessageInnerHTML("<br>--------------发送消息:" + message + "<br>"); document.getElementById('text').value = null; } function checkEnter(event) { if (event.keyCode === 13) { send(); } }</script></html>
2、后端代码
2.1、maven依赖
<!--thymeleaf模版的--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <!--websocket的--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!--webClient--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.22</version> </dependency>
2.2、model
2.2.1、请求接口的格式
如果想简单的话也可以不用封装对象,直接使用json
@Data@NoArgsConstructor@AllArgsConstructorpublic class ChatGptRequestParameter { private String model = "gpt-3.5-turbo-16k-0613";// 是否支持流式输出 private boolean stream = true;// 请求对象数组 List<ChatGptMessage> messages=new ArrayList(); public void addMessages(ChatGptMessage message) { this.messages.add(message); }}
ChatGptMessage @Data@NoArgsConstructor@AllArgsConstructorpublic class ChatGptMessage implements Serializable { String role; String content;}
2.2.2、响应数据对象
@Data@NoArgsConstructor@AllArgsConstructorpublic class ChatGptResponseParameter implements Serializable { String id; String object; String created; String model; Usage usage; List<Choices> choices; String system_fingerprint;}
Choices @Data@NoArgsConstructor@AllArgsConstructorpublic class Choices implements Serializable { ChatGptMessage delta; String finish_reason; Integer index; String logprobs;}
ChatGptMessage @Data@NoArgsConstructor@AllArgsConstructorpublic class ChatGptMessage implements Serializable { String role; String content;}
2.3、工具类
2.3.1、?使用WebClient调用chatgpt
方法
@Component@Slf4jpublic class WebChatGPT { /** * 自己chatGpt的ApiKey */ private String apiKey = "sk-***"; private ChatGptRequestParameter chatGptRequestParameter = new ChatGptRequestParameter(); /** * 推送 * @param session webSocket会话对象 * @param str 请求数据 * @return Flux<String> */ public Flux<String> getAnswer(Session session, String str) { ChatGptMessage chatGptMessage = new ChatGptMessage("user", str); // 保存消息,用于记录前面的消息,方便上下文记忆,因为3.5不能自动实现上下文记忆 chatGptRequestParameter.addMessages(chatGptMessage); return webClient().post() .accept(MediaType.TEXT_EVENT_STREAM) //接收text/event-stream流的数据 .body(BodyInserters.fromValue(chatGptRequestParameter)) //参数 .retrieve() //执行请求并获取响应结果 .bodyToFlux(String.class) // 将响应体转换为 Flux 类型,这里是将 SSE 流转换为字符串类型。 .map(s -> { //对每个响应元素进行处理 log.info("Gpt输出:{}", s); if (!Objects.equals(s, "[DONE]")) { ChatGptMessage message = JSONUtil.toBean(s, ChatGptResponseParameter.class).getChoices().get(0).getDelta(); String content = message.getContent(); if (content != null) { try { if (session != null) {// 通过websocket回写到页面 session.getBasicRemote().sendText(content); } log.info("Gpt输出:{}", s); } catch (IOException e) { e.printStackTrace(); } return content; } } return ""; }) .onErrorResume(WebClientResponseException.class, ex -> Flux.just(ex.getResponseBodyAsString())) //请求失败 .doFinally(signalType -> log.info("完成{}", signalType)); //在 Flux 完成时执行,无论是成功还是错误,都会打印日志表示请求完成。 } private WebClient webClient(){ return WebClient.builder()// .clientConnector(new ReactorClientHttpConnector(// HttpClient.create().proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP).host("127.0.0.1").port(1080)) //代理// )) .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8") .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey) .baseUrl("https://api.openai.com/v1/chat/completions") //官方api请求地址 .build(); }}
2.3.2、? webSocket连接对话方法
通过该方法和html页面建立websocket连接
@Slf4j@Service@ServerEndpoint("/websocket/{uid}")@Componentpublic class WebSocketServer2 { //连接建立时长 private static final long sessionTimeout = 600000; // 用来存放每个客户端对应的WebSocketServer对象 private static Map<String, WebSocketServer2> webSocketMap = new ConcurrentHashMap<>(); // 与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; // 接收id private String uid; private static WebChatGPT webChatGPT; static { webChatGPT = SpringUtil.getBean(WebChatGPT.class); } /** * 连接建立成功调用的方法 * @author zhengfuping * @date 2023/8/22 * @param session * @param uid */ @OnOpen public void onOpen(Session session , @PathParam("uid") String uid){ session.setMaxIdleTimeout(sessionTimeout); this.session = session; this.uid = uid; if (webSocketMap.containsKey(uid)){ webSocketMap.remove(uid); } webSocketMap.put(uid,this); log.info("websocket连接成功编号uid: " + uid + ",当前在线数: " + getOnlineClients()); try{ // 响应客户端实际业务数据! sendMessage("conn_success"); }catch (Exception e){ log.error("websocket发送连接成功错误编号uid: " + uid + ",网络异常!!!"); } } /** * 连接关闭调用的方法 * @author zhengfuping * @date 2023/8/22 */ @OnClose public void onClose(){ try { if (webSocketMap.containsKey(uid)){ webSocketMap.remove(uid); } log.info("websocket退出编号uid: " + uid + ",当前在线数为: " + getOnlineClients()); } catch (Exception e) { log.error("websocket编号uid连接关闭错误: " + uid + ",原因: " + e.getMessage()); } } /** * 收到客户端消息后调用chatGpt的方法 * @param message 客户端发送过来的消息 * @param session */ @OnMessage public void onMessage(String message, Session session) { try { JSON parse = JSONUtil.parse(message); String msg = parse.getByPath("msg").toString(); if (StrUtil.isNotEmpty(msg)){// 调用 Flux<String> answer = webChatGPT.getAnswer(session, msg);// 触发实际的请求操作 answer.subscribe(); } } catch (Exception e) { log.error("websocket发送消息失败编号uid为: " + uid + ",报文: " + message); } } /** * 发生错误时调用 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("websocket编号uid错误: " + this.uid + "原因: " + error.getMessage()); error.printStackTrace(); } /** * 实现服务器主动推送 * @author yingfeng * @date 2023/8/22 10:11 * @Param * @param null * @return */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 获取客户端在线数 * @author zhengfuping * @date 2023/8/22 10:11 * @param */ public static synchronized int getOnlineClients() { if (Objects.isNull(webSocketMap)) { return 0; } else { return webSocketMap.size(); } } }
2.4、Controller
跳转thymeleaf页的接口@Controllerpublic class ChatWeb { @RequestMapping("/webSocket") public String webSocket(){ return "webSocket"; }}
ChatGptController:页面请求访问 @RestController@RequestMapping("/chat")public class ChatGptController { @Resource private WebChatGPT webChatGPT; @GetMapping(value ="/stringFlux", produces = "application/json; charset=utf-8") public Flux<String> stringFlux(String c) { Flux<String> flux = webChatGPT.getAnswer(null,c); return flux; }}