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

微信小程序(四十)API的封装与调用
注释很详细,直接上代码 上一篇 新增内容: 1.在单独的js文件中写js接口 2.以注册为全局wx的方式调用接口 源码: utils/testAPI.js const testAPI{/*** * param {*} title */simpleToast(title提示){//可传参,默认为‘提示’wx.sho…...

WebSocket+Http实现功能加成
WebSocketHttp实现功能加成 前言 首先,WebSocket和HTTP是两种不同的协议,它们在设计和用途上有一些显著的区别。以下是它们的主要特点和区别: HTTP (HyperText Transfer Protocol): 请求-响应模型: HTTP 是基于请求-响应模型的协…...
go语言实现LRU缓存
go语言实现LRU Cache 题目描述详细代码 题目描述 设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最…...
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(set user ID)和 setgid(set group ID)权限通常是出于安全考虑。这两个权限位允许进程在执行时以文件所有者或文件所属组的身份运行,而不是以调用进程的用户身份运行。 删除 setuid 和 setgid…...

Flink 动态表 (Dynamic Table) 解读
博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,…...

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

第70讲axios后端请求工具类封装
axios工具类封装: // 引入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题 减少非法野生动物贸易】【解题思路】
一、题目 (一) 赛题原文 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步进电机系列教程】
↑↑↑点击上方【目录】,查看本系列全部文章 摘要:本节介绍用定时器定时的方式,精准控制脉冲时间,从而控制步进电机速度。 一、计算过程 电机每一步的角速度等于走这一步所花费的时间,走一步角度等于步距角ÿ…...

【51单片机】LCD1602(可视化液晶屏)调试工具的使用
前言 大家好吖,欢迎来到 YY 滴 单片机系列 ,热烈欢迎! 本章主要内容面向接触过单片机的老铁 主要内容含: 欢迎订阅 YY滴C专栏!更多干货持续更新!以下是传送门! YY的《C》专栏YY的《C11》专栏YY…...

Netty应用(四) 之 Reactor模型 零拷贝
目录 6.Reactor模型 6.1 单线程Reactor 6.2 主从多线程Reactor (主--->Boss | 从--->Worker | 一主多从机制) 7.扩展与补充 8.Reactor模型的实现 8.1 多线程Reactor模型的实现(一个Boss线程,一个Worker线程) 8.2 多线程Reactor模…...

Huggingface上传模型
Huggingface上传自己的模型 参考 https://juejin.cn/post/7081452948550746148https://huggingface.co/blog/password-git-deprecationAdding your model to the Hugging Face Hub, huggingface.co/docs/hub/ad…Welcome,huggingface.co/welcome三句指…...
kyuubi 接入starrocks | doris
kyuubi 接入starrocks 一、环境 Hadoop集群 组件版本Hadoop3.1.1spark3.Xzookeeper3.XHive3.X kyuubi 版本 1.7.1 starrocks 2.X 已将kyuubi部署到yarn上,并且接入了spark3引擎,并通过Ambari进行kyuubi组件的管理,下面步骤为新增对sta…...

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

HiveSQL——连续增长问题
注:参考文章: SQL连续增长问题--HQL面试题35_sql判断一个列是否连续增长-CSDN博客文章浏览阅读2.6k次,点赞6次,收藏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 是当前站点的标题;2、默认值是 :VitePress;3、当使用默认主题时,会直接展示在 页面的【导航条】中;4、一个特殊的作用 : 会作为单个页面的默认标题后缀!除非又指定了【title…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...