IPFS提供了支持多种语言的 API版本,具体x可以在github上搜索ipfs查看相关介绍。这里我们主要介绍NodeJs上如何使用IPFS的API。
(1)准备环境
第一步:创建一个truffle-react项目;
第二步:在命令行上进入client目录,然后执行npm命令安装ipfs-api的包。
>> npm i --save ipfs-api
(2)使用IPFS API
第一步:在client目录下新建一个js文件,然后编辑文件,导入ipfs-api。
let IpfsApi = require("ipfs-api")
第二步:连接ipfs服务。
var ipfs = IpfsApi('localhost', '5001', {protocol : 'http'})
注意:连接ipfs服务之前,要先启动ipfs服务。
第三步:调用api方法
let ipfsTest = () => {
return new Promise(async () => {
// 把数据转换成Buffer
const content = ipfs.types.Buffer.from('ABC')
// 往ipfs中添加数据,添加成功后ipfs会返回一个hash
const results = await ipfs.add(content)
// 得到生成的hash
const hash = results[0].hash
console.log("往ipfs中添加内容成功,生成的hash为 : ", hash)
})
}
ipfsTest()
下面以ipfs中的cat、ls方法为例介绍api的具体用法。详细介绍请参考:https://github.com/ipfs/js-ipfs#core-api。
函数 | 说明 |
---|---|
ipfs.add(data, [options], [callback]) | 添加数据或文件 |
ipfs.cat(ipfsPath, [options], [callback]) | 查看指定ipfs哈希的文件 |
ipfs.ls(ipfsPath, [callback]) | 列出指定ipfs哈希中的文件 |
let ipfsTest = () => {
return new Promise(async () => {
let info = await ipfs.cat(“QmXeKtRSz7SVKp8Qh6tXtv6whRF5WXWQPwsqA38houakhZ”)
console.log("读取指定哈希位置存储的内容 : ", info.toString())
// 列出指定哈希位置的所有文件
let files= await ipfs.ls("QmSwkQt43JTUpXSAqT7z2DJF44HFz8i3juFGaLD3psKyVu")
files.forEach((file) => {
console.log(file.name)
})
})
}
功能需求:
1)定义一个图片上传页面;
2)将图片上传到ipfs,返回一个哈希值;
3)将哈希存储在以太坊(数据上链);
4)从以太坊获取哈希;
5)把指定哈希的图片展示在浏览器上;
该案例所涉及到的技术:react、web3、node.js和ipfs-api。
(1)创建一个truffle-react工程,然后进入client目录,下载ipfs-api的包。
(2)关闭metamask插件。这样工会导致浏览器中没有了web3实例。因此,程序获取连接到本地网络(http://localhost:9545)。
(3)在命令行进入项目所在路径,执行truffle develop命令启动truffle内置服务。
(4)清理App.js文件
import React, { Component } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./utils/getWeb3";
import "./App.css";
class App extends Component {
state = { storageValue: 0, web3: null, accounts: null, contract: null };
componentDidMount = async () => {
try {
const web3 = await getWeb3();
const accounts = await web3.eth.getAccounts();
const networkId = await web3.eth.net.getId();
const deployedNetwork = SimpleStorageContract.networks[networkId];
const instance = new web3.eth.Contract(
SimpleStorageContract.abi,
deployedNetwork && deployedNetwork.address,
);
this.setState({ web3, accounts, contract: instance });
} catch (error) {
console.error(error);
}
};
render() {
return (
Hello Truffle-React-IPFS
);
}
}
export default App;
(5)启动React项目测试,在浏览器访问localhost:3000。
>> cd client
>> nmp start
(1)由于上传图片的哈希保存在合约中storedData字段中,但是该字段默认是uint类型,因此,需要修改为string类型。
pragma solidity ^0.5.0;
contract SimpleStorage {
string storedData;
function set(string x) public {
storedData = x;
}
function get() public view returns (string) {
return storedData;
}
}
修改完成后,记得在truffle develop中重新编译和部署合约。
(2)在App.js文件中定义文件上传的页面元素。
render() {
return (
请上传图片:
);
}
运行效果如下图所示:
(3)定义一个按钮,点击按钮的时候抓取图片数据。
Ref是React的一个特殊属性,用于绑定到render函数输出的任何组件上。当组件添加到DOM之后,ref属性会添加一个组件的引用到this.refs中。所以,可以通过this.refs来获取指定React组件。
界面效果如下图所示:
点击按钮,在控制台输出文件内容:
可以看到,已经抓取到文件的信息。那么接下来,我们就要文件上传到IPFS中。
(4)把图片上传到IPFS。
第一步:定义一个函数,实现上传功能。
upload = async(info) => {
let reader = new FileReader()
reader.readAsArrayBuffer(info)
reader.onloadend = () => {
console.log("result : ", reader.result)
}
}
第二步:在按钮的onClick事件中调用该函数。
第三步:在App.js中引入ipfs。
// 导入ipfs
import IpfsApi from "ipfs-api";
// 创建ipfs实例(要启动ipfs daemon服务)
var ipfs = IpfsApi('localhost', '5001', {protocol : 'http'})
第四步:定义一个函数,该函数调用ipfs-api方法添加图片数据。
saveToIpfs = (data) => {
return new Promise(async(resolve, reject) => {
try {
let res = await ipfs.add(data)
let hash = res[0].hash
resolve(hash)
} catch (e) {
reject(e)
}
})
}
第五步:在upload函数中调用saveToIpfs函数。
upload = async (info) => {
...
reader.onloadend = () => {
this.saveToIpfs(reader.result).then(hash => {
console.log("hash : ", hash)
})
}
}
运行程序,在页面上传文件失败。在浏览器console中看到以下错误信息:
导致该问题的原因是由于未对CORS进行配置。所以先退出ipfs服务,然后运行下面命令:
>> ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "[\"PUT\",\"GET\", \"POST\"]"
>> ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"*\"]"
>> ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
>> ipfs config --json API.HTTPHeaders.Access-Control-Allow-Headers "[\"Authorization\"]"
>> ipfs config --json API.HTTPHeaders.Access-Control-Expose-Headers "[\"Location\"]"
运行完成后,重新启动ipfs服务即可。
第六步:把结果显示在页面上。
// 添加状态遍历picHash
state = {storageValue: 0, web3: null, accounts: null, contract: null, picHash: ''};
// 把上传图片的哈希保存在状态遍历中
upload = async (info) => {
...
reader.onloadend = () => {
this.saveToIpfs(reader.result).then(hash => {
this.setState({picHash : hash})
})
}
}
// 显示哈希在页面上
render() {
let {picHash} = this.state
return (
请上传图片:
{
picHash && 图片已经上传到IPFS:{picHash}
}
);
}
第一步:定义状态变量isOk,记录是否上传成功
state = {storageValue: 0, web3: null, accounts: null, contract: null, picHash: '', isOk: false};
第二步:定义一个函数,调用合约set方法把哈希上传到以太坊。
saveToEth = async() => {
try {
// 获取合约实例和图片哈希
let {contract, accounts, picHash} = this.state
// 调用合约方法
let res = await contract.methods.set(picHash).send({
from : accounts[0]
})
this.setState({isOk : true})
} catch (e) {
console.log(e)
}
}
第三步:添加按钮组件。
{
picHash &&
}
第一步:定义状态变量response,记录获取到的图片哈希
state = {storageValue: 0, web3: null, accounts: null, contract: null, picHash: '', isOk: false, response: ''};
第二步:先把isOk和response状态变量结构出来,然后再定义按钮组件。
render() {
// 把isOk状态变量解构出来
let {picHash, isOk, response} = this.state
...
return (
...
{
isOk &&
}
);
}
第三步:实现getPicHash方法。
getPicHash = () => {
try {
// 获取合约中保存的图片哈希
let {contract} = this.state
let response = contract.methods.get().call()
response.then(hash => {
// 保存在状态变量response中
this.setState({response : hash})
})
} catch (e) {
console.log(e)
}
}
第四步:显示图片。
{
response &&
}
最后进行测试,效果如下图所示: