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服务器就连接成功了,并且能够进行实时对话,但可以看到,我们的页面只能显示最后输出的一句话,所以仍然需要进行修改,由于篇幅与时间问题,我这里就不再修改了,留给读者们自由发挥的空间,这篇文章是作者的呕心沥血之作,也填补了目前网站所没有的空白,所以可以的话,可以给作者点一个赞或关注鼓励一下作者,如果有问题的话也欢迎与作者进行交流与讨论。
相关文章:
Android studio前沿开发--利用socket服务器连接AI实现前后端交互(全站首发思路)
我们在前几期学习了利用socket进行前后端的交互,但那只是基础性知识,这次,通过参考讯飞星火的java参考文档,再结合之前所学的socket服务,成功实现了通过后端将AI的调用实现在了自己的APP中。 本次的学习内容 1.真机的…...
Redis的下载安装和使用(超详细)
目录 一、所需的安装包资源小编放下述网盘了,提取码:wshf 二、双击打开文件redis.desktop.manager.exe 三、点击next后,再点击i agree 四、点击箭头指向,选择安装路径,然后点击Install进行安装 五、安装完后依次点…...
手机状态:UML 状态图(State Diagram)的解析与绘画
目录 一、UML 状态图(State Diagram)是什么 二、题目原型 三、手机状态图的解析 状态转换的触发条件 四、状态图的构建与解读 图的解读 五、状态图的实际应用 六、总结与展望 一、UML 状态图(State Diagram)是什么 UML …...
MyBatisPlus-QueryWrapper的exists方法拼接SQL中的EXISTS子句
在 MyBatis-Plus 中,QueryWrapper 的 exists 方法用于拼接 SQL 中的 EXISTS 子句,通常用于构 建子查询条件。以下是具体用法和示例: 1. 基本语法 // 判断是否存在符合条件的记录 queryWrapper.exists(String existsSql); queryWrapper.notExists(String existsSq…...
HarmonyOS-ArkUI: 自定义组件冻结功能@ComonentV2 freezeWhenInactive属性
引 @ComponentV2 装饰器是可以接收参数的,叫freezeWhenInactive, 顾名思义,就是当组件变成Inactive的时候,冻结。其默认值是false。所以如果您没有传参数时,默认不冻结。 冻结到底是一种什么状态呢?说简单点就是状态变量不响应更新。@Monitor修饰的那些状态变量更新检测…...
【问题】一招解决vscode输出和终端不一致的困扰
背景(闲话Trae) Trae是挺好,用了几天,发现它时不时检查文件,一检测就转悠半天,为此我把当前环境清空,就留一个正在调的程序,结果还照样检测,虽然没影响什么,…...
【CODESYS学习笔记001】MODBUS-TCP 与 标准TCP通信的优缺点对比
1. MODBUS-TCP 优点: 1. 标准化协议 - 基于工业标准(RFC标准),兼容性强,几乎所有PLC和工业设备都支持。 - 固定功能码(如0x03读寄存器、0x10写寄存器),开发简单。 2. 数据格…...
⭐ Unity 使用Odin Inspector增强编辑器的功能:UIManager脚本实例
先看一下测试效果: 在Unity开发中,Odin Inspector已经成为了一个非常受欢迎的工具,它通过增强编辑器的功能,使得开发者在工作中更加高效,尤其是在处理复杂数据和自定义编辑器方面。今天,我们将通过一个简…...
Linux网络协议栈深度解析:从数据封装到子网划分的底层架构
知识点5 1、封装和解封装的流程 封装数据报文:发送数据 解封装数据报文:接收报文 以后我们的网络编程过程中,只需要告知IP与端口号,链路层的MAC地址 有协议栈帮我们提供。 2、链路层报文格式(mac报文) …...
Java与MySQL数据库连接的JDBC驱动配置教程
系列文章目录 Java JDBC编程 文章目录 系列文章目录前言一、JDBC简介:二、mysql-connector-java驱动详解: 驱动版本特性介绍: 三、JDBC驱动安装与配置: 1.IDE项目设置:2.命令行安装:3.使用Maven或Gradle :…...
光伏产品研发项目如何降本增效?8Manage 项目管理软件在复合材料制造的应用
在复合材料制造领域,特别是光伏PECVD石墨舟和燃料电池石墨双极板等高精尖产品的研发过程中,高效的项目管理直接决定了产品开发周期、质量和市场竞争力。然而,许多企业在项目立项、进度跟踪、资源分配和质量控制等环节面临挑战。 针对这些痛点…...
矫平机:工业制造中的“板材整形师“
在机械制造车间此起彼伏的轰鸣声中,一卷卷冷轧钢卷正经历着神奇的蜕变。经过开卷、矫平、剪切等工序,原本蜷曲的金属板材变得平整如镜,这些改变都源于生产线上一个关键设备——矫平机。这台被称作"板材整形师"的精密机械࿰…...
数据江湖:Node.js 与 SQLite3 的轻量之道
前言 在这个“万码奔腾”的时代,想在江湖中闯出一片天地,不光要有剑(JavaScript),还得有招式(数据库)!本篇秘籍便是教你如何用 Node.js + SQLite3 打造一座小而美的“数据藏经阁”。初学者可轻松上手,高手可在细节中悟出更深的“数据库心法”。 简介 SQLite 在前端…...
4.15BUUCTF Ez_bypass,HardSQL,AreUSerialz,BabyUpload,CheckIn
[MRCTF2020]Ez_bypass 打开环境,看源码 include flag.php; $flagMRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}; if(isset($_GET[gg])&&isset($_GET[id])) {$id$_GET[id];$gg$_GET[gg];if (md5($id) md5($gg) && $id ! $gg) {echo You got the first step;i…...
【HarmonyOS NEXT+AI】问答 03:找不到 DevEco Studio Cangjie Plugin 下载链接?
【HarmonyOS NEXTAI】问答 03:找不到 DevEco Studio Cangjie Plugin 下载链接? 在 "HarmonyOS NEXTAI 大模型打造智能助手 APP (仓颉版)" 课程里面,有学员提到了这样一个问题:我在华为开发者社区官网找不到 DevEco Stu…...
使用 reverse-sourcemap 工具反编译 Vue 项目
要使用 reverse-sourcemap 工具反编译 Vue 项目,可以按照以下步骤操作: 步骤一:安装 reverse-sourcemap 首先,需要全局安装 reverse-sourcemap 工具。在命令行中执行以下命令: npm install --global reverse-sourcem…...
通信安全员历年考试重难点有哪些?
通信安全员考试的重难点紧密围绕行业特性和法规更新展开,需结合最新政策与实践案例综合掌握。以下是基于历年考试趋势及 2025 年新规的深度解析: 一、核心法规与标准体系(占比 30%-40%) 1. 安全生产法与行业规定 《安全生产法》…...
C++(OpenCV)实现MATLAB的edge(I, “sobel“)边缘检测
文章目录 方案分析具体代码实现关键步骤说明注意事项 为了实现类似于MATLAB的edge(I, "sobel")函数的C代码,我们需要复现其完整的边缘检测流程,包括梯度计算、非极大值抑制和阈值处理。以下是具体的方案及代码实现: 方案分析 图像…...
uniapp通过uni.addInterceptor实现路由拦截
注:此拦截不能首次拦截路由跳转的方法(switchTab, navigateTo, reLaunch, redirectTo),拦截request请求api可以 1. app.vue 代码 import { onLaunch} from dcloudio/uni-appimport permission from ./utils/permissiononLaunch(…...
vue2.x Echart label根据数据长度选择不同的间隔显示
折线图需要在各个点上方展示数据,但是数据数字的位数可能达到5~8位,需要根据密度进行间隔展示。例如,如果数据长度小于7,则每一项都展示,如果在7~10之间,2位展示一项,如果大于10,那么…...
Wifi密码查看软件V1.0
⭐本软件用于查看电脑连接过所有WiFi密码,不具备破解功能。 可在忘记WiFi密码或他人输入密码自己不知道的情况下使用。 ⭐⭐为便于快速分享,加入双击【密码】列可将WIFI密码复制在粘贴板。 ⭐⭐⭐双击【名称】列可生成用于手机连接的二维码进行显示&…...
Hyperf (Swoole)的多进程 + 单线程协程、Gin (Go)Go的单进程 + 多 goroutine 解说
1. 核心概念解析 (1) Hyperf (Swoole): 多进程 单线程协程 Swoole 并发模型详解 Swoole 的并发模型基于多进程架构,每个进程是单线程的,线程内运行多个协程。以下是其结构的关键点: 多进程:Swoole 应用程序启动时,…...
国内网络设备厂商名单(List of Domestic Network Equipment Manufacturers)
国内网络设备厂商名单 运维工程师必须广泛熟悉国内外各大厂商的设备,深入掌握其应用场景、功能特点及优势。这不仅有助于在故障排查时迅速定位问题,还能在系统设计、优化与升级中做出更合理的决策。对设备特性的精准把握,能够显著提升运维效…...
基础元器件-电感(2025.4.17)
1.电感是电磁感应器件,它是储能元器件。 2.电感表示形式(直标法和色标法) 3.电感读取基准是mH,3R3指的是3.3mH,R代表小数点。 4.电感特性:通直流阻交流 5.电感的分类 注:用电容或者电感滤波是…...
高通手机抓取sniffer log的方法
方法如下: adb root adb remount adb shell echo 4 >/sys/module/wlan/parameters/con_mode //不同的高通基线这块目录存在差异性 ifconfig wlan0 up iwpriv wlan0 setMonChan 149 2 //设置信道和bandwitdh tcpdump -i wlan0 -v -w /data/chan149.pcap 生成…...
React 设计艺术:如何精确拆分组件接口,实现接口隔离原则
接口隔离原则 接口隔离原则(Interface Segregation Principle,简称 ISP)也是面向对象设计中的重要原则之一。它的核心思想是,一个类不应该依赖它不需要的接口。在 React 开发中,遵循接口隔离原则可以提高代码的可维护性…...
BFS DFS ----习题
题目1 答案1 #include <bits/stdc.h>using namespace std;const int N 210; int n,k; int arr[N]; int res 0;void dfs(int x,int start,int nowsum) {if (nowsum > n) return ;if(x>k){if(nowsum n) res;return ;}for(int i start;nowsumi*(k-x1)<n;i){a…...
第十七届“华中杯”大学生数学建模挑战赛题目A题 晶硅片产销策略优化 完整成品 代码 模型 思路 分享
近年来,高纯度晶硅片需求的增长引发了更激烈的市场竞争。晶硅片企业需要在成本控制、利润优化和供需管理之间取得平衡,以提高经营效率和市场竞争力。晶硅片的生产是一个高能耗、高成本的过程,企业效益会受到原材料价格波动、市场需求变化以及…...
java 设计模式之单例模式
简介 单例模式:一个类有且仅有一个实例,该类负责创建自己的对象,同时确保只有一个对象被创建。 特点:类构造器私有、持有自己实例、对外提供获取实例的静态方法。 单例模式的实现方式 饿汉式 类被加载时,就会实例…...
新能源汽车能量流测试的传感器融合技术应用指南
第一部分:核心原理模块化拆解 模块1:多源传感器物理层融合 关键技术: 高精度同步采集架构 采用PXIe-8840控制器同步定时模块(NI PXIe-6674T),实现CAN/LIN/模拟量信号的μs级同步光纤电压传感器࿰…...
高级java每日一道面试题-2025年4月11日-微服务篇[Nacos篇]-Nacos使用的数据库及其数据同步机制是什么?
如果有遗漏,评论区告诉我进行补充 面试官: Nacos使用的数据库及其数据同步机制是什么? 我回答: Nacos 使用的数据库及其数据同步机制详解 在微服务架构中,Nacos 作为服务注册与配置管理的核心组件,其数据存储和同步机制对系统的高可用性和…...
音视频相关协议和技术内容
视频编解码: H264(AVC,MPEG-4 Part 10) 高压缩率,支持多种分辨率和帧率,用于在线流媒体、会议、数字电视 编码过程: 分块处理,将视频帧划分为宏块(16x16)使用帧预测和…...
SpringBoot整合Rabbitmq(包括docker配置Rabbitmq的详细过程)
一、什么是mq MQ(message queue),从字面意思上看就个 FIFO 先入先出的队列,只不过队列中存放的内容是 message 而已,它是一种具有接收数据、存储数据、发送数据等功能的技术服务。 在互联网架构中,MQ 是一种非常常见的上下游“逻…...
20个常用的初级Java笔试题及其参考答案
### 1. Java基本数据类型有哪些? - **答案**:Java中的基本数据类型有: - `byte`:8位 - `short`:16位 - `int`:32位 - `long`:64位 - `float`:32位 - `double`:64位 - `char`:16位(Unicode字符) - `boolean`:表示真或假(没有固定大小) ### 2. Java中的字符串是可…...
矫平机:工业制造的精密“雕刻师”
在金属加工的浩瀚图景中,矫平机犹如一位沉默的雕塑大师,用机械的精准与科学的智慧,将扭曲变形的板材重塑为工业艺术的杰作。从新能源电池极片到空间站耐压舱体,矫平工艺贯穿现代制造的每一处精度巅峰。 一、核心技术:从…...
游戏数据分析,力扣(游戏玩法分析 I~V)mysql+pandas
力扣的游戏玩法分析 I~V, ps:虽然表结构不变但是力扣输入示例数据有些许变化,所以你使用上一题的数据跑下一题的代码可能产生的结果和示例中的不一样,建议点击连接到力扣中直接运行! 目录 1. 游戏玩法分析 I mysql …...
C++之哈希
目录 一、unordered_set 1.1、unordered_set的介绍 1.2、unordered_set和set的使用差异 二、unordered_map 2.1、unordered_map和map的差异 2.2、unordered_multimap/unordered_multiset 三、哈希表 3.1、哈希概念 3.1.1、直接定地址法 3.1.2、哈希冲突 3.1.3、负载…...
DSP、MCU、FPGA 的详细总结
一、核心定义与特点 类型定义核心特点DSP(数字信号处理器)专为高速数字信号处理设计的处理器- 哈佛架构,单周期乘加(MAC) - 实时性强,低延迟处理流式数据 - 专用指令集优化算法(如FFT、滤波&am…...
linux学习 3.用户的操作
用户 建议在系统操作的时候不要一直使用root用户,因为root用户具有最高权限,你可能因为某些操作影响了你的系统,采用子用户则可以避免这一点 这里的学习不用太深入,掌握如何创建删除切换即可(除非你要做详细的用户管理࿰…...
闭坑-- `a-auto-complete` 组件中的 `options` 数据存在重复
当 ant-design 的 a-auto-complete 组件中的 options 数据存在重复时,可能会导致以下问题: 1. 交互问题 键盘导航失效: 使用键盘上下键选择时,可能会在重复项之间跳转,无法正常移动到下一个选项。选择结果不准确&…...
【Rust基础】使用Rocket构建基于SSE的流式回复
背景 我们正在使用Rust开发基于RAG的知识库系统,其中对于模型的回复使用了常用的SSE,Web框架使用Rocket,Rocket提供了一个简单的方式支持SSE,但没有会话保持、会话恢复等功能,因此我们自己简单实现这两个功能。 使用R…...
一种改进的CFAR算法用于目标检测(解决多目标掩蔽)
摘要 恒虚警率(CFAR)技术在雷达自动检测过程中起着关键作用。单元平均(CA)CFAR算法在几乎所有的多目标情况下都会受到掩蔽效应的影响。最小单元平均(SOCA)CFAR算法仅当干扰目标位于参考窗口的前后方时才具有…...
什么是人工智能芯片?
行业专家指出,许多智能设备和物联网设备都是由某种形式的人工智能(AI)驱动的——无论是语音助理、面部识别摄像头,还是电脑。这些设备需要采用某种技术为它们进行的数据处理提供支持。有些设备需要在云平台的大型数据中心处理数据,而也有一些…...
0.深入探秘 Rust Web 框架 Axum
在当今的 Web 开发领域,Rust 凭借其出色的性能、内存安全性和并发处理能力,正逐渐崭露头角。而 Axum 作为 Rust 生态系统中一款备受瞩目的 Web 框架,更是为开发者提供了高效、灵活且强大的工具,用于构建现代化的 Web 应用程序。本…...
深度监听 ref 和 reactive 的区别详解
深度监听 ref 和 reactive 的区别详解 一、ref 的深度监听(示例代码)关键点:1. ref 的存储方式:2. 监听 ref 的特性 二、reactive 的深度监听(示例代码)关键点:1. reactive 的深度响应性2. 监听…...
面向对象—有理数类的设计
目录 1.代码呈现 1.1编写toString、equals方法 1.2测试代码 1.3有理数类的代码 2.论述题 3.有理类设计 1.代码呈现 1.1编写toString、equals方法 (1)toString方法 Overridepublic String toString(){if(this.v20){return "Undefined";}return this.v1 "/…...
OpenHarmony Camera开发指导(四):相机会话管理(ArkTS)
概述 相机在使用预览、拍照、录像、获取元数据等功能前,都需要先创建相机会话。 相机会话Session的功能如下: 配置相机的输入流和输出流。 配置输入流即添加设备输入,通俗来讲即选择某一个摄像头进行拍照录像;配置输出流&#x…...
Linux电源管理(三),CPUIdle 和 ARM的PSCI
更多linux系统电源管理相关的内容请看:Linux电源管理、功耗管理 和 发热管理 (CPUFreq、CPUIdle、RPM、thermal、睡眠 和 唤醒)-CSDN博客 1 简介 Linux下的空闲进程cpuidle在内核中是一个子系统。cpuidle子系统所需要做的事情就是在CPU进入idle状态后,…...
【测试工具】JMeter使用小记
JMeter 使用小记 下载与安装 jdk 下载地址:https://www.oracle.com/java/technologies/downloads/#jdk18-windowsJMeter 下载地址:https://jmeter.apache.org/download_jmeter.cgi 教程参考:JMeter下载及安装详细教程-CSDN博客 设置中文界…...
Obsidian的简单使用
一、安装并配置仓耳今楷字体 优化阅读体验,个人实测觉得正文用 仓耳今楷04-W03最合适(前面的数字代表字体,数字越大,越偏向于楷体,而01就很像黑体。后面的数字代表粗细,正常粗细是W03,最粗是W0…...