@h-ai/storage 0.1.0-alpha.66 → 0.1.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +100 -25
- package/README.ja.md +101 -26
- package/README.md +141 -26
- package/dist/browser.d.ts +399 -93
- package/dist/browser.js +3 -2
- package/dist/{chunk-KXE6OO5T.js → chunk-C7FR45VO.js} +8 -5
- package/dist/chunk-C7FR45VO.js.map +1 -0
- package/dist/chunk-I4BSES63.js +357 -0
- package/dist/chunk-I4BSES63.js.map +1 -0
- package/dist/chunk-P65WYXJG.js +33 -0
- package/dist/chunk-P65WYXJG.js.map +1 -0
- package/dist/client/index.d.ts +57 -48
- package/dist/client/index.js +1 -1
- package/dist/node.d.ts +15 -11
- package/dist/node.js +296 -136
- package/dist/node.js.map +1 -1
- package/dist/schemas.d.ts +103 -0
- package/dist/schemas.js +3 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +7 -2
- package/dist/chunk-25GYTRA7.js +0 -274
- package/dist/chunk-25GYTRA7.js.map +0 -1
- package/dist/chunk-KXE6OO5T.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,16 +4,34 @@
|
|
|
4
4
|
|
|
5
5
|
[框架首页](../../README.md) · 相关模块:[@h-ai/core](../core/README.md)
|
|
6
6
|
|
|
7
|
+
## 简介
|
|
8
|
+
|
|
7
9
|
对象存储模块,提供统一 `storage` 访问入口,支持 S3 兼容存储与本地文件系统。
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
初始化只在健康检查成功后发布连接;失败时释放候选资源。若清理仍失败,初始化错误的 `error.ext.cleanupPending` 为 `true`。`storage.close()` 返回 `Promise<HaiResult<void>>`,等待正在进行的初始化,并合并并发关闭。开始清理后 `isInitialized` 为 `false`,配置与业务操作不再可用;清理失败仍保留资源,后续 `close()` 或 `init()` 先重试释放,成功前不会创建替代连接。调用方必须检查关闭结果。
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const closed = await storage.close()
|
|
15
|
+
if (!closed.success) {
|
|
16
|
+
// 处理清理故障后有限重试;再次失败应向应用报告。
|
|
17
|
+
const retried = await storage.close()
|
|
18
|
+
if (!retried.success)
|
|
19
|
+
throw new Error(retried.error.message)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
S3 普通请求客户端与禁用请求重试的批量删除客户端分别释放;一个失败不阻止另一个清理,已释放的客户端不会重复释放。
|
|
24
|
+
|
|
25
|
+
## 核心功能清单
|
|
10
26
|
|
|
11
27
|
- S3 协议(AWS S3 / MinIO / RustFS / OSS 等)
|
|
12
28
|
- 本地文件系统
|
|
13
29
|
|
|
14
|
-
##
|
|
30
|
+
## 使用入门
|
|
31
|
+
|
|
32
|
+
### 快速开始
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
#### Node.js 服务端
|
|
17
35
|
|
|
18
36
|
```ts
|
|
19
37
|
import { storage } from '@h-ai/storage'
|
|
@@ -36,6 +54,10 @@ const file = await storage.file.get('uploads/image.png')
|
|
|
36
54
|
|
|
37
55
|
// 签名 URL
|
|
38
56
|
const url = await storage.presign.getUrl('uploads/image.png', { expiresIn: 3600 })
|
|
57
|
+
if (!url.success) {
|
|
58
|
+
// local 不支持预签名;向用户呈现错误,或使用应用已有的鉴权文件接口。
|
|
59
|
+
// 失败结果不含可上传/下载的 URL。
|
|
60
|
+
}
|
|
39
61
|
|
|
40
62
|
// 关闭连接
|
|
41
63
|
const closeResult = await storage.close()
|
|
@@ -44,31 +66,38 @@ if (!closeResult.success) {
|
|
|
44
66
|
}
|
|
45
67
|
```
|
|
46
68
|
|
|
47
|
-
|
|
69
|
+
#### 浏览器客户端
|
|
48
70
|
|
|
49
71
|
浏览器环境下仅导出客户端能力(签名 URL 上传/下载),不包含 `storage` 服务对象。
|
|
50
72
|
|
|
51
73
|
```ts
|
|
74
|
+
import { apiClient } from '@h-ai/api-client'
|
|
52
75
|
import { downloadAndSave, uploadWithPresignedUrl } from '@h-ai/storage/client'
|
|
53
76
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
56
|
-
|
|
77
|
+
// apiClient 已初始化,服务端使用 S3。
|
|
78
|
+
const signed = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
|
|
79
|
+
if (!signed.success)
|
|
80
|
+
throw new Error(signed.error.message)
|
|
81
|
+
await uploadWithPresignedUrl(signed.data.url, file)
|
|
57
82
|
```
|
|
58
83
|
|
|
59
84
|
> 注意:`@h-ai/storage` 不内置 HTTP 路由;请在应用层自行实现签名 URL API。
|
|
60
85
|
|
|
61
|
-
|
|
86
|
+
### HTTP API 契约
|
|
62
87
|
|
|
63
|
-
公共 HTTP API 统一由 `@h-ai/api-contract` 提供,并由 `@h-ai/serv` 的 `createStorageProcedures({ storage })` 绑定到本模块能力。
|
|
88
|
+
公共 HTTP API 统一由 `@h-ai/api-contract` 提供,并由 `@h-ai/serv` 的 `createStorageProcedures({ storage, authorize })` 绑定到本模块能力。
|
|
64
89
|
|
|
65
90
|
```ts
|
|
66
91
|
import { apiClient } from '@h-ai/api-client'
|
|
67
92
|
|
|
68
93
|
const result = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
|
|
94
|
+
if (!result.success)
|
|
95
|
+
throw new Error(result.error.message)
|
|
69
96
|
```
|
|
70
97
|
|
|
71
|
-
|
|
98
|
+
预签名只支持 S3。local 的 `getUrl()` / `putUrl()` 返回 `HaiStorageError.UNSUPPORTED_OPERATION`(`hai:storage:018`),`publicUrl()` 返回 `null`;不会返回虚拟 URL。serv 与 apiClient 保留该失败结果,调用方须先检查 `success` 再消费 URL。本地开发文件可由应用已有的鉴权上传/下载接口处理,本模块不内置 HTTP 文件路由或签名验证服务。
|
|
99
|
+
|
|
100
|
+
### API 概览
|
|
72
101
|
|
|
73
102
|
- `storage.file`
|
|
74
103
|
- `put/get/getStream/head/exists/delete/deleteMany/copy`
|
|
@@ -83,6 +112,21 @@ const result = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar
|
|
|
83
112
|
|
|
84
113
|
上传选项 `{ ifNoneMatch: '*' }` 通过标准 S3 条件写或本地原子发布拒绝覆盖,冲突返回 `ALREADY_EXISTS`;S3 服务不支持条件写时返回错误,不降级为覆盖。失败/取消会清理临时文件或未完成分片;取消不能撤销已经发布的对象。
|
|
85
114
|
|
|
115
|
+
`file.deleteMany(keys)` 返回 `StorageDeleteResult`:成功时 `data` 为 `{ deletedKeys, failures: [] }`;任一删除项未确认完成时返回 `OPERATION_FAILED`,进度在 `error.ext?.deleteMany`(初始化等前置错误可能不带进度)。重复输入只处理一次,输出 key 不含配置 prefix。S3 每批最多 1000 项,逐项检查 `Deleted` 与 `Errors`;HTTP 200 不代表全部成功,遗漏/重复/未知回执标为 `unknown`。本地删除最多并发 32 项。
|
|
116
|
+
|
|
117
|
+
只对明确的 `InternalError / ServiceUnavailable / SlowDown / RequestTimeout` 项最多尝试三次;成功及权限失败项不重发。`DeleteObjects` 专用客户端禁用 SDK 请求重试;网络中断立即标为 `unknown`,未发送的后续批次标为 `notAttempted`,不盲目重放未知结果。`retryable` 只表示故障类别,调用方重试前仍须核对未知结果及并发写入。`dir.delete()` 传播删除失败;它不能阻止并发新增对象。S3 使用不带 VersionId 的删除,开启版本控制的桶可能创建删除标记,不保证清除历史版本。
|
|
118
|
+
|
|
119
|
+
`@h-ai/serv` / `@h-ai/api-client` 的 `storage.files.deleteMany` 使用同一成功结构并透传失败进度。
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const result = await storage.file.deleteMany(['a.txt', 'b.txt'])
|
|
123
|
+
if (!result.success) {
|
|
124
|
+
const progress = result.error.ext?.deleteMany
|
|
125
|
+
// 结合 outcome 与业务写入状态决定补偿;未知结果不能当作未执行。
|
|
126
|
+
const knownFailures = progress?.failures.filter(item => item.outcome === 'failed' && item.retryable) ?? []
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
86
130
|
`checksum` 是 Node 端无状态工具,无需初始化,按实际输入字节生成/校验,流输入仅消费一次。默认 SHA-256、小写 hex;可选 `algorithm: 'sha256' | 'sha384' | 'sha512' | 'sha1' | 'md5'`、`encoding: 'hex' | 'base64'` 和 `signal`。不匹配返回 `ok(false)`,非法格式/算法及读取失败返回错误。MD5/SHA-1 只用于兼容性完整性校验,不用于安全认证;ETag、S3 multipart 复合 checksum 不等同于完整文件摘要。此工具不强制 S3 兼容服务支持 AWS checksum 扩展。
|
|
87
131
|
|
|
88
132
|
取消计算会结束对下一块数据的等待,并请求关闭输入迭代器。本地 `getStream()` 读取目录时直接返回 `INVALID_PATH`,不将目录报告为可消费的文件流。
|
|
@@ -91,7 +135,7 @@ const result = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar
|
|
|
91
135
|
|
|
92
136
|
> 注意:`storage.config` 返回的是**脱敏配置快照**;S3 密钥类字段会被替换为 `[REDACTED]`,不能用于重新初始化。
|
|
93
137
|
|
|
94
|
-
|
|
138
|
+
### 客户端辅助函数
|
|
95
139
|
|
|
96
140
|
`@h-ai/storage/client` 还提供以下浏览器侧工具:
|
|
97
141
|
|
|
@@ -102,7 +146,7 @@ const result = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar
|
|
|
102
146
|
- `getMimeType(extension)`
|
|
103
147
|
- `formatFileSize(bytes)`
|
|
104
148
|
|
|
105
|
-
|
|
149
|
+
### 配置
|
|
106
150
|
|
|
107
151
|
- **S3**:`bucket / region / accessKeyId / secretAccessKey` 必填,可选 `endpoint / forcePathStyle / prefix / publicUrl`
|
|
108
152
|
- **Local**:`root` 必填,可选 `directoryMode / fileMode`
|
|
@@ -124,7 +168,7 @@ if (!initResult.success) {
|
|
|
124
168
|
}
|
|
125
169
|
```
|
|
126
170
|
|
|
127
|
-
|
|
171
|
+
#### 操作日志
|
|
128
172
|
|
|
129
173
|
可在存储配置中通过 `operationLog` 开启文件、目录和预签名 URL 操作日志。日志在 Local/S3 Provider 的真实操作处输出,不在 `storage-main.ts` 中做统一包装;文件内容不会写入日志,上传数据只记录字节长度。
|
|
130
174
|
|
|
@@ -154,9 +198,22 @@ await storage.init({
|
|
|
154
198
|
|
|
155
199
|
`storage.presign.putUrl()` 当前仅支持 `contentType` 与 `expiresIn` 等签名参数,不提供 `maxSize` 约束。
|
|
156
200
|
|
|
157
|
-
|
|
201
|
+
本地 `storage.dir.list()` 将 `prefix` 作为 key 字符串前缀(如 `reports/2026-`),按 UTF-8 字节序分页。`maxKeys` 为 1–1000 的整数,默认 1000,`files` 与 `commonPrefixes` 共用该预算。`delimiter` 将前缀之后首次出现的分隔符及其后内容折叠为一个 `commonPrefixes` 条目;空字符串不折叠,空物理目录不生成条目。这些分组与计数规则参照 [S3 ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html)。
|
|
202
|
+
|
|
203
|
+
仅当还有条目时返回 `isTruncated: true` 与 `nextContinuationToken`。本地游标无需进程内缓存,绑定规范根目录、`prefix` 和 `delimiter`;相同根目录的独立实例或重启后可续页,也可调整下一页的 `maxKeys`。跨范围、格式非法或版本不支持的游标返回 `INVALID_LIST_OPTIONS`。游标不是授权凭据或目录快照:调用方仍须授权前缀,并发增删可能改变后续页;不支持将不同挂载路径的根目录视为同一范围。本地每次请求仍扫描、排序匹配目录,大目录应缩小前缀或使用 S3。
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
let continuationToken: string | undefined
|
|
207
|
+
do {
|
|
208
|
+
const page = await storage.dir.list({ prefix: 'reports/2026-', maxKeys: 100, continuationToken })
|
|
209
|
+
if (!page.success)
|
|
210
|
+
return page
|
|
211
|
+
// 消费 page.data.files 与 page.data.commonPrefixes。
|
|
212
|
+
continuationToken = page.data.nextContinuationToken
|
|
213
|
+
} while (continuationToken)
|
|
214
|
+
```
|
|
158
215
|
|
|
159
|
-
|
|
216
|
+
### 错误处理
|
|
160
217
|
|
|
161
218
|
```ts
|
|
162
219
|
import { HaiStorageError, storage } from '@h-ai/storage'
|
|
@@ -169,17 +226,31 @@ if (!result.success && result.error.code === HaiStorageError.NOT_INITIALIZED.cod
|
|
|
169
226
|
|
|
170
227
|
常用错误码:
|
|
171
228
|
|
|
172
|
-
| 错误码
|
|
173
|
-
|
|
|
174
|
-
| `HaiStorageError.NOT_INITIALIZED`
|
|
175
|
-
| `HaiStorageError.CONNECTION_FAILED`
|
|
176
|
-
| `HaiStorageError.OPERATION_FAILED`
|
|
177
|
-
| `HaiStorageError.NOT_FOUND`
|
|
178
|
-
| `HaiStorageError.PERMISSION_DENIED`
|
|
179
|
-
| `HaiStorageError.PRESIGN_FAILED`
|
|
180
|
-
| `HaiStorageError.
|
|
229
|
+
| 错误码 | code | 说明 |
|
|
230
|
+
| --------------------------------------- | ----------------- | --------------------------------------------- |
|
|
231
|
+
| `HaiStorageError.NOT_INITIALIZED` | `hai:storage:010` | 未初始化 |
|
|
232
|
+
| `HaiStorageError.CONNECTION_FAILED` | `hai:storage:001` | 连接失败 |
|
|
233
|
+
| `HaiStorageError.OPERATION_FAILED` | `hai:storage:002` | 操作失败 |
|
|
234
|
+
| `HaiStorageError.NOT_FOUND` | `hai:storage:003` | 文件不存在 |
|
|
235
|
+
| `HaiStorageError.PERMISSION_DENIED` | `hai:storage:005` | 权限不足 |
|
|
236
|
+
| `HaiStorageError.PRESIGN_FAILED` | `hai:storage:013` | 预签名 URL 生成失败 |
|
|
237
|
+
| `HaiStorageError.UNSUPPORTED_OPERATION` | `hai:storage:018` | 当前 Provider 不支持该操作(如 local 预签名) |
|
|
238
|
+
| `HaiStorageError.INVALID_LIST_OPTIONS` | `hai:storage:019` | 本地列表参数或游标无效、游标查询范围不匹配 |
|
|
239
|
+
| `HaiStorageError.CONFIG_ERROR` | `hai:storage:012` | 配置错误 |
|
|
240
|
+
|
|
241
|
+
## 生产共享状态
|
|
242
|
+
|
|
243
|
+
生产使用共享 S3 或兼容服务;local 只用于开发/明确测试。对象业务授权和读改写的条件提交由应用负责。 NODE_ENV 或 HAI_CORE_ENV 为 production 时在初始化发布成功前校验;仅 HAI_E2E=1/HAI_E2E_MOCK=1 明确测试可豁免。 [状态、并发和恢复边界](../../.agents/instructions/distributed-state.md)。
|
|
244
|
+
|
|
245
|
+
## 输入约束与 HTTP 能力
|
|
181
246
|
|
|
182
|
-
|
|
247
|
+
存储 key 按 UTF-8 最多 1024 字节,并拒绝绝对路径、反斜线、控制字符及点路径段;公开 HTTP 批量删除每次 1–1000 项。`@h-ai/storage/schemas` 提供浏览器安全的共同规则。预签名有效期为整数 1–604800 秒(Node 默认 3600,HTTP 默认 900);HTTP 上传仅接受 key/contentType/expiresIn,不支持 contentLength/maxSize/metadata/checksum。无效输入返回 HTTP 400,typed client 映射 VALIDATION_FAILED。FileUpload 的大小/类型限制仅为前端体验,实际对象须由应用服务端复核。Node 流式读写/checksum/条件写等没有默认 HTTP 端点。完整映射与授权边界见 [存储 API 能力](README.md#存储输入约束与跨端能力)。
|
|
248
|
+
|
|
249
|
+
## 浏览器上传取消
|
|
250
|
+
|
|
251
|
+
`uploadWithPresignedUrl(url, data, { onProgress, abortController })` 使用原生 XHR 报告进度;不传 `onProgress` 时使用 fetch。预先取消不发送请求,准备阶段和进行中取消、外部 XHR abort、网络/超时及进度回调异常都只结算一次失败,并移除本次监听。完成后再取消不会操作旧 XHR。取消只终止本地请求,不能回滚远端已写入的对象;应用须先核对对象状态,再授权删除。
|
|
252
|
+
|
|
253
|
+
## 开发测试
|
|
183
254
|
|
|
184
255
|
```bash
|
|
185
256
|
pnpm --filter @h-ai/storage test
|
|
@@ -192,6 +263,50 @@ S3 集成测试需要可用的 Docker 或兼容运行时;同一套初始化、
|
|
|
192
263
|
|
|
193
264
|
容器使用临时生成的测试凭据,等待服务就绪后通过 S3 API 建桶;最后一个租约释放时移除容器及匿名卷。
|
|
194
265
|
|
|
195
|
-
|
|
266
|
+
源码 JSDoc 遵循[统一规范](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证):公开示例使用实际发布入口,内部示例在原模块作用域检查。构建后执行 `pnpm check:jsdoc --package storage`;示例编译不代替真实后端行为验证。
|
|
267
|
+
|
|
268
|
+
真实 Chrome 回归:先安装可用的 Chrome,设置 `HAI_STORAGE_RUN_BROWSER_GATES=1` 后执行 `pnpm --filter @h-ai/storage exec vitest run tests/storage-client-browser.test.ts`。该门禁实际发送 HTTP PUT 并检查原生取消和监听清理;缺少浏览器会失败。
|
|
269
|
+
|
|
270
|
+
## 许可证
|
|
196
271
|
|
|
197
272
|
Apache-2.0
|
|
273
|
+
|
|
274
|
+
## 存储输入约束与跨端能力
|
|
275
|
+
|
|
276
|
+
`@h-ai/storage/schemas` 是仅依赖 Zod 的浏览器安全入口;`api-contract` 与 `serv` 复用同一规则。Schema 只校验输入,服务端仍先认证、再按可信 session 授权每个资源,Local Provider 仍执行文件系统防逃逸。调用方不能把前缀校验或分页游标当成授权。
|
|
277
|
+
|
|
278
|
+
### 输入边界
|
|
279
|
+
|
|
280
|
+
- `StorageKeySchema`:非空、不是纯空白,UTF-8 最多 1024 **字节**;不允许开头 `/`、反斜线、控制字符,以及独立的 `.` / `..` 路径段。不截断、不归一化 key。
|
|
281
|
+
- `StoragePrefixSchema`:同样的路径和字节限制,另允许空字符串;允许部分文件名,`report` 不等于目录 `report/`。
|
|
282
|
+
- `StorageDeleteKeysSchema`:每个 HTTP 请求 1–1000 个键,按原始数组数量计数,重复项不能绕过上限。Node `file.deleteMany` 允许更大数组,S3 每批最多 1000、Local 并发最多 32;失败时检查 `error.ext.deleteMany` 的已完成/失败/未知/未尝试进度,不能盲目重放整个数组。
|
|
283
|
+
- 列表 `maxKeys`:整数 1–1000,`files` 与 `commonPrefixes` 共用预算。续页使用响应原样返回的 token,并保持相同 Provider/根目录/prefix/delimiter;不保证并发写入期间的快照。
|
|
284
|
+
- 签名有效期:整数 1–604800 秒,Node 默认 3600 秒,默认 HTTP procedures 使用 900 秒。HTTP 上传签名只接受 `key`、`contentType`、`expiresIn`;下载签名只接受 `key`、`expiresIn`。未知字段直接拒绝,包括未实现的 `contentLength` / `maxSize`,不静默丢弃或假装限制已生效。
|
|
285
|
+
- 输入校验在 HTTP 层返回 400;`apiClient` 返回 `HaiApiClientError.VALIDATION_FAILED`,`error.cause.status` 保留 400。合法输入的业务失败仍按 `HaiResult.success` 判断。
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { StorageDeleteFilesInputSchema } from '@h-ai/api-contract/storage'
|
|
289
|
+
import { StorageKeySchema } from '@h-ai/storage/schemas'
|
|
290
|
+
|
|
291
|
+
const key = StorageKeySchema.safeParse('users/u1/report.pdf')
|
|
292
|
+
if (key.success) {
|
|
293
|
+
const input = StorageDeleteFilesInputSchema.parse({ keys: [key.data] })
|
|
294
|
+
// 把 input 交给已初始化的 client.storage.files.deleteMany;仍须检查返回值 success。
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 能力映射
|
|
299
|
+
|
|
300
|
+
| Node storage 能力 | api-contract / serv 默认端点 | apiClient / 浏览器消费 | 边界 |
|
|
301
|
+
| -------------------------------------------------- | --------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
302
|
+
| `presign.getUrl` | `POST /storage/presigned-urls/download` | `storage.presignedUrls.createDownload` | 仅 S3;Local 返回 `hai:storage:018`,不返回虚拟 URL |
|
|
303
|
+
| `presign.putUrl` | `POST /storage/presigned-urls/upload` | `storage.presignedUrls.createUpload` 后直接 PUT 到 S3 | 仅内容类型与有效期;不是文件代理上传,也不提供上传大小/内容校验保证 |
|
|
304
|
+
| `dir.list` | `GET /storage/files` | `storage.files.list` | 共享前缀约束与分页预算;逐次授权 prefix |
|
|
305
|
+
| `file.head` | `POST /storage/files/metadata` | `storage.files.getMetadata` | 返回元数据,不能把 ETag 当内容 checksum |
|
|
306
|
+
| `file.delete` | `DELETE /storage/files` | `storage.files.delete` | 逐资源授权 |
|
|
307
|
+
| `file.deleteMany` | `POST /storage/files/delete-many` | `storage.files.deleteMany` | HTTP 总量上限 1000,返回可确认的删除进度 |
|
|
308
|
+
| `file.put/get/getStream/copy/exists`、`dir.delete` | 无默认端点 | 应用自行提供有界且鉴权的服务端流程 | 流、Range、AbortSignal、metadata、cacheControl、条件写等 Node 选项不能直接作为预签名 API 入参 |
|
|
309
|
+
| `checksum.generate/verify` | 无默认端点 | 应用选择明确的浏览器实现或服务端校验流程 | Node 无状态工具;不是上传接口自动校验 |
|
|
310
|
+
| `presign.publicUrl` | 无默认端点 | 应用按公开资源策略使用 | 仅配置公开 S3 地址时有 URL;不是鉴权链接,Local 为 null |
|
|
311
|
+
|
|
312
|
+
`FileUpload` / `ImageUpload` / `AvatarUpload` 的 `uploadHandler` 由应用实现。组件的 `maxSize`、`accept` 只负责前端选择体验,不能约束拿到签名后绕过组件的上传;服务端应在对象落库/回调或独立代理上传处验证实际大小、类型、内容和归属,再发布业务记录。不要把 Node `UploadOptions` 当成签名选项,也不要给组件编造不存在的框架上传端点。
|