// OSS 直传工具 - 配合后端 /common/oss/sign // 流程: 1) GET /common/oss/sign?dir=xxx 拿签名 // 2) POST 到 OSS bucket, formData 携带 policy/signature/key/accessKeyId // 3) 拿回 OSS URL 写回表单 import request from '@/utils/request' /** * 获取 OSS 上传签名 * @param {string} dir 上传目录 (如 'ry8080/invitation/') * @returns {Promise<{host, dir, accessKeyId, policy, signature, expire}>} */ export async function getOssSign(dir) { const res = await request({ url: '/common/oss/sign', method: 'get', params: { dir } }) if (res.code !== 200) { throw new Error(res.msg || 'OSS 签名失败') } return res } /** * 直接上传 File 到 OSS bucket * @param {File} file 要上传的文件 * @param {string} dir 上传目录 * @returns {Promise} 完整 URL (https://bucket.xxx/key) */ // 根据文件扩展名返回正确的 MIME,避免 OSS 默认给 application/octet-stream function getMimeByExt(name) { const ext = (name || '').split('.').pop().toLowerCase() const map = { pdf: 'application/pdf', doc: 'application/msword', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' } return map[ext] || 'application/octet-stream' } export async function uploadToOss(file, dir) { const sign = await getOssSign(dir) // 原文件名进 OSS key: 原始名称_时间戳_随机串.原扩展名 (保留中文, 仅清洗 URL/路径危险字符) // 这样上传控件能从 URL 里还原原名显示, 无需额外 name 字段/列 const rawName = file.name || 'file' const dot = rawName.lastIndexOf('.') const ext = (dot > 0 ? rawName.slice(dot) : '').replace(/[\\/:*?"<>|%\s]+/g, '') const base = (dot > 0 ? rawName.slice(0, dot) : rawName) .replace(/[\\/:*?"<>|%\s]+/g, '_') || 'file' const key = sign.dir + base + '_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8) + ext // 显式传 Content-Type,让 OSS 按此存文件元数据, 浏览器拿到 application/pdf 即可内嵌预览 const mime = getMimeByExt(file.name) const fd = new FormData() fd.append('key', key) fd.append('policy', sign.policy) fd.append('OSSAccessKeyId', sign.accessKeyId) fd.append('signature', sign.signature) fd.append('success_action_status', '200') fd.append('Content-Type', mime) fd.append('file', file) // ⚠️ 不要手动设置 Content-Type header,会让 multipart/form-data boundary 丢失 // FormData 会自动生成正确的 multipart 格式 const resp = await fetch(sign.host, { method: 'POST', body: fd }) if (!resp.ok) { throw new Error('OSS 上传失败: HTTP ' + resp.status) } // 返回 URL 时对路径逐段编码 (保留 / 分隔), 让含中文/特殊字符的 key 在所有下载场景下都是 ASCII 安全 URL // OSS 对象 key 本身仍是原始值 (FormData 的 key 字段未编码), 下载时 OSS 会自动把 %XX 还原 return sign.host + '/' + key.split('/').map(encodeURIComponent).join('/') }