Skip to content
微信小程序上传腾讯云cos封装

下载js工具包

https://github.com/tencentyun/cos-wx-sdk-v5/blob/master/demo/lib/cos-wx-sdk-v5.js

把上面的js文件下载下来

封装上传的工具包cor.js

js
// videoUploadService.js
import COS from "./cos-wx-sdk-v5";

/**
 * 腾讯云COS视频上传服务
 */
class VideoUploadService {
  constructor(config = {}) {
    // 保存配置
    this.config = {
      // 必填参数
      bucket: config.bucket || '', // 存储桶名称
      region: config.region || 'ap-beijing', // 地区
      
      // 权限参数(应该从后端获取)
      TmpSecretId: config.TmpSecretId || '',
      TmpSecretKey: config.TmpSecretKey || '',
      SecurityToken: config.SecurityToken || '',
      
      // 可选参数
      videoPrefix: config.videoPrefix || 'wxapp/videos/',
      parallel: config.parallel || 2, // 并发数
      timeout: config.timeout || 300000, // 超时时间(毫秒)
      chunkSize: 1024 * 1024 * 20, // 设置较大的分片大小,基本不会触发分片
    };

    this.cos = null;
    this.currentUploadTask = null;
    this.isUploading = false;

    // 初始化COS实例
    this.initCOS();
  }

  /**
   * 初始化COS
   */
  initCOS() {
    this.cos = new COS({
      SecretId: this.config.TmpSecretId,
      SecretKey: this.config.TmpSecretKey,
      SecurityToken: this.config.SecurityToken,
      ChunkSize: this.config.chunkSize, // 设置为20MB,10秒视频基本不会超过
      Parallel: this.config.parallel,
      Timeout: this.config.timeout
    });
  }

  /**
   * 上传单个视频
   */
  uploadVideo(filePath, onProgress = null, customKey = null) {
    if (!this.cos) {
      return Promise.reject(new Error('COS未初始化'));
    }

    if (this.isUploading) {
      return Promise.reject(new Error('当前有视频正在上传,请等待或取消'));
    }

    this.isUploading = true;

    return new Promise((resolve, reject) => {
      const fileName = filePath.split('/').pop();
      const fileExt = this.getVideoExt(fileName);
      const key = customKey || this.generateVideoKey(fileExt);

      console.log('开始上传视频:', {
        filePath,
        fileName,
        key,
        bucket: this.config.bucket,
        region: this.config.region
      });

      this.currentUploadTask = this.cos.uploadFile({
        Bucket: this.config.bucket,
        Region: this.config.region,
        Key: key,
        FilePath: filePath,

        onProgress: (progressData) => {
          if (onProgress) {
            const percent = Math.round(progressData.percent * 100);
            const speed = progressData.speed ? `${(progressData.speed / 1024).toFixed(2)}KB/s` : '0KB/s';

            onProgress({
              percent,
              speed,
              loaded: progressData.loaded,
              total: progressData.total
            });
          }
        },

        onFileFinish: (err, data) => {
          this.isUploading = false;
          this.currentUploadTask = null;

          if (err) {
            console.error('视频上传失败:', err);
            reject(new Error(`上传失败: ${err.message || '未知错误'}`));
            return;
          }

          console.log('上传完成:', data);

          if (data.statusCode === 200) {
            const result = {
              success: true,
              url: data.Location,
              key: data.Key,
              videoUrl: `https://${data.Location}`,
              etag: data.ETag,
              headers: data.headers
            };
            resolve(result);
          } else {
            reject(new Error(`服务器响应异常: HTTP ${data.statusCode}`));
          }
        }
      });
    });
  }

  /**
   * 取消上传
   */
  cancelUpload() {
    if (this.currentUploadTask) {
      this.currentUploadTask.cancel();
      this.currentUploadTask = null;
      this.isUploading = false;
      return true;
    }
    return false;
  }

  /**
   * 生成视频存储key
   */
  generateVideoKey(ext = 'mp4') {
    const timestamp = Date.now();
    const randomStr = Math.random().toString(36).substr(2, 8);
    return `${this.config.videoPrefix}${timestamp}_${randomStr}.${ext}`;
  }

  /**
   * 获取视频扩展名
   */
  getVideoExt(filename) {
    const ext = filename.toLowerCase().split('.').pop();
    const videoExts = ['mp4', 'mov', 'avi', 'm4v', 'webm', 'flv', '3gp', 'wmv'];
    return videoExts.includes(ext) ? ext : 'mp4';
  }

  /**
   * 更新配置
   */
  updateConfig(newConfig) {
    Object.assign(this.config, newConfig);
    this.initCOS(); // 重新初始化COS实例
  }
}

// 不再使用单例模式,因为每个上传任务可能需要不同的配置
export const createVideoUploader = (config) => {
  return new VideoUploadService(config);
};

使用工具,做个选择视频并上传的示例代码

wxml代码为

js
<!-- pages/uploadVideo/uploadVideo.wxml -->
<view class="container">
  <!-- 选择视频区域 -->
  <view class="select-area" wx:if="{{!videoPath && !uploading}}">
    <view class="select-btn" bindtap="selectVideo">
      选择视频
    </view>
  </view>

  <!-- 视频信息 -->
  <view class="video-info" wx:if="{{videoPath && !uploading && !result}}">
    <view>视频: {{videoName}}</view>
    <view>大小: {{videoSize}}</view>
    <view class="upload-btn" bindtap="startUpload">
      开始上传
    </view>
  </view>

  <!-- 上传中 -->
  <view class="uploading" wx:if="{{uploading}}">
    <view>上传中... {{progress}}%</view>
    <progress percent="{{progress}}" show-info />
  </view>

  <!-- 上传结果 -->
  <view class="result" wx:if="{{result}}">
    <view class="result-title" wx:if="{{result.success}}">
      ✓ 上传成功
    </view>
    <view class="result-title error" wx:else>
      ✗ 上传失败
    </view>

    <view wx:if="{{result.success && result.url}}">
      <view class="url-text">{{result.url}}</view>
      <view class="action-btn" bindtap="copyUrl">复制链接</view>
    </view>

    <view wx:else-if="{{!result.success}}">
      <view class="error-text">{{result.error}}</view>
    </view>

    <view class="reupload-btn" bindtap="reUpload">
      重新上传
    </view>
  </view>
</view>

wxss代码为

js
/* pages/uploadVideo/uploadVideo.wxss */
.container {
  padding: 40rpx;
  min-height: 100vh;
  background: #f5f5f5;
}

.select-area {
  padding: 100rpx 0;
  text-align: center;
}

.select-btn {
  display: inline-block;
  padding: 20rpx 60rpx;
  background: #07c160;
  color: white;
  border-radius: 10rpx;
  font-size: 32rpx;
}

.video-info {
  background: white;
  padding: 40rpx;
  border-radius: 10rpx;
  margin-bottom: 30rpx;
}

.upload-btn {
  margin-top: 30rpx;
  padding: 20rpx;
  background: #1989fa;
  color: white;
  text-align: center;
  border-radius: 10rpx;
}

.uploading {
  background: white;
  padding: 40rpx;
  border-radius: 10rpx;
  text-align: center;
}

.result {
  background: white;
  padding: 40rpx;
  border-radius: 10rpx;
}

.result-title {
  font-size: 36rpx;
  font-weight: bold;
  color: #07c160;
  margin-bottom: 30rpx;
}

.result-title.error {
  color: #f44;
}

.url-text {
  background: #f5f5f5;
  padding: 20rpx;
  border-radius: 5rpx;
  font-size: 24rpx;
  word-break: break-all;
  margin-bottom: 20rpx;
}

.action-btn {
  padding: 20rpx;
  background: #1989fa;
  color: white;
  text-align: center;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
}

.error-text {
  color: #f44;
  margin-bottom: 20rpx;
}

.reupload-btn {
  padding: 20rpx;
  background: #ff976a;
  color: white;
  text-align: center;
  border-radius: 10rpx;
}

js代码为

js
// pages/uploadVideo/uploadVideo.js
import { createVideoUploader } from '../../../../utils/cos';

Page({
  data: {
    // 上传状态
    uploading: false,
    progress: 0,
    
    // 视频信息
    videoPath: '',
    videoName: '',
    videoSize: '',
    
    // 上传结果
    result: null
  },

  /**
   * 选择视频
   */
  selectVideo() {
    wx.chooseMedia({
      count: 1,
      mediaType: ['video'],
      sourceType: ['album', 'camera'],
      success: (res) => {
        console.log('res',res)
        const video = res.tempFiles[0];
        console.log('video',video)
        const sizeMB = (video.size / 1024 / 1024).toFixed(2);
        
        this.setData({
          videoPath: video.tempFilePath,
          videoName: video.tempFilePath.split('/').pop(),
          videoSize: sizeMB + 'MB',
          result: null
        });
      },
      fail: () => {
        wx.showToast({
          title: '选择失败',
          icon: 'error'
        });
      }
    });
  },

 // 在页面JS中
async startUpload() {
  if (!this.data.videoPath) {
    wx.showToast({
      title: '请先选择视频',
      icon: 'none'
    });
    return;
  }

  if (this.data.uploading) return;

  // 1. 先获取凭证
  // const credentials = await this.getCredentials(); // 这里正常是从接口取,我目前在下面写死了临时密钥和Token
  const credentials = {
    "Return": 1,
    "Result": "{\"Credentials\":{\"Token\":\"XXXXXX\",\"TmpSecretId\":\"XXXXX\",\"TmpSecretKey\":\"XXXXXX\"},\"RequestId\":\"XXXXXX\",\"Expiration\":\"XXXXXXX\",\"ExpiredTime\":1772701464,\"StartTime\":1772615064,\"CDNPath\":\"\"}",
    "ReturnInfo": ""
  }
  if (!credentials) return;

  // 2. 解析凭证(你的凭证格式是JSON字符串嵌套在Result中)
  let cosCredentials;
  try {
    const resultData = JSON.parse(credentials.Result);
    cosCredentials = resultData.Credentials;
  } catch (err) {
    wx.showToast({
      title: '凭证解析失败',
      icon: 'error'
    });
    console.error('凭证解析失败:', err, credentials);
    return;
  }

  // 3. 创建上传器 - 使用解析后的凭证(这里就是主要的上传逻辑,将凭证上传给函数就上传了)
  const uploader = createVideoUploader({
    bucket: 'XXXX', // 你的bucket
    region: 'XXXXXX', // 你的region
    TmpSecretId: cosCredentials.TmpSecretId,
    TmpSecretKey: cosCredentials.TmpSecretKey,
    SecurityToken: cosCredentials.Token
  });

  this.setData({ uploading: true, progress: 0 });

  try {
    // 这里上传微信小程序生成的临时文件路径即可
    const result = await uploader.uploadVideo(this.data.videoPath, (p) => {
      this.setData({ progress: Math.round(p.percent) });
    });

    this.setData({
      result: {
        success: true,
        url: result.videoUrl
      },
      uploading: false
    });

    wx.showToast({
      title: '上传成功',
      icon: 'success'
    });

  } catch (error) {
    this.setData({
      result: {
        success: false,
        error: error.message
      },
      uploading: false
    });

    wx.showToast({
      title: '上传失败: ' + error.message,
      icon: 'error'
    });
  }
},

  /**
   * 获取COS凭证
   */
  getCredentials() {
    return new Promise((resolve) => {
      // 这里替换为你的后端接口
      wx.request({
        url: 'https://your-backend.com/api/get-cos-credentials',
        method: 'GET',
        success: (res) => {
          if (res.data.code === 0) {
            resolve(res.data.data);
          } else {
            wx.showToast({
              title: '获取凭证失败',
              icon: 'error'
            });
            resolve(null);
          }
        },
        fail: () => {
          wx.showToast({
            title: '网络错误',
            icon: 'error'
          });
          resolve(null);
        }
      });
    });
  },

  /**
   * 复制链接
   */
  copyUrl() {
    if (this.data.result?.url) {
      wx.setClipboardData({
        data: this.data.result.url,
        success: () => {
          wx.showToast({
            title: '已复制',
            icon: 'success'
          });
        }
      });
    }
  },

  /**
   * 重新上传
   */
  reUpload() {
    this.setData({
      result: null,
      videoPath: '',
      videoName: '',
      videoSize: ''
    });
  }
});