第四章:minio的presigned URLs上传文件

章节快捷访问:

第一章:minio介绍与安装

https://blog.csdn.net/hzw2312/article/details/106077729

第二章:minio单机版,使用客户端备份文件

https://blog.csdn.net/hzw2312/article/details/106078150

第三章:minio的javaAPI

https://blog.csdn.net/hzw2312/article/details/106078390

第四章:minio的presigned URLs上传文件

https://blog.csdn.net/hzw2312/article/details/106078604

--------------------------------------------------

 

当我们通过java的API上传文件的时候就会发现,我们把java的API封装了一下,提供了一个接口给其他应用调用,那么整个的上传流程就变成了“应用客户端”-->“JavaAPI端”-->“minio服务端”。中间通过JavaAPI转了一次。比如我们的“应用客户端”是web浏览器的时候,能不能直接从浏览器上传到“minio服务端”呢?答案是可以的,minio有提供JSSDK,我们可以通过JSAPI直接上传到“minio服务端”。

JSSDK

参考官网的文档:https://docs.min.io/cn/javascript-client-quickstart-guide.html

var Minio = require('minio')

// Instantiate the minio client with the endpoint
// and access keys as shown below.
var minioClient = new Minio.Client({
    endPoint: 'play.min.io',
    port: 9000,
    useSSL: true,
    accessKey: 'Q3AM3UQ867SPQQA43P2F',
    secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG'
});

// File that needs to be uploaded.
var file = '/tmp/photos-europe.tar'

// Make a bucket called europetrip.
minioClient.makeBucket('europetrip', 'us-east-1', function(err) {
    if (err) return console.log(err)

    console.log('Bucket created successfully in "us-east-1".')

    var metaData = {
        'Content-Type': 'application/octet-stream',
        'X-Amz-Meta-Testing': 1234,
        'example': 5678
    }
    // Using fPutObject API upload your file to the bucket europetrip.
    minioClient.fPutObject('europetrip', 'photos-europe.tar', file, metaData, function(err, etag) {
      if (err) return console.log(err)
      console.log('File uploaded successfully.')
    });
});

根据官网的文档,我们可以看到,从js上传文件需要将我们的accessKey跟secretKey暴露出去。这样的话,别人拿到我们的key岂不是可以为所欲为了!

这样是很危险的,所以我们不能这样做。除非你是用这段脚本来写node.js服务,把他当作服务端。但是这样的话,岂不是又是通过“应用客户端”-->“api端”-->“minio服务端”这样的路径来进行上传了。

presigned URLs

minio并没有像商用的七牛云存储那样,可以通过临时的token来上传文件。但是,这个但是很重要啊,他提供了使用pre-signed URLs通过浏览器上传文件。那么这个pre-signed URLs是啥子?pre-signed URLs简单来说就是预处理签名的一个URL。就是url中包含了签名信息,但是是经过加密的。而且还有时效性的。这样一来就跟七牛云的临时token有异曲同工之处了。既保证了用最短的途径能上传文件,又保证了我们的上传签名信息不会被恶意利用。

官网的文档:https://docs.min.io/cn/upload-files-from-browser-using-pre-signed-urls.html

这里官网给出的例子是node.js的,我们使用Java该怎么用呢!

首先在我们之前的MinioTemplate类中,加上支持方法:

/**
 * 获得上传的URL
 * @param bucketName
 * @param objectName
 * @param expires
 * @return
 * @throws IOException
 * @throws InvalidKeyException
 * @throws NoSuchAlgorithmException
 * @throws InsufficientDataException
 * @throws InvalidExpiresRangeException
 * @throws InternalException
 * @throws NoResponseException
 * @throws InvalidBucketNameException
 * @throws XmlPullParserException
 * @throws ErrorResponseException
 */
public String presignedPutObject(String bucketName, String objectName, Integer expires) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InvalidExpiresRangeException, InternalException, NoResponseException, InvalidBucketNameException, XmlPullParserException, ErrorResponseException {
    if(expires == null){
        // 7天
        return client.presignedPutObject(bucketName,objectName);
    }
    return client.presignedPutObject(bucketName,objectName,expires);
}

然后在我们之前的控制器中加上方法:

/**
 * 获得上传的URL
 * @param bucketName    文件桶名称
 * @param objectName    文件对象名称:(pdf/2019/0512/test.pdf)
 */
@ApiOperation(value = "获得上传的URL", response = ActionResult.class)
@GetMapping("/uploadUrl")
public ActionResult<Object> uploadUrl(
        @ApiParam(name = "bucketName",value = "文件桶名称") String bucketName,
        @ApiParam(name = "objectName",value = "文件对象名称:(pdf/2019/0512/test.pdf)") String objectName,
        @ApiParam(name = "expires", value ="链接有效时间(单位秒)") Integer expires)
        throws IOException, XmlPullParserException, NoSuchAlgorithmException, InvalidKeyException, InvalidPortException, ErrorResponseException, NoResponseException, InvalidBucketNameException, InsufficientDataException,  InternalException, InvalidExpiresRangeException {
    String url = template.presignedPutObject(bucketName,objectName,expires);
    return success(url,"获取成功");
}

这样js在上传之前先调用我们的uploadUrl方法得到我们的上传URL

在之前我们配置java的api链接的是51.78服务端,那么我们先来看看里面有什么文件。

可以看到,只有一张图片,下面我们调用java获取上传URL的方法:

可以看到我们调用方法,得到了一个带有签名信息的URL,下面我将使用这个url来进行文件的上传:

需要注意一下,这里是要发送一个PUT请求,还有上传的是binary二进制文件流。

这里上传成功后,并没有返回任何信息...我们打开51.78服务来看看

文件已经成功上传了,在看看我们的51.80备份库。

我们看到,也备份成功了。这一串路径是因为51.78文件服务的存储地址就在这个路径下面,所以备份的时候也将这些路径带来了。

这里需要注意的是 ,我使用的是Postman测试工具来测试的,如果使用前端测试,需要注意跨域的问题,官网推荐使用的上传js工具包是Fetch。我们看看官网的例子:

<input type="file" id="selector" multiple>
<button onclick="upload()">Upload</button>

<div id="status">No uploads</div>

<script type="text/javascript">
    // `upload` iterates through all files selected and invokes a helper function called `retrieveNewURL`.
    function upload() {
        // Get selected files from the input element.
        var files = document.querySelector("#selector").files;
        for (var i = 0; i < files.length; i++) {
            var file = files[i];
            // 从服务器获取一个URL
            retrieveNewURL(file, (file, url) => {
                // 上传文件到服务器
                uploadFile(file, url);
            });
        }
    }

    // 发请求到Node.js server获取上传URL。
    // `retrieveNewURL` accepts the name of the current file and invokes the `/presignedUrl` endpoint to
    // generate a pre-signed URL for use in uploading that file: 
    function retrieveNewURL(file, cb) {
        fetch(`/presignedUrl?name=${file.name}`).then((response) => {
            response.text().then((url) => {
                cb(file, url);
            });
        }).catch((e) => {
            console.error(e);
        });
    }

    // 使用Fetch API来上传文件到S3。
    // ``uploadFile` accepts the current filename and the pre-signed URL. It then uses `Fetch API`
    // to upload this file to S3 at `play.min.io:9000` using the URL:
    function uploadFile(file, url) {
        if (document.querySelector('#status').innerText === 'No uploads') {
            document.querySelector('#status').innerHTML = '';
        }
        fetch(url, {
            method: 'PUT',
            body: file
        }).then(() => {
            // If multiple files are uploaded, append upload status on the next line.
            document.querySelector('#status').innerHTML += `<br>Uploaded ${file.name}.`;
        }).catch((e) => {
            console.error(e);
        });
    }
</script>

好了,单机版本基本就结束了,如果后续还有啥要补充的,我会持续更新的。

================

这里还是直接给出VUE的例子

页面

 

<template>
    <div id="addFilesDiag">
        <v-modal title="上传文件对象" :confirm-loading="asyncConfirmLoading" :visible="visible" @ok="handleOkFiles" @cancel="handleCancelFiles" :width="400" :maskClosable="false">
            <v-form direction="horizontal" :model="filesForm" :rules="rules" ref="filesForm">
                <v-form-item label="文件桶名称" :label-col="labelCol" :wrapper-col="wrapperCol">
                    <v-input placeholder="请输入文件桶名称" size="large" :disabled="true" type="text" v-model="filesForm.bucketName"></v-input>
                </v-form-item>
                <v-form-item label="文件对象" :label-col="labelCol" :wrapper-col="wrapperCol" style="height:80px;">
                    <v-input class="" type="file" name="upfile" id="fileExport" v-model="filesForm.upfile" @change="handleFileChange" ref="inputer"></v-input>
                    <v-progress-line :percent="uploadSize" :status="uploadStatus" v-show="uploadShow"></v-progress-line>
                </v-form-item>
            </v-form>
            <div slot="footer">
                <v-button key="cancel" type="ghost" size="large" @click="handleCancelFiles">
                    取消
                </v-button>
                <v-button key="confirm" type="primary" size="large" @click="handleOkFiles">
                    确定
                </v-button>
            </div>
        </v-modal>
    </div>
</template>
<script>
import { mapState } from "vuex";
import api from "@/api/index.js";
export default {
    computed: {
        ...mapState({
            filesForm: state => state.filesAddEdit.filesForm
        })
    },
    props: ['visible'],
    data: function() {
        return {
            uploadStatus: "",
            uploadShow: false,
            uploadSize: 0,
            labelCol: {
                span: 8
            },
            wrapperCol: {
                span: 15
            },
            rules: {
                bucketName: [{
                    required: true,
                    message: "请填文件桶名称"
                }]
            },
            asyncConfirmLoading: false
        };
    },
    methods: {
        open(bucketName){
            this.uploadShow = false;
            this.uploadStatus = "";
            this.uploadSize = 0;
            console.log("方法",bucketName);
            this.filesForm.bucketName = bucketName
        },
        handleFileChange (e) {
            const inputDOM = this.$refs.inputer;
            // 通过DOM取文件数据
            this.file    = inputDOM.files[0];
            this.errText = "";

            const size = Math.floor(this.file.size / 1024);
            console.log(this.file);
            //new一个FileReader实例
            const reader = new FileReader();
            const _this = this;
            // reader.readAsText(this.file)  //文本读取,默认utf-8,格式 其他格式:reader.readAsText(this.file, "GBK")
            reader.readAsDataURL(this.file);  //base 64 读取
            reader.onload = function(e){
                // 读取结果
                console.log(this.result);
                // if (size > ...) {
                //     // 这里可以加个文件大小控制
                //     return false
                // }
                // vue 中使用 window.FormData(),否则会报 'FormData isn't definded'
            };
        },
        handleOkFiles() {
            const file = document.querySelector("input[type=file]").files[0];
            if(!file){
                this.$message.warning("请选择文件", 1);
                return
            }
            this.uploadShow = true;
            this.rolebutstate = true;
            const that = this;
            this.$store.dispatch("filesAddEdit/addSave",
                function(progressEvent){
                let complete = (progressEvent.loaded / progressEvent.total * 100 | 0);
                console.log("上传进度:",complete)
                    that.uploadSize = complete
            }).then(res => {
                this.rolebutstate = false;
                console.log("外面", res);
                if (res.success) {
                    this.$message.success("上传成功", 1);
                    this.$refs["filesForm"].resetFields();
                    this.$emit("ok");

                }else{
                    this.$message.error("上传失败", 1);

                }
            });
        },
        handleCancelFiles() {
            this.rolebutstate = false;
            this.$emit("cancel");
            this.$refs["filesForm"].resetFields();
        }
    }
};
</script>

JS

import Vue from "vue";
import api from "@/api/index.js";
import axios from "axios";
//权限管理模块
export default {
    namespaced: true,
    state: {
        iptstate: false, //表单是否可以编辑
        watch: false, //表单是否为查看状态
        sysidstate: false, //表单是否为编辑状态
        titleName: "新增文件对象",
        filesForm: {
            bucketName: "",
            perfixName: "",
            expires: "",
            dateFile: 1,
            upfile: ""
        }
    },
    actions: {
        // 添加文件对象
        addSave: async function({ dispatch,commit, state },progressEvent) {
            let params = state.filesForm;
            const file = document.querySelector("input[type=file]").files[0];
            console.log(file)

            const options = {};
            options.method = 'PUT';
            options.onUploadProgress = progressEvent;
            options.data = file;
            // 这里api.getURL("files/getUploadUrl")从后台获得了真正上传的签名URL
            return await api.request("get", api.getURL("files/getUploadUrl"), {bucketName: params.bucketName, objectName: file.name}).then(resUrl => {
                console.log(resUrl.data.data)
                options.url = resUrl.data.data;
                // 设置URL后,上传到minio
                return axios(options).then((res) => {
                    console.log("res...", res);
                    return {success:true};
                }).catch(thrown => {
                    return {success:false};
                });
            });
        }
    },
    mutations: {
        // 初始化数据
        CHANGE_BASIC_ID(state, data) {
            state.filesForm.bucketName = "";
            state.filesForm.perfixName = "";
            state.filesForm.expires = "";
            state.filesForm.dateFile = 1;
            state.filesForm.upfile = "";
        }
    }
    //getters: {}
};

效果

好了,这里VUE的上传就算完成了。

 

 

 

 

 

评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BUG胡汉三

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值