## 主要變更 ### 新增全域檔案傳輸模組 (src/transfer/) - constants.ts: 定義傳輸常數(10MB 門檻、5MB chunk 大小) - types.ts: 傳輸類型定義 - uploadManager.ts: 後端上傳管理器(支援直傳與 chunk) - downloadManager.ts: 後端下載管理器(支援直傳與 chunk) - archiveManager.ts: 封裝管理器(僅允許 .tar) - index.ts: 統一匯出 ### 新增 API 端點 - uploadChunk.tsx: Chunk 上傳 API - downloadChunk.tsx: Chunk 下載 API ### 前端更新 - public/script.js: 整合智慧傳輸策略(≤10MB 直傳,>10MB chunk) - public/transfer.js: 前端傳輸管理模組 ### 轉換器更新 - mineru.ts: 改用 .tar 格式(不壓縮),禁止 .tar.gz - download.tsx: 使用統一的封裝管理器 ### 測試 - tests/transfer/: 完整傳輸機制測試套件 - tests/converters/mineru.test.ts: 更新以符合 .tar 規範 ### 文件 - README.md: 新增檔案傳輸機制說明 ## 設計原則 - 檔案 ≤10MB:直接傳輸 - 檔案 >10MB:使用 5MB chunks 分段傳輸 - 多檔輸出:僅允許 .tar 封裝(禁止 .tar.gz/.zip) - 引擎層不感知 chunk(僅傳輸層處理)
57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
import path from "node:path";
|
||
import { Elysia } from "elysia";
|
||
import sanitize from "sanitize-filename";
|
||
import { outputDir } from "..";
|
||
import db from "../db/db";
|
||
import { WEBROOT } from "../helpers/env";
|
||
import { userService } from "./user";
|
||
import { createJobArchive, shouldUseChunkedDownload } from "../transfer";
|
||
|
||
export const download = new Elysia()
|
||
.use(userService)
|
||
.get(
|
||
"/download/:userId/:jobId/:fileName",
|
||
async ({ params, redirect, user }) => {
|
||
const userId = user.id;
|
||
const job = await db
|
||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||
.get(user.id, params.jobId);
|
||
|
||
if (!job) {
|
||
return redirect(`${WEBROOT}/results`, 302);
|
||
}
|
||
// parse from URL encoded string
|
||
const jobId = decodeURIComponent(params.jobId);
|
||
const fileName = sanitize(decodeURIComponent(params.fileName));
|
||
|
||
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
|
||
return Bun.file(filePath);
|
||
},
|
||
{
|
||
auth: true,
|
||
},
|
||
)
|
||
.get(
|
||
"/archive/:jobId",
|
||
async ({ params, redirect, user }) => {
|
||
const userId = user.id;
|
||
const job = await db
|
||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||
.get(user.id, params.jobId);
|
||
|
||
if (!job) {
|
||
return redirect(`${WEBROOT}/results`, 302);
|
||
}
|
||
|
||
const jobId = decodeURIComponent(params.jobId);
|
||
const outputPath = `${outputDir}${userId}/${jobId}`;
|
||
|
||
// 使用統一的封裝管理器建立 .tar(不壓縮)
|
||
const outputTar = await createJobArchive(outputPath, jobId);
|
||
|
||
return Bun.file(outputTar);
|
||
},
|
||
{
|
||
auth: true,
|
||
},
|
||
);
|