带你玩转区块链--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;

 

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