Files
easyimg/server/api/upload/private.post.js
T

166 lines
4.6 KiB
JavaScript
Raw 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 db from '../../utils/db.js'
import { processImage, getImageMetadata, saveUploadedFile } from '../../utils/image.js'
import { parseFormData } from '../../utils/upload.js'
import { v4 as uuidv4 } from 'uuid'
import { sendUploadNotification } from '../../utils/notification.js'
export default defineEventHandler(async (event) => {
const clientIP = getRequestIP(event, { xForwardedFor: true }) || 'unknown'
try {
// 获取 ApiKey(从 header 或 query)
const apiKey = getHeader(event, 'x-api-key') || getQuery(event).apiKey
if (!apiKey) {
throw createError({
statusCode: 401,
message: '缺少 API Key'
})
}
// 验证 ApiKey
const keyDoc = await db.apikeys.findOne({ key: apiKey, enabled: true })
if (!keyDoc) {
throw createError({
statusCode: 401,
message: 'API Key 无效或已禁用'
})
}
// 获取私有 API 配置
const configDoc = await db.settings.findOne({ key: 'privateApiConfig' })
const config = configDoc?.value || {}
// 解析表单数据
const { file } = await parseFormData(event)
if (!file) {
throw createError({
statusCode: 400,
message: '请选择要上传的图片'
})
}
// 私有 API 支持所有格式,只检查是否为图片
const fileExt = file.originalFilename?.split('.').pop()?.toLowerCase() || ''
// 检查文件大小
const maxFileSize = config.maxFileSize || 100 * 1024 * 1024
if (file.size > maxFileSize) {
throw createError({
statusCode: 400,
message: `文件大小超过限制 (最大 ${Math.round(maxFileSize / 1024 / 1024)}MB)`
})
}
// 生成 UUID
const imageUuid = uuidv4()
// 处理图片(可选转换为 WebP)
let processedBuffer = file.buffer
let finalFormat = fileExt
let isWebp = false
if (config.convertToWebp && fileExt !== 'gif') {
processedBuffer = await processImage(file.buffer, {
format: 'webp',
quality: config.webpQuality || 80
})
finalFormat = 'webp'
isWebp = true
}
// 获取图片元数据
const metadata = await getImageMetadata(processedBuffer)
// 保存文件
const filename = `${imageUuid}.${finalFormat}`
await saveUploadedFile(processedBuffer, filename)
// 获取用户信息(通过 ApiKey 关联)
const uploadedBy = keyDoc.name || 'API用户'
// 保存到数据库
const imageDoc = {
_id: uuidv4(),
uuid: imageUuid,
originalName: file.originalFilename,
filename: filename,
format: finalFormat,
size: processedBuffer.length,
width: metadata.width || 0,
height: metadata.height || 0,
isWebp: isWebp,
isDeleted: false,
uploadedBy: uploadedBy,
uploadedByType: 'private',
apiKeyId: keyDoc._id,
ip: clientIP,
uploadedAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
await db.images.insert(imageDoc)
// 获取站点 URL 配置,用于生成完整图片链接
const appSettingsDoc = await db.settings.findOne({ key: 'appSettings' })
let siteUrl = appSettingsDoc?.value?.siteUrl || ''
// 如果没有配置站点 URL,使用请求的 Host 作为兜底
if (!siteUrl) {
const protocol = getHeader(event, 'x-forwarded-proto') || 'http'
const host = getHeader(event, 'host') || 'localhost'
siteUrl = `${protocol}://${host}`
}
// 移除末尾斜杠,确保 URL 拼接正确
siteUrl = siteUrl.replace(/\/+$/, '')
const fullImageUrl = `${siteUrl}/i/${imageUuid}.${finalFormat}`
// 发送上传通知(异步,不阻塞响应)
sendUploadNotification(
{
id: imageDoc._id,
filename: filename,
format: finalFormat,
size: processedBuffer.length,
url: fullImageUrl
},
{
name: uploadedBy,
type: 'private',
ip: clientIP
}
).catch(err => {
console.error('[Upload] 发送上传通知失败:', err)
})
// 返回结果
return {
success: true,
message: '上传成功',
data: {
id: imageDoc._id,
uuid: imageUuid,
filename: filename,
format: finalFormat,
size: processedBuffer.length,
width: metadata.width || 0,
height: metadata.height || 0,
url: `/i/${imageUuid}.${finalFormat}`,
uploadedAt: imageDoc.uploadedAt
}
}
} catch (error) {
if (error.statusCode) {
throw error
}
console.error('[Upload] 私有上传失败:', error)
throw createError({
statusCode: 500,
message: '上传失败,请稍后重试'
})
}
})