一、什麼是IPFS
1. 定義
星際文件系統
IPFS(InterPlanetary File System)是一個面向全球的、點對點的分佈式版本文件系統,目標是爲了補充(甚至是取代)目前統治互聯網的超文本傳輸協議(HTTP),將所有具有相同文件系統的計算設備連接在一起。使數據訪問的速度更快、更安全、更健壯、更持久。由Juan Benet在2014年5月份發起
一句話概括:IPFS是一種點對點的超媒體文件存儲、索引、交換協議。
2. 特點
- 內容可尋址(區別於位置尋址): ==通過文件的哈希值進行索引==
- 版本管理功能==(永遠告別404,只要上傳過,就一定會存在==):
- 點對點超媒體(P2P)
3. 定位
下一代互聯網技術,替代http
4. http存在的問題
- 內容無法永久保存 404
- 浪費資源
- 效率低
- 不安全
二、安裝配置IPFS
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命令
1.啓動服務
當前節點已經創建成功,但是尚未和ipfs系統聯繫起來,我們需要運行ipfs服務,將本地節點鏈接至ipfs網絡。
ipfs daemon
若只想使用ipfs網絡,不想和網絡交互,則使用:
ipfs daemon --offline
在啓動服務後,可以輸入http://127.0.0.1:5001/webui查看本節點信息
2 添加文件數據(add)
add
命令表示向ipfs
網絡添加數據,這裏的數據包括文件
或者文件夾
,通過-r
選項來控制
執行添加動作:
[duke ~/ethStudy/ipfsTest]$ ipfs add helloItcast.txt
added QmPcaCGWxVkqwX2UxkS8i8RjXMhsfCYdrPb54vAArzd7Wd helloItcast.txt
[duke ~/ethStudy/ipfsTest]$
此時,該文件會被添加到本地ipfs節點中,且會返回一個唯一標識這個文件內容的哈希值。
注意,ipfs只根據文件內容進行識別存儲,如果文件名字不同,但是內容相同,那麼ipfs不會重複添加,且會返回上一個文件的
我們會發現,返回的哈希值和源文件是同一個。
3. 查看文件內容(cat)
ipfs提供cat參數來進行文件的讀取,操作如下:
ipfs cat QmPcaCGWxVkqwX2UxkS8i8RjXMhsfCYdrPb54vAArzd7Wd
4. 添加文件夾(add -r)
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]$
可以看到,文件夾和裏面的文件都生成了各自的哈希值,對於文件的查看,與上面的相同,
5. 查看文件夾內容(ls)
我們直接查看一下文件夾,指定該文件夾哈希值,效果如下:
[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
成功列出兩個文件!
6. 下載數據(get)
使用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格式
- -C :壓縮成.gz格式
==注,這兩個參數可以與-o
一起使用,也可以單獨使用==
7. 查看被引用哈希(refs)
refs命令可以查看當前的哈希值被哪些哈希引用,其實就是ipfs ls 不顯示文件名,只顯示哈希
[duke ~/ethStudy/ipfsTest]$ ipfs refs QmNRQ4C8n7QGSpNzPPdrg6VmFUZDyKon1fQqa59hgjQmta
QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx
QmcyYJGUmYYWxK2DWPbGRcxL875ZRmCHyHcSkcyhNT7Jzd
8.文件交互命令(files)
指定files命令之後,可以使得操作ipfs上的操作如同unix文件系統一樣,具體如下:
-
ipfs files mkdir - Make directories.
-
ipfs files cp - Copy files into mfs.
- ipfs files flush [] - Flush a given path's data to disk.
- ipfs files ls [] - List directories in the local mutable namespace.
- ipfs files mv - Move files.
- ipfs files read - Read a file in a given mfs.
- ipfs files rm ... - Remove a file.
- ipfs files stat - Display file status.
- ipfs files write - Write to a mutable file in a given filesystem.
ipfs有一個虛擬的根目錄 '/'
9 發佈和解析(name)
需求分析
想象一下如下的場景:當我們發佈一個網站到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
注意:
- 使用ipns,而不是ipfs,
- 要鏈接網絡
http://localhost:8080/ipns/QmNp7qXXpcUg2TRAgYsgcuz3FWYKAfbd711XX2RnnC8rX6
此時,如果修改網站內容,重新發布即可。
解析IPNS
我們可以使用resolve參數對IPNS進行解析,指定節點ID,返回發佈根目錄的哈希,效果如下:
[duke ~/ethStudy/ipfsTest]$ ipfs name resolve QmNp7qXXpcUg2TRAgYsgcuz3FWYKAfbd711XX2RnnC8rX6
/ipfs/Qmaft9vqHqwBQ72kqdDnGVNUcWqsGxSSzvX6bcRwAHkmy6
[duke ~/ethStudy/ipfsTest]$
三、ipfs-Api
1. 設置跨域資源共享(CORS)配置
爲了方便後續開發,我們需要對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結合(重點)
1、意義:
解決以太坊存儲昂貴的問題,我們只在以太坊上存儲哈希,在ipfs存儲圖片或者視頻等大文件。
2、安裝
創建文件夾:eth-react-ipfs目錄,進入目錄,在下面執行如下命令:
truffle unbox react
切換到項目根目錄下,執行如下命令:
npm install --save ipfs-api
3、編寫代碼,前端上傳到ipfs
引入ipfs
const ipfsAPI = require('ipfs-api');
const ipfs = ipfsAPI({host: 'localhost', port: '5001', protocol: 'http'});
前端
h2>上傳圖片到ipfs</h2>
<div>
<label id="file">請選擇上傳的文件</label>
<input type="file" ref="fileid" id="file" name="file" multiple="multiple"/>
<button onClick={async () => this.upload(this.refs.fileid.files[0])}>點擊我上傳</button>
</div>
上傳圖片函數
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 (
<div>
<h1>合約地址:</h1>
<h2>上傳圖片到ipfs</h2>
<div>
<label id="file">請選擇上傳的文件</label>
<input type="file" ref="fileid" id="file" name="file" multiple="multiple"/>
<button onClick={async () => this.upload(this.refs.fileid.files[0])}>點擊我上傳</button>
{
pictureHash && <h2>hash: {pictureHash}</h2>
}
{
pictureHash && <button onClick={()=>this.saveEth()}>上傳以太坊</button>
}
<p></p>
{
writeOK && <button onClick={()=>this.getHashFromEth()}>獲取圖片</button>
}
{
<div>
瀏覽器訪問結果:{"http://localhost:8080/ipfs/" + response}
<img src={"http://localhost:8080/ipfs/" + response}/>
</div>
}
</div>
</div>
);
}
}
export default App;