主頁 > 企業開發 > 京喜前端自動化測驗之路(小程式篇)

京喜前端自動化測驗之路(小程式篇)

2020-09-14 16:39:09 企業開發

作者:阿翔

如果你已經閱讀過 《京喜前端自動化測驗之路(一)》,可跳過前言部分閱讀,

前言

京喜(原京東拼購)專案,作為京東戰略級業務,擁有千萬級別的流量入口,為了保障線上業務的穩定運行,每月例行開展前端容災演習,主要包含小程式及 H5 版本,要求各頁面各模塊在例外情況下進行適當的降級處理,不能出現空窗、樣式錯亂、不合理的錯誤提示等體驗問題,

容災演習是一項長期持續的作業,且涉及頁面功能及場景多,人工的切換場景模擬例外導致演習效率較低,因此想通過開發自動化測驗工具來提升演習效率,讓容災演習作業隨時可以輕松開展,由于京喜 H5 和小程式場景差異比較大,自動化測驗分 H5 和小程式兩部分進行,前期已經分享過 H5 的自動化測驗方案 —— 京喜前端自動化測驗之路(一),本文則主要講述小程式版的自動化測驗方案,

綜上所述,我們希望京喜小程式自動化測驗工具可以提供以下功能:

  1. 訪問目標頁面,對頁面進行截圖;
  2. 模擬用戶點擊、滑動頁面操作;
  3. 網路攔截、模擬例外情況(介面回應碼 500、介面回傳資料例外);
  4. 操作快取資料(模擬有無快取的場景等),

小程式自動化 SDK

聊到小程式的自動化工具,微信官方為開發者提供了一套小程式自動化 SDK —— miniprogram-automator , 我們不需要關注技術選型,可直接使用,

小程式自動化 SDK 為開發者提供了一套通過外部腳本操控小程式的方案,從而實作小程式自動化測驗的目的,

如果你之前使用過 Selenium WebDriver 或者 Puppeteer,那你可以很容易快速上手,小程式自動化 SDK 與它們的作業原理是類似的,主要區別在于控制物件由瀏覽器換成了小程式,

特性

通過該 SDK,你可以做到以下事情:

  • 控制小程式跳轉到指定頁面
  • 獲取小程式頁面資料
  • 獲取小程式頁面元素狀態
  • 觸發小程式元素系結事件
  • 往 AppService 注入代碼片段
  • 呼叫 wx 物件上任意介面
  • ...

示例

const automator = require('miniprogram-automator')

automator
    .launch({
        cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 工具 cli 位置(絕對路徑)
        projectPath: 'path/to/project', // 專案檔案地址(絕對路徑)
    })
    .then(async miniProgram => {
        const page = await miniProgram.reLaunch('/pages/index/index')
        await page.waitFor(500)
        const element = await page.$('.banner')
        console.log(await element.attribute('class'))
        await element.tap()
        await miniProgram.close()
    })

綜上所述,我們選擇使用官方維護的 SDK —— miniprogram-automator 開發小程式的自動化測驗工具,通過 SDK 提供的一系列 API ,實作訪問目標頁面、模擬例外場景、生成截圖的程序自動化,最后再通過人工比對截圖,判斷頁面降級處理是否符合預預期、用戶體驗是否友好,

實作方案

原來的容災演習程序:

小程式的通信方式改成 HTTPS ,通過 Whistle 對介面回傳進行修改來模擬例外情況,驗證各頁面各模塊的降級處理符合預期,

現階段的容災演習自動化方案:

我們將容災演習程序分為自動化流程人工操作兩部分,

自動化流程:

  1. 啟動微信開發者工具(開發版);
  2. 訪問目標頁面,模擬用戶點擊、滑動等行為;
  3. 模擬例外場景:攔截網路請求,修改介面回傳資料(介面回傳 500、例外資料等);
  4. 生成截圖,

人工操作:

自動化腳本執行完畢后,人工比對各個場景的截圖,判斷是否符合預期,

方案流程圖:
xxx

開發實錄

快速創建測驗用例

為了提高測驗腳本的可維護性、擴展性,我們將測驗用例的資訊都配置到 JSON 檔案中,這樣撰寫測驗腳本的時候,我們只需關注測驗流程的實作,

測驗用例 JSON 資料配置包括公用資料(global)私有資料

公用資料(global):各測驗用例都需要用到的資料,如:模擬訪問的目標頁面地址、名字、描述、設備型別等,

私有資料: 各測驗用例特定的資料,如測驗模塊資訊、api 地址、測驗場景、預期結果、截圖名字等資料,

{
  "global": {
    "url": "/pages/index/index",
    "pageName": "index",
    "pageDesc": "首頁",
    "device": "iPhone X"
  },
  "homePageApi": {
    "id": 1,
    "module": "home_page_api",
    "moduleDesc": "首頁主介面",
    "api": "https://xxx",
    "operation": "模擬回應碼 500",
    "expectRules": [
      "1. 有快取資料,顯示容災兜底資料",
      "2. 請求容災介面,顯示容災兜底資料",
      "3. 容災介面例外,顯示信例外息、重繪按鈕",
      "4. 恢復網路,點擊重繪按鈕,顯示正常資料"
    ],
    "screenshot": [
      {
        "name": "normal",
        "desc": "正常場景"
      },
      {
        "name": "500_cache",
        "desc": "有快取-主介面回傳500"
      },
      {
        "name": "500_no_cache",
        "desc": "無快取-主介面回傳500-容災兜底資料"
      },
      {
        "name": "500_no_cache_500_disaster",
        "desc": "無快取-主介面回傳500-容災兜底介面回傳500"
      },
      {
        "name": "500_no_cache_recover",
        "desc": "無快取-回傳500-恢復網路"
      }
    ]
  },
  …
}

撰寫測驗腳本

我們以京喜首頁主介面的測驗用例為例子,通過模擬主介面回傳 500 回應碼的例外場景,驗證主介面的例外處理機制是否完善、用戶體驗是否友好,

預期效果:

  • 主介面例外,有快取資料,顯示快取資料
  • 主介面例外,無快取資料,則請求容災介面,顯示容災兜底資料
  • 主介面、容災介面例外,無快取資料,顯示信例外息、重繪按鈕
  • 恢復網路,點擊重繪按鈕,顯示正常資料

測驗流程:

ddd

場景實作:

根據測驗流程以及配置的測驗用例資訊,撰寫測驗腳本,模擬測驗用例場景:

  1. 訪問頁面
const miniProgram = await automator.launch({
      cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 開發者工具命令列工具(絕對路徑)
      projectPath: 'jx_project', // 專案地址(絕對路徑)

})
await miniProgram.reLaunch('/pages/index/index')
  1. 生成截圖
await miniProgram.screenshot({
    path: 'jx_weapp_index_home_page_500.png'
})

  1. 模擬例外資料
const getMockData = (url, mockType, mockValue) => {
    const result = {
      data: 'test',
      cookies: [],
      header: {},
      statusCode: 200,
    }

    switch (mockType) {
      case 'data':
        result.data = https://www.cnblogs.com/o2team/p/getMockResponse(url, mockValue) // 修改回傳資料
        break
      case'cookies':
        result.cookies = mockValue // 修改回傳資料
        break
      case 'header':
        result.header = mockValue // 修改回傳回應頭
        break
      case 'statusCode':
        result.statusCode = mockValue // 修改回傳回應頭
        break
    }

    return {
      rule: url,
      result
    }
  }

 // 修改本地存盤資料
 const mockValue = {
     data: {
         modules: [{
            tpl:'3000',
            content: []
         }]
     }
 }
 const mockData =  https://www.cnblogs.com/o2team/p/[
    getMockData(api1,'statusCode', 500), // 模擬介面回傳 500
    getMockData(api2, 'data', mockValue) // 模擬介面回傳例外資料
    ...
 ]
 
  1. 攔截介面請求,修改回傳資料
const interceptAPI = async (miniProgram, url, mockData) => {
    try {
      await miniProgram.mockWxMethod(
        'request',
        function(obj, data) { // 處理回傳函式
          for (let i = 0, len = data.length; i < len; i++) {
            const item = data[i]
            // 命中規則的回傳 mockData
            if (obj.url.indexOf(item.rule) > -1) {
              return item.result
            }
          }
          // 沒命中規則的真實訪問后臺
          return new Promise(resolve => {
            obj.success = res => resolve(res)
            obj.fail = res => resolve(res)
            / origin 指向原始方法
            this.origin(obj)
          })
        },
        mockData, // 傳入 mock 資料
      )

    } catch (e) {
      console.error(`攔截【${url}】API報錯`)
      console.error(e)
    }
  }

await interceptAPI(interceptAPI, url, mockData)
  • miniProgram.mockWxMethod:覆寫 wx 物件上指定方法的呼叫結果,利用該 API,可以覆寫 wx.request API,攔截網路請求,修改回傳資料,
  • 目前是本地存盤一份介面回傳的 JSON 資料,通過修改本地的 JSON 資料生成 mockData,若需要修改介面實時回傳的資料,可在 obj.success 中獲取實時資料并修改,
  1. 清除快取
try {
    await miniProgram.callWxMethod('clearStorage')
} catch (e) {
    await console.log(`清除快取報錯: `)
    await console.log(e)
}
  1. 點擊重繪按鈕
const page = await miniProgram.currentPage()
const $refreshBtn = await page.$('.page-error__refresh-btn') // 同 WXSS,僅支持部分 CSS 選擇器
await $refreshBtn.tap()
  1. 取消攔截,恢復網路
const cancelInterceptAPI = async (miniProgram) => {
    try {
      await miniProgram.restoreWxMethod('request') // 重置 wx.request ,消除 mockWxMethod 呼叫的影響,
    } catch (e) {
      console.error(`取消攔截【${url}】API報錯`)
      console.error(e)
    }
}

await cancelInterceptAPI(miniProgram)

啟動自動化測驗

由于第一階段的測驗工具尚未平臺化,先通過在終端輸入命令列,運行腳本的方式,啟動自動化測驗,

在專案的 package.json 檔案中,使用 scripts 欄位定義腳本命令:

 "scripts": {
    "start": "node pages/index/index.js"
  },

運行環境:

  • 安裝 Node.js 并且版本大于 8.0
  • 基礎庫版本為 2.7.3 及以上
  • 開發者工具版本為 1.02.1907232 及以上

運行:

在終端切入到專案根目錄路徑,輸入以下命令列,就可以啟動測驗工具,運行測驗腳本,

$ npm run start

測驗結果

人工比對截圖結果:

測驗結果圖

運行腳本示例:

運行腳本示例

使用 SDK,你必須知道 Shadow DOM

當我們想控制小程式頁面時,需獲取頁面實體 page,利用 page 提供的方法控制頁面內的元素,

比如,當我們想點擊頁面中搜索框時,我們一般會這么做:

 const page = await miniProgram.currentPage()
 const $searchBar = await page.$('search-bar')
 await $searchBar.tap()

但這樣真的可行嗎?答案是:

試試就知道了,

運行這段測驗腳本后生成的截圖:

沒有觸發點擊

我們得到的結果是:根本沒有觸發點擊事件,

Shadow DOM:

它是 HTML 的一個規范,它允許在檔案( document )渲染時插入一顆DOM元素子樹,但是這個子樹不在主 DOM 樹中,

它允許瀏覽器開發者封裝自己的 HTML 標簽、css 樣式和特定的 javascript 代碼、同時開發人員也可以創建類似 <input>、<video>、<audio> 等、這樣的自定義的一級標簽,創建這些標簽內容相關的 API,可以被叫做 Web Component,

Shadow DOM 的關鍵所在,它可以將一個隱藏的、獨立的 DOM 附加到一個元素上,

  • Shadow host: 一個常規 DOM 節點,Shadow DOM 會被附加到這個節點上,它是 Shadow DOM 的一個宿主元素,比如:<input />、<audio>、<video> 標簽,就是 Shadow DOM 的宿主元素,
  • Shadow tree: Shadow DOM 內部的 DOM 樹,
  • Shadow root: Shadow DOM 的根節點,通過 createShadowRoot 回傳的檔案片段被稱為 shadow-root , 它和它的后代元素,都會對用戶隱藏,

回到我們剛剛的問題:

由于小程式使用了 Shadow DOM,因此我們不能直接通過 page 實體獲取到搜索框真實 DOM,我們看到的頁面中渲染的搜索框,實際上是一個 Shadow DOM,因此,我們必須先獲取到搜索框 Shadow DOM 的宿主元素,并通過宿主元素獲取到搜索框真實的 DOM,最后觸發真實 DOM 的點擊事件,

  const page = await miniProgram.currentPage()
  const $searchBarShadow = await page.$('search-bar')
  const $searchBar = await $searchBarShadow.$('.search-bar')
  const { height } = await $searchBar.size()

運行這段測驗腳本后生成的截圖:

搜索頁

從截圖可以看到,觸發了搜索框的點擊事件,

更多測驗場景實作

1. 下拉重繪

const pullDownRefresh = async (miniProgram) => {
    try {
      await miniProgram.callWxMethod('startPullDownRefresh')
    } catch (e) {
      console.error('下拉重繪操作失敗')
      console.error(e)
    }
}

2. 滾動到指定 DOM

const page = await miniProgram.currentPage() // 獲取頁面實體
const $recommendTabShadow = await page.$('recommend-tab') // 獲取Shadow DOM
const $recommendTab = await $recommendTabShadow.$('.recommend') // 獲取真實 DOM
const { top } = await $recommendTab.offset() // 獲取DOM 定位
await miniProgram.pageScrollTo(top) // 滾動到指定DOM

3. 事件

  • 日志列印;
  • 監聽頁面崩潰事件
// 日志列印時觸發
miniProgram.on('console', msg => {
    console.log(msg.type, msg.args)
  })
})

// 頁面 JS 出錯時觸發
page.on('error', (e) => {
    console.log(e)
})

結語

第一階段的小程式自動化測驗之路告一段落,和 H5 自動化測驗一樣,容災演習已實作了半自動化,可通過在終端運行測驗腳本,模擬例外場景自動生成截圖,再配合人工比對截圖操作,判斷演習結果是否符合預期,目前已投入到每個月的容災演習中使用,

由于 H5 和小程式的差異比較大,第一階段的自動化測驗分兩端進行,測驗腳本語法也是截然不同,需要同時維護兩套測驗工具,為了降低維護成本,提升測驗腳本的開發效率,我們正在研發第二階段的自動化測驗工具,一套代碼支持兩端測驗,目前已經進入內測階段,更多彩蛋,敬請期待第二階段自動化測驗工具——多端自動化測驗 SDK ,


歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:

歡迎關注凹凸實驗室公眾號

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/36866.html

標籤:JavaScript

上一篇:詳解JS中定時器setInterval和setTImeout的this指向問題

下一篇:d3js scales深入理解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more