前端大文件直傳阿里雲OSS(適配uniapp和Element)


這幾天接到一個需求是這樣的,用戶需要在客戶端上傳視頻,一般大小都在50M以上。

最開始我們的方案是先把文件上傳到后端,后端再上傳到阿里雲OSS的。
由於文件過大,文件上傳非常慢。為了用戶體驗增加了等待進度條,但這時又出現了新的問題,進度條100%但是后端沒及時成功響應。因為后端雖然接收了文件,但阿里雲OSS還沒有返回響應,這個耗時也是非常久。於是考慮使用前端直傳阿里雲OSS。

oss配置

首先我們需要對OSS進行以下的配置:

image.png

如果開發小程序的話需要配置uploadFile合法域名(oss的域名)。

uniapp代碼

uniapp端的話可以考慮使用這個插件。
阿里雲oss文件直傳-無需后台簽名 - DCloud 插件市場

插件修改

修改了插件內的部分代碼
uploadFile.js

const env = require('./config.js'); //配置文件,在這文件里配置你的OSS keyId和KeySecret,timeout:87600;

const base64 = require('./base64.js'); //Base64,hmac,sha1,crypto相關算法
require('./hmac.js');
require('./sha1.js');
const Crypto = require('./crypto.js');




const getPolicyBase64 = function() {
	let date = new Date();
	date.setHours(date.getHours() + env.timeout);
	let srcT = date.toISOString();
	const policyText = {
		"expiration": srcT, //設置該Policy的失效時間,超過這個失效時間之后,就沒有辦法通過這個policy上傳文件了 
		"conditions": [
			["content-length-range", 0, 5000 * 1024 * 1024] // 設置上傳文件的大小限制,5mb
		]
	};

	const policyBase64 = base64.encode(JSON.stringify(policyText));
	console.log(policyBase64);
	return policyBase64;
}

const getSignature = function(policyBase64) {
	const accesskey = env.AccessKeySecret;

	const bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, accesskey, {
		asBytes: true
	});
	const signature = Crypto.util.bytesToBase64(bytes);
	console.log(signature);
	return signature;
}

const aliyunServerURL = env.uploadImageUrl; //OSS地址,需要https
const accessid = env.OSSAccessKeyId;
const policyBase64 = getPolicyBase64();
const signature = getSignature(policyBase64); //獲取簽名
const uploadFile = {
	url: aliyunServerURL, //開發者服務器 url
	policyBase64,
	accessid,
	signature,
}

module.exports = uploadFile;

使用

html代碼

<view class="upVideo" :style="{paddingTop:titleHeight+'px'}" @click="chooseVideo()" v-if="video_type != 'link'">
        <image class="upvideo" src="/static/upvideo.png" mode=""></image>
        <image class="hasVido" :src="fromData.cover_image_url" mode="" v-if="fromData.cover_image_url"></image>
        <view class="addBox loading-box" v-if="loading">
                <view class="loading">
                        <u-circle-progress active-color="#333333" width="240" duration="100" :percent="percent">
                                <view class="u-progress-content">
                                        <template v-if="percent==100">
                                                <template v-if="state == 'fail'">
                                                        <view class="u-progress-dot fail"></view>
                                                        <text class='u-progress-info'>上傳失敗</text>
                                                </template>
                                                <template v-else>
                                                        <view class="u-progress-dot success"></view>
                                                        <text class='u-progress-info'>上傳完成</text>
                                                </template>
                                        </template>
                                        <template v-else>
                                                <view class="u-progress-dot"></view>
                                                <text class='u-progress-info'>上傳{{percent}}%</text>
                                        </template>
                                </view>
                        </u-circle-progress>
                </view>
        </view>
        <view class="addBox" v-else>
                <image class="jv" src="/static/jv.png" mode=""></image>
                <view class="text">上傳視頻</view>
                <!-- <view class="texts">MP4格式(手機拍攝),最大50M</view> -->
                <view class="texts">MP4/3GP/M3U8格式(手機拍攝),時長30~120秒</view>
        </view>
</view>
//引入插件
import uploadImage from '@/js_sdk/yushijie-ossutil/ossutil/uploadFile.js';
methods: {
    chooseVideo() { // 上傳視頻
            var that = this;
            uni.chooseVideo({
                    count: 1,
                    compressed: false,
                    sourceType: ['album', 'camera'],
                    success: async function(resVideo) {
                            // let maxSize = (resVideo.size / 1024 / 1024).toFixed(2)
                            // if (maxSize > 50) {
                            // 	uni.showToast({
                            // 		icon: 'none',
                            // 		title: '視頻最大為50M',
                            // 	})
                            // 	return false
                            // }
                            console.log(resVideo);
                            const extension = ["mp4", "3gp", "m3u8"]
                            console.log(resVideo);
                            let maxDuration = resVideo.duration
                            // #ifdef H5
                            const type = resVideo.tempFile.type.split('/')[1]

                            var ua = navigator.userAgent.toLowerCase();
                            var isWeixin = ua.indexOf('micromessenger') != -1;
                            if (isWeixin) {
                                    // 微信瀏覽器內大視頻獲取duration經常性為0,特此進行處理
                                    maxDuration = await that.getDuration(resVideo);
                            }
                            // #endif
                            // #ifdef MP-WEIXIN
                            const index = resVideo.tempFilePath.lastIndexOf('.')
                            const type = resVideo.tempFilePath.slice(index + 1)
                            // #endif

                            if (!extension.includes(type)) {
                                    uni.showToast({
                                            icon: 'none',
                                            title: '視頻格式不支持',
                                    })
                                    return false
                            }

                            if (maxDuration < 30) {
                                    uni.showToast({
                                            icon: 'none',
                                            title: '視頻必須大於30秒',
                                    })
                                    return false
                            }
                            if (maxDuration > 120) {
                                    uni.showToast({
                                            icon: 'none',
                                            title: '視頻最長為2分鍾',
                                    })
                                    return false
                            }
                            const {
                                    url,
                                    policyBase64,
                                    accessid,
                                    signature
                            } = uploadImage


                            that.loading = true
                            that.state = ""
                            that.percent = 0
                            that.fromData.video_url = ""
                            that.fromData.cover_image_url = ""
                            const aliyunFileKey = 'video/' + new Date().getTime() + Math.floor(Math.random() *
                                    150) + '.' + type;
                            let uploadTask = uni.uploadFile({
                                    url, //僅為示例,非真實的接口地址
                                    filePath: resVideo.tempFilePath,
                                    formData: {
                                            'key': aliyunFileKey,
                                            'policy': policyBase64,
                                            'OSSAccessKeyId': accessid,
                                            'signature': signature,
                                            'success_action_status': '200',
                                    },
                                    name: 'file',
                                    success: (uploadFileRes) => {
                                            if (uploadFileRes.statusCode == 200) {
                                                    uni.showToast({
                                                            icon: 'none',
                                                            title: "上傳成功"
                                                    })
                                                    console.log(url + aliyunFileKey);
                                                    that.fromData.video_url = url + aliyunFileKey
                                                    that.fromData.cover_image_url = url + aliyunFileKey +
                                                            "?x-oss-process=video/snapshot,t_1000,f_jpg,m_fast"
                                                    that.state = 'success'
                                                    setTimeout(() => {
                                                            that.percent = 0
                                                            that.loading = false
                                                    }, 3000)
                                            } else {
                                                    that.state = 'fail'
                                                    setTimeout(() => {
                                                            that.percent = 0
                                                            that.loading = false
                                                    }, 3000)
                                                    uni.showToast({
                                                            icon: 'none',
                                                            title: '上傳失敗,請更換視頻或重新上傳'
                                                    })
                                            }
                                    },
                                    fail: (err) => {
                                            console.log(err);
                                            that.state = "fail"
                                            setTimeout(() => {
                                                    that.percent = 0
                                                    that.loading = false
                                            }, 3000)
                                            uni.showToast({
                                                    icon: 'none',
                                                    title: '上傳失敗,請更換視頻或重新上傳'
                                            })

                                    }
                            });


                            uploadTask.onProgressUpdate(that.onProgressUpdate((res) => {
                                    // console.log('上傳進度' + res.progress);
                                    // console.log('已經上傳的數據長度' + res.totalBytesSent);
                                    // console.log('預期需要上傳的數據總長度' + res.totalBytesExpectedToSend);
                                    that.percent = res.progress
                            }, 100))
                    }
            })
    },
    onProgressUpdate(func, wait = 50) {
            // 上一次執行該函數的時間
            let lastTime = 0
            return function(...args) {
                    // 當前時間
                    let now = +new Date()
                    // 將當前時間和上一次執行函數時間對比
                    // 如果差值大於設置的等待時間就執行函數
                    if (now - lastTime > wait) {
                            lastTime = now
                            func.apply(this, args)
                    }
            }
    },
}

H5端的話要考慮AccessKeySecret以及OSSAccessKeyId暴露的問題,可以考慮單獨用ali-oss.js庫單獨進行處理,或者做好OSS的域名設置防止跨域請求。

PC端

PC端使用了ali-oss庫進行開發

插件修改

/**
 * 阿里雲oss上傳工具
 */
let OSS = require('ali-oss');
let config = {
    region: 'oss-cn-hangzhou',
    secure:true,
    accessKeyId: '',
    accessKeySecret: '',
    bucket: ''
};

/** 
 * 配置
 */
let init = ()=>{
    return new OSS(config);
}

/** 
 * 生成uuid
 */
let guid = ()=>{
    let S4 = ()=>{
        return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
    }
    return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}

/** 
 * 修改文件名字
 */
let fileName = (file)=>{
    let arr = file.name.split(".");
    var uuid = "oss"+guid();
    if(arr.length>1){
        return uuid+'.'+arr[arr.length-1];
    }else{
        return uuid;
    }
}

/**
 * 上傳文件
 */
let ossPut = (file,callback)=>{
    return new Promise((resolve, reject) => {
			
        let objectName = fileName(file);
        //videos/指的是放在oss的videos目錄下
        init().multipartUpload('videos/'+objectName, file,{
					progress(p){ // 獲取上傳進度,上傳進度為0 - 1, 1為上傳100%
							callback(p)
					}
				}).then(({res,url}) => {
            if (res && res.status == 200) {
                console.log('阿里雲OSS上傳文件成功回調', res,url);
                resolve(res,url);
            }
        }).catch((err) => {
            console.log('阿里雲OSS上傳文件失敗回調', err);
            reject(err)
        });
    })
}

/**
 * 下載文件
 */
let ossGet = (name)=>{
    return new Promise((resolve, reject) => {
        init().get(name).then(({res}) => {
            if (res && res.status == 200) {
                console.log('阿里雲OSS下載文件成功回調', res);
                resolve(res);
            }
        }).catch((err) => {
            console.log('阿里雲OSS下載文件失敗回調', err);
            reject(err)
        });
    })
}

export default {ossPut,ossGet}

使用

import ossClient from './api/aliyun.oss.client.js';
Vue.prototype.$ossClient = ossClient;
<el-upload
    class="avatar-uploader"
    accept=".mp4,.3gp,.m3u8"
    :http-request="uploadHttp"
    action
    :show-file-list="false"
    :before-upload="beforeUploadVideo"
    >
    <img v-if="postData.cover_image_url" :src="postData.cover_image_url" class="avatar">
    <div v-else class="upIocn">
            <div v-if="videoFlag == false">
                    <img class="add" src="../assets/add.png" alt="">
                    <div class="tip">上傳視頻</div>
                    <div class="msg">MP4格式(手機拍攝),最長2分鍾,最短30秒</div>
            </div>
    </div>
    <el-progress class="progress" v-if="videoFlag == true" type="circle" :percentage="parseFloat(videoUploadPercent)" />
</el-upload>
methods:{
    beforeUploadVideo(file){
            const that = this
            return new Promise((resolve,reject)=>{
                    let url = URL.createObjectURL(file);
                    let audioElement = new Audio(url);
                    audioElement.addEventListener("loadedmetadata", (_event)=> {
                            console.log(_event);
                            // let maxSize = (file.size / 1024 / 1024).toFixed(2)
                            // if (maxSize > 50) {
                            // 	that.$message({
                            // 		type: 'error',
                            // 		message: '視頻最大為50M',
                            // 	})
                            // 	reject(false)
                            // 	return
                            // }
                            let duration = audioElement.duration;
                            console.log('duration',duration);
                            // 大小 時長
                            if (duration < 30) {
                                    //超過需求限制
                                    that.$message({
                                            message: '視頻必須大於30秒',
                                            type: 'error'
                                    });
                                    reject(false)
                                    return
                            }
                            if (duration > 120) {
                                    //超過需求限制
                                    that.$message({
                                            message: '視頻最長為2分鍾',
                                            type: 'error'
                                    });
                                    reject(false)
                                    return
                            }
                            resolve(true)
                    });
            })
    },
    coverImageSuccess(e) {
      this.postData.cover_image_url = e.data.cover_image_url
			this.postData.video_url = e.data.video_url
			this.videoFlag = false
    },
    progress(p){
            let pecent = p * 100
            this.videoFlag = true
            this.videoUploadPercent = pecent.toFixed(0)
    },
    uploadHttp({ file }) {
            this.postData.cover_image_url = ""
            this.postData.video_url = ""
            this.videoFlag = true
            try{
                    this.$ossClient.ossPut(file, this.progress).then(res=>{
                            if(res.statusCode==200){
                                    let video_url = res.requestUrls[0]
                                    let index = video_url.lastIndexOf('?')
                                    video_url = video_url.slice(0, index)

                                    const cover_image_url = video_url + "?x-oss-process=video/snapshot,t_1000,f_jpg,m_fast"
                                    const data = {
                                            video_url,
                                            cover_image_url
                                    }
                                    console.log('data');
                                    this.coverImageSuccess({
                                            data
                                    })
                            }else{
                                    this.$message.error('上傳失敗,請更換視頻或重新上傳')
                            }
                    })
            }catch(e){
                    //TODO handle the exception
                    this.$message.error('上傳失敗,請更換視頻或重新上傳')
            }

    },    
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM