资讯专栏INFORMATION COLUMN

IPFS数据模型-IPLD

Salamander / 842人阅读

摘要:的内容寻址允许加密完整性检查解析链接的值可以通过哈希来测试。例子使用以下数据集路径的一个例子将遍历第一个对象并获取字符串。数据模型也非常简单易用。由于基于数据模型,因此它完全可以通过与和关联数据标准兼容。

ipld.io

Github:ipld

原文:IPLD specs

有许多系统使用merkle-treehash-chain受启发的数据结构(例如git,bittorrent,ipfs,tahoe-lafs,sfsro)。IPLD(星际链接数据)定义:

merkle-links:merkle-graph的核心单元

merkle-dag:任何边为merkle-links的图。dag代表“有向无环图”

merkle-paths:使用命名的merkl-links来遍历merkl-dags的unix风格的路径。

IPLD格式:可以表示IPLD对象的一组格式,例如JSON,CBOR,CSON,YAML,Protobuf,XML,RDF等。

IPLD规范格式:一种序列化格式的确定性描述,确保相同的逻辑对象始终被序列化到相同的位序列。这对于链接和所有加密应用程序都是至关重要的。

介绍 什么是merkle-link?

merkl-link是两个对象之间的链接,它们是由目标对象的加密散列处理的,并嵌入到源对象中。merkl-links的内容寻址允许:

加密完整性检查:解析链接的值可以通过哈希来测试。反过来,这可以实现广泛,安全和可靠的数据交换(例如git或bittorrent),因为其他人不能给你任何不会散列到链接值的数据。

不可变的数据结构:带有merkle链接的数据结构不能改变,这对于分布式系统来说是一个不错的属性。这对于版本控制,表示分布式可变状态(例如CRDT)和长期存档很有用。

一个merkle-link在IPLD对象模型中由包含一个key/mapped到“链接值”的映射表示。例如:

一个以json表示的“链接对象”的链接

{ "/" : "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k" }
// "/" is the link key
// "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k" is the link value

对象为foo/baz的链接

{
  "foo": {
    "bar": "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k", // not a link
    "baz": {"/": "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k"} // link
  }
}

对象在files/cat.jpg/link的实际链接以及files/cat.jpg中的伪“链接对象”。

{
   “ files ”: {
      “ cat.jpg ”: { //将链接属性封装在另一个对象中
      “ link ”: { “ / ”: “ / ipfs / QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k ” },//链接
      “ mode ”: 0755,
      “owner”: “ jbenet ”
    }
  }
}

当取消链接时,映射本身将被它指向的对象替换,除非链接路径无效。

该链接可以是multihash,在这种情况下,假设它是/ipfs层次结构中的链接,或者直接指向对象的绝对路径。目前,只允许使用/ipfs层次结构。

如果应用程序想要将具有单个/key的对象用于其他目的,则应用程序本身应负责转义/IPLD对象中的/key,这样应用程序的key就不会与IPLD的特殊/key发生冲突。

什么是merkle-graph或merkle-dag?

带有merkl-links的对象形成一个Graph(merkle-graph),如果加密散列函数的属性保持不变,则这些对象必然都是定向的,并且可以认为它是非循环的,即merkle-dag。因此,所有使用merkle-linking(merkle-graph)的图必定也是有向无环图(DAG,因此为merkle-dag)。

什么是merkle路径?

merkl-path是一种unix风格的路径(例如,/a/b/c/d),它最初通过merkl-link进行引用,并允许访问被引用节点和其他节点的元素。

我们鼓励通用文件系统在IPLD上设计一个对象模型,该模型将专门用于文件操作,并有特定的路径算法来查询该模型。

merkle-paths如何工作?

merkl-path是一种unix风格的路径,最初通过merkl-link进行引用,然后在中间对象中命名merkl-links。名称后面的意思是查找对象,查找名称并解析相关的merkl-link。

例如,假设我们有这个merkle-path:

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/c/d

路径表述:

ipfs 是一个协议命名空间(允许计算机识别要做什么)

QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k 是一个加密哈希。

a/b/c/d是一个路径遍历,就像在unix中一样。

路径遍历,用符号表示/,发生在两种链接上:

对象内遍历遍历同一对象内的数据。

跨对象遍历从一个对象遍历到另一个对象,通过merkle-link解析。

例子

使用以下数据集:

> ipfs object cat --fmt=yaml QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k
---
a:
  b:
    link:
      /: QmV76pUdAAukxEHt9Wp2xwyTpiCmzJCvjnMxyQBreaUeKT
    c: "d"
    foo:
      /: QmQmkZPNPoRkPd7wj2xUJe5v5DsY6MX33MFaGhZKB2pRSE

> ipfs object cat --fmt=yaml QmV76pUdAAukxEHt9Wp2xwyTpiCmzJCvjnMxyQBreaUeKT
---
c: "e"
d:
  e: "f"
foo:
  name: "second foo"

> ipfs object cat --fmt=yaml QmQmkZPNPoRkPd7wj2xUJe5v5DsY6MX33MFaGhZKB2pRSE
---
name: "third foo"

路径的一个例子:

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/c将遍历第一个对象并获取字符串d

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/c将遍历两个对象并获取字符串e

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/d/e遍历两个对象并获取字符串f

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/foo/name遍历第一个和第二个对象并获取字符串second foo

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/foo/name遍历第一个和最后一个对象并获取字符串third foo

什么是IPLD数据模型?

IPLD数据模型为所有merkle-dag定义了一个简单的基于JSON的结构,并标识了一组格式来将结构编码进去。

限制和期望

一些限制:

IPLD路径必须是明确的。给定的路径字符串必须总是确定性地遍历到同一个对象。(例如避免重复链接名称)

IPLD路径必须是通用的,避免对非英语国家不友好(例如使用UTF-8,而不是ASCII)。

IPLD路径必须在UNIX和Web上干净地分层(使用/,对ASCII系统具有确定性的变换)。

考虑到JSON的广泛成功,大量的系统提供了JSON接口。IPLD必须能够对JSON进行简单的导入和导出。

JSON数据模型也非常简单易用。IPLD必须一样易于使用。

定义新数据结构必须非常简单。要在IPLD上尝试新的定义,不应该很麻烦,也不需要太多的知识。

由于IPLD基于JSON数据模型,因此它完全可以通过JSON-LD与RDF和关联数据标准兼容。

IPLD序列化格式(在磁盘上和在线上)必须快速且节省空间。(不应该使用JSON作为存储格式,而应使用CBOR或类似的格式)

IPLD密码哈希必须是可升级的(使用multihash)

一些不错的点:

IPLD不应该犯下错误,例如JSON中缺少整数。

IPLD应该是可升级的,例如,如果出现更好的磁盘格式,系统应该能够迁移到它之上并且最小化这样做的成本。

IPLD对象应该能够解析路径的属性,而不仅仅是merkle links。

IPLD Canonical Format应该易于编写解析器。

IPLD Canonical Format应该能在不解析完整对象的情况下进行查找。(CBOR和Protobuf允许)。

格式定义

(注意:这里我们将使用JSON和YML来显示格式是什么样的。我们显式地使用这两种方法来显示不同格式的对象的等价性。)

IPLD数据模型“是JSON”,它(a)也是基于树的文档,具有一些基本类型,(b)1:1映射到JSON, (c)用户可以通过JSON本身使用它。它“不是JSON”(a)在某些错误上有所改进,(b)具有高效的序列化表示,(c)实际上并没有指定单一的on-wire格式,因为众所周知,这个世界正在改进。

基本的节点

以下是JSON中的示例IPLD对象:

{
   “ name ”:“ Vannevar Bush ” 
}

假设它散列的multihash值QmAAA...AAA。注意,它根本没有链接,只是一个字符串名称值。但是我们仍然能够“解析”它下面的key name:

> ipld cat --json QmAAA...AAA
{
  "name": "Vannevar Bush"
}

> ipld cat --json QmAAA...AAA/name
"Vannevar Bush"

当然,我们可以用其他格式来查看它

> ipld cat --yml QmAAA...AAA
---
name: Vannevar Bush

> ipld cat --xml QmAAA...AAA
 

  Vannevar Bush
链接节点之间

节点之间的Merkle-Linking是IPLD存在的原因。IPLD中的链接只是一种特殊格式的嵌入式节点:

{
  "title": "As We May Think",
  "author": {
    "/": "QmAAA...AAA" // links to the node above.
  }
}

假设这个散列值为multihash值QmBBB...BBB。该节点通过子路径author链接到QmAAA...AAA,上面这一节的节点。所以我们现在可以做到:

> ipld cat --json QmBBB...BBB
{
  "title": "As We May Think",
  "author": {
    "/": "QmAAA...AAA" // links to the node above.
  }
}

> ipld cat --json QmBBB...BBB/author
{
  "name": "Vannevar Bush"
}

> ipld cat --yml QmBBB...BBB/author
---
name: "Vannevar Bush"

> ipld cat --json QmBBB...BBB/author/name
"Vannevar Bush"
链接属性约定

IPLD允许用户构建复杂的数据结构,以及与链接相关的其他属性。这对编码其他信息以及链接(例如关系类型或链接中所需的辅助数据)很有用。这与下面讨论的“链接对象约定”不同,它们本身非常有用。但有时候,你只是想在链接上添加一些数据而不必创建另一个对象。IPLD不会妨碍你。您可以简单地通过将实际的IPLD链接嵌套在另一个对象中,并使用其他属性来完成。

重要提示:链接属​​性不允许直接在链接对象中使用,因为存在明显的歧义。阅读规格历史,了解有关难点的讨论。

例如,假设您有一个文件系统,并希望在对象之间的链接中分配类似于权限或所有者的元数据。假设你有一个哈希值为QmCCC...CCC的目录对象像这样:

{
  "foo": { // link wrapper with more properties
    "link": {"/": "QmCCC...111"} // the link
    "mode": "0755",
    "owner": "jbenet"
  },
  "cat.jpg": {
    "link": {"/": "QmCCC...222"},
    "mode": "0644",
    "owner": "jbenet"
  },
  "doge.jpg": {
    "link": {"/": "QmCCC...333"},
    "mode": "0644",
    "owner": "jbenet"
  }
}

或YML

---
foo:
  link:
    /: QmCCC...111
  mode: 0755
  owner: jbenet
cat.jpg:
  link:
    /: QmCCC...222
  mode: 0644
  owner: jbenet
doge.jpg:
  link:
    /: QmCCC...333
  mode: 0644
  owner: jbenet

虽然我们在特定于此数据结构的链接中拥有新属性,但我们仍然可以很好地解析链接:

> ipld cat --json QmCCC...CCC/cat.jpg
{
  "data": "u0008u0002u0012��u0008����u0000u0010JFIFu0000u0001u0001u0001u0000Hu0000H..."
}

> ipld cat --json QmCCC...CCC/doge.jpg
{
  "subfiles": [
    {
      "/": "QmPHPs1P3JaWi53q5qqiNauPhiTqa3S1mbszcVPHKGNWRh"
    },
    {
      "/": "QmPCuqUTNb21VDqtp5b8VsNzKEMtUsZCCVsEUBrjhERRSR"
    },
    {
      "/": "QmS7zrNSHEt5GpcaKrwdbnv1nckBreUxWnLaV4qivjaNr3"
    }
  ]
}

> ipld cat --yml QmCCC...CCC/doge.jpg
---
subfiles:
  - /: QmPHPs1P3JaWi53q5qqiNauPhiTqa3S1mbszcVPHKGNWRh
  - /: QmPCuqUTNb21VDqtp5b8VsNzKEMtUsZCCVsEUBrjhERRSR
  - /: QmS7zrNSHEt5GpcaKrwdbnv1nckBreUxWnLaV4qivjaNr3

> ipld cat --json QmCCC...CCC/doge.jpg/subfiles/1/
{
  "data": "u0008u0002u0012��u0008����u0000u0010JFIFu0000u0001u0001u0001u0000Hu0000H..."
}

但是我们无法像其他属性那样很好地提取链接,因为链接是要通过解析的。

重复属性keys

注意,有两个同名的属性是不允许的,但实际上不可能阻止(有人会这样做并将它提供给解析器),所以为了安全起见,我们定义了路径遍历的值作为序列化表示中的第一个条目。例如,假设我们有对象:

{
  "name": "J.C.R. Licklider",
  "name": "Hans Moravec"
}

假设这是规范格式(不是json,而是cbor)的准确顺序,并且它的散列为QmDDD…DDD。我们总是得到:

> ipld cat --json QmDDD...DDD
{
  "name": "J.C.R. Licklider",
  "name": "Hans Moravec"
}
> ipld cat --json QmDDD...DDD/name
"J.C.R. Licklider"
路径限制

Unix和Web中的路径描述有一些重要的问题。有关讨论,请参阅此讨论。为了与unix和web的模型和期望兼容,IPLD明确禁止具有特定路径组件的路径。请注意,数据本身可能仍然包含这些属性(有人会这样做,并且有合法用途)。所以只有路径解析器不能通过这些路径来解析。这些限制与典型的unix和UTF-8路径系统相同:

todo:

[ ] 列表路径解析限制

[ ] show示例

JSON中的整型

IPLD可以直接与JSON兼容,以利用JSON的成功,但它不需要因为JSON的错误而受到限制。这是我们可以遵循格式惯用选择的地方,但必须注意确保始终存在定义明确的1:1映射。

关于整数,在JSON中存在多种表示整数为字符串的格式,例如EJSON。这些可以被使用和转换到其他格式,这是自然发生的- 也就是说,将JSON转换为CBOR时,应该将EJSON整数自然地转换为合适的CBOR整数,而不是将其表示为字符串值的映射。

序列化数据格式

IPLD通过multicodec支持各种序列化数据格式。这些可以使用,但是对于格式是惯用的,例如CBOR,我们可以使用CBOR类型tags来表示merkl-link,并避免写出完整的字符串键@link。鼓励用户充分使用这些格式,并以最有意义的任何格式存储和传输IPLD数据。唯一的要求是必须有一个明确定义的IPLD规范格式的一对一映射。这样就可以将数据从一种格式转换为另一种格式,而不必改变其含义或密码哈希值。

带标签的序列化CBOR

在CBOR中,可以使用定义在RFC 7049 section 2.4.中的标记来表示IPLD链接。

标签被定义。这个标签可以是一个文本字符串(主类型3)或者是对应于链接目标的字节字符串(主类型2)。

将IPLD“链接对象”编码到CBOR时,请使用以下算法:

提取链接值。

如果链接值是一个有效的multiaddress,并且将该链接文本转换为多地址二进制字符串,并返回到文本将保证产生完全相同的文本,则该链接将转换为存储在CBOR中的二进制多地址字节字符串(主类型2)。

否则,链接值被存储为文本(主类型3)

由此产生的编码是链接值的CBOR表示

当解码CBOR并将其转换为IPLD时,的每一个事件都由以下算法转换:

以下值必须是提取的链接值。

如果链接是二进制字符串,则将其解释为多地址并转换为文本格式。否则,直接使用文本字符串。

用一个键值对创建一个映射。关键是标准的IPLD链接key/,该值是包含链接值的文本字符串。

当一个IPLD对象以这里所述的方式包含这些标记时,用于表示对象编解码器的multicodec头必须是/cbor/ IPLD -tagsv1,而不仅仅是/cbor。读者应该能够使用优化的阅读过程来检测使用这些标签的链接。

规范格式

为了保持merkle-linking的能力,我们必须确保IPLD文档有一个单一的规范序列化表示。这可确保应用程序获得相同的加密哈希。应该指出的是,这是一个系统范围的参数。未来的系统可能会改变它来演进表示。不过,我们估计这需要十年不超过一次。

IPLD规范格式是带tags的规范化CBOR。

除了此处定义的规则外,规范CBOR格式必须遵循RFC 7049 section 3.9中定义的规则。

这种格式的用户不应该期望keys的任何特定排序,因为keys可能以不同的非标准格式排序。

传统的规范格式是protocol buffers。

这种规范格式用于决定首次创建对象并计算其哈希时使用的格式。一旦为IPLD对象决定了格式,它必须在所有通信中使用,以便发送者和接收者可以根据散列来检查数据。

例如,当发送一个在protocol buffers中编码的传统对象时,发送方不得发送CBOR版本,因为接收方将无法检查文件的有效性。

同样,当接收者存储对象时,它必须确保该对象的规范格式与对象一起存储,以便能够与其他节点共享该对象。

用它们的格式存储这些对象的一种简单方法是用它们的multicodec头存储它们。

数据结构示例

重要的是,IPLD是一种简单,灵活和可扩展的格式,不会妨碍用户定义新的或导入旧数据文件的方式。为此,下面我将展示一些示例数据结构。

Unix文件系统

一个小文件

{
   “ data ”: “ hello world ”,
   “ size ”: “ 11 ” 
}

一个分块文件
分割成多个独立的子文件。

{
  "size": "1424119",
  "subfiles": [
    {
      "link": {"/": "QmAAA..."},
      "size": "100324"
    },
    {
      "link": {"/": "QmAA1..."},
      "size": "120345",
      "repeat": "10"
    },
    {
      "link": {"/": "QmAA1..."},
      "size": "120345"
    },
  ]
}

目录

{
   “ foo ”: {
     “ link ”: { “ / ”: “ QmCCC ... 111 ” },
     “ mode ”: “ 0755 ”,
     “ owner ”: “ jbenet ”
  },
  “ cat.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 222 ” },
     “ mode ”: “ 0644 ”,
     “ owner ”: “ jbenet ”
  },
  “ doge.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 333 ” },
     “ mode ”: “ 0644 ”,
     “ owner ”: “ jbenet ”
  }
}
git

git blob

{
   “ data ”: “ hello world ” 
}

git tree

{
   “ foo ”: {
     “ link ”: { “ / ”: “ QmCCC ... 111 ” },
     “ mode ”: “ 0755 ”
  },
  “ cat.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 222 ” },
     “ mode ”: “ 0644 ”
  },
  “ doge.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 333 ” },
     “ mode ”: “ 0644 ”
  }
}

git commit

{
  "tree": {"/": "e4647147e940e2fab134e7f3d8a40c2022cb36f3"},
  "parents": [
    {"/": "b7d3ead1d80086940409206f5bd1a7a858ab6c95"},
    {"/": "ba8fbf7bc07818fa2892bd1a302081214b452afb"}
  ],
  "author": {
    "name": "Juan Batiz-Benet",
    "email": "juan@benet.ai",
    "time": "1435398707 -0700"
  },
  "committer": {
    "name": "Juan Batiz-Benet",
    "email": "juan@benet.ai",
    "time": "1435398707 -0700"
  },
  "message": "Merge pull request #7 from ipfs/iprs

(WIP) records + merkledag specs"
}
比特币

比特币block

{
   “ parent ”: { “ / ”: “ Qm000000002CPGAzmfdYPghgrFtYFB6pf1BqMvqfiPDam8 ” },
   “ transactions ”: { “ / ”: “ QmTgzctfxxE8ZwBNGn744rL5R826EtZWzKvv2TF2dAcd9n ” },
   “ nonce ”: “ UJPTFZnR2CPGAzmfdYPghgrFtYFB6pf1BqMvqfiPDam8 ” 
}

比特币交易

这一次,在YML中。TODO:让它是一个真正的txn

---
inputs:
  - input: {/: Qmes5e1x9YEku2Y4kDgT6pjf91TPGsE2nJAaAKgwnUqR82}
    amount: 100
outputs:
  - output: {/: Qmes5e1x9YEku2Y4kDgT6pjf91TPGsE2nJAaAKgwnUqR82}
    amount: 50
  - output: {/: QmbcfRVZqMNVRcarRN3JjEJCHhQBcUeqzZfa3zoWMaSrTW}
    amount: 30
  - output: {/: QmV9PkR2gXcmUgNH7s7zMg9dsk7Hy7bLS18S9SHK96m7zV}
    amount: 15
  - output: {/: QmP8r8fLUnEywGnRRUrHB28nnBKwmshMLiYeg8udzYg7TK}
    amount: 5
script: OP_VERIFY

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/24134.html

相关文章

  • 【戴嘉乐】详解IPFS的本质、技术架构以及应用

    摘要:戴嘉乐去年月参与了的众筹,从而了解到技术,独立开发了两款基于的开源应用,一个是与有关的系统,另一个是相关的地理位置检索系统。现在支持的数据结构,支持比特币以太坊的区块数据。 戴嘉乐是前百度高级研发工程师,ipfser.org&巴比特专栏作者。戴嘉乐去年8月参与了FileCoin的众筹,从而了解到IPFS技术,独立开发了两款基于IPFS的开源应用,一个是与IPFS有关的wiki系统,另一...

    whlong 评论0 收藏0
  • LibP2P规范文档 - 中文翻译

    摘要:要完成这些,实现必须支持中继,虽然它应该是可选的,并且能够被终端用户关闭连接中继应该作为传输来实现,以便对上层透明中继的实现,可参考启用多种网络拓扑不同的系统有不同的需求,进而导致不同的拓扑结构。 目前进度为80%, 持续更新... 1 介绍 在开发IPFS(星际文件系统)的过程中,我们遇到了很多在异构设备之上运行分布式文件系统所带来的若干挑战,这些设备具有不同的网络设置和能力。在这个...

    fevin 评论0 收藏0
  • IPFS安装与概览

    摘要:安装与概览由于我使用的实现为,所以首先要安装配置环境。六安装辅助工具和,前者是一个桌面客户端,后者是一个浏览器插件,可以在及等浏览器上安装。你可以新开一个终端使用下等功能好了,本次对的安装配置到此结束,后续会更新更多内容。 IPFS安装与概览 由于我使用的IPFS实现为go-ipfs,所以首先要安装配置Golang环境。 我使用的系统为Ubuntu18.04,后面所有操作均在此系统下完...

    leanxi 评论0 收藏0
  • 【戴嘉乐】基于IPFS和GeoHash构建具有地理位置价值服务的DDApp(理论篇)

    摘要:数据将具有如下个特点将二维的经纬度转换成字符串,比如下图展示了北京个区域的字符串,分别是,等等,每一个字符串代表了某一矩形区域。例如,坐标对,位于北京安定门附近,后形成的值为。 作者简介:戴嘉乐( Mr.Maple ) | 前百度高级研发工程师 | IPFS应用实践者&布道师|个人网站:https://www.daijiale.cn联系方式:微信号:daijiale6239。 show...

    lmxdawn 评论0 收藏0
  • 【许晓笛】EOS:IPFS落地的重要途径

    摘要:写在前面,这一篇文章是许晓笛在北京开发者圆桌会议上的发言实录,感谢主办方戴嘉乐和董天一的邀请,感谢编辑们。我这次分享题目是有可能有点标题党,前面拉了三个字有可能是落地的一个非常重要的途径。共识机制共识机制,就是所有代币持有人选举。 写在前面,这一篇文章是许晓笛 2018.05.20 在北京 《IPFS开发者圆桌会议》上的发言实录,感谢主办方戴嘉乐和董天一的邀请,感谢编辑们。先介绍一下《...

    tuomao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<