做者:freewind前端
比原项目仓库:git
Github地址:https://github.com/Bytom/bytomgithub
Gitee地址:https://gitee.com/BytomBlockchain/bytomweb
在前面一篇文章,咱们粗略的研究了一下比原的dashboard是如何作出来的,可是对里面提到的各类细节功能,并无深刻的去研究。那么从本文开始,咱们将在这一段时间,分别研究里面提到的每一项功能。json
在前一篇文章中,当咱们第一次在浏览器中打开dashboard时,由于尚未建立过密钥,因此比原会提示咱们输入一些别名和密码,为咱们建立一个密钥和相应的账户。就是下面这张图所对应的: redux
那么本文就将研究一下,当咱们点击了"Register"按钮之后,咱们在前端页面上填写的参数,究竟是如何一步步的传到比原的后端的。后端
跟以前同样,咱们将对这个问题进行细分,而后各个击破:api
当咱们点击了"Register"按钮,在前端页面中,必定会在某个地方触发一个向比原节点webapi接口发出请求的操做。到底是访问的哪一个web api?提交的数据又是什么样的呢?让咱们先从前端代码中寻找一下。浏览器
注意,比原的前端代码位于另外一个项目仓库bytom/dashboard中。为了能与咱们在本系列文章中使用的比原v1.0.1的代码相匹配,我找到了dashboard中的v1.0.0的代码,而且提交到了一个单独的项目中:freewind/bytom-dashboard-v1.0.0。注意该项目代码未作任何修改,其master
分支对应于官方代码仓库的v1.0.0
分支。之因此要弄一个单独的出来,这是由于咱们在文章中,每次引用一段代码的时候,都会给出相应的github上的连接,方便读者跳过去查看全貌,使用一个独立项目,会让这个过程更简便一些。app
因为比原的前端页面是使用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个数字并非从上到下标注,而是按照咱们关注的顺序来的:
TextField
的fieldProps
属性,它对应咱们提交到后台的数据的name
type
是submit
,也就是说,点击它之后,将会触发所在form
的onSubmit
方法form
的开头。注意它的onSubmit
里面,调用的是handleSubmit(this.submitWithErrors)
。其中的handleSubmit
是从该表单所使用的第三方redux-form中传入的,用来处理表单提交,咱们在这里不关注它,只须要知道咱们须要把本身的处理函数this.submitWithErrors
传给它。而在后者中,咱们将会调用比原节点提供的web apithis.submitWithErrors
最终将走到这里定义的submitWithErrors
函数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
是为了让后台建立密钥而须要准备的参数,一个是alias
,一个是password
,它们都是用户填写的1.2
是调用后台用于建立密钥的接口,把keyData
传过去,而且拿到返回的resp
后,进行后续的处理2.1
是为了让后台建立账户而须要准备的参数,分别是root_xpubs
, quorum
和alias
,其中root_xpubs
是建立密钥后返回的公钥,quorum
目前不知道(TODO),alias
是用户填写的账户别名2.2
这一句没有做用(通过官方确认了),由于我在代码中没有找处处理CREATE_REGISTER_KEY
的代码。能够看这个issue#282.3
调用后台建立账户,把accountData
传过去,能够拿到返回的resp
2.4
调用成功后,再使用redux的dispatch
函数分发一个CREATE_REGISTER_ACCOUNT
信息。不过这个信息好像也没有太大用处。关于CREATE_REGISTER_ACCOUNT
,我在代码中找到了两处相关:
const accountInit = (state = false, action) => { if (action.type == 'CREATE_REGISTER_ACCOUNT') { return true } return state }
export const flashMessages = (state = {}, action) => { switch (action.type) { // ... case 'CREATE_REGISTER_ACCOUNT': { return newSuccess(state, 'CREATE_REGISTER_ACCOUNT') } // ... } }
第一个看起来没什么用,第二个应该是用来在操做完成后,显示相关的错误信息。
那就让咱们把关注点放在1.2
和2.3
这两个后台调用的地方吧。
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
接口。通过一长串的跟踪,咱们终于找到了一个。
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
方法:
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)) // ...
很快,咱们就发现了:
/create-account
: 对应a.createAccount
/create-key
: 对应a.pseudohsmCreateKey
让咱们先看一下a.pseudohsmCreateKey
:
func (a *API) pseudohsmCreateKey(ctx context.Context, in struct { Alias string `json:"alias"` Password string `json:"password"` }) Response { // ... }
能够看到,pseudohsmCreateKey
的第二个参数,是一个struct
,它有两个字段,分别是Alias
和Password
,这正好和前面从前端传过来的参数keyData
对应。那么这个参数的值是怎么由提交的JSON数据转换过来的呢?上次咱们说到,主要是由a.pseudohsmCreateKey
外面套着的那个jsonHandler
进行的,它会处理与http协议相关的操做,以及把JSON数据转换成这里须要的Go类型的参数,pseudohsmCreateKey
就能够直接用了。
因为在这个小问题中,咱们问题的边界是比原后台是如何拿到数据的,因此咱们到这里就能够中止对这个方法的分析了。它具体是怎么建立密钥的,这在之后的文章中将详细讨论。
再看a.createAccount
:
// 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 { // ... }
与前面同样,这个方法的参数RootXPubs
、Quorum
和Alias
也是由前端提交,而且由jsonHandler
自动转换好的。
当咱们清楚了在本文中,先后端数据是如何交互的,就很容易推广到更多的情景。在前端还在不少的页面和表单,在不少地方都须要调用后端的接口,我相信按照本文的思路,应该均可以快速的找到。若是有比较特殊的状况,咱们之后会再专门写文章讲解。