fsd 0.14.1 → 0.15.0

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +748 -2
  3. package/index.d.ts +1083 -71
  4. package/package.json +8 -3
package/README.md CHANGED
@@ -1,5 +1,751 @@
1
1
  # fsd
2
2
 
3
- General file system driver for Node.js
3
+ 通用文件系统驱动库 - Node.js 提供统一的文件存储抽象层。
4
4
 
5
- Document : https://github.com/liangxingchen/fsd
5
+ [![npm version](https://badge.fury.io/js/fsd.svg)](https://www.npmjs.com/package/fsd)
6
+
7
+ ## 概述
8
+
9
+ FSD (File System Driver) 是一个适配器模式的文件存储抽象层,提供统一的 API 接口,支持多种存储后端(本地磁盘、阿里云 OSS、阿里云 VOD)。
10
+
11
+ ### 核心特性
12
+
13
+ - **统一 API** - 一套 API,多种存储后端
14
+ - **适配器模式** - 轻松扩展新存储后端
15
+ - **Promise 风格** - 所有操作返回 Promise
16
+ - **流式支持** - 支持大文件的流式读写
17
+ - **类型安全** - 完整的 TypeScript 类型定义
18
+
19
+ ### 架构
20
+
21
+ ```
22
+ ┌─────────────────────────────────────┐
23
+ │ Your Application │
24
+ └──────────────┬──────────────────────┘
25
+
26
+
27
+ ┌─────────────────────────────────────┐
28
+ │ FSD Core │
29
+ │ (统一 API 抽象层) │
30
+ └──────┬──────────┬──────────┬────────┘
31
+ │ │ │
32
+ ▼ ▼ ▼
33
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
34
+ │ FS │ │ OSS │ │ VOD │
35
+ │ Adapter │ │ Adapter │ │ Adapter │
36
+ │(本地磁盘) │ │ (阿里云) │ │ (阿里云) │
37
+ └──────────┘ └──────────┘ └──────────┘
38
+ ```
39
+
40
+ ## 安装
41
+
42
+ ```bash
43
+ npm install fsd
44
+ ```
45
+
46
+ 还需要安装对应的适配器包:
47
+
48
+ ```bash
49
+ # 本地文件系统
50
+ npm install fsd-fs
51
+
52
+ # 阿里云 OSS
53
+ npm install fsd-oss
54
+
55
+ # 阿里云 VOD
56
+ npm install fsd-vod
57
+ ```
58
+
59
+ ## 快速开始
60
+
61
+ ### 基础用法
62
+
63
+ ```typescript
64
+ import FSD from 'fsd';
65
+ import FSAdapter from 'fsd-fs';
66
+
67
+ // 创建 FSD 实例
68
+ const fsd = FSD({
69
+ adapter: new FSAdapter({ root: '/uploads' })
70
+ });
71
+
72
+ // 创建文件对象
73
+ const file = fsd('/test.txt');
74
+
75
+ // 写入文件
76
+ await file.write('Hello, FSD!');
77
+
78
+ // 读取文件
79
+ const content = await file.read('utf8');
80
+ console.log(content); // 'Hello, FSD!'
81
+ ```
82
+
83
+ ### 目录操作
84
+
85
+ ```typescript
86
+ // 创建目录(注意:目录路径必须以 / 结尾)
87
+ const dir = fsd('/photos/');
88
+ await dir.mkdir(true); // 递归创建
89
+
90
+ // 列出目录内容
91
+ const files = await dir.readdir();
92
+ for (const f of files) {
93
+ console.log(f.name); // 文件名
94
+ console.log(f.path); // 完整路径
95
+ }
96
+ ```
97
+
98
+ ## 路径约定(重要)
99
+
100
+ FSD 对文件和目录路径有严格的约定,违反会抛出错误:
101
+
102
+ ### 文件路径
103
+
104
+ - **必须** 不以 `/` 结尾
105
+ - 自动补全前导 `/`
106
+
107
+ ```typescript
108
+ // ✅ 正确
109
+ const file = fsd('/test.txt'); // 正确
110
+ const file = fsd('test.txt'); // 自动补全为 '/test.txt'
111
+
112
+ // ❌ 错误
113
+ const file = fsd('/test.txt/'); // Error: file path should not ends with /
114
+ ```
115
+
116
+ ### 目录路径
117
+
118
+ - **必须** 以 `/` 结尾
119
+ - 自动补全前导 `/`
120
+
121
+ ```typescript
122
+ // ✅ 正确
123
+ const dir = fsd('/uploads/'); // 正确
124
+ const dir = fsd('uploads'); // 错误:必须以 / 结尾
125
+
126
+ // ❌ 错误
127
+ const dir = fsd('/uploads'); // Error: directory path should be ends with /
128
+ ```
129
+
130
+ ## 使用不同适配器
131
+
132
+ ### 本地文件系统 (fsd-fs)
133
+
134
+ ```typescript
135
+ import FSD from 'fsd';
136
+ import FSAdapter from 'fsd-fs';
137
+
138
+ const fsd = FSD({
139
+ adapter: new FSAdapter({
140
+ root: '/app/uploads', // 必需:存储根目录
141
+ mode: 0o644, // 可选:文件权限,默认 0o644
142
+ tmpdir: '/tmp/fsd-tmp', // 可选:临时目录(分段上传时使用)
143
+ urlPrefix: 'https://cdn.example.com' // 可选:URL 前缀
144
+ })
145
+ });
146
+
147
+ const file = fsd('/test.txt');
148
+ await file.write('Local file content');
149
+ ```
150
+
151
+ ### 阿里云 OSS (fsd-oss)
152
+
153
+ ```typescript
154
+ import FSD from 'fsd';
155
+ import OSSAdapter from 'fsd-oss';
156
+
157
+ const fsd = FSD({
158
+ adapter: new OSSAdapter({
159
+ accessKeyId: 'your-access-key-id', // 必需
160
+ accessKeySecret: 'your-access-key-secret', // 必需
161
+ region: 'oss-cn-hangzhou', // 必需:区域
162
+ bucket: 'your-bucket-name', // 可选:Bucket 名称
163
+ root: '/uploads', // 可选:存储根路径
164
+ urlPrefix: 'https://cdn.example.com', // 可选:URL 前缀
165
+ publicRead: true, // 可选:公共读
166
+ internal: false, // 可选:内网访问
167
+ secure: true // 可选:HTTPS
168
+ })
169
+ });
170
+
171
+ const file = fsd('/uploads/test.txt');
172
+ await file.write('OSS file content');
173
+ ```
174
+
175
+ **⚠️ 注意**: `endpoint` 选项已废弃,请使用 `region/[internal]/[secure]`。
176
+
177
+ ### 阿里云 VOD (fsd-vod)
178
+
179
+ ```typescript
180
+ import FSD from 'fsd';
181
+ import VODAdapter from 'fsd-vod';
182
+
183
+ const fsd = FSD({
184
+ adapter: new VODAdapter({
185
+ accessKeyId: 'your-access-key-id', // 必需
186
+ accessKeySecret: 'your-access-key-secret', // 必需
187
+ region: 'cn-shanghai', // 可选:默认 cn-shanghai
188
+ privateKey: 'your-rsa-private-key', // 必需:视频上传签名私钥
189
+ templateGroupId: 'your-template-group-id', // 可选:转码模板组
190
+ workflowId: 'your-workflow-id', // 可选:工作流 ID
191
+ urlPrefix: 'https://cdn.example.com' // 可选:URL 前缀
192
+ })
193
+ });
194
+
195
+ // VOD 必须先分配视频 ID
196
+ const videoId = await fsd.adapter.alloc({ name: 'video.mp4' });
197
+ const file = fsd(videoId);
198
+
199
+ await file.write(videoBuffer);
200
+ ```
201
+
202
+ ## API 参考
203
+
204
+ ### FSDFile 对象属性
205
+
206
+ ```typescript
207
+ const file = fsd('/path/to/document.pdf');
208
+
209
+ console.log(file.path); // '/path/to/document.pdf' - 完整路径
210
+ console.log(file.dir); // '/path/to/' - 目录路径
211
+ console.log(file.base); // 'document.pdf' - 文件名(含扩展名)
212
+ console.log(file.name); // 'document' - 文件名(不含扩展名)
213
+ console.log(file.ext); // '.pdf' - 扩展名
214
+ console.log(file.needEnsureDir); // false/true - 是否需要确保目录存在
215
+ ```
216
+
217
+ ### 文件操作
218
+
219
+ #### write(data) - 写入文件
220
+
221
+ ```typescript
222
+ // 写入字符串
223
+ await file.write('Hello, World!');
224
+
225
+ // 写入 Buffer
226
+ await file.write(Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]));
227
+
228
+ // 写入流
229
+ const readStream = fs.createReadStream('/local/file.txt');
230
+ await file.write(readStream);
231
+
232
+ // 创建空文件
233
+ await file.write();
234
+ ```
235
+
236
+ #### read([encoding] | [position, length] | [position, length, encoding]) - 读取文件
237
+
238
+ ```typescript
239
+ // 读取为 UTF-8 字符串
240
+ const text = await file.read('utf8');
241
+
242
+ // 读取为 Buffer
243
+ const buffer = await file.read();
244
+
245
+ // 读取指定范围(前 100 字节)
246
+ const partial = await file.read(0, 100);
247
+
248
+ // 读取指定范围并解码
249
+ const text = await file.read(100, 50, 'utf8');
250
+ ```
251
+
252
+ #### append(data) - 追加内容
253
+
254
+ ```typescript
255
+ // 追加字符串
256
+ await file.append('\nNew line');
257
+
258
+ // 追加 Buffer
259
+ await file.append(Buffer.from('additional data'));
260
+
261
+ // 追加流
262
+ const sourceStream = fs.createReadStream('/append.txt');
263
+ await file.append(sourceStream);
264
+ ```
265
+
266
+ ### 流式操作
267
+
268
+ #### createReadStream([options]) - 创建可读流
269
+
270
+ ```typescript
271
+ // 创建完整文件流
272
+ const readStream = await file.createReadStream();
273
+ readStream.pipe(process.stdout);
274
+
275
+ // 指定范围读取(从第 100 字节到 199 字节)
276
+ const partialStream = await file.createReadStream({
277
+ start: 100,
278
+ end: 199
279
+ });
280
+
281
+ // 流式下载到本地
282
+ const fs = require('fs');
283
+ const readStream = await file.createReadStream();
284
+ const writeStream = fs.createWriteStream('/local/download.txt');
285
+ readStream.pipe(writeStream);
286
+ await new Promise(resolve => writeStream.on('finish', resolve));
287
+ ```
288
+
289
+ #### createWriteStream([options]) - 创建可写流
290
+
291
+ ```typescript
292
+ // 创建可写流
293
+ const writeStream = await file.createWriteStream();
294
+ writeStream.write('Hello');
295
+ writeStream.end();
296
+
297
+ // 等待流完成(如果适配器支持 promise 属性)
298
+ if (writeStream.promise) {
299
+ await writeStream.promise;
300
+ }
301
+
302
+ // 从本地文件流式上传
303
+ const fs = require('fs');
304
+ const readStream = fs.createReadStream('/local/file.txt');
305
+ const writeStream = await file.createWriteStream();
306
+ readStream.pipe(writeStream);
307
+ if (writeStream.promise) {
308
+ await writeStream.promise;
309
+ }
310
+ ```
311
+
312
+ ### 目录操作
313
+
314
+ #### mkdir([recursive]) - 创建目录
315
+
316
+ ```typescript
317
+ // 创建单级目录(父目录必须存在)
318
+ await dir.mkdir();
319
+
320
+ // 递归创建多级目录
321
+ await dir.mkdir(true); // 等价于 mkdir -p
322
+ ```
323
+
324
+ #### readdir([recursion]) - 读取目录
325
+
326
+ ```typescript
327
+ // 列出直接子项
328
+ const files = await dir.readdir();
329
+ for (const f of files) {
330
+ console.log(f.name);
331
+ if (await f.isFile()) {
332
+ console.log(`File: ${f.path}`);
333
+ } else if (await f.isDirectory()) {
334
+ console.log(`Directory: ${f.path}`);
335
+ }
336
+ }
337
+
338
+ // 递归列出所有文件
339
+ const allFiles = await dir.readdir(true);
340
+ console.log(`Total: ${allFiles.length} files`);
341
+
342
+ // 使用 glob 模式筛选
343
+ const images = await dir.readdir('**/*.jpg');
344
+ for (const img of images) {
345
+ console.log(img.path);
346
+ }
347
+ ```
348
+
349
+ ### 文件信息
350
+
351
+ #### exists() - 判断文件/目录是否存在
352
+
353
+ ```typescript
354
+ if (await file.exists()) {
355
+ console.log('File exists');
356
+ } else {
357
+ await file.write('Create this file');
358
+ }
359
+ ```
360
+
361
+ #### isFile() / isDirectory() - 判断类型
362
+
363
+ ```typescript
364
+ if (await file.isFile()) {
365
+ console.log('This is a file');
366
+ console.log(`Size: ${await file.size()} bytes`);
367
+ }
368
+
369
+ if (await dir.isDirectory()) {
370
+ console.log('This is a directory');
371
+ }
372
+ ```
373
+
374
+ #### size() - 获取文件大小
375
+
376
+ ```typescript
377
+ const size = await file.size();
378
+ console.log(`File size: ${size} bytes`);
379
+
380
+ // 格式化显示
381
+ const sizeMB = size / (1024 * 1024);
382
+ console.log(`File size: ${sizeMB.toFixed(2)} MB`);
383
+ ```
384
+
385
+ #### lastModified() - 获取最后修改时间
386
+
387
+ ```typescript
388
+ const modified = await file.lastModified();
389
+ console.log('Last modified:', modified.toISOString());
390
+
391
+ // 判断文件是否过期(例如 1 天前)
392
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
393
+ if (modified < oneDayAgo) {
394
+ console.log('File is older than 1 day');
395
+ }
396
+ ```
397
+
398
+ ### 文件管理
399
+
400
+ #### copy(dest) - 复制文件
401
+
402
+ ```typescript
403
+ // 复制到同级目录
404
+ const copy = await file.copy('file_copy.txt');
405
+
406
+ // 复制到子目录
407
+ const copy = await file.copy('backup/file.txt');
408
+
409
+ // 复制到绝对路径
410
+ const copy = await file.copy('/other/path/file.txt');
411
+
412
+ // 复制目录(递归)
413
+ const dirCopy = await dir.copy('backup/');
414
+ ```
415
+
416
+ #### rename(dest) - 重命名/移动
417
+
418
+ ```typescript
419
+ // 重命名文件
420
+ await file.rename('new_name.txt');
421
+
422
+ // 移动文件到子目录
423
+ await file.rename('backup/file.txt');
424
+
425
+ // 移动文件到绝对路径
426
+ await file.rename('/other/path/file.txt');
427
+
428
+ // 重命名目录
429
+ await dir.rename('new_folder/');
430
+ ```
431
+
432
+ #### unlink() - 删除文件/目录
433
+
434
+ ```typescript
435
+ // 删除文件
436
+ await file.unlink();
437
+
438
+ // 删除目录及其所有内容
439
+ await dir.unlink();
440
+ ```
441
+
442
+ ### URL 生成
443
+
444
+ #### createUrl([options]) - 创建访问 URL
445
+
446
+ ```typescript
447
+ // 创建默认链接(1 小时后过期)
448
+ const url = await file.createUrl();
449
+ console.log('Download URL:', url);
450
+
451
+ // 创建 10 分钟后过期的链接
452
+ const url = await file.createUrl({ expires: 600 });
453
+
454
+ // 创建带下载提示的链接
455
+ const url = await file.createUrl({
456
+ expires: 3600,
457
+ response: {
458
+ 'content-type': 'application/pdf',
459
+ 'content-disposition': 'attachment; filename="document.pdf"'
460
+ }
461
+ });
462
+
463
+ // VOD 适配器:获取不同清晰度的视频播放地址
464
+ const hdUrl = await file.createUrl({ path: '/video/HD' });
465
+ const sdUrl = await file.createUrl({ path: '/video/SD' });
466
+ ```
467
+
468
+ ### 分片上传(大文件)
469
+
470
+ #### initMultipartUpload(partCount) - 初始化分片上传
471
+
472
+ ```typescript
473
+ // 初始化 3 个分片
474
+ const tasks = await file.initMultipartUpload(3);
475
+ console.log(tasks);
476
+ // ['task://uploadId123?1', 'task://uploadId123?2', 'task://uploadId123?3']
477
+ ```
478
+
479
+ #### writePart(partTask, data, [size]) - 上传分片
480
+
481
+ ```typescript
482
+ const tasks = await file.initMultipartUpload(3);
483
+ const parts = [];
484
+
485
+ // 上传第一个分片(Buffer)
486
+ const part1 = await file.writePart(tasks[0], Buffer.from('part1 data'));
487
+ parts.push(part1);
488
+
489
+ // 上传第二个分片(流)
490
+ const stream = fs.createReadStream('/tmp/part2');
491
+ const part2 = await file.writePart(tasks[1], stream, 1024);
492
+ parts.push(part2);
493
+
494
+ // 上传第三个分片(字符串)
495
+ const part3 = await file.writePart(tasks[2], 'part3 data');
496
+ parts.push(part3);
497
+ ```
498
+
499
+ #### completeMultipartUpload(parts) - 完成分片上传
500
+
501
+ ```typescript
502
+ const tasks = await file.initMultipartUpload(3);
503
+ const parts = [];
504
+
505
+ for (let i = 0; i < tasks.length; i++) {
506
+ const part = await file.writePart(tasks[i], getDataForPart(i));
507
+ parts.push(part);
508
+ }
509
+
510
+ // 完成上传(注意:parts 顺序可以与 tasks 顺序不同)
511
+ await file.completeMultipartUpload(parts);
512
+ ```
513
+
514
+ ### 完整示例:文件上传服务
515
+
516
+ ```typescript
517
+ import FSD from 'fsd';
518
+ import FSAdapter from 'fsd-fs';
519
+ import express from 'express';
520
+
521
+ const app = express();
522
+ const fsd = FSD({
523
+ adapter: new FSAdapter({ root: '/uploads' })
524
+ });
525
+
526
+ // 单文件上传
527
+ app.post('/upload', async (req, res) => {
528
+ const file = req.files.file;
529
+ const destFile = fsd(`/uploads/${file.name}`);
530
+
531
+ // 使用流式上传
532
+ await destFile.write(file.data);
533
+
534
+ // 生成下载链接(24 小时后过期)
535
+ const downloadUrl = await destFile.createUrl({ expires: 86400 });
536
+
537
+ res.json({
538
+ path: destFile.path,
539
+ size: await destFile.size(),
540
+ downloadUrl
541
+ });
542
+ });
543
+
544
+ // 分片上传大文件
545
+ app.post('/upload/multipart', async (req, res) => {
546
+ const { fileName, partCount } = req.body;
547
+ const file = fsd(`/uploads/${fileName}`);
548
+
549
+ // 初始化分片上传
550
+ const tasks = await file.initMultipartUpload(partCount);
551
+ res.json({ tasks });
552
+ });
553
+
554
+ app.post('/upload/part', async (req, res) => {
555
+ const { task } = req.body;
556
+ const file = fsd('/uploads/target.txt');
557
+
558
+ // 上传分片
559
+ const part = await file.writePart(task, req.body.data, req.body.size);
560
+ res.json({ part });
561
+ });
562
+
563
+ app.post('/upload/complete', async (req, res) => {
564
+ const { fileName, parts } = req.body;
565
+ const file = fsd(`/uploads/${fileName}`);
566
+
567
+ // 完成上传
568
+ await file.completeMultipartUpload(parts);
569
+
570
+ res.json({ success: true });
571
+ });
572
+
573
+ // 文件列表
574
+ app.get('/files/:path', async (req, res) => {
575
+ const dir = fsd(`/${req.params.path}/`);
576
+ const files = await dir.readdir(true); // 递归列出
577
+
578
+ const fileList = await Promise.all(files.map(async f => ({
579
+ name: f.name,
580
+ path: f.path,
581
+ size: await f.size(),
582
+ isFile: await f.isFile()
583
+ })));
584
+
585
+ res.json(fileList);
586
+ });
587
+
588
+ app.listen(3000);
589
+ ```
590
+
591
+ ### 使用 OSS 实现云存储
592
+
593
+ ```typescript
594
+ import FSD from 'fsd';
595
+ import OSSAdapter from 'fsd-oss';
596
+
597
+ const fsd = FSD({
598
+ adapter: new OSSAdapter({
599
+ accessKeyId: process.env.OSS_ACCESS_KEY_ID,
600
+ accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
601
+ region: 'oss-cn-hangzhou',
602
+ bucket: 'my-bucket',
603
+ root: '/uploads'
604
+ })
605
+ });
606
+
607
+ // 上传文件并获取访问 URL
608
+ async function uploadFile(filename, data) {
609
+ const file = fsd(`/uploads/${filename}`);
610
+ await file.write(data);
611
+
612
+ const url = await file.createUrl({ expires: 3600 });
613
+ return { path: file.path, url };
614
+ }
615
+
616
+ // 批量上传
617
+ async function uploadMultiple(files) {
618
+ const results = await Promise.all(
619
+ files.map(f => uploadFile(f.name, f.data))
620
+ );
621
+ return results;
622
+ }
623
+ ```
624
+
625
+ ### 自定义适配器
626
+
627
+ 你可以创建自己的存储适配器:
628
+
629
+ ```typescript
630
+ import { Adapter } from 'fsd';
631
+
632
+ class MyCustomAdapter extends Adapter<MyOptions> {
633
+ readonly instanceOfFSDAdapter = true;
634
+ readonly name = 'MyCustomAdapter';
635
+ readonly needEnsureDir = false;
636
+
637
+ constructor(options: MyOptions) {
638
+ super(options);
639
+ // 初始化你的存储系统
640
+ }
641
+
642
+ async write(path: string, data: any): Promise<void> {
643
+ // 实现写入逻辑
644
+ }
645
+
646
+ async read(path: string, options?: any): Promise<any> {
647
+ // 实现读取逻辑
648
+ }
649
+
650
+ // ... 实现其他必需方法
651
+ }
652
+
653
+ // 使用自定义适配器
654
+ const fsd = FSD({
655
+ adapter: new MyCustomAdapter({ /* options */ })
656
+ });
657
+ ```
658
+
659
+ ## TypeScript 支持
660
+
661
+ 完整的 TypeScript 类型定义已包含在包中:
662
+
663
+ ```typescript
664
+ import FSD, { FSDFile, Adapter, ReadStreamOptions } from 'fsd';
665
+
666
+ const fsd: ReturnType<typeof FSD> = FSD({
667
+ adapter: myAdapter
668
+ });
669
+
670
+ const file: FSDFile = fsd('/test.txt');
671
+ ```
672
+
673
+ ## 常见问题
674
+
675
+ ### Q: 如何切换存储后端?
676
+
677
+ A: 只需更换适配器,应用代码无需修改:
678
+
679
+ ```typescript
680
+ // 开发环境使用本地文件系统
681
+ const devFsd = FSD({
682
+ adapter: new FSAdapter({ root: '/uploads' })
683
+ });
684
+
685
+ // 生产环境使用 OSS
686
+ const prodFsd = FSD({
687
+ adapter: new OSSAdapter({
688
+ accessKeyId: process.env.OSS_ACCESS_KEY_ID,
689
+ accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
690
+ region: process.env.OSS_REGION,
691
+ bucket: process.env.OSS_BUCKET
692
+ })
693
+ });
694
+
695
+ // 使用方式完全相同
696
+ await devFsd('/test.txt').write('Hello');
697
+ await prodFsd('/test.txt').write('Hello');
698
+ ```
699
+
700
+ ### Q: 如何处理大文件?
701
+
702
+ A: 使用流式操作或分片上传:
703
+
704
+ ```typescript
705
+ // 流式上传(适合中等大小文件)
706
+ const stream = await file.createWriteStream();
707
+ sourceStream.pipe(stream);
708
+ await stream.promise;
709
+
710
+ // 分片上传(适合大文件)
711
+ const tasks = await file.initMultipartUpload(5);
712
+ // ... 上传分片
713
+ await file.completeMultipartUpload(parts);
714
+ ```
715
+
716
+ ### Q: 如何检查适配器类型?
717
+
718
+ A: 使用 `adapter.name` 属性:
719
+
720
+ ```typescript
721
+ console.log(fsd.adapter.name); // 'FSAdapter', 'OSSAdapter', 'VODAdapter'
722
+
723
+ if (fsd.adapter.name === 'OSSAdapter') {
724
+ console.log('Using OSS storage');
725
+ }
726
+ ```
727
+
728
+ ### Q: 目录操作失败怎么办?
729
+
730
+ A: 确保目录路径以 `/` 结尾,或使用递归创建:
731
+
732
+ ```typescript
733
+ // ❌ 错误
734
+ await fsd('/uploads').mkdir(); // Error
735
+
736
+ // ✅ 正确
737
+ await fsd('/uploads/').mkdir(); // Success
738
+
739
+ // ✅ 或递归创建
740
+ await fsd('/uploads/subdir/').mkdir(true); // Success
741
+ ```
742
+
743
+ ## 相关包
744
+
745
+ - [fsd-fs](https://www.npmjs.com/package/fsd-fs) - 本地文件系统适配器
746
+ - [fsd-oss](https://www.npmjs.com/package/fsd-oss) - 阿里云 OSS 适配器
747
+ - [fsd-vod](https://www.npmjs.com/package/fsd-vod) - 阿里云 VOD 适配器
748
+
749
+ ## License
750
+
751
+ MIT