前端图片上传 OSS 实践分享

258 阅读2分钟

🚀 前端图片上传 OSS 实践分享(el-upload + PUT 上传到阿里云)

在实际业务中,我们常常需要将图片上传至阿里云 OSS。以下是我在项目中实践的一种实现方式,结合 Element UIel-upload 组件 + 后端返回的 OSS 预签名地址实现直传,分享给大家。


📸 1. 使用 el-upload 组件上传图片

<el-form-item
  prop="pictureUrls"
  label="图片"
>
  <el-upload
    ref="uploadRef"
    class="upload-demo"
    drag
    multiple
    :file-list="fileList"
    action=""
    :before-upload="beforeAvatarUpload"
    :on-remove="handleRemove"
    :limit="10"
  >
    <i class="el-icon-upload" />
    <div class="el-upload__tip" slot="tip">
      {{ showTip }}
    </div>
  </el-upload>
</el-form-item>
  • file-list: 上传的文件列表,例如:
    [{ name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg' }]
  • before-upload: 上传前校验钩子,控制文件是否允许上传
  • on-remove: 删除文件时触发,可同步处理后端图片数据

🔌 2. 后端接口返回结构(预签名上传地址)

{
  "code": 0,
  "data": {
    "uploadUrl": "https://oss-url.com/xxx?Signature=xxx", // PUT 上传地址
    "originUrl": "https://oss-url.com/xxx" // 图片访问地址
  }
}

🔁 3. 上传流程概览

1️⃣ 向后端申请预签名的上传地址 uploadUrl
2️⃣ 使用 axios.put(uploadUrl, file) 上传图片至 OSS
3️⃣ 上传成功后,记录 originUrl 作为图片访问地址


🔍 4. 上传前校验尺寸 + 手动上传逻辑(核心)

beforeAvatarUpload(file) {
  const type = this.form.pictureType;

  if (typeof type !== 'number' && !type) {
    this.$message.error('请先选择图片分类');
    return false;
  }

  const { type: fileType } = file;
  if (!fileType.includes('image')) {
    this.$message.error('只支持上传图片');
    return false;
  }

  const reader = new FileReader();
  reader.onload = (event) => {
    const img = new Image();
    img.onload = async () => {
      const { width, height } = img;
      // 校验图片尺寸
      const isValid = (type === pictureTypeMap.card && width === 240 && height === 144)
        || (type === pictureTypeMap.business && width === 220 && height === 164)
        || (type === pictureTypeMap.detail && width === 702 && height === 364);

      if (!isValid) {
        this.$message.error('图片尺寸不正确,请根据图片类型选择对应尺寸');
        return;
      }

      // ✅ 校验通过后手动上传
      this.uploadImage(file);
      // ✅ 添加到 fileList 以便展示
      this.fileList.push(file);
    };
    img.onerror = () => {
      this.$message.error('图片加载失败');
    };
    img.src = event.target.result;
  };

  reader.readAsDataURL(file);
  // ❗️阻止 el-upload 的默认上传行为
  return false;
}

📤 5. 上传图片至 OSS 的核心逻辑

async uploadImage(file) {
  this.loading = true;
  try {
    // 1. 调用后端,获取 uploadUrl + originUrl
    const { code, data } = await uploadCouponMallPicture({
      imageType: 83,
      type: 3,
      isPrivate: false,
      contentType: file.type,
    });

    // 2. 使用 PUT 请求上传图片到 OSS
    if (code === 0 && data?.uploadUrl && data?.originUrl) {
      const response = await axios.put(data.uploadUrl, file, {
        headers: {
          'Content-Type': file.type,
        },
      });

      if (response.status === 200) {
        this.$message.success('成功上传到 OSS!');
      }

      // 3. 成功后更新 fileList 和 imgList
      this.fileList = this.$refs.uploadRef.uploadFiles;
      this.imgList.push({
        ...data,
        uid: file.uid,
        url: data.originUrl,
      });
    }
  } catch (e) {
    this.$message.error('上传失败,请重试');
    this.fileList.pop(file);
  } finally {
    this.loading = false;
  }
}

🗑️ 6. 删除文件处理

// 删除图片
handleRemove(file, fileList) {
  // file 是当前被删除的文件,fileList 是当前剩余的文件列表
  this.fileList = fileList;

  // 已上传成功的cdn链接列表,也需要同步处理:
  this.imgList = this.imgList.filter(img => img.uid !== file.uid);
},

⚠️ 注意事项

项目说明
上传方式必须使用 PUT 请求(不是 POST)
Content-Type必须设置为 file.type,如 image/jpeg
请求体不要用 form-data,文件内容就是 body
URL 有有效期过期会 403,需重新获取
OSS 跨域Bucket 必须允许跨域 PUT 上传(需后端配置)