java web Excel导入、导出的实现

在做web开发时,我们经常会用到数据表格的导入导出功能,这样可以帮我们节省人工操作的时间,极大提高办事效率,下面,直入正题:
笔者所做的导入导出是针对java springMVC框架、工作簿poi以及前端plupload.js插件设计的。

第一步、总体介绍

首先,来看页面展示,如下图:
这里写图片描述
导入菜单包含模板下载和导入数据,如下图:
这里写图片描述
点击模板下载,弹出模板下载对话框,如下图:
这里写图片描述
点击导入数据,弹出导入对话框,如下图:
这里写图片描述
接下来,再来看看导出,包含导出当前页和导出全部,如下图:
这里写图片描述
点击导出当前页或是导出全部后,生成excel,如下图:
这里写图片描述
最后再来看看导入模板和生成的excel,如下图:
这里写图片描述
我的模板是放在src/main/webapp/template下的。

第二步、模板下载和导入

首先,来看看前台导入、导出的jsp代码:

<!-- 导出,当前页和页大小隐藏域 -->
<input type="hidden" name="currentPage" id="current" value="0"/>
<input type="hidden" name="pageSize" id="size" value="0"/>
ID:<input type="text" name="id" id="userId"/> 姓名:<input type="text" name="userName"/> <a href="javascript:void(0);" id="search">搜索</a>
</form>
</div>

<!-- 导入 -->
<a 
type="import" 
id="import" 
class="easyui-menubutton" 
suffix="xls"
resouseCode="user" 
importurl="sys/user/importData.do"
callback="myCallback">导入</a>
<!-- 导出 -->
<a type="export" id="export" class="easyui-menubutton">导出</a>

然后,再来看看js:

$(function() {
  var uploadsubmenu = '<div id="upload_sub" style="width:100px;">'+
  '<div id="download_templ"><span class="exportDownload"></span>模板下载</div>'+
  '<h1 class="excel-line" id="line1"></h1>'+
  '<div id="import_data"><span class="exportData"></span>导入数据</div>'+
  '</div>';
  if($("#upload_sub").length<1){
    $("body").append(uploadsubmenu);
  }

  //将导入设为菜单按钮
  var importMenu = $('#import').menubutton({ 
    menu: "#upload_sub",
    iconCls:"icon-import"
  }); 

  //为子菜单添加点击事件
  $(importMenu.menubutton('options').menu).menu({
    onClick: function (item) {
      if(item.id == 'download_templ'){
        var resouseCode = $('#import').attr('resouseCode');
        var suffix = $('#import').attr('suffix');
        loadTemplate(resouseCode, suffix);
      }
      if(item.id == 'import_data'){
        var importurl = $('#import').attr('importurl');
        var callback = $('#import').attr('callback');
        var resouseCode = $('#import').attr('resouseCode');
        makerUpload(importurl, callback, resouseCode);
      }
    }
});
});

/**
 * 模板下载
 */
function loadTemplate(resouseCode, suffix) {
  //文件名+后缀名
  var fileName = resouseCode + '.' + suffix

  var templatediv = '<div id="templatediv" style="width:400px;height:200px;">'+
  '</div>';
  if (window.top.$('#templatediv').length == 0) {
    window.top.$("body").append(templatediv);
  }
  var templistspan = "";
  templistspan +='<div class="download-list">'
  templistspan += '<span><a id="downloadBtn" href="file/fileHandle/downloadtemplate.do?fileName='+encodeURI(encodeURI( fileName))+'">'+fileName+'</a></span>'
  templistspan +='</div>';
  window.top.$("#templatediv").html(templistspan);
  //弹出下载模板
  window.top.$("#templatediv").dialog({
    title:"下载模板",
    modal:true  
  });

  //下载模板完了就关闭对话框
  window.top.$("#downloadBtn").click(function() {
    window.top.$("#templatediv").dialog('close');
  });
}

/**
 * 上传导入
 */
function makerUpload(importurl, callback, resouseCode) {
  console.log('弹出导入对话框。。');

  //用户详情对话框
  var importWindow = window.top.$('#importWin');
  if(importWindow.length <= 0) {
    importWindow = window.top.$("<div id='importWin'/>").appendTo(window.top.document.body);
  }
  importWindow.window({
    title:'导入',
    closed: false,
    closable: true,
    draggable: true,
    resizable: true,
    width: 500,
    height: 350,
    modal: true,
    href: 'sys/redirect/redirectHomePage.do?path=import&callback=' + callback + '&resouseCode=' + resouseCode + '&importurl=' + importurl,
    onClose: function() {
      importWindow.window("destroy");
    }
  });
}

然后,再来看看导出对话框对应的jsp页面import.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
  String callback = request.getParameter("callback");
  String importurl = request.getParameter("importurl");
  String resouseCode = request.getParameter("resouseCode");
  System.out.println("callback:" + callback);
  System.out.println("importurl:" + importurl);
  System.out.println("resouseCode:" + resouseCode);
%>
<link rel="stylesheet" href="js/plugs/plupload/queue/css/jquery.plupload.queue.css" type="text/css"></link>
<script type="text/javascript" src="js/plugs/plupload/plupload.js"></script>
<script type="text/javascript" src="js/plugs/plupload/plupload.html4.js"></script>
<script type="text/javascript" src="js/plugs/plupload/plupload.html5.js"></script>
<script type="text/javascript" src="js/plugs/plupload/plupload.flash.js"></script>
<script type="text/javascript" src="js/plugs/plupload/zh_CN.js"></script>
<script type="text/javascript" src="js/plugs/plupload/queue/jquery.plupload.queue.js"></script>
<script type="text/javascript">
var callback = '<%=callback%>';
var resouseCode = '<%=resouseCode%>';
var importurl = '<%=importurl%>';
console.log('callback:', callback);
console.log('resouseCode:', resouseCode);
console.log('importurl:', importurl);
</script>
<!-- <div style="width:100%;height:100%;position:relative;">
    <button id="startImport" style="position:relative;top:224px;left:50px;height:20px;">开始导入</button>
</div> -->

<form id="form">
<div id="uploader">
</div>
</form>

<script type="text/javascript" src="resources/logicJS/common/common.js"></script>
<script type="text/javascript" src="resources/logicJS/common/import.js"></script>

再来看看其对应的import.js:

/*$(function() {
  console.log('windows是否一致', window === window.top);
  $('#startImport').click(function() {
    console.log('开始导入');

    var iframe = $('#mainPanel > iframe' ,window.top.document)[0];
    var currentChildWindow = iframe.contentWindow || iframe.window;
    console.log('callback', callback);
    currentChildWindow[callback]('qiyongkang');
  });
});*/

$(function() {
  var files = [];
  var errors = [];
  var type = 'file';
  var chunk = true;
  var startbutton = null;
  var des = "<div class='upload-sm'>";
  des += "单个文件最大不超过1mb; &nbsp;&nbsp;";
  des += "批量上传文件个数不超过1个; &nbsp;&nbsp;";
  des += "上传文件格式为:xls";
  des += "</div>";

  $("#uploader").pluploadQueue($.extend({
    runtimes : 'flash,html4,html5',
    url : 'file/fileHandle/upload.do?resouseCode=' + resouseCode,
    max_file_size : '1mb',
    file_data_name:'file',
    filters : [],
    dragdrop : false,
    flash_swf_url : 'js/plugs/plupload/plupload.flash.swf',
    init:{
      Init:function(uploader){
        //开始上传按钮
        startbutton = $(".plupload_start");
        $(".plupload_header").html(des);
      },
      QueueChanged:function(uploader){
        if(uploader.files.length > 1){
          var error = "<div class='upload-sm'>";
          error += "<font color=red>最多只能上传1个</font>";
          error += "</div>";
          $(".plupload_header").html(error);
          startbutton.hide();
        }else{
          $(".plupload_header").html(des);
          startbutton.show();
        }
      },
      FileUploaded:function(uploader,file,response){
        if(response.response){
          var rs = $.parseJSON(response.response);
          console.log(rs);
          if(rs.status){
            files.push(file.name);
          }else{
            errors.push(file.name);
          }
        }
      },
      UploadProgress:function(uploader,fs){
        var begin = "<div class='upload-sm'>";
        begin +="开始上传文件<img src='js/plugs/plupload/queue/img/uploading.gif'>";
        begin +="</div>";
        $(".plupload_header").html(begin);
      },
      //上传完毕后触发
      UploadComplete:function(uploader,fs){
        var e= errors.length ? ",失败"+errors.length+"个("+errors.join("、")+")。" : "。";
        var begin = "<div class='upload-sm'>";
        var value=parseInt(fs.length)-parseInt(errors.length);
        begin +="上传完成!共"+fs.length+"个。成功"+value+"个";
        begin +="</div>";
        $(".plupload_header").html(begin);
          importdata(importurl,callback);
      }
    }
  },(chunk ? {chunk_size:'1mb'} : {})));

});

/**
 * 导入数据
 * @param importurl
 * @param callback
 */
function importdata(importurl,callback){
  var des = "<div class='upload-sm'>";
  des +="正在解析入库<img src='js/plugs/plupload/queue/img/uploading.gif'>";
  des +="</div>";
  $(".plupload_header").html(des);

  $.ajax({
    url:importurl,
    type:"POST",
    dataType: "json",
    success: function (data) {
      var msg = "<div class='upload-sm'>";
      msg +=data.msg;
      msg +="</div>";
      $(".plupload_header").html(msg);

      //回调,重新加载数据
      var iframe = $('#mainPanel > iframe' ,window.top.document)[0];
      var currentChildWindow = iframe.contentWindow || iframe.window;
      console.log('callback', callback);
      currentChildWindow[callback]('qiyongkang');
    }
  });
}

其中的chunk的含义,笔者稍作解释,如果文件的大小超过chunk_size,那么此文件将分多次请求上传,待会儿就可以看到后台处理上传的请求逻辑。

第三步、导出当前页和导出全部

对应的jsp代码已在第二步给出,再来看导出对应的js:

/**
 * 初始化导出按钮
 */
function initMyExportMenu(){
  var exportsubmenu = '<div id="exportMenu_sub">'+
  '<div id="export_crrent"><span class="exportcurrent"></span>导出当前页</div>'+
  '<h1 class="excel-line"></h1>'+
  '<div id="export_all"><span class="exportall"></span>导出全部</div>'+
  '</div>';
  $("body").append(exportsubmenu);
  var exports = $("a[type='export']");
  $.each(exports,function(data){

    var exportMenu = $(this).menubutton({ 
      menu: "#exportMenu_sub",
      iconCls:"icon-export"

    }); 
    $(exportMenu.menubutton('options').menu).menu({
      onHide:function(){
      },
      onClick: function (item) {
      if(item.id=="export_crrent"){
        console.log('导出当前页');
        var options = $('#userListTab').datagrid('getPager').data("pagination").options;
        var curr = options.pageNumber;
        console.log('当前页:', curr);
        var pageSize = options.pageSize;
        console.log('页大小', pageSize);

        //通过隐藏域传参数
        $('#current').val(curr);
        $('#size').val(pageSize);

        $("#queryForm").form('submit',{
              url : 'sys/user/exportData.do',
              onSubmit : function() {
                return $(this).form('validate');
              },
              success : function(data) {
              }
         });

      }
      if(item.id=="export_all"){
        //通过隐藏域传参数
        $('#current').val(0);
        $('#size').val(0);

        console.log('导出全部');
        $("#queryForm").form('submit',{
          url : 'sys/user/exportData.do',
          onSubmit : function() {
            return $(this).form('validate');
          },
          success : function(data) {
            console.log('data', data);
          }
         });

      }

      }
    });

  });
}

/**
 * 回调函数
 * @returns
 */
function myCallback(name) {
  console.log('回调函数执行。。' + name);
  //重新加载datagrid
  $("#userListTab").datagrid('reload'); 
}

上面的回调函数是用于导入成功后重新加载datagrid。

第四步、导入导出的后台处理逻辑

首先,来看看UserController.java对应的代码:

/**
     * 
     * importTeleFraudEvent:导入数据. <br/>
     *
     * @author qiyongkang
     * @param request
     * @return
     * @since JDK 1.6
     */
    @RequestMapping
    @ResponseBody
    public ExtJsObject importData(HttpServletRequest request) {
        ExtJsObject result = new ExtJsObject(false, "导入不成功", null);
        try {
            String path = request.getSession().getAttribute("justpath").toString();
            File f = new File(path);
            FileInputStream fis = new FileInputStream(f);
            HSSFWorkbook book = new HSSFWorkbook(fis);
            result = userService.importData(book);
        } catch (Exception e) {
            e.printStackTrace();
            return renderObject(false, "导入不成功", null);
        }
        return result;
    }

    @RequestMapping
    @ResponseBody
    public ExtJsObject exportData(User user, HttpServletRequest request,
            HttpServletResponse response) {
        List<User> userList = null;
        try {
            if (user.getCurrentPage() != null && !user.getCurrentPage().equals(0)) {
                // 导出当前页
                userList = userService.getPageList(user);
            } else {
                // 导出所有
                userList = userService.listAll(user);
            }

            if (userList != null && userList.size() > 0) {
                // 开始导出,获取模板路径
                String templatePath = request.getSession().getServletContext().getRealPath("template/user.xls");

                // 写入工作簿
                userService.exportData(userList, templatePath, response);
            }
        } catch (Exception e) {
            log.error("导出失败", e);
            return new ExtJsObject(false, "导出失败", null);
        }
        return new ExtJsObject(true, "导出成功", null);
    }

以及对应的service实现:

@Override
    public ExtJsObject importData(HSSFWorkbook workBook) {
        HSSFSheet sheet = workBook.getSheetAt(0);
        int startReadRow = 2;
        int lastRowNum = sheet.getLastRowNum();
        for(int i = startReadRow; i <= lastRowNum; i++) {
            try {
                User entity = new User();

                HSSFRow row = sheet.getRow(i);

                //用户编号
                long id = Long.valueOf(ExcelUtil.getCellValue(row.getCell(0)));
                entity.setId(id);

                //用户姓名
                String userName = ExcelUtil.getCellValue(row.getCell(1));
                entity.setUserName(userName);

                //年龄
                int age = Integer.valueOf(ExcelUtil.getCellValue(row.getCell(2)));
                entity.setAge(age);

                userMapper.addUser(entity);
            } catch (Exception e) {
                e.printStackTrace();
                return new ExtJsObject(true, "导入失败", null);
            }
        }
        return new ExtJsObject(true, "导入成功", null);
    }

    @Override
    public List<User> getPageList(User user) {
        return userMapper.listUser(user);
    }

    @Override
    public void exportData(List<User> userList, String templatePath, HttpServletResponse response) throws Exception {
        //获取文件输入流,创建工作簿
        FileInputStream fis = new FileInputStream(new File(templatePath));
        HSSFWorkbook workBook = new HSSFWorkbook(fis);

        //设置导出文件名,并编码
        String fileName = "用户信息导出_" + System.currentTimeMillis()+".xls";
        fileName = URLEncoder.encode(fileName, "UTF-8");  
        response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        response.setContentType("application/octet-stream;charset=UTF-8"); 

        //创建输出流
        OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());

        exportUserData(workBook, userList);

        //删除模板页
        workBook.removeSheetAt(0);

        //将工作簿写入输出流
        workBook.write(outputStream);

        //关闭资源流
        fis.close();
        outputStream.flush();  
        outputStream.close();
    }

    /**
     * 
     * exportUserData:将数据写入工作簿. <br/>
     *
     * @author qiyongkang
     * @param workBook
     * @param userList
     * @since JDK 1.6
     */
    private void exportUserData(HSSFWorkbook workBook, List<User> userList) {
        //克隆模板sheet
        HSSFSheet singleSheet = workBook.cloneSheet(0);

        //设置克隆的sheet名称
        workBook.setSheetName(1, "用户信息表");

        //获取cellStyle
        HSSFCellStyle style = ExcelUtil.getStyle(workBook); 

        //定义起始行,从第二行开始
        int singleRowIndex = 2;

        //开始遍历
        for (User user : userList) {
            try {
                //起始列索引
                int singleColIndex=0;

                //创建行
                HSSFRow singleRow = singleSheet.createRow(singleRowIndex++);

                //创建列
                HSSFCell singleCell = null;

                //用户编号,0
                singleCell=singleRow.createCell(singleColIndex++);
                singleCell.setCellValue(user.getId());
                singleCell.setCellStyle(style);

                //用户姓名,1
                singleCell=singleRow.createCell(singleColIndex++);
                singleCell.setCellValue(user.getUserName());
                singleCell.setCellStyle(style);

                //用户年龄,2
                singleCell=singleRow.createCell(singleColIndex++);
                singleCell.setCellValue(user.getAge());
                singleCell.setCellStyle(style);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    @Override
    public List<User> listAll(User user) {
        return userMapper.listAll(user);
    }

再来看看模板下载以及文件上传的处理类FileHandleCtrl.java:

/**
 * Project Name:qyk_testSpringMVC
 * File Name:FileHandleCtrl.java
 * Package Name:com.qiyongkang.file.ctrl
 * Date:2015年11月1日下午3:01:05
 * Copyright (c) 2015, CANNIKIN(http://http://code.taobao.org/p/cannikin/src/) All Rights Reserved.
 *
*/

package com.qiyongkang.file.ctrl;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.Iterator;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import com.qiyongkang.file.util.FileUtil;
import com.qiyongkang.sys.controller.BaseController;

/**
 * ClassName:FileHandleCtrl <br/>
 * Function: TODO ADD FUNCTION. <br/>
 * Reason:   TODO ADD REASON. <br/>
 * Date:     2015年11月1日 下午3:01:05 <br/>
 * @author   qiyongkang
 * @version  
 * @since    JDK 1.6
 * @see      
 */
@Controller
@RequestMapping
public class FileHandleCtrl extends BaseController {

    /**
     * 
     * downloadtemplate:下载模板. <br/>
     *
     * @author qiyongkang
     * @param request
     * @param response
     * @throws IOException
     * @since JDK 1.6
     */
    @RequestMapping
    public void downloadtemplate(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        request.setCharacterEncoding("UTF-8");
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        //获取template的目录路径
        String templatePath = request.getSession().getServletContext().getRealPath("template");
        System.out.println("templatePath:" + templatePath);

        String downLoadPath  = templatePath + File.separator + URLDecoder.decode(new String(request.getParameter("fileName")), "UTF-8");

        if("/".equals(File.separator)){
            downLoadPath = downLoadPath.replace("\\", "/");
        }
        try {
            File file = new File(downLoadPath);
            long fileLength = file.length();
            response.setContentType("application/x-msdownload;");
            response.setHeader("Content-disposition", "attachment; filename="+new String(file.getName().getBytes("UTF-8"), "ISO8859-1" ));
            response.setHeader("Content-Length", String.valueOf(fileLength));
            bis = new BufferedInputStream(new FileInputStream(downLoadPath));  
            bos = new BufferedOutputStream(response.getOutputStream());
            byte[] buff = new byte[2048];
            int bytesRead;
            while (-1 != (bytesRead = bis.read(buff, 0, buff.length))){
                bos.write(buff, 0, bytesRead);
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(bis != null){
              bis.close();
            }
            if(bos != null){
              bos.close();
            }
        }
    }

    /**
     * 
     * upload:文件上传. <br/>
     *
     * @author qiyongkang
     * @param request
     * @param response
     * @since JDK 1.6
     */
    @RequestMapping
    public void upload(HttpServletRequest request, HttpServletResponse response) {
        String justpath = "";
        String savePath = FileUtil.getResouseValue("uploadpath");
        String filename = null;
        // 当前正在处理的文件分块序号
        int chunk = Integer.valueOf(request.getParameter("chunk"));
        // 分块上传总数
        int chunks = Integer.valueOf(request.getParameter("chunks"));
        //文件名
        String resouseCode = request.getParameter("resouseCode");

        // 判断当前表单是否为"multipart/form-data"
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        try {
            if (isMultipart) {
                MultipartHttpServletRequest multiReq = (MultipartHttpServletRequest) request;
                Iterator<?> i = multiReq.getFileNames();
                while (i.hasNext()) {
                    multiReq.getAttribute("chunk");
                    MultipartFile f = multiReq.getFile((String) i.next());
                    InputStream input = f.getInputStream();

                    // 文件名
                    filename = f.getOriginalFilename();
                    if (!FileUtil.savedFileNameCache.containsKey(filename)) {
                        FileUtil.savedFileNameCache.put(filename,
                                FileUtil.getSavedFileName(filename));
                    }
                    // 保存文件目录绝对路径
                    File dir = new File(savePath+"/"+resouseCode);
                    if (!dir.isDirectory() || !dir.exists()) {
                        dir.mkdirs();
                    }
                    // 保存文件绝对路径
                    justpath = dir + "/"+ FileUtil.savedFileNameCache.get(filename);
                    if (chunk == 0) {
                        File file = new File(justpath);
                        if (file.exists()) {
                            file.delete();
                        }
                        // 上传文件
                        FileUtil.uploadFile(input, justpath);
                    }
                    if (chunk > 0) {
                        // 追加文件
                        FileUtil.uploadFile(input, justpath, true);
                    }
                    if (chunk + 1 == chunks || chunks == 0) {
                        FileUtil.savedFileNameCache.remove(filename);
                        break;
                    }
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        request.getSession().setAttribute("justpath", justpath);
    }
}

第五步、总结

其实,导入导出的核心逻辑就是首先上传文件,然后再对文件进行读写,而与excel操作相关的类就是工作簿类了。
以上的导入导出,笔者讲得很概括,只是贴出了代码,具体的含义还得大家去领悟,由于不同的框架所使用的导入导出有可能不一致,不过大体的思路应该是一致的,有兴趣的小伙伴可以自己去尝试下,挺有意思的!

  • 17
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值