Files
easyimg/server/utils/upload.js
T

176 lines
4.0 KiB
JavaScript

import { existsSync, mkdirSync, unlinkSync } from 'fs'
import { writeFile } from 'fs/promises'
import { join } from 'path'
import { v4 as uuidv4 } from 'uuid'
import { processImage, getImageMetadata } from './image.js'
import db from './db.js'
// 上传目录:生产环境使用 /app/uploads,开发环境使用项目根目录下的 uploads
const uploadsDir = process.env.NODE_ENV === 'production'
? '/app/uploads'
: join(process.cwd(), 'uploads')
// 确保上传目录存在
if (!existsSync(uploadsDir)) {
mkdirSync(uploadsDir, { recursive: true })
}
console.log('[Upload] 上传目录:', uploadsDir)
/**
* 解析 multipart/form-data 请求
*/
export async function parseFormData(event) {
const formData = await readMultipartFormData(event)
if (!formData || formData.length === 0) {
return { file: null }
}
// 查找文件字段
const fileField = formData.find(field => field.name === 'file' || field.name === 'image')
if (!fileField || !fileField.data) {
return { file: null }
}
return {
file: {
buffer: fileField.data,
originalFilename: fileField.filename || 'unknown',
mimetype: fileField.type,
size: fileField.data.length
}
}
}
/**
* 获取文件扩展名(内部使用)
*/
function getFileExtension(filename) {
const ext = filename.split('.').pop().toLowerCase()
return ext
}
/**
* 验证文件格式
*/
export function validateFormat(filename, allowedFormats) {
const ext = getFileExtension(filename)
return allowedFormats.map(f => f.toLowerCase()).includes(ext)
}
/**
* 验证文件大小
*/
export function validateSize(size, maxSize) {
return size <= maxSize
}
/**
* 保存图片文件
* @param {Buffer} buffer - 图片数据
* @param {Object} options - 配置选项
* @param {string} options.originalName - 原始文件名
* @param {boolean} options.convertToWebp - 是否转换为 WebP
* @param {number} options.webpQuality - WebP 质量
* @param {string} options.uploadedBy - 上传者
* @param {string} options.ip - 上传者 IP
* @param {boolean} options.isPublic - 是否为公共上传
*/
export async function saveUploadedImage(buffer, options) {
const {
originalName,
convertToWebp = false,
webpQuality = 80,
uploadedBy = '访客',
ip = '',
isPublic = true
} = options
const uuid = uuidv4()
const originalExt = getFileExtension(originalName)
let finalBuffer = buffer
let finalExt = originalExt
let isWebp = false
// 如果需要转换为 WebP
if (convertToWebp && originalExt !== 'gif') {
finalBuffer = await processImage(buffer, {
format: 'webp',
quality: webpQuality
})
finalExt = 'webp'
isWebp = true
}
// 获取图片信息
const imageInfo = await getImageMetadata(finalBuffer)
// 生成文件名和路径
const filename = `${uuid}.${finalExt}`
const filepath = join(uploadsDir, filename)
// 保存文件
await writeFile(filepath, finalBuffer)
// 保存到数据库
const imageRecord = {
_id: uuidv4(),
uuid,
originalName,
filename,
size: finalBuffer.length,
format: finalExt,
width: imageInfo.width,
height: imageInfo.height,
isWebp,
isPublic,
uploadedBy,
ip,
isDeleted: false,
uploadedAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
await db.images.insert(imageRecord)
// 返回图片信息(不包含敏感信息)
return {
uuid,
filename,
format: finalExt,
size: finalBuffer.length,
width: imageInfo.width,
height: imageInfo.height,
url: `/i/${uuid}.${finalExt}`
}
}
/**
* 删除图片文件
*/
export async function deleteImageFile(filename) {
const filepath = join(uploadsDir, filename)
if (existsSync(filepath)) {
unlinkSync(filepath)
return true
}
return false
}
/**
* 获取图片文件路径
*/
export function getImagePath(filename) {
return join(uploadsDir, filename)
}
/**
* 获取上传目录路径
*/
export function getUploadsDirPath() {
return uploadsDir
}