Vue获取服务端签名web端直传OSS,各种报错The bucket POST must contain the specified ‘key‘.等解决办法

本文涉及的产品
对象存储 OSS,OSS 加速器 50 GB 1个月
简介: Vue获取服务端签名web端直传OSS,各种报错The bucket POST must contain the specified ‘key‘.等解决办法

项目场景:

文件上传阿里云OSS,通常情况下的上传方式是页面先文件上传到我们的后台服务器,我们的后台服务器在上传到OSS,这样的话一个文件的上传操作就相当于消耗了两份服务器带宽,

流程图如图所示:

而如果项目的文件或者图片上传业务比较大的话,显然这对服务器来说是一个不必要的开销。

当然,我们也有优化的解决办法,通过服务端给我们生成一个允许我们上传文件到OSS的签名,我们前端项目拿到这个签名去直接上传到OSS,这样就减少了额外的带宽开销,流程图如下所示:

具体的官方信息描述可以点击这里查看:
https://help.aliyun.com/document_detail/31927.html?spm=a2c4g.11186623.6.1744.17e03bd38ehYZz

Vue获取服务端签名web端直传OSS

<template>
    <div class="test">
        <div>
            <input type="file" id="file" name="file" />
            <a @click="upload()" href="javascript:;">上传</a>
        </div>
    </div>
</template>
<script>
export default {
    data(){
        return {
        }
    },
    mounted() {
        this.getOssToken();
    },
    methods: {
        //获取上传通行证
        getOssToken(){
            var _self = this;
             axios.get('/api/oss/getPolicy').then(function(res){
          console.log(res);
                if(res.data.code == 200){
                    _self.aliyunOssToken = res.data.data;
                }else{
                    _self.$message.error(res.data.message);
                }
            }).catch(function(error){
                console.log(error);
            })
        },
        upload(){
            var _self = this;
            var getSuffix = function (fileName) {
                var pos = fileName.lastIndexOf(".");
                var suffix = '';
                if (pos != -1) {
                    suffix = fileName.substring(pos);
                }
                return suffix;
            }
            var file = $("#file").val();
            if (file.length == 0) {
                alert("请选择文件");
            }
            var filename = new Date().getTime() + getSuffix(file);
            var formData = new FormData();
            //注意formData里append添加的键的大小写
            formData.append('key', _self.aliyunOssToken.dir + filename); //存储在oss的文件路径
            formData.append('OSSAccessKeyId', _self.aliyunOssToken.accessid); //accessKeyId
            formData.append('policy', _self.aliyunOssToken.policy); //policy
            formData.append('Signature', _self.aliyunOssToken.signature); //签名
//如果是base64文件,那么直接把base64字符串转成blob对象进行上传就可以了
            formData.append("file", $("#file")[0].files[0]);
            formData.append('success_action_status', 200); //成功后返回的操作码
            var url = _self.aliyunOssToken.host;
            var fileUrl = _self.aliyunOssToken.host +'/'+ _self.aliyunOssToken.dir + filename;
            $.ajax({
                url: url,
                type: 'POST',
                data: formData,
                // async: false,
                cache: false,
                contentType: false,
                processData: false,
                success: function (data) {
                    console.log(fileUrl);
                    console.log(data);
                },
                error: function (data) {
                    console.log(data);
                }
            });
        }
    }
}
</script>

这里需要注意的是:

如果你遇到了

<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error>\n  <Code>InvalidArgument</Code>\n  <Message>The bucket POST must contain the specified 'key'. If it is specified, please check the order of the fields</Message>\n  <RequestId>60950F076AD6D5

这种错误的话,那么可能,是你的key字段不存在,这里所有的字段名称一定要和官方文档给定的一致,否则就会报错,注意大小写。

如果不是的话,那么你可能就是遇到了跟我一样的错误,如图所示:

查阅官方文档后,官方文档给出的解释是这样的,点击查看:

如图所示:

如果还有其他错误,可对照官方文档一步步排查。

Java后台授权代码

这里我也给贴出来吧,省得你再去翻看官方文档了:

有点多,你就看你有用的就行:

package cc.mrbird.febs.external.oss;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.exception.FebsException;
import cc.mrbird.febs.common.utils.RandomUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import lombok.Data;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
 * @ Author 马超伟
 * @ Date 2021-04-30 09:21
 * @ Description:
 * @ Version:
 */
@Data
@Service
public class OssFileUpload {
    @Value("${aliyun.oss.file.endpoint}")
    private String endpoint;
    @Value("${aliyun.oss.file.keyid}")
    private String accessKeyId;
    @Value("${aliyun.oss.file.keysecret}")
    private String accessKeySecret;
    @Value("${aliyun.oss.file.filehost}")
    private String fileHost;
    @Value("${aliyun.oss.file.bucketname}")
    private String bucketName;
    @Value("${aliyun.oss.file.callbackUrl}")
    private String callbackUrl;
    @Value("${aliyun.oss.file.baseDir}")
    private String dir;
    public OSS getOssClient() {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
        return ossClient;
    }
    /**
     * @ Author: 马超伟
     * @ Date: 2021/4/30 10:59
     * @ Params: [file, basePath]
     * @ return: java.lang.String
     * @ Description: 文件上传
     */
    public String upload(MultipartFile file, String basePath) {
        String uploadUrl;
        try {
            //判断oss实例是否存在:如果不存在则创建,如果存在则获取
            OSS ossClient = getOssClient();
            //获取上传文件流
            InputStream inputStream = file.getInputStream();
            //构建日期路径:avatar/2019/02/26/文件名
            String filePath = new DateTime().toString("yyyy/MM/dd");
            //文件名:uuid/原始文件名到后缀
            String original = file.getOriginalFilename();
            String fileName = RandomUtil.getLinkNo();
            String newName = fileName + "/" + original;
            String fileUrl = basePath + "/" + filePath + "/" + newName;
            //文件上传至阿里云
            ossClient.putObject(bucketName, fileUrl, inputStream);
            // 关闭OSSClient。
            ossClient.shutdown();
            //获取url地址
            uploadUrl = fileHost + "/" + fileUrl;
        } catch (IOException e) {
            throw new FebsException("文件上传异常!");
        }
        return uploadUrl;
    }
    /**
     * @return List<String>  文件路径和大小集合
     * @ Param ossClient  oss客户端
     * @ Param bucketName bucket名称
     * @Title: queryAllObject
     * @ Description: 查询某个bucket里面的所有文件
     */
    public List<String> queryAllObject() {
        List<String> results = new ArrayList<>();
        try {
            // ossClient.listObjects返回ObjectListing实例,包含此次listObject请求的返回结果。
            ObjectListing objectListing = getOssClient().listObjects(bucketName);
            // objectListing.getObjectSummaries获取所有文件的描述信息。
            for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
                results.add(" - " + objectSummary.getKey() + "  " + "(size = " + objectSummary.getSize() + ")");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return results;
    }
    /**
     * 从阿里云下载单个文件
     *
     * @ Param objectName
     */
    public void download(String objectName, HttpServletResponse response) {
        BufferedInputStream input = null;
        OutputStream outputStream;
        OSSObject ossObject;
        try {
            objectName = objectName.replace(fileHost + "/", "");
            response.reset();
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/x-msdownload");
            String substring = objectName.substring(objectName.lastIndexOf("/") + 1);
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(substring, "UTF-8"));
            // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
            ossObject = getOssClient().getObject(bucketName, objectName);
            input = new BufferedInputStream(ossObject.getObjectContent());
            byte[] buffBytes = new byte[1024];
            outputStream = response.getOutputStream();
            int read;
            while ((read = input.read(buffBytes)) != -1) {
                outputStream.write(buffBytes, 0, read);
            }
            input.close();
            ossObject.close();
            outputStream.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                if (input != null) {
                    input.close();
                }
            } catch (IOException e) {
//                e.printStackTrace();
            }
        }
    }
    public void downloadObject(String objectName) {
        // 创建OSSClient实例。
        OSS ossClient = getOssClient();
        // 下载Object到本地文件,并保存到指定的本地路径中。如果指定的本地文件存在会覆盖,不存在则新建。
        // 如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。
        objectName = objectName.replace(fileHost + "/", "");
        ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File("D:\\localpath\\"+objectName));
        // 关闭OSSClient。
        ossClient.shutdown();
    }
    /**
     * 文件及文件夹的递归删除
     *
     * @ Param file
     */
    private static void deleteFile(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            assert files != null;
            for (File f : files) {
                deleteFile(f);
                //将循环后的空文件夹删除
                if (f.exists()) {
                    f.delete();
                }
            }
        } else {
            file.delete();
        }
    }
    /**
     * @ Author: 马超伟
     * @ Date: 2021/5/7 18:16
     * @ Params: []
     * @ return: cc.mrbird.febs.common.entity.FebsResponse
     * @ Description: 前台获取文件上传签名授权
     */
    public FebsResponse policy() {
        //https://gulimall-hello.oss-cn-beijing.aliyuncs.com/hahaha.jpg
        // callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//        String callbackUrl = "http://88.88.88.88:8888";
        String filePath = new DateTime().toString("yyyy/MM/dd");
        String dir = "file/" + filePath; // 用户上传文件时指定的前缀。
        Map<String, String> respMap = null;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
            String postPolicy = getOssClient().generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = getOssClient().calculatePostSignature(postPolicy);
            respMap = new LinkedHashMap<>();
            respMap.put("accessid", accessKeyId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", fileHost);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            // respMap.put("expire", formatISO8601Date(expiration));
        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        }
        return new FebsResponse().success().put("data", respMap);
    }
}

总结

遇到问题并不可怕,可怕的是找不出来问题在哪, 这种调用人家方法的,就按照人家的要求一步步走就行了,自己的任何‘**投机取巧、标新立异’**都是行不通的。

找到报错信息,去官方文档查阅‘常见问题’,解决起来就轻松多了。

相关实践学习
对象存储OSS快速上手——如何使用ossbrowser
本实验是对象存储OSS入门级实验。通过本实验,用户可学会如何用对象OSS的插件,进行简单的数据存、查、删等操作。
目录
相关文章
|
11月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
779 0
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
574 20
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
607 0
【Azure Web Job】Azure Web Job执行Powershell脚本报错 The term 'Select-AzContext' is not recognized as the name
【Azure Web Job】Azure Web Job执行Powershell脚本报错 The term 'Select-AzContext' is not recognized as the name
176 3
|
SQL 安全 关系型数据库
PHP作为一种流行的服务端脚本语言,在Web开发领域具有显著的优势
【10月更文挑战第11天】PHP作为一种流行的服务端脚本语言,在Web开发领域具有显著的优势
265 0
|
Shell PHP Windows
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
308 0
|
API Python
python flask 提供web的get/post开发
python flask 提供web的get/post开发
297 0
|
8月前
|
存储 人工智能 Cloud Native
阿里云渠道商:OSS与传统存储系统的差异在哪里?
本文对比传统存储与云原生对象存储OSS的架构差异,涵盖性能、成本、扩展性等方面。OSS凭借高持久性、弹性扩容及与云服务深度集成,成为大数据与AI时代的优选方案。
|
10月前
|
存储 运维 安全
阿里云国际站OSS与自建存储的区别
阿里云国际站对象存储OSS提供海量、安全、低成本的云存储解决方案。相比自建存储,OSS具备易用性强、稳定性高、安全性好、成本更低等优势,支持无限扩展、自动冗余、多层防护及丰富增值服务,助力企业高效管理数据。