25、使用ipfsapi与IPFS交互的全面指南

使用ipfsapi与IPFS交互的全面指南

在分布式存储和内容寻址的领域中,IPFS(InterPlanetary File System)是一个强大的工具。本文将深入探讨如何使用ipfsapi与IPFS进行交互,包括数据序列化、哈希处理、编码转换以及文件和目录的添加等操作。

1. 数据序列化与反序列化

原始数据 b'I am a good unicorn.\n' 首先会被 unixfs 模块的 Data 包装,然后再被 merkledag 模块的 PBNode 包装。在脚本中,我们需要先使用 PBNode 对序列化的数据进行反序列化,然后再使用 Data 对结果进行反序列化。

2. 多哈希(Multihash)

IPFS 使用多哈希(Multihash)对数据进行哈希处理。多哈希不仅输出哈希结果,还会输出所使用的哈希函数、该哈希函数的输出长度以及哈希输出。

2.1 多哈希示例

假设我们要哈希的数据是 b'i love you' ,选择 sha256 作为哈希函数:

from hashlib import sha256
print(sha256(b'i love you').hexdigest())

输出结果为:

'1c5863cd55b5a4413fd59f054af57ba3c75c0698b3851d70f99b8de2d5c7338f'

哈希输出的长度为 64 个字符,由于十六进制格式的数字每个占两个字符,所以实际长度为 32(64 / 2)。我们需要的十六进制版本的 32 是 0x20 或 20。

在多哈希支持的哈希函数表中, sha256 对应的编号是 12。我们将它们组合起来:

12 + 20 + 1c5863cd55b5a4413fd59f054af57ba3c75c0698b3851d70f99b8de2d5c7338f

也可以写成:

12201c5863cd55b5a4413fd59f054af57ba3c75c0698b3851d70f99b8de2d5c7338f

如果使用 sha1 哈希函数:

from hashlib import sha1
print(sha1(b'i love you').hexdigest())

输出结果为:

'bb7b1901d99e8b26bb91d2debdb7d7f24b3158cf'

哈希输出长度为 40 个字符,实际长度为 20(40 / 2),十六进制版本为 0x14 或 14。 sha1 在哈希函数表中的编号是 0x11 或 11,组合后的输出为:

11 + 14 + bb7b1901d99e8b26bb91d2debdb7d7f24b3158cf

即:

1114bb7b1901d99e8b26bb91d2debdb7d7f24b3158cf
2.2 为什么使用多哈希

传统的哈希函数(如 sha1 sha256 keccak256 )可能会被破解,即有人可能在合理的时间内找到两个不同的输入但具有相同的哈希输出。这对于数据完整性检查非常危险。

例如,我们发送一个治疗癌症的秘密文档,为确保其未被篡改,会对文档进行哈希并广播哈希结果。但如果敌人创建了一个不同的文档(如创建病毒的指南),却具有相同的哈希输出,那么接收者可能会误执行该文件。

当哈希函数被破解时,程序员需要升级系统,但通常会遇到困难,因为他们对哈希函数的输出长度有假设。而使用多哈希,升级过程会更简单,因为哈希函数和输出长度都嵌入在多哈希的输出中,我们不再需要对哈希输出长度进行假设。

可以通过以下代码进行实验:

(ipfs-venv) $ pip install pymultihash
(ipfs-venv) $ python
import multihash
the_universal_hash = multihash.digest(b'i love you', 'sha1')
print(the_universal_hash.verify(b'i love you'))

当发现 sha1 哈希函数被破解时,只需将 'sha1' 替换为 'sha2_256'

the_universal_hash = multihash.digest(b'i love you', 'sha2_256')
print(the_universal_hash.verify(b'i love you'))
3. Base58 编码

Base58 是 Base64 的修改版本,通常用于将二进制数据编码为 ASCII 字符串。

3.1 Base64 编码示例
import base64
print(base64.b64encode(b'i love you'))

输出结果为:

b'aSBsb3ZlIHlvdQ=='

Base64 编码常用于将二进制数据(如图像文件)编码,以便在不支持二进制数据的环境中传输,如电子邮件。

3.2 Base58 编码示例

Base58 编码去除了打印时容易混淆的字符(如 0、O、I 和 l)以及 + / 字符。它最初由中本聪设计用于编码大整数,例如比特币地址。

安装 base58 库并进行实验:

import base58
print(base58.b58encode(b'i love you'))

输出结果为:

b'6uZUjTpoUEryQ8'

使用 Base58 可以创建一个长十六进制字符串,方便我们用肉眼检查和验证。

4. 结合 Protobuf、多哈希和 Base58

现在我们已经了解了 Protobuf、多哈希和 Base58,可以解释 b'I am a good unicorn.\n' 文件内容如何转换为 'QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR'

4.1 数据序列化

b'I am a good unicorn.\n' 数据被包装在 IPFS 节点中,并使用 Protobuf 序列化为 b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15'

创建 serialize_unicorn.py 脚本:

import unixfs_pb2
import merkledag_pb2
precious_data = b'I am a good unicorn.\n'
unicorn = unixfs_pb2.Data()
unicorn.Type = unixfs_pb2.Data.File
unicorn.Data = precious_data
unicorn.filesize = len(precious_data)
serialized_unicorn_node = unicorn.SerializeToString()
outer_node = merkledag_pb2.PBNode()
outer_node.Data = serialized_unicorn_node
print(outer_node.SerializeToString())

运行脚本:

(ipfs-venv) $ python serialize_unicorn.py

输出结果为:

b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15'
4.2 哈希处理

使用 sha256 对 Protobuf 序列化的数据进行哈希处理:

import hashlib
print(hashlib.sha256(b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15').hexdigest())

输出结果为:

'912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536'

sha256 在多哈希表中的编号是 12,哈希输出长度为 32(十六进制为 0x20 )。将它们连接起来:

12 + 20 + 912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536

即:

1220912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536
4.3 Base58 编码

由于 b58encode() 方法只接受字节对象,我们需要先将十六进制字符串转换为字节对象:

import codecs
byte_data = codecs.decode('1220912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536', 'hex')
import base58
print(base58.b58encode(byte_data))

输出结果为:

b'QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR'
5. ipfsapi API 操作

我们可以使用 ipfsapi API 与 IPFS 进行交互,包括添加文件、列出块以及重建文件等操作。

5.1 添加大文件

从 Unsplash 下载一个至少 1MB 的图像文件,例如 milada-vigerova-1284157-unsplash.jpg ,并将其放在与 IPFS Python 脚本相同的目录中。

创建 add_image_file.py 脚本:

import ipfsapi
c = ipfsapi.connect()
result = c.add('milada-vigerova-1284157-unsplash.jpg')
print(result)

运行脚本:

(ipfs-venv) $ python add_image_file.py

输出结果为:

{'Name': 'milada-vigerova-1284157-unsplash.jpg', 'Hash': 'QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM', 'Size': '2604826'}
5.2 列出块

创建 list_blocks.py 脚本:

import ipfsapi
import pprint
c = ipfsapi.connect()
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(blocks)

运行脚本:

(ipfs-venv) $ python list_blocks.py

输出结果为包含多个块信息的字典。

大文件在 IPFS 中会被分割成多个块,每个块的大小通常为 262,158 字节(最后一个块除外)。IPFS 使用 Merkle 树计算文件内容的根哈希。

5.3 反向工程 IPFS 块

可以使用以下命令获取文件内容的 IPFS 块并保存为二进制文件:

$ ipfs block get QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM > block.raw

然后使用 protoc 编译器解码二进制文件:

$ protoc --decode_raw < block.raw

解码时,如果没有 .proto 文件,需要猜测某些块中数字(如 1、2、3 和 4)的含义。因此,在解码序列化数据之前,最好先获取 .proto 文件。

5.4 从块重建原始文件

创建 construct_image_from_blocks.py 脚本:

import ipfsapi
c = ipfsapi.connect()
images_bytes = []
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
for block in blocks['Objects'][0]['Links']:
    bytes = c.cat(block['Hash'])
    images_bytes.append(bytes)
images = b''.join(images_bytes)
with open('image_from_blocks.jpg', 'wb') as f:
    f.write(images)

运行脚本后,打开 image_from_blocks.jpg 文件,即可查看原始图像。

5.5 添加目录

创建一个名为 mysite 的目录,在其中创建一个 img 目录,并将 cat.jpg 图像文件放入 img 目录。在 img 目录旁边创建一个 index.html 文件和一个 README.md 文件。

index.html 文件内容如下:

<html>
  <head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  </head>
  <body>
    <img src="img/cat.jpg" class="rounded-circle" />
  </body>
</html>

README.md 文件内容如下:

This is Readme file.

创建 add_directory.py 脚本:

import ipfsapi
import pprint
c = ipfsapi.connect()
result = c.add('mysite', True)
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(result)

运行脚本:

(ipfs-venv) $ python add_directory.py

输出结果为包含目录中每个文件和目录的哈希信息的列表。

可以通过以下 URL 在浏览器中打开网站:

http://localhost:8080/ipfs/QmZamPcNnfZjjTkoyrYjYMEA8pp29KmpmkuSvkicSGiZDp/

也可以使用另一个网关访问:

https://ipfs.io/ipfs/QmZamPcNnfZjjTkoyrYjYMEA8pp29KmpmkuSvkicSGiZDp/
6. 星际命名系统(IPNS)

有时候,我们希望使用相同的链接发布动态文件,即哈希链接在不同时间生成不同的内容。这时可以使用星际命名系统(IPNS)。

6.1 创建星座预测文件

创建两个星座预测文件 horoscope1.txt horoscope2.txt ,内容分别为:

You will meet the love of your life today!
You need to be careful when going outside!

创建 add_horoscope_predictions.py 脚本:

import ipfsapi
c = ipfsapi.connect()
result = c.add('horoscope1.txt')
print(result)
result = c.add('horoscope2.txt')
print(result)

运行脚本后,记录输出中的两个哈希值。

6.2 列出密钥

创建 keys_list.py 脚本:

import ipfsapi
c = ipfsapi.connect()
print(c.key_list())

运行脚本:

(ipfs-venv) $ python keys_list.py

输出结果为包含密钥信息的字典。

6.3 发布星座预测

创建 publish_horoscope1.py 脚本:

import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR')
result = c.cat('/ipns/' + peer_id)
print(result)

运行脚本可能需要一些时间,因为在 IPNS 中发布文件有点慢。

创建 publish_horoscope2.py 脚本:

import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('Qme1FUeEhA1myqQ8C1sCSXo4dDJzZApGD6StE26S72ZqyU')
result = c.cat('/ipns/' + peer_id)
print(result)

运行脚本会得到与之前不同的结果,尽管 IPNS 路径相同。

6.4 生成新的 IPNS 路径

创建 generate_another_key.py 脚本:

import ipfsapi
c = ipfsapi.connect()
print(c.key_list())
c.key_gen('another_key', 'rsa')
print(c.key_list())

运行脚本后,会得到一个新的密钥和对应的 IPNS 路径。

创建 publish_horoscope1_in_another_ipns.py 脚本:

import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][1]['Id']
c.name_publish('QmTG4eE6ruUDhSKxqwofJXXqDFAmNzQiGdo4Z7WvVdLZuS', key='another_key')
result = c.cat('/ipns/' + peer_id)
print(result)

通过以上步骤,我们可以全面了解如何使用 ipfsapi 与 IPFS 进行交互,包括数据处理、文件和目录的添加以及动态内容的发布。这些操作可以帮助我们更好地利用 IPFS 的分布式存储和内容寻址特性。

使用ipfsapi与IPFS交互的全面指南

7. 操作流程总结

为了更清晰地展示使用 ipfsapi 与 IPFS 进行交互的过程,我们将前面的操作步骤总结如下:

7.1 数据处理流程
步骤 操作 代码示例
1 数据序列化 创建 serialize_unicorn.py 脚本并运行
import unixfs_pb2
import merkledag_pb2
precious_data = b'I am a good unicorn.\n'
unicorn = unixfs_pb2.Data()
unicorn.Type = unixfs_pb2.Data.File
unicorn.Data = precious_data
unicorn.filesize = len(precious_data)
serialized_unicorn_node = unicorn.SerializeToString()
outer_node = merkledag_pb2.PBNode()
outer_node.Data = serialized_unicorn_node
print(outer_node.SerializeToString())

| 2 | 哈希处理 | 使用 sha256 对序列化数据进行哈希 |

import hashlib
print(hashlib.sha256(b'\n\x1b\x08\x02\x12\x15I am a good unicorn.\n\x18\x15').hexdigest())

| 3 | Base58 编码 | 将哈希结果转换为 Base58 编码 |

import codecs
byte_data = codecs.decode('1220912d1af8f0013cd12a514859d20e9a196eb2845981408a84cf3543bb359a4536', 'hex')
import base58
print(base58.b58encode(byte_data))
7.2 文件和目录操作流程
步骤 操作 代码示例
1 添加大文件 创建 add_image_file.py 脚本并运行
import ipfsapi
c = ipfsapi.connect()
result = c.add('milada-vigerova-1284157-unsplash.jpg')
print(result)

| 2 | 列出块 | 创建 list_blocks.py 脚本并运行 |

import ipfsapi
import pprint
c = ipfsapi.connect()
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(blocks)

| 3 | 反向工程 IPFS 块 | 使用命令获取并解码块 |

$ ipfs block get QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM > block.raw
$ protoc --decode_raw < block.raw

| 4 | 从块重建原始文件 | 创建 construct_image_from_blocks.py 脚本并运行 |

import ipfsapi
c = ipfsapi.connect()
images_bytes = []
blocks = c.ls('QmV5KPoHHqbq2NsALniERnaYjCJPi3UxLnpwdTkV1EbNZM')
for block in blocks['Objects'][0]['Links']:
    bytes = c.cat(block['Hash'])
    images_bytes.append(bytes)
images = b''.join(images_bytes)
with open('image_from_blocks.jpg', 'wb') as f:
    f.write(images)

| 5 | 添加目录 | 创建 add_directory.py 脚本并运行 |

import ipfsapi
import pprint
c = ipfsapi.connect()
result = c.add('mysite', True)
pp = pprint.PrettyPrinter(indent=2)
pp.pprint(result)
7.3 IPNS 操作流程
步骤 操作 代码示例
1 创建星座预测文件 创建 horoscope1.txt horoscope2.txt 文件
2 添加文件 创建 add_horoscope_predictions.py 脚本并运行
import ipfsapi
c = ipfsapi.connect()
result = c.add('horoscope1.txt')
print(result)
result = c.add('horoscope2.txt')
print(result)

| 3 | 列出密钥 | 创建 keys_list.py 脚本并运行 |

import ipfsapi
c = ipfsapi.connect()
print(c.key_list())

| 4 | 发布星座预测 | 创建 publish_horoscope1.py publish_horoscope2.py 脚本并运行 |

# publish_horoscope1.py
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR')
result = c.cat('/ipns/' + peer_id)
print(result)

# publish_horoscope2.py
import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][0]['Id']
c.name_publish('Qme1FUeEhA1myqQ8C1sCSXo4dDJzZApGD6StE26S72ZqyU')
result = c.cat('/ipns/' + peer_id)
print(result)

| 5 | 生成新的 IPNS 路径 | 创建 generate_another_key.py 脚本并运行 |

import ipfsapi
c = ipfsapi.connect()
print(c.key_list())
c.key_gen('another_key', 'rsa')
print(c.key_list())

| 6 | 在新 IPNS 路径发布内容 | 创建 publish_horoscope1_in_another_ipns.py 脚本并运行 |

import ipfsapi
c = ipfsapi.connect()
peer_id = c.key_list()['Keys'][1]['Id']
c.name_publish('QmTG4eE6ruUDhSKxqwofJXXqDFAmNzQiGdo4Z7WvVdLZuS', key='another_key')
result = c.cat('/ipns/' + peer_id)
print(result)
8. 操作流程图

下面是使用 mermaid 绘制的操作流程图,展示了使用 ipfsapi 与 IPFS 交互的主要流程:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(数据处理):::process
    B --> B1(数据序列化):::process
    B --> B2(哈希处理):::process
    B --> B3(Base58 编码):::process
    B1 --> B2
    B2 --> B3
    A --> C(文件和目录操作):::process
    C --> C1(添加大文件):::process
    C --> C2(列出块):::process
    C --> C3(反向工程 IPFS 块):::process
    C --> C4(从块重建原始文件):::process
    C --> C5(添加目录):::process
    C1 --> C2
    C2 --> C3
    C3 --> C4
    C5 --> C4
    A --> D(IPNS 操作):::process
    D --> D1(创建星座预测文件):::process
    D --> D2(添加文件):::process
    D --> D3(列出密钥):::process
    D --> D4(发布星座预测):::process
    D --> D5(生成新的 IPNS 路径):::process
    D --> D6(在新 IPNS 路径发布内容):::process
    D1 --> D2
    D2 --> D3
    D3 --> D4
    D4 --> D5
    D5 --> D6
    B3 --> C1
    C4 --> D1
    D6 --> E([结束]):::startend
9. 注意事项和常见问题

在使用 ipfsapi 与 IPFS 进行交互时,有一些注意事项和常见问题需要我们关注:

9.1 哈希函数的选择

虽然多哈希可以简化哈希函数的升级过程,但在选择哈希函数时,仍然需要考虑其安全性和性能。例如, sha1 已经被证明是不安全的,建议使用更安全的哈希函数,如 sha256

9.2 大文件处理

大文件在 IPFS 中会被分割成多个块,每个块的大小可以配置。在处理大文件时,需要确保有足够的存储空间和网络带宽。

9.3 解码序列化数据

在使用 protoc 编译器解码序列化数据时,如果没有 .proto 文件,会增加解码的难度。因此,建议在解码之前先获取 .proto 文件。

9.4 IPNS 发布速度

在 IPNS 中发布文件可能会比较慢,需要耐心等待。同时,要注意 IPNS 路径的管理,避免混淆。

10. 总结

通过本文的介绍,我们详细了解了如何使用 ipfsapi 与 IPFS 进行交互。从数据的序列化、哈希处理和 Base58 编码,到文件和目录的添加、块的管理以及动态内容的发布,每个环节都有其重要性和操作要点。

使用 IPFS 的分布式存储和内容寻址特性,可以帮助我们更好地管理和共享数据。同时,IPNS 为我们提供了发布动态内容的能力,使得我们可以使用相同的链接在不同时间发布不同的内容。

在实际应用中,我们可以根据具体需求选择合适的操作流程和方法,同时注意避免常见问题,以充分发挥 IPFS 的优势。希望本文能够帮助你更好地掌握使用 ipfsapi 与 IPFS 进行交互的技巧。

【电力系统】单机无穷大电力系统短路故障暂态稳定Simulink仿真(带说明文档)内容概要:本文档围绕“单机无穷大电力系统短路故障暂态稳定Simulink仿真”展开,提供了完整的仿真模型说明文档,重点研究电力系统在发生短路故障后的暂态稳定性问题。通过Simulink搭建单机无穷大系统模型,模拟不同类型的短路故障(如三相短路),分析系统在故障期间及切除后的动态响应,包括发电机转子角度、转速、电压和功率等关键参数的变化,进而评估系统的暂态稳定能力。该仿真有助于理解电力系统稳定性机理,掌握暂态过程分析方法。; 适合人群:电气工程及相关专业的本科生、研究生,以及从事电力系统分析、运行控制工作的科研人员和工程师。; 使用场景及目标:①学习电力系统暂态稳定的基本概念分析方法;②掌握利用Simulink进行电力系统建模仿真的技能;③研究短路故障对系统稳定性的影响及提高稳定性的措施(如故障清除时间优化);④辅助课程设计、毕业设计或科研项目中的系统仿真验证。; 阅读建议:建议结合电力系统稳定性理论知识进行学习,先理解仿真模型各模块的功能参数设置,再运行仿真并仔细分析输出结果,尝试改变故障类型或系统参数以观察其对稳定性的影响,从而深化对暂态稳定问题的理解。
本研究聚焦于运用MATLAB平台,将支持向量机(SVM)应用于数据预测任务,并引入粒子群优化(PSO)算法对模型的关键参数进行自动调优。该研究属于机器学习领域的典型实践,其核心在于利用SVM构建分类模型,同时借助PSO的全局搜索能力,高效确定SVM的最优超参数配置,从而显著增强模型的整体预测效能。 支持向量机作为一种经典的监督学习方法,其基本原理是通过在高维特征空间中构造一个具有最大间隔的决策边界,以实现对样本数据的分类或回归分析。该算法擅长处理小规模样本集、非线性关系以及高维度特征识别问题,其有效性源于通过核函数将原始数据映射至更高维的空间,使得原本复杂的分类问题变得线性可分。 粒子群优化算法是一种模拟鸟群社会行为的群体智能优化技术。在该算法框架下,每个潜在解被视作一个“粒子”,粒子群在解空间中协同搜索,通过不断迭代更新自身速度位置,并参考个体历史最优解和群体全局最优解的信息,逐步逼近问题的最优解。在本应用中,PSO被专门用于搜寻SVM中影响模型性能的两个关键参数——正则化参数C核函数参数γ的最优组合。 项目所提供的实现代码涵盖了从数据加载、预处理(如标准化处理)、基础SVM模型构建到PSO优化流程的完整步骤。优化过程会针对不同的核函数(例如线性核、多项式核及径向基函数核等)进行参数寻优,并系统评估优化前后模型性能的差异。性能对比通常基于准确率、精确率、召回率及F1分数等多项分类指标展开,从而定量验证PSO算法在提升SVM模型分类能力方面的实际效果。 本研究通过一个具体的MATLAB实现案例,旨在演示如何将全局优化算法机器学习模型相结合,以解决模型参数选择这一关键问题。通过此实践,研究者不仅能够深入理解SVM的工作原理,还能掌握利用智能优化技术提升模型泛化性能的有效方法,这对于机器学习在实际问题中的应用具有重要的参考价值。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值