@kne/fastify-file-manager 3.0.7 → 3.0.9
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/libs/controllers/index.js +136 -53
- package/libs/schema/response.js +53 -0
- package/package.json +1 -1
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
const fp = require('fastify-plugin');
|
|
2
|
+
const { num, list, success, empty, fileRecordProperties, fileRecord, folderNode, folderTreeNode } = require('../schema/response');
|
|
2
3
|
|
|
3
4
|
const DEFAULT_FOLDER_TYPE = 'admin-file-system';
|
|
4
5
|
|
|
6
|
+
const TAG_FILE = '文件管理-文件';
|
|
7
|
+
const TAG_LIBRARY = '文件管理-文件库';
|
|
8
|
+
const TAG_FOLDER = '文件管理-文件夹';
|
|
9
|
+
|
|
10
|
+
const uploadedRecord = description =>
|
|
11
|
+
success(description, {
|
|
12
|
+
type: 'object',
|
|
13
|
+
additionalProperties: true,
|
|
14
|
+
properties: Object.assign({}, fileRecordProperties, {
|
|
15
|
+
folder: Object.assign({}, folderNode, { description: '自动挂载到文件系统生成的文件节点;同目录已挂载同一文件时不重复挂载,此时无该字段' })
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const TENANT_ID = '租户 ID;接口不校验,按原样传给文件夹服务用于隔离不同租户的树(通常由宿主鉴权钩子注入)';
|
|
20
|
+
|
|
5
21
|
const readMultipartField = (file, name) => {
|
|
6
22
|
const field = file?.fields?.[name];
|
|
7
23
|
if (!field) {
|
|
@@ -51,9 +67,10 @@ module.exports = fp(async (fastify, options) => {
|
|
|
51
67
|
{
|
|
52
68
|
onRequest: options.createAuthenticate('file:write'),
|
|
53
69
|
schema: {
|
|
70
|
+
tags: [TAG_FILE],
|
|
54
71
|
summary: '上传文件',
|
|
55
72
|
description:
|
|
56
|
-
'上传单个文件到服务器或配置的存储服务。保存成功后自动挂到文件系统(默认 type=admin-file-system,未传 path 时挂到根目录;传 path
|
|
73
|
+
'上传单个文件到服务器或配置的存储服务。保存成功后自动挂到文件系统(默认 type=admin-file-system,未传 path 时挂到根目录;传 path 则按路径创建并挂载)。请求为 multipart/form-data,文件字段名为 file;path、type、language 也可作为表单字段传入(query 优先)',
|
|
57
74
|
query: {
|
|
58
75
|
type: 'object',
|
|
59
76
|
properties: {
|
|
@@ -68,7 +85,8 @@ module.exports = fp(async (fastify, options) => {
|
|
|
68
85
|
},
|
|
69
86
|
language: { type: 'string', description: '语言' }
|
|
70
87
|
}
|
|
71
|
-
}
|
|
88
|
+
},
|
|
89
|
+
response: uploadedRecord('上传后的文件记录,挂载到文件系统时带 folder')
|
|
72
90
|
}
|
|
73
91
|
},
|
|
74
92
|
async request => {
|
|
@@ -91,13 +109,14 @@ module.exports = fp(async (fastify, options) => {
|
|
|
91
109
|
{
|
|
92
110
|
onRequest: options.createAuthenticate('file:write'),
|
|
93
111
|
schema: {
|
|
112
|
+
tags: [TAG_FILE],
|
|
94
113
|
summary: '上传URL文件',
|
|
95
114
|
description:
|
|
96
|
-
'
|
|
115
|
+
'服务端下载 url 指向的文件并保存到服务器或配置的存储服务,文件名优先取 url 的 filename 参数,其次响应头 Content-Disposition,再次 url 路径末段。保存成功后自动挂到文件系统(默认 type=admin-file-system,未传 path 时挂到根目录;传 path 则按路径创建并挂载)',
|
|
97
116
|
body: {
|
|
98
117
|
type: 'object',
|
|
99
118
|
properties: {
|
|
100
|
-
url: { type: 'string', description: '文件url' },
|
|
119
|
+
url: { type: 'string', description: '文件url,服务端会发起下载,下载失败返回错误' },
|
|
101
120
|
namespace: { type: 'string', description: '文件分类命名空间' },
|
|
102
121
|
path: {
|
|
103
122
|
type: 'string',
|
|
@@ -109,7 +128,8 @@ module.exports = fp(async (fastify, options) => {
|
|
|
109
128
|
},
|
|
110
129
|
language: { type: 'string', description: '语言' }
|
|
111
130
|
}
|
|
112
|
-
}
|
|
131
|
+
},
|
|
132
|
+
response: uploadedRecord('保存后的文件记录,挂载到文件系统时带 folder')
|
|
113
133
|
}
|
|
114
134
|
},
|
|
115
135
|
async request => {
|
|
@@ -128,14 +148,18 @@ module.exports = fp(async (fastify, options) => {
|
|
|
128
148
|
{
|
|
129
149
|
onRequest: options.createAuthenticate('file:read'),
|
|
130
150
|
schema: {
|
|
151
|
+
tags: [TAG_FILE],
|
|
131
152
|
summary: '获取文件url',
|
|
132
|
-
description: '
|
|
153
|
+
description: '获取文件访问地址:本地存储返回 {prefix}/file/{hash}{扩展名}?filename={文件名}(本地文件不存在返回 404),OSS 存储返回 ossAdapter.getFileLink 的结果',
|
|
133
154
|
params: {
|
|
134
155
|
type: 'object',
|
|
135
156
|
required: ['id'],
|
|
136
157
|
properties: {
|
|
137
|
-
id: { type: 'string', description: '文件id' }
|
|
158
|
+
id: { type: 'string', description: '文件id(uuid,兼容主键)' }
|
|
138
159
|
}
|
|
160
|
+
},
|
|
161
|
+
response: {
|
|
162
|
+
200: { description: '文件访问地址;本地存储时为 text/plain 字符串' }
|
|
139
163
|
}
|
|
140
164
|
}
|
|
141
165
|
},
|
|
@@ -150,21 +174,25 @@ module.exports = fp(async (fastify, options) => {
|
|
|
150
174
|
{
|
|
151
175
|
onRequest: options.createAuthenticate('file:read'),
|
|
152
176
|
schema: {
|
|
177
|
+
tags: [TAG_FILE],
|
|
153
178
|
summary: '获取文件信息',
|
|
154
|
-
description: '
|
|
179
|
+
description: '直接输出文件内容(非 JSON):OSS 存储下载后按原 MIME 类型返回;本地存储通过静态文件服务返回。attachment 为 true 时以附件形式下载',
|
|
155
180
|
query: {
|
|
156
181
|
type: 'object',
|
|
157
182
|
properties: {
|
|
158
|
-
attachment: { type: 'boolean', description: '
|
|
159
|
-
filename: { type: 'string', description: '
|
|
183
|
+
attachment: { type: 'boolean', description: '是否下载(Content-Disposition: attachment)' },
|
|
184
|
+
filename: { type: 'string', description: '下载文件名,默认原文件名' }
|
|
160
185
|
}
|
|
161
186
|
},
|
|
162
187
|
params: {
|
|
163
188
|
type: 'object',
|
|
164
189
|
required: ['id'],
|
|
165
190
|
properties: {
|
|
166
|
-
id: { type: 'string', description: '文件id' }
|
|
191
|
+
id: { type: 'string', description: '文件id(uuid,兼容主键)' }
|
|
167
192
|
}
|
|
193
|
+
},
|
|
194
|
+
response: {
|
|
195
|
+
200: { description: '文件二进制内容,Content-Type 为文件 MIME 类型' }
|
|
168
196
|
}
|
|
169
197
|
}
|
|
170
198
|
},
|
|
@@ -189,23 +217,36 @@ module.exports = fp(async (fastify, options) => {
|
|
|
189
217
|
{
|
|
190
218
|
onRequest: options.createAuthenticate('file:mange'),
|
|
191
219
|
schema: {
|
|
220
|
+
tags: [TAG_LIBRARY],
|
|
192
221
|
summary: '获取文件列表',
|
|
193
|
-
description: '
|
|
222
|
+
description: '分页查询文件库,按创建时间倒序',
|
|
194
223
|
body: {
|
|
195
224
|
type: 'object',
|
|
196
225
|
properties: {
|
|
197
|
-
perPage: { type: 'number', description: '
|
|
198
|
-
currentPage: { type: 'number', description: '
|
|
226
|
+
perPage: { type: 'number', description: '每页数量,默认 20' },
|
|
227
|
+
currentPage: { type: 'number', description: '当前页数,默认 1' },
|
|
199
228
|
filter: {
|
|
200
229
|
type: 'object',
|
|
230
|
+
description: '筛选条件',
|
|
201
231
|
properties: {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
id: { description: '按文件 uuid 精确匹配' },
|
|
233
|
+
namespace: { type: 'string', description: '文件分类命名空间,模糊匹配' },
|
|
234
|
+
size: { type: 'array', items: { type: 'number' }, description: '文件大小范围 [最小, 最大],单位 KB,0 或空表示不限' },
|
|
235
|
+
filename: { type: 'string', description: '文件名,模糊匹配' },
|
|
236
|
+
createdAt: { description: '创建时间范围,形如 { startTime, endTime },可只传其一' },
|
|
237
|
+
updatedAt: { description: '更新时间范围,形如 { startTime, endTime },可只传其一' }
|
|
205
238
|
}
|
|
206
239
|
}
|
|
207
240
|
}
|
|
208
|
-
}
|
|
241
|
+
},
|
|
242
|
+
response: success('文件分页列表', {
|
|
243
|
+
type: 'object',
|
|
244
|
+
additionalProperties: true,
|
|
245
|
+
properties: {
|
|
246
|
+
pageData: list('当前页文件列表', fileRecord),
|
|
247
|
+
totalCount: num('符合条件的文件总数')
|
|
248
|
+
}
|
|
249
|
+
})
|
|
209
250
|
}
|
|
210
251
|
},
|
|
211
252
|
async request => {
|
|
@@ -232,14 +273,16 @@ module.exports = fp(async (fastify, options) => {
|
|
|
232
273
|
{
|
|
233
274
|
onRequest: options.createAuthenticate('file:mange'),
|
|
234
275
|
schema: {
|
|
276
|
+
tags: [TAG_LIBRARY],
|
|
235
277
|
summary: '替换文件',
|
|
236
|
-
description: '
|
|
278
|
+
description: '用上传的新文件(multipart/form-data,字段名 file)覆盖指定文件记录的内容与文件名,文件 id 不变;未传 id 时新建文件记录。不会挂载到文件系统',
|
|
237
279
|
query: {
|
|
238
280
|
type: 'object',
|
|
239
281
|
properties: {
|
|
240
|
-
id: { type: 'string', description: '
|
|
282
|
+
id: { type: 'string', description: '要替换的文件id(uuid,兼容主键)' }
|
|
241
283
|
}
|
|
242
|
-
}
|
|
284
|
+
},
|
|
285
|
+
response: success('替换后的文件记录', fileRecord)
|
|
243
286
|
}
|
|
244
287
|
},
|
|
245
288
|
async request => {
|
|
@@ -256,15 +299,17 @@ module.exports = fp(async (fastify, options) => {
|
|
|
256
299
|
{
|
|
257
300
|
onRequest: options.createAuthenticate('file:mange'),
|
|
258
301
|
schema: {
|
|
302
|
+
tags: [TAG_LIBRARY],
|
|
259
303
|
summary: '重命名文件',
|
|
260
|
-
description: '
|
|
304
|
+
description: '修改文件记录的文件名;新文件名无扩展名时沿用原扩展名,非法字符替换为下划线',
|
|
261
305
|
body: {
|
|
262
306
|
type: 'object',
|
|
263
307
|
properties: {
|
|
264
|
-
id: { type: 'string', description: '文件id' },
|
|
308
|
+
id: { type: 'string', description: '文件id(uuid,兼容主键)' },
|
|
265
309
|
filename: { type: 'string', description: '新文件名' }
|
|
266
310
|
}
|
|
267
|
-
}
|
|
311
|
+
},
|
|
312
|
+
response: empty('重命名成功')
|
|
268
313
|
}
|
|
269
314
|
},
|
|
270
315
|
async request => {
|
|
@@ -278,15 +323,17 @@ module.exports = fp(async (fastify, options) => {
|
|
|
278
323
|
{
|
|
279
324
|
onRequest: options.createAuthenticate('file:mange'),
|
|
280
325
|
schema: {
|
|
326
|
+
tags: [TAG_LIBRARY],
|
|
281
327
|
summary: '删除文件',
|
|
282
|
-
description: '
|
|
328
|
+
description: '按 uuid 删除文件记录(不删除存储中的文件实体,也不删除文件夹树中引用它的节点)',
|
|
283
329
|
body: {
|
|
284
330
|
type: 'object',
|
|
285
331
|
required: ['ids'],
|
|
286
332
|
properties: {
|
|
287
|
-
ids: { type: 'array', items: { type: 'string' }, description: '文件id
|
|
333
|
+
ids: { type: 'array', items: { type: 'string' }, description: '文件id(uuid)列表' }
|
|
288
334
|
}
|
|
289
|
-
}
|
|
335
|
+
},
|
|
336
|
+
response: empty('删除成功')
|
|
290
337
|
}
|
|
291
338
|
},
|
|
292
339
|
async request => {
|
|
@@ -301,15 +348,19 @@ module.exports = fp(async (fastify, options) => {
|
|
|
301
348
|
{
|
|
302
349
|
onRequest: options.createAuthenticate('file:mange'),
|
|
303
350
|
schema: {
|
|
351
|
+
tags: [TAG_LIBRARY],
|
|
304
352
|
summary: '批量下载压缩包',
|
|
305
|
-
description: '将选中文件打包为 zip
|
|
353
|
+
description: '将选中文件打包为 zip 并下载;无效文件会被跳过,全部无效时返回错误',
|
|
306
354
|
body: {
|
|
307
355
|
type: 'object',
|
|
308
356
|
required: ['ids'],
|
|
309
357
|
properties: {
|
|
310
|
-
ids: { type: 'array', items: { type: 'string' }, description: '文件id
|
|
358
|
+
ids: { type: 'array', items: { type: 'string' }, description: '文件id(uuid)列表' },
|
|
311
359
|
filename: { type: 'string', description: '下载压缩包文件名,默认 files.zip' }
|
|
312
360
|
}
|
|
361
|
+
},
|
|
362
|
+
response: {
|
|
363
|
+
200: { description: 'zip 压缩包文件流,Content-Type 为 application/zip,以附件形式下载' }
|
|
313
364
|
}
|
|
314
365
|
}
|
|
315
366
|
},
|
|
@@ -355,16 +406,19 @@ module.exports = fp(async (fastify, options) => {
|
|
|
355
406
|
{
|
|
356
407
|
onRequest: folderOnRequest('read', 'query'),
|
|
357
408
|
schema: {
|
|
409
|
+
tags: [TAG_FOLDER],
|
|
358
410
|
summary: '获取文件夹树',
|
|
359
|
-
description: '获取指定 type 的文件树;kind=folder 时仅返回文件夹节点;未传 type 时使用 defaultFolderType',
|
|
411
|
+
description: '获取指定 type 的文件树;kind=folder 时仅返回文件夹节点;未传 type 时使用 defaultFolderType。文件节点会补充文件记录的 size、mimetype、filename 与时间',
|
|
360
412
|
query: {
|
|
361
413
|
type: 'object',
|
|
362
414
|
properties: {
|
|
363
415
|
type: { type: 'string', description: '业务域类型,默认 admin-file-system' },
|
|
364
416
|
language: { type: 'string', description: '语言' },
|
|
365
|
-
kind: { type: 'string', description: '传 folder
|
|
417
|
+
kind: { type: 'string', description: '传 folder(或 foldersOnly)时仅返回文件夹树' },
|
|
418
|
+
tenantId: { description: TENANT_ID }
|
|
366
419
|
}
|
|
367
|
-
}
|
|
420
|
+
},
|
|
421
|
+
response: success('根节点列表,子节点在 children 中', { type: 'array', items: folderTreeNode })
|
|
368
422
|
}
|
|
369
423
|
},
|
|
370
424
|
async request => {
|
|
@@ -377,8 +431,9 @@ module.exports = fp(async (fastify, options) => {
|
|
|
377
431
|
{
|
|
378
432
|
onRequest: folderOnRequest('read', 'body'),
|
|
379
433
|
schema: {
|
|
434
|
+
tags: [TAG_FOLDER],
|
|
380
435
|
summary: '分页获取目录子节点',
|
|
381
|
-
description: '按 parentId 分页列出直接子节点;文件夹优先,再按名称排序;未传 type 时使用 defaultFolderType',
|
|
436
|
+
description: '按 parentId 分页列出直接子节点;文件夹优先,再按名称排序;未传 type 时使用 defaultFolderType。perPage 最大 200',
|
|
382
437
|
body: {
|
|
383
438
|
type: 'object',
|
|
384
439
|
properties: {
|
|
@@ -389,7 +444,15 @@ module.exports = fp(async (fastify, options) => {
|
|
|
389
444
|
keyword: { type: 'string', description: '按名称模糊搜索' },
|
|
390
445
|
language: { type: 'string', description: '语言' }
|
|
391
446
|
}
|
|
392
|
-
}
|
|
447
|
+
},
|
|
448
|
+
response: success('子节点分页列表', {
|
|
449
|
+
type: 'object',
|
|
450
|
+
additionalProperties: true,
|
|
451
|
+
properties: {
|
|
452
|
+
pageData: list('当前页子节点列表', folderNode),
|
|
453
|
+
totalCount: num('直接子节点总数')
|
|
454
|
+
}
|
|
455
|
+
})
|
|
393
456
|
}
|
|
394
457
|
},
|
|
395
458
|
async request => {
|
|
@@ -417,18 +480,21 @@ module.exports = fp(async (fastify, options) => {
|
|
|
417
480
|
{
|
|
418
481
|
onRequest: getFolderAuthenticate('write'),
|
|
419
482
|
schema: {
|
|
483
|
+
tags: [TAG_FOLDER],
|
|
420
484
|
summary: '新建文件夹',
|
|
421
|
-
description: '在指定 type
|
|
485
|
+
description: '在指定 type 下创建文件夹节点;同一文件夹下重名返回错误',
|
|
422
486
|
body: {
|
|
423
487
|
type: 'object',
|
|
424
488
|
required: ['type', 'name'],
|
|
425
489
|
properties: {
|
|
426
490
|
type: { type: 'string', description: '业务域类型' },
|
|
427
491
|
name: { type: 'string', description: '文件夹名称' },
|
|
428
|
-
parentId: { type: 'string', description: '父文件夹 id' },
|
|
429
|
-
language: { type: 'string', description: '语言' }
|
|
492
|
+
parentId: { type: 'string', description: '父文件夹 id,不传为根目录' },
|
|
493
|
+
language: { type: 'string', description: '语言' },
|
|
494
|
+
tenantId: { description: TENANT_ID }
|
|
430
495
|
}
|
|
431
|
-
}
|
|
496
|
+
},
|
|
497
|
+
response: success('创建的文件夹节点', folderNode)
|
|
432
498
|
}
|
|
433
499
|
},
|
|
434
500
|
async request => {
|
|
@@ -441,8 +507,9 @@ module.exports = fp(async (fastify, options) => {
|
|
|
441
507
|
{
|
|
442
508
|
onRequest: getFolderAuthenticate('write'),
|
|
443
509
|
schema: {
|
|
510
|
+
tags: [TAG_FOLDER],
|
|
444
511
|
summary: '上传文件到文件夹',
|
|
445
|
-
description: '
|
|
512
|
+
description: '上传文件实体(multipart/form-data,字段名 file)并在 group 中创建文件节点。通过 path 定位目标目录(经 ensurePath 解析/创建;空字符串为根目录);同目录重名时文件名自动追加 [_时间戳]',
|
|
446
513
|
query: {
|
|
447
514
|
type: 'object',
|
|
448
515
|
required: ['type'],
|
|
@@ -452,7 +519,8 @@ module.exports = fp(async (fastify, options) => {
|
|
|
452
519
|
namespace: { type: 'string', description: '文件命名空间' },
|
|
453
520
|
language: { type: 'string', description: '语言' }
|
|
454
521
|
}
|
|
455
|
-
}
|
|
522
|
+
},
|
|
523
|
+
response: success('创建的文件节点,file 为上传的文件记录', Object.assign({}, folderNode, { properties: Object.assign({}, folderNode.properties, { file: fileRecord }) }))
|
|
456
524
|
}
|
|
457
525
|
},
|
|
458
526
|
async request => {
|
|
@@ -475,17 +543,20 @@ module.exports = fp(async (fastify, options) => {
|
|
|
475
543
|
{
|
|
476
544
|
onRequest: getFolderAuthenticate('delete'),
|
|
477
545
|
schema: {
|
|
546
|
+
tags: [TAG_FOLDER],
|
|
478
547
|
summary: '删除文件夹节点',
|
|
479
|
-
description: '递归删除节点及其子孙;文件节点同步删除 file-record',
|
|
548
|
+
description: '递归删除节点及其子孙;文件节点同步删除 file-record(linked 引用节点只删树节点,不删文件记录)',
|
|
480
549
|
body: {
|
|
481
550
|
type: 'object',
|
|
482
551
|
required: ['type', 'id'],
|
|
483
552
|
properties: {
|
|
484
553
|
type: { type: 'string', description: '业务域类型' },
|
|
485
554
|
id: { type: 'string', description: '节点 id' },
|
|
486
|
-
language: { type: 'string', description: '语言' }
|
|
555
|
+
language: { type: 'string', description: '语言' },
|
|
556
|
+
tenantId: { description: TENANT_ID }
|
|
487
557
|
}
|
|
488
|
-
}
|
|
558
|
+
},
|
|
559
|
+
response: empty('删除成功')
|
|
489
560
|
}
|
|
490
561
|
},
|
|
491
562
|
async request => {
|
|
@@ -498,8 +569,9 @@ module.exports = fp(async (fastify, options) => {
|
|
|
498
569
|
{
|
|
499
570
|
onRequest: getFolderAuthenticate('write'),
|
|
500
571
|
schema: {
|
|
572
|
+
tags: [TAG_FOLDER],
|
|
501
573
|
summary: '移动文件夹节点',
|
|
502
|
-
description: '将一个或多个文件/文件夹节点移动到目标文件夹(parentId
|
|
574
|
+
description: '将一个或多个文件/文件夹节点移动到目标文件夹(parentId 为空表示根目录);目标须为文件夹,目标下重名返回错误',
|
|
503
575
|
body: {
|
|
504
576
|
type: 'object',
|
|
505
577
|
required: ['type', 'ids'],
|
|
@@ -512,9 +584,11 @@ module.exports = fp(async (fastify, options) => {
|
|
|
512
584
|
description: '要移动的节点 id 列表'
|
|
513
585
|
},
|
|
514
586
|
parentId: { type: ['string', 'null'], description: '目标父文件夹 id,空为根目录' },
|
|
515
|
-
language: { type: 'string', description: '语言' }
|
|
587
|
+
language: { type: 'string', description: '语言' },
|
|
588
|
+
tenantId: { description: TENANT_ID }
|
|
516
589
|
}
|
|
517
|
-
}
|
|
590
|
+
},
|
|
591
|
+
response: success('移动后的节点列表', { type: 'array', items: folderNode })
|
|
518
592
|
}
|
|
519
593
|
},
|
|
520
594
|
async request => {
|
|
@@ -527,6 +601,7 @@ module.exports = fp(async (fastify, options) => {
|
|
|
527
601
|
{
|
|
528
602
|
onRequest: getFolderAuthenticate('write'),
|
|
529
603
|
schema: {
|
|
604
|
+
tags: [TAG_FOLDER],
|
|
530
605
|
summary: '复制文件夹节点',
|
|
531
606
|
description:
|
|
532
607
|
'将一个或多个文件/文件夹节点复制到目标文件夹(parentId 为空表示根目录);文件以 linked 节点挂载,文件夹递归复制',
|
|
@@ -542,9 +617,11 @@ module.exports = fp(async (fastify, options) => {
|
|
|
542
617
|
description: '要复制的节点 id 列表'
|
|
543
618
|
},
|
|
544
619
|
parentId: { type: ['string', 'null'], description: '目标父文件夹 id,空为根目录' },
|
|
545
|
-
language: { type: 'string', description: '语言' }
|
|
620
|
+
language: { type: 'string', description: '语言' },
|
|
621
|
+
tenantId: { description: TENANT_ID }
|
|
546
622
|
}
|
|
547
|
-
}
|
|
623
|
+
},
|
|
624
|
+
response: success('复制生成的顶层节点列表(与 ids 一一对应)', { type: 'array', items: folderNode })
|
|
548
625
|
}
|
|
549
626
|
},
|
|
550
627
|
async request => {
|
|
@@ -557,8 +634,9 @@ module.exports = fp(async (fastify, options) => {
|
|
|
557
634
|
{
|
|
558
635
|
onRequest: getFolderAuthenticate('write'),
|
|
559
636
|
schema: {
|
|
637
|
+
tags: [TAG_FOLDER],
|
|
560
638
|
summary: '重命名文件夹节点',
|
|
561
|
-
description: '重命名文件或文件夹节点;文件节点同步更新 file-record
|
|
639
|
+
description: '重命名文件或文件夹节点;文件节点同步更新 file-record 文件名(linked 引用节点只改树节点名称);同一文件夹下重名返回错误',
|
|
562
640
|
body: {
|
|
563
641
|
type: 'object',
|
|
564
642
|
required: ['type', 'id', 'name'],
|
|
@@ -566,9 +644,11 @@ module.exports = fp(async (fastify, options) => {
|
|
|
566
644
|
type: { type: 'string', description: '业务域类型' },
|
|
567
645
|
id: { type: 'string', description: '节点 id' },
|
|
568
646
|
name: { type: 'string', description: '新名称' },
|
|
569
|
-
language: { type: 'string', description: '语言' }
|
|
647
|
+
language: { type: 'string', description: '语言' },
|
|
648
|
+
tenantId: { description: TENANT_ID }
|
|
570
649
|
}
|
|
571
|
-
}
|
|
650
|
+
},
|
|
651
|
+
response: success('重命名后的节点', folderNode)
|
|
572
652
|
}
|
|
573
653
|
},
|
|
574
654
|
async request => {
|
|
@@ -582,6 +662,7 @@ module.exports = fp(async (fastify, options) => {
|
|
|
582
662
|
onRequest: getFolderAuthenticate('write'),
|
|
583
663
|
preHandler: withDefaultFolderType('body'),
|
|
584
664
|
schema: {
|
|
665
|
+
tags: [TAG_FOLDER],
|
|
585
666
|
summary: '将已有文件挂到文件夹树',
|
|
586
667
|
description:
|
|
587
668
|
'把文件库中已有文件以 linked 节点形式加入指定文件夹(parentId 为空表示根目录);删除该类节点不会删除文件库实体;未传 type 时使用 defaultFolderType',
|
|
@@ -597,9 +678,11 @@ module.exports = fp(async (fastify, options) => {
|
|
|
597
678
|
description: '文件库 file-record id 列表'
|
|
598
679
|
},
|
|
599
680
|
parentId: { type: ['string', 'null'], description: '目标父文件夹 id,空为根目录' },
|
|
600
|
-
language: { type: 'string', description: '语言' }
|
|
681
|
+
language: { type: 'string', description: '语言' },
|
|
682
|
+
tenantId: { description: TENANT_ID }
|
|
601
683
|
}
|
|
602
|
-
}
|
|
684
|
+
},
|
|
685
|
+
response: success('新挂载的文件节点列表;目标目录已挂载的文件会跳过,不出现在结果中', { type: 'array', items: folderNode })
|
|
603
686
|
}
|
|
604
687
|
},
|
|
605
688
|
async request => {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// 响应 schema 会参与 fast-json-stringify 序列化:对象一律 additionalProperties: true 以免裁掉未声明字段;
|
|
2
|
+
// 用 nullable 而非类型数组(类型数组遇到不匹配的值会直接 500),类型不确定的字段(如 id、日期、JSON 列)只写说明不声明类型
|
|
3
|
+
const str = description => ({ type: 'string', nullable: true, description });
|
|
4
|
+
const num = description => ({ type: 'number', nullable: true, description });
|
|
5
|
+
const any = description => ({ description });
|
|
6
|
+
const list = (description, items) => ({ type: 'array', nullable: true, description, items });
|
|
7
|
+
const obj = (description, properties = {}) => ({ type: 'object', nullable: true, description, additionalProperties: true, properties });
|
|
8
|
+
|
|
9
|
+
const success = (description, schema) => ({
|
|
10
|
+
200: Object.assign({}, schema || { type: 'object' }, { description })
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const empty = (description = '操作成功,返回空对象') => success(description);
|
|
14
|
+
|
|
15
|
+
const fileRecordProperties = {
|
|
16
|
+
id: any('文件 ID(即 uuid),用于 file-url / file-id 等接口'),
|
|
17
|
+
uuid: any('文件 uuid'),
|
|
18
|
+
filename: str('文件名(已替换非法字符)'),
|
|
19
|
+
hash: str('文件内容 md5,存储文件名为 {hash}{扩展名}'),
|
|
20
|
+
namespace: str('文件分类命名空间'),
|
|
21
|
+
size: num('文件大小(字节)'),
|
|
22
|
+
encoding: str('上传编码'),
|
|
23
|
+
mimetype: str('文件 MIME 类型'),
|
|
24
|
+
storageType: str('存储类型:local 本地文件系统,oss 远程 OSS 存储'),
|
|
25
|
+
options: any('扩展字段(JSON)'),
|
|
26
|
+
createdAt: any('创建时间'),
|
|
27
|
+
updatedAt: any('更新时间'),
|
|
28
|
+
deletedAt: any('删除时间,未删除为 null')
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const fileRecord = obj('文件记录', fileRecordProperties);
|
|
32
|
+
|
|
33
|
+
const folderNodeProperties = {
|
|
34
|
+
id: any('节点 ID'),
|
|
35
|
+
tenantId: any('租户 ID'),
|
|
36
|
+
type: str('业务域类型'),
|
|
37
|
+
code: str('节点编码'),
|
|
38
|
+
name: str('节点名称(文件节点为显示文件名)'),
|
|
39
|
+
language: str('语言'),
|
|
40
|
+
description: str('描述'),
|
|
41
|
+
index: num('排序'),
|
|
42
|
+
parentId: any('父文件夹节点 ID,根目录为 null'),
|
|
43
|
+
options: any('节点扩展信息(JSON):kind 节点类型 file/folder;文件节点含 fileId(文件 uuid)、size、mimetype、filename,linked 为 true 表示引用文件库已有文件(删除节点不删文件实体)'),
|
|
44
|
+
createdAt: any('创建时间(文件节点在树/列表中取文件记录的时间)'),
|
|
45
|
+
updatedAt: any('更新时间(文件节点在树/列表中取文件记录的时间)'),
|
|
46
|
+
deletedAt: any('删除时间,未删除为 null')
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const folderNode = obj('文件夹树节点', folderNodeProperties);
|
|
50
|
+
|
|
51
|
+
const folderTreeNode = obj('文件夹树节点', Object.assign({}, folderNodeProperties, { children: list('子节点列表,结构同本节点', obj('子节点')) }));
|
|
52
|
+
|
|
53
|
+
module.exports = { str, num, any, list, obj, success, empty, fileRecordProperties, fileRecord, folderNodeProperties, folderNode, folderTreeNode };
|