C-Blockstack Storage Tutorial
在本教程中,您将使用多播放器Gaia存储构建一个微型博客应用程序。Gaia是blockstack的分布式高性能存储系统。本教程包含以下主题:
关于本教程和您需要的先决条件
使用npm安装Yeoman和blockstack应用程序生成器
生成并启动公共应用程序
添加publish_data范围来登录请求
了解Gaia存储方法
添加对用户状态提交和查找的支持
获取和显示状态
改变风格
查找用户配置文件
--- 添加新路由
--- 为处理URL路径添加规则。(点)
--- 把它们放在一起
结束
资源
本教程不教你如何进行身份验证。这在hello-blockstack教程中有详细介绍。
关于本教程和您需要的先决条件
使用本教程构建的存储应用程序是一个完全去中心化的、无服务器的React.js应用程序。虽然不是严格要求遵循,但对React.js有基本的了解是有帮助的。
完成后,app可以实现以下功能:
- 使用blockstack对用户进行身份验证
- 发布新状态
- 在用户配置文件中显示状态
- 查找其他用户的配置文件和状态
基本标识和存储服务由blockstack.js提供。要测试应用程序,您需要已经注册了一个blockstack ID。
本教程依赖于npm依赖关系管理器。在开始之前,使用which命令验证是否安装了npm。
$ which npm
/usr/local/bin/npm
如果在系统中没有找到npm,请安装它。最后,如果您在编写本教程的过程中遇到困难,可以使用完整的源代码检查您的工作。你也可以尝试这个应用的实时构建。
使用npm安装Yeoman和blockstack应用程序生成器
使用npm安装Yeoman。Yeoman是一个通用的脚手架系统,帮助用户快速启动新项目,并简化现有项目的维护。
1.安装Yeoman.
npm install -g yo
2.安装blockstack应用程序生成器。
npm install -g generator-blockstack
生成并启动公共应用程序
在本节中,您将构建一个名为Publik的初始response .js应用程序。
1.创建一个publik目录。
mkdir publik
2.切换到新目录。
cd publik
3.使用Yeoman和Blockstack应用程序生成器创建初始的publik应用程序。
您应该会看到几个交互提示。
$ yo blockstack:react
? ==========================================================================
We're constantly looking for ways to make yo better!
May we anonymously report usage statistics to improve the tool over time?
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== No
----- ╭──────────────────────────╮
| | │ Welcome to the │
|--(o)--| │ Blockstack app │
---------´ │ generator! │ ( _´U
_ ) ╰──────────────────────────╯
/A\ /
| ~ |
'..’_
´|° ´ Y
? Are you ready to build a Blockstack app in React? (Y/n)
4.响应提示填充初始应用程序。
流程成功完成后,您会看到类似如下提示:
[fsevents] Success:
"/Users/theuser/repos/publik/node_modules/fsevents/lib/binding/Release/node-v59-darwin-x64/fse.node”
is installed via remote npm notice created a lockfile as package-lock.json.
You should commit this file. added 1060 packages in 26.901s
5.运行初始应用程序。
npm start
系统提示您接受传入连接。
image
6.选择允许。
7.打开浏览器到http://localhost:8080。
您应该看到一个简单的React应用程序。
8.选择使用Blockstack登录。
应用程序会告诉你它会读取你的基本信息。
image
让您的新应用程序继续运行,然后进入下一节。
添加publish_data范围来登录请求
每个使用Gaia存储的应用程序都必须将自己添加到用户的profile.json文件。当身份验证期间请求publish_data作用域时,Blockstack浏览器自动执行此操作。对于这个应用程序,存储在Gaia上的用户文件通过用户配置文件中的apps属性对其他人可见profile.json文件。
修改您的身份验证请求,以包含publish_data范围。
1.打开src/components/App.jsx文件
2.定位文件开头附近的AppConfig声明。
const appConfig = new AppConfig()
3.修改文件
const appConfig = new AppConfig(['store_write', 'publish_data’])
默认情况下,身份验证请求包括启用存储的store_write范围。这允许您将信息存储到Gaia。添加publish_data范围允许应用程序在用户之间共享数据。
4.保存修改
5.回到你的应用程序http://localhost:8080/。
6.退出并再次登录。
身份验证请求现在提示用户允许发布为应用程序存储的数据。
image
了解Gaia存储方法
使用store_write和publish_data对用户进行身份验证之后,就可以开始为用户管理数据了。Blockstack JS在UserSession类UserSession中提供了两个方法。getFile UserSession。用于与Gaia存储交互的putFile。存储方法支持所有文件类型。这意味着您可以存储markdown、JSON甚至自定义格式。
可以使用这两种方法创建有意义的复杂数据层。在创建应用程序之前,请考虑基本数据体系结构,并对如何建模数据做出一些决策。例如,考虑构建一个简单的杂货清单应用程序。用户应该能够创建、读取、更新和删除杂货清单。
一个单一的文件集合存储项目作为一个数组嵌套在每个杂货店列表:
// grocerylists.json
{
"3255": {
"items": [
"1 Head of Lettuce”,
"Haralson apples”
]
},
// ...more lists with items
}
从概念上讲,这是管理购物清单最简单的方法。当你阅读食品杂货清单时。使用getFile()返回一个或多个杂货店列表及其商品。当您编写一个列表时,putFile()方法将覆盖整个列表。因此,对于一个新的或更新的杂货列表的写操作也必须提交所有存在列表。
此外,因为这在客户机上运行,任何地方都可能出错。如果客户端代码遇到带有用户输入值的解析错误,您可以使用以下命令覆盖整个文件:
line 6: Parsing Error: Unexpected token.
此外,一个文件使分页不可能,如果您的应用程序为所有列表存储一个文件,那么您对文件权限的控制就会更少。为了避免这些问题,您可以创建一个存储id数组的索引文件。这些id指向grocerylists文件夹中另一个文件的名称。
image这种设计允许您只获取所需的文件,并避免意外地覆盖所有列表。此外,您只在添加或删除杂货列表时更新索引文件;更新列表没有影响。
添加对用户状态提交和查找的支持
在此步骤中,您将添加三个blockstack.js方法,它们支持发布“状态”。这些是UserSession。putFile UserSession。getFile和lookupProfile方法。
1.打开src/components/Profile.jsx文件
2.替换constructor()方法中的初始状态,使其包含应用程序所需的关键属性。
这段代码构造一个Blockstack Person对象来保存概要文件。你的构造函数应该是这样的:
constructor(props) {
super(props);
this.state = {
person: {
name() {
return ‘Anonymous’;
},
avatarUrl() {
return avatarFallbackImage;
},
},
username: “”,
newStatus: “”,
statuses: [],
statusIndex: 0,
isLoading: false
};
}
3.定位render()方法。
4.修改render()方法,向应用程序添加文本输入和提交按钮。
下面的代码显示person.name和person。显示的概要文件中的avatarURL属性:
render() {
const { handleSignOut, userSession } = this.props;
const { person } = this.state;
const { username } = this.state;
return (
!userSession.isSignInPending() && person ?
<div className=“container”>
<div className=“row”>
<div className="col-md-offset-3 col-md-6”>
<div className="col-md-12”>
<div className="avatar-section”>
<img
src={ person.avatarUrl() ? person.avatarUrl() : avatarFallbackImage }
className="img-rounded avatar”
id="avatar-image”
/>
<div className=“username”>
<h1>
<span id="heading-name">{ person.name() ? person.name()
: 'Nameless Person' }</span>
</h1>
<span>{username}</span>
<span>
|
<a onClick={ handleSignOut.bind(this) }>(Logout)</a>
</span>
</div>
</div>
</div>
<div className="new-status”>
<div className="col-md-12”>
<textarea className="input-status”
value={this.state.newStatus}
onChange={e => this.handleNewStatusChange(e)}
placeholder="Enter a status”
/>
</div>
<div className="col-md-12”>
<button
className="btn btn-primary btn-lg”
onClick={e => this.handleNewStatusSubmit(e)}>
Submit
</button>
</div>
</div>
</div>
</div>
</div> : null
);
}
这段代码允许应用程序发布状态。它还显示用户的blockstack ID。要显示此ID,应用程序必须从用户配置文件数据中提取该ID。
注意,传递到概要文件呈现器中的userSession属性包含isSignInPending()方法,该方法检查是否有一个sign in操作处于挂起状态。
5.定位componentWillMount()方法。
6.在person属性下面添加username属性。
您将在用户会话中使用Blockstack loadUserData()方法来访问用户名。
componentWillMount() {
const { userSession } = this.props
this.setState({
person: new Person(userSession.loadUserData().profile),
username: userSession.loadUserData().username
});
}
在Profile类中添加两个方法来处理状态输入事件:
handleNewStatusChange(event) {
this.setState({newStatus: event.target.value})
}
handleNewStatusSubmit(event) {
this.saveNewStatus(this.state.newStatus)
this.setState({
newStatus: “”
})
}
8.添加一个saveNewStatus()方法来保存新的状态。
saveNewStatus(statusText) {
const { userSession } = this.props
let statuses = this.state.statuses
let status = {
id: this.state.statusIndex++,
text: statusText.trim(),
created_at: Date.now()
}
statuses.unshift(status)
const options = { encrypt: false }
userSession.putFile('statuses.json', JSON.stringify(statuses), options)
.then(() => {
this.setState({
statuses: statuses
})
})
}
9.保存Profile.jsx文件。
当应用程式成功编译后,你的应用程式应显示如下:
image
10.在文本框中输入状态并按Submit按钮。
此时,不会显示您刚刚提交的状态。在下一节中,您将添加代码,以将状态作为博客条目显示回用户。
获取和显示状态
更新Profile.jsx文件。
1.回到render()方法。
2.找到包含文本输入和提交按钮的<div className="new-status”>。
3.在本节中匹配的结束元素</div>之后,添加这个块。
<div className="col-md-12 statuses”>
{this.state.isLoading && <span>Loading...</span>}
{this.state.statuses.map((status) => (
<div className="status" key={status.id}>
{status.text}
</div>
)
)}
</div>
这将显示现有状态。您的代码需要在页面加载时获取状态。
4.在saveNewStatus()方法之后添加一个名为fetchData()的新方法。
fetchData() {
const { userSession } = this.props
this.setState({ isLoading: true })
const options = { decrypt: false }
userSession.getFile('statuses.json', options)
.then((file) => {
var statuses = JSON.parse(file || '[]’)
this.setState({
person: new Person(userSession.loadUserData().profile),
username: userSession.loadUserData().username,
statusIndex: statuses.length,
statuses: statuses,
})
})
.finally(() => {
this.setState({ isLoading: false })
})
}
5.从componentDidMount()方法调用fetchData()。
componentDidMount() {
this.fetchData()
}
6.保存文件
应用程序编译成功后,用户可以提交多个状态并在应用程序中查看它们。
image
改变风格
1.编辑src/styles/style.css文件
2.将内容替换为:
/* Globals */
a,a:focus,a:hover{color:#fff;}
html,body{height:100%;text-align:center;background-color:#191b22;}
body{color:#fff}
.hide{display:none;}
.landing-heading{font-family:'Lato',Sans-Serif;font-weight:400;}
/* Buttons */
.btn{font-family:'Lato',Sans-Serif;padding:0.5625rem 2.5rem;font-size:0.8125rem;font-weight:400;line-height:1.75rem;border-radius:0!important;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}
.btn-lg{font-size:1.5rem;padding:0.6875rem 3.4375rem;line-height:2.5rem;}
.btn:focus,.btn:active:focus,.btn.active:focus{outline:none;}
.btn-primary{color:#fff;border:1px solid #2C96FF;background-color:#2C96FF;}
.btn-primary:hover,.btn-primary:focus,.btn-primary:active{color:#fff;border:1px solid #1a6ec0;background-color:#1a6ec0;}
/* Avatar */
.avatar{width:100px;height:100px;}
.avatar-section{margin-bottom:25px;display:flex;text-align:left;}
.username{margin-left:20px;}
/* Scaffolding */
.site-wrapper{display:table;width:100%;height:100vh;min-height:100%;}
.site-wrapper-inner{display:flex;flex-direction:column;justify-content:center;margin-right:auto;margin-left:auto;width:100%;height:100vh;}
.panel-authed{padding:0 0 0 0;}
/* Home button */
.btn-home-hello{position:absolute;font-family:'Source Code Pro',monospace;font-size:11px;font-weight:400;color:rgba(255,255,255,0.85);top:15px;left:15px;padding:3px 20px;background-color:rgba(255,255,255,0.15);border-radius:6px;-webkit-box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);-moz-box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);}
/* Input */
input, textarea{color:#000;padding:10px;}
.input-status{width:100%;height:70px;border-radius:6px;}
.new-status{text-align:right;}
/* Statuses */
.statuses{padding-top:30px;}
.status{margin:15px 0px;padding:20px;background-color:#2e2e2e;border-radius:6px}
3.保存并关闭src/styles/style.css文件。
在应用程式编译完成后,你应该会看到以下内容:
image
现在,您已经有了一个基本的微博应用程序,用户可以使用它发布和查看状态。但是,无法查看其他用户的状态。您将在下一节中添加它。
查找用户配置文件
现在让我们修改配置文件。用于显示其他用户的配置文件。您将使用前面添加到import语句中的lookupProfile()方法。lookupProfile()接受一个参数,该参数是概要文件的blockstack ID,并返回一个概要文件对象。
添加新路由
更改应用程序的路由结构,以便用户可以通过访问http://localhost:8080/other_user.id查看其他用户的配置文件
1.确保您位于publik项目的根目录中。
2.安装react-router:
npm install --save react-router-dom
3.编辑src/index.js文件
4.在顶部的文件中添加一个import:
import { BrowserRouter } from 'react-router-dom’
5.将src/index.js中的ReactDOM.render()方法更改为:
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root’));
6.保存并关闭src/index.js文件。
7.编辑src/components/App.jsx文件。
8.从response -router-dom导入交换机和路由组件,添加新路由:
import { Switch, Route } from 'react-router-dom’
9.在render()方法中找到下面这一行:
: <Profile userSession={userSession} handleSignOut={ this.handleSignOut }
用以下案文取代:
<Switch>
<Route
path='/:username?’
render={
routeProps =>
<Profile
userSession={userSession}
handleSignOut={ this.handleSignOut }
{…routeProps}
/>
}
/>
</Switch>
这将设置一个路由并捕获URL中的路由路径作为概要文件查找用户名。
11.保存并关闭src/components/App。jsx文件。
为处理URL路径添加规则。(点)
还需要在应用程序的webpack配置中添加一条规则,以便正确处理包含。例如,http://localhost:8080/other_user.id
Note
在生产应用程序中,必须确保web服务器已配置为处理此问题。
1.在根项目目录中打开webpack.config.js,找到以下行:
historyApiFallback: true,
2.替换为:
historyApiFallback: {
disableDotRule:true
},
您需要重新运行npm才能使此更改生效。别担心,后面会有一个步骤来提醒你。
3.保存并关闭webpack.config.js文件。
4.打开src/components/Profile.jsx文件。
5.展开从blockstack语句导入,以包含lookupProfile方法。
在Person之后添加lookupProfile。
完成后,import语句应该如下所示:
import {
Person,
lookupProfile
} from ‘blockstack’;
6.在Profile类中添加一个方法,确定应用程序是查看本地用户的概要文件还是查看其他用户的概要文件。
isLocal() {
return this.props.match.params.username ? false : true
}
使用isLocal()检查用户是否正在查看本地用户配置文件或其他用户的配置文件。如果是本地用户配置文件,应用程序将运行在前面步骤中添加的getFile()函数。否则,应用程序将使用lookupProfile()方法查找属于用户名的概要文件。
7.修改fetchData()方法如下:
fetchData() {
const { userSession } = this.props
this.setState({ isLoading: true })
if (this.isLocal()) {
const options = { decrypt: false }
userSession.getFile('statuses.json', options)
.then((file) => {
var statuses = JSON.parse(file || '[]’)
this.setState({
person: new Person(userSession.loadUserData().profile),
username: userSession.loadUserData().username,
statusIndex: statuses.length,
statuses: statuses,
})
})
.finally(() => {
this.setState({ isLoading: false })
})
} else {
const username = this.props.match.params.username
lookupProfile(username)
.then((profile) => {
this.setState({
person: new Person(profile),
username: username
})
})
.catch((error) => {
console.log('could not resolve profile’)
})
}
}
Note
对于https部署,用于名称查找的默认blockstack核心API端点应该更改为指向https上服务的核心API。否则,由于浏览器阻塞混合内容,名称查找将失败。有关详细信息,请参阅Blockstack.js文档。
8.在调用lookupProfile(username)... catch((error)=>{..}之后立即将以下块添加到fetchData()中
const options = { username: username, decrypt: false }
userSession.getFile('statuses.json', options)
.then((file) => {
var statuses = JSON.parse(file || '[]’)
this.setState({
statusIndex: statuses.length,
statuses: statuses
})
})
.catch((error) => {
console.log('could not fetch statuses’)
})
.finally(() => {
this.setState({ isLoading: false })
})
这将获取用户状态。
最后,必须有条件地呈现logout按钮、状态输入文本框和submit按钮,以便在查看其他用户的配置文件时不会显示它们。
9.将render()方法替换为:
render() {
const { handleSignOut, userSession } = this.props;
const { person } = this.state;
const { username } = this.state;
return (
!userSession.isSignInPending() && person ?
<div className=“container”>
<div className=“row”>
<div className="col-md-offset-3 col-md-6”>
<div className="col-md-12”>
<div className="avatar-section”>
<img
src={ person.avatarUrl() ? person.avatarUrl() : avatarFallbackImage }
className="img-rounded avatar”
id="avatar-image”
/>
<div className=“username”>
<h1>
<span id="heading-name">{ person.name() ? person.name()
: 'Nameless Person' }</span>
</h1>
<span>{username}</span>
{this.isLocal() &&
<span>
|
<a onClick={ handleSignOut.bind(this) }>(Logout)</a>
</span>
}
</div>
</div>
</div>
{this.isLocal() &&
<div className="new-status”>
<div className="col-md-12”>
<textarea className="input-status”
value={this.state.newStatus}
onChange={e => this.handleNewStatusChange(e)}
placeholder="What's on your mind?”
/>
</div>
<div className="col-md-12 text-right”>
<button
className="btn btn-primary btn-lg”
onClick={e => this.handleNewStatusSubmit(e)}>
Submit
</button>
</div>
</div>
}
<div className="col-md-12 statuses”>
{this.state.isLoading && <span>Loading...</span>}
{this.state.statuses.map((status) => (
<div className="status" key={status.id}>
{status.text}
</div>
)
)}
</div>
</div>
</div>
</div> : null
);
}
通过使用{isLocal() && …} 条件,可以检查用户是否正在查看自己的配置文件。
把它们放在一起
1.通过发送CTRL-C来停止终端中正在运行的应用程序。
2.重新启动应用程序,以便禁用。(点)规则生效。
npm start
3.将浏览器指向http://localhost:8080/your_username.id。blockstack以查看最终应用程序。
结束
祝贺您,一切都完成了!我们希望您喜欢学习更多关于blockstack的知识。
需要注意的几件事是,您将注意到,在我们的putFile()和getFile()调用中,我们选择不加密/解密,因为我们的应用程序旨在公开共享状态。默认情况下,putFile()和getFile()将加密存储的所有数据,使除登录用户外的所有人都无法读取这些数据。