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

鸿蒙 WiFi 扫描流程(2)

接着上篇没有记录完的,我们继续梳理,需要上一篇做基础的请看:鸿蒙 WiFi 扫描流程(1)
上一篇我们讲到 scan_service.cpp 里面的 SingleScan 方法,继续这个方法往下看:

// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_scan/scan_service.cppbool ScanService::SingleScan(ScanConfig &scanConfig)
{WIFI_LOGI("Enter ScanService::SingleScan.\n");#ifndef OHOS_ARCH_LITEif (!standByListerner.AllowScan()) {WIFI_LOGE("Scan not allowed when device in standby state.\n");return WIFI_OPT_FAILED;}
#endifGetAllowBandFreqsControlInfo(scanConfig.scanBand, scanConfig.scanFreqs);                   ---> 哪个Bandif ((scanConfig.scanBand == SCAN_BAND_UNSPECIFIED) && (scanConfig.scanFreqs.empty())) {WIFI_LOGE("Have no allowed band or freq.\n");return false;}InterScanConfig interConfig;interConfig.fullScanFlag = scanConfig.fullScanFlag;interConfig.hiddenNetworkSsid.assign(scanConfig.hiddenNetworkSsid.begin(), scanConfig.hiddenNetworkSsid.end());interConfig.scanStyle = scanConfig.scanStyle;/* Specified frequency */if (scanConfig.scanBand == SCAN_BAND_UNSPECIFIED) {interConfig.scanFreqs.assign(scanConfig.scanFreqs.begin(), scanConfig.scanFreqs.end());/** When band is SCAN_BAND_BOTH_WITH_DFS, need to scan all frequency,* scanFreqs can be empty.*/} else if (scanConfig.scanBand != SCAN_BAND_BOTH_WITH_DFS) {/* Converting frequency bands to frequencies. */if (!GetBandFreqs(scanConfig.scanBand, interConfig.scanFreqs)) {WIFI_LOGE("GetBandFreqs failed.\n");return false;}}/* Save the configuration. */int requestIndex = StoreRequestScanConfig(scanConfig, interConfig);if (requestIndex == MAX_SCAN_CONFIG_STORE_INDEX) {WIFI_LOGE("StoreRequestScanConfig failed.\n");return false;}std::unique_lock<std::mutex> lock(scanConfigMapMutex);if (pScanStateMachine == nullptr) {WIFI_LOGE("pScanStateMachine is null.\n");return false;}/* Construct a message. */   // 去状态机里面处理这个消息InternalMessage *interMessage =pScanStateMachine->CreateMessage(static_cast<int>(CMD_START_COMMON_SCAN), requestIndex);if (interMessage == nullptr) {scanConfigMap.erase(requestIndex);WIFI_LOGE("CreateMessage failed.\n");return false;}if (!AddScanMessageBody(interMessage, interConfig)) {scanConfigMap.erase(requestIndex);MessageManage::GetInstance().ReclaimMsg(interMessage);WIFI_LOGE("AddScanMessageBody failed.\n");return false;}pScanStateMachine->SendMessage(interMessage);return true;
}//foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_scan/scan_state_machine.cpp
bool ScanStateMachine::HardwareReady::ExecuteStateMsg(InternalMessage *msg)
{WIFI_LOGI("ScanStateMachine::HardwareReady::ExecuteStateMsg.\n");if (msg == nullptr) {WIFI_LOGE("msg is null.\n");return true;}switch (msg->GetMessageName()) {case CMD_START_COMMON_SCAN:      ---》 处理这个消息pScanStateMachine->CommonScanRequestProcess(msg);return true;case CMD_START_PNO_SCAN:pScanStateMachine->PnoScanRequestProcess(msg);return true;default:return false;}
}
// 发起 CommonScanRequestProcess 流程
void ScanStateMachine::CommonScanRequestProcess(InternalMessage *interMessage)
{WIFI_LOGI("ScanStateMachine::CommonScanRequestProcess.\n");int requestIndex = 0;InterScanConfig scanConfig;if (!GetCommonScanRequestInfo(interMessage, requestIndex, scanConfig)) {ReportCommonScanFailed(requestIndex);return;}if (!VerifyScanStyle(scanConfig.scanStyle)) {WIFI_LOGE("invalid scan type");return;}{std::unique_lock<std::shared_mutex> guard(lock);waitingScans.insert(std::pair<int, InterScanConfig>(requestIndex, scanConfig));}StartNewCommonScan();
}void ScanStateMachine::StartNewCommonScan()
{WIFI_LOGI("Enter ScanStateMachine::StartNewCommonScan.\n");{std::shared_lock<std::shared_mutex> guard(lock);if (waitingScans.size() == 0) {ContinuePnoScanProcess();return;}ClearRunningScanSettings();bool hasFullScan = false;/* Traverse the request list and combine parameters */std::map<int, InterScanConfig>::iterator configIter = waitingScans.begin();for (; configIter != waitingScans.end(); ++configIter) {runningScanSettings.scanStyle = MergeScanStyle(runningScanSettings.scanStyle, configIter->second.scanStyle);std::vector<std::string>::iterator hiddenIter = configIter->second.hiddenNetworkSsid.begin();/* Remove duplicate hidden list */for (; hiddenIter != configIter->second.hiddenNetworkSsid.end(); ++hiddenIter) {if (std::find(runningScanSettings.hiddenNetworkSsid.begin(),runningScanSettings.hiddenNetworkSsid.end(),*hiddenIter) != runningScanSettings.hiddenNetworkSsid.end()) {continue;}runningScanSettings.hiddenNetworkSsid.push_back(*hiddenIter);}if (!hasFullScan) {/* When scanFreqs is empty, it means that scan all frequenties */if (configIter->second.scanFreqs.empty()) {runningScanSettings.scanFreqs.clear();runningFullScanFlag = true;hasFullScan = true;} else {std::vector<int>::iterator freqIter = configIter->second.scanFreqs.begin();/* Repetitions are eliminated */for (; freqIter != configIter->second.scanFreqs.end(); ++freqIter) {if (std::find(runningScanSettings.scanFreqs.begin(),runningScanSettings.scanFreqs.end(),*freqIter) != runningScanSettings.scanFreqs.end()) {continue;}runningScanSettings.scanFreqs.push_back(*freqIter);}}}}}if (!StartSingleCommonScan(runningScanSettings)) {       ---》 继续看这个ReportCommonScanFailedAndClear(false);ContinuePnoScanProcess();return;}std::unique_lock<std::shared_mutex> guard(lock);runningScans.swap(waitingScans);waitingScans.clear();SwitchState(commonScanningState);WIFI_LOGI("StartNewCommonScan success.\n");
}bool ScanStateMachine::StartSingleCommonScan(WifiScanParam &scanParam)
{WIFI_LOGI("Enter ScanStateMachine::StartSingleCommonScan.\n");for (auto freqIter = scanParam.scanFreqs.begin(); freqIter != scanParam.scanFreqs.end(); ++freqIter) {WIFI_LOGI("freq is %{public}d.\n", *freqIter);}for (auto hiddenIter = scanParam.hiddenNetworkSsid.begin(); hiddenIter != scanParam.hiddenNetworkSsid.end();++hiddenIter) {WIFI_LOGI("hidden ssid is %{public}s.\n", SsidAnonymize(*hiddenIter).c_str());}WIFI_LOGI("Begin call Scan.\n");WifiErrorNo ret = WifiStaHalInterface::GetInstance().Scan(scanParam);      ---> 是不是很熟悉,要通过idl_clientif ((ret != WIFI_IDL_OPT_OK) && (ret != WIFI_IDL_OPT_SCAN_BUSY)) {WIFI_LOGE("WifiStaHalInterface::GetInstance().scan failed.");return false;}WIFI_LOGI("End call Scan.\n");/** Start the timer. If no result is returned for a long time, the scanning* fails*/StartTimer(static_cast<int>(WAIT_SCAN_RESULT_TIMER), MAX_WAIT_SCAN_RESULT_TIME);return true;
}

继续看下代码,WifiStaHalInterface里面的 Scan 方法:
foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_sta_hal_interface.cpp

WifiErrorNo WifiStaHalInterface::Scan(const WifiScanParam &scanParam)
{CHECK_NULL_AND_RETURN(mIdlClient, WIFI_IDL_OPT_FAILED);return mIdlClient->Scan(scanParam);
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_idl_client.cpp
WifiErrorNo WifiIdlClient::Scan(const WifiScanParam &scanParam)
{CHECK_CLIENT_NOT_NULL;ScanSettings settings;if (memset_s(&settings, sizeof(settings), 0, sizeof(settings)) != EOK) {return WIFI_IDL_OPT_FAILED;}bool bfail = false;do {if (scanParam.hiddenNetworkSsid.size() > 0) {settings.hiddenSsidSize = scanParam.hiddenNetworkSsid.size();settings.hiddenSsid = ConVectorToCArrayString(scanParam.hiddenNetworkSsid);if (settings.hiddenSsid == nullptr) {bfail = true;break;}}if (scanParam.scanFreqs.size() > 0) {settings.freqSize = scanParam.scanFreqs.size();settings.freqs = (int *)calloc(settings.freqSize, sizeof(int));if (settings.freqs == nullptr) {bfail = true;break;}for (int i = 0; i < settings.freqSize; ++i) {settings.freqs[i] = scanParam.scanFreqs[i];}}if (scanParam.scanStyle > 0) {settings.scanStyle = scanParam.scanStyle;}} while (0);WifiErrorNo err = WIFI_IDL_OPT_FAILED;if (!bfail) {err = StartScan(&settings);      ---> 向 Hal 发起扫描}if (settings.freqs != nullptr) {free(settings.freqs);settings.freqs = nullptr;}if (settings.hiddenSsid != nullptr) {for (int i = 0; i < settings.hiddenSsidSize; ++i) {free(settings.hiddenSsid[i]);settings.hiddenSsid[i] = nullptr;}free(settings.hiddenSsid);settings.hiddenSsid = nullptr;}return err;
}
// 中间省略了 RPC的调用,代码可以看wifi_hal_crpc_server类,处理客户端请求,然后根据请求找到对应的函数,在调用HAL的方法
// 直接看调用的HAL方法:foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_sta_interface.c
WifiErrorNo StartScan(const ScanSettings *settings)
{LOGD("Ready to start scan with param.");
#ifdef HDI_INTERFACE_SUPPORTint ret = HdiStartScan(settings);
#elseWifiWpaStaInterface *pStaIfc = GetWifiStaInterface(0);if (pStaIfc == NULL) {return WIFI_HAL_SUPPLICANT_NOT_INIT;}int ret = pStaIfc->wpaCliCmdScan(pStaIfc, settings);    ---> 往supplicant 发送命令if (ret < 0) {LOGE("StartScan failed! ret=%{public}d", ret);return WIFI_HAL_FAILED;}
#endifif (ret == WIFI_HAL_SCAN_BUSY) {LOGD("StartScan return scan busy");return WIFI_HAL_SCAN_BUSY;}LOGD("StartScan successfully!");return WIFI_HAL_SUCCESS;
}// int (*wpaCliCmdScan)(WifiWpaStaInterface *p, const ScanSettings *settings);
// wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wpa_sta_hal/wifi_supplicant_hal.c
static int WpaCliCmdScan(WifiWpaStaInterface *this, const ScanSettings *settings)
{if (this == NULL) {LOGE("WpaCliCmdScan, this is NULL!");return -1;}/* Invalidate expired scan results */WpaCliCmdBssFlush(this);unsigned len = CMD_BUFFER_SIZE;unsigned expectedLen = 0;if (settings != NULL) {expectedLen = AssignCmdLen(this, settings);}if (expectedLen >= len) {len = expectedLen + 1;}char *pcmd = (char *)calloc(len, sizeof(char));if (pcmd == NULL) {LOGE("WpaCliCmdScan, pcmd is NULL!");return -1;}int pos = 0;int res = 0;if (settings != NULL) {if (settings->scanStyle == SCAN_TYPE_PNO && settings->isStartPnoScan) {res = snprintf_s(pcmd, len, len - 1, "IFNAME=%s set pno 1", this->ifname);} else if (settings->scanStyle == SCAN_TYPE_PNO && !settings->isStartPnoScan) {res = snprintf_s(pcmd, len, len - 1, "IFNAME=%s set pno 0", this->ifname);} else {res = snprintf_s(pcmd, len, len - 1, "IFNAME=%s SCAN", this->ifname);}}if (res < 0) {LOGE("WpaCliCmdScan, snprintf_s error!");free(pcmd);return -1;}pos += res;if (settings != NULL && ConcatScanSetting(settings, pcmd + pos, len - pos) < 0) {LOGE("snprintf scan settings error");free(pcmd);return -1;}char buf[REPLY_BUF_SMALL_LENGTH] = {0};if (WpaCliCmd(pcmd, buf, sizeof(buf)) != 0) {      ---》 命令下发free(pcmd);return -1;}free(pcmd);if (strncmp(buf, "FAIL-BUSY", strlen("FAIL-BUSY")) == 0) {LOGE("WpaCliCmdScan, WpaCliCmd return FAIL-BUSY!");return FAIL_BUSY;}return 0;
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wifi_wpa_common.c
int 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);if (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 收到命令后去做扫描动作,那扫描到结果如何通知上层呢?下一篇我们继续记录。

相关文章:

鸿蒙 WiFi 扫描流程(2)

接着上篇没有记录完的&#xff0c;我们继续梳理&#xff0c;需要上一篇做基础的请看&#xff1a;鸿蒙 WiFi 扫描流程&#xff08;1&#xff09; 上一篇我们讲到 scan_service.cpp 里面的 SingleScan 方法&#xff0c;继续这个方法往下看&#xff1a; // foundation/communicat…...

微信小程序(四十)API的封装与调用

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.在单独的js文件中写js接口 2.以注册为全局wx的方式调用接口 源码&#xff1a; utils/testAPI.js const testAPI{/*** * param {*} title */simpleToast(title提示){//可传参&#xff0c;默认为‘提示’wx.sho…...

WebSocket+Http实现功能加成

WebSocketHttp实现功能加成 前言 首先&#xff0c;WebSocket和HTTP是两种不同的协议&#xff0c;它们在设计和用途上有一些显著的区别。以下是它们的主要特点和区别&#xff1a; HTTP (HyperText Transfer Protocol): 请求-响应模型&#xff1a; HTTP 是基于请求-响应模型的协…...

go语言实现LRU缓存

go语言实现LRU Cache 题目描述详细代码 题目描述 设计和构建一个“最近最少使用”缓存&#xff0c;该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值)&#xff0c;并在初始化时指定最大容量。当缓存被填满时&#xff0c;它应该删除最近最…...

git的奇特知识点

展示帮助信息 git help -gThe common Git guides are:attributes Defining attributes per pathcli Git command-line interface and conventionscore-tutorial A Git core tutorial for developerscvs-migration Git for CVS usersdiff…...

按键扫描16Hz-单片机通用模板

按键扫描16Hz-单片机通用模板 一、按键扫描的原理1、直接检测高低电平类型2、矩阵扫描类型3、ADC检测类型二、key.c的实现1、void keyScan(void) 按键扫描函数①void FHiKey(void) 按键按下功能②void FSameKey(void) 按键长按功能③void FLowKey(void) 按键释放功能三、key.h的…...

在容器镜像中为了安全为什么要删除 setuid 和 setgid?

在容器镜像中删除 setuid&#xff08;set user ID&#xff09;和 setgid&#xff08;set group ID&#xff09;权限通常是出于安全考虑。这两个权限位允许进程在执行时以文件所有者或文件所属组的身份运行&#xff0c;而不是以调用进程的用户身份运行。 删除 setuid 和 setgid…...

Flink 动态表 (Dynamic Table) 解读

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…...

【原创 附源码】Flutter海外登录--Google登录最详细流程

最近接触了几个海外登录的平台&#xff0c;踩了很多坑&#xff0c;也总结了很多东西&#xff0c;决定记录下来给路过的兄弟坐个参考&#xff0c;也留着以后留着回顾。更新时间为2024年2月8日&#xff0c;后续集成方式可能会有变动&#xff0c;所以目前的集成流程仅供参考&#…...

第70讲axios后端请求工具类封装

axios工具类封装&#xff1a; // 引入axios import axios from axios;// 创建axios实例 const httpService axios.create({// url前缀-http:xxx.xxx// baseURL: process.env.BASE_API, // 需自定义baseURL:http://localhost:80/,// 请求超时时间timeout: 3000 // 需自定义 })…...

【数学建模】【2024年】【第40届】【MCM/ICM】【F题 减少非法野生动物贸易】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 ICM Problem F: Reducing Illegal Wildlife Trade Illegal wildlife trade negatively impacts our environment and threatens global biodiversity. It is estimated to involve up to 26.5 billion US dollars per y…...

第3节、电机定速转动【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;本节介绍用定时器定时的方式&#xff0c;精准控制脉冲时间&#xff0c;从而控制步进电机速度。 一、计算过程 电机每一步的角速度等于走这一步所花费的时间&#xff0c;走一步角度等于步距角&#xff…...

【51单片机】LCD1602(可视化液晶屏)调试工具的使用

前言 大家好吖&#xff0c;欢迎来到 YY 滴 单片机系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY…...

Netty应用(四) 之 Reactor模型 零拷贝

目录 6.Reactor模型 6.1 单线程Reactor 6.2 主从多线程Reactor (主--->Boss | 从--->Worker | 一主多从机制) 7.扩展与补充 8.Reactor模型的实现 8.1 多线程Reactor模型的实现&#xff08;一个Boss线程&#xff0c;一个Worker线程&#xff09; 8.2 多线程Reactor模…...

Huggingface上传模型

Huggingface上传自己的模型 参考 https://juejin.cn/post/7081452948550746148https://huggingface.co/blog/password-git-deprecationAdding your model to the Hugging Face Hub&#xff0c; huggingface.co/docs/hub/ad…Welcome&#xff0c;huggingface.co/welcome三句指…...

kyuubi 接入starrocks | doris

kyuubi 接入starrocks 一、环境 Hadoop集群 组件版本Hadoop3.1.1spark3.Xzookeeper3.XHive3.X kyuubi 版本 1.7.1 starrocks 2.X   已将kyuubi部署到yarn上&#xff0c;并且接入了spark3引擎&#xff0c;并通过Ambari进行kyuubi组件的管理&#xff0c;下面步骤为新增对sta…...

notepad++成功安装后默认显示英文怎么设置中文界面?

前几天使用电脑华为管家清理电脑后&#xff0c;发现一直使用的notepad软件变回了英文界面&#xff0c;跟刚成功安装的时候一样&#xff0c;那么应该怎么设置为中文界面呢&#xff1f;具体操作如下&#xff1a; 1、打开notepad软件&#xff0c;点击菜单栏“Settings – Prefere…...

HiveSQL——连续增长问题

注&#xff1a;参考文章&#xff1a; SQL连续增长问题--HQL面试题35_sql判断一个列是否连续增长-CSDN博客文章浏览阅读2.6k次&#xff0c;点赞6次&#xff0c;收藏30次。目录0 需求分析1 数据准备3 小结0 需求分析假设我们有一张订单表shop_order shop_id,order_id,order_time…...

使用cocos2d-console初始化一个项目

先下载好cocos2d-x的源码包 地址 https://www.cocos.com/cocos2dx-download 这里使用的版本是 自己的电脑要先装好python27 用python安装cocos2d-console 看到项目中有个setup.py的一个文件 python setup.py 用上面的命令执行一下。 如果执行正常的话回出现上面的图 然后…...

VitePress-13- 配置-title的作用详解

作用描述 1、title 是当前站点的标题&#xff1b;2、默认值是 &#xff1a;VitePress&#xff1b;3、当使用默认主题时&#xff0c;会直接展示在 页面的【导航条】中&#xff1b;4、一个特殊的作用 &#xff1a; 会作为单个页面的默认标题后缀&#xff01;除非又指定了【title…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

Git 命令全流程总结

以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结&#xff0c;按操作场景分类整理&#xff1a; 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...

奈飞工厂官网,国内Netflix影视在线看|中文网页电脑版入口

奈飞工厂是一个专注于提供免费Netflix影视资源的在线播放平台&#xff0c;致力于为国内用户提供的Netflix热门影视内容。该平台的资源与Netflix官网基本同步&#xff0c;涵盖电影、电视剧、动漫和综艺等多个领域。奈飞工厂的界面简洁流畅&#xff0c;资源分类清晰&#xff0c;方…...