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

鸿蒙 WiFi 连接 流程

那当界面上显示扫描到的所有Ap时,我们选择其中的一个Ap发起连接,看下代码流程是怎样的。

// applications/standard/settings/product/phone/src/main/ets/model/wifiImpl/WifiModel.tsconnectWiFi(password: string) {let apInfo = this.userSelectedAp.getApInfo();let ret = false;let connectParam: any = {"ssid": apInfo.ssid,"bssid": apInfo.bssid,"preSharedKey": password,"isHiddenSsid": false, // we don't support connect to hidden ap yet"securityType": apInfo.securityType};LogUtil.info(MODULE_TAG + 'disconnect WiFi isConnected is ' + wifi.isConnected());     ---》当前如果没有连接就是falseif (wifi.isConnected() === true) {ret = wifi.disconnect();LogUtil.info(MODULE_TAG + 'disconnect WiFi ret is ' + ret);this.registerWiFiConnectionObserver((code: Number) => {if (code === 0) {ret = wifi.connectToDevice(connectParam);this.unregisterWiFiConnectionObserver();}})}else{ret = wifi.connectToDevice(connectParam);                        ---> 就会走这里发起连接LogUtil.info(MODULE_TAG + 'connect WiFi ret is ' + ret);}return ret;}
// 有了前面的基础我们知道Wifi_Device的实现是wifi_device_impl, 然后在通过代理和服务端交互,所以我们直接看服务端的实现即可,调用流程这里就省略掉
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_device_service_impl.cpp
ErrCode WifiDeviceServiceImpl::ConnectToDevice(const WifiDeviceConfig &config)
{if (!WifiAuthCenter::IsSystemAppByToken()) {WIFI_LOGE("ConnectToDevice:NOT System APP, PERMISSION_DENIED!");return WIFI_OPT_NON_SYSTEMAPP;}if (WifiPermissionUtils::VerifySetWifiInfoPermission() == PERMISSION_DENIED) {WIFI_LOGE("ConnectToDevice:VerifySetWifiInfoPermission PERMISSION_DENIED!");return WIFI_OPT_PERMISSION_DENIED;}if (WifiPermissionUtils::VerifyWifiConnectionPermission() == PERMISSION_DENIED) {WIFI_LOGE("ConnectToDevice:VerifyWifiConnectionPermission PERMISSION_DENIED!");return WIFI_OPT_PERMISSION_DENIED;}if (WifiPermissionUtils::VerifySetWifiConfigPermission() == PERMISSION_DENIED) {WIFI_LOGE("ConnectToDevice:VerifySetWifiConfigPermission PERMISSION_DENIED!");return WIFI_OPT_PERMISSION_DENIED;}if (!CheckConfigPwd(config)) {WIFI_LOGE("CheckConfigPwd failed!");return WIFI_OPT_INVALID_PARAM;}if (!IsStaServiceRunning()) {WIFI_LOGE("ConnectToDevice: sta service is not running!");return WIFI_OPT_STA_NOT_OPENED;}IStaService *pService = WifiServiceManager::GetInstance().GetStaServiceInst();if (pService == nullptr) {WIFI_LOGE("ConnectToNetwork: pService is nullptr!");return WIFI_OPT_STA_NOT_OPENED;}return pService->ConnectToDevice(config);
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_interface.cpp
ErrCode StaInterface::ConnectToDevice(const WifiDeviceConfig &config)
{LOGD("Enter StaInterface::Connect.\n");CHECK_NULL_AND_RETURN(pStaService, WIFI_OPT_FAILED);if (pStaService->ConnectToDevice(config) != WIFI_OPT_SUCCESS) {LOGD("ConnectTo failed.\n");return WIFI_OPT_FAILED;}return WIFI_OPT_SUCCESS;
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_service.cpp
ErrCode StaService::ConnectToDevice(const WifiDeviceConfig &config) const
{LOGI("Enter StaService::ConnectToDevice, ssid = %{public}s.\n", SsidAnonymize(config.ssid).c_str());CHECK_NULL_AND_RETURN(pStaStateMachine, WIFI_OPT_FAILED);int netWorkId = AddDeviceConfig(config);       ---》 对于新的AP,先添加,和Android都是一样的if(netWorkId == INVALID_NETWORK_ID) {LOGD("StaService::ConnectToDevice, AddDeviceConfig failed!");return WIFI_OPT_FAILED;}LOGI("StaService::ConnectToDevice, netWorkId: %{public}d", netWorkId);pStaStateMachine->SendMessage(WIFI_SVR_CMD_STA_CONNECT_NETWORK, netWorkId, NETWORK_SELECTED_BY_USER);    ---> 让状态机处理连接return WIFI_OPT_SUCCESS;
}

这里主要是两个函数:
一个是 AddDeviceConfig ,
一个是SendMessage(WIFI_SVR_CMD_STA_CONNECT_NETWORK, netWorkId, NETWORK_SELECTED_BY_USER)
流程都是一样的,通过,通过RPC访问HAL层,然后再把命令发给wpa,wpa收到命令后触发相应动作,这里我们看后者,状态机处理消息
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_state_machine.cpp

// 调用的这个函数:
void StaStateMachine::DealConnectToUserSelectedNetwork(InternalMessage *msg)
{LOGI("enter DealConnectToUserSelectedNetwork.\n");if (msg == nullptr) {LOGE("msg is null.\n");return;}int networkId = msg->GetParam1();int connTriggerMode = msg->GetParam2();if (connTriggerMode != NETWORK_SELECTED_BY_RETRY) {linkedInfo.retryedConnCount = 0;}if (networkId == linkedInfo.networkId) {if (linkedInfo.connState == ConnState::CONNECTED) {staCallback.OnStaConnChanged(OperateResState::CONNECT_AP_CONNECTED, linkedInfo);WIFI_LOGI("This network is in use and does not need to be reconnected.\n");return;}if (linkedInfo.connState == ConnState::CONNECTING &&linkedInfo.detailedState == DetailedState::OBTAINING_IPADDR) {WIFI_LOGI("This network is connecting and does not need to be reconnected.\n");return;}}/* Save connection information. */SaveDiscReason(DisconnectedReason::DISC_REASON_DEFAULT);SaveLinkstate(ConnState::CONNECTING, DetailedState::CONNECTING);/* Callback result to InterfaceService. */staCallback.OnStaConnChanged(OperateResState::CONNECT_CONNECTING, linkedInfo);if (StartConnectToNetwork(networkId) != WIFI_OPT_SUCCESS) {     ---》 继续看这个流程OnConnectFailed(networkId);return;}/* Sets network status. */WifiSettings::GetInstance().EnableNetwork(networkId, connTriggerMode == NETWORK_SELECTED_BY_USER);WifiSettings::GetInstance().SetDeviceAfterConnect(networkId);WifiSettings::GetInstance().SetDeviceState(networkId, (int)WifiDeviceConfigStatus::ENABLED, false);
}//
ErrCode StaStateMachine::StartConnectToNetwork(int networkId)
{targetNetworkId = networkId;SetRandomMac(targetNetworkId);     ---> 随机mac地址WifiDeviceConfig deviceConfig;if (WifiSettings::GetInstance().GetDeviceConfig(networkId, deviceConfig) != 0) {LOGE("StartConnectToNetwork get GetDeviceConfig failed!");return WIFI_OPT_FAILED;}WifiStaHalInterface::GetInstance().SetBssid(networkId, deviceConfig.userSelectBssid.c_str());// 使能apif (WifiStaHalInterface::GetInstance().EnableNetwork(targetNetworkId) != WIFI_IDL_OPT_OK) {LOGE("EnableNetwork() failed!");return WIFI_OPT_FAILED;}// 连接if (WifiStaHalInterface::GetInstance().Connect(targetNetworkId) != WIFI_IDL_OPT_OK) {LOGE("Connect failed!");staCallback.OnStaConnChanged(OperateResState::CONNECT_SELECT_NETWORK_FAILED, linkedInfo);return WIFI_OPT_FAILED;}// 保存if (WifiStaHalInterface::GetInstance().SaveDeviceConfig() != WIFI_IDL_OPT_OK) {/* OHOS's wpa don't support save command, so don't judge as failure */LOGE("SaveDeviceConfig() failed!");}StopTimer(static_cast<int>(CMD_NETWORK_CONNECT_TIMEOUT));StartTimer(static_cast<int>(CMD_NETWORK_CONNECT_TIMEOUT), STA_NETWORK_CONNECTTING_DELAY);return WIFI_OPT_SUCCESS;// 上面的三步骤和Android都差不多,基本上大家都熟悉
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_sta_hal_interface.cpp
WifiErrorNo WifiStaHalInterface::Connect(int networkId)
{CHECK_NULL_AND_RETURN(mIdlClient, WIFI_IDL_OPT_FAILED);return mIdlClient->ReqConnect(networkId);
}
// idl_client , 前面几篇都讲过了,这里直接贴HAL层的代码了
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_crpc_sta.c
int RpcConnect(RpcServer *server, Context *context)
{if (server == NULL || context == NULL) {return HAL_FAILURE;}int networkId = 0;if (ReadInt(context, &networkId) < 0) {return HAL_FAILURE;}WifiErrorNo err = Connect(networkId);    ---> 看这个的实现WriteBegin(context, 0);WriteInt(context, err);WriteEnd(context);return HAL_SUCCESS;
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_sta_interface.c
WifiErrorNo Connect(int networkId)
{LOGD("Connect() networkid %{public}d", networkId);WifiWpaStaInterface *pStaIfc = GetWifiStaInterface(0);if (pStaIfc == NULL) {return WIFI_HAL_SUPPLICANT_NOT_INIT;}int ret = pStaIfc->wpaCliCmdSelectNetwork(pStaIfc, networkId);if (ret < 0) {LOGE("WpaCliCmdSelectNetwork failed! ret=%{public}d", ret);return WIFI_HAL_FAILED;}return WIFI_HAL_SUCCESS;
}

继续看 pStaIfc->wpaCliCmdSelectNetwork 的调用,

//foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wpa_sta_hal/wifi_supplicant_hal.c 
static int WpaCliCmdSelectNetwork(WifiWpaStaInterface *this, int networkId)
{if (this == NULL) {return -1;}char cmd[CMD_BUFFER_SIZE] = {0};char buf[REPLY_BUF_SMALL_LENGTH] = {0};if (snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "IFNAME=%s SELECT_NETWORK %d", this->ifname, networkId) < 0) {LOGE("snprintf err");return -1;}return WpaCliCmd(cmd, buf, sizeof(buf));
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wifi_wpa_common.cint WpaCliCmd(const char *cmd, char *buf, size_t bufLen)
{if (cmd == NULL || buf == NULL || bufLen <= 0) {LOGE("WpaCliCmd, invalid parameters!");return -1;}WpaCtrl *ctrl = GetWpaCtrl();if (ctrl == NULL || ctrl->pSend == NULL) {LOGE("WpaCliCmd, ctrl/ctrl->pSend is NULL!");return -1;}size_t len = bufLen - 1;LOGI("wpa_ctrl_request -> cmd: %{private}s", cmd);int ret = wpa_ctrl_request(ctrl->pSend, cmd, strlen(cmd), buf, &len, NULL);    ---》 发送给wpaif (ret == WPA_CMD_RETURN_TIMEOUT) {LOGE("[%{private}s] command timed out.", cmd);return WPA_CMD_RETURN_TIMEOUT;} else if (ret < 0) {LOGE("[%{private}s] command failed.", cmd);return -1;}buf[len] = '\0';LOGI("wpa_ctrl_request -> buf: %{private}s", buf);if (strncmp(buf, "FAIL\n", strlen("FAIL\n")) == 0 ||strncmp(buf, "UNKNOWN COMMAND\n", strlen("UNKNOWN COMMAND\n")) == 0) {LOGE("%{private}s request success, but response %{public}s", cmd, buf);return -1;}return 0;
}

至此,连接的命令就发给wpa了,接下来就是协议上的连接了(四次握手),由wpa去交互完成,连接成功后,wpa上报 CTRL-EVENT-CONNECTED 事件,上层就收到,就开始走DHCP/或者是静态ip的流程,下一篇继续梳理这个。

相关文章:

鸿蒙 WiFi 连接 流程

那当界面上显示扫描到的所有Ap时&#xff0c;我们选择其中的一个Ap发起连接&#xff0c;看下代码流程是怎样的。 // applications/standard/settings/product/phone/src/main/ets/model/wifiImpl/WifiModel.tsconnectWiFi(password: string) {let apInfo this.userSelectedAp…...

golang 创建unix socket http服务端

服务端 package mainimport ("fmt""net""net/http""os" )func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("hello"))})http.HandleFunc("/world", …...

annaconda如何切换当前python环境

annaconda默认的python环境是base&#xff1a; 把各种项目的依赖都安装到base环境中不是一个好的习惯&#xff0c;比如说我们做爬虫项目和做自动化测试项目等所需要的依赖是不一样的&#xff0c;我们可以将为每个项目创建自己的环境&#xff0c;在各自的环境中安装自己的依赖&…...

gtkmm 与 Cambalache 与 Gtk::Builder (新手向)_

文章目录 前言Cambalache检查Xml.cpp文件如何写才能显示UI首先creat获取ui里的对象显示 前言 新手刚刚使用时的笔记 Cambalache检查Xml 窗口右键inspect UI Definition切换到Xml视图, 可以全选复制粘贴到你的ui文件里, Cambalache 只能保存为.cmb工程文件, 导出也不知道导出…...

uniapp小程序端使用计算属性动态绑定style样式踩坑

踩坑点: 使用uniapp编译小程序端动态绑定复杂style使用计算属性方式&#xff0c;return必须返回json字符串格式&#xff0c;不能返回object&#xff0c;否则会不起作用。 代码总览 视图层 逻辑层&#xff08;注意这里是使用的计算属性哈&#xff09; 这里我封装成了一个个性化…...

计算机网络概念、组成、功能和分类

文章目录 概要1.怎么学习计算机网络2.概念3.功能、组成4.工作方式、功能组成5.分类 概要 概念、组成、功能和分类 1.怎么学习计算机网络 2.概念 通信设备&#xff1a;比如路由器、路由器 线路&#xff1a;将系统和通信设备两者联系的介质之类的 计算机网络是互连的、自治的的计…...

MyBatisPlus基础操作之增删改查

目录 一、基本使用 1.1 插入数据 1.2 删除操作 1.3 更新操作 二、条件构造器Wrapper 2.1 常用AbstractWrapper方法 2.1.1 示例一 2.2.2 示例二 2.2.3 示例三 2.2 常用QueryWrapper方法 2.2.1 示例一 2.2.2 示例二 2.2.3 示例三&#xff08;常用&#xff09; 2.3 常…...

视频处理学习笔记1:YUYV422、NV12和h264

最近因为工作关系在恶补视频相关知识点&#xff0c;在此做一记录便于日后复习。 以下均是个人学习经验总结&#xff0c;可能存在错误和坑&#xff0c;欢迎大佬指教。 工作中用到的是YUYV422存储格式。存储的就是裸流YUYV422格式文件。 YUYV422是两个像素点共用一个UV分量&am…...

CTFshow web(命令执行29-36)

?ceval($_GET[shy]);&shypassthru(cat flag.php); #逃逸过滤 ?cinclude%09$_GET[shy]?>&shyphp://filter/readconvert.base64-encode/resourceflag.php #文件包含 ?cinclude%0a$_GET[cmd]?>&cmdphp://filter/readconvert.base64-encode/…...

PyTorch深度学习实战(23)——从零开始实现SSD目标检测

PyTorch深度学习实战&#xff08;23&#xff09;——从零开始实现SSD目标检测 0. 前言1. SSD 目标检测模型1.1 SSD 网络架构1.2 利用不同网络层执行边界框和类别预测1.3 不同网络层中默认框的尺寸和宽高比1.4 数据准备1.5 模型训练 2. 实现 SSD 目标检测2.1 SSD300 架构2.2 Mul…...

【附代码】NumPy加速库NumExpr(大数据)

文章目录 相关文献测试电脑配置数组加减乘除数组乘方Pandas加减乘除总结 作者&#xff1a;小猪快跑 基础数学&计算数学&#xff0c;从事优化领域5年&#xff0c;主要研究方向&#xff1a;MIP求解器、整数规划、随机规划、智能优化算法 如有错误&#xff0c;欢迎指正。如有…...

4、安全开发-Python-蓝队项目流量攻击分析文件动态监控图片隐写技术

用途&#xff1a;个人学习笔记&#xff0c;有所借鉴&#xff0c;欢迎指正&#xff01; 总结&#xff1a; &#xff08;1&#xff09;使用python脚本Scapy库实现指定网卡的流量抓包分析 &#xff08;2&#xff09;使用python脚本Watchdog实现指定目录文件行为监控 &#xff08;…...

MySQL 日志管理

4.6&#xff09;日志管理 MySQL 支持丰富的日志类型&#xff0c;如下&#xff1a; 事务日志&#xff1a;transaction log 事务日志的写入类型为 "追加"&#xff0c;因此其操作为 "顺序IO"&#xff1b; 通常也被称为&#xff1a;预写式日志 write ahead…...

Python CSV文件读取和写入

本文主要为Python 实现CSV文件读取和写入操作。 CSV文件写入和读取 因为没有现成的csv文件&#xff0c;所以csv的顺序为先写入后读取。 写入 创建csv文件并把数据写入&#xff0c;有两种实现方式&#xff1a;直接插入所有行和插入单行。 示例如下&#xff1a; import csv i…...

如何使用C#调用LabVIEW算法

新建一个工程 这是必须的&#xff1b; 创建项目 项目 点击完成&#xff1b; 将项目另存为&#xff1b;方便后续的使用&#xff1b; 创建 一个测试VI 功能很简单&#xff0c;用的一个加法&#xff1b;将加数A&#xff0c;B设置为输入&#xff0c;和C设置为输出&#xff0c;…...

调用百度文心AI作画API实现中文-图像跨模态生成

作者介绍 乔冠华&#xff0c;女&#xff0c;西安工程大学电子信息学院&#xff0c;2020级硕士研究生&#xff0c;张宏伟人工智能课题组。 研究方向&#xff1a;机器视觉与人工智能。 电子邮件&#xff1a;1078914066qq.com 一&#xff0e;文心AI作画API介绍 1. 文心AI作画 文…...

JAVA SpringBoot中使用redis的事务

目录 一、Java语言介绍 二、SpringBoot框架介绍 三、Redis缓存介绍 四、什么是redis的事务 一、Java语言介绍 Java是一种广泛使用的高级编程语言,由Sun Microsystems公司于1995年推出。它的设计目标是要求“一次编写,到处运行”&#xff08;Write Once, Run Anywhere, WOR…...

docker部署自己的网站wordpress

目录 安装 1.创建目录 2.创建并启动mysql 3.创建并启动wordpress 使用 1.设置语言 2.设置基础信息 3.首页 安装 1.创建目录 mkdir -p /opt/wordpress/{db,data} 2.创建并启动mysql docker run -d --name my_mysql --restart always -e MYSQL_ROOT_PASSWORD123456 -e …...

基于ISO13400 (DoIP) 实现车辆刷写

近年来&#xff0c;在整车研发中基于以太网实现车辆高带宽通讯无疑是人们热议的话题。无论是车内基于车载以太网来减少线束成本&#xff0c;实现ADAS、信息娱乐系统等技术&#xff0c;还是基于新的电子电气架构以及远程诊断需求来实现以太网诊断&#xff08;DoIP&#xff09;&a…...

Chrome 沙箱逃逸 -- Plaid CTF 2020 mojo

文章目录 前置知识参考文章环境搭建题目环境调试环境 题目分析附件分析漏洞分析OOBUAF 漏洞利用总结 前置知识 Mojo & Services 简介 chromium mojo 快速入门 Mojo docs Intro to Mojo & Services 译文&#xff1a;利用Mojo IPC的UAF漏洞实现Chrome浏览器沙箱逃逸原文…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...