UniApp 上传图片到阿里云 OSS 教程

  1. 概述

本文档详细介绍了在 UniApp 项目中如何实现图片上传到阿里云 OSS 的功能。主要包括以下内容:

  • 阿里云 OSS 上传原理
  • 前端实现流程
  • 关键代码解析
  • 常见问题及解决方案
  1. 阿里云 OSS 上传原理

2.1 基本概念

阿里云对象存储服务(Object Storage Service,简称 OSS)是阿里云提供的海量、安全、低成本、高可靠的云存储服务。在移动应用中,我们通常采用 STS(Security Token Service)临时授权的方式进行上传,这种方式更加安全。

2.2 上传流程

  1. 客户端向应用服务器请求临时授权(STS Token)

  2. 应用服务器返回临时授权信息(AccessKeyId、AccessKeySecret、SecurityToken 等)

  3. 客户端使用这些临时凭证直接向 OSS 发起上传请求

  4. OSS 验证请求合法性并存储文件

  5. 客户端接收上传结果

  6. 前端实现流程

3.1 完整流程

在我们的 UniApp 项目中,图片上传到 OSS 的完整流程如下:

  1. 用户点击上传按钮,弹出选择菜单(从相册选择或拍照上传)
  2. 用户选择图片或拍照后,获取临时文件路径
  3. 调用后端接口获取 OSS 临时授权信息
  4. 使用 uni.uploadFile 方法将图片直接上传到 OSS
  5. 上传成功后,更新界面显示已上传的图片

3.2 关键步骤

  1. 选择图片:使用 uni.chooseImage API 从相册选择或拍照获取图片

  2. 获取授权:调用后端接口获取 OSS 临时授权信息

  3. 生成签名:根据 OSS 规则生成 Policy 和 Signature

  4. 上传文件:使用 uni.uploadFile 直接上传到 OSS 服务器

  5. 处理结果:根据上传结果更新界面状态

  6. 关键代码解析

4.1 组件结构

组件主要包含两个部分:

  • 未上传状态:显示上传按钮

  • 已上传状态:显示图片预览、文件名和删除按钮

    <!-- 上传按钮区域 -->
    <view class="upload-buttons" v-if="fileList.length === 0">
      <button class="custom-upload-btn" @click="showUploadOptions">
        <image class="upload-icon" src="/static/img/icon/上传.png"></image>
        <text class="upload-text">上传报告</text>
      </button>
    </view>
    

4.2 选择图片

用户可以通过相册选择或拍照两种方式上传图片:

// 显示上传选项菜单
showUploadOptions() {
  uni.showActionSheet({
    itemList: ['从相册选择', '拍照上传'],
    success: (res) => {
      switch (res.tapIndex) {
        case 0: // 从相册选择
          this.chooseFromAlbum();
          break;
        case 1: // 拍照上传
          this.takePhoto();
          break;
      }
    }
  });
}

// 从相册选择图片
chooseFromAlbum() {
  uni.chooseImage({
    count: 1, // 最多选择1张图片
    sizeType: ['compressed'], // 强制使用压缩图提高上传速度
    sourceType: ['album'], // 从相册选择
    success: (res) => {
      if (res.tempFilePaths && res.tempFilePaths.length > 0) {
        const tempFilePath = res.tempFilePaths[0];
        const tempFile = {
          path: tempFilePath,
          name: this.getFileNameFromPath(tempFilePath)
        };
        this.uploadFile(tempFile);
      }
    }
  });
}

4.3 获取 OSS 授权

从后端获取 OSS 临时授权信息:

// 上传文件到OSS
uploadFile(file) {
  uni.showLoading({
    title: '上传中...'
  });

  // 获取OSS Token
  getOssToken().then((tokenRes) => {
    if (!tokenRes || tokenRes.status !== 200) {
      console.error("OSS Token 响应无效:", tokenRes);
      uni.hideLoading();
      uni.showToast({
        title: 'OSS Token 响应无效',
        icon: 'none'
      });
      return;
    }

    const data = tokenRes.data;
    
    // 检查必要参数
    if (!data.accessKeyId || !data.bucket || !data.filePath) {
      console.error("OSS Token 数据不完整:", data);
      uni.hideLoading();
      uni.showToast({
        title: 'OSS 配置不完整',
        icon: 'none'
      });
      return;
    }

    try {
      // 生成唯一文件名
      const fileName = this.generateFileName(file.name);
      const key = `${data.filePath}/${fileName}`;
      
      // 上传文件
      this.uploadWithOSSSDK(
        file.path, 
        key, 
        data.bucket, 
        data.accessKeyId, 
        data.accessKeySecret, 
        data.securityToken
      );
    } catch (error) {
      this.logError("上传准备失败", error);
      uni.hideLoading();
      uni.showToast({
        title: '上传准备失败',
        icon: 'none'
      });
    }
  }).catch((err) => {
    this.logError("获取OSS Token失败", err);
    uni.hideLoading();
    uni.showToast({
      title: 'OSS Token获取失败',
      icon: 'none'
    });
  });
}

4.4 生成签名

根据 OSS 规则生成 Policy 和 Signature:

// 生成policy和signature
generatePolicyAndSignature(key, accessKeySecret, securityToken) {
  try {
    // 设置policy过期时间(1小时后)
    const expiration = new Date(Date.now() + 3600 * 1000).toISOString();
    
    // 构建policy
    const policyText = {
      expiration: expiration,
      conditions: [
        ["content-length-range", 0, 10485760], // 10MB限制
        ["starts-with", "$key", key.substring(0, key.lastIndexOf('/') + 1)] // 限制上传路径
      ]
    };
    
    // Base64编码policy
    const policyBase64 = this.base64Encode(JSON.stringify(policyText));
    
    // 生成signature
    const signature = this.hmacSha1(policyBase64, accessKeySecret);
    
    return {
      policy: policyBase64,
      signature: signature
    };
  } catch (error) {
    console.error("生成policy和signature失败:", error);
    throw error;
  }
}

// HMAC-SHA1签名(使用crypto-js标准实现)
hmacSha1(message, key) {
  try {
    // 使用crypto-js生成HMAC-SHA1签名
    const hash = CryptoJS.HmacSHA1(message, key);
    const signature = CryptoJS.enc.Base64.stringify(hash);
    return signature;
  } catch (error) {
    console.error("HMAC-SHA1签名生成失败:", error);
    throw error;
  }
}

4.5 上传文件

使用 uni.uploadFile 直接上传到 OSS:

// 使用uni.uploadFile进行上传
uploadWithUniUpload(filePath, key, bucket, accessKeyId, accessKeySecret, securityToken) {
  try {
    // 生成policy和signature
    const { policy, signature } = this.generatePolicyAndSignature(key, accessKeySecret, securityToken);
    
    // 构建OSS上传URL
    const uploadUrl = `https://${bucket}.xxxx.aliyuncs.com`;
    
    // 构建表单数据(按照OSS要求的格式)
    const formData = {
      'key': key,
      'policy': policy,
      'OSSAccessKeyId': accessKeyId,
      'signature': signature,
      'success_action_status': '200'
    };
    
    // 添加STS Token
    if (securityToken) {
      formData['x-oss-security-token'] = securityToken;
    }
    
    // 使用uni.uploadFile进行上传
    const uploadTask = uni.uploadFile({
      url: uploadUrl,
      filePath: filePath,
      name: 'file',
      formData: formData,
      success: (uploadRes) => {
        if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
          uni.hideLoading();
          
          const fileUrl = `https://${bucket}.xxxx.aliyuncs.com/${key}`;
          
          this.fileList = [{
            name: this.getFileNameFromPath(key),
            url: fileUrl,
            ossKey: key
          }];
          
          this.emitInput(key);
          
          uni.showToast({
            title: '上传成功',
            icon: 'success'
          });
        } else {
          console.error("uni.uploadFile上传失败,状态码:", uploadRes.statusCode);
          uni.hideLoading();
          uni.showToast({
            title: `上传失败(${uploadRes.statusCode})`,
            icon: 'none'
          });
        }
      },
      fail: (err) => {
        console.error("uni.uploadFile上传失败:", err);
        uni.hideLoading();
        uni.showToast({
          title: '上传失败',
          icon: 'none'
        });
      }
    });
    
    // 监听上传进度
    uploadTask.onProgressUpdate((res) => {
      console.log('上传进度:', res.progress + '%');
    });
    
  } catch (error) {
    console.error("生成上传参数失败:", error);
    uni.hideLoading();
    uni.showToast({
      title: '上传参数生成失败',
      icon: 'none'
    });
  }
}
  1. 后端接口

后端需要提供一个获取 OSS 临时授权的接口,返回以下信息:

// 获取OSS上传Token
export function getOssToken() {
  return axios.post('/demo/oss/remote/getOssToken.action')
}

接口返回数据格式:

{
  "status": 200,
  "data": {
    "accessKeyId": "临时AccessKeyId",
    "accessKeySecret": "临时AccessKeySecret",
    "securityToken": "临时SecurityToken",
    "bucket": "OSS存储桶名称",
    "filePath": "文件存储路径前缀"
  }
}
  1. 常见问题及解决方案

6.1 上传失败问题

问题1:签名验证失败

可能原因:

  • Policy 或 Signature 生成错误
  • AccessKeySecret 使用错误

解决方案:

  • 确保 Policy 格式正确,包含必要的条件限制
  • 检查 HMAC-SHA1 签名算法实现是否正确
  • 验证 AccessKeySecret 是否正确传递

问题2:权限不足

可能原因:

  • STS Token 权限配置不足
  • Bucket 权限设置限制

解决方案:

  • 检查后端 STS Token 授权策略是否包含足够的权限
  • 确认 Bucket 的访问控制设置是否允许上传

6.2 兼容性问题

问题:不同平台上传方式差异

解决方案:

  • 针对安卓平台,直接使用 uni.uploadFile 方法
  • 确保正确处理文件路径,特别是在不同平台下的路径格式差异
  1. 最佳实践

  2. 安全性:

    • 始终使用 STS 临时授权方式,避免使用永久 AccessKey
    • 设置合理的 Policy 限制,如文件大小、上传路径等
  3. 性能优化:

    • 上传前压缩图片(sizeType: [‘compressed’])
    • 监控上传进度并提供反馈
    • 处理网络异常情况,提供重试机制
  4. 用户体验:

    • 提供上传进度反馈
    • 上传成功后立即显示预览
    • 提供删除已上传文件的功能
  5. 总结

本文档详细介绍了在 UniApp 中实现图片上传到阿里云 OSS 的完整流程和关键代码。通过使用 uni.uploadFile 结合 OSS 的表单上传方式,我们可以在移动应用中高效、安全地实现文件上传功能。

关键点包括:

  1. 获取 OSS 临时授权信息
  2. 正确生成 Policy 和 Signature
  3. 使用 uni.uploadFile 直接上传到 OSS
  4. 处理上传结果和异常情况

通过遵循本文档的实践指南,开发者可以在 UniApp 项目中轻松实现图片上传到阿里云 OSS 的功能。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐