# 文档

> 餐厅自己的文档库（证书、操作规程、报告），每条记录都是一份归档的文件，各有自己的媒体类型。

Source: https://developers.backresto.com/zh/docs/drive-files

每家餐厅在那些查验之外都还留着纸：供应商证书、清洁规程、上一次检查的报告、一份
签过字的计划。应用给了他们一个地方把这些归档，而这个集合就是那个档案柜：每份
文档一条记录，每一条都指向那个文件本身。

| 集合 | 权限范围 | 一条记录是什么 |
| --- | --- | --- |
| `drive-files` | `drive-files:read` | 一份归档在这家餐厅文档库里的文档。 |

它共用那个[快照信封](https://developers.backresto.com/zh/docs/collections)：`id`、`deleted`、`capturedAt`、
`receivedAt`、`sequence` 以及其余字段，都围在下面的 `data` 周围。

## `drive-files`

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `s3Key` | string, **必填** | 这份文档在库里的位置。是一个键，不是一个 URL。最长 1024 个字符。 |
| `name` | string, **必填** | 餐厅看到的那个文件名。 |
| `sizeBytes` | number, **必填** | 这份文档的大小，以字节计。 |
| `contentType` | string \| null | 应用记下的那个媒体类型。可能缺失。 |
| `asset` | object | 那个文件。请读下一节。 |

```json
{
  "id": "a94c7e51-3f60-4b2d-8e17-05c9d3a6f482",
  "collection": "drive-files",
  "deleted": false,
  "capturedAt": "1789743015220",
  "receivedAt": "1789743098644",
  "sequence": "97",
  "data": {
    "s3Key": "drive/fournisseurs/attestation-metro-2026.pdf",
    "name": "Attestation Metro 2026.pdf",
    "sizeBytes": 184320,
    "contentType": "application/pdf",
    "asset": {
      "objectKey": "restaurants/36eaa3fa/drive/a17c93be4f02",
      "status": "uploaded",
      "contentType": "application/pdf",
      "byteLength": 184320,
      "sha256": "9f2a…"
    }
  }
}
```

`data` 里没有 `timestamp`。这份文档是什么时候归档的，是信封上的 `capturedAt`，和
其他每一个集合一样。

## 文件才是重点

在别处，记录是信息，文件是凭证。这里正好反过来：五个字段描述一份文档，而这份文档
的内容，才是任何人来读这个集合的全部理由。所以你几乎总是会顺着记录走到字节那边
去。

```http
GET /v1/restaurants/{restaurantId}/collections/drive-files/records/{recordId}/assets
```

签名 URL，有效**十五分钟**，每个都自带授权。[集合那一页](https://developers.backresto.com/zh/docs/collections)上有
那些规则，而其中两条在这里比在别处更要紧：

- **`sha256` 能让你省掉一次下载。** 它是这些字节的摘要。一份自你上次遍历以来没有
  变过的证书，不需要再取一次，而文档是这个 API 会交给你的最大的东西。
- **现在就取，存字节，绝不要存链接。** 十五分钟长到足够下载完，也短到让你数据库里
  的一个 URL 到第二天早上就成了一条死链。

**`asset` 描述这个文件；但只有那个端点会把它交给你。** 它带着 `objectKey`、
`status`、`contentType`、`byteLength` 和 `sha256`（足够你决定要不要这些字节），
但没有 URL，因为一个永不过期的 URL，会成为一条通往一家餐厅文书资料的、永久的、
不需要认证的链接。

`status` 是你该拿来分支的那个字段。字节落地并通过校验之后它是 `uploaded`，而在
某台设备还在发送它们的时候是 `pending`。一个 `pending` 的附件不会出现在 assets
端点里，这是正确的行为，而不是坏掉了：过一会儿再问一次，而不要把它当成一份丢失的
文档。`asset` 也是这里唯一一个可以整个缺席的字段：一条还没有文件的归档记录。

## 这些不是图片

一份清洁凭证是一张照片。而一份文档是餐厅归档进来的任何东西：一份 PDF 证书、一份
Excel 或 CSV 的温度导出、一份 Word 规程、某人用手机扫的一张扫描件。它们全都会出现
在这里。

**请读 `contentType`，而不要假定一个。** 它在记录里可以为 null（应用并不总是有
它），所以当它是 null 时，改用附件上的那个 `contentType`；那一个是真正被存下来的那
个对象的媒体类型，而且在两者不一致的时候，它才是该信的那个。一个把所有东西都渲染
成图片、或者把所有东西都存成 `.pdf` 的客户端，会在遇到第一份表格时就坏掉。

**也不要从 `name` 或 `s3Key` 推出类型。** 扩展名是一个惯例，不是一个保证，而这两
个字段都是一个人在平板上给文件起名时打出来的自由文本。

## 在你这边归档它们

**`s3Key` 是一个键，不是一个地址。** 它在库里标识那个对象：它没有协议、没有主
机，也没有开头的斜杠，而且你没法拿它去取东西。你拿到的每一个字节都来自 assets
端点。

**以信封上的 `id` 为键，而不是 `name`。** 没有任何东西阻止两份文档都叫
`Attestation 2026.pdf`，在同一家餐厅里，相隔一年归档。

**在下载之前，`sizeBytes` 值得一读。** 它是决定要不要在移动网络上拉一份 40 MB
扫描件的最廉价的办法。而被存下来的那个对象自己的 `byteLength`，在附件上，才是权威
的那一个。

**一条 tombstone 会把文件一起带走。** `deleted: true` 意味着这份文档在应用里被删除
了，它的附件也不再被列出。如果你当时需要那份证书，你需要的是那些字节。tombstone
是怎么表现的，见[信封那一页](https://developers.backresto.com/zh/docs/collections)。
