摘要:下一步,将进入比原的节点也就是后端。它具体是怎么创建密钥的,这在以后的文章中将详细讨论。当我们清楚了在本文中,前后端数据是如何交互的,就很容易推广到更多的情景。
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockc...
在前面一篇文章,我们粗略的研究了一下比原的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.// ... ) } }
上面的代码,共有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#28
2.3调用后台创建帐户,把accountData传过去,可以拿到返回的resp
2.4调用成功后,再使用redux的dispatch函数分发一个CREATE_REGISTER_ACCOUNT信息。不过这个信息好像也没有太大用处。
关于CREATE_REGISTER_ACCOUNT,我在代码中找到了两处相关:
src/features/core/reducers.js#L229-L234
const accountInit = (state = false, action) => { if (action.type == "CREATE_REGISTER_ACCOUNT") { return true } return state }
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.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方法:
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)) // ...
很快,我们就发现了:
/create-account: 对应a.createAccount
/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,它有两个字段,分别是Alias和Password,这正好和前面从前端传过来的参数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 { // ... }
与前面一样,这个方法的参数RootXPubs、Quorum和Alias也是由前端提交,并且由jsonHandler自动转换好的。
当我们清楚了在本文中,前后端数据是如何交互的,就很容易推广到更多的情景。在前端还在很多的页面和表单,在很多地方都需要调用后端的接口,我相信按照本文的思路,应该都可以快速的找到。如果有比较特殊的情况,我们以后会再专门写文章讲解。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/24183.html
摘要:如果传的是,就会在内部使用默认的随机数生成器生成随机数并生成密钥。使用的是,生成的是一个形如这样的全球唯一的随机数把密钥以文件形式保存在硬盘上。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前一篇,我们探讨了从浏览器的dashb...
摘要:而本文将继续讨论,比原是如何通过接口来创建帐户的。把各信息打包在一起,称之为另外,在第处还是一个需要注意的。比原在代码中使用它保存各种数据,比如区块帐户等。到这里,我们已经差不多清楚了比原的是如何根据用户提交的参数来创建帐户的。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://git...
摘要:所以本文本来是想去研究一下,当别的节点把区块数据发给我们之后,我们应该怎么处理,现在换成研究比原的是怎么做出来的。进去后会看到大量的与相关的配置。它的功能主要是为了在访问与的函数之间增加了一层转换。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlo...
摘要:继续看生成地址的方法由于这个方法里传过来的是而不是对象,所以还需要再用查一遍,然后,再调用这个私有方法创建地址该方法可以分成部分在第块中主要关注的是返回值。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在比原的dashboard中...
摘要:所以这个文章系列叫作剥开比原看代码。所以我的问题是比原初始化时,产生了什么样的配置文件,放在了哪个目录下下面我将结合源代码,来回答这个问题。将用来确认数据目录是有效的,并且将根据传入的不同,来生成不同的内容写入到配置文件中。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee...
阅读 549·2021-11-25 09:44
阅读 2635·2021-11-24 09:39
阅读 2304·2021-11-22 15:29
阅读 3519·2021-11-15 11:37
阅读 3379·2021-09-24 10:36
阅读 2507·2021-09-04 16:41
阅读 991·2021-09-03 10:28
阅读 1831·2019-08-30 15:55