天天看點

java web Excel導入、導出的實作

在做web開發時,我們經常會用到資料表格的導入導出功能,這樣可以幫我們節省人工操作的時間,極大提高辦事效率,下面,直入正題:

筆者所做的導入導出是針對java springMVC架構、工作簿poi以及前端plupload.js插件設計的。

第一步、總體介紹

首先,來看頁面展示,如下圖:

java web Excel導入、導出的實作

導入菜單包含模闆下載下傳和導入資料,如下圖:

java web Excel導入、導出的實作

點選模闆下載下傳,彈出模闆下載下傳對話框,如下圖:

java web Excel導入、導出的實作

點選導入資料,彈出導入對話框,如下圖:

java web Excel導入、導出的實作

接下來,再來看看導出,包含導出目前頁和導出全部,如下圖:

java web Excel導入、導出的實作

點選導出目前頁或是導出全部後,生成excel,如下圖:

java web Excel導入、導出的實作

最後再來看看導入模闆和生成的excel,如下圖:

java web 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<){
    $("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 == ) {
    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 <= ) {
    importWindow = window.top.$("<div id='importWin'/>").appendTo(window.top.document.body);
  }
  importWindow.window({
    title:'導入',
    closed: false,
    closable: true,
    draggable: true,
    resizable: true,
    width: ,
    height: ,
    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 > ){
          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)[];
      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();
        $('#size').val();

        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()) {
                // 導出目前頁
                userList = userService.getPageList(user);
            } else {
                // 導出所有
                userList = userService.listAll(user);
            }

            if (userList != null && userList.size() > ) {
                // 開始導出,擷取模闆路徑
                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();
        int startReadRow = ;
        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()));
                entity.setId(id);

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

                //年齡
                int age = Integer.valueOf(ExcelUtil.getCellValue(row.getCell()));
                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();

        //将工作簿寫入輸出流
        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();

        //設定克隆的sheet名稱
        workBook.setSheetName(, "使用者資訊表");

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

        //定義起始行,從第二行開始
        int singleRowIndex = ;

        //開始周遊
        for (User user : userList) {
            try {
                //起始列索引
                int singleColIndex=;

                //建立行
                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[];
            int bytesRead;
            while (- != (bytesRead = bis.read(buff, , buff.length))){
                bos.write(buff, , 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 == ) {
                        File file = new File(justpath);
                        if (file.exists()) {
                            file.delete();
                        }
                        // 上傳檔案
                        FileUtil.uploadFile(input, justpath);
                    }
                    if (chunk > ) {
                        // 追加檔案
                        FileUtil.uploadFile(input, justpath, true);
                    }
                    if (chunk +  == chunks || chunks == ) {
                        FileUtil.savedFileNameCache.remove(filename);
                        break;
                    }
                }


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

           

第五步、總結

其實,導入導出的核心邏輯就是首先上傳檔案,然後再對檔案進行讀寫,而與excel操作相關的類就是工作簿類了。

以上的導入導出,筆者講得很概括,隻是貼出了代碼,具體的含義還得大家去領悟,由于不同的架構所使用的導入導出有可能不一緻,不過大體的思路應該是一緻的,有興趣的小夥伴可以自己去嘗試下,挺有意思的!

繼續閱讀