HTML多图上传中的问题探索

在编写实现“天云图床”这个程序的时候,想要实现批量上传图片的操作,便于自己和广大站长的使用,其中遇到了一些简单但又不常见的问题,网上资料良莠不齐,遂总结一遍

一开始,天云图床只有单图上传的功能,开源到技术QQ群中,大家都建议小东写一个多图上传的功能,于是趁着周六有时间,开始编写代码。

本以为是一个非常简单的工作,哪知道弄了一整天的时间。

0x01一开始的思路

一开始,后台API服务仅支持单图上传,想着,不动服务器的代码,尝试从客户端来实现。

开发前的环境:

新浪图床服务端仅支持单图(form表单形式)上传图片即可

前端(客户端 https://api.top15.cn/picbed/)使用 ajax,实例 form表单 内容,发送给服务端,服务端接受数据,读取$_FILES中的文件

服务端(PHP),读取post过来的文件,然后模拟POST远程上传新浪服务器,新浪图床的接受参数为`base64`编码过的图片资源。

0x02 客户端的问题

在这之前,参考了一些开源项目,比如 ssi-uploaderzuypload 这些基于 jquery 的三方插件,他们的共同功能是,能够读物本地文件,并将本地图片展示在前端页面上。

站长之家中多图上传案例

在站长之家的网站上找到如上的多图上传案例,支持拖拽和多图上传,想着自己是否可以尝试写一下呐?

首先客户端中 HTML 代码需要注意 inputname 属性值要为数组,否则上传了数据,在服务端值会接受一个文件数据

如下是我在实现过程中的方式,并设置了隐藏,因为本身 input 文件组件不够美观,用另外的按钮来控制此组件。

<form id="form" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple="multiple" id="files" accept="image/png,image/gif,image/jpg,image/jpeg" style="display: none;">
</form>

与此同时,想要实现读取 files 中的图片并显示在界面上,那么就需要监听 input[type=file] 组件的变换

<script src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="javascript/text">
    $(function(){
        $("#files").change(function(){
            console.log($("#files")[0].files)    //得到一个FileList对象
        })
    })
</script>

上述代码在控制台打印上传文件的信息,可以看到如下情况:

files内容

files中,包含了文件的大小,名字,时间和相对路径


0x03 如何展示预览图片?

HTML5 中有一个 FileReader() 的东西,能够读取文件内容,

// javascript

function loadImg(){
    //获取文件
    var file = $("#files")[0].files[0];

    //创建读取文件的对象
    var reader = new FileReader();

    //创建文件读取相关的变量
    var imgFile;

    //为文件读取成功设置事件
    reader.onload=function(e) {
        imgFileUrl = e.target.result;
         $("#gallery").append('<img src="'+imgFileUrl+'" width="200px" height="200px">');
    };
    reader.readAsDataURL(file);
}


//////////////////////////////////////////////////////////////
FileReader对象的方法
方法名                  参数              描述
readAsBinaryString    file              将文件读取为二进制编码
readAsText            file,[encoding]      将文件读取为文本
readAsDataURL        file              将文件读取为DataURL
abort                (none)              终端读取操作

在控制台中执行上述代码,然后运行 loadImg() 函数,即可打印准备上传文件中的第一张图片内容到控制台,类似这样的字符串 data:image/png;base64,iVBORw0KGgoAAAANSUhEU...,这是将图片进行了base64编码,服务端只需要解码base64即可恢复原文件。将内容复制浏览器的地址栏打开是没法儿预览的,小东现在想了一下,其原因主要是:浏览器地址栏,类似GET请求,限制了URL的长度和大小,故无法正常显示图片(此处浪费了较多时间,一直以为读取出来的数据不对!!!其实是完全正确的)。

然后本地写了一个测试文件,如下:

base64

不同文件的数据头部是不一样的:data:image/png;base64,这是上传了一个 png 图片的案例,上传 jpeg,gif等等头部都会改变,真正有效的是,(逗号)后面的内容,才是文件内容的编码,客户端直接提交整个参数即可无需处理,处理交给服务端即可。

服务端支持一个图片上传,那么我们循环读取,然后上传即可,至于返回的数据,以json方式传输,将单个上传信息直接array_push()到一个数组即可。

服务端代码的改写(部分):

<?php

$result = [];
foreach ($_POST as $key => $value) {
    $file = $value;            //BASE64
    $cookie = $config['cookie'];
    $file = explode(',', $file);
    $imgarr = upload($file[1], $type, $cookie);        //TYPE是GET得到,值为bs64,避免post内容混淆
    array_push($result, $imgarr);
}
echo json_encode($result);
exit();

?>

至此,客户端 base64 版本及服务端改造完成。


0x04 FormData版本

Javascript中,有Jquery中的 FormData方法,使得 Form表单以 ajax 方法传输变得可行。

博主个人觉得这种方式更好,代码具有简洁性,同时后端按照传统方式来处理

前端代码(javascript):

<script type="text/javascript">
        $(function(){

            //选择图片
            $('.btn-primary').click(function(){
                $('#files').trigger('click');
            })

            $('#files').change(function(){
                var fileNum = $("#files")[0].files.length;
                $('.btn-primary').text('上传文件('+fileNum+')');
            })

            //点击上传图片
            $('#upload_btn').click(function(){
                  $("#upload_btn").text("上传中...");
                $('#upload_btn').attr('disabled', true);
                var fileNum = $("#files")[0].files.length;
                if(fileNum && fileNum <= 10){
                    $.ajax({
                        url : "https://api.top15.cn/picbed/picApi.php?type=multipart&num=multiple",        //此API可供大家免费使用,且行且珍惜
                        type : 'POST',
                        cache: false,
                        data : new FormData($('#form')[0]),
                        processData : false,
                        contentType : false,
                        async : false,
                        success : function(data) {
                            if(data){
                                var str = '';
                                var str2 = '';
                                $.each(data, function(index, obj){
                                    str += '<a href="'+ obj.url2+'" target="_blank">'+obj.url2+'</a><br>';
                                    str2 += '<img src="'+obj.url2+'" width="'+obj.width+'" height="'+obj.height+'">';
                                })
                                $(".imgs_src").html(str);
                                $(".textarea").text(str2);
                            }
                        }
                    });
                    $("#upload_btn").attr('disabled', false);
                    $('#files').val('');
                    $('.btn-primary').text('上传文件(0)');

                }
                else{
                    alert("请选择上传文件,且上传文件数不超过10!");
                    $('#files').val('');
                    $('.btn-primary').text('上传文件(0)');
                    $("#upload_btn").attr('disabled', false);
                }
              $("#upload_btn").text("一键上传");
            })
        })
</script>

后端处理:

<?php
    $result = [];
    foreach ($_FILES['files']['tmp_name'] as $key => $value) {        //从$_FILES中读取文件
      $file = $value;
      $cookie = $config['cookie'];
      $imgarr = upload($file, $type, $cookie);
      array_push($result, $imgarr);
    }
    echo json_encode($result);
    exit();
?>

0x05 最终完成:

完成项目的展示网站:https://api.top15.cn/picbed/

开源地址:https://github.com/dyboy2017/sinaPictureBed

天云图床

0x06 总结

本次程序实现中,input 控件中的 name 属性值要为数组,其次 HTML5 功能 FileReader() 是一个不错的方法,有利于及时展示即将上传的图片,一些多图上传的插件,使用的就是此种方式,但是会耗费客户端较多的计算资源,可能导致客户端的卡顿(可以限制上传图片的大小以及数量是一种解决方案),简单的直接使用 Jquery 中的 FormData() 即可使用 ajax异步传输数据,最后测试,发现在 Android 手机浏览器上还是无法多图上传,但是在 Iphone 手机上可以多图上传,在PC端,360浏览器运行该页面会卡顿但不影响正常操作,在 Firfox 浏览器上表现正常!HTML5特性在需要的查询文档使用即可,

发表评论 / Comment

用心评论~

金玉良言 / Appraise
疯子战神LV 1
2019-02-26 00:45
非常棒的文章,学习了

Warning: Cannot modify header information - headers already sent by (output started at /www/wwwroot/blog.dyboy.cn/content/templates/dyblog/footer.php:56) in /www/wwwroot/blog.dyboy.cn/include/lib/view.php on line 23