七牛云对象存储(Kodo)是一款稳定、安全、高效、易用的云存储服务,适用于各种场景下的数据存储需求。本文将详细介绍如何在项目中集成七牛云对象存储,并分别提供小程序端和H5端的实现代码。

准备工作

1. 注册七牛云账号

首先访问七牛云官网注册账号并完成实名认证。

2. 创建存储空间(Bucket)

登录七牛云控制台后:

  1. 进入"对象存储"服务

  2. 点击"创建存储空间"

  3. 填写Bucket名称(需全局唯一)

  4. 选择存储区域(根据用户分布选择)

  5. 设置访问控制(建议先设置为公开,后续可按需调整)

3. 获取必要凭证

在"个人中心" > "密钥管理"中获取:

  • Access Key (AK)

  • Secret Key (SK) 

 核心讲解

一、核心上传流程

七牛云文件上传的核心流程分为三步:

  1. 获取上传凭证(uptoken)

  2. 初始化上传器并配置参数

  3. 执行上传并处理结果

二、前端核心代码

1. 初始化上传器

通用配置参数:

const config = {
  bucket: 'your-bucket',      // 存储空间名称
  region: 'z0',              // 存储区域(z0-华东,z1-华北等)
  uptoken: '',               // 上传凭证(通常从服务端获取)
  domain: 'https://xxx.com', // 空间绑定的域名
  shouldUseQiniuFileName: false // 是否使用七牛生成的文件名
};

2、获取服务端给的token和key(key可以是前端写死,但是最好还是用后端传的,交给后端来控制) 

// 获取七牛云上传token
async function getQiniuUploadToken() {
  try {
    const res = await api.getQiniuAccesstoekn()
    if (res.code !== 200) {
      throw new Error(res.message || '获取七牛云token失败')
    }
    return res.data
  }
  catch (error) {
    console.error('获取七牛云token失败:', error)
    throw error
  }
}

3、发起请求,上传文件

初始化上传任务

const observable = qiniu.upload(
      file,           // 文件对象/路径
      key,            // 文件保存的名称
      config.uptoken, // 上传凭证
      { region: config.region }, // 额外配置
      { useCdnDomain: true }     // 使用CDN加速
    );

订阅上传进度

const subscription = observable.subscribe({
      next: (res) => {
        console.log('上传进度:', res.total.percent);
      },
      error: (err) => {
        console.error('上传失败:', err);
        reject(err);
      },
      complete: (res) => {
        // 拼接完整URL
        const fileUrl = `${config.domain}/${res.key}`;
        console.log('上传完成:', fileUrl);
        resolve({
          ...res,
          fileUrl: fileUrl
        });
      }
    });

封装demo(h5和小程序)

H5demo

import api from '@/api/module/cos'
import * as qiniu from 'qiniu-js'

// 七牛云配置
const qiniuConfig = {
  uploadDomain: 'https://upload.qiniup.com' // 华东,
  cdnDomain: 'cdn域名',
  maxRetryCount: 2,
  timeout: 3000,
}

// 获取七牛云上传token
async function getQiniuUploadToken() {
  try {
    const res = await api.getQiniuAccesstoekn()
    if (res.code !== 200) {
      throw new Error(res.message || '获取七牛云token失败')
    }
    return res.data
  }
  catch (error) {
    console.error('获取七牛云token失败:', error)
    throw error
  }
}

/**
 * 高级七牛云文件上传(H5版)
 * @param {object} options 上传选项
 * @param {File} options.file 文件对象
 * @param {string} options.cloudPath 云端存储路径
 * @param {Function} [options.onProgress] 上传进度回调
 * @param {Function} [options.onCancel] 取消上传回调
 * @param {number} [options.retryCount] 当前重试次数
 * @returns {Promise<{url: string, key: string, hash: string}>} 上传结果
 */
async function uploadFileToQiniuH5(options) {
  const {
    file,
    cloudPath,
    onProgress = () => { },
    onCancel = () => { },
    retryCount = 0,
  } = options

  try {
    // 获取上传token
    const { token, key } = await getQiniuUploadToken()
    const cloudFile = `${key}/${cloudPath}`

    // 配置上传选项
    const putExtra = {
      fname: file.name,
      params: {},
      mimeType: file.type || null,
    }

    const config = {
      useCdnDomain: true,
      region: qiniu.region.z0, // 华东区域
      retryCount: qiniuConfig.maxRetryCount,
      disableStatisticsReport: false,
    }

    // 执行上传
    return new Promise((resolve, reject) => {
      let subscription = null
      let timer = null

      // 超时处理
      timer = setTimeout(() => {
        if (subscription) {
          subscription.unsubscribe()
        }
        reject(new Error('上传超时'))
      }, qiniuConfig.timeout)

      const observable = qiniu.upload(
        file,
        cloudFile,
        token,
        putExtra,
        config,
      )

      subscription = observable.subscribe({
        next: (response) => {
          // 进度更新
          onProgress({
            loaded: response.total.loaded,
            total: response.total.size,
            percent: Math.round(response.total.percent),
          })
        },
        error: (err) => {
          clearTimeout(timer)
          // 重试逻辑
          if (retryCount < qiniuConfig.maxRetryCount) {
            console.log(`准备第${retryCount + 1}次重试...`)
            setTimeout(() => {
              uploadFileToQiniuH5({
                ...options,
                retryCount: retryCount + 1,
              }).then(resolve).catch(reject)
            }, 1000 * (retryCount + 1))
          }
          else {
            reject(new Error(`上传失败: ${err.message}`))
          }
        },
        complete: (res) => {
          clearTimeout(timer)
          console.log('上传成功:', res)
          resolve({
            url: `${qiniuConfig.cdnDomain}/${res.key}`,
            key: res.key,
            hash: res.hash,
          })
        },
      })

      // 提供取消上传的方法
      onCancel(() => {
        clearTimeout(timer)
        if (subscription) {
          subscription.unsubscribe()
        }
        reject(new Error('上传已取消'))
      })
    })
  }
  catch (error) {
    console.error('上传文件到七牛云失败:', error)
    throw error
  }
}

/**
 * 简化版上传(H5版)
 * @param {File} file 文件对象
 * @param {string} cloudPath 云端存储路径
 * @returns {Promise<string>} 文件URL
 */
async function simpleUploadH5(file, cloudPath) {
  const result = await uploadFileToQiniuH5({
    file,
    cloudPath,
    onProgress: (progress) => {
      console.log(`上传进度: ${progress.percent}%`)
    },
  })
  return result.url
}


export {
  simpleUploadH5,
  uploadFileToQiniuH5,
}

小程序demo

import { getQiniuAccesstoekn } from '../api/module/cos'

// 七牛云配置
const qiniuConfig = {
  uploadDomain: 'https://upload.qiniup.com' //华东,
  // 你的CDN域名
  cdnDomain: 'cdn域名',
  // 上传重试次数
  maxRetryCount: 2,
  timeout: 3000
};

// 获取七牛云上传token
const getQiniuUploadToken = async () => {
  try {
    const res = await getQiniuAccesstoekn();
    if (res.code !== 200) {
      throw new Error(res.message || '获取七牛云token失败');
    }
    return res.data;
  } catch (error) {
    console.error('获取七牛云token失败:', error);
    throw error;
  }
};

/**
 * 高级七牛云文件上传
 * @param {Object} options 上传选项
 * @param {string} options.filePath 本地文件路径
 * @param {string} options.cloudPath 云端存储路径
 * @param {Function} [options.onProgress] 上传进度回调
 * @param {Function} [options.onCancel] 取消上传回调
 * @param {number} [options.retryCount=0] 当前重试次数
 * @returns {Promise<{url: string, key: string, hash: string}>} 上传结果
 */
const uploadFileToQiniu = async (options) => {
  const {
    filePath,
    cloudPath,
    onProgress = () => { },
    onCancel = () => { },
    retryCount = 0
  } = options;

  try {
    // 获取上传token
    const { token, key } = await getQiniuUploadToken();

    const cloudFile = `${key}/${cloudPath}`

    // 获取文件信息
    const fileInfo = await new Promise((resolve, reject) => {
      wx.getFileInfo({
        filePath,
        success: resolve,
        fail: reject
      });
    });

    // 执行上传
    return new Promise((resolve, reject) => {
      let uploadTask = null;
      let timer = null;

      // 超时处理
      timer = setTimeout(() => {
        if (uploadTask) {
          uploadTask.abort();
        }
        reject(new Error('上传超时'));
      }, qiniuConfig.timeout);

      uploadTask = wx.uploadFile({
        url: qiniuConfig.uploadDomain,
        filePath,
        name: 'file',
        formData: {
          token,
          key: cloudFile
        },
        success: (res) => {
          clearTimeout(timer);
          if (res.statusCode === 200) {
            try {
              const data = JSON.parse(res.data);
              console.log('上传成功:', data);

              resolve({
                url: `${qiniuConfig.cdnDomain}/${data.key}`,
                key: data.key,
                hash: data.hash
              });
            } catch (e) {
              reject(new Error('解析响应数据失败'));
            }
          } else {
            reject(new Error(`上传失败: ${res.data}`));
          }
        },
        fail: (err) => {
          clearTimeout(timer);
          // 重试逻辑
          if (retryCount < qiniuConfig.maxRetryCount) {
            console.log(`准备第${retryCount + 1}次重试...`);
            setTimeout(() => {
              uploadFileToQiniu({
                ...options,
                retryCount: retryCount + 1
              }).then(resolve).catch(reject);
            }, 1000 * (retryCount + 1));
          } else {
            reject(new Error(`上传失败: ${err.errMsg}`));
          }
        },
        complete: () => {
          clearTimeout(timer);
        }
      });

      // 确保uploadTask存在再绑定事件
      if (uploadTask && uploadTask.onProgressUpdate) {
        uploadTask.onProgressUpdate((res) => {
          onProgress({
            loaded: res.totalBytesSent,
            total: res.totalBytesExpectedToSend,
            percent: res.progress
          });
        });
      }

      // 提供取消上传的方法
      onCancel(() => {
        clearTimeout(timer);
        if (uploadTask) {
          uploadTask.abort();
        }
        reject(new Error('上传已取消'));
      });
    });
  } catch (error) {
    console.error('上传文件到七牛云失败:', error);
    throw error;
  }
};

/**
 * 简化版上传
 * @param {string} filePath 本地文件路径
 * @param {string} cloudPath 云端存储路径
 * @returns {Promise<string>} 文件URL
 */
const simpleUpload = async (filePath, cloudPath) => {
  const result = await uploadFileToQiniu({
    filePath,
    cloudPath,
    onProgress: (progress) => {
      console.log(`上传进度: ${progress.percent}%`);
    }
  });

  return result.url;
};

export {
  uploadFileToQiniu,
  simpleUpload
};

注意:在小程序端可以直接请求七牛云的域名,h5用七牛云的时候用qiniu.upload的时候报错,控制台说这个方法没有被定义,但是我是按照官方的文档写的,我用的版本是最新的4.X meta版本,但是换到3.1.2版本就不会有报错了,可能是API更新了官方的文档没有更新之类的,或者其他原因导致的

Logo

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

更多推荐