5 changed files with 412 additions and 10 deletions
@ -0,0 +1,87 @@ |
|||||
|
import multer from "multer"; |
||||
|
import fs from "node:fs"; |
||||
|
import os from "node:os"; |
||||
|
import path from "node:path"; |
||||
|
import { callNodeListener, getRequestIP } from "h3"; |
||||
|
import { POST_MEDIA_PUBLIC_PREFIX } from "#server/constants/media"; |
||||
|
import { replaceMediaAssetFileFromTempUpload } from "#server/service/media"; |
||||
|
import { requireAdmin } from "#server/utils/admin-guard"; |
||||
|
import { R } from "#server/utils/response"; |
||||
|
import { assertUnderRateLimit } from "#server/utils/simple-rate-limit"; |
||||
|
|
||||
|
type MulterUploadedFile = { |
||||
|
originalname: string; |
||||
|
filename: string; |
||||
|
path: string; |
||||
|
mimetype: string; |
||||
|
size: number; |
||||
|
}; |
||||
|
|
||||
|
export default defineWrappedResponseHandler(async (event) => { |
||||
|
const admin = await requireAdmin(event); |
||||
|
const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown"; |
||||
|
assertUnderRateLimit(`admin-media-asset-reupload:${ip}`, 40, 60_000); |
||||
|
|
||||
|
const idRaw = getRouterParam(event, "id"); |
||||
|
const assetId = Number(idRaw); |
||||
|
if (!Number.isInteger(assetId) || assetId < 1) { |
||||
|
throw createError({ statusCode: 400, statusMessage: "无效的资源 id" }); |
||||
|
} |
||||
|
|
||||
|
const upload = multer({ |
||||
|
storage: multer.diskStorage({ |
||||
|
destination: (_req, _file, cb) => { |
||||
|
cb(null, os.tmpdir()); |
||||
|
}, |
||||
|
filename: (_req, file, cb) => { |
||||
|
const ext = path.extname(file.originalname).toLowerCase(); |
||||
|
const baseName = path.basename(file.originalname, ext).replace(/[^a-z0-9]/gi, "-"); |
||||
|
cb(null, `reupload-${Date.now()}-${Math.round(Math.random() * 1e9)}-${baseName}${ext}`); |
||||
|
}, |
||||
|
}), |
||||
|
limits: { fileSize: 10 * 1024 * 1024 }, |
||||
|
fileFilter: (_req, file, cb) => { |
||||
|
const allowed = ["image/png", "image/jpeg", "image/jpg", "image/webp"]; |
||||
|
if (allowed.includes(file.mimetype)) { |
||||
|
cb(null, true); |
||||
|
} else { |
||||
|
cb(new Error("只支持 PNG/JPG/WebP 格式图片")); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
await callNodeListener( |
||||
|
// @ts-expect-error multer 中间件类型
|
||||
|
upload.single("file"), |
||||
|
event.node.req, |
||||
|
event.node.res, |
||||
|
); |
||||
|
|
||||
|
// @ts-expect-error multer 挂载 file
|
||||
|
const file = event.node.req.file as MulterUploadedFile | undefined; |
||||
|
if (!file?.path) { |
||||
|
throw createError({ statusCode: 400, statusMessage: "请选择要上传的图片" }); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
const result = await replaceMediaAssetFileFromTempUpload({ |
||||
|
assetId, |
||||
|
actorUserId: admin.id, |
||||
|
actorIsAdmin: true, |
||||
|
tempInputPath: file.path, |
||||
|
}); |
||||
|
return R.success({ |
||||
|
url: `${POST_MEDIA_PUBLIC_PREFIX}${result.storageKey}`, |
||||
|
sizeBytes: result.sizeBytes, |
||||
|
}); |
||||
|
} catch (err) { |
||||
|
if (file.path && fs.existsSync(file.path)) { |
||||
|
try { |
||||
|
fs.unlinkSync(file.path); |
||||
|
} catch { |
||||
|
/* ignore */ |
||||
|
} |
||||
|
} |
||||
|
throw err; |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,83 @@ |
|||||
|
import multer from "multer"; |
||||
|
import fs from "node:fs"; |
||||
|
import os from "node:os"; |
||||
|
import path from "node:path"; |
||||
|
import { callNodeListener } from "h3"; |
||||
|
import { POST_MEDIA_PUBLIC_PREFIX } from "#server/constants/media"; |
||||
|
import { replaceMediaAssetFileFromTempUpload } from "#server/service/media"; |
||||
|
import { R } from "#server/utils/response"; |
||||
|
|
||||
|
type MulterUploadedFile = { |
||||
|
originalname: string; |
||||
|
filename: string; |
||||
|
path: string; |
||||
|
mimetype: string; |
||||
|
size: number; |
||||
|
}; |
||||
|
|
||||
|
export default defineWrappedResponseHandler(async (event) => { |
||||
|
const user = await event.context.auth.requireUser(); |
||||
|
const idRaw = getRouterParam(event, "id"); |
||||
|
const assetId = Number(idRaw); |
||||
|
if (!Number.isInteger(assetId) || assetId < 1) { |
||||
|
throw createError({ statusCode: 400, statusMessage: "无效的资源 id" }); |
||||
|
} |
||||
|
|
||||
|
const upload = multer({ |
||||
|
storage: multer.diskStorage({ |
||||
|
destination: (_req, _file, cb) => { |
||||
|
cb(null, os.tmpdir()); |
||||
|
}, |
||||
|
filename: (_req, file, cb) => { |
||||
|
const ext = path.extname(file.originalname).toLowerCase(); |
||||
|
const baseName = path.basename(file.originalname, ext).replace(/[^a-z0-9]/gi, "-"); |
||||
|
cb(null, `reupload-${Date.now()}-${Math.round(Math.random() * 1e9)}-${baseName}${ext}`); |
||||
|
}, |
||||
|
}), |
||||
|
limits: { fileSize: 10 * 1024 * 1024 }, |
||||
|
fileFilter: (_req, file, cb) => { |
||||
|
const allowed = ["image/png", "image/jpeg", "image/jpg", "image/webp"]; |
||||
|
if (allowed.includes(file.mimetype)) { |
||||
|
cb(null, true); |
||||
|
} else { |
||||
|
cb(new Error("只支持 PNG/JPG/WebP 格式图片")); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
await callNodeListener( |
||||
|
// @ts-expect-error multer 中间件类型
|
||||
|
upload.single("file"), |
||||
|
event.node.req, |
||||
|
event.node.res, |
||||
|
); |
||||
|
|
||||
|
// @ts-expect-error multer 挂载 file
|
||||
|
const file = event.node.req.file as MulterUploadedFile | undefined; |
||||
|
if (!file?.path) { |
||||
|
throw createError({ statusCode: 400, statusMessage: "请选择要上传的图片" }); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
const result = await replaceMediaAssetFileFromTempUpload({ |
||||
|
assetId, |
||||
|
actorUserId: user.id, |
||||
|
actorIsAdmin: false, |
||||
|
tempInputPath: file.path, |
||||
|
}); |
||||
|
return R.success({ |
||||
|
url: `${POST_MEDIA_PUBLIC_PREFIX}${result.storageKey}`, |
||||
|
sizeBytes: result.sizeBytes, |
||||
|
}); |
||||
|
} catch (err) { |
||||
|
if (file.path && fs.existsSync(file.path)) { |
||||
|
try { |
||||
|
fs.unlinkSync(file.path); |
||||
|
} catch { |
||||
|
/* ignore */ |
||||
|
} |
||||
|
} |
||||
|
throw err; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
Loading…
Reference in new issue