Files
O96u 469d3be735 release: v1.2.0 — unified images/ storage and legacy migration
Consolidate local images under data/images/, add docker migrate CLI,
startup index sync with orphan cleanup, and remove upload folder UI/API.
2026-08-28 17:18:03 +08:00

103 lines
2.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readMultipartFormData } from 'h3'
import { verifyUploadTokenValue } from '../../utils/access'
import { checkUploadRateLimit } from '../../utils/rate-limit'
import {
detectMimeFromSignature,
isAllowedMimeType
} from '../../utils/file-signature'
import {
mimeMatchesDeclared,
processSingleImageUpload
} from '../../utils/process-image-upload'
import { getApiUploadToken } from '../../utils/env'
/** EasyImages 2.0 兼容上传(Twikoo IMAGE_CDN=easyimage) */
export default defineEventHandler(async (event) => {
setHeader(event, 'Content-Type', 'application/json; charset=utf-8')
const config = useRuntimeConfig(event)
const devBypass = import.meta.dev && config.devBypassAccess
const formData = await readMultipartFormData(event)
if (!formData?.length) {
return easyImageError(400, '未收到上传数据')
}
const tokenField = formData.find(part => part.name === 'token')
const token = tokenField?.data?.length
? new TextDecoder().decode(tokenField.data).trim()
: ''
if (!devBypass) {
if (!getApiUploadToken(event)) {
return easyImageError(500, '服务端未配置 API_UPLOAD_TOKEN')
}
if (!verifyUploadTokenValue(event, token)) {
return easyImageError(401, 'token 错误或无效')
}
checkUploadRateLimit(event, token)
}
const imagePart = formData.find(
part => part.name === 'image' && part.data?.length
)
if (!imagePart?.data?.length) {
return easyImageError(400, '未收到图片文件')
}
const bytes = new Uint8Array(imagePart.data)
const declaredMime = imagePart.type?.toLowerCase() ?? ''
const detectedMime = detectMimeFromSignature(bytes)
if (detectedMime && !mimeMatchesDeclared(declaredMime, detectedMime)) {
return easyImageError(415, '文件类型与内容不匹配')
}
if (declaredMime && isAllowedMimeType(declaredMime) && !detectedMime) {
return easyImageError(415, '无法识别的图片格式')
}
// Twikoo 走 token 上传,记入 activity log source=api
const result = await processSingleImageUpload(event, {
bytes,
filename: imagePart.filename,
source: 'api'
})
if ('error' in result) {
const code = easyImageCodeFromError(result.error.error.code)
return easyImageError(code, result.error.error.message)
}
const { item } = result
return {
result: 'success',
code: 200,
url: item.url,
srcName: item.originalName,
thumb: item.url,
del: ''
}
})
function easyImageError(code: number, message: string) {
return {
result: 'failed',
code,
message
}
}
function easyImageCodeFromError(code: string): number {
switch (code) {
case 'FILE_TOO_LARGE':
return 413
case 'UNSUPPORTED_FILE_TYPE':
case 'INVALID_FILE_SIGNATURE':
return 415
case 'UPLOAD_FAILED':
return 500
default:
return 400
}
}