Core 是较为底层的内部实现。一般的发帖、feed 获取、通知等场景并不需要它。只有当你需要直接操作 AT Protocol 数据结构时才需要参考本页。
AT Protocol 的数据模型概览
AT Protocol 的仓库采用了内容寻址式数据结构。数据的保存、传输和校验依赖以下几个组件。
| 类 | 作用 |
|---|
CBOR | 二进制序列化 |
CID | 内容寻址(哈希标识符) |
CAR | 仓库归档的读写 |
TID | 时序 record key |
Varint | 变长整数编码(内部使用) |
CBOR
CBOR 类提供了 AT Protocol 所采用的 DAG-CBOR 格式的编解码。作为标准 CBOR 的扩展,它支持 CID link(tag 42)。
use Revolution\Bluesky\Core\CBOR;
$record = [
'$type' => 'app.bsky.feed.post',
'text' => 'Hello, Bluesky!',
'createdAt' => '2025-01-01T00:00:00.000Z',
];
$bytes = CBOR::encode($record); // 二进制字符串
use Revolution\Bluesky\Core\CBOR;
// 解码单个 item
$data = CBOR::decode($bytes);
// 解码流开头的 item,并返回剩余部分
[$item, $remainder] = CBOR::decodeFirst($bytes);
// 解码流中所有 item
$items = CBOR::decodeAll($bytes);
如果想手动计算和校验帖子的 CID,需要使用 CBOR 编码(另见 verify 页面)。
use Revolution\Bluesky\Core\CBOR;
use Revolution\Bluesky\Core\CID;
$record = data_get($block, 'value');
$cbor = CBOR::encode($record);
$bool = CID::verify($cbor, data_get($block, 'cid'));
CID (Content Identifier)
CID 是基于数据哈希的自描述标识符。在 AT Protocol 中,SHA-256 哈希会被 multihash 包裹,再通过 multicodec 和 multibase 编码。
CIDv0 与 CIDv1
| 版本 | 编码 | 首字符 | 用途 |
|---|
| v0 | base58btc | Qm... | 旧规格(blob) |
| v1 | base32 | bafy... | 新规格(record) |
主要 API
use Revolution\Bluesky\Core\CID;
// 从数据生成 CID
$cid = CID::encode($data, CID::DAG_CBOR); // 用于 record
$cid = CID::encode($data, CID::RAW); // 二进制(图片等)
// 校验 CID
$bool = CID::verify($data, $cid, CID::DAG_CBOR);
$bool = CID::verify($file, $cid, CID::RAW);
// 解析 CID
$decoded = CID::decode($cid); // ['version', 'codec', 'hash']
// 查看 CID 版本
$version = CID::version($cid); // 0 or 1
// 与字节表示互转
$bytes = CID::decodeBytes($cid);
$cid = CID::encodeBytes($bytes);
CAR (Content Addressable aRchive)
CAR 文件是把 AT Protocol 仓库以 block 序列形式存储的二进制格式。通过 com.atproto.sync.getRepo 获取的数据就是这种格式。
use Revolution\Bluesky\Core\CAR;
// 解码整个 CAR 文件(root 与全部 block)
['roots' => $roots, 'blocks' => $blocks] = CAR::decode($carData);
// 仅获取 root CID
$roots = CAR::decodeRoots($carData);
// 遍历 block
foreach (CAR::blockIterator($carData) as [$cid, $block]) {
// $cid: CID 字符串,$block: 二进制数据
}
// 作为 record map 获取
foreach (CAR::blockMap($carData) as $key => $record) {
// $key: record key, $record: 已解码的数组
}
校验带签名的 Commit
要确认 CAR 文件确实属于某个用户,需要用 DID Document 中的公钥来校验 Signed Commit 的签名。
use Revolution\Bluesky\Core\CAR;
use Revolution\Bluesky\Crypto\DidKey;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Support\DidDocument;
$did = 'did:plc:***';
$didDoc = DidDocument::make(Bluesky::identity()->resolveDID($did)->json());
$publicKey = DidKey::parse($didDoc->publicKey());
$signed = CAR::signedCommit($carData);
$bool = CAR::verifySignedCommit($signed, $publicKey);
参考实现:DownloadRepoCommand
TID (Timestamp Identifier)
TID 是 AT Protocol 中作为 record key 使用的时序 ID。它由微秒级时间戳与 clock ID 组合而成,用 base32 编码,共 13 个字符。
生成与转换
use Revolution\Bluesky\Core\TID;
// 生成下一个 TID
$tid = TID::next();
echo $tid->toString(); // 类似 "3jujm55ngfc24" 的字符串
// 从字符串构建 TID
$tid = TID::fromStr('3jujm55ngfc24');
// 从时间戳(微秒)和 clock ID 构建
$tid = TID::fromTime(microtime(true) * 1000000, 0);
TID 的结构
通过 TID::s32encode() 与 TID::s32decode() 可以在整数与字符串之间互相转换。
Varint(变长整数)
Varint 是 CAR / CBOR 二进制解析时使用的内部工具类。通常不需要直接使用。
use Revolution\Bluesky\Core\Varint;
// 对整数编码
$bytes = Varint::encode(1234);
// 从流中解码(返回值与消费的字节数)
[$value, $bytesRead] = Varint::decodeStream($stream);
关于 Core 的 mock
Core 的功能(CBOR/CID/CAR/TID)不会访问外部服务,因此测试时不需要 mock。
// 测试里也可以直接使用
$bytes = CBOR::encode(['text' => 'Hello']);
$cid = CID::encode($bytes, CID::DAG_CBOR);
$bool = CID::verify($bytes, $cid);
参考链接