剥开比原看代码09:通过dashboard创建密钥时,前端的数据是如何传到后端的?
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
在前面一篇文章,我们粗略的研究了一下比原的dashboard是如何做出来的,但是对里面提到的各种细节功能,并没有深入的去研究。那么从本文开始,我们将在这一段时间,分别研究里面提到的每一项功能。
在前一篇文章中,当我们第一次在浏览器中打开dashboard时,因为还没有创建过密钥,所以比原会提示我们输入一些别名和密码,为我们创建一个密钥和相应的帐户。就是下面这张图所对应的:
那么本文就将研究一下,当我们点击了"Register"按钮以后,我们在前端页面上填写的参数,到底是如何一步步的传到比原的后端的。
跟之前一样,我们将对这个问题进行细分,然后各个击破:
- 前端:当我们填完表单,点了提交以后,比原在前端是如何发送数据的?
- 后端:比原的后端是如何接收到数据的?
前端:当我们填完表单,点了提交以后,数据会发送到后端的哪个接口?
当我们点击了"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个数字并不是从上到下标注,而是按照我们关注的顺序来的:
- 表单上的各个输入框,就是我们填写别名和密码的地方。这里需要关注的是每个
TextField
的fieldProps
属性,它对应我们提交到后台的数据的name
- 就是那个“Register”按钮了。需要注意的是,它的
type
是submit
,也就是说,点击它以后,将会触发所在form
的onSubmit
方法 - 回到了
form
的开头。注意它的onSubmit
里面,调用的是handleSubmit(this.submitWithErrors)
。其中的handleSubmit
是从该表单所使用的第三方redux-form中传入的,用来处理表单提交,我们在这里不关注它,只需要知道我们需要把自己的处理函数this.submitWithErrors
传给它。而在后者中,我们将会调用比原节点提供的web api - 第3步中的
this.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
自动转换好的。
当我们清楚了在本文中,前后端数据是如何交互的,就很容易推广到更多的情景。在前端还在很多的页面和表单,在很多地方都需要调用后端的接口,我相信按照本文的思路,应该都可以快速的找到。如果有比较特殊的情况,我们以后会再专门写文章讲解。
剥开比原看代码09:通过dashboard创建密钥时,前端的数据是如何传到后端的?的更多相关文章
- 剥开比原看代码08:比原的Dashboard是怎么做出来的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码16:比原是如何通过/list-transactions显示交易信息的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码13:比原是如何通过/list-balances显示帐户余额的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码12:比原是如何通过/create-account-receiver创建地址的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码11:比原是如何通过接口/create-account创建帐户的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码10:比原是如何通过/create-key接口创建密钥的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码03:比原是如何监听p2p端口的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- zookeeper-分布式锁的代码实现-【每日五分钟搞定大数据】
本文涉及到几个zookeeper简单的知识点,永久节点.有序节点.watch机制.比较基础,熟悉的就别看了跳过这篇吧 每个线程在/locks节点下创建一个临时有序节点test_lock_0000000 ...
- 看代码学知识之(2) ListView无数据时显示其他View
看代码学知识之(2) ListView无数据时显示其他View 今天看的一块布局是这样的: <!-- The frame layout is here since we will be show ...
随机推荐
- Python - 3. Input and Output
from:http://interactivepython.org/courselib/static/pythonds/Introduction/InputandOutput.html Input a ...
- Python学习记录之-----类
面向过程 VS 面向对象 编程范式 编程是 程序 员 用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 , 一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大 ...
- Axis2之异步调用
本章主要介绍axis2接口的异步调用方式. 一般情况下,我们使用同步方法(invokeBlocking)调用axis2接口,如果被调用的WebService方法长时间不返回,客户端将一直被阻塞,直到该 ...
- TCP协议的三次握手
TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输. l IP地址:用来唯一表示我们自己的电脑的,是一个网络标示 ...
- appium随笔
目录结构如下: Test_edaike---page object设计思想 定位元素和脚本分离Images目录---用例失败截图case目录 eTestfastfood.py---定位app界面元素& ...
- webpack4.0 实战记录
从零配置webpack4.0 搭建React工程. 基本环境:Node(v8.1.2)+ webpack(v4.16.2) 1.在项目目录 命令窗口 执行 npm init 初始化项目,执行完后项 ...
- vue之vue-cookies安装和使用说明
vue之vue-cookies安装和使用说明npm官方链接:https://www.npmjs.com/package/vue-cookies 安装,在对应项目根目录下执行:npm install v ...
- linux下postgresql的连接数配置
1.查询当前连接数: select count(*) from pg_stat_activity; 2.查询最大连接数 show max_connections; 3.修改最大连接数 SHOW con ...
- hud3007 Buried memory
题目链接 最小圆覆盖 并不知道为什么是O(n)的,而且要随机化点的顺序 #include<algorithm> #include<iostream> #include<c ...
- 基于Theano的深度学习框架keras及配合SVM训练模型
https://blog.csdn.net/a819825294/article/details/51334397 1.介绍 Keras是基于Theano的一个深度学习框架,它的设计参考了Torch, ...