feat(files): 文件管理接入 Alist 存储(浏览、增删改、移动复制、上传下载、回收站)
- 文件管理根多出 st:<存储 id>(对照 MP 存储切换),Alist 媒体库目录照常出现在媒体库组 - 列目录一次 list,refresh 只在用户刷新时带;连不上返回 503 不当成空目录 - 新建目录 / 改名(同名不覆盖)/ 删除进 Alist 上的 .umt-trash 并可还原 - 存储内移动复制走 Alist API,本地 ↔ Alist 走整理同一套执行与账本 - 新增 PUT /files/upload(同名不覆盖,Alist 经服务端转传);下载 / 预览流式转发直链 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
c2bf8a071a
commit
9d95076a0e
24 files changed
+901
-72
No files matched your search
@@ -266,6 +266,14 @@ export const organizeApi = {
|
||||
body: files.FilesProbeRequest,
|
||||
response: files.FilesProbeResult,
|
||||
}),
|
||||
upload: ep({
|
||||
method: "put",
|
||||
path: "/files/upload",
|
||||
scope: "organize.execute",
|
||||
summary: "上传文件到目录(请求体为文件字节;同名不覆盖;Alist 根经服务端转传)",
|
||||
query: files.FilesUploadQuery,
|
||||
response: files.FilesUploadResult,
|
||||
}),
|
||||
download: ep({
|
||||
method: "get",
|
||||
path: "/files/download",
|
||||
|
||||
@@ -13,8 +13,10 @@ import { ScrapeMode, ScrapeRecord } from "./scrape.ts";
|
||||
// 移动/复制/整理/删除都是两段式:dryRun=true 取得 planId 与前后路径清单,执行时回传 planId;
|
||||
// 源文件 size/mtime 变化或 planId 过期/与请求不符 → 409 plan_stale。
|
||||
|
||||
/** `lib:<library_root.id>` 或 `dl:<downloader_instance.id>:<路径映射序号>` */
|
||||
export const FileRootId = z.string().regex(/^(lib:[0-9a-f-]{36}|dl:[0-9a-f-]{36}:\d{1,3})$/i, { message: "非法根 id" });
|
||||
/** `lib:<library_root.id>`、`dl:<downloader_instance.id>:<路径映射序号>` 或 `st:<storage_instance.id>`(Alist 存储) */
|
||||
export const FileRootId = z
|
||||
.string()
|
||||
.regex(/^(lib:[0-9a-f-]{36}|dl:[0-9a-f-]{36}:\d{1,3}|st:[0-9a-f-]{36})$/i, { message: "非法根 id" });
|
||||
export type FileRootId = z.infer<typeof FileRootId>;
|
||||
|
||||
/** 根内相对路径:不以 / 开头,不含 `..` 段与 NUL;服务端仍会 realpath 复核 */
|
||||
@@ -41,7 +43,7 @@ export const IdempotencyKey = z.string().min(8).max(200);
|
||||
|
||||
// ── 根 ──
|
||||
|
||||
export const FileRootKind = z.enum(["library", "download"]);
|
||||
export const FileRootKind = z.enum(["library", "download", "storage"]);
|
||||
export type FileRootKind = z.infer<typeof FileRootKind>;
|
||||
|
||||
export const FileRoot = z.object({
|
||||
@@ -59,6 +61,8 @@ export const FileRoot = z.object({
|
||||
/** 健康探测失败原因(离线时) */
|
||||
healthReason: z.string().nullable(),
|
||||
usage: z.object({ totalBytes: NonNegInt, freeBytes: NonNegInt }).nullable(),
|
||||
/** 根所在存储:本机为 null;Alist 根给实例 id 与名称(界面显示「存储名:/路径」) */
|
||||
storage: z.object({ id: z.string(), name: z.string(), path: z.string() }).nullable().default(null),
|
||||
/** G6:下载目录下待识别视频数(与列表"待识别"筛选同口径,递归,缓存 5 分钟);库根与离线根为 null */
|
||||
pendingCount: NonNegInt.nullable(),
|
||||
/** G6:递归超过扫描上限时为 true,pendingCount 是下限 */
|
||||
@@ -182,6 +186,8 @@ export const FilesListQuery = z.object({
|
||||
.optional(),
|
||||
/** 默认 false:隐藏点文件、.uhd-partial、.umt-trash、哨兵 */
|
||||
hidden: boolQuery.default(false),
|
||||
/** Alist 根:让 Alist 刷新目录缓存(穿透到网盘接口;只在用户点「刷新」时带) */
|
||||
refresh: boolQuery.default(false),
|
||||
});
|
||||
export type FilesListQuery = z.infer<typeof FilesListQuery>;
|
||||
|
||||
@@ -797,6 +803,20 @@ export type FilesProbeResult = z.infer<typeof FilesProbeResult>;
|
||||
|
||||
// 下载(W5-A2):GET /files/download 直接返回文件流(非 JSON 信封);仅管理员会话;
|
||||
// 仅 nfo/字幕/图片且 ≤ FILES_DOWNLOAD_MAX_BYTES(默认 20 MiB);inline=true 仅对图片生效(预览)。
|
||||
/**
|
||||
* 上传(PUT /files/upload,请求体是文件原始字节):放进 {root, path} 目录,文件名 name;同名已存在 409(不覆盖)。
|
||||
* Alist 根经服务端转传(先写本机临时文件再上传到 Alist,同 MP);本地根直接写临时文件后不覆盖改名。
|
||||
*/
|
||||
export const FilesUploadQuery = z.object({
|
||||
root: FileRootId,
|
||||
path: FileRelPath,
|
||||
name: FileName,
|
||||
idempotencyKey: IdempotencyKey,
|
||||
});
|
||||
export type FilesUploadQuery = z.infer<typeof FilesUploadQuery>;
|
||||
export const FilesUploadResult = z.object({ path: FileRelPath, size: NonNegInt });
|
||||
export type FilesUploadResult = z.infer<typeof FilesUploadResult>;
|
||||
|
||||
export const FilesDownloadQuery = z.object({ root: FileRootId, path: FileRelPath, inline: boolQuery.default(false) });
|
||||
export type FilesDownloadQuery = z.infer<typeof FilesDownloadQuery>;
|
||||
/** OpenAPI 描述用:实际响应为二进制流 */
|
||||
|
||||
Reference in new issue
Block a user