前端文件上传下载及进度条实现

/ 文件存储前端 / 2 条评论 / 10071浏览

这一次的java大作业是关于资源管理的一个小项目,里面涉及到了文件的上传与下载,在做的过程中遇到了一些问题,也想着实现一些曾经没实现过的功能,比如进度条、异步下载文件等。之前开发的VR360里面有涉及到文件操作,但是那个系统没有权限管理,所有资源都可以通过静态文件映射即可下载,而这次我加了一些权限管理,所以“未经允许”的用户不能随便上传、下载文件,并且每次操作都要进行记录,那么这个时候之前的办法就行不通了,此时只能进行异步操作了。

异步上传文件及进度条实现

基本实现

需求:

  • 通过ajax上传文件
  • 上传文件时同时附带上传参数
  • 实时显示上传进度条

如果是同步提交需要在form表单里面添加一个参数enctype="multipart/form-data",这个时候我们的表单是这样写的:

<form action="/upload" enctype="multipart/form-data">
    <input type="file" name="file" id="imgFile" accept="image/jpeg,image/gif,image/png">
</form>

在提交的时候只需要submit就可以了,但如果我要加入另外的参数,甚至需要在请求的时候加一些header参数,那么同步方法就无法实现了,这个时候我们只好用异步,我的做法是这样的。

显示创建一个表单,里面可以是多个input,对文件选择的input取一个id名,然后在上传的时候直接获取即可,这里我直接贴出实际写的时候的代码。

html代码

<input type="file" name="img" id="imgFile" accept="image/jpeg,image/gif,image/png" style="visibility:hidden; height:0;width: 0">
<div class="form-group row" style="margin: 40px 0">
    <label class="col-sm-2 col-form-label"><b>Resource Image</b>*</label>
    <div class="col-sm-10">
        <figure class="figure">
            <img src="../images/img-icon.png" id="chooseImg" class="figure-img img-fluid rounded shadow" alt="Select an image no larger than 1M in size.">
            <figcaption class="figure-caption">Select an image no larger than 1M in size.</figcaption>
        </figure>
    </div>
</div>
<form id="resourceForm">
    <div class="form-group row" style="margin: 40px 0">
        <label class="col-sm-2 col-form-label"><b>Title</b>*</label>
        <div class="col-sm-10">
            <input type="text" name="title" class="form-control" maxlength="20" placeholder="Please enter your title within 20 words">
        </div>
    </div>
    <div class="form-group row" style="margin: 40px 0">
        <label class="col-sm-2 col-form-label"><b>Summary</b>*</label>
        <div class="col-sm-10">
            <input type="text" name="summary" class="form-control" maxlength="30" placeholder="Please enter your abstract within 30 words">
        </div>
    </div>
    <div class="form-group row" style="margin: 40px 0">
        <label class="col-sm-2 col-form-label"><b>Resource Type</b>*</label>
        <div class="col-sm-10">
            <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="PPT" value="ppt" name="type" class="custom-control-input" checked>
                <label class="custom-control-label" for="PPT">PPT</label>
            </div>
            <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="examination" value="examination" name="type" class="custom-control-input">
                <label class="custom-control-label" for="examination">Examination</label>
            </div>
            <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="project" value="project" name="type" class="custom-control-input">
                <label class="custom-control-label" for="project">Project</label>
            </div>
            <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="software" value="software" name="type" class="custom-control-input">
                <label class="custom-control-label" for="software">Software</label>
            </div>
        </div>
    </div>
</form>
<div class="form-group row" style="margin: 40px 0">
    <label class="col-sm-2 col-form-label"><b>Choose File</b>*</label>
    <div class="input-group input-file col-sm-10" name="file" tabindex="0" data-toggle="tooltip" data-placement="top" title="支持类型:png、jpg、jpeg、gif、zip、doc、docx、xlsx、xls、ppt、pptx、json、txt、MP2、MP3、MP4">
        <input type="text" class="form-control" placeholder='Please choose a file...' />
        <span class="input-group-btn " >
                    <button class="btn btn-primary btn-choose" type="button">Choose</button>
                </span>
        <span class="input-group-btn">
                        <button class="btn btn-warning btn-reset" type="button">Reset</button>
                </span>
    </div>
</div>

js代码

/**
 * 序列化表单,将其转化为json格式
 */
$.fn.serializeJson = function() {
    var arr = this.serializeArray();
    var json = {};
    arr.forEach(function(item) {
        var name = item.name;
        var value = item.value;
        if (!json[name]) {
            json[name] = value;
        } else if ($.isArray(json[name])) {
            json[name].push(value);
        } else {
            json[name] = [json[name], value];
        }
    });
    return json;
}
/**
 * 上传文件按钮
 */
function bs_input_file() {
    $(".input-file").before(
        function() {
            if ( ! $(this).prev().hasClass('input-ghost') ) {
                var element = $("<input type='file' " +
                    "accept='image/jpeg,image/gif,application/json,image/png," +
                    "audio/mp4,video/mp4,application/pdf,text/plain," +
                    "application/vnd.ms-excel,aplication/zip," +
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/msword,audio/mpeg, video/mpeg," +
                    "application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation' " +
                    "class='input-ghost' id='sourceFile' style='visibility:hidden; height:0;width: 0'>");
                element.attr("name",$(this).attr("name"));
                element.change(function(){
                    element.next(element).find('input').val((element.val()).split('\\').pop());
                });
                $(this).find("button.btn-choose").click(function(){
                    element.click();
                });
                $(this).find("button.btn-reset").click(function(){
                    element.val(null);
                    $(this).parents(".input-file").find('input').val('');
                });
                $(this).find('input').css("cursor","pointer");
                $(this).find('input').mousedown(function() {
                    $(this).parents('.input-file').prev().click();
                    return false;
                });
                return element;
            }
        }
    );
}
/**
 * 上传资源
 */
function checkResource() {
    var resourceForm = $("#resourceForm").serializeJson();
    var file = $('#sourceFile').get(0).files[0];
    var img = $('#imgFile').get(0).files[0];
    if(img == null){
        systemAlert("red","请为资源文件选择一张封面图片")
    }else if (resourceForm.title.trim() == "") {
        systemAlert("red","资源标题不能为空")
    }else if(resourceForm.summary.trim() == ""){
        systemAlert("red","资源摘要不能为空")
    }else if(file == null){
        systemAlert("red","请选择资源文件")
    }else{
        systemConfirm("确定上传该资源吗?",function () {
            var formData = new FormData();
            formData.append("file",file);
            formData.append("img",img);
            formData.append("title",resourceForm.title);
            formData.append("summary",resourceForm.summary);
            formData.append("type",resourceForm.type);
            formData.append("introduction",editor.getData());
            uploadResource(formData);
        })
    }
}

上面的js函数已经将文件的FormData存到一个变量里面了,其中有量个文件,一个是图片file一个是资源file,并且将我们需要传入的其他参数也相应的加进去了(为了防止读者验证出错,工具方法也一并给出),接下来的我们就可以进行上传数据的ajax请求了,这里我是用了jQuery的ajax,在请求的过程中我们需要对xhr进度进行监听,以实现进度条的功能,下面是我写的上传资源的方法(写的不是很好仅供参考哦~)。

/**
 * 将文件上传至服务器
 * @param formData
 */
function uploadResource(formData) {
    if($.cookie(USER_DIGEST_COOKIE) == null){
        systemConfirm("您未登录,是否前往登录?",function () {
            location.href = "login.html?client_url="+urlEncode(location.href);
        })
    }else{
        $(".progress-model").fadeIn("fast");
        $.ajax({
            type:"post",
            async:true,  //这里要设置异步上传,才能成功调用myXhr.upload.addEventListener('progress',function(e){}),progress的回掉函数
            data:formData,
            headers:{
                token:$.cookie(USER_DIGEST_COOKIE)  //  用于请求者身份验证
            },
            url: HOST + COMMON.NONPUBLIC_PREFIX + API.RESOURCE + "/upload",
            processData: false, // 告诉jQuery不要去处理发送的数据
            contentType: false, // 告诉jQuery不要去设置Content-Type请求头
            xhr:function(){
                myXhr = $.ajaxSettings.xhr();
                if(myXhr.upload){ // check if upload property exists
                    myXhr.upload.addEventListener('progress',function(e){
                        var loaded = e.loaded;                  //已经上传大小情况
                        var total = e.total;                      //附件总大小
                        var percent = Math.floor(100*loaded/total)+"%";     //已经上传的百分比
                        $("#progressPercent").text(percent);
                        $("#progressBar").css("width",percent);
                    }, false); // for handling the progress of the upload
                }
                return myXhr;
            },
            success:function(res){
                console.log(res);
                switch (res.status) {
                    case -1:
                        systemAlert("red","系统出错!上传失败,"+res.msg);
                        break;
                    case 0:
                        uploadSource = true;    // 修改页面上传状态
                        autoCloseAlert("green","上传成功!3秒后跳转",3000,function () {
                            location.href = "/resource-view.html?id="+res.data;
                        });
                        break;
                    case 100015:
                        systemAlert("red","您的操作太快,请稍后再试哦~");
                        break;
                    case 100016:
                        systemAlert("red","由于你的请求过快,被视为恶意请求,系统已禁用你的请求,2小时后失效。<br>为营造良好上网氛围,请不要进行恶意操作!");
                        break;
                    case 100017:
                        systemAlert("red","你的ip正处于禁用期间,暂无法使用该系统,请解禁后再试(禁用时间2小时)<br>为营造良好上网氛围,请不要进行恶意操作!");
                        break;
                    case 10006:
                        systemAlert("red","您暂无权限上传资源文件!如需开通权限请联系管理员。");
                        break;
                    case 10009:
                        systemAlert("red","文件格式错误!");
                        break;
                    case 100011:
                        systemAlert("red","资源文件太大");
                        break;
                }
            },
            error:function(res){
                systemAlert("red","出错啦,code:"+res.status);
            },
            complete:function () {
                $(".progress-model").fadeOut("fast");
                $("#progressBar").css("width","0%");
            }
        });
    }
}

效果图

下面是以上代码实现的效果图 https://vr360-beifengtz.oss-cn-beijing.aliyuncs.com/beifeng-blog/article/%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8/20181127220722.jpg

https://vr360-beifengtz.oss-cn-beijing.aliyuncs.com/beifeng-blog/article/%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8/20181127220956.jpg

https://vr360-beifengtz.oss-cn-beijing.aliyuncs.com/beifeng-blog/article/%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8/20181127221012.jpg

异步下载文件及进度条显示

对于下载文件来说,平常的操作可能就是直接抛给客户端一个文件地址,通过浏览器打开这个地址,如果浏览器无法识别这个文件会默认下载该文件,这也是我之前的一种做法。还有一种做法就是用js创建一个form表单,通过js来请求文件地址来实现下载,这种方法也算是同步的,同样的诟病就是无法传入自定义的参数,特别是对请求头进行传值。现在的需求是这样的:只允许登录后的用户下载文件,并且客户端也只能拿到一个文件地址,通过这个地址来进行下载,用浏览器直接打开是不行的,因为没有进行身份验证(这个的具体实现后面会出文章分享我的做法),后台身份验证的方式是从请求头里面传入一个token来进行,很明显,要想在下载文件的通知只能通过异步的方法,接下来分享一下我的做法。

具体实现

基本思路:拿到文件地址、用户身份令牌-->将身份令牌放入请求头中-->异步请求文件地址-->以二进制Blob的形式接收文件-->将二进制写入Reader或Blob对象中-->将Blob对象保存成文件

简述需求

  • 登录用户才可下载文件,未登录用户不可下载
  • 实时显示下载进度条

接下来也将我实际写的贴出来,因为jQuery封装的ajax对返回类型进行了修改,它不能接受blob类型的响应,这个时候我们还是得回到ajax本身XMLHTTPRequest对象了,下面是我写的方法,不是很好仅供参考:

/**
 * 下载文件
 * @param url 例如:/v1/nonpub/download/3ab44b5afb83487caea2272984c59f96/2018/11/27/1543327807299.pdf
 */
function download(url) {
    var token = $.cookie(USER_DIGEST_COOKIE);
    if(token == null){
        systemAlert("red","请登录后下载");
    }else{
        downloadFile = false;   // 开启页面保护,防止用户关闭页面
        var fileName = url.substring(url.lastIndexOf(".")-13,url.length);
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);    // 也可以使用POST方式,根据接口
        xhr.responseType = "blob";  // 返回类型blob
        xhr.setRequestHeader("token",token);    // 传入用户令牌(此参数后台定义)
        xhr.setRequestHeader("Download-File-Digest",tempFileId);    // 传入下载文件摘要,用于统计(此参数后台定义)
        $(".progress").fadeIn("fast");  //  显示进度条
        //监听进度事件
        xhr.addEventListener("progress", function (evt) {
            var percent = (evt.loaded/evt.total)*100;
            $("#progressBar").css("width",percent+"%");
        }, false);

        // 定义请求完成的处理函数,请求前也可以增加加载框/禁用下载按钮逻辑
        $("#download").attr("disabled",true);
        xhr.onload = function () {
            // 请求完成
            if (this.status === 200) {
                downloadFile = true;
                // 返回200
                var blob = this.response;
                // downFile(blob,fileName);
                saveAs(blob,"E resource-"+fileName);
                $(".progress").fadeOut("fast");
                $("#progressBar").css("width","0%");
                $("#download").attr("disabled",false);
            }
        };
        // 发送ajax请求
        xhr.send()
    }
}

上面的方法在保存文件的时候我用到了FileSaver.js这个插件,它保存文件的兼容性比较好,之前在网上又看到过一个方法,开始也是用的这个方法,但是发现在大部分浏览器里面可以,少部分浏览器(IE、360)无法使用,在手机端直接不能兼容,所以弃用了该方法,不过看它的实现可以很容易看到保存文件的原理,FileSaver.js的做法其实和它类似。

/**
 * js下载文件流(兼容性不好)
 * @param blob
 * @param fileName
 */
function downFile(blob, fileName) {
    if (window.navigator.msSaveOrOpenBlob) {
        navigator.msSaveBlob(blob, fileName);
    } else {
        var link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = fileName;
        link.click();
        window.URL.revokeObjectURL(link.href);
    }
}

效果图

下面是我实现的效果图

https://vr360-beifengtz.oss-cn-beijing.aliyuncs.com/beifeng-blog/article/%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8/20181127223238.jpg

https://vr360-beifengtz.oss-cn-beijing.aliyuncs.com/beifeng-blog/article/%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8/20181127223251.jpg

https://vr360-beifengtz.oss-cn-beijing.aliyuncs.com/beifeng-blog/article/%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8/20181127223301.jpg

结语

以上的做法也是我第一次尝试,不知道在真实应用中是否是这样操作(毕竟不是专门学前端的),这篇博客也是为了记录下文件的相关操作,以便于以后翻阅温习,欢迎和我交流学习~~

微信公众号浏览体验更佳,在这里还有更多优秀文章为你奉上,快来关注吧!

北风IT之路

  1. html代码没贴完整 服了

  2. 异步下载文件及进度条显示能不能帖出详细代码!3Q