剝開比原看代碼09:通過dashboard創建密鑰時,前端的數據是如何傳到後端的?

来源:https://www.cnblogs.com/bytom/archive/2018/07/23/9356787.html
-Advertisement-
Play Games

作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在前面一篇文章,我們粗略的研究了一下比原的dashboard是如何做出來的,但是對里 ...


作者:freewind

比原項目倉庫:

Github地址:https://github.com/Bytom/bytom

Gitee地址:https://gitee.com/BytomBlockchain/bytom

在前面一篇文章,我們粗略的研究了一下比原的dashboard是如何做出來的,但是對裡面提到的各種細節功能,並沒有深入的去研究。那麼從本文開始,我們將在這一段時間,分別研究裡面提到的每一項功能。

在前一篇文章中,當我們第一次在瀏覽器中打開dashboard時,因為還沒有創建過密鑰,所以比原會提示我們輸入一些別名和密碼,為我們創建一個密鑰和相應的帳戶。就是下麵這張圖所對應的:  

那麼本文就將研究一下,當我們點擊了"Register"按鈕以後,我們在前端頁面上填寫的參數,到底是如何一步步的傳到比原的後端的。

跟之前一樣,我們將對這個問題進行細分,然後各個擊破:

  1. 前端:當我們填完表單,點了提交以後,比原在前端是如何發送數據的?
  2. 後端:比原的後端是如何接收到數據的?

前端:當我們填完表單,點了提交以後,數據會發送到後端的哪個介面?

當我們點擊了"Register"按鈕,在前端頁面中,一定會在某個地方觸發一個向比原節點webapi介面發出請求的操作。究竟是訪問的哪個web api?提交的數據又是什麼樣的呢?讓我們先從前端代碼中尋找一下。

註意,比原的前端代碼位於另一個項目倉庫bytom/dashboard中。為了能與我們在本系列文章中使用的比原v1.0.1的代碼相匹配,我找到了dashboard中的v1.0.0的代碼,並且提交到了一個單獨的項目中:freewind/bytom-dashboard-v1.0.0。註意該項目代碼未做任何修改,其master分支對應於官方代碼倉庫的v1.0.0分支。之所以要弄一個單獨的出來,這是因為我們在文章中,每次引用一段代碼的時候,都會給出相應的github上的鏈接,方便讀者跳過去查看全貌,使用一個獨立項目,會讓這個過程更簡便一些。

由於比原的前端頁面是使用React為主的,所以我猜想在代碼中,也該會有一個名為Register的組件,或者某個表單中有一個名為Register的按鈕。經過搜索,我們幸運的發現了Register.jsx 這個組件文件,它正好是我們需要的。

經過高度簡化後的代碼如下:

src/features/app/components/Register/Register.jsx#L9-L148

class Register extends React.Component {
  // ...
  // 4. 
  submitWithErrors(data) {
    return new Promise((resolve, reject) => {
      // 5. 
      this.props.registerKey(data)
        .catch((err) => reject({_error: err.message}))
    })
  }
  // ...

  render() {
    // ...
    return (
      // ...
      // 3.
      <form className={styles.form} onSubmit={handleSubmit(this.submitWithErrors)}>
        // 1.
        <TextField
          title={lang === 'zh' ? '賬戶別名' : 'Account Alias'}
          placeholder={lang === 'zh' ? '請輸入賬戶別名...' : 'Please enter the account alias...'}
          fieldProps={accountAlias} />
        <TextField
          title={lang === 'zh' ? '密鑰別名' : 'Key Alias'}
          placeholder={lang === 'zh' ? '請輸入密鑰別名...' : 'Please enter the key alias...'}
          fieldProps={keyAlias}/>
        <TextField
          title={lang === 'zh' ? '密鑰密碼' : 'Key Password'}
          placeholder={lang === 'zh' ? '請輸入密鑰密碼...' : 'Please enter the key password...'}
          fieldProps={password}
          type='password'/>
        <TextField
          title={lang === 'zh' ? '重覆輸入密鑰密碼' : 'Repeat your key password'}
          placeholder={lang === 'zh' ? '請重覆輸入密鑰密碼...' : 'Please repeat the key password...'}
          fieldProps={repeatPassword}
          type='password'/>

        // 2. 
        <button type='submit' className='btn btn-primary' disabled={submitting}>
          {lang === 'zh' ? '註冊' : 'Register'}
        </button>
        // ....
        </form>
      // ...
    )
  }
}

上面的代碼,共有5個地方需要註意,被我用數字標示出來了。註意這5個數字並不是從上到下標註,而是按照我們關註的順序來的:

  1. 表單上的各個輸入框,就是我們填寫別名和密碼的地方。這裡需要關註的是每個TextFieldfieldProps屬性,它對應我們提交到後臺的數據的name
  2. 就是那個“Register”按鈕了。需要註意的是,它的typesubmit,也就是說,點擊它以後,將會觸發所在formonSubmit方法
  3. 回到了form的開頭。註意它的onSubmit裡面,調用的是handleSubmit(this.submitWithErrors)。其中的handleSubmit是從該表單所使用的第三方redux-form中傳入的,用來處理表單提交,我們在這裡不關註它,只需要知道我們需要把自己的處理函數this.submitWithErrors傳給它。而在後者中,我們將會調用比原節點提供的web api
  4. 第3步中的this.submitWithErrors最終將走到這裡定義的submitWithErrors函數
  5. submitWithErrors將會發起一個非同步請求,最終調用由外部傳進來的registerKey函數

從這裡我們還看不到調用的是哪個api,所以我們必須繼續去尋找registerKey。很快就在同文件中找到了registerKey

src/features/app/components/Register/Register.jsx#L176-L180

(dispatch) => ({
    registerKey: (token) => dispatch(actions.core.registerKey(token)),
    // ...
  })

它又將會調用actions.core.registerKey這個函數:

src/features/core/actions.js#L44-L87

const registerKey = (data) => {
  return (dispatch) => {
    // ...
    // 1.1
    const keyData = {
      'alias': data.keyAlias,
      'password': data.password
    }
    // 1.2
    return chainClient().mockHsm.keys.create(keyData)
      .then((resp) => {
        // ...
        // 2.1
        const accountData = {
          'root_xpubs':[resp.data.xpub],
          'quorum':1,
          'alias': data.accountAlias}
        // 2.2
        dispatch({type: 'CREATE_REGISTER_KEY', data})

        // 2.3
        chainClient().accounts.create(accountData)
          .then((resp) => {
            // ...
            // 2.4
            if(resp.status === 'success') {
              dispatch({type: 'CREATE_REGISTER_ACCOUNT', resp})
            }
          })
    // ...
      })
    // ...
  }
}

可以看到,在這個函數中,做的事情還是很多的。而且並不是我一開始預料的調用一次後臺介面就行了,而是調用了兩次(分別是創建密鑰和創建帳戶)。下麵進行分析:

  1. 1.1是為了讓後臺創建密鑰而需要準備的參數,一個是alias,一個是password,它們都是用戶填寫的
  2. 1.2是調用後臺用於創建密鑰的介面,把keyData傳過去,並且拿到返回的resp後,進行後續的處理
  3. 2.1是為了讓後臺創建帳戶而需要準備的參數,分別是root_xpubsquorumalias,其中root_xpubs是創建密鑰後返回的公鑰,quorum目前不知道(TODO),alias是用戶填寫的帳戶別名
  4. 2.2這一句沒有作用(經過官方確認了),因為我在代碼中沒有找到處理CREATE_REGISTER_KEY的代碼。可以看這個issue#28
  5. 2.3調用後臺創建帳戶,把accountData傳過去,可以拿到返回的resp
  6. 2.4調用成功後,再使用redux的dispatch函數分發一個CREATE_REGISTER_ACCOUNT信息。不過這個信息好像也沒有太大用處。

關於CREATE_REGISTER_ACCOUNT,我在代碼中找到了兩處相關:

  1. src/features/core/reducers.js#L229-L234
const accountInit = (state = false, action) => {
  if (action.type == 'CREATE_REGISTER_ACCOUNT') {
    return true
  }
  return state
}
  1. src/features/app/reducers.js#L10-L115
export const flashMessages = (state = {}, action) => {
  switch (action.type) {
    // ...
    case 'CREATE_REGISTER_ACCOUNT': {
      return newSuccess(state, 'CREATE_REGISTER_ACCOUNT')
    }
    // ...
  }
}

第一個看起來沒什麼用,第二個應該是用來在操作完成後,顯示相關的錯誤信息。

那就讓我們把關註點放在1.22.3這兩個後臺調用的地方吧。

  1. chainClient().mockHsm.keys.create(keyData)對應的是:

src/sdk/api/mockHsmKeys.js#L3-L31

const mockHsmKeysAPI = (client) => {
  return {
    create: (params, cb) => {
      let body = Object.assign({}, params)
      const uri = body.xprv ? '/import-private-key' : '/create-key'

      return shared.tryCallback(
        client.request(uri, body).then(data => data),
        cb
      )
    },
    // ...
  }
}

可以看到在create方法中,如果找不到body.xprv(就是本文對應的情況),則會調用後臺的/create-key介面。經過一長串的跟蹤,我們終於找到了一個。

  1. chainClient().accounts.create(accountData)對應的是:

src/sdk/api/accounts.js#L3-L30

const accountsAPI = (client) => {
  return {
    create: (params, cb) => shared.create(client, '/create-account', params, {cb, skipArray: true}),
    // ...
  }
}

很快我們在這邊,也找到了創建帳戶時調用的介面為/create-account

前端這邊,我們終於分析完了。下一步,將進入比原的節點(也就是後端)。

後端:比原的後端是如何接收到數據的?

如果我們對前一篇文章還有印象的話,會記得比原在啟動之後,會在Node.initAndstartApiServer方法中啟動web api對應的http服務,並且在API.buildHandler()方法中會配置很多的功能點,其中一定會有我們這裡調用的介面。

讓我們看看API.buildHandler方法:

api/api.go#L164-L244

func (a *API) buildHandler() {
    walletEnable := false
    m := http.NewServeMux()

    if a.wallet != nil {
        walletEnable = true
        // ...
        m.Handle("/create-account", jsonHandler(a.createAccount))
        // ...
        m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
        // ...

很快,我們就發現了:

  1. /create-account: 對應a.createAccount
  2. /create-key: 對應a.pseudohsmCreateKey

讓我們先看一下a.pseudohsmCreateKey

api/hsm.go#L23-L32

func (a *API) pseudohsmCreateKey(ctx context.Context, in struct {
    Alias    string `json:"alias"`
    Password string `json:"password"`
}) Response {
    // ...
}

可以看到,pseudohsmCreateKey的第二個參數,是一個struct,它有兩個欄位,分別是AliasPassword,這正好和前面從前端傳過來的參數keyData對應。那麼這個參數的值是怎麼由提交的JSON數據轉換過來的呢?上次我們說到,主要是由a.pseudohsmCreateKey外面套著的那個jsonHandler進行的,它會處理與http協議相關的操作,以及把JSON數據轉換成這裡需要的Go類型的參數,pseudohsmCreateKey就可以直接用了。

由於在這個小問題中,我們問題的邊界是比原後臺是如何拿到數據的,所以我們到這裡就可以停止對這個方法的分析了。它具體是怎麼創建密鑰的,這在以後的文章中將詳細討論。

再看a.createAccount

api/accounts.go#L15-L30

// POST /create-account
func (a *API) createAccount(ctx context.Context, ins struct {
    RootXPubs []chainkd.XPub `json:"root_xpubs"`
    Quorum    int            `json:"quorum"`
    Alias     string         `json:"alias"`
}) Response {
    // ...
}

與前面一樣,這個方法的參數RootXPubsQuorumAlias也是由前端提交,並且由jsonHandler自動轉換好的。

當我們清楚了在本文中,前後端數據是如何交互的,就很容易推廣到更多的情景。在前端還在很多的頁面和表單,在很多地方都需要調用後端的介面,我相信按照本文的思路,應該都可以快速的找到。如果有比較特殊的情況,我們以後會再專門寫文章講解。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原創 尋求圖中最短路徑的方法有很多,最近剛開始學習,先用深搜實現,用鄰接矩陣來存儲圖。 直接上圖上代碼: 尋求從0~4的最短路徑 利用深搜找出所有從0~4的路徑,一一比較選擇出最小的 測試數據: 輸入: 5 8 0 1 2 0 4 10 1 2 3 1 4 7 2 0 4 2 3 4 3 4 5 4 ...
  • java.io包下的File類用於描述和創建一個文件或文件夾對象,只能對文件或文件夾做一些簡單操作,不能修改文件的內容,功能比較有限。下麵是對於File類中常用方法的程式演示。 [1] 演示程式一 [2] 演示程式二 註:希望與各位讀者相互交流,共同學習進步。 ...
  • 在debug VS c工程文件時,碰到cannot convert from 'int (__cdecl *)(char *)' to 'xxx'這個問題,發現問題在於typedef函數指針類型時,將函數調用方法__cdecl寫成了_stdcall。 後來百度了兩者的區別,如下: __cdecl 是 ...
  • 需求分析 要做安全方面的內容,依靠人臉識別通過和庫中的臉比對後判定相似率來驗證用戶身份。 快速從圖片中識別出人的信息,用於尋人功能等。 其實從安全的角度出發有很多可以囊括的。比如智能家居中的刷臉開門,支付軟體的刷臉支付等都是例子。 人臉特征提取的步驟 真正的人臉識別需要很多的知識,大體上粗略的可以分 ...
  • 互聯網這股東風不久前刮到了甘涼國,國王老甘獨具慧眼,想趕緊趁著東風未停大力發展移動互聯網,因為他篤信布斯雷的理論:“站在風口上,豬都能飛起來”。無奈地方偏僻落後,國內無可用之才啊。老甘一籌莫展的低頭凝思應聲被打斷,“啟奏陛下,有四個從東土大唐來的和尚前來更換通關文牒”,聽到“東土大唐”四個字,老甘心 ...
  • 因為IDEA和Goland來自同一家非常有名的捷克公司:JetBrains 很好用,智能化程度高 最新版下載地址:http://www.jetbrains.com/go/?fromMenu 相關使用手冊:https://www.jetbrains.com/help/go/discover-golan ...
  • 作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在前一篇文章中,我們試圖理解比原是如何交易的,但是由於內容太多,我們把它分成了幾個小 ...
  • 作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在比原的dashboard中,我們可以為一個帳戶創建地址(address),這樣就可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...