区块链存储 1GB 数据需要数百 ETH。元数据、图片、前端静态文件必须放在链下存储。IPFS(内容寻址,可能丢失)和 Arweave(一次付费,永久保存)是两大标准。
14.5.1 内容寻址 vs 位置寻址
传统 URL(HTTPS):通过位置访问。
https://somesite.com/image.jpg—— 文件在这个服务器的这个路径上,服务器关闭 = 文件消失。
IPFS:通过内容访问。
ipfs://Qmabc.../image.jpg—— 这个 CID(Content Identifier)永远指向这个内容,存储在哪台机器上无关紧要。
CID 的生成
typescript
/**
* IPFS 的内容寻址(简化模拟)
*/
function sha256(data: string): string {
// 简化哈希演示
let h = 0;
for (let i = 0; i < data.length; i++) {
h = ((h << 5) - h + data.charCodeAt(i)) | 0;
}
return h.toString(16).padStart(64, '0');
}
function computeCID(data: string): string {
const hash = sha256(data);
// IPFS 使用 CIDv1 多格式编码:
// 0x12 = sha2-256, 0x20 = 32 字节, 0x01 = CIDv1, 0x55 = raw
return "bafybei" + hash.slice(0, 44); // 简化 base32 前缀
}
// 关键特性:相同数据 => 相同 CID
const cid1 = computeCID("Hello World");
const cid2 = computeCID("Hello World");
console.log("CID 匹配:", cid1 === cid2, cid1);
// 即使文件名不同,内容一样,CID 也一样
const cid3 = computeCID("Hello World");
console.log("内容相同 CID 相同:", cid1 === cid3);14.5.2 IPFS 的工作机制
graph LR
subgraph IPFS["IPFS 网络"]
A[上传文件] --> B[分片: 256KB 块]
B --> C[计算每个块 CID]
C --> D[Merkle DAG 链接]
D --> E[根 CID]
E --> F[广播到 DHT 节点]
end
User[用户] --> |"GET /ipfs/<cid>"| Gateway[Gateway 节点]
Gateway --> |DHT 查找| P1[节点 P1] & P2[节点 P2]
P1 --> |返回分片| Gateway
Gateway --> |重组文件| User
style IPFS fill:#e3f2fd
前端集成路径
| 服务 | 类型 | 持久化 | 免费额度 | 用途 |
|---|---|---|---|---|
| Pinata | 托管 pinning | 付费长期 | 1GB 免费 | NFT 元数据 |
| Web3.storage | IPFS + Filecoin | 协商 | 5GB 免费 | DApp 存储 |
| NFT.storage | IPFS + Filecoin | 永久(*) | 免费 | NFT 专属 |
| Infura IPFS | 节点托管 | 付费 | 5GB 免费 | 企业级 |
| 公共 Gateway | 只读 | 不保证 | 免费 | 最简访问 |
(*) NFT.storage 将文件同时 pin 到 IPFS 和 Filecoin 存储交易,理论上永久免费(受项目资金限制)。
前端上传示例
typescript
/**
* IPFS 上传(前端概念模拟)
*/
interface IPFSPinataResponse {
IpfsHash: string; // CID
PinSize: number; // 字节数
Timestamp: string;
}
async function uploadToIPFS(
apiKey: string,
secret: string,
file: File,
): Promise<string> {
const formData = new FormData();
formData.append("file", file);
// Pinata 的 pinning 服务
const res = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
method: "POST",
headers: {
Authorization: `Bearer ${await signJWT(apiKey, secret)}`,
},
body: formData,
});
const data: IPFSPinataResponse = await res.json();
// 返回的 CID 就是 URI: ipfs://{IpfsHash}
return `ipfs://${data.IpfsHash}`;
}
// 选择 Gateway
function resolveIPFSUri(uri: string): string {
if (uri.startsWith("ipfs://")) {
const cid = uri.replace("ipfs://", "");
// 选择可靠的 Gateway
return `https://cloudflare-ipfs.com/ipfs/${cid}`;
// 备选: https://ipfs.io/ipfs/${cid}
// 备选: https://gateway.pinata.cloud/ipfs/${cid}
}
return uri;
}
function signJWT(_apiKey: string, _secret: string): Promise<string> {
return Promise.resolve("mock-jwt-token");
}
console.log("IPFS URI 示例:", resolveIPFSUri("ipfs://Qmdemo.../metadata.json"));14.5.3 Arweave:一次付费,永久存储
经济模型
| 对比 | 传统云 (AWS S3) | IPFS | Arweave |
|---|---|---|---|
| 支付 | 订阅/月付 | 免费/按 pin | 一次性 |
| 持久化 | 只要付费就存在 | 只要有人 pin 就存在 | 永久 |
| 成本 | 10/GB | $1-5/GB 一次性 | |
| 访问 | HTTP | ipfs:// 或 gateway | 专用网关 |
| 元数据标准 | 无 | 无 | ANS-110 等 |
Permaweb:网页都存在 Arweave 上
Arweave 不仅存文件,还可以托管完整的前端应用(HTML/JS/CSS)。
typescript
/**
* Arweave 上传(概念模拟)
*/
interface ArweaveTransaction {
id: string; // 交易 ID 同时作为访问地址
data: string; // 文件内容(base64)
tags: { name: string; value: string }[]; // 元数据标签
reward: bigint; // 存储费用
signature?: string;
}
async function uploadToArweave(
fileContent: string,
fileType: string,
): Promise<string> {
const arweave = {
// ANS-110 标准标签
tags: [
{ name: "Content-Type", value: fileType },
{ name: "App-Name", value: "MyDApp" },
{ name: "App-Version", value: "1.0.0" },
],
// 费用 = 数据大小 × 当前 AR 价格 × 永久存储成本模型
async getPrice(dataSize: number): Promise<bigint> {
return BigInt(dataSize) * 10000n; // 简化
},
};
const price = await arweave.getPrice(fileContent.length);
console.log(`Storage cost: {Number(price) / 1e12} AR)`);
// 签名并提交到 Arweave 网络
const txId = "abc123...";
return `https://arweave.net/${txId}`; // 永久可访问
}
// NFT 元数据标准:指向 Arweave 永久 URI
const artMetadata = {
name: "Digital Art #1",
description: "...",
image: "https://arweave.net/txId_of_image", // 永久图片
animation_url: "https://arweave.net/txId_of_glb", // 永久 3D 模型
};
console.log("Arweave: 存一次,永远可用");14.5.4 前端去中心化存储的实战建议
| 场景 | 推荐方案 | URI 格式 |
|---|---|---|
| NFT 小图片 (< 10MB) | IPFS (Pinata / Web3.storage) | ipfs:// |
| NFT 大图/视频 | Arweave (Irys 代付) | https://arweave.net/ |
| DApp 前端托管 | Arweave Permaweb | https://arweave.net/ |
| 链上元数据 (base64) | data URI | data:application/json;base64,... |
| 临时测试 | Pinata 免费层 | ipfs:// |
graph LR
subgraph Storage["存储方案决策树"]
Q1{"数据大小?"}
Q2{"持久性要求?"}
Q3{"预算?"}
end
Q1 --> |< 1MB| Small[IPFS free]
Q1 --> |> 10MB| Large[Arweave 或 Filecoin]
Q2 --> |临时| Temp[IPFS, 自行 pin]
Q2 --> |永久| Perm[Arweave / NFT.storage]
Q3 --> |零预算| Free[NFT.storage / Web3.storage]
Q3 --> |可付费| Paid[Pinata / Infura]
style Storage fill:#e3f2fd
> ← 14.4 事件同步 | 前往 → 14.6 多链与跨链桥 |*
评论
0评论加载中…