0


前端直传阿里云OSS

阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。

目前通过Web端直传文件(Object)到OSS,有两种方案:

一、利用OSS Browser.js SDK将文件上传到OSS。该方案通过OSS Browser.js SDK直传数据到OSS,支持断点续传,支持各种主流浏览器,可以将File对象、Blob数据以及OSS Buffer上传OSS,该方案还支持下载和删除

二、利用OSS提供的PostObject接口来实现表单上传,不支持断点续传,支持h5,小程序,支持uniapp的uni.uploadFile接口

方案一:使用阿里云SDK上传

由于前端环境不安全,为避免暴露阿里云账号访问密钥(AccessKey ID和AccessKey Secret),该方案需要搭建STS服务获取临时访问密钥(AccessKey ID和AccessKey Secret)和安全令牌(SecurityToken),需要先开通STS服务,参考官方文档

后端

后端需要导入aliyun-sdk-oss包,用于获取前端需要的key和secret

JDK版本:jdk8

<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>2.0.7</version><!--<version>3.15.0</version>最新--></dependency>

如果是java9及以上版本,则需要添加jaxb相关依赖。添加jaxb相关依赖示例代码如下:

<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency><!-- no more than 2.3.3--><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version></dependency>

搭建STS服务(部分代码)

// STS接入地址,例如sts.cn-hangzhou.aliyuncs.com。
      @Value("${ramEndpoint}")private String ramEndpoint;// 访问密钥AccessKey ID和AccessKey Secret
    @Value("${ramAccessKeyId}")private String ramAccessKeyId;
    @Value("${ramAccessKeySecret}")private String ramAccessKeySecret;// 角色ARN
    @Value("${ramRoleArn}")// 自定义角色会话名称,用来区分不同的令牌,例如可填写为SessionTestprivate String ramRoleArn;
    @Value("${ramRoleSessionName}")private String ramRoleSessionName;private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    RedisTemplate redisTemplate;/**
     * 通过RAM子账号获取stsToken,作为临时凭据
     */
    @RequestMapping(value ="getStsToken", method ={RequestMethod.GET, RequestMethod.POST})public Object getStsToken(){
        ResponseVo responseVo =newResponseVo();
        AssumeRoleResponse response =null;
        Object redisToken =null;try{
            redisToken = redisTemplate.opsForValue().get("stsToken");}catch(Exception e){
            logger.error(e.getMessage());
            responseVo.setError(GlobalErrorCode.SYS_RUN_ERROR.getCode());return responseVo;}if(redisToken !=null){
            response = JSONObject.parseObject(redisToken.toString(), AssumeRoleResponse.class);
            responseVo.setSuccess(response);return responseVo;}else{
            String policy ="{\n"+"    \"Version\": \"1\", \n"+"    \"Statement\": [\n"+"        {\n"+"            \"Action\": [\n"+"                \"oss:*\"\n"+"            ], \n"+"            \"Resource\": [\n"+"                \"acs:oss:*:*:*\" \n"+"            ], \n"+"            \"Effect\": \"Allow\"\n"+"        }\n"+"    ]\n"+"}";try{
                DefaultProfile.addEndpoint("","","Sts", ramEndpoint);// 构造default profile(参数留空,无需添加region ID)
                IClientProfile profile = DefaultProfile.getProfile("", ramAccessKeyId, ramAccessKeySecret);// 用profile构造client
                DefaultAcsClient client =newDefaultAcsClient(profile);
                final AssumeRoleRequest request =newAssumeRoleRequest();
                request.setMethod(MethodType.POST);
                request.setRoleArn(ramRoleArn);
                request.setRoleSessionName(ramRoleSessionName);
                request.setPolicy(policy);// 若policy为空,则用户将获得该角色下所有权限
                request.setDurationSeconds(20* 60L);// 设置凭证有效时间,单位秒//获取凭证
                response = client.getAcsResponse(request);/*
                 * 缓存该凭证,凭证失效后才从OSS再次获取凭证
                 * 凭证有效时间为20分钟,Redis里只缓存10分钟
                 */
                redisTemplate.opsForValue().set("stsToken", JSONObject.toJSONString(response),10*60, TimeUnit.SECONDS);
                responseVo.setSuccess(response);return responseVo;}catch(ClientException e){
                logger.error(e.getErrMsg());
                responseVo.setError(GlobalErrorCode.SYS_RUN_ERROR.getCode());return responseVo;}}}

参考阿里云文档

前端

安装

$ npm install ali-oss --save

部分代码

onLoad(){this.getStsToken()},

methods

/**
* @param {String} pathAndName Object完整路径。Object完整路径中不能包含Bucket名称("exampledir/exampleobject.txt")
* @param {Object} data (file对象、Blob数据或者OSS Buffer)
*/asyncputObject(pathAndName, data){try{// 您可以通过自定义文件名(例如exampleobject.txt)或文件完整路径(例如exampledir/exampleobject.txt)的形式实现将数据上传到当前Bucket或Bucket中的指定目录。const result =awaitthis.getClient().put(
            pathAndName,
            data
        );
        console.log('result:', result);}catch(e){
        console.log(e);}},getClient(){if(this.client){returnthis.client
        }constOSS=require('ali-oss');const client =newOSS({// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。region:'oss-cn-qingdao',// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。accessKeyId:this.stsToken.credentials.accessKeyId,accessKeySecret:this.stsToken.credentials.accessKeySecret,// 从STS服务获取的安全令牌(SecurityToken)。stsToken:this.stsToken.credentials.securityToken,refreshSTSToken:async()=>{// 向您搭建的STS服务获取临时访问凭证。let info =awaitthis.$post(GET_STS_TOKEN)
                info = info.data
                console.log('-----------refresh--token')return{accessKeyId: info.credentials.accessKeyId,accessKeySecret: info.credentials.accessKeySecret,stsToken: info.credentials.securityToken
                }},// 刷新临时访问凭证的时间间隔,单位为毫秒。每隔一段时间定时器会自动掉后台接口刷新tokenrefreshSTSTokenInterval:600000,// 填写Bucket名称。bucket:'zxxxxth-bucket'});this.client = client
        returnthis.client
    },getStsToken(){//从后台获取stsToken(改成自己的前端请求接口)this.$post(GET_STS_TOKEN).then(rsp=>{if(rsp.success){this.stsToken = rsp.data;// 初始化一下client让定时任务启动,自动刷新token(避免过期)this.getClient()
                    console.log('this.stsToken:',this.stsToken)}else{
                    uni.showToast({title: rsp.message,duration:2000});}})},

refreshSTSToken参数说明:当初始化new OSS()时,定时器会启动,当时间到了refreshSTSTokenInterval所设置的值时,并不会立即调用后台接口获取token,只有手动触发put()接口时,才会调用后台接口获取token

参考阿里云文档

开通STS服务步骤

方案二:使用PostObject接口来实现表单上传

这个方案支持小程序上传,uniapp上传。无需开通STS服务

后端

获取postObject接口需要的policy,OSSAccessKeyId,signature 参考官方文档

这里签名使用后端签名,所以不需要申请开通STS服务

简化版,无回调
/**
     * 利用OSS提供的PostObject接口,通过表单上传的方式将文件上传到OSS。
     * 该方案兼容大部分浏览器,但在网络状况不好的时候,如果单个文件上传失败,
     * 只能重试上传。上传的Object大小不能超过5 GB。
     * @return ResponseVo{success:true,message:'',data:{},code:200}
     */@RequestMapping(value ="getPostObjectParams", method ={RequestMethod.GET,RequestMethod.POST})publicObjectgetPostObjectParams(){ResponseVo responseVo =newResponseVo();
        responseVo.setSuccess(OSSServer.getPostObjectParams());return responseVo;}

OSSServer.class

publicstaticOSSClientgetOSSClient(){if(null== ossClient){
            ossClient =newOSSClient(endpoint, accessKeyId, accessKeySecret);}return ossClient;}/**
     * 获取表单上传的方式的PostObject参数
     * @return
     */publicstaticMap<String,Object>getPostObjectParams(){Map<String,Object> respMap =newLinkedHashMap();// 限制参数的生效时间,单位为分钟,默认值为20。int expireTime =20;// 限制上传文件的大小,单位为MB,默认值为100。int maxSize =100;// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。// 如果值为"test"那么前端的key参数必须以"test"开头,如test/*、test1.jpg、test/comment/11.jpgString dir ="";// 创建OSSClient实例。OSS ossClient =getOSSClient();try{long expireEndTime =System.currentTimeMillis()+ expireTime *1000*60;Date expiration =newDate(expireEndTime);PolicyConditions policyConds =newPolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,0, maxSize *1024*1024);
            policyConds.addConditionItem(MatchMode.StartWith,PolicyConditions.COND_KEY, dir);String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);byte[] binaryData = postPolicy.getBytes("utf-8");String encodedPolicy =BinaryUtil.toBase64String(binaryData);String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap.put("accessKeyId", accessKeyId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);

            respMap.put("expire", expireEndTime /1000);}catch(Exception e){
            log.error("getPostObjectParams", e);}return respMap;}

大多数情况下,用户上传文件后,应用服务器需要知道用户上传了哪些文件以及文件名;如果上传了图片,还需要知道图片的大小等,为此OSS提供了上传回调方案。

流程图:

在这里插入图片描述

当用户要上传一个文件到OSS,而且希望将上传的结果返回给应用服务器时,需要设置一个回调函数,将请求告知应用服务器。用户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户。

有回调的PostObject参数
/**
     * 获取表单上传的方式的PostObject参数【有回调】
     * @return
     */publicstaticMap<String,Object>getPostObjectParams(){Map<String,Object> respMap =newLinkedHashMap();// 限制参数的生效时间,单位为分钟,默认值为20。int expireTime =20;// 限制上传文件的大小,单位为MB,默认值为10。int maxSize =10;// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。// 如果值为"test"那么前端的key参数必须以"test"开头,如test/*、test1.jpg、test/comment/11.jpg// 可以让用户没有办法上传到其他的目录,从而保证了数据的安全性String dir ="";// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。String callbackUrl ="https://jmt.xxx.cn/common/postObjectCallBack/";// 创建OSSClient实例。OSS ossClient =getOSSClient();try{long expireEndTime =System.currentTimeMillis()+ expireTime *1000*60;Date expiration =newDate(expireEndTime);PolicyConditions policyConds =newPolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,0, maxSize *1024*1024);
            policyConds.addConditionItem(MatchMode.StartWith,PolicyConditions.COND_KEY, dir);String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);byte[] binaryData = postPolicy.getBytes("utf-8");String encodedPolicy =BinaryUtil.toBase64String(binaryData);String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap.put("accessKeyId", accessKeyId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("expire", expireEndTime /1000);// 配置回调地址JSONObject jasonCallback =newJSONObject();
            jasonCallback.put("callbackUrl", callbackUrl);//            jasonCallback.put("callbackBody",//                    "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");

            jasonCallback.put("callbackBody","{\"filename\":${object},\"mimeType\":${mimeType}}");

            jasonCallback.put("callbackBodyType","application/json");//application/x-www-form-urlencodedString base64CallbackBody =BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
            respMap.put("callback", base64CallbackBody);}catch(Exception e){
            log.error("getPostObjectParams", e);}return respMap;}
给oss回调的接口
@RequestMapping(value ="postObjectCallBack", method =RequestMethod.POST)publicObjectpostObjectCallBack(HttpServletRequest request,@RequestBodyObject callbackBody)throwsIOException{

        log.info("---callbackBody={}",callbackBody);//        "{"filename":"test/comment/tt1.jpg","mimeType":"image/png"}"return callbackBody;}

前端

小程序
const host ='<host>';//"https://examplebucket.oss-cn-hangzhou.aliyuncs.com"const signature ='<signatureString>';const ossAccessKeyId ='<accessKey>';const policy ='<policyBase64Str>';const key ='<object name>';const securityToken ='<x-oss-security-token>';const filePath ='<filePath>';// 待上传文件的文件路径。
wx.uploadFile({url: host,// 这个是阿里云bucket的根地址,使用这个地址拼接路径可以访问已上传的文件。如果是自己服务器则是开发者服务器的URL。filePath: filePath,// 本地文件路径,小程序chooseImage方法返回的路径name:'file',// 必须填file。formData:{
    key,
    policy,OSSAccessKeyId: ossAccessKeyId,
    signature,// 'x-oss-security-token': securityToken // 使用STS签名时必传。},success:(res)=>{if(res.statusCode ===204){
      console.log('上传成功');}},fail:err=>{
    console.log(err);}});
uniapp、uView的Upload组件
uni.uploadFile({url:'https://res.xxx.cn',//这个是阿里云bucket的根地址,使用这个地址拼接路径可以访问已上传的文件filePath: url,// 本地文件路径,小程序chooseImage方法返回的路径name:'file',// 必须填fileformData:{key:'test/comment/tt1.jpg',//会把tt1.jpg图片上传至bucket(上方url所指向)的test/comment目录policy:this.postObject.policy,OSSAccessKeyId:this.postObject.accessKeyId,signature:this.postObject.signature,// callback: this.postObject.callback},success:(res)=>{
        console.log('uni.uploadFile success:', res)if(res.statusCode===204){// 上传成功
            console.log('-------------success------------')}elseif(res.statusCode===403){// Policy expired.
            uni.showToast({title:'网络超时',duration:2000});// 续期this.getPostObjectParams()}else{
            console.log('上传失败')}// setTimeout(() => {resolve(res)// }, 1000)},fail(err){
        console.error('uni.uploadFile: fail', err)}});
onLoad(){this.getPostObjectParams()},...........getPostObjectParams(){//从后台获取stsTokenthis.$post(GET_POST_OBJECT_PARAMS).then(rsp=>{if(rsp.success){this.postObject = rsp.data;
            console.log('this.postObject:',this.postObject)}else{
            uni.showToast({title: rsp.message,duration:2000,icon:'none'});}})},

参考官方文档

举一个uniapp例子

UI库:uView
在这里插入图片描述

<template><!-- 发表评论 --><view class="create-comment"><view class="star comment-common"><view class="title">评分</view><view class="control"><text class="name">游玩体验</text><u-rate :count="5" v-model="starCount":touchable="false" active-color="#E65526" size="24"></u-rate></view></view><view class="content comment-common"><view class="title">评价内容</view><textarea v-model="resourceComment.content" maxlength="200"
                placeholder="游玩的满意吗?大家都想了解这里值得去吗?有什么亮点?期待你精彩的点评!"></textarea></view><view class="picture comment-common"><view class="title">图片</view><!-- name=1对应fileList1 --><u-upload :fileList="fileList1" @afterRead="afterRead" @delete="deletePic" name="1"multiple:maxCount="15":maxSize="maxSize"></u-upload></view><view class="picture comment-common"><view class="title">视频</view><!-- name=2对应fileList2 --><u-upload :fileList="fileList2" @afterRead="afterRead" @delete="deletePic" name="2"multiple:maxCount="1":maxSize="maxSize" accept="video" uploadIcon="movie"></u-upload></view><button @click="submit()" type="warn"class="submit":loading="loading":disabled="loading">发布</button></view></template><script>import{GET_POST_OBJECT_PARAMS}from'../../api/api.js'exportdefault{data(){return{starCount:0,resourceComment:{content:''},fileList1:[],fileList2:[],loading:false,postObject:{expire:0},maxSize:100*1024*1024}},onLoad(){},methods:{// -----upload start// 新增图片asyncafterRead(event){
                console.log('event:', event)// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式let lists =[].concat(event.file)// console.log('lists:', lists)let fileListLen =this[`fileList${event.name}`].length
                // console.log('fileListLen:', fileListLen)
                lists.map((item)=>{this[`fileList${event.name}`].push({...item,status:'uploading',message:'上传中'})})let time =newDate().getTime()/1000// console.log('time:', time)if(time >this.postObject.expire){// policy过期,续期awaitthis.getPostObjectParams()}for(let i =0; i < lists.length; i++){const result =awaitthis.uploadFilePromise(lists[i].url)let item =this[`fileList${event.name}`][fileListLen]this[`fileList${event.name}`].splice(fileListLen,1, Object.assign(item,{status: result ?'success':'failed',message:'',url: result
                    }))
                    fileListLen++}},compressJpgImage(src){returnnewPromise((resolve, reject)=>{// uni.compressImage({//   src: src,//   quality: 80,//   success: res => {//     console.log(res.tempFilePath)//   }// })})},uploadFilePromise(url){returnnewPromise((resolve, reject)=>{let a = uni.uploadFile({url:'https://res.xxxx.cn',filePath: url,name:'file',// 必须填fileformData:{key:'test/comment/tt3.jpg',policy:this.postObject.policy,OSSAccessKeyId:this.postObject.accessKeyId,signature:this.postObject.signature,callback:this.postObject.callback
                        },success:(res)=>{// 未配置回调 上传成功返回 {date:"",errMsg:"uploadFile:ok",statusCode:204},如果配置了回调data参数才会有值// 配置了回调 上传成功返回 {{data:{"filename":"test/comment/tt1.jpg","mimeType":"image/png"},errMsg:"uploadFile:ok",statusCode:200}
                            console.log('uni.uploadFile success():', res)if(res.statusCode ===204|| res.statusCode ===200){// 上传成功
                                console.log('-------------uploaded success')resolve(url)}else{
                                console.log('-------------uploaded failed')
                                uni.showToast({title:'上传失败',duration:2000,icon:'error'});resolve()}},fail(err){
                            console.error('uni.uploadFile: fail():', err)}});})},// 删除图片deletePic(event){this[`fileList${event.name}`].splice(event.index,1)},// -----upload endgetPostObjectParams(){//从后台获取postObjectreturnthis.$post(GET_POST_OBJECT_PARAMS,{folderType:'comment'}).then(rsp=>{// this.postObject = rspif(rsp.success){this.postObject = rsp.data;
                        console.log('this.postObject:',this.postObject)}else{
                        console.error('getPostObjectParams:', rsp.message ||'系统错误')}})}}}</script><style lang="scss">.create-comment {padding: 12px;.comment-common {
            margin-bottom: 10px;padding: 15px 10px;
            background-color: white;
            border-radius: 10px;}.title {
            margin-bottom: 10px;
            font-size: 16px;
            font-weight: bold;}.star {.control {display: flex;
                align-items: center;.name {
                    margin-right: 10px;}}}.content {
            textarea {
                font-size: 14px;width:100%;}}.picture {}.submit {
            margin-top: 20px;width:80%;
            font-size: 15px;color: white;
            background-color: #e65526;}}</style>

附:根据blob链接获取blob对象

/**
             * 根据blob链接获取blob对象
             * @param {Object} url "blob:http://localhost:8085/d688ce4f-0f5d-418c-85ad-62bcb3f38dee"
             * @returns Blob(31846) {size: 31846, type: "image/jpeg"}
             */getBlobByUrl(url){return uni.request({url: url,// 合法值:text、arraybufferresponseType:'arraybuffer'}).then(data=>{const[error, rsp]= data;if(error){
                        console.error(`post-error:${error}, url:${url}`)return{message: error.errMsg}}else{let buffer = rsp.data
                        // ArrayBuffer(185) {}
                        console.log('buffer:', buffer)returnnewBlob([buffer])}})// return new Promise((resolve, reject) => {//     let xhr = new XMLHttpRequest()//     xhr.open('GET', url, true)//     xhr.responseType = 'blob'//     xhr.onload = function(e) {//         if (this.status == 200) {//             let myBlob = this.response//             // let file = new window.File(//             //     [myBlob],//             //     'myfile.jpg', {//             //         type: myBlob.type//             //     }//             // )//             // console.log("files:", file)//             resolve(myBlob)//         } else {//             reject(false)//         }//     }//     xhr.send()// })},

blobUrl、blob、base64、file相互转化:https://www.cnblogs.com/jing-zhe/p/15402775.html

uniapp选择file

<button @click="submit()" type="warn"class="submit":loading="loading":disabled="loading">发布</button>submit(){let utils =newUtils()
    uni.chooseImage({count:6,//默认9sizeType:['original','compressed'],//可以指定是原图还是压缩图,默认二者都有sourceType:['album'],//从相册选择success:function(res){
            console.log(JSON.stringify(res.tempFilePaths));
            console.log(res.tempFiles)

            utils.getFileMD5(res.tempFiles[0],function(md5){
                console.log('md5:', md5)})}});},
标签: 前端 阿里云

本文转载自: https://blog.csdn.net/dan_seek/article/details/126588331
版权归原作者 风中蒲公英 所有, 如有侵权,请联系我们删除。

“前端直传阿里云OSS”的评论:

还没有评论