七牛直傳文件

官方 API 地址: https://developer.qiniu.com/kodo/sdk/1283/javascript

由于七牛提供了 JavaScript 上傳的插件,本文將采用該插件實(shí)現(xiàn)七牛直傳。
1. 獲取七牛上傳憑證
  • 與阿里云OSS直傳不同的是敲董,七牛只需要獲取一個(gè) token 即可(本質(zhì)是一樣的)顽染。

  • 獲取需要后端的配合,提供一個(gè)接口狸臣。

  • 要注意獲取和保存的時(shí)機(jī)莹桅,根據(jù)過期時(shí)間記錄相關(guān) sessionStorage 比較好,既能減少請求的次數(shù)烛亦,又能保證上傳時(shí)憑證有效诈泼。

  • 獲取 token 流程:

(1)發(fā)送獲取 token 請求 --> (2)得到 token 并本地保存懂拾,用于之后的直傳 --> (3)帶著 token 參數(shù)向七牛發(fā)送請求,直傳圖片 --> (4) 上傳成功铐达,七牛會返回相應(yīng)的圖片地址(這個(gè)地址后續(xù)需要按照一定規(guī)則傳給后端)

  • 獲取 token 示例
/**
    * 獲取七牛圖片上傳 token (進(jìn)入相關(guān)頁面預(yù)先請求)
    * @returns {Promise.<T>}
*/
const getUploadToken = () => {
    http.post('getUploadToken').then(res => {
        if (res.code === 200) {
            this.isPull = false;
            const expireTime = 30 * 60 * 1000; // 過期時(shí)間岖赋,單位毫秒(ms)
            sessionStorage.setItem('uploadToken', res.data.upload_token);
            sessionStorage.setItem('uploadTokenExpireTime', new Date().getTime() + expireTime); // 設(shè)置 token 過期時(shí)間
            sessionStorage.setItem('imgURL', res.data.image_url); // 暫存圖片地址
        } else {
            // 錯(cuò)誤統(tǒng)一處理函數(shù)
            this.errorHandler(res);
        }
    }).catch(() => {
        console.log('error');
    });
}

2. 圖片處理
  • 這里即將使用的 qiniu.min.js 也包含了圖片裁剪、縮放瓮孙、旋轉(zhuǎn)等功能唐断。有興趣的可以看下這個(gè)文檔:七牛 - 圖片高級處理

這里介紹一下自己實(shí)現(xiàn)圖片的簡單處理。

(1) 壓縮圖片在 iOS 上存在一個(gè)旋轉(zhuǎn)的問題衷畦,需要先做處理栗涂,這里引入了 exif-js

import Exif from 'exif-js'

(2)圖片旋轉(zhuǎn)

/**
 *  iOS 旋轉(zhuǎn)圖片
 * @param img  圖片
 * @param direction 旋轉(zhuǎn)方向
 * @param canvas 畫布
 */
const rotateImg = (img, direction, canvas) => {
    // 最小與最大旋轉(zhuǎn)方向,圖片旋轉(zhuǎn)4次后回到原方向
    const min_step = 0;
    const max_step = 3;
    if (img === null) return;
    // img 的高度和寬度不能在 img 元素隱藏后獲取祈争,否則會出錯(cuò)
    let height = img.height;
    let width = img.width;
    let step = 2;
    if (step === null) {
        step = min_step;
    }
    if (direction === 'right') {
        step++;
        // 旋轉(zhuǎn)到原位置斤程,即超過最大值
        step > max_step && (step = min_step);
    } else {
        step--;
        step < min_step && (step = max_step);
    }
    // 旋轉(zhuǎn)角度以弧度值為參數(shù)
    let degree = step * 90 * Math.PI / 180;
    let ctx = canvas.getContext('2d');
    switch (step) {
        case 0:
            canvas.width = width;
            canvas.height = height;
            ctx.drawImage(img, 0, 0);
            break;
        case 1:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, 0, -height);
            break;
        case 2:
            canvas.width = width;
            canvas.height = height;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, -height);
            break;
        case 3:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, 0);
            break;
    }
}

(3)圖片壓縮

/*
 * canvas 壓縮圖片
 * @param img 圖片
 * @param Orientation 旋轉(zhuǎn)參數(shù)
 * @returns ndata 壓縮后的數(shù)據(jù)
*/
const compress = (img, Orientation) => {
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext('2d');
    //瓦片canvas
    let tCanvas = document.createElement("canvas");
    let tctx = tCanvas.getContext("2d");
    let initSize = img.src.length;
    let width = img.width;
    let height = img.height;
    let time = new Date().getTime();
    // 如果圖片大于四百萬像素,計(jì)算壓縮比并將大小壓至400萬以下
    let ratio;
    if ((ratio = width * height / 4000000) > 1) {
        console.log("大于400萬像素");
        ratio = Math.sqrt(ratio);
        width /= ratio;
        height /= ratio;
    } else {
        ratio = 1;
    }
    canvas.width = width;
    canvas.height = height;
    // 鋪底色
    ctx.fillStyle = "#fff";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    // 如果圖片像素大于100萬則使用瓦片繪制
    let count;
    if ((count = width * height / 1000000) > 1) {
        console.log("超過100W像素");
        count = ~~(Math.sqrt(count) + 1); //計(jì)算要分成多少塊瓦片
        // 計(jì)算每塊瓦片的寬和高
        let nw = ~~(width / count);
        let nh = ~~(height / count);
        tCanvas.width = nw;
        tCanvas.height = nh;
        for (let i = 0; i < count; i++) {
            for (let j = 0; j < count; j++) {
                tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
                ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
            }
        }
    } else {
        ctx.drawImage(img, 0, 0, width, height);
    }
    // 修復(fù)ios上傳圖片的時(shí)候 被旋轉(zhuǎn)的問題
    if (Orientation != '' && Orientation != 1) {
        switch (Orientation) {
            case 6: // 需要順時(shí)針(向左)90度旋轉(zhuǎn)
                this.rotateImg(img, 'left', canvas);
                break;
            case 8: // 需要逆時(shí)針(向右)90度旋轉(zhuǎn)
                this.rotateImg(img, 'right', canvas);
                break;
            case 3: // 需要180度旋轉(zhuǎn)
                this.rotateImg(img, 'right', canvas);//轉(zhuǎn)兩次
                this.rotateImg(img, 'right', canvas);
                break;
        }
    }
    // 進(jìn)行最小壓縮
    let ndata = canvas.toDataURL('image/jpeg', 0.1);
    console.log('壓縮前:' + initSize);
    console.log('壓縮后:' + ndata.length);
    console.log('壓縮率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%");
    tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
    console.log('使用時(shí)間:' + (new Date().getTime() - time) + 'ms');
    return ndata;
}

(4) 將 base64 圖片編碼轉(zhuǎn)為 Blob 對象

/*
 * 將 base64 圖片編碼轉(zhuǎn)為 Blob 對象
 * @dataURI 圖片 base64 編碼
 * @returns {Blob}
*/
const dataURItoBlob = (dataURI) => {
    let byteString = atob(dataURI.split(',')[1]);
    let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
    let ab = new ArrayBuffer(byteString.length);
    let ia = new Uint8Array(ab);
    for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], {type: mimeString});
}

3. 七牛上傳方法

(1)引入七牛

import * as qiniu from 'qiniu-js'

(2)上傳方法

 /**
 *  七牛上傳
 *  @param base64String 文件信息(Blob 或 base64String)
 */
const qiniuUpload = (base64String) => {
     // 由于有些大圖進(jìn)行壓縮后變成了 base64String菩混,而小圖沒有經(jīng)過處理仍是 Blob 對象忿墅,七牛接收的參數(shù)是 Blob 對象,故對字符串類型的參數(shù)進(jìn)行轉(zhuǎn)換
     let file;
     if (typeof base64String === 'string'){
         file = this.dataURItoBlob(base64String);
     } else {
         file = base64String;
     }
    // 七牛上傳的方法
    // 第一個(gè)參數(shù)是文件對象(Blob)沮峡,第二個(gè)參數(shù)是文件名稱(這個(gè)名稱不能相同疚脐,命名時(shí)候請加入時(shí)間搓,確保文件名稱唯一)邢疙,第三個(gè)參數(shù)是 token
    const observable = qiniu.upload(file, file.name, sessionStorage.getItem('uploadToken'), {
        fname: file.name,
        params: {},
        mimeType: [] || null
    }, {
         useCdnDomain: true, // cdn
         unique_names: false, // 唯一名稱
         region: qiniu.region.z0 // 根據(jù)區(qū)域設(shè)置上傳的域名
    });

    // 返回 Promise棍弄,在里面執(zhí)行七牛直傳
    return new Promise( (resolve, reject) => {
        const subscription = observable.subscribe({
            next (res) {
                // 上傳的進(jìn)度,可以獲得總大小疟游,已傳大小呼畸,上傳百分比進(jìn)度
            },
            error (err) {
                console.log('失敗');
                console.log(err);
            },
            complete (res) {
                // 成功后,回調(diào)操作
                console.log('上傳成功');
                resolve({
                    base64String: base64String,
                    imgUrl: sessionStorage.getItem('imgURL')+'/'+ res.key
                });
            }
        });
    });
}

4. 前面所做的都是準(zhǔn)備工作颁虐,真正觸發(fā)上傳的在這里
/*
 * 讀文件(<input type="file"> 由 change 事件觸發(fā))
 * @param file 文件 Blob 對象蛮原,由 change 事件觸發(fā)后傳入該參數(shù)
 * @returns {Promise}
*/
const readFiles = (file) => {
    // 首先判斷 token 是否過期
    async function getToken() {
        if (new Date().getTime() > sessionStorage.getItem('uploadTokenExpireTime')) {
            getUploadToken();
        }
    }

    return getToken().then(() => {
        let Orientation;
        let base64String = '';
        const reader = new FileReader(); // 創(chuàng)建 FileReader 對象 并調(diào)用 render 函數(shù)來完成渲染

        // 將圖片將轉(zhuǎn)成 base64 格式
        base64String = reader.readAsDataURL(file);
        // iOS 旋轉(zhuǎn)問題
        Exif.getData(file, function () {
            Orientation = Exif.getTag(this, 'Orientation');
        });

        // 讀取后壓縮文件
        return new Promise(resolve => {
            reader.onloadend = function () {
                let result = this.result;
                let img = new Image();
                img.src = result;
                //判斷圖片是否大于100K,是就直接上傳,反之壓縮圖片
                if (this.result.length <= (100 * 1024)) {
                    resolve(qiniuUpload(result));
                } else {
                    img.onload = function () {
                        let data = that.compress(img, Orientation);
                        resolve(that.qiniuUpload(data));
                    }
                }
            };
        });
    });
}

在低版本瀏覽器使用上存在一定的兼容問題另绩;

上面的方法使用了 es6 語法儒陨;

上面的方法不能搬來直接使用,需要做一點(diǎn)小改動笋籽。

5. 使用 readFile 后會返回七牛的回調(diào)數(shù)據(jù)蹦漠,在 readFile().then(res => {...}) 寫相應(yīng)操作即可
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市车海,隨后出現(xiàn)的幾起案子津辩,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喘沿,死亡現(xiàn)場離奇詭異闸度,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚜印,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門莺禁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窄赋,你說我怎么就攤上這事哟冬。” “怎么了忆绰?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵浩峡,是天一觀的道長。 經(jīng)常有香客問我错敢,道長翰灾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任稚茅,我火速辦了婚禮纸淮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘亚享。我一直安慰自己咽块,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布欺税。 她就那樣靜靜地躺著侈沪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晚凿。 梳的紋絲不亂的頭發(fā)上峭竣,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音晃虫,去河邊找鬼。 笑死扣墩,一個(gè)胖子當(dāng)著我的面吹牛哲银,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呻惕,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荆责,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亚脆?” 一聲冷哼從身側(cè)響起做院,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后键耕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寺滚,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年屈雄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了村视。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酒奶,死狀恐怖蚁孔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惋嚎,我是刑警寧澤杠氢,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站另伍,受9級特大地震影響鼻百,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜质况,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一愕宋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧结榄,春花似錦中贝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至视哑,卻和暖如春绣否,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挡毅。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工蒜撮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人跪呈。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓段磨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耗绿。 傳聞我的和親對象是個(gè)殘疾皇子苹支,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容