帶你玩轉區塊鏈--IPFS/FileCoin/以太坊與ipfs結合-第二章-第四節【ipfs篇】

一、什麼是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命令可以指定文件,也可以指定目錄。==

  1. 直接指定哈希下載:
[duke ~/ethStudy/ipfsTest]$ ipfs  get QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx
Saving file(s) to QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx
 26 B / 26 B [==========================================================] 100.00% 0

此時成功下載QmXuTGSdFrTHWbpNzjGTkPsAU6nzUMq6jH28mxNFA1Ymdx所指定文件,且存儲在與哈希同名文件中。

  1. 使用-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
  1. 下載的同時進行壓縮,可以使用-a-c參數指定

  2. -a : 壓縮成.tar格式

  3. -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

注意:

  1. 使用ipns,而不是ipfs,
  2. 要鏈接網絡
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;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章