operation-evidence.post.ts 1.8 KB
const MAX_BYTES = 50 * 1024 * 1024
const ALLOWED_TYPES = new Set([
  'image/jpeg',
  'image/png',
  'image/webp',
  'image/heic',
  'video/mp4',
  'video/quicktime',
  'video/webm'
])

function pad2(value: number) {
  return String(value).padStart(2, '0')
}

function randomId() {
  return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`
}

function extFromFile(fileName?: string, contentType?: string) {
  if (typeof fileName === 'string') {
    const dot = fileName.lastIndexOf('.')
    if (dot >= 0 && dot < fileName.length - 1) {
      return fileName.slice(dot).toLowerCase()
    }
  }

  return contentType?.startsWith('video/') ? '.mp4' : '.jpg'
}

export default eventHandler(async (event) => {
  const files = await readMultipartFormData(event)
  const file = files?.find(item => item.name === 'file' && item.data)

  if (!file?.data || file.data.length === 0) {
    return {
      success: false,
      errorCode: 'VALIDATION_ERROR',
      message: '请上传图片或视频附件。'
    }
  }

  if (!file.type || !ALLOWED_TYPES.has(file.type)) {
    return {
      success: false,
      errorCode: 'UNSUPPORTED_FILE_TYPE',
      message: '附件格式仅支持图片或视频。'
    }
  }

  if (file.data.length > MAX_BYTES) {
    return {
      success: false,
      errorCode: 'FILE_TOO_LARGE',
      message: '附件大小不能超过 50MB。'
    }
  }

  const now = new Date()
  const relativePath = [
    'operation-evidence',
    String(now.getUTCFullYear()),
    pad2(now.getUTCMonth() + 1),
    pad2(now.getUTCDate()),
    `${randomId()}${extFromFile(file.filename, file.type)}`
  ].join('/')

  return {
    success: true,
    errorCode: null,
    message: '附件上传成功。',
    relativePath,
    contentType: file.type,
    fileName: file.filename || '',
    size: file.data.length
  }
})