0


uniapp+vue3前端直传图片至阿里oss,附前端增加oss水印

文章目录


前言

由于公司项目后端带宽吃紧,遂要求在前端直传图片至阿里oss,不调用后端接口先传至后端再上传,同时项目需要在某些图片上传后用户看到的图片是自带水印的。关于水印这个问题找了很多帖子都不太适用,于是还是自己封装一个前端直传oss的组件达成需求。

阿里oss上传所需关键的bucketName,accessid和secret,需要替换成自己的,怎么获取的教程其他帖子都有,就偷个懒不贴了。

废话不多说,直接下面贴代码,详细和完整都贴,兄弟们按需求cv(笑)。


一、前端直传oss

在common目录下创建alUpload.js文件

1.传统直传

因为需求该变更,项目中密匙通过后端接口获取,贴传统方式仅供参考


import {
    Crypto,
    Base64
} from '@/common/bundle.js';//引入下面第二段的工具代码//传统方式直传
export const alUploadImage =function(path, showLoading = true){return new Promise((resolve, reject)=>{
        let date = new Date(new Date().getTime()+1000*3600);
        let expiration = date.toISOString();//let expiration = date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate()+'T'+date.getHours()+':'+date.getMinutes()+':00.000Z';
        let policyText ={
            expiration,//设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了"conditions":[["content-length-range",0,1048576000]// 设置上传文件的大小限制]};
        let bucketName ='*********';//你的bucketName
        let policyBase64 = Base64.encode(JSON.stringify(policyText))
        let accessid ='*********';//你的阿里oss accessid
        let accesskey ='*********';//你的阿里oss secret
        let host ='http://'+ bucketName +'.oss-cn-chengdu.aliyuncs.com';//上传oss地址,注意地区对不对,我这示例给的是shanghai
        let bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, accesskey,{
            asBytes: true
        });
        let signature = Crypto.util.bytesToBase64(bytes);//签名// 在阿里云存储路径=>文件地址,地址根据需求生成,下方仅为示例
        let fileName ='App/'+'建议使用dayJs'+'/'+'随便你打什么都行,建议使用random函数生成随机字数'+'.'+ path
            .split('.').pop();//文件名 注意:相同文件名会覆盖之前的文件if(showLoading) uni.showLoading({
            mask: true,
            title:'上传中..'})
        uni.uploadFile({
            url: host,
            filePath: path,
            fileType:'',
            name:'file',
            formData:{
                name: fileName,
                key: fileName,//文件名
                policy: policyBase64,// 输入你获取的的policy
                OSSAccessKeyId: accessid,// 输入你的AccessKeyId
                success_action_status:'200',// 让服务端返回200,不然,默认会返回204
                signature,// 输入你获取的的signature},
            success:(res)=>{if(showLoading) uni.hideLoading()
                res.file = host +'/'+ fileName
                resolve(res)},
            fail:(res)=>{if(showLoading) uni.hideLoading()reject(false)}})})}

2.后端参数直传oss

因为项目安全问题,项目中密匙通过后端接口获取,获得索需的参数,对应直传里的policy,OSSAccessKeyId等参数,initOssPolicy()为调用后端接口拿到参数

// 获取Oss上传签名
export const uploadOss =(uploadRequestOptions)=>{return new Promise((resolve)=>{
        let ossPolicy ={}const file = uploadRequestOptions
        initOssPolicy(file.url).then(resp =>{//调用下面的后端接口获取参数
            Object.assign(ossPolicy, resp.data.data)alUpload(file, ossPolicy).then(res =>{
                console.log("上传后res", res)if(res.errMsg =="uploadFile:ok"){
                    console.log("resp.data.data--------", resp.data.data)resolve(resp.data.data)}})})})}//上传
export const alUpload =function(file, ossPolicy){return new Promise((resolve, reject)=>{
        uni.uploadFile({
            url: ossPolicy.host,
            fileType:'',
            name:'file',
            filePath: file.url,
            formData:{'name': file.url,'key': ossPolicy.dir + ossPolicy.fileName,'policy': ossPolicy.policy,'signature': ossPolicy.signature,'host': ossPolicy.host,'dir': ossPolicy.dir,'success_action_status':'200','Content-Disposition': ossPolicy.contentDisposition,'x-oss-content-type': ossPolicy.contentType,'OSSAccessKeyId': ossPolicy.accessKeyId,},
            success:(res)=>{// console.log("成功****************sesss", res)resolve(res)},
            fail:(res)=>{// console.log("失败*************fail", res)reject(false)}})})}

3.调用后端获取所需要的参数

因为项目安全问题,项目中密匙通过后端接口获取,获得索需的参数,对应直传里的policy,OSSAccessKeyId等参数,

//调取后端接口拿到参数
export const initOssPolicy =(fileName)=>{return new Promise((resolve, reject)=>{
        uni.request({
            url: 'https://s.mga1.cn/saas-api/sys/file/getOssPolicy?fileName=' + fileName,
            success:(res)=>{// console.log("回调-----sesss", res)resolve(res)},
            fail:(res)=>{// console.log("回调-----fail", res)reject(false)}})})}//接口回调:{accessKeyId:"让你们后端给阿里oss的accessKeyId"
contentDisposition:"inline;filename=bv7OceYfGTwx2fced1094491a9a5c05316c520cc6387_171356.jpeg"
contentType:"image/jpeg"
dir:"20240530/"
fileName:"让你们后端给文件的名字"
fileUrl:"让你们后端给上传后的文件路径"
host:"https://换成你自己的bucketName.oss-cn-chengdu.aliyuncs.com"
policy:"eyJleHBpcmF0aW9uIjoiMjAyNC0wNS0zMFQwOToyMzo1Ni41NjNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0seyJDb250ZW50LURpc3Bvc2l0aW9uIjoiaW5saW5lO2ZpbGVuYW1lPWJ2N09jZVlmR1R3eDJmY2VkMTA5NDQ5MWE5YTVjMDUzMTZjNTIwY2M2Mzg3XzE3MTM1Ni5qcGVnIn0seyJDb250ZW50LVR5cGUiOiJpbWFnZS9qcGVnIn0seyJzdWNjZXNzX2FjdGlvbl9zdGF0dXMiOiIyMDAifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsIjIwMjQwNTMwLyJdXX0="
signature:"JRfgVnCd8y5NgeZJtwayK3mSAfM="}

4.业务文件中使用

业务文件demo.vue中使用如下(示例):

<script setup>
    import {
        uploadOss,
        uploadOssWatermark
    } from '@/common/alUpload.js'
    import dayjs from "dayjs"const paramsStore =paramasUserStore()const{
        proxy
    }=getCurrentInstance()//选择图片const chooseImg =(){
                uni.chooseImage({
                    success:function(res){
                        console.log(res.tempFiles[0]);const result = await uploadFilePromise(res.tempFiles[0])
                        console.log(result.fileUrl);//返回结果对象可以你自己定义}})}//上传文件const uploadFilePromise =(url)=>{return new Promise((resolve, reject)=>{uploadOssWatermark(url).then(res =>{
                        console.log('上传回调--------------', res.fileUrl)resolve(res);})});};</script>

二、添加水印

利用阿里云 OSS 给图片加水印很简单,只需要在图片 URL 的后面跟上一对水印参数就行,前端访问这个url的时候看到的就是带水印的图片

提示:链接直接浏览器访问即可看到效果

例如:
在这里插入图片描述在这里插入图片描述
原始图片:https://oss.mga1.cn/20240530/jo1qy2cLRYCM9cd16bdbf9d9c7786d0b421d65c01d35_175244.jpeg

水印图片:
https://oss.mga1.cn/20240530/jo1qy2cLRYCM9cd16bdbf9d9c7786d0b421d65c01d35_175244.jpeg?x-oss-process=image/watermark,text_MTc6NTI,g_sw,p_15,color_FFFFFF,size_60,x_100,y_120/watermark,text_fA==,g_sw,color_FFFF00,size_80,x_280,y_100/watermark,text_MjAyNC0wNS0zMA=,g_sw,p_15,color_FFFFFF,size_40,x_300,y_150/watermark,text_57Gz5p6c56CB5rWL6K-V,g_sw,p_15,color_FFFFFF,size_40,x_300,y_100/watermark,text_5oiQ6YO95biC6Z2S576K5Yy65YyX5omT6ZOc6KGXOOWPtw=,g_sw,p_15,color_FFFFFF,size_40,x_100,y_50/watermark,text_5bCP572X,g_se,p_15,color_FFFFFF,size_40,x_100,y_100/

1.水印上传

直接用下方代码替换上面的uploadOss()即可

export const uploadOssWatermark =(uploadRequestOptions, time, addr, date, man, shopName)=>{return new Promise((resolve)=>{
        let ossPolicy ={}const file = uploadRequestOptions
        initOssPolicy(file.url).then(resp =>{
            Object.assign(ossPolicy, resp.data.data)alUploadWatermark(file, ossPolicy, time, addr, date, man, shopName).then(res =>{
                console.log("上传后res", res)if(res.errMsg =="uploadFile:ok"){//添加水印
                    let mid ="|"
                    resp.data.data.fileUrl = resp.data.data.fileUrl +"?x-oss-process=image/watermark,text_"+
                        Base64.encode(time).replace('+','-').replace('/','_').replace('=','')+",g_sw,p_15,color_FFFFFF,size_60,x_100,y_120/watermark,text_"+
                        Base64.encode(mid)+",g_sw,color_FFFF00,size_80,x_280,y_100/watermark,text_"+
                        Base64.encode(date).replace('+','-').replace('/','_').replace('=','')+",g_sw,p_15,color_FFFFFF,size_40,x_300,y_150/watermark,text_"+
                        Base64.encode(shopName).replace('+','-').replace('/','_').replace('=','')+",g_sw,p_15,color_FFFFFF,size_40,x_300,y_100/watermark,text_"+
                        Base64.encode(addr).replace('+','-').replace('/','_').replace('=','')+",g_sw,p_15,color_FFFFFF,size_40,x_100,y_50/watermark,text_"+
                        Base64.encode(man).replace('+','-').replace('/','_').replace('=','')+",g_se,p_15,color_FFFFFF,size_40,x_100,y_100/"

                    console.log("resp.data.data--------", Base64.encode(time),'*******',
                        Base64.encode(addr).replace('+','-'),)resolve(resp.data.data)}})})})}//2024.7.16优化,最近发现一个小bug,如果特殊字符“+”,“/”,“=”这几个字符在转义后出现多次,原写法的replace只会替换第一个,所以这里优化一下,使用正则来匹配特殊字符,全局替换。替换内容如下://原:replace('+', '-').replace('/', '_').replace('=','')//正则:replace(/[+]/g, '-').replace(/[/]/g, '_').replace(/[=]/g,'')

2.水印参数解释

文字内容需进行Base64编码(Base64编码之前中文字符串的最大字节长度为64个字符。)且需要将文字中的+转换为-,/转换为_,=转换为""

例如:
图片URL+ “?x-oss-process=image/”+“/watermark,text_" +
Base64.encode(你想要添加的水印内容).replace(‘+’, ‘-’).replace(‘/’, ‘_’).replace(‘=’,‘’) +",g_sw,p_15,color_FFFFFF,size_60,x_100,y_120

* text_ 文字水印的文字内容,文字内容需进行Base64编码(Base64编码之前中文字符串的最大字节长度为64个字符。)
     * type_ 文字水印的字体,字体名称需进行Base64编码。默认值:wqy-zenhei( 编码后的值为d3F5LXplbmhlaQ)
            wqy-zenhei             文泉驿正黑             d3F5LXplbmhlaQ
            wqy-microhei         文泉微米黑             d3F5LW1pY3JvaGVp
            fangzhengshusong     方正书宋             ZmFuZ3poZW5nc2h1c29uZw
            fangzhengkaiti         方正楷体             ZmFuZ3poZW5na2FpdGk
            fangzhengheiti         方正黑体             ZmFuZ3poZW5naGVpdGk
            fangzhengfangsong     方正仿宋             ZmFuZ3poZW5nZmFuZ3Nvbmc
            droidsansfallback     DroidSansFallback     ZHJvaWRzYW5zZmFsbGJhY2s
     * color_ 文字水印的文字颜色,参数值为RGB颜色值。RGB颜色值,例如:000000表示黑色,FFFFFF表示白色。 默认值:000000(黑色)
     * size_ 文字水印的文字大小。(0,1000] 默认值:40 单位:px
     * t_ 图片水印或水印文字的透明度。[0,100] 默认值:100, 表示透明度100%(不透明)。
     * shadow_ 文字水印的阴影透明度。[0,100] 默认值:0,表示没有阴影。
     * rotate_ 文字顺时针旋转角度。[0,360] 默认值:0,表示不旋转。
     * fill_ 是否将文字水印铺满原图。0、1(1:表示将文字水印铺满原图。0(默认值):表示不将文字水印铺满全图。)
     * p_ [1,100] 指定图片水印按照原图的比例进行缩放,取值为缩放的百分比。如设置参数值为10,如果原图为100×100, 则图片水印大小为10×10。当原图变成了200×200,则图片水印大小为20×20。
     * g_ 水印在图片中的位置。
            nw:左上
            north:中上
            ne:右上
            west:左中
            center:中部
            east:右中
            sw:左下
            south:中下
            se(默认值):右下
     * x_ 指定水印的水平边距, 即距离图片边缘的水平距离。这个参数只有当水印位置是左上、左中、左下、右上、右中、右下才有意义。[0,4096] 默认值:10 单位:像素(px)
     * y_ 指定水印的垂直边距,即距离图片边缘的垂直距离, 这个参数只有当水印位置是左上、中上、右上、左下、中下、右下才有意义。 [0,4096] 默认值:10 单位:px
     * voffset_ 定水印的中线垂直偏移。当水印位置在左中、中部、右中时,可以指定水印位置根据中线往上或者往下偏移。[-1000,1000] 默认值:0 单位:px
  

附件bundle.js

附件:以下bundle.js是前置工具类,在common目录下创建bundle.js文件,复制粘接即用
//bundle.js
 
 
var base64map ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";// Global Crypto object
var Crypto ={};// Crypto utilities
var util = Crypto.util ={// Bit-wise rotate left
    rotl:function(n, b){return(n << b)|(n >>>(32- b));},// Bit-wise rotate right
    rotr:function(n, b){return(n <<(32- b))|(n >>> b);},// Swap big-endian to little-endian and vice versa
    endian:function(n){// If number given, swap endianif(n.constructor == Number){return util.rotl(n,8)&0x00FF00FF|
                   util.rotl(n,24)&0xFF00FF00;}// Else, assume array and swap all itemsfor(var i =0; i < n.length; i++)
            n[i]= util.endian(n[i]);return n;},// Generate an array of any length of random bytes
    randomBytes:function(n){for(var bytes =[]; n >0; n--)
            bytes.push(Math.floor(Math.random()*256));return bytes;},// Convert a string to a byte array
    stringToBytes:function(str){
        var bytes =[];for(var i =0; i < str.length; i++)
            bytes.push(str.charCodeAt(i));return bytes;},// Convert a byte array to a string
    bytesToString:function(bytes){
        var str =[];for(var i =0; i < bytes.length; i++)
            str.push(String.fromCharCode(bytes[i]));return str.join("");},// Convert a string to big-endian 32-bit words
    stringToWords:function(str){
        var words =[];for(var c =0, b =0; c < str.length; c++, b +=8)
            words[b >>>5]|= str.charCodeAt(c)<<(24- b %32);return words;},// Convert a byte array to big-endian 32-bits words
    bytesToWords:function(bytes){
        var words =[];for(var i =0, b =0; i < bytes.length; i++, b +=8)
            words[b >>>5]|= bytes[i]<<(24- b %32);return words;},// Convert big-endian 32-bit words to a byte array
    wordsToBytes:function(words){
        var bytes =[];for(var b =0; b < words.length *32; b +=8)
            bytes.push((words[b >>>5]>>>(24- b %32))&0xFF);return bytes;},// Convert a byte array to a hex string
    bytesToHex:function(bytes){
        var hex =[];for(var i =0; i < bytes.length; i++){
            hex.push((bytes[i]>>>4).toString(16));
            hex.push((bytes[i]&0xF).toString(16));}return hex.join("");},// Convert a hex string to a byte array
    hexToBytes:function(hex){
        var bytes =[];for(var c =0; c < hex.length; c +=2)
            bytes.push(parseInt(hex.substr(c,2),16));return bytes;},// Convert a byte array to a base-64 string
    bytesToBase64:function(bytes){// Use browser-native function if it exists// if (typeof btoa == "function") return btoa(util.bytesToString(bytes));
 
        var base64 =[],
            overflow;for(var i =0; i < bytes.length; i++){switch(i %3){case0:
                    base64.push(base64map.charAt(bytes[i]>>>2));
                    overflow =(bytes[i]&0x3)<<4;break;case1:
                    base64.push(base64map.charAt(overflow |(bytes[i]>>>4)));
                    overflow =(bytes[i]&0xF)<<2;break;case2:
                    base64.push(base64map.charAt(overflow |(bytes[i]>>>6)));
                    base64.push(base64map.charAt(bytes[i]&0x3F));
                    overflow =-1;}}// Encode overflow bits, if there are anyif(overflow != undefined && overflow !=-1)
            base64.push(base64map.charAt(overflow));// Add paddingwhile(base64.length %4!=0) base64.push("=");return base64.join("");},// Convert a base-64 string to a byte array
    base64ToBytes:function(base64){// Use browser-native function if it existsif(typeof atob =="function")return util.stringToBytes(atob(base64));// Remove non-base-64 characters
        base64 = base64.replace(/[^A-Z0-9+\/]/ig,"");
 
        var bytes =[];for(var i =0; i < base64.length; i++){switch(i %4){case1:
                    bytes.push((base64map.indexOf(base64.charAt(i -1))<<2)|(base64map.indexOf(base64.charAt(i))>>>4));break;case2:
                    bytes.push(((base64map.indexOf(base64.charAt(i -1))&0xF)<<4)|(base64map.indexOf(base64.charAt(i))>>>2));break;case3:
                    bytes.push(((base64map.indexOf(base64.charAt(i -1))&0x3)<<6)|(base64map.indexOf(base64.charAt(i))));break;}}return bytes;}};// Crypto mode namespace
Crypto.mode ={};//hmac
 
var util = Crypto.util;
 
Crypto.HMAC =function(hasher, message, key, options){// Allow arbitrary length keys
    key = key.length > hasher._blocksize *4?hasher(key,{ asBytes: true }):
          util.stringToBytes(key);// XOR keys with pad constants
    var okey = key,
        ikey = key.slice(0);for(var i =0; i < hasher._blocksize *4; i++){
        okey[i]^=0x5C;
        ikey[i]^=0x36;}
 
    var hmacbytes =hasher(util.bytesToString(okey)+hasher(util.bytesToString(ikey)+ message,{ asString: true }),{ asBytes: true });return options && options.asBytes ? hmacbytes :
           options && options.asString ? util.bytesToString(hmacbytes):
           util.bytesToHex(hmacbytes);};//sha1// Shortcut
var util = Crypto.util;// Public API
var SHA1 = Crypto.SHA1 =function(message, options){
    var digestbytes = util.wordsToBytes(SHA1._sha1(message));return options && options.asBytes ? digestbytes :
           options && options.asString ? util.bytesToString(digestbytes):
           util.bytesToHex(digestbytes);};// The core
SHA1._sha1 =function(message){
 
    var m  = util.stringToWords(message),
        l  = message.length *8,
        w  =[],
        H0 =1732584193,
        H1 =-271733879,
        H2 =-1732584194,
        H3 =271733878,
        H4 =-1009589776;// Padding
    m[l >>5]|=0x80<<(24- l %32);
    m[((l +64>>>9)<<4)+15]= l;for(var i =0; i < m.length; i +=16){
 
        var a = H0,
            b = H1,
            c = H2,
            d = H3,
            e = H4;for(var j =0; j <80; j++){if(j <16) w[j]= m[i + j];else{
                var n = w[j-3]^ w[j-8]^ w[j-14]^ w[j-16];
                w[j]=(n <<1)|(n >>>31);}
 
            var t =((H0 <<5)|(H0 >>>27))+ H4 +(w[j]>>>0)+(
                     j <20?(H1 & H2 |~H1 & H3)+1518500249:
                     j <40?(H1 ^ H2 ^ H3)+1859775393:
                     j <60?(H1 & H2 | H1 & H3 | H2 & H3)-1894007588:(H1 ^ H2 ^ H3)-899497514);
 
            H4 =  H3;
            H3 =  H2;
            H2 =(H1 <<30)|(H1 >>>2);
            H1 =  H0;
            H0 =  t;}
 
        H0 += a;
        H1 += b;
        H2 += c;
        H3 += d;
        H4 += e;}return[H0, H1, H2, H3, H4];};// Package private blocksize
SHA1._blocksize =16;//base64
 
    var Base64 ={// private property
        _keyStr :"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",// public method for encoding
        encode :function(input){
            var output ="";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i =0;
 
            input = Base64._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;}elseif(isNaN(chr3)){
                    enc4 =64;}
 
                output = output +
                this._keyStr.charAt(enc1)+ this._keyStr.charAt(enc2)+
                this._keyStr.charAt(enc3)+ this._keyStr.charAt(enc4);}return output;},// public method for decoding
        decode :function(input){
            var output ="";
            var chr1, chr2, chr3;
            var enc1, enc2, enc3, enc4;
            var i =0;
 
            input = input.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(i < input.length){
 
                enc1 = this._keyStr.indexOf(input.charAt(i++));
                enc2 = this._keyStr.indexOf(input.charAt(i++));
                enc3 = this._keyStr.indexOf(input.charAt(i++));
                enc4 = this._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 = Base64._utf8_decode(output);return output;},// private method for UTF-8 encoding
        _utf8_encode :function(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);}elseif((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
        _utf8_decode :function(utftext){
            var string ="";
            var i =0;
            var c = c1 = c2 =0;while( i < utftext.length ){
 
                c = utftext.charCodeAt(i);if(c <128){
                    string += String.fromCharCode(c);
                    i++;}elseif((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;}}
 
export {Crypto, Base64}
标签: uni-app 前端 vue

本文转载自: https://blog.csdn.net/lyz19960724/article/details/139328679
版权归原作者 夕溯流光 所有, 如有侵权,请联系我们删除。

“uniapp+vue3前端直传图片至阿里oss,附前端增加oss水印”的评论:

还没有评论