星际文件系统
IPFS(InterPlanetary File System)是一个面向全球的、点对点的分布式版本文件系统,目标是为了补充(甚至是取代)目前统治互联网的超文本传输协议(HTTP),将所有具有相同文件系统的计算设备连接在一起。使数据访问的速度更快、更安全、更健壮、更持久。由Juan Benet在2014年5月份发起
一句话概括:IPFS是一种点对点的超媒体文件存储、索引、交换协议。
下一代互联网技术,替代http
1.点击https://dist.ipfs.io/#go-ipfs,访问下载对应版本。执行./install.sh
2.解压go-ipfs_v0.4.14_windows-amd64.zip到你的目录中,会生成一个go-ipfs的文件夹,
3此时可以通过执行ipfs version检查是否初始化成功
4.执行完 ipfs init,生成本机ID
5. 若需修改存储原则:
1.修改存储空间为10G,打开文件后,修改StorageMax
字段:
vi .ipfs/config
2.修改存储位置
//修改.ipfs中的config文件
ipfs config edit
//修改Ipfs默认村粗位置,windows需要配置到环境变量中的用户变量中
export IPFS_PATH=/path/to/ipfsrepo
当前节点已经创建成功,但是尚未和ipfs系统联系起来,我们需要运行ipfs服务,将本地节点链接至ipfs网络。
ipfs daemon
若只想使用ipfs网络,不想和网络交互,则使用:
ipfs daemon --offline
在启动服务后,可以输入http://127.0.0.1:5001/webui查看本节点信息
add
命令表示向ipfs
网络添加数据,这里的数据包括文件
或者文件夹
,通过-r
选项来控制
执行添加动作:
[duke ~/ethStudy/ipfsTest]$ ipfs add helloItcast.txt
added QmPcaCGWxVkqwX2UxkS8i8RjXMhsfCYdrPb54vAArzd7Wd helloItcast.txt
[duke ~/ethStudy/ipfsTest]$
此时,该文件会被添加到本地ipfs节点中,且会返回一个唯一标识这个文件内容的哈希值。
注意,ipfs只根据文件内容进行识别存储,如果文件名字不同,但是内容相同,那么ipfs不会重复添加,且会返回上一个文件的
我们会发现,返回的哈希值和源文件是同一个。
ipfs提供cat参数来进行文件的读取,操作如下:
ipfs cat QmPcaCGWxVkqwX2UxkS8i8RjXMhsfCYdrPb54vAArzd7Wd
ipfs相同文件内容上传的时候如果发现已经存在了,就不会重复上传
相同的文件会在不同的节点有多个备份,放在缓存中
该命令与添加文件相同, 只不过需要额外指定一个参数-r
,即递归(recursive),操作如下:
[duke ~/ethStudy/ipfsTest]$ ipfs add -r testFolder/
added QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx testFolder/animals.txt
added QmcyYJGUmYYWxK2DWPbGRcxL875ZRmCHyHcSkcyhNT7Jzd testFolder/cars.txt
added QmURkBHop38X7BxL1NTo57H96gvEBmjZ2DqfJRz8AjKtht testFolder
[duke ~/ethStudy/ipfsTest]$
可以看到,文件夹和里面的文件都生成了各自的哈希值,对于文件的查看,与上面的相同,
我们直接查看一下文件夹,指定该文件夹哈希值,效果如下:
[duke ~/ethStudy/ipfsTest]$ ipfs cat QmURkBHop38X7BxL1NTo57H96gvEBmjZ2DqfJRz8AjKtht
Error: this dag node is a directory
我们会发现有错误出现,这是因为对于文件夹不能使用cat
命令,而应该使用ls
命令,这个ls
参数可以使得ipfs上的数据像unix的文件系统一样展示,重新测试一下:
[duke ~/ethStudy/ipfsTest]$ ipfs ls QmURkBHop38X7BxL1NTo57H96gvEBmjZ2DqfJRz8AjKtht
QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx 26 animals.txt
QmcyYJGUmYYWxK2DWPbGRcxL875ZRmCHyHcSkcyhNT7Jzd 25 cars.txt
成功列出两个文件!
使用get
命令可以对系统上的数据进行下载,==get命令可以指定文件,也可以指定目录。==
[duke ~/ethStudy/ipfsTest]$ ipfs get QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx
Saving file(s) to QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx
26 B / 26 B [==========================================================] 100.00% 0
此时成功下载QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx
所指定文件,且存储在与哈希同名文件中。
-o
参数,自定义下载文件的名字为animals_get.txt
,操作如下:[duke ~/ethStudy/ipfsTest]$ ipfs get QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx -o animals_get.txt
Saving file(s) to animals_get.txt
26 B / 26 B [=======================================================] 100.00% 0s
下载的同时进行压缩,可以使用-a
和-c
参数指定
-a : 压缩成.tar格式
==注,这两个参数可以与-o
一起使用,也可以单独使用==
refs命令可以查看当前的哈希值被哪些哈希引用,其实就是ipfs ls 不显示文件名,只显示哈希
[duke ~/ethStudy/ipfsTest]$ ipfs refs QmNRQ4C8n7QGSpNzPPdrg6VmFUZDyKon1fQqa59hgjQmta
QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx
QmcyYJGUmYYWxK2DWPbGRcxL875ZRmCHyHcSkcyhNT7Jzd
指定files命令之后,可以使得操作ipfs上的操作如同unix文件系统一样,具体如下:
ipfs files mkdir - Make directories.
ipfs files cp - Copy files into mfs.
ipfs有一个虚拟的根目录 '/'
需求分析
想象一下如下的场景:当我们发布一个网站到ipfs时,会返回一个网站根目录的哈希,我们在ipfs系统中可以通过这个哈希对网站进行访问。但是网站的内容是会更新的,而每一次更新都会导致根目录的哈希值发生变化,用户想访问新的内容就要需要不断的改变访问的url,这样很不友好,所以我们需要一种手段来避免这种问题。
这时就需要引入一个新的名词:IPNS,这个IPNS可以将某个哈希与节点的ID绑定起来,从而通过不变的ID来访问经常变化的网站。
使用语法为:
ipfs name publish <目录的哈希>
此时,启动ipfs后台服务(必须启动主网,offline模式不行)
ipfs daemon
我们可以将网站发布到IPNS,在IPNS中,允许我们节点的域名空间中引用一个IPFS 哈希(也就是将节点ID与某个项目的==根目录进行绑定==)从而完成使用ID来访问网站,那么这个时候,如果网站内容更新,我们只需重新绑定一次即可,用户仍使用原来的HASH进行访问。
发布方式:
命令
ipfs name publish XXX
Qmaftxx...xkmy6是site目录的哈希,执行后这个哈希与节点的id绑定,通过节点的id就能找到这个目录
返回值
Published to QmNp7qXXpcUg2TRAgYsgcuz3FWYKAfbd711XX2RnnC8rX6: /ipfs/Qmaft9vqHqwBQ72kqdDnGVNUcWqsGxSSzvX6bcRwAHkmy6
这里面的"Publicshed to "后面的哈希就是我们的节点ID,我们可以通过下面的命令来确认一下:
[duke ~/ethStudy/ipfsTest]$ ipfs id
{
"ID": "QmNp7qXXpcUg2TRAgYsgcuz3FWYKAfbd711XX2RnnC8rX6",
...
}
访问IPNS
注意:
http://localhost:8080/ipns/QmNp7qXXpcUg2TRAgYsgcuz3FWYKAfbd711XX2RnnC8rX6
此时,如果修改网站内容,重新发布即可。
解析IPNS
我们可以使用resolve参数对IPNS进行解析,指定节点ID,返回发布根目录的哈希,效果如下:
[duke ~/ethStudy/ipfsTest]$ ipfs name resolve QmNp7qXXpcUg2TRAgYsgcuz3FWYKAfbd711XX2RnnC8rX6
/ipfs/Qmaft9vqHqwBQ72kqdDnGVNUcWqsGxSSzvX6bcRwAHkmy6
[duke ~/ethStudy/ipfsTest]$
为了方便后续开发,我们需要对ipfs的跨域资源共享(CORS)进行配置,==(请执行ctrl+c退出刚刚启动的daemon服务)==,直接在命令行执行如下命令。
使用ipfs-api的时候会用到这里,然后将下面5句话复制执行。
linux以及mac
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT","GET", "POST", "OPTIONS"]'
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"]'
windows:
将上面命令修改成如下方式如:
1 ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "[\"PUT\", \"GET\", \"POST\", \"OPTIONS\"]"
2 ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"*\"]"
3 ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
4 ipfs config --json API.HTTPHeaders.Access-Control-Allow-Headers "[\"Authorization\"]"
5 ipfs config --json API.HTTPHeaders.Access-Control-Expose-Headers "[\"Location\"]"
2.node与ipfsApi交互
安装ipfs-api
切换到项目根目录下,执行如下命令:
npm install --save ipfs-api
交互
let ipfsAPI = require('ipfs-api')
const ipfs = ipfsAPI('localhost', '5001', {protocol: 'http'}) // leaving out the arguments will default to these values
let test = async () => {
let res = await ipfs.files.add(Buffer.from('hellworld'))
console.log('res :', res[0].hash)
res = await ipfs.files.cat(res[0].hash)
console.log('res :', res.toString())
// let files = await ipfs.ls('QmRTyNKyGFW1XcNRTn9cAXbcwKzwzxuWXpq8s4eXzHGNbk')
let files = await ipfs.files.ls('/box')
files.forEach((file) => {
// console.log(file.name)
console.log(file)
})
}
test()
解决以太坊存储昂贵的问题,我们只在以太坊上存储哈希,在ipfs存储图片或者视频等大文件。
创建文件夹:eth-react-ipfs目录,进入目录,在下面执行如下命令:
truffle unbox react
切换到项目根目录下,执行如下命令:
npm install --save ipfs-api
引入ipfs
const ipfsAPI = require('ipfs-api');
const ipfs = ipfsAPI({host: 'localhost', port: '5001', protocol: 'http'});
前端
h2>上传图片到ipfs
上传图片函数
upload = async (info) => {
console.log('info :', info)
let reader = new window.FileReader()
reader.readAsArrayBuffer(info)
console.log('111reader:', reader)
console.log('2222 : result :', reader.results) <<----注意这里!!!
//在上传结束后, reader里面就是图片的数据
reader.onloadend = () => {
console.log('111:', reader)
console.log('222:', reader.result)
//上传到ipfs //TODO
// saveImageOnIpfs(reader).then(hash => {
// console.log('333:', hash)
// this.setState({hash})
// })
}
}
上传到ipfs
let saveImageOnIpfs = (reader) => {
return new Promise(function (resolve, reject) {
const buffer = Buffer.from(reader.result);
ipfs.add(buffer).then((response) => {
console.log(response)
resolve(response[0].hash);
}).catch((err) => {
console.error(err)
reject(err);
})
})
}
若报错则请设置跨域。
编写代码,ipfs存储到ETH
saveHashToEth = async () => {
let {contractInstance, hash, web3} = this.state
try {
let accounts = await web3.eth.getAccounts()
let res = await contractInstance.set(hash, {from: accounts[0]})
// console.log({txHash: res.txHash.receipt})
console.log('writeOK:', true)
this.setState({writeOK: true})
} catch (e) {
console.log(e)
this.setState({writeOK: false})
console.log('writeOK :', false)
}
}
从以太坊获取ipfs哈希值
getHash = async () => {
let {Instance} = this.state
try {
// let accounts = await web3.eth.getAccounts()
let hash = await contractInstance.get()
this.setState({response: hash})
console.log(hash)
} catch (e) {
console.log(e)
}
}
完整代码App.js
import React, { Component } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./getWeb3";
import "./App.css";
let ipfsAPI = require('ipfs-api');
let ipfs = ipfsAPI({host: 'localhost', port: '5001', protocol: 'http'});
class App extends Component {
state = { storageValue: 7, web3: null, accounts: null, contract: null ,writeOK:true,response:''};
componentWillMount = async () => {
try {
// Get network provider and web3 instance.
const web3 = await getWeb3();
// Use web3 to get the user's accounts.
const accounts = await web3.eth.getAccounts();
// Get the contract instance.
const networkId = await web3.eth.net.getId();
const deployedNetwork = SimpleStorageContract.networks[networkId];
const instance = new web3.eth.Contract(
SimpleStorageContract.abi,
deployedNetwork && deployedNetwork.address,
);
// Set web3, accounts, and contract to the state, and then proceed with an
// example of interacting with the contract's methods.
this.setState({ web3, accounts, contract: instance });
} catch (error) {
// Catch any errors for any of the above operations.
alert(
`Failed to load web3, accounts, or contract. Check console for details.`,
);
console.error(error);
}
};
runExample = async () => {
const { accounts, contract } = this.state;
// Stores a given value, 5 by default.
await contract.methods.set(5).send({ from: accounts[0] });
// Get the value from the contract to prove it worked.
const response = await contract.methods.get().call();
console.log(response,"oooooooooooooo")
// Update state with the result.
this.setState({ storageValue: response });
};
upload = async (info) => {
let reader = new window.FileReader()
reader.readAsArrayBuffer(info)
console.log('111reader:', reader)
console.log('2222 : result :', reader.results)
//在上传结束后, reader里面就是图片的数据
reader.onloadend = () => {
console.log('111:', reader);
console.log('222:', reader.result)
//上传到ipfs //
this.SaveToIpfs(reader).then(hash => {
console.log('333:', hash);
this.setState({hash})
})
}
};
SaveToIpfs = (reader) => {
return new Promise(function (resolve, reject) {
const buffer = Buffer.from(reader.result);
ipfs.add(buffer).then((response) => {
console.log(response);
resolve(response[0].hash);
}).catch((err) => {
console.error(err);
reject(err);
})
})
};
saveEth=async ()=>{
let {contract, hash, web3,accounts} = this.state;
try {
let res1 = await contract.methods.get().call();
console.log(res1,"pppppppppppppppppppppppppppppppppp")
let res = contract.methods.set(hash).send({ from: accounts[0] });
// console.log({txHash: res.txHash.receipt})
console.log('writeOK:', true)
this.setState({writeOK: true})
} catch (e) {
console.log(e)
this.setState({writeOK: false})
console.log('writeOK :', false)
}
};
getHashFromEth=async ()=>{
let {contract} = this.state;
try {
let res = await contract.methods.get().call();
console.log('writeOK:', true)
this.setState({response: res})
} catch (e) {
console.log(e)
this.setState({response: false})
console.log('writeOK :', false)
}
};
render() {
//truffle 直接用contranct.address就可获取实例
//原生的用 contract.options.address
let instance=this.state.contract;
let pictureHash=this.state.hash;
let {writeOK,response}=this.state;
console.log(instance,"99999999999999999");
return (
合约地址:
上传图片到ipfs
{
pictureHash && hash: {pictureHash}
}
{
pictureHash &&
}
{
writeOK &&
}
{
浏览器访问结果:{"http://localhost:8080/ipfs/" + response}
}
);
}
}
export default App;