天天看點

uniapp阿裡雲STS上傳檔案

文章目錄

  • ​​uniapp阿裡雲STS上傳檔案​​
  • 需求
  • 問題所在
  • 1. 我們使用uniapp作為APP開發工具,阿裡雲OSS提供的一些SDK不兼
  • 2. 後端需要單獨開發一個給前端調用OSS接口擷取token的接口
  • 可能遇到的問題
  • 1. OSS老是提示跨域
  • 2. uniapp的代碼問題
  • 代碼部分
  • 1. 需要引用的依賴
  • ``npm install js-base64``
  • ``npm install crypto-js``
  • 2. 前端代碼
  • 上傳檔案入口程式
  • base64.js
  • 上傳檔案JS檔案
  • 尾巴
  • 鳴謝

uniapp阿裡雲STS上傳檔案

使用uniapp開發的時候就會遇到有上傳圖檔視訊之類的需求,但是我們實際的伺服器帶寬是有限的,而且如果是先上傳到伺服器在從伺服器上傳到OSS也是沒有太大意義,是以我們就可以直接從APP裡面将檔案上傳到OSS伺服器,然後儲存上傳後的檔案位址即可。

使用者上傳檔案 ---> OSS伺服器 ---> [得到檔案的URL] ---> 背景伺服器記錄檔案資訊
      

一開始直接用官方提供node.js的SDK (ali-oss) 處理檔案的時候是沒有任何問題的,可以直接上傳檔案,且配置比較簡單,隻需要背景傳回一些配置資訊即可,但是後面我打包APP的時候發現出現錯誤,頁面打不開;看日志也隻能看到一些粗略的東西,大概意思就是裡面使用了dom對象是以沒辦法初始化。如果是不需要打包app的話是可以直接使用的,可以參考這個文檔  

意思就是我們不能直接把我們的accessKeyId 和 accessKeySecret 傳回給前台調用,這樣不安全。為此OSS也提供了臨時通路憑證,配置起來比較麻煩,大家可以參考這個文檔 ​​ ;跟着文檔一步一步來還是可以搞定的。

這裡就正常參照文檔提示的跨域配置即可, 參考官方提供的文檔  重點來了一定一定使用的上傳的位址是你的​

​Bucket域名​

​ 而非 ​

​Endpoint​

​ 這裡我是被坑的最慘的時候了,老是提示跨域一直搞一直錯。

就是導入庫存在的各種問題,這裡目前我遇到的就是使用​

​js-base64​

​ 這個庫的時候一直都是不行的,我也一直都沒有搞定,找了别人的一段代碼搞定的,不用 ​

​js-base64​

這裡就隻提供uniapp相關的前端代碼,後端代碼其實比較簡答,主要就是各種阿裡雲上的配置比較麻煩,有需要可以照着文檔一步一步來。

​npm install js-base64​

 ​

​npm install crypto-js​

這兩個都是用來做加密的,js-base64可以用或者不用

<template>
<view>
<button type="default" @click="selectImages">這是一個大大的按鈕</button>
</view>
</template>
<script>

import UploadOss from 'utils/UploadOss'
export default {
methods: {
/**
     * 選擇上傳圖檔
     */
selectImages() {
uni.chooseImage({
//預設9
count: 1,
//可以指定是原圖還是壓縮圖,預設二者都有
sizeType: ['original', 'compressed'],
//從相冊選擇
sourceType: ['album'],
success: function (res) {
console.log(res);
UploadOss.uploadFile(res).then(res => {
console.log('上傳檔案的結果為:', res)
          })
        }
      });
    }
  }
}
</script>


      

這裡主要是因為我使用 ​

​js-base64​

​ 庫會出問題, 如果你的不會則不需要此段代碼,可以直接使用​

​js-base64​

​的庫

// 私鑰
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// 加密
const encode = function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = _utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
enc4 = 64;
        }
output = output +
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
    }
return output;
}
// 解密
const decode = (input) => {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
if (input == undefined || input == null) {

    } else {
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++));
enc2 = _keyStr.indexOf(input.charAt(i++));
enc3 = _keyStr.indexOf(input.charAt(i++));
enc4 = _keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
            }
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
            }
        }
output = _utf8_decode(output);
return output;
    }

}
// private method for UTF-8 encoding
const _utf8_encode = (string) => {
string = string.replace(/\r\n/g, "\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
        } else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
        } else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
        }
    }
return utftext;
}
// private method for UTF-8 decoding
const _utf8_decode = (utftext) => {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
var c1 = 0;
var c2 = 0;
var c3 = 0;
while (i < utftext.length) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
        } else if ((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i + 1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
        } else {
c2 = utftext.charCodeAt(i + 1);
c3 = utftext.charCodeAt(i + 2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
        }
    }
return string;
}

module.exports = {
decode: decode,
encode: encode
};

      

具體業務邏輯JS中有描述,這裡不做贅述

const crypto = require('crypto-js')
// 這裡主要是因為我使用 js-base64 庫會出問題, 如果你的不會則不需要此段代碼,可以直接使用js-base64的庫
// const base64 = require('js-base64')
const base64 = require('./base64s')
// 擷取STS參數資訊的接口位址
const tokenurl = 'https://baicu.com/gettoken';
// 本地緩存資料的key
const cacheKey = "xxxxtoken";
// 本地資料有效期
const validtimes = 1000 * 100;

/**
 * 上傳檔案
 * @param fileobj
 * @returns {Promise<unknown>}
 */
export function uploadFile(fileobj) {
const filename = fileobj.tempFiles[0].name;
const filepath = fileobj.tempFilePaths[0];
return new Promise((resolve, reject) => {
getFormData(filename).then(data => {
uni.uploadFile({
//背景傳回的阿裡雲存儲的上傳位址
url: data.host,
filePath: filepath,
fileType: "image",
name: 'file',
formData: data.formdata,
success: res => {
uni.hideLoading();
uni.showToast({title: '上傳成功', icon: 'success', duration: 2000});
console.log(data.host + data.formdata.key);
resolve(data.host + data.formdata.key);
                },
fail: err => {
reject(err)
uni.hideLoading();
uni.showModal({
content: err.errMsg,
showCancel: false
                    });
                }
            });
        })
    })
}

/**
 * 擷取上傳檔案需要的formdata資料
 */
const getFormData = (filename) => {
// 先從本地擷取token
return new Promise((resolve => {
getToken().then(res => {
// 擷取到的token為
console.log('擷取到的token為', res)
const ossAccessKeyId = res.accessKeyId;
const securityToken = res.stsToken;
// 這裡遇到過坑, 上傳檔案的路徑一定是bucket域名
const aliyunServerURL = 'https://' + res.bucket + '.' + res.endpoint;
const policy = getPolicyBase64(5, 150)
const signature = computeSignature(res.accessKeySecret, policy);
const aliyunFileKey = getOssPath(filename)

const formdata = {
//檔案名
key: aliyunFileKey,
//背景擷取逾時時間
policy: policy,
//背景擷取臨時ID
OSSAccessKeyId: ossAccessKeyId,
// 如果是STS的話這裡的參數必須要
'x-oss-security-token': securityToken,
//讓服務端傳回200,不然,預設會傳回204
success_action_status: '200',
//背景擷取簽名
signature: signature
            };
let data = {
host: aliyunServerURL,
formdata: formdata
            }
resolve(data);
        })
    }))
}

/**
 * 擷取token資訊的方法
 */
const getToken = () => {
return new Promise((resolve, reject) => {
// 先從緩存中擷取資料
const tempData = uni.getStorageSync(cacheKey);
// 是否有效
let isvalid = false;
if (tempData && tempData !== '') {
// 資料存在,判斷資料是否過期
const tokenObj = JSON.parse(tempData);
// 判斷是否過期 (createtime:是後端指點的建立時間字段)
var diff = new Date().getTime() - Number.parseFloat(tokenObj.createtime);
// 判斷資料是否過期
isvalid = diff < validtimes;
        }

if (isvalid) {
// 資料有效且未過期則使用緩存中的資料
resolve(JSON.parse(tempData));
        } else {
// 無有效資料或資料已過期則從新擷取資料
getToken4Api(cacheKey).then(res => {
resolve(res);
            })
        }
    })
}


/**
 * 從網關擷取資料
 * @param cacheKey
 */
const getToken4Api = (cacheKey) => {
return new Promise((resolve, reject) => {
uni.request({
url: tokenurl,
success: (res) => {
if (res.data.status === 200) {
// 将資料存到本地存儲, 由于token資訊是有有效時間的,
uni.setStorageSync(cacheKey, JSON.stringify(res.data.data))
resolve(res.data.data)
                }
            },
fail: (err => {
reject(err);
            })
        })
    })
}

/**
 * 設定OSS的存儲路徑
 * @param filename 檔案名稱
 * @returns {string}
 */
const getOssPath = (filename) => {
const current_timestamp = new Date().getTime()
const upload_name = crypto.MD5(current_timestamp + Math.random() * 10000) + filename;
const current_day = dateformat('YYYYmmdd', new Date())
return 'files/' + current_day + '/' + upload_name
}

/**
 * 日期格式化方法
 * @param fmt ex: yyyy-MM-dd HH:mm:ss
 * @param date new Date()
 * @returns {*}
 */
const dateformat = (fmt, date) => {
let ret;
const opt = {
"Y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"H+": date.getHours().toString(), // 時
"M+": date.getMinutes().toString(), // 分
"S+": date.getSeconds().toString() // 秒
// 有其他格式化字元需求可以繼續添加,必須轉化成字元串
    };
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length === 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
        }
    }
return fmt;
}

/**
 * 安全政策需要轉成base64
 * @param timeout 逾時時間
 * @param maxsize 檔案大小限制
 * @returns {*}
 */
const getPolicyBase64 = function (timeout, maxsize) {
const date = new Date()
// 限制參數的生效時間(機關:小時)。
const timeOut = timeout || 1
// 限制上傳檔案大小(機關:MB)。 1MB = 1048576B
const maxSize = maxsize || 10
date.setHours(date.getHours() + timeOut)
const policyText = {
expiration: date.toISOString(),
conditions: [
            ['content-length-range', 0, maxSize * 1024 * 1024]
        ]
    }
// policy必須為base64的string。
return base64.encode(JSON.stringify(policyText))
}

/**
 * 計算簽名
 * @param accessKeySecret
 * @param canonicalString
 * @returns {string}
 */
const computeSignature = function (accessKeySecret, canonicalString) {
return crypto.enc.Base64.stringify(
crypto.HmacSHA1(canonicalString, accessKeySecret)
    )
}


module.exports = {
uploadFile: uploadFile
};