[毕设项目]Thinkphp结合PHPExcel实现Excel数据的导入

毕设论文写的差不多了,写的时候一直在想那个没完成的功能,就是在系统中加一个批量导入功能。于是,在完成论文内容后开始着手编写这部分代码。PS:恰逢Github又挂了,所以只能先在简书记录了。

Thinkphp文件上传

Thinkphp自带文件上传功能,主要有单文件上传和多文件上传两种方式,由于对本次设计区别不是很大,这里不做过多介绍,具体参见文档http://www.kancloud.cn/manual/thinkphp/1876

上传操作

ThinkPHP文件上传操作使用Think\Upload类,根据官方文档,Upload类参数主要有以下几种:

Upload类支持的属性设置

其中,圈红部分是常用参数。
通过实例化Upload类,我们就可以向后台传入文件了。我们结合官方文档进行解读:

$config = array(
    'maxSize'    =>    3145728,    //设置上传附件的大小
    'rootPath'   =>    './Uploads/',   //设置上传文件的根目录
    'savePath'   =>    '',    //设置上传文件的子目录
    'saveName'   =>    array('uniqid',''),  //设置文件保存规则
    'exts'       =>    array('jpg', 'gif', 'png', 'jpeg'), //设置上传文件的后缀
    'autoSub'    =>    true,     //设置保存上传文件采用子目录形式存储
    'subName'    =>    array('date','Ymd'),  //设置子目录创建方式
);
$upload = new \Think\Upload($config);  // 实例化上传类

Upload类也支持动态赋值,形式如$upload->maxSize = 3145728;,效果是一致的。

上传文件信息

通过上传。文件被放到指定文件夹中,并在Upload类中携带以下属性:

文件信息

这里同样圈红部分是常用参数,这里需要注意下,savename文件的保存名字是带后缀的。

多文件上传和单文件上传

两者区别较小(我只是根据API来说的,并未仔细阅读过源码),多文件上传无非是前端可提交多个文件数据,即<input>标签可编写多个。而单文件上传无非是使用另一个API,即uploadOne方法,只是这种方法注意需要指定文件,如$info = $upload->uploadOne($_FILES['photo1']);

代码编写

对于后台文件上传这部分,我把代码编写到了\Application\Admin\Common\Common\function.php中,作为公共方法进行调用:

/**
 * 上传文件
 * @param array $fileName 文件
 * @param string $dir   文件上传子目录
 * @param string $saveName  文件名字保存规则
 * @return array 操作结果
 */
function uploadFile($fileName = array(), $dir = '', $saveName = '')
{
    $config = array(
        'maxSize' => 3145728,               //设置文件大小
        'exts' => array('xls', 'xlsx'),        //设置附件上传类型
        'rootPath' => './Public/Excel/',    //设置文件上传根目录
        'savePath' => $dir . '/',           //设置文件上传子目录
        'autoSub' => false,                 //自动使用子目录保存文件
        'saveName' => $saveName,            //上传文件的保存规则
    );
    $upload = new \Think\Upload($config);   //实例化上传类
    $info = $upload->uploadOne($fileName);  //上传单个文件
    if (!$info)
        return array('status' => 0);
    else
        return array('status' => 1, 'filepath' => $upload->rootPath . $info['savepath'] . $info['savename']);
}

jQuery实现Ajax异步提交文件上传

前端的东西算是新手,所以可能写的不是很清楚,并且有些地方是直接转载别人的话(我会标出来),见谅……

用过jquery的Ajax的人肯定都知道,Ajax的默认编码方式是application/x-www-form-urlencoded,此编码方式只能编码文本类型的数据,因此Ajax发送请求的时候,会把data序列化成 一个个String类型的键值对,此种传输数据的方式能够满足大部分应用场景,然而当传输的数据里有附件的时候,此序列化机制便是我们的绊脚石。Ajax本身的序列化机制的硬伤归其原因在于在html4的时代,没有FileReader接口,在页面里无法读取File(Blob)文件,用document.getElementById("文件控件的id").value只能拿到文件的name,因此去序列化去编码它也无从谈起。
——http://blog.csdn.net/qq_33556185/article/details/51086114

上面这段大概就能理解,Ajax本来是不支持文件上传,因为上传的是数组对象,而HTML5新增了一种数据对象FormData,可以快速拿到整个表单对象。而要作此操作,需要先在表单中添加enctype="multipart/form-data"属性,然后在Ajax中屏蔽编码机制。具体代码参考如下:

<div class="modal fade" id="uploadModal" role="dialog" aria-labelledby="uploadModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                        aria-hidden="true">×</span></button>
                <h4 class="modal-title" id="uploadModalLabel">上传文件</h4>
            </div>
            <div class="modal-body">
                <form class="form-horizontal" id="uploadForm" enctype="multipart/form-data">
                    <input type="file" id="leftFile" style="display:none" name="classFile">
                    <div class="input-append">
                        <input type="text" class="input-large" id="fileCover">
                        <a class="btn btn-default" onclick="$('input[id=leftFile]').click();">浏览</a>
                        <a class="text-red">文件后缀:.xls或.xlsx</a>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-primary" id="submitUpload">提交</button>
                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
            </div>
        </div>
    </div>
</div>

批量上传模态框效果图

这里讲些注意点,表单中的<input>我是添加了style="display:none"属性的,所以界面中看到的并不是原生的<input>标签;这里的文本框是通过JS进行控制显示内容的,后面会讲到。
HTML代码部分完成后,只需要结合jQuery就可以完成前端向后端发送文件的请求 。以下是JS代码部分:

//批量添加
        $upload.click(function () {
            $upload_modal.modal('show');
            return false;
        });
        //input框中控制显示文件路径
        $('input[id=leftFile]').change(function () {
            $('#fileCover').val($(this).val());
        });
        //文件上传提交
        $upload_submit.click(function () {
            var fileExt = checkFile();
            if (fileExt[0] === '') {
                toastr.warning('请上传文件');
            } else if (fileExt[0] !== 'xls' && fileExt[0] !== 'xlsx') {
                toastr.warning('上传文件类型不支持');
            } else {
                var formData = new FormData($upload_form[0]);
                //TODO: Ajax异步文件提交
            }
        });

这里单独抽出来Ajax的异步提交内容:

$.ajax({
    url: 'uploadClass',
    type: 'post',
    data: formData,
    cache: false,
    processData: false,
    contentType: false,
    dataType: 'json',
    success: function (response) {
        switch (response.status) {
            case 'success': {
                toastr.success(response.message);
                $upload_modal.modal('hide');
                    $table.bootstrapTable('refresh');
                break;
            }
            case 'error': {
                toastr.error(response.message);
                $upload_modal.modal('hide');
                $table.bootstrapTable('refresh');
                break;
            }
        }
    }
});

这里processDatacontentType是用来屏蔽Ajax的编码机制,这样上传的FormData对象就不会上传到后台就变成其他编码的了。另外提一点,这里的dataType属性是指返回对象属性为JSON格式(这也是毕设中发现的一点)。
这样,前端就将文件数据传给后台了,后台Controller通过$_FILES[]数组进行获取,代码如下:

public function uploadClass()
    {
        $upload = uploadFile($_FILES['classFile'], 'Class', date('TmdHis'));  //获取上传的 文件
        if ($upload['status']) {
            $excel = import_excel($upload['filepath']);
            D('Clas')->upload($excel);
        }
        show('success', '批量添加成功');
    }

PHPExcel

PHPExcel是一款处理表格文件数据上传下载的插件,项目地址:https://github.com/PHPOffice/PHPExcel ,文档也在上面有,但是由于时间问题我没仔细阅读过,大部分还是根据别人的博客上的代码学习的。

引入PHPExcel插件

Thinkphp是将PHPExcel引入\Think\Library\Vendor 第三方扩展库中,Thinkphp自带引入函数 Vendor(),具体我就不介绍了,有兴趣可以查下官方文档。

代码编写

由于文档没有完整读过,所以这里贴代码了,使用起来很简单,这里我对表格内容转换为数组部分做了处理,使得获取的数据更符合我后台的数据库模型接口设计。这部分代码同样我存放在\Application\Admin\Common\Common\function.php中作为公共方法使用:

/**
 * 导入表格
 * @param $filePath 文件路径
 * @return array    表格数据
 */
function import_excel($filePath)
{
    // 判断文件是什么格式
    $type = pathinfo($filePath);
    $type = strtolower($type["extension"]);
    $type = $type === 'Excel5' ? $type : 'Excel2007';
    ini_set('max_execution_time', '0');
    Vendor('PHPExcel.Classes.PHPExcel');                    // 引入PHPExcel类
    $objReader = PHPExcel_IOFactory::createReader($type);   // 判断使用哪种格式
    $objPHPExcel = $objReader->load($filePath);             // 加载表格
    $sheet = $objPHPExcel->getSheet(0);              // 获取Excel表格sheet1页
    $highestRow = $sheet->getHighestRow();                  // 取得总行数
    $highestColumn = $sheet->getHighestColumn();            // 取得总列数
    //循环读取excel文件,读取一条,插入一条
    for ($j = 2; $j <= $highestRow; $j++) {                 // 从第一行开始读取数据
        for ($k = 'A'; $k <= $highestColumn; $k++) {        // 从A列读取数据
            $data[$j - 2][$objPHPExcel->getActiveSheet()->getCell("$k" . "1")->getValue()] = $objPHPExcel->getActiveSheet()->getCell("$k$j")->getValue();  // 读取单元格
        }
    }
    return $data;
}

补充:数据库模型设计

到这里前面的基本都讲清楚了,前端、后台的代码都贴完整了,这里主要是针对毕设项目做一个数据库模型的补充。

public function upload($jsonArray = array())
    {
        foreach ($jsonArray as $item) {
            $result = $this->Clas->add($item, $option = array(), $replace = true);
            if (!$result) return false;
        }
        return true;
    }

这里的add()方法的参数需要讲下,正常来说add()方法中只需要传格式规范的数据数组就可以了,但是如果遇到例如这里的批量上传可能会碰见数据主键重复的现象,如果对于大量数据来说,每次数据库录入重复数据报错一次会很影响速度,所以Thinkphp提供了一个参数(只支持3.2.3版本以上)$replace,当设置为true时,如果录入数据遇到重复主键,Thinkphp会自动进行覆盖更新数据,不用担心报错跳出的情况。

后记

当然,这里也会带来一个问题,批量导入过程中我不知道哪些数据重复主键,这也算毕设中的一个问题,等待以后有空再考虑吧……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,646评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,979评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,391评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,356评论 0 215
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,740评论 3 293
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,836评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,022评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,764评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,487评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,728评论 2 252
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,200评论 1 263
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,548评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,217评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,134评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,921评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,919评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,766评论 2 274

推荐阅读更多精彩内容