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

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 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搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
目录
相关文章
|
1月前
|
安全 对象存储
OSS对象存储JavaV4签名
本文介绍了如何使用阿里云OSS-SDK生成V4版本的签名URL和Header签名。通过设置时间、访问密钥等参数,代码示例展示了如何创建带有V4签名的请求,适用于安全访问对象存储服务。相关文档链接提供了更多详细信息。
194 7
|
5月前
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
|
7月前
|
JavaScript 前端开发 开发者
vue3+ts配置跨域报错问题解决:> newpro2@0.1.0 serve > vue-cli-service serve ERROR Invalid options in vue.
【6月更文挑战第3天】在 Vue CLI 项目中遇到 &quot;ERROR Invalid options in vue.config.js: ‘server’ is not allowed&quot; 错误是因为尝试在 `vue.config.js` 中使用不被支持的 `server` 选项。正确配置开发服务器(如代理)应使用 `devServer` 对象,例如设置代理到 `http://xxx.com/`: ```javascript module.exports = { devServer: {
321 1
Vue3,setup的使用需要搭配return进行使用,Vue3中带setup的script的标签和不带能不能合并到一起,export default不能放到setup里会报错,script
Vue3,setup的使用需要搭配return进行使用,Vue3中带setup的script的标签和不带能不能合并到一起,export default不能放到setup里会报错,script
|
6月前
|
前端开发
Vue2和Vue3的区别,在setup中定义的数据,在data(){return中能否定义到},在setup我们不能用this,写在return中可以用this,但是不能在setup否则会报错
Vue2和Vue3的区别,在setup中定义的数据,在data(){return中能否定义到},在setup我们不能用this,写在return中可以用this,但是不能在setup否则会报错
|
4月前
|
JavaScript
Vue+element_Table树形数据与懒加载报错Error in render: “RangeError: Maximum call stack size exceeded“
本文讨论了在使用Vue和Element UI实现树形数据和懒加载时遇到的“Maximum call stack size exceeded”错误,指出问题的原因通常是因为数据中的唯一标识符`id`不唯一,导致递归渲染造成调用栈溢出。
257 1
Vue+element_Table树形数据与懒加载报错Error in render: “RangeError: Maximum call stack size exceeded“
|
4月前
|
人工智能 JavaScript 索引
Duplicate keys detected: This may cause an update error.【Vue遍历渲染报错的解决】
这篇文章讨论了在Vue中进行列表渲染时遇到的“Duplicate keys detected”错误。这个错误通常发生在使用 `v-for` 指令渲染列表时,如果没有为每个循环项指定一个唯一的 `key` 属性,或者指定的 `key` 属性值重复了。文章提供了导致错误的原始代码示例,并给出了修正后的代码,通过在 `key` 绑定中加入索引确保 `key` 的唯一性。此外,文章还解释了为什么需要唯一 `key` 以及如何解决这个问题。
Duplicate keys detected: This may cause an update error.【Vue遍历渲染报错的解决】
|
3月前
|
JavaScript
Vue启动时报错的解决方案,以及解决相同路径跳转报错的问题
Vue启动时报错的解决方案,以及解决相同路径跳转报错的问题
497 0
|
4月前
|
SQL 开发框架 安全
Web开发中常见的安全缺陷及解决办法
Web开发中常见的安全缺陷及解决办法
|
5月前
|
存储 JavaScript 前端开发
Vue中实现图片上传,上传后的图片回显,存储图片到服务器 【使用对象存储OSS】
这篇文章介绍了在Vue中实现图片上传到阿里云OSS对象存储服务的完整流程,包括服务端签名直传的前提知识、后端设置、前端组件封装以及图片上传和回显的效果展示。