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,获取当前时间

java 复制代码
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地址

java 复制代码
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服务的ip
    public 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服务的ip
    public final static int CHAT_PORT = 9011; // 聊天Socket服务的端口
}

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


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

java 复制代码
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:

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" >

    <EditText
        android: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" />

    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送文本消息"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <TextView
        android:id="@+id/tv_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>

java:

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; // 声明一个套接字对象

    @Override
    protected 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));
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSocket.off("receive_text"); // 取消接收传来的文本消息
        if (mSocket.connected()) { // 已经连上Socket服务器
            mSocket.disconnect(); // 断开Socket连接
        }
        mSocket.close(); // 关闭Socket连接
    }
}

后端idea的代码:

java 复制代码
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通信:

java 复制代码
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底下的代码受到保护,无法编译

java 复制代码
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.0Ultra

    public 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();
              // 构建鉴权url
              String 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();
            }
        }
    }

    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        super.onOpen(webSocket, response);
        System.out.print("大模型:");
        MyThread myThread = new MyThread(webSocket);
        myThread.start();
    }

    @Override
    public 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;
        }
    }

    @Override
    public 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 block
            e.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 复制代码
<?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>
java 复制代码
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.0Ultra

    public 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();
            }
        }
    }

    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        JSONObject requestJson = buildRequestJson(history);
        webSocket.send(requestJson.toString());
    }

    @Override
    public 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();

        // Header
        JSONObject header = new JSONObject();
        header.put("app_id", appid);
        header.put("uid", UUID.randomUUID().toString().substring(0, 10));

        // Parameter
        JSONObject 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);

        // Payload
        JSONObject 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;
    }

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

相关推荐
海天胜景28 分钟前
HTTP Error 500.31 - Failed to load ASP.NET Core runtime
后端·asp.net
fanstuck29 分钟前
从知识图谱到精准决策:基于MCP的招投标货物比对溯源系统实践
人工智能·知识图谱
海天胜景31 分钟前
Asp.Net Core IIS发布后PUT、DELETE请求错误405
数据库·后端·asp.net
dqsh0632 分钟前
树莓派5+Ubuntu24.04 LTS串口通信 保姆级教程
人工智能·python·物联网·ubuntu·机器人
独行soc1 小时前
2025年渗透测试面试题总结-某服面试经验分享(附回答)(题目+回答)
linux·运维·服务器·网络安全·面试·职场和发展·渗透测试
打小就很皮...1 小时前
编写大模型Prompt提示词方法
人工智能·语言模型·prompt
Aliano2172 小时前
Prompt(提示词)工程师,“跟AI聊天”
人工智能·prompt
weixin_445238122 小时前
第R8周:RNN实现阿尔兹海默病诊断(pytorch)
人工智能·pytorch·rnn
KingDol_MIni2 小时前
ResNet残差神经网络的模型结构定义(pytorch实现)
人工智能·pytorch·神经网络
源码云商2 小时前
Spring Boot + Vue 实现在线视频教育平台
vue.js·spring boot·后端