当前位置: 首页 > news >正文

Android studio前沿开发--利用socket服务器连接AI实现前后端交互(全站首发思路)

我们在前几期学习了利用socket进行前后端的交互,但那只是基础性知识,这次,通过参考讯飞星火的java参考文档,再结合之前所学的socket服务,成功实现了通过后端将AI的调用实现在了自己的APP中。

本次的学习内容

1.真机的调用

2.讯飞星火账号appid的申请与调用

3.前后端的交互

4.服务器与AI的结合

1.真机的调用

打开真机的开发者模式,并用USB线将电脑与手机连接起来,并在手机的选中文件传输,让你的手机打开流量并开启热点,并保证你的电脑连接到这个热点,接下来,打开你电脑的命令提示窗口,输入以下命令:
请添加图片描述
获得IPv4地址,这个地址就是你的手机ip地址,记录下来后面要用到,这样你的手机已经可以连接到你的电脑后端了。

2.申请讯飞星火的账号

这个没什么好说的在讯飞星火的开发平台中申请一个账号,
请添加图片描述
并将这三个key记录下来,这是连接AI的必要条件,之后下载讯飞星火的Java示例代码,之后我们的服务器要在它的示例代码上进行修改:
请添加图片描述
当然,讯飞星火的AI也可以直接在app前端中进行调用,但目前版本适配性过低,而且限定的Java与SDK的版本,对版本不统一的使用者来说十分不友好,所以我还是建议通过后端去调用AI,避免前端线程的混乱。

3.前后端的交互

这里的代码与Android studio进阶开发(三)–socket通信服务的使用的代码几乎是一致的,只是把其中的IP地址转换为真机的地址
这里直接放代码:
Android:
1.创建dateuitl,获取当前时间

package com.example.newsoket;import android.annotation.SuppressLint;import java.text.SimpleDateFormat;
import java.util.Date;@SuppressLint("SimpleDateFormat")
public class DateUtil {// 获取当前的日期时间public static String getNowDateTime() {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");return sdf.format(new Date());}// 获取当前的时间public static String getNowTime() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");return sdf.format(new Date());}// 获取当前的分钟public static String getNowMinute() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");return sdf.format(new Date());}// 获取当前的时间(精确到毫秒)public static String getNowTimeDetail() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");return sdf.format(new Date());}// 将长整型的时间数值格式化为日期时间字符串public static String formatDate(long time) {Date date = new Date(time);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf.format(date);}}

2.创建NetConst ,初始化端口和IP地址

package com.example.newsoket;public class NetConst {// HTTP地址的前缀public final static String HTTP_PREFIX = "http://192.168.1.7:8080/HttpServer/";// WebSocket服务的前缀public final static String WEBSOCKET_PREFIX = "ws://192.168.1.7:8080/HttpServer/";//public final static String BASE_IP = "192.168.1.7"; // 基础Socket服务的ippublic final static String BASE_IP = "192.168.43.9"; // 基础Socket服务的ip(这里要改为前面获取的IPv4的地址)public final static int BASE_PORT = 9010; // 基础Socket服务的端口public final static String CHAT_IP = "192.168.1.7"; // 聊天Socket服务的ippublic final static int CHAT_PORT = 9011; // 聊天Socket服务的端口
}

请添加图片描述

这里一定要改,否则连接不上后端服务器


3.创建SocketUtil 与socket连接进行判断

package com.example.newsoket;import android.app.Activity;
import android.widget.Toast;import com.google.gson.Gson;import org.json.JSONObject;import java.net.InetSocketAddress;
import java.net.SocketAddress;import io.socket.client.Socket;public class SocketUtil {// 把对象数据转换为json串,然后发给Socket服务器public static void emit(Socket socket, String event, Object obj) {try {JSONObject json = new JSONObject(new Gson().toJson(obj));socket.emit(event, json);} catch (Exception e) {e.printStackTrace();}}// 判断Socket能否连通public static void checkSocketAvailable(Activity act, String host, int port) {new Thread(() -> {try (java.net.Socket socket = new java.net.Socket()) {SocketAddress address = new InetSocketAddress(host, port);socket.connect(address, 1500);} catch (Exception e) {e.printStackTrace();act.runOnUiThread(() -> {Toast.makeText(act, "无法连接Socket服务器", Toast.LENGTH_SHORT).show();});}}).start();}}

4.创建sockettake.activity进行交互
xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="5dp" ><EditTextandroid:id="@+id/et_input"android:layout_width="match_parent"android:layout_height="40dp"android:background="@drawable/editext_selector"<!可有可无,可以改为自己想要的>android:hint="请输入聊天内容"android:textColor="@color/black"android:textSize="17sp" /><Buttonandroid:id="@+id/btn_send"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="发送文本消息"android:textColor="@color/black"android:textSize="17sp" /><TextViewandroid:id="@+id/tv_response"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/black"android:textSize="17sp" />
</LinearLayout>

java:

package com.example.newsoket;import android.os.Bundle;import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import com.example.newsoket.NetConst;
import com.example.newsoket.DateUtil;
import com.example.newsoket.SocketUtil;import java.net.URISyntaxException;import io.socket.client.IO;
import io.socket.client.Socket;import java.net.URISyntaxException;public class Sockettext extends AppCompatActivity {private static final String TAG = "SocketioTextActivity";private EditText et_input; // 声明一个编辑框对象private TextView tv_response; // 声明一个文本视图对象private Socket mSocket; // 声明一个套接字对象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_sockettext);et_input = findViewById(R.id.et_input);tv_response = findViewById(R.id.tv_response);findViewById(R.id.btn_send).setOnClickListener(v -> {String content = et_input.getText().toString();if (TextUtils.isEmpty(content)) {Toast.makeText(this, "请输入聊天消息", Toast.LENGTH_SHORT).show();return;}mSocket.emit("send_text", content); // 往Socket服务器发送文本消息});initSocket(); // 初始化套接字}// 初始化套接字private void initSocket() {// 检查能否连上Socket服务器SocketUtil.checkSocketAvailable(this, NetConst.BASE_IP, NetConst.BASE_PORT);try {String uri = String.format("http://%s:%d/", NetConst.BASE_IP, NetConst.BASE_PORT);mSocket = IO.socket(uri); // 创建指定地址和端口的套接字实例} catch (URISyntaxException e) {throw new RuntimeException(e);}mSocket.connect(); // 建立Socket连接// 等待接收传来的文本消息mSocket.on("receive_text", (args) -> {String desc = String.format("%s 收到服务端消息:%s",DateUtil.getNowTime(), (String) args[0]);runOnUiThread(() -> tv_response.setText(desc));});}@Overrideprotected void onDestroy() {super.onDestroy();mSocket.off("receive_text"); // 取消接收传来的文本消息if (mSocket.connected()) { // 已经连上Socket服务器mSocket.disconnect(); // 断开Socket连接}mSocket.close(); // 关闭Socket连接}
}

后端idea的代码:

package com.socketio.server;import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;public class SocketServer {public static void main(String[] args) {Configuration config = new Configuration();// 如果调用了setHostname方法,就只能通过主机名访问,不能通过IP访问//config.setHostname("localhost");config.setPort(9010); // 设置监听端口final SocketIOServer server = new SocketIOServer(config);// 添加连接连通的监听事件server.addConnectListener(client -> {System.out.println(client.getSessionId().toString()+"已连接");});// 添加连接断开的监听事件server.addDisconnectListener(client -> {System.out.println(client.getSessionId().toString()+"已断开");});// 添加文本发送的事件监听器server.addEventListener("send_text", String.class, (client, message, ackSender) -> {System.out.println(client.getSessionId().toString()+"发送文本消息:"+message);client.sendEvent("receive_text", "我已收到,马上做出回应。");});// 添加图像发送的事件监听器server.addEventListener("send_image", JSONObject.class, (client, json, ackSender) -> {String desc = String.format("%s,序号为%d", json.getString("name"), json.getIntValue("seq"));System.out.println(client.getSessionId().toString()+"发送图片消息:"+desc);client.sendEvent("receive_image", json);});server.start(); // 启动Socket服务}}

我们先来看下之前没有放出的示例图:
一定要先运行服务端的代码,再打开APP调试
请添加图片描述
请添加图片描述
这样前后端就有了响应。

4.服务器与AI的结合

还记得我们的标题吗,没错是我们与AI的互动,也就说,我们在对话框中输出文字并发送之后,给予我们回复的不在是固定的文字,而是AI根据我们的问题进行回复,实现基本的人机交互。
(1)解压在讯飞星火下载的压缩包
请添加图片描述请添加图片描述

用idea打开文件夹下的讯飞大模型请添加图片描述
将这部分填好,试着运行一下:
请添加图片描述
在下方中的“我”后面输入问题,看看是否有“大模型”进行回复

我们先来看一下两段代码

socket通信:

package com.socketio.server;import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;public class SocketServer {public static void main(String[] args) {Configuration config = new Configuration();// 如果调用了setHostname方法,就只能通过主机名访问,不能通过IP访问//config.setHostname("localhost");config.setPort(9010); // 设置监听端口final SocketIOServer server = new SocketIOServer(config);// 添加连接连通的监听事件server.addConnectListener(client -> {System.out.println(client.getSessionId().toString()+"已连接");});// 添加连接断开的监听事件server.addDisconnectListener(client -> {System.out.println(client.getSessionId().toString()+"已断开");});// 添加文本发送的事件监听器server.addEventListener("send_text", String.class, (client, message, ackSender) -> {System.out.println(client.getSessionId().toString()+"发送文本消息:"+message);client.sendEvent("receive_text", "我已收到,马上做出回应。");});// 添加图像发送的事件监听器server.addEventListener("send_image", JSONObject.class, (client, json, ackSender) -> {String desc = String.format("%s,序号为%d", json.getString("name"), json.getIntValue("seq"));System.out.println(client.getSessionId().toString()+"发送图片消息:"+desc);client.sendEvent("receive_image", json);});server.start(); // 启动Socket服务}}

AI大模型(BigModelNew类 )

注意:是src底下的类,target底下的代码受到保护,无法编译

请添加图片描述

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOServer;
import com.google.gson.Gson;
import okhttp3.*;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;public class BigModelNew extends WebSocketListener {// 各版本的hostUrl及其对应的domian参数,具体可以参考接口文档 https://www.xfyun.cn/doc/spark/Web.html// Spark Lite      https://spark-api.xf-yun.com/v1.1/chat      domain参数为lite// Spark Pro       https://spark-api.xf-yun.com/v3.1/chat      domain参数为generalv3// Spark Pro-128K  https://spark-api.xf-yun.com/chat/pro-128k  domain参数为pro-128k// Spark Max       https://spark-api.xf-yun.com/v3.5/chat      domain参数为generalv3.5// Spark Max-32K   https://spark-api.xf-yun.com/chat/max-32k   domain参数为max-32k// Spark4.0 Ultra  https://spark-api.xf-yun.com/v4.0/chat      domain参数为4.0Ultrapublic static final String hostUrl = "https://spark-api.xf-yun.com/v3.1/chat";public static final String domain = "generalv3";public static final String appid = "你的appid";public static final String apiSecret = "你的apiSecret";public static final String apiKey = "你的apiKey";public static List<RoleContent> historyList=new ArrayList<>(); // 对话历史存储集合public static String totalAnswer=""; // 大模型的答案汇总// 环境治理的重要性  环保  人口老龄化  我爱我的祖国public static  String NewQuestion = "";public static final Gson gson = new Gson();// 个性化参数private String userId;private Boolean wsCloseFlag;private static Boolean totalFlag=true; // 控制提示用户是否输入// 构造函数public BigModelNew(String userId, Boolean wsCloseFlag) {this.userId = userId;this.wsCloseFlag = wsCloseFlag;}// 主函数public static void main(String[] args) throws Exception {while (true){if(totalFlag){Scanner scanner=new Scanner(System.in);System.out.print("我:");totalFlag=false;NewQuestion=scanner.nextLine();// 构建鉴权urlString authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);OkHttpClient client = new OkHttpClient.Builder().build();String url = authUrl.toString().replace("https://", "ws://").replace("https://", "wss://");Request request = new Request.Builder().url(url).build();for (int i = 0; i < 1; i++) {totalAnswer="";WebSocket webSocket = client.newWebSocket(request, new BigModelNew(i + "",false));}}else{Thread.sleep(200);}}}public static boolean canAddHistory(){  // 由于历史记录最大上线1.2W左右,需要判断是能能加入历史int history_length=0;for(RoleContent temp:historyList){history_length=history_length+temp.content.length();}if(history_length>12000){historyList.remove(0);historyList.remove(1);historyList.remove(2);historyList.remove(3);historyList.remove(4);return false;}else{return true;}}// 线程来发送音频与参数class MyThread extends Thread {private WebSocket webSocket;public MyThread(WebSocket webSocket) {this.webSocket = webSocket;}public void run() {try {JSONObject requestJson=new JSONObject();JSONObject header=new JSONObject();  // header参数header.put("app_id",appid);header.put("uid",UUID.randomUUID().toString().substring(0, 10));JSONObject parameter=new JSONObject(); // parameter参数JSONObject chat=new JSONObject();chat.put("domain",domain);chat.put("temperature",0.5);chat.put("max_tokens",4096);parameter.put("chat",chat);JSONObject payload=new JSONObject(); // payload参数JSONObject message=new JSONObject();JSONArray text=new JSONArray();// 历史问题获取if(historyList.size()>0){for(RoleContent tempRoleContent:historyList){text.add(JSON.toJSON(tempRoleContent));}}// 最新问题RoleContent roleContent=new RoleContent();roleContent.role="user";roleContent.content=NewQuestion;text.add(JSON.toJSON(roleContent));historyList.add(roleContent);message.put("text",text);payload.put("message",message);requestJson.put("header",header);requestJson.put("parameter",parameter);requestJson.put("payload",payload);// System.err.println(requestJson); // 可以打印看每次的传参明细webSocket.send(requestJson.toString());// 等待服务端返回完毕后关闭while (true) {// System.err.println(wsCloseFlag + "---");Thread.sleep(200);if (wsCloseFlag) {break;}}webSocket.close(1000, "");} catch (Exception e) {e.printStackTrace();}}}@Overridepublic void onOpen(WebSocket webSocket, Response response) {super.onOpen(webSocket, response);System.out.print("大模型:");MyThread myThread = new MyThread(webSocket);myThread.start();}@Overridepublic void onMessage(WebSocket webSocket, String text) {// System.out.println(userId + "用来区分那个用户的结果" + text);JsonParse myJsonParse = gson.fromJson(text, JsonParse.class);if (myJsonParse.header.code != 0) {System.out.println("发生错误,错误码为:" + myJsonParse.header.code);System.out.println("本次请求的sid为:" + myJsonParse.header.sid);webSocket.close(1000, "");}List<Text> textList = myJsonParse.payload.choices.text;for (Text temp : textList) {System.out.print(temp.content);totalAnswer=totalAnswer+temp.content;}if (myJsonParse.header.status == 2) {// 可以关闭连接,释放资源System.out.println();System.out.println("*************************************************************************************");if(canAddHistory()){RoleContent roleContent=new RoleContent();roleContent.setRole("assistant");roleContent.setContent(totalAnswer);historyList.add(roleContent);}else{historyList.remove(0);RoleContent roleContent=new RoleContent();roleContent.setRole("assistant");roleContent.setContent(totalAnswer);historyList.add(roleContent);}wsCloseFlag = true;totalFlag=true;}}@Overridepublic void onFailure(WebSocket webSocket, Throwable t, Response response) {super.onFailure(webSocket, t, response);try {if (null != response) {int code = response.code();System.out.println("onFailure code:" + code);System.out.println("onFailure body:" + response.body().string());if (101 != code) {System.out.println("connection failed");System.exit(0);}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 鉴权方法public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {URL url = new URL(hostUrl);// 时间SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);format.setTimeZone(TimeZone.getTimeZone("GMT"));String date = format.format(new Date());// 拼接String preStr = "host: " + url.getHost() + "\n" +"date: " + date + "\n" +"GET " + url.getPath() + " HTTP/1.1";// System.err.println(preStr);// SHA256加密Mac mac = Mac.getInstance("hmacsha256");SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");mac.init(spec);byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));// Base64加密String sha = Base64.getEncoder().encodeToString(hexDigits);// System.err.println(sha);// 拼接String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);// 拼接地址HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().//addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).//addQueryParameter("date", date).//addQueryParameter("host", url.getHost()).//build();// System.err.println(httpUrl.toString());return httpUrl.toString();}//返回的json结果拆解class JsonParse {Header header;Payload payload;}class Header {int code;int status;String sid;}class Payload {Choices choices;}class Choices {List<Text> text;}class Text {String role;String content;}class RoleContent{String role;String content;public String getRole() {return role;}public void setRole(String role) {this.role = role;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}}
}

这是官方给我们的示例代码,但是只能将在下方运行框中进行使用,但是,如果你仔细看过我们的socket代码,我们其中的send_text和receive_text也是会在底下的输入框中进行输出的,所以我们可以将send_text替换为原AI代码中的“我”的部分,把receive_text的部分转换为“大模型“的部分并返回给页面,同时我们还要加上socket服务,在启动时,先要打开服务器,然后进行一系列操作

这是改正之后的,代码相当于两部分结合了一下,想要理解的同学不妨直接扔给AI解释一下(dogs):

AI与socket的结合

pom.xml的配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>big_model</artifactId><version>1.0-SNAPSHOT</version><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>8</source><target>8</target></configuration></plugin></plugins></build><properties><java.version>1.8</java.version></properties><dependencies><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.67</version></dependency><dependency><groupId>com.corundumstudio.socketio</groupId><artifactId>netty-socketio</artifactId><version>1.7.19</version> <!-- 版本号根据需求调整 --></dependency><!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency><!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket --><dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.3.8</version></dependency><!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp --><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.10.0</version></dependency><!-- https://mvnrepository.com/artifact/com.squareup.okio/okio --><dependency><groupId>com.squareup.okio</groupId><artifactId>okio</artifactId><version>2.10.0</version></dependency></dependencies></project>
package com.day;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.google.gson.Gson;
import okhttp3.*;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;public class BigModelNew extends WebSocketListener {// 各版本的hostUrl及其对应的domian参数,具体可以参考接口文档 https://www.xfyun.cn/doc/spark/Web.html// Spark Lite      https://spark-api.xf-yun.com/v1.1/chat      domain参数为lite// Spark Pro       https://spark-api.xf-yun.com/v3.1/chat      domain参数为generalv3// Spark Pro-128K  https://spark-api.xf-yun.com/chat/pro-128k  domain参数为pro-128k// Spark Max       https://spark-api.xf-yun.com/v3.5/chat      domain参数为generalv3.5// Spark Max-32K   https://spark-api.xf-yun.com/chat/max-32k   domain参数为max-32k// Spark4.0 Ultra  https://spark-api.xf-yun.com/v4.0/chat      domain参数为4.0Ultrapublic static final String hostUrl = "https://spark-api.xf-yun.com/v3.1/chat";public static final String domain = "generalv3";public static final String appid = "你的appid";public static final String apiSecret = "你的apiSecret";public static final String apiKey = "你的apiKey";private static final Map<String, List<RoleContent>> sessionHistories = new ConcurrentHashMap<>();public static List<RoleContent> historyList=new ArrayList<>(); // 对话历史存储集合// 环境治理的重要性  环保  人口老龄化  我爱我的祖国public static  String NewQuestion = "";public static final Gson gson = new Gson();// 个性化参数private String userId;private Boolean wsCloseFlag;private static Boolean totalFlag=true; // 控制提示用户是否输入// 构造函数public BigModelNew(String userId,SocketIOClient client,List<RoleContent> history,Runnable historyUpdater,String message) {  // 新增参数this.userId = userId;this.client = client;this.history = new ArrayList<>(history);this.historyUpdater = historyUpdater;this.wsCloseFlag = false;this.message = message;  // 新增字段}// 新增字段定义private final String message;private final SocketIOClient client;private final List<RoleContent> history;private final Runnable historyUpdater;private String totalAnswer = "";private String getLatestQuestion() {return history.isEmpty() ? "" :history.get(history.size()-1).content;}// 主函数public static void main(String[] args) throws Exception {Configuration config = new Configuration();config.setPort(9010);final SocketIOServer server = new SocketIOServer(config);server.addConnectListener(client -> {String sessionId = client.getSessionId().toString();sessionHistories.put(sessionId, new ArrayList<>());System.out.println(sessionId + "已连接");});server.addDisconnectListener(client -> {String sessionId = client.getSessionId().toString();sessionHistories.remove(sessionId);System.out.println(sessionId + "已断开");});server.addEventListener("send_text", String.class, (client, msg, ackSender) -> {String sessionId = client.getSessionId().toString();CompletableFuture.runAsync(() -> {try {List<RoleContent> history = sessionHistories.getOrDefault(sessionId, new ArrayList<>());BigModelNew handler = new BigModelNew(sessionId,client,history,() -> sessionHistories.put(sessionId, new ArrayList<>(history)),msg  // 传递用户消息);handler.processWithAI();} catch (Exception e) {client.sendEvent("error", "处理错误: " + e.getMessage());e.printStackTrace();}});});server.start();}private void processWithAI() throws Exception {String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);OkHttpClient okHttpClient = new OkHttpClient.Builder().build();Request request = new Request.Builder().url(authUrl.replace("https://", "wss://")).build();okHttpClient.newWebSocket(request, this);// 添加用户消息到历史(使用this.message)history.add(new RoleContent("user", this.message));// 控制历史长度while (calculateHistoryLength(history) > 12000) {history.remove(0);}}private static int calculateHistoryLength(List<RoleContent> history) {return history.stream().mapToInt(r -> r.content.length()).sum();}public static boolean canAddHistory(){  // 由于历史记录最大上线1.2W左右,需要判断是能能加入历史int history_length=0;for(RoleContent temp:historyList){history_length=history_length+temp.content.length();}if(history_length>12000){historyList.remove(0);historyList.remove(1);historyList.remove(2);historyList.remove(3);historyList.remove(4);return false;}else{return true;}}// 线程来发送音频与参数class MyThread extends Thread {private WebSocket webSocket;public MyThread(WebSocket webSocket) {this.webSocket = webSocket;}public void run() {try {JSONObject requestJson=new JSONObject();JSONObject header=new JSONObject();  // header参数header.put("app_id",appid);header.put("uid",UUID.randomUUID().toString().substring(0, 10));JSONObject parameter=new JSONObject(); // parameter参数JSONObject chat=new JSONObject();chat.put("domain",domain);chat.put("temperature",0.5);chat.put("max_tokens",4096);parameter.put("chat",chat);JSONObject payload=new JSONObject(); // payload参数JSONObject message=new JSONObject();JSONArray text=new JSONArray();// 历史问题获取if(historyList.size()>0){for(RoleContent tempRoleContent:historyList){text.add(JSON.toJSON(tempRoleContent));}}// 最新问题RoleContent roleContent=new RoleContent();roleContent.role="user";roleContent.content=NewQuestion;text.add(JSON.toJSON(roleContent));historyList.add(roleContent);message.put("text",text);payload.put("message",message);requestJson.put("header",header);requestJson.put("parameter",parameter);requestJson.put("payload",payload);// System.err.println(requestJson); // 可以打印看每次的传参明细webSocket.send(requestJson.toString());// 等待服务端返回完毕后关闭while (true) {// System.err.println(wsCloseFlag + "---");Thread.sleep(200);if (wsCloseFlag) {break;}}webSocket.close(1000, "");} catch (Exception e) {e.printStackTrace();}}}@Overridepublic void onOpen(WebSocket webSocket, Response response) {JSONObject requestJson = buildRequestJson(history);webSocket.send(requestJson.toString());}@Overridepublic void onMessage(WebSocket webSocket, String text) {// System.out.println(userId + "用来区分那个用户的结果" + text);JsonParse myJsonParse = gson.fromJson(text, JsonParse.class);if (myJsonParse.header.code != 0) {System.out.println("发生错误,错误码为:" + myJsonParse.header.code);System.out.println("本次请求的sid为:" + myJsonParse.header.sid);webSocket.close(1000, "");}List<Text> textList = myJsonParse.payload.choices.text;for (Text temp : textList) {System.out.print(temp.content);totalAnswer=totalAnswer+temp.content;}if (myJsonParse.header.status == 2) {// 可以关闭连接,释放资源System.out.println();System.out.println("*************************************************************************************");if(canAddHistory()){RoleContent roleContent=new RoleContent();roleContent.setRole("assistant");roleContent.setContent(totalAnswer);historyList.add(roleContent);}else{historyList.remove(0);RoleContent roleContent=new RoleContent();roleContent.setRole("assistant");roleContent.setContent(totalAnswer);historyList.add(roleContent);}wsCloseFlag = true;totalFlag=true;}if (!textList.isEmpty()) {String delta = textList.get(0).content;client.sendEvent("receive_text", delta);}if (myJsonParse.header.status == 2) {// 最终结果处理RoleContent assistantMsg = new RoleContent("assistant", totalAnswer);history.add(assistantMsg);historyUpdater.run();client.sendEvent("receive_end", totalAnswer);}}private JSONObject buildRequestJson(List<RoleContent> history) {JSONObject requestJson = new JSONObject();// HeaderJSONObject header = new JSONObject();header.put("app_id", appid);header.put("uid", UUID.randomUUID().toString().substring(0, 10));// ParameterJSONObject parameter = new JSONObject();JSONObject chat = new JSONObject();chat.put("domain", domain);chat.put("temperature", 0.5);chat.put("max_tokens", 4096);parameter.put("chat", chat);// PayloadJSONObject payload = new JSONObject();JSONObject messageObj = new JSONObject();JSONArray text = new JSONArray();for (RoleContent content : history) {text.add(JSON.toJSON(content));}text.add(JSON.toJSON(new RoleContent("user", message)));messageObj.put("text", text);payload.put("message", messageObj);requestJson.put("header", header);requestJson.put("parameter", parameter);requestJson.put("payload", payload);return requestJson;}@Overridepublic void onFailure(WebSocket webSocket, Throwable t, Response response) {super.onFailure(webSocket, t, response);try {if (null != response) {int code = response.code();System.out.println("onFailure code:" + code);System.out.println("onFailure body:" + response.body().string());if (101 != code) {System.out.println("connection failed");System.exit(0);}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 鉴权方法public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {URL url = new URL(hostUrl);// 时间SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);format.setTimeZone(TimeZone.getTimeZone("GMT"));String date = format.format(new Date());// 拼接String preStr = "host: " + url.getHost() + "\n" +"date: " + date + "\n" +"GET " + url.getPath() + " HTTP/1.1";// System.err.println(preStr);// SHA256加密Mac mac = Mac.getInstance("hmacsha256");SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");mac.init(spec);byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));// Base64加密String sha = Base64.getEncoder().encodeToString(hexDigits);// System.err.println(sha);// 拼接String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);// 拼接地址HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().//addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).//addQueryParameter("date", date).//addQueryParameter("host", url.getHost()).//build();// System.err.println(httpUrl.toString());return httpUrl.toString();}//返回的json结果拆解class JsonParse {Header header;Payload payload;}class Header {int code;int status;String sid;}class Payload {Choices choices;}class Choices {List<Text> text;}class Text {String role;String content;}class RoleContent{String role;String content;public RoleContent(String role, String content) {this.role = role;this.content = content;}public RoleContent() {}public String getRole() {return role;}public void setRole(String role) {this.role = role;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}}
}

我们来看一下效果:
请添加图片描述
请添加图片描述
请添加图片描述

尾言

这样看来,我们的ai+Socket服务器就连接成功了,并且能够进行实时对话,但可以看到,我们的页面只能显示最后输出的一句话,所以仍然需要进行修改,由于篇幅与时间问题,我这里就不再修改了,留给读者们自由发挥的空间,这篇文章是作者的呕心沥血之作,也填补了目前网站所没有的空白,所以可以的话,可以给作者点一个赞或关注鼓励一下作者,如果有问题的话也欢迎与作者进行交流与讨论。

相关文章:

  • Springboot 自动装配原理是什么?SPI 原理又是什么?
  • 【PGCCC】Postgres MVCC 内部:更新与插入的隐性成本
  • Selenium 获取 Web 页面信息的全指南
  • 【补充篇】Davinci工具要求的dbc格式
  • FortiAI 重塑Fortinet Security Fabric全面智能化进阶
  • ECharts散点图-散点图8,附视频讲解与代码下载
  • 解锁智能制造:PLC远程下载如何让设备运维效率提升10倍?
  • 【C++ Qt】Hello World、初始信号槽、理解对象树 ~~~(通俗易懂 图文并茂)
  • Prometheus thanos架构
  • 【HDFS入门】HDFS高可用性与容错机制深度解析
  • 正则表达式在爬虫中的应用:匹配 HTML 和 JSON 的技巧
  • YOLOv12即插即用---RFAConv
  • 3DS 转 STL 全攻略:传统工具与迪威模型网在线转换深度解析
  • 自动驾驶系列—GLane3D: Detecting Lanes with Graph of 3D Keypoints
  • 2025.04.16【GroupedandStackedbarplot】生信数据可视化技法
  • R语言之环境清理
  • eclipse常用快捷键
  • FreeRTOS二值信号量详解与实战教程
  • kafka发送消息,同时支持消息压缩和不压缩
  • 比较UNION ALL与WITH ROLLUP
  • 黄仁勋的新逻辑:从“卖铲人”到“全球AI基建运营商”
  • 专访|金七猫奖得主:以非遗为舟,在现实题材中疗愈与成长
  • 学生靠老干妈下饭、职工餐肉类又多又好?纪委出手整治
  • 武汉警方通报一起故意伤害案件:1人死亡,嫌疑人已被抓获
  • 取得金奖西瓜品种独家使用权的上海金山,为何要到异地“试种”?
  • 以开放促发展,以发展促开放,浙江加快建设高能级开放强省