EasyPoi文檔教程V1.0(原地址:http://easypoi.mydoc.io/)


1. 前傳

1.1 前言

easypoi功能如同名字easy,主打的功能就是容易,讓一個沒見接觸過poi的人員 就可以方便的寫出Excel導出,Excel模板導出,Excel導入,Word模板導出,通過簡單的注解和模板 語言(熟悉的表達式語法),完成以前復雜的寫法

**

這個服務即將關閉,文檔遷移到 http://www.wupaas.com/ 請大家訪問最新網站

** 不如poi那么自定義,不如jxl那么多標簽,但是我們就是寫的少,寫的少

EasyPoi的主要特點

1.設計精巧,使用簡單
2.接口豐富,擴展簡單
3.默認值多,write less do more
4.spring mvc支持,web導出可以簡單明了

功能

Excel自適應xls和xlsx兩種格式,word只支持docx模式

1.Excel導入

  • 注解導入

  • Map導入

  • 大數據量導入sax模式

  • 導入文件保存

  • 文件校驗

  • 字段校驗

2.Excel導出

  • 注解導出

  • 模板導出

  • html導出

3.Excel轉html

4.word導出

5.pdf導出

1.2 Easypoi介紹

Easypoi 為誰而開發

  • 不太熟悉poi的

  • 不想寫太多重復太多的

  • 只是簡單的導入導出的

  • 喜歡使用模板的

都可以使用easypoi

Easypoi的目標是什么 Easypoi的目標不是替代poi,而是讓一個不懂導入導出的快速使用poi完成Excel和word的各種操作,而不是看很多api才可以完成這樣工作

為什么會寫Easypoi

以前的以前(歲月真TMD的快)我雖然寫了不少代碼但還是很少寫poi,然后跳到一家公司之后就和業務人員聊上了,來這個需要個報表,這個報表樣式是這樣的,這個表頭是這樣的,就這樣我寫了大量的poi代碼,每次都是大量的篇幅,copy to copy,無聊的一逼,然后加入了jeecg,jeecg中有一個小的工具類,雖然我也不知道是誰寫的,然是可以用注解搞定最簡單的導出,突然豁然開朗,我可以完善,讓我從報表的苦海當中脫離出來,這樣我花了一周的時間做了第一個版本支持導入導出放到了jeecg,發現還是不錯的,慢慢的用的人越來越多,我就把這塊獨立出來了,再然后有人提出了模板,然后就加入了模板功能,提出了word的需求,加入了word的功能,后來工作忙了雖然沒再參與jeecg,但還是一直維持這easypoi的更新,根據見識的增長也不斷的重構這代碼,直到現在

獨特的功能

  • 基於注解的導入導出,修改注解就可以修改Excel

  • 支持常用的樣式自定義

  • 基於map可以靈活定義的表頭字段

  • 支持一堆多的導出,導入

  • 支持模板的導出,一些常見的標簽,自定義標簽

  • 支持HTML/Excel轉換,如果模板還不能滿足用戶的變態需求,請用這個功能

  • 支持word的導出,支持圖片,Excel

小白如何開始

  • 下載demo運行看看,基本上常見的用用法都在里面easypoi-test

  • 查看幾個*Util的用法,Easypoi的主要輸出就是這個

  • 看看注解的意思

  • 看看模板的標簽用法

  • 可以出師了

1.3 使用

  • 1.easypoi 父包--作用大家都懂得

  • 2.easypoi-annotation 基礎注解包,作用與實體對象上,拆分后方便maven多工程的依賴管理

  • 3.easypoi-base 導入導出的工具包,可以完成Excel導出,導入,Word的導出,Excel的導出功能

  • 4.easypoi-web 耦合了spring-mvc 基於AbstractView,極大的簡化spring-mvc下的導出功能

  • 5.sax 導入使用xercesImpl這個包(這個包可能造成奇怪的問題哈),word導出使用poi-scratchpad,都作為可選包了

如果不使用spring mvc的便捷福利,直接引入easypoi-base 就可以了,easypoi-annotation

如果使用maven,請使用如下坐標

 <dependency>
           <groupId>cn.afterturn</groupId>
           <artifactId>easypoi-base</artifactId>
           <version>3.2.0</version>
       </dependency>
       <dependency>
           <groupId>cn.afterturn</groupId>
           <artifactId>easypoi-web</artifactId>
           <version>3.2.0</version>
       </dependency>
       <dependency>
           <groupId>cn.afterturn</groupId>
           <artifactId>easypoi-annotation</artifactId>
           <version>3.2.0</version>
       </dependency>

如果沒有maven請直接下jar,在alimaven

1.4 測試項目

測試這個事情真不是個容易的事情 測試項目包括兩塊 Junit 的常見測試和spring 的view測試

1.spring view測試 運行application就可以了,訪問界面,然后看到界面 view 對應的代碼在view下面

2.Junit的測試目錄結構如下

  • tohtml html預覽測試

  • view 導出的view測試

  • cache 自定義緩存測試

  • html html互轉測試

    • test

      • excel

        • read 讀取Excel測試

          • check 導入檢查測試

          • hanlder 導入數據處理

          • img 含圖片導入測試

      • styler 導出樣式自定義測試

      • template 模板導出測試

        • sum 導出含統計測試

      • test 導出測試

        • groupname groupname 屬性測試

        • img 導出圖片測試

  • pdf pdf測試

  • word word導出測試

  • util util 內部測試

目前的測試覆蓋率 輸入圖片說明

1.5 可能存在的小坑

緩存問題 緩存問題好像是很多童鞋都遇到的問題,可能是我設計的邏輯問題,但是一直沒有好的解決, 但是我也給了一個無奈的解決方案,大家可以自己實現接口IFileLoader

public interface IFileLoader {
   /**
    * 可以自定義KEY的作用
    * @param key
    * @return
    */
   public byte[] getFile(String key);

}

來自己獲取自己的文件,用來解決文件獲取不到的問題 設置提供了兩種方案,都是POICacheManager 的靜態方法

    public static void setFileLoder(IFileLoader fileLoder) {
       POICacheManager.fileLoder = fileLoder;
  }

   /**
    * 一次線程有效
    * @param fileLoder
    */
   public static void setFileLoderOnce(IFileLoader fileLoder) {
       if (fileLoder != null) {
           LOCAL_FILELOADER.set(fileLoder);
      }
  }

第一個是全局替換,可以在項目啟動的時候,設置下就可以了,第一個是當前線程有效,希望可以幫助大家解決問題,我再研究下更通用的文件獲取,或者那位朋友提供下自己的通用方案。

2. Excel 注解版

2.1 Excel導入導出

Excel的導入導出是Easypoi的核心功能,前期基本也是圍繞這個打造的,主要分為三種方式的處理,其中模板和Html目前只支持導出,因為支持Map.class其實導入應該是怎樣都支持的

  • 注解方式,注解變種方式

  • 模板方式

  • Html方式

下面分別就這三種方式進行講解

2.2 注解

注解介紹

easypoi起因就是Excel的導入導出,最初的模板是實體和Excel的對應,model--row,filed--col 這樣利用注解我們可以和容易做到excel到導入導出 經過一段時間發展,現在注解有5個類分別是

  • @Excel 作用到filed上面,是對Excel一列的一個描述

  • @ExcelCollection 表示一個集合,主要針對一對多的導出,比如一個老師對應多個科目,科目就可以用集合表示

  • @ExcelEntity 表示一個繼續深入導出的實體,但他沒有太多的實際意義,只是告訴系統這個對象里面同樣有導出的字段

  • @ExcelIgnore 和名字一樣表示這個字段被忽略跳過這個導導出

  • @ExcelTarget 這個是作用於最外層的對象,描述這個對象的id,以便支持一個對象可以針對不同導出做出不同處理

注解中的ID的用法 這個ID算是一個比較獨特的例子,比如

@ExcelTarget("teacherEntity")
public class TeacherEntity implements java.io.Serializable {
   /** name */
   @Excel(name = "主講老師_teacherEntity,代課老師_absent", orderNum = "1", mergeVertical = true,needMerge=true,isImportField = "true_major,true_absent")
   private String name;

這里的@ExcelTarget 表示使用teacherEntity這個對象是可以針對不同字段做不同處理 同樣的ExcelEntity 和ExcelCollection 都支持這種方式 當導出這對象時,name這一列對應的是主講老師,而不是代課老師還有很多字段都支持這種做法

@Excel

這個是必須使用的注解,如果需求簡單只使用這一個注解也是可以的,涵蓋了常用的Excel需求,需要大家熟悉這個功能,主要分為基礎,圖片處理,時間處理,合並處理幾塊,name_id是上面講的id用法,這里就不累言了

屬性 類型 默認值 功能
name String null 列名,支持name_id
needMerge boolean fasle 是否需要縱向合並單元格(用於含有list中,單個的單元格,合並list創建的多個row)
orderNum String "0" 列的排序,支持name_id
replace String[] {} 值得替換 導出是{a_id,b_id} 導入反過來
savePath String "upload" 導入文件保存路徑,如果是圖片可以填寫,默認是upload/className/ IconEntity這個類對應的就是upload/Icon/
type int 1 導出類型 1 是文本 2 是圖片,3 是函數,10 是數字 默認是文本
width double 10 列寬
height double 10 列高,后期打算統一使用@ExcelTarget的height,這個會被廢棄,注意
isStatistics boolean fasle 自動統計數據,在追加一行統計,把所有數據都和輸出 這個處理會吞沒異常,請注意這一點
isHyperlink boolean false 超鏈接,如果是需要實現接口返回對象
isImportField boolean true 校驗字段,看看這個字段是不是導入的Excel中有,如果沒有說明是錯誤的Excel,讀取失敗,支持name_id
exportFormat String "" 導出的時間格式,以這個是否為空來判斷是否需要格式化日期
importFormat String "" 導入的時間格式,以這個是否為空來判斷是否需要格式化日期
format String "" 時間格式,相當於同時設置了exportFormat 和 importFormat
databaseFormat String "yyyyMMddHHmmss" 導出時間設置,如果字段是Date類型則不需要設置 數據庫如果是string 類型,這個需要設置這個數據庫格式,用以轉換時間格式輸出
numFormat String "" 數字格式化,參數是Pattern,使用的對象是DecimalFormat
imageType int 1 導出類型 1 從file讀取 2 是從數據庫中讀取 默認是文件 同樣導入也是一樣的
suffix String "" 文字后綴,如% 90 變成90%
isWrap boolean true 是否換行 即支持\n
mergeRely int[] {} 合並單元格依賴關系,比如第二列合並是基於第一列 則{0}就可以了
mergeVertical boolean fasle 縱向合並內容相同的單元格
fixedIndex int -1 對應excel的列,忽略名字
isColumnHidden boolean false 導出隱藏列
@ExcelTarget

限定一個到處實體的注解,以及一些通用設置,作用於最外面的實體

屬性 類型 默認值 功能
value String null 定義ID
height double 10 設置行高
fontSize short 11 設置文字大小
@ExcelEntity

標記是不是導出excel 標記為實體類,一遍是一個內部屬性類,標記是否繼續穿透,可以自定義內部id

屬性 類型 默認值 功能
id String null 定義ID
@ExcelCollection

一對多的集合注解,用以標記集合是否被數據以及集合的整體排序

屬性 類型 默認值 功能
id String null 定義ID
name String null 定義集合列名,支持nanm_id
orderNum int 0 排序,支持name_id
type Class<?> ArrayList.class 導入時創建對象使用
@ExcelIgnore

忽略這個屬性,多使用需循環引用中,無需多解釋吧^^

2.3 注解導出,導入

2.3.1 對象定義

注解介紹了這么多,大家基本上也了解我們的注解是如何定義Excel的了吧,下面我們來跟着路飛實戰吧 這天老師吧路飛叫到了辦公室,讓給給老師實現一個報表的需求,就是從教育平台把某個班級的人員導出來 需求是,導出我們班的所有學生的姓名,性別,出生日期,進校日期 正巧路飛剛看到Easypo,就打算用Easypoi來實現,實現方法如下:

首先定義一個我們導出的對象,*為了節省篇幅,統一忽略getter,setter*

 public class StudentEntity implements java.io.Serializable {
   /**
    * id
    */
   private String        id;
   /**
    * 學生姓名
    */
   @Excel(name = "學生姓名", height = 20, width = 30, isImportField = "true_st")
   private String        name;
   /**
    * 學生性別
    */
   @Excel(name = "學生性別", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st")
   private int           sex;

   @Excel(name = "出生日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20)
   private Date          birthday;

   @Excel(name = "進校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd")
   private Date registrationDate;

}

這里設置我們的4列分別是學生姓名,學生性別,出生日期,進校日期 其中學生姓名定義了我們的列的行高,學生性別因為我們基本上都是存在數據庫都是數字所以我們轉換下,兩個日期我們都是進行了格式化輸出了,這樣我們就完成了業務對我們Excel的樣式需求,后面只有把這個學生列表輸出就可以了 生成Excel代碼如下

 Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("計算機一班學生","學生"),
           StudentEntity .class, list);

這樣我們就得到的一個java中的Excel,然后把這個輸出就得到我們的Excel了https://static.oschina.net/uploads/space/2017/0622/212811_uh7e_1157922.png

img

2.3.2 集合定義

路飛很快的完成了老師的任務,花了也就是喝杯茶的時間,就交差了,但過了一會就又被老師叫去了,讓他給出一個某個班級選擇選擇某些課的學生以及對應的老師 路飛又很快的想到了Easypoi,其中有一對多的導出,這不正是一對多的體現嗎,然后他繼續定義實體: 一個課程對應一個老師 一個課程對應N個學生 課程的實體

 @ExcelTarget("courseEntity")
public class CourseEntity implements java.io.Serializable {
   /** 主鍵 */
   private String        id;
   /** 課程名稱 */
   @Excel(name = "課程名稱", orderNum = "1", width = 25)
   private String        name;
   /** 老師主鍵 */
   @ExcelEntity(id = "absent")
   private TeacherEntity mathTeacher;

   @ExcelCollection(name = "學生", orderNum = "4")
   private List<StudentEntity> students;
}

教師的實體

@ExcelTarget("teacherEntity")
public class TeacherEntity implements java.io.Serializable {
private String id;
/** name */
@Excel(name = "主講老師_major,代課老師_absent", orderNum = "1", isImportField = "true_major,true_absent")
private String name;

這里在課程這個實體里面就完成了一堆多的導出,達到了我們基礎需求,同時使用了orderNum對我們的列進行了排序,滿足老師的需求,導出代碼如下

 Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("2412312", "測試", "測試"),
CourseEntity.class, list);

這樣我們就完成了老師的需求,效果如圖2.3.2-1 但是課程名和代課老師沒有合並,不太美觀

路飛又果斷給課程名稱和代課老師加了needMerge = true的屬性,就可以完成單元格的合並

   /** 課程名稱 */
@Excel(name = "課程名稱", orderNum = "1", width = 25,needMerge = true)
private String name;

//--------------------------------
/** name */
@Excel(name = "主講老師_major,代課老師_absent", orderNum = "1",needMerge = true, isImportField = "true_major,true_absent")

效果如圖2.3.2-2 到這里,路飛就完美的完成了老師的任務,快樂的去交差了

圖2.3.2-1

img

圖2.3.2-2

img

2.3.3 圖片的導出

在日常運作中不可避免的會遇到圖片的導入導出,這里提供了兩種類型的圖片導出方式

@Excel(name = "公司LOGO", type = 2 ,width = 40 , height = 20,imageType = 1)
private String companyLogo;
  1. 表示type =2 該字段類型為圖片,imageType=1 (默認可以不填),表示從file讀取,字段類型是個字符串類型 可以用相對路徑也可以用絕對路徑,絕對路徑優先依次獲取

    @Excel(name = "公司LOGO", type = 2 ,width = 40 , height = 20,imageType = 1)
    private byte[] companyLogo;

    2.表示type =2 該字段類型為圖片,imageType=2 ,表示從數據庫或者已經讀取完畢,字段類型是個字節數組 直接使用 同時,image 類型的cell最好設置好寬和高,

    會百分百縮放到cell那么大,不是原尺寸,這里注意下

效果如下

List<CompanyHasImgModel> list;

@Before
public void initData() {
list = new ArrayList<CompanyHasImgModel>();
list.add(new CompanyHasImgModel("百度", "imgs/company/baidu.png", "北京市海淀區西北旺東路10號院百度科技園1號樓"));
list.add(new CompanyHasImgModel("阿里巴巴", "imgs/company/ali.png", "北京市海淀區西北旺東路10號院百度科技園1號樓"));
list.add(new CompanyHasImgModel("Lemur", "imgs/company/lemur.png", "亞馬遜熱帶雨林"));
list.add(new CompanyHasImgModel("一眾", "imgs/company/one.png", "山東濟寧俺家"));


}

@Test
public void exportCompanyImg() throws Exception {

File savefile = new File("D:/excel/");
if (!savefile.exists()) {
savefile.mkdirs();
}
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), CompanyHasImgModel.class, list);
FileOutputStream fos = new FileOutputStream("D:/excel/ExcelExportHasImgTest.exportCompanyImg.xls");
workbook.write(fos);
fos.close();
}

運行效果

2.3.3 -1

img

2.3.4 Excel導入介紹

有導出就有導入,基於注解的導入導出,配置配置上是一樣的,只是方式反過來而已,比如類型的替換 導出的時候是1替換成男,2替換成女,導入的時候則反過來,男變成1 ,女變成2,時間也是類似 導出的時候date被格式化成 2017-8-25 ,導入的時候2017-8-25被格式成date類型 下面說下導入的基本代碼,注解啥的都是上面講過了,這里就不累贅了

  @Test
public void test2() {
ImportParams params = new ImportParams();
params.setTitleRows(1);
params.setHeadRows(1);
long start = new Date().getTime();
List<MsgClient> list = ExcelImportUtil.importExcel(
new File(PoiPublicUtil.getWebRootPath("import/ExcelExportMsgClient.xlsx")),
MsgClient.class, params);
System.out.println(new Date().getTime() - start);
System.out.println(list.size());
System.out.println(ReflectionToStringBuilder.toString(list.get(0)));
}

基本是寫法也很簡單,ImportParams 參數介紹下

屬性 類型 默認值 功能
titleRows int 0 表格標題行數,默認0
headRows int 1 表頭行數,默認1
startRows int 0 字段真正值和列標題之間的距離 默認0
*keyIndex* int 0 *主鍵設置,如何這個cell沒有值,就跳過 或者認為這個是list的下面的值* 這一列必須有值,不然認為這列為無效數據
startSheetIndex int 0 開始讀取的sheet位置,默認為0
sheetNum int 1 上傳表格需要讀取的sheet 數量,默認為1
needSave boolean false 是否需要保存上傳的Excel
needVerfiy boolean false 是否需要校驗上傳的Excel
saveUrl String "upload/excelUpload" 保存上傳的Excel目錄,默認是 如 TestEntity這個類保存路徑就是 upload/excelUpload/Test/yyyyMMddHHmss** 保存名稱上傳時間*五位隨機數
verifyHanlder IExcelVerifyHandler null 校驗處理接口,自定義校驗
lastOfInvalidRow int 0 最后的無效行數,不讀的行數
readRows int 0 手動控制讀取的行數
importFields String[] null 導入時校驗數據模板,是不是正確的Excel
keyMark String ":" Key-Value 讀取標記,以這個為Key,后面一個Cell 為Value,多個改為ArrayList
readSingleCell boolean false 按照Key-Value 規則讀取全局掃描Excel,但是跳過List讀取范圍提升性能 僅僅支持titleRows + headRows + startRows 以及 lastOfInvalidRow
dataHanlder IExcelDataHandler null 數據處理接口,以此為主,replace,format都在這后面
2.3.5 Excel導入小功能
  1. 讀取指定的sheet 比如要讀取上傳得第二個sheet 那么需要把startSheetIndex = 1 就可以了

  2. 讀取幾個sheet 比如讀取前2個sheet,那么 sheetNum=2 就可以了

  3. 讀取第二個到第五個sheet 設置 startSheetIndex = 1 然后sheetNum = 4

  4. 讀取全部的sheet sheetNum 設置大點就可以了

  5. 保存Excel 設置 needVerfiy = true,默認保存的路徑為upload/excelUpload/Test/yyyyMMddHHmss** 保存名稱上傳時間*五位隨機數 如果自定義路徑 修改下saveUrl 就可以了,同時saveUrl也是圖片上傳時候的保存的路徑

  6. 判斷一個Excel是不是合法的Excel importFields 設置下值,就是表示表頭必須至少包含的字段,如果缺一個就是不合法的excel,不導入

2.3.6 圖片的導入

有圖片的導出就有圖片的導入,導入的配置和導出是一樣的,但是需要設置保存路徑 1.設置保存路徑saveUrl 默認為"upload/excelUpload" 可以手動修改 ImportParams 修改下就可以了

 @Test
public void test() {
try {
ImportParams params = new ImportParams();
params.setNeedSave(true);
List<CompanyHasImgModel> result = ExcelImportUtil.importExcel(
new File(PoiPublicUtil.getWebRootPath("import/imgexcel.xls")),
CompanyHasImgModel.class, params);
for (int i = 0; i < result.size(); i++) {
System.out.println(ReflectionToStringBuilder.toString(result.get(i)));
}
Assert.assertTrue(result.size() == 4);
} catch (Exception e) {
e.printStackTrace();
}
}
}

導入日志

16:35:43.081 [main] DEBUG c.a.e.e.imports.ExcelImportServer - Excel import start ,class is class cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel
16:35:43.323 [main] DEBUG c.a.e.e.imports.ExcelImportServer - start to read excel by is ,startTime is 1503650143323
16:35:43.344 [main] DEBUG c.a.e.e.imports.ExcelImportServer - end to read excel by is ,endTime is 1503650143344
16:35:43.429 [main] DEBUG c.a.e.e.imports.ExcelImportServer - end to read excel list by pos ,endTime is 1503650143429
cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@1b083826[companyName=百度,companyLogo=upload/CompanyHasImgModel/pic88273295062.PNG,companyAddr=北京市海淀區西北旺東路10號院百度科技園1號樓]
cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@105fece7[companyName=阿里巴巴,companyLogo=upload/CompanyHasImgModel/pic22507938183.PNG,companyAddr=北京市海淀區西北旺東路10號院百度科技園1號樓]
cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@3ec300f1[companyName=Lemur,companyLogo=upload/CompanyHasImgModel/pic86390457892.PNG,companyAddr=亞馬遜熱帶雨林]
cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@482cd91f[companyName=一眾,companyLogo=upload/CompanyHasImgModel/pic69566571093.PNG,companyAddr=山東濟寧俺家]

2.3.5-1

img

2.3.7 Excel多Sheet導出

目前單Sheet和單Class的方式比較多,對於多Sheet的方式還是一片空白,這里做一下說明:

導出基本采用ExportParams 這個對象,進行參數配置; 我們需要進行多Sheet導出,那么就需要定義一個基礎配置對象

public class ExportView {

public ExportView(){

}


private ExportParams exportParams;
private List<?> dataList;
private Class<?> cls;

public ExportParams getExportParams() {
return exportParams;
}
public void setExportParams(ExportParams exportParams) {
this.exportParams = exportParams;
}

public Class<?> getCls() {
return cls;
}
public void setCls(Class<?> cls) {
this.cls = cls;
}
public List<?> getDataList() {
return dataList;
}
public void setDataList(List<?> dataList) {
this.dataList = dataList;
}


public ExportView(Builder builder) {
this.exportParams = builder.exportParams;
this.dataList = builder.dataList;
this.cls = builder.cls;
}

public static class Builder {
private ExportParams exportParams=null;
private List<?> dataList=null;
private Class<?> cls=null;

public Builder() {

}
public Builder exportParams(ExportParams exportParams) {
this.exportParams = exportParams;
return this;
}

public Builder dataList(List<?> dataList) {
this.dataList = dataList;
return this;
}
public Builder cls(Class<?> cls) {
this.cls = cls;
return this;
}

public ExportView create() {
return new ExportView(this);
}
}


}

對象主要有三個屬性: // 該注解配置的導出屬性

  1. ExportParams exportParams // 對應注解 class 實例對象的數據集合

  2. List<?> dataList // 對應注解的 class

  3. Class<?> cls

這里沒有用泛型,因為多Sheet導出時,會引用到不同的注解對象;

定義基礎配置的集合

public class ExportMoreView {
private List<ExportView> moreViewList=Lists.newArrayList();

public List<ExportView> getMoreViewList() {
return moreViewList;
}

public void setMoreViewList(List<ExportView> moreViewList) {
this.moreViewList = moreViewList;
}
}

最后在實現調用的方法中,對整個集合進行配置和解析

List<Map<String, Object>> exportParamList=Lists.newArrayList();
//該行主要用於獲取業務數據,請根據具體的情況進行修改和調整
ExportMoreView moreView=this.getBaseTransferService().mergeExportView(templateTypeCode);
//迭代導出對象,將對應的配置信息寫入到實際的配置中
for(ExportView view:moreView.getMoreViewList()){
Map<String, Object> valueMap=Maps.newHashMap();
valueMap.put(NormalExcelConstants.PARAMS,view.getExportParams());
valueMap.put(NormalExcelConstants.DATA_LIST,view.getDataList());
valueMap.put(NormalExcelConstants.CLASS,view.getCls());
exportParamList.add(valueMap);
}
//實現導出配置
modelMap.put(NormalExcelConstants.FILE_NAME,new DateTime().toString("yyyyMMddHHmmss"));
//將轉換完成的配置接入到導出中
modelMap.put(NormalExcelConstants.MAP_LIST,exportParamList);
return NormalExcelConstants.JEECG_EXCEL_VIEW;

如果不是采用的MVC的方式,請將轉換的配置采用以下的方式實現:

參見ExcelExportUtil

img

2.4 注解變種-更自由的導出

這天老師又把路飛喊道的辦公室,要求路飛導出班級學生的整體信息

    @Excel(name = "學生姓名", height = 20, width = 30, isImportField = "true_st")
private String name;
@Excel(name = "學生性別", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st")
private int sex;
@Excel(name = "出生日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20)
private Date birthday;
@Excel(name = "進校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd")
private Date registrationDate;

路飛飛快的用到上面的學到的知識搞定了,這這時有一個老師把路飛叫去,說想要導出一個不要出生日期的Excel,感覺用戶需求很無奈,路飛又造兩個一個bean,把這個注解去掉了,來導出

    @Excel(name = "學生姓名", height = 20, width = 30, isImportField = "true_st")
private String name;
@Excel(name = "學生性別", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st")
private int sex;
@Excel(name = "進校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd")
private Date registrationDate;

雖然解決了老師的需求,但這個並不是一個完美的解決方案,下面介紹一個更自由的解決方案

注解的導出,規定我們必須把model寫好,並且注解寫好,每次導出的Excel都是固定的,無法動態控制導出的列,雖然可以通過id來處理一個案例,但是自由度遠遠不夠,這里介紹個變種支持,基本支持注解所有的功能

基於List<ExcelExportEntity> 的導出,ExcelExportEntity是注解經過處理翻譯成的實體類,兩者幾乎是一對的,所以如果我們要動態自定義導出列,我們只要動態拼裝ExcelExportEntity就可以了 下面我們看下這個類

/**
* 如果是MAP導出,這個是map的key
*/
private Object key;

private double width = 10;

private double height = 10;

/**
* 圖片的類型,1是文件,2是數據庫
*/
private int exportImageType = 0;

/**
* 排序順序
*/
private int orderNum = 0;

/**
* 是否支持換行
*/
private boolean isWrap;

/**
* 是否需要合並
*/
private boolean needMerge;
/**
* 單元格縱向合並
*/
private boolean mergeVertical;
/**
* 合並依賴
*/
private int[] mergeRely;
/**
* 后綴
*/
private String suffix;
/**
* 統計
*/
private boolean isStatistics;

private String numFormat;

private List<ExcelExportEntity> list;

基本上是和注解對應的, List<ExcelExportEntity> list 這個是對應的一對多的導出,相當於集合,其他基本上都是和注解保持一致 下面給出正常的demo

public void test() {
try {
List<ExcelExportEntity> entity = new ArrayList<ExcelExportEntity>();
//構造對象等同於@Excel
ExcelExportEntity excelentity = new ExcelExportEntity("姓名", "name");
excelentity.setNeedMerge(true);
entity.add(excelentity);
entity.add(new ExcelExportEntity("性別", "sex"));
excelentity = new ExcelExportEntity(null, "students");
List<ExcelExportEntity> temp = new ArrayList<ExcelExportEntity>();
temp.add(new ExcelExportEntity("姓名", "name"));
temp.add(new ExcelExportEntity("性別", "sex"));
//構造List等同於@ExcelCollection
excelentity.setList(temp);
entity.add(excelentity);
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
//把我們構造好的bean對象放到params就可以了
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("測試", "測試"), entity,
list);
FileOutputStream fos = new FileOutputStream("D:/excel/ExcelExportForMap.tt.xls");
workbook.write(fos);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

路飛想到了這個方案,並且用上面做了測試可以完美解決所以他把之前的代碼改為了(代碼有刪減,基本上都是和注解對應的)

List<ExcelExportEntity> beanList = new ArrayList<ExcelExportEntity>();
beanList .add(new ExcelExportEntity(new ExcelExportEntity("學生姓名", "name"));
beanList .add(new ExcelExportEntity("學生性別", "sex"));
beanList .add(new ExcelExportEntity("進校日期", "registrationDate"));
if(needBirthday()){
beanList .add(new ExcelExportEntity("出生日期", "birthday"));
}
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("測試", "測試"), beanList ,
list);

用同一套代買完美了支持了老師的需求,心滿意足的回宿舍了^^

2.5 Map導入,自由發揮

這天,老師把路飛叫到辦公室,總是被叫,能者的悲哀啊,讓他臨時導入一批數據,到數據庫,但是中間需要處理一些字段邏輯沒辦法直接導入到數據庫, 這時路飛首先想到構造一個bean然后標記注解,導入處理對象,但是想想一次的對象太過於浪費,不如用map試試,獲取map處理map也是一樣的 導入的邏輯就變成了

        ImportParams params = new ImportParams();
params.setDataHanlder(new MapImportHanlder());
long start = new Date().getTime();
List<Map<String, Object>> list = ExcelImportUtil.importExcel(
new File(PoiPublicUtil.getWebRootPath("import/check.xls")), Map.class, params);

導入后,處理每個map,然后入庫完美的解決了老師的需求,簡單更快捷,和bean導入基礎沒有區別,省去了bean的構造時間

PS:這個作者也只是在臨時方案中或者一次性活當中使用,一般還是推薦注解這種方式,擁有更高的代碼閱讀性 !!!測試了時間的,最好導入使用文本格式,可以獲取時間格式可能無法獲取

2.6 Excel的樣式自定義

"路飛,來辦公室一趟",就這樣路飛又被叫到了辦公室,這次老師的需求是,想要一個漂亮點的Excel,希望路飛可以點綴下Excel,思來想去還是需要用poi的style來解決,但是如果每個都寫style是不是太麻煩,而且Excel的styler數量是有限制的,這里就需要盡量復用已經創造的style,看看之前的Excel表格,大體上可以分為[標題,表頭,表體],那可以說的就是創建一個接口每次調用這三個接口就可以了不說干就干

public interface IExcelExportStyler {
/**
* 列表頭樣式
* @param headerColor
* @return
*/
public CellStyle getHeaderStyle(short headerColor);
/**
* 標題樣式
* @param color
* @return
*/
public CellStyle getTitleStyle(short color);
/**
* 獲取樣式方法
* @param Parity
* @param entity
* @return
*/
public CellStyle getStyles(boolean Parity, ExcelExportEntity entity);
}

實現類盡量復用已經創建的Styler,切記 這樣路飛先造了一個帶邊框的styler ,ExcelExportStylerBorderImpl 效果如下 2.6-1 然后路飛又手癢寫了個帶換行顏色的 ExcelExportStylerColorImpl 效果如下 2.6-2

客官看到這里應該就大體理解了我們的實現方法了吧, 最后路飛實現了一個復雜的按照老師要求的樣式交差了


styler接口用法 上面兩個表頭和標題樣式不用解釋 后面這個是傳入當前列的以及奇偶行,用戶可以根據需求實現業務,包括去掉Excel的小箭頭(也就是設置數字為數字格式的Cell),完成居中,字體等等各式各樣的需求 但是這里無法實現特別沒的Excel,如果有這種需求可以使用模板來實現,在Excel點點就可以完美實現

2.7 如何自定義數據處理

導入導出總有一些自定義格式轉換,EasyPoi雖然定義了很多服務,但是也無法滿足所有客戶的需求,這個時候就需要咱們自己定義數據處理 EasyPoi提供了

/**
* Excel 導入導出 數據處理接口
*
* @author JueYue
* 2014年6月19日 下午11:59:45
*/
public interface IExcelDataHandler<T> {

/**
* 導出處理方法
*
* @param obj
* 當前對象
* @param name
* 當前字段名稱
* @param value
* 當前值
* @return
*/
public Object exportHandler(T obj, String name, Object value);

/**
* 獲取需要處理的字段,導入和導出統一處理了, 減少書寫的字段
*
* @return
*/
public String[] getNeedHandlerFields();

/**
* 導入處理方法 當前對象,當前字段名稱,當前值
*
* @param obj
* 當前對象
* @param name
* 當前字段名稱
* @param value
* 當前值
* @return
*/
public Object importHandler(T obj, String name, Object value);

/**
* 設置需要處理的屬性列表
* @param fields
*/
public void setNeedHandlerFields(String[] fields);

/**
* 設置Map導入,自定義 put
* @param map
* @param originKey
* @param value
*/
public void setMapValue(Map<String, Object> map, String originKey, Object value);

/**
* 獲取這個字段的 Hyperlink ,07版本需要,03版本不需要
* @param creationHelper
* @param obj
* @param name
* @param value
* @return
*/
public Hyperlink getHyperlink(CreationHelper creationHelper, T obj, String name, Object value);

}

簡單的使用方法如下

 CourseHandler hanlder = new CourseHandler();
hanlder.setNeedHandlerFields(new String[] { "課程名稱" });
exportParams.setDataHandler(hanlder);

我們自己實現以下這個類,也可以繼承ExcelDataHandlerDefaultImpl ,避免實現多余的接口 setNeedHandlerFields 這個是需要我們自己處理的字段,需要手動設置

讓我們看一個demo

public class MapImportHandler extends ExcelDataHandlerDefaultImpl<Map<String, Object>> {

@Override
public void setMapValue(Map<String, Object> map, String originKey, Object value) {
if (value instanceof Double) {
map.put(getRealKey(originKey), PoiPublicUtil.doubleToString((Double) value));
} else {
map.put(getRealKey(originKey), value != null ? value.toString() : null);
}
}

private String getRealKey(String originKey) {
if (originKey.equals("交易賬戶")) {
return "accountNo";
}
if (originKey.equals("姓名")) {
return "name";
}
if (originKey.equals("客戶類型")) {
return "type";
}
return originKey;
}
}

這里我們在map導入的時候把map的key給轉了,從中文轉為習慣的英文

2.8 Excel導入校驗

校驗,是一個不可或缺的功能,現在java校驗主要是JSR 303 規范,實現方式主流的有兩種

  • Hibernate Validator

  • Apache Commons Validator

這個EasyPoi沒有限制,只要你防止一個實現丟到maven中就可以了,但是Hibernate Validator用的貌似多一些 之前的版本EasyPoi有定義自己的實現,但是后來拋棄了,沒有必要造這種輪子,這個了功能已經夠豐富了

使用

*對象*

EasyPoi的校驗使用也很簡單,對象上加上通用的校驗規則或者這定義的這個看你用的哪個實現 然后params.setNeedVerfiy(true);配置下需要校驗就可以了 看下具體的代碼

 /**
* Email校驗
*/
@Excel(name = "Email", width = 25)
private String email;
/**
* 最大
*/
@Excel(name = "Max")
@Max(value = 15,message = "max 最大值不能超過15" ,groups = {ViliGroupOne.class})
private int max;
/**
* 最小
*/
@Excel(name = "Min")
@Min(value = 3, groups = {ViliGroupTwo.class})
private int min;
/**
* 非空校驗
*/
@Excel(name = "NotNull")
@NotNull
private String notNull;
/**
* 正則校驗
*/
@Excel(name = "Regex")
@Pattern(regexp = "[\u4E00-\u9FA5]*", message = "不是中文")
private String regex;

這里的校驗規則都是JSR 303 的,使用方式也是的,這里就不做解釋了 然后使用方式是

@Test
public void basetest() {
try {
ImportParams params = new ImportParams();
params.setNeedVerfiy(true);
params.setVerfiyGroup(new Class[]{ViliGroupOne.class});
ExcelImportResult<ExcelVerifyEntity> result = ExcelImportUtil.importExcelMore(
new File(PoiPublicUtil.getWebRootPath("import/verfiy.xlsx")),
ExcelVerifyEntity.class, params);
FileOutputStream fos = new FileOutputStream("D:/excel/ExcelVerifyTest.basetest.xlsx");
result.getWorkbook().write(fos);
fos.close();
for (int i = 0; i < result.getList().size(); i++) {
System.out.println(ReflectionToStringBuilder.toString(result.getList().get(i)));
}
Assert.assertTrue(result.getList().size() == 1);
Assert.assertTrue(result.isVerfiyFail());
} catch (Exception e) {
LOGGER.error(e.getMessage(),e);
}
}

*ExcelImportResult*

我們會返回一個ExcelImportResult 對象,比我們平時返回的list多了一些元素

 /**
* 結果集
*/
private List<T> list;

/**
* 是否存在校驗失敗
*/
private boolean verfiyFail;

/**
* 數據源
*/
private Workbook workbook;

一個是集合,是一個是是否有校驗失敗的數據,一個原本的文檔,但是在文檔后面追加了錯誤信息

*注意,這里的list,有兩種返回*

  • 一種是只返回正確的數據

  • 一種是返回全部的數據,但是要求這個對象必須實現IExcelModel接口,如下

*IExcelModel*

public class ExcelVerifyEntityOfMode extends ExcelVerifyEntity implements IExcelModel {

private String errorMsg;

@Override
public String getErrorMsg() {
return errorMsg;
}

@Override
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}

}

*IExcelDataModel 獲取錯誤數據的行號

public interface IExcelDataModel {

/**
* 獲取行號
* @return
*/
public int getRowNum();

/**
* 設置行號
* @param rowNum
*/
public void setRowNum(int rowNum);

}

需要對象實現這個接口

每行的錯誤數據也會填到這個錯誤信息中,方便用戶后面自定義處理 看下代碼

   @Test
public void baseModetest() {
try {
ImportParams params = new ImportParams();
params.setNeedVerfiy(true);
ExcelImportResult<ExcelVerifyEntityOfMode> result = ExcelImportUtil.importExcelMore(
new FileInputStream(new File(PoiPublicUtil.getWebRootPath("import/verfiy.xlsx"))),
ExcelVerifyEntityOfMode.class, params);
FileOutputStream fos = new FileOutputStream("D:/excel/baseModetest.xlsx");
result.getWorkbook().write(fos);
fos.close();
for (int i = 0; i < result.getList().size(); i++) {
System.out.println(ReflectionToStringBuilder.toString(result.getList().get(i)));
}
Assert.assertTrue(result.getList().size() == 4);
} catch (Exception e) {
LOGGER.error(e.getMessage(),e);
}
}

*IExcelVerifyHandler*

加入上面的不滿足你,你可以用接口實現自己的校驗規則,比如唯一性校驗,等等,需要返回錯誤信息和成功與否

public interface IExcelVerifyHandler<T> {

/**
* 導入校驗方法
*
* @param obj
* 當前對象
* @return
*/
public ExcelVerifyHanlderResult verifyHandler(T obj);

}

調用順序是先通用的,再接口,到這里校驗的就完整了,下面給大家看下錯誤的excel返回 yy

2.9 Excel 大批量讀取

2.10 Excel大數據導出

大數據導出是當我們的導出數量在幾萬,到上百萬的數據時,一次從數據庫查詢這么多數據加載到內存然后寫入會對我們的內存和CPU都產生壓力,這個時候需要我們像分頁一樣處理導出分段寫入Excel緩解Excel的壓力 EasyPoi提供的是兩個方法 *強制使用 xssf版本的Excel*

  /**
* @param entity
* 表格標題屬性
* @param pojoClass
* Excel對象Class
* @param dataSet
* Excel對象數據List
*/
public static Workbook exportBigExcel(ExportParams entity, Class<?> pojoClass,
Collection<?> dataSet) {
ExcelBatchExportServer batachServer = ExcelBatchExportServer
.getExcelBatchExportServer(entity, pojoClass);
return batachServer.appendData(dataSet);
}

public static void closeExportBigExcel() {
ExcelBatchExportServer batachServer = ExcelBatchExportServer.getExcelBatchExportServer(null,
null);
batachServer.closeExportBigExcel();
}

添加數據和關閉服務,關閉服務不是必須的,可以調也可以不掉 我們只需要for循環寫入Excel就可以了

@Test
public void bigDataExport() throws Exception {

List<MsgClient> list = new ArrayList<MsgClient>();
Workbook workbook = null;
Date start = new Date();
ExportParams params = new ExportParams("大數據測試", "測試");
for (int i = 0; i < 1000000; i++) { //一百萬數據量
MsgClient client = new MsgClient();
client.setBirthday(new Date());
client.setClientName("小明" + i);
client.setClientPhone("18797" + i);
client.setCreateBy("JueYue");
client.setId("1" + i);
client.setRemark("測試" + i);
MsgClientGroup group = new MsgClientGroup();
group.setGroupName("測試" + i);
client.setGroup(group);
list.add(client);
if(list.size() == 10000){
workbook = ExcelExportUtil.exportBigExcel(params, MsgClient.class, list);
list.clear();
}
}
ExcelExportUtil.closeExportBigExcel();
System.out.println(new Date().getTime() - start.getTime());
File savefile = new File("D:/excel/");
if (!savefile.exists()) {
savefile.mkdirs();
}
FileOutputStream fos = new FileOutputStream("D:/excel/ExcelExportBigData.bigDataExport.xlsx");
workbook.write(fos);
fos.close();
}

生成的Excel數據 測試結果

Cpu和內存 CPU和內存

多次測試用時統計,速度還是可以接受的,^^

數據量 用時 文件大小 列數
100W 16.4s 24.3MB 5
100W 15.9s 24.3MB 5
200W 29.5s 48.5MB 5
100W 30.8s 37.8MB 10
200W 58.7s 76.1MB 10

2.11 導入獲取Key-Value

from 3.0.1 工作中是否會遇到導入讀取一些特定的字段比如 導入圖片 Excel 中的委托方,代理方,日期,單號,或者尾部的身份證號,電話等等,需要我們統一入庫,這些字段沒有具體位置,只能特定計算 這里給出了一個全新的解決辦法 key-value 導入方法 key 是要導入的字段名稱比如 委托方: 就認為是一個要導入的字段,后面的一個cell就是起對應的值 比如委托方: 一眾科技有限公司 這樣導入進去就是 key委托方,value 一眾科技有限公司 示例代碼

@Test
public void test() {
try {
ImportParams params = new ImportParams();
params.setKeyMark(":");
params.setReadSingleCell(true);
params.setTitleRows(7);
params.setLastOfInvalidRow(9);
ExcelImportResult<Map> result = ExcelImportUtil.importExcelMore(
new File(PoiPublicUtil.getWebRootPath("import/業務委托單.xlsx")),
Map.class, params);
for (int i = 0; i < result.getList().size(); i++) {
System.out.println(result.getList().get(i));
}
Assert.assertTrue(result.getList().size() == 10);
System.out.println(result.getMap());
} catch (Exception e) {
LOGGER.error(e.getMessage(),e);
}
}

需要設置兩個或者一個值 params.setKeyMark(":"); 判斷一個cell是key的規則,可以自定義,默認就是 ":" params.setReadSingleCell(true); 是否需要讀取這種單獨的sql 讀取完畢后,通過result.getMap() 就可以拿到自己想要的值了比如上面的Excel讀取到的map就是

{境內詳細收貨地址、聯系人、電話:=1.3112345678E10, 委托方:=一眾科技有限公司, 代理方:=上海一眾金融信息服務有限公司, 委托單號:=XH-HZHY-20170504, 日期:=2017.5.4, 供應商交貨方式:=, 合計:=, 境內交貨方式:=, 指定收貨人身份證號:=3.7082719880102099E17}

這樣就比較方便的處理較為復雜的Excel導入了

2.12 groupname和ExcelEntity的name屬性

之前一直沒想好,雙號表頭如何處理數據,直到前幾天突然想到了groupname這個屬性,下面先介紹下這兩個屬性解決的問題,也是之前很多朋友問到的問題 aaa大 這種雙行的表頭,之前只有在集合的模式情況下才會支持,但是很多情況都不是集合模式,也只是一列數據,

  • 簡單的groupname

比如這里的時間算是兩個時間的聚合,單也是對象當中的元素而已,我們要導出這樣的數據現在只要設置下groupname就可以了

@Excel(name = "電話號碼", groupName = "聯系方式", orderNum = "1")
private String clientPhone = null;
// 客戶姓名
@Excel(name = "姓名")
private String clientName = null;
// 備注
@Excel(name = "備注")
private String remark = null;
// 生日
@Excel(name = "出生日期", format = "yyyy-MM-dd", width = 20, groupName = "時間", orderNum = "2")
private Date birthday = null;
// 創建人
@Excel(name = "創建時間", groupName = "時間", orderNum = "3")
private String createBy = null;

這樣就會把兩個groupname合並到一起展示,使用也比較簡單

  • ExcelEntity 一個對象在一起

假如我們需要一個對象屬性統一在一起,name我們需要設置下這個對象的name屬性,並且show=true 這兩個是 且的關系 比如

 @Excel(name = "電話號碼", groupName = "聯系方式", orderNum = "1")
private String clientPhone = null;
@Excel(name = "姓名")
private String clientName = null;
@ExcelEntity(name = "學生", show = true)
private GnStudentEntity studentEntity;

學生對象的內部就是普通的注解

@Excel(name = "學生姓名", height = 20, width = 30, orderNum = "2")
private String name;

@Excel(name = "學生性別", replace = {"男_1", "女_0"}, suffix = "生", orderNum = "3")
private int sex;

@Excel(name = "出生日期", format = "yyyy-MM-dd", width = 20, orderNum = "4")
private Date birthday;

@Excel(name = "進校日期", format = "yyyy-MM-dd", orderNum = "5")
private Date registrationDate;

出來的效果如下 輸入圖片說明

使用起來還是很簡單的,導入的話同樣設置就可以獲取到了

  • 排序問題

導出時,表頭雙行顯示,聚合,排序以最小的值參與總體排序再內部排序 導出排序跟定義了annotation的字段的順序有關 可以使用a_id,b_id來確實是否使用 優先弱與 @ExcelEntity 的name和show屬性

簡單說就是先排外部順序,再排內部順序

3. Excel 模板版

3.1 模板 指令介紹

模板是處理復雜Excel的簡單方法,復雜的Excel樣式,可以用Excel直接編輯,完美的避開了代碼編寫樣式的雷區,同時指令的支持,也提了模板的有效性 下面列舉下EasyPoi支持的指令以及作用,最主要的就是各種fe的用法

  • 空格分割

  • 三目運算 {{test ? obj:obj2}}

  • n: 表示 這個cell是數值類型 {{n:}}

  • le: 代表長度{{le:()}} 在if/else 運用{{le:() > 8 ? obj1 : obj2}}

  • fd: 格式化時間 {{fd:(obj;yyyy-MM-dd)}}

  • fn: 格式化數字 {{fn:(obj;###.00)}}

  • fe: 遍歷數據,創建row

  • !fe: 遍歷數據不創建row

  • $fe: 下移插入,把當前行,下面的行全部下移.size()行,然后插入

  • #fe: 橫向遍歷

  • v_fe: 橫向遍歷值

  • !if: 刪除當前列 {{!if:(test)}}

  • 單引號表示常量值 '' 比如'1' 那么輸出的就是 1

  • &NULL& 空格

  • ]] 換行符 多行遍歷導出

  • sum: 統計數據

整體風格和el表達式類似,大家應該也比較熟悉 采用的寫法是{{}}代表表達式,然后根據表達式里面的數據取值

關於樣式問題 easypoi不會改變excel原有的樣式,如果是遍歷,easypoi會根據模板的那一行樣式進行復制

測試項目

在cn.afterturn.easypoi.test.excel.template 這個目錄下面 https://gitee.com/lemur/easypoi-test/tree/master/src/test/java/cn/afterturn/easypoi/test/excel/template

3.2 基本導出

看一個常見的到處模板--專項支出用款申請書 模板s 這里面有正常的標簽以及$fe遍歷,$fe遍歷應該是使用最廣的遍歷,用來解決遍歷后下面還有數據的處理方式 我們要生成的是這個需要一些list集合和一些單純的數據

fe的寫法 fe標志 冒號 list數據 單個元素數據(默認t,可以不寫) 第一個元素 {{$fe: maplist t t.id }}

看下數據代碼,主要是構造數據TemplateExportParams是主要的參數數據

@Test
public void fe_map() throws Exception {
TemplateExportParams params = new TemplateExportParams(
"WEB-INF/doc/專項支出用款申請書_map.xls");
Map<String, Object> map = new HashMap<String, Object>();
map.put("date", "2014-12-25");
map.put("money", 2000000.00);
map.put("upperMoney", "貳佰萬");
map.put("company", "執筆潛行科技有限公司");
map.put("bureau", "財政局");
map.put("person", "JueYue");
map.put("phone", "1879740****");
List<Map<String, String>> listMap = new ArrayList<Map<String, String>>();
for (int i = 0; i < 4; i++) {
Map<String, String> lm = new HashMap<String, String>();
lm.put("id", i + 1 + "");
lm.put("zijin", i * 10000 + "");
lm.put("bianma", "A001");
lm.put("mingcheng", "設計");
lm.put("xiangmumingcheng", "EasyPoi " + i + "期");
lm.put("quancheng", "開源項目");
lm.put("sqje", i * 10000 + "");
lm.put("hdje", i * 10000 + "");

listMap.add(lm);
}
map.put("maplist", listMap);

Workbook workbook = ExcelExportUtil.exportExcel(params, map);
File savefile = new File("D:/excel/");
if (!savefile.exists()) {
savefile.mkdirs();
}
FileOutputStream fos = new FileOutputStream("D:/excel/專項支出用款申請書_map.xls");
workbook.write(fos);
fos.close();
}

看下輸出的效果 模板導出效果

3.3 模板當中使用注解

3.4 圖片導出

模板圖片導出,沒有注解導出圖片那么容易,但也不算復雜,構建一個ImageEntity 設置下高寬,地址或者byte[]及可以了

ImageEntity image = new ImageEntity();
image.setHeight(200);
image.setWidth(500);
image.setUrl("imgs/company/baidu.png");

具體的導出代碼

 @Test
public void one() throws Exception {
TemplateExportParams params = new TemplateExportParams(
"doc/exportTemp_image.xls", true);
Map<String, Object> map = new HashMap<String, Object>();
// sheet 2
map.put("month", 10);
Map<String, Object> temp;
for (int i = 1; i < 8; i++) {
temp = new HashMap<String, Object>();
temp.put("per", i * 10);
temp.put("mon", i * 1000);
temp.put("summon", i * 10000);
ImageEntity image = new ImageEntity();
image.setHeight(200);
image.setWidth(500);
image.setUrl("imgs/company/baidu.png");
temp.put("image", image);
map.put("i" + i, temp);
}
Workbook book = ExcelExportUtil.exportExcel(params, map);
File savefile = new File("D:/excel/");
if (!savefile.exists()) {
savefile.mkdirs();
}
FileOutputStream fos = new FileOutputStream("D:/excel/exportTemp_image.xls");
book.write(fos);
fos.close();

}

4. Excel<->Html

4.1 Excel 的Html預覽

Excel預覽,這里支持了比較簡單的預覽,樣式也都可以轉換過去,支持03 和 更高版本 使用也是簡單的很ExcelXorHtmlUtil.excelToHtml(params),也支持圖片的預覽,demo如下

  /**
* 07 版本EXCEL預覽
*/
@RequestMapping("07")
public void toHtmlOf07Base(HttpServletResponse response) throws IOException, InvalidFormatException {
ExcelToHtmlParams params = new ExcelToHtmlParams(WorkbookFactory.create(POICacheManager.getFile("exceltohtml/testExportTitleExcel.xlsx")));
response.getOutputStream().write(ExcelXorHtmlUtil.excelToHtml(params).getBytes());
}
/**
* 03 版本EXCEL預覽
*/
@RequestMapping("03img")
public void toHtmlOf03Img(HttpServletResponse response) throws IOException, InvalidFormatException {
ExcelToHtmlParams params = new ExcelToHtmlParams(WorkbookFactory.create(POICacheManager.getFile("exceltohtml/exporttemp_img.xls")),true,"yes");
response.getOutputStream().write(ExcelXorHtmlUtil.excelToHtml(params).getBytes());
}

返回一個string的html界面,輸出到前台就可以了

4.2 html轉Excel更神奇的導出

這個是一個MM提出的需求,需求原因是,她要導出一個比較復雜的Excel,無論用模板還是注解都比較難實現,所以她想到了這個方案,然后就實現了如下的方法,我的使用方法如下 自己搞個html,然后用模板引擎,beetl,freemark等生成html,然后調用easypoi提供的方法轉換成Excel,因為html的標簽以及規則大家比Excel要熟悉的多,更容易編寫復雜的table,然后easypoi轉換成Excel再導出,麻煩了點,但是可以處理一些特定的情況,也同樣生成兩個版本的Excel都支持 使用demo

    @Test
public void htmlToExcelByStr() throws Exception {
StringBuilder html = new StringBuilder();
Scanner s = new Scanner(getClass().getResourceAsStream("/html/sample.html"), "utf-8");
while (s.hasNext()) {
html.append(s.nextLine());
}
s.close();
Workbook workbook = ExcelXorHtmlUtil.htmlToExcel(html.toString(), ExcelType.XSSF);
File savefile = new File("D:\\home\\lemur");
if (!savefile.exists()) {
savefile.mkdirs();
}
FileOutputStream fos = new FileOutputStream("D:\\home\\lemur\\htmlToExcelByStr.xlsx");
workbook.write(fos);
fos.close();
workbook = ExcelXorHtmlUtil.htmlToExcel(html.toString(), ExcelType.HSSF);
fos = new FileOutputStream("D:\\home\\lemur\\htmlToExcelByStr.xls");
workbook.write(fos);
fos.close();
}

@Test
public void htmlToExcelByIs() throws Exception {
Workbook workbook = ExcelXorHtmlUtil.htmlToExcel(getClass().getResourceAsStream("/html/sample.html"), ExcelType.XSSF);
File savefile = new File("D:\\home\\lemur");
if (!savefile.exists()) {
savefile.mkdirs();
}
FileOutputStream fos = new FileOutputStream("D:\\home\\lemur\\htmlToExcelByIs.xlsx");
workbook.write(fos);
fos.close();
workbook = ExcelXorHtmlUtil.htmlToExcel(getClass().getResourceAsStream("/html/sample.html"), ExcelType.HSSF);
fos = new FileOutputStream("D:\\home\\lemur\\htmlToExcelByIs.xls");
workbook.write(fos);
fos.close();
}

提供了流或者字符串的入參,內部都多了緩存,多次生成不會重復解析

5. Word

5.1 word模板導出

word模板和Excel模板用法基本一致,支持的標簽也是一致的,僅僅支持07版本的word也是只能生成后綴是docx的文檔,poi對doc支持不好,所以這里也就懶得支持了,支持表格和圖片,具體demo如下

 /**

* 簡單導出包含圖片

*/
@Test
public void imageWordExport() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("department", "Easypoi");
map.put("person", "JueYue");
map.put("time", format.format(new Date()));
WordImageEntity image = new WordImageEntity();
image.setHeight(200);
image.setWidth(500);
image.setUrl("cn/afterturn/easypoi/test/word/img/testCode.png");
image.setType(WordImageEntity.URL);
map.put("testCode", image);
try {
XWPFDocument doc = WordExportUtil.exportWord07(
"cn/afterturn/easypoi/test/word/doc/Image.docx", map);
FileOutputStream fos = new FileOutputStream("D:/excel/image.docx");
doc.write(fos);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

/**

* 簡單導出沒有圖片和Excel

*/
@Test
public void SimpleWordExport() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("department", "Easypoi");
map.put("person", "JueYue");
map.put("time", format.format(new Date()));
map.put("me","JueYue");
map.put("date", "2015-01-03");
try {
XWPFDocument doc = WordExportUtil.exportWord07(
"cn/afterturn/easypoi/test/word/doc/Simple.docx", map);
FileOutputStream fos = new FileOutputStream("D:/excel/simple.docx");
doc.write(fos);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

6. PDF

7. Spring MVC

7.1 View 介紹

easypoi view 項目是為了更簡單的方便搭建在導出時候的操作,利用spring mvc 的view 封裝,更加符合spring mvc的風格 view下面包括多個 view的實現

  • EasypoiBigExcelExportView 大數據量導出

  • EasypoiMapExcelView map 列表導出

  • EasypoiPDFTemplateView pdf導出

  • EasypoiSingleExcelView 注解導出

  • EasypoiTemplateExcelView 模板導出

  • EasypoiTemplateWordView word模板導出

  • MapGraphExcelView 圖表導出

view的是使用方法大同小異,都有一個對應的bean,里面保護指定的參數常量 同意用modelmap.put(‘常量參數名’,‘值’)就可以,最后返回這個view名字

注解目錄掃描的時候加上 cn.afterturn.easypoi.view 就可以使用了

7.2 大數據導出View的用法

EasypoiBigExcelExportView 是針對大數據量導出特定的View,在跳轉到這個View的時候不需要查詢數據,而且這個View自己去查詢數據,用戶只要實現IExcelExportServer接口就可以了 對應的常量類BigExcelConstants

public interface IExcelExportServer {
/**
* 查詢數據接口
* @param obj 查詢條件
* @param page 當前頁數
* @return
*/
public List<Object> selectListForExcelExport(Object obj, int page);

}

EasypoiBigExcelExportView 判斷是否還有下一頁的條件是,如果selectListForExcelExport 返回null就認為是最后一頁了,如果返回有數據這page+1繼續查詢 在我們自己的controller中

 @RequestMapping("load")
public void downloadByPoiBaseView(ModelMap map, HttpServletRequest request,
HttpServletResponse response) {
ExportParams params = new ExportParams("2412312", "測試", ExcelType.XSSF);
params.setFreezeCol(2);
map.put(BigExcelConstants.CLASS, MsgClient.class);
map.put(BigExcelConstants.PARAMS, params);
//就是我們的查詢參數,會帶到接口中,供接口查詢使用
map.put(BigExcelConstants.DATA_PARAMS, new HashMap<String,String>());
map.put(BigExcelConstants.DATA_INTER,excelExportServer);
PoiBaseView.render(map, request, response, BigExcelConstants.EASYPOI_BIG_EXCEL_VIEW);

}

我們需要把參數條件封裝成map或者其他類型,上面的obj可以把參數自己轉回來 參數名字 BigExcelConstants.DATA_PARAM 然后把實現查詢的接口注入進來就可以了 *map.put(BigExcelConstants.DATA_INTER,excelExportServer);* 后面就和其他View一樣了

7.3 注解導出View用法

注解導出的View是這個EasypoiSingleExcelView,其實View大家可以忽略不看,主要用到的還是他對應的bean對象 NormalExcelConstants 注解到處還比較簡單,大家只要把datalist,class和params 這幾個參數put下就可以了。 具體的案例

 @RequestMapping()
public String download(ModelMap map) {
List<MsgClient> list = new ArrayList<MsgClient>();
for (int i = 0; i < 100; i++) {
MsgClient client = new MsgClient();
client.setBirthday(new Date());
client.setClientName("小明" + i);
client.setClientPhone("18797" + i);
client.setCreateBy("JueYue");
client.setId("1" + i);
client.setRemark("測試" + i);
MsgClientGroup group = new MsgClientGroup();
group.setGroupName("測試" + i);
client.setGroup(group);
list.add(client);
}
ExportParams params = new ExportParams("2412312", "測試", ExcelType.XSSF);
params.setFreezeCol(2);
map.put(NormalExcelConstants.DATA_LIST, list); // 數據集合
map.put(NormalExcelConstants.CLASS, MsgClient.class);//導出實體
map.put(NormalExcelConstants.PARAMS, params);//參數
map.put(NormalExcelConstants.FILE_NAME, params);//文件名稱
return NormalExcelConstants.EASYPOI_EXCEL_VIEW;//View名稱

}

和非View導出基本一致,只是把調用方法封裝了而已,其他參數還都是一樣的,具體可以看下測試項目的 EasypoiSingleExcelViewTest

7.4 注解變種Map類型的導出View

作為動態注解存在的 List<ExcelExportEntity> ,也提供的單獨的View方便大家使用,EasypoiMapExcelView 使用方法都是一樣,直接看下例子吧

 @RequestMapping()
public String download(ModelMap modelMap) {
List<ExcelExportEntity> entity = new ArrayList<ExcelExportEntity>();
ExcelExportEntity excelentity = new ExcelExportEntity("姓名", "name");
excelentity.setNeedMerge(true);
entity.add(excelentity);
entity.add(new ExcelExportEntity("性別", "sex"));
excelentity = new ExcelExportEntity(null, "students");
List<ExcelExportEntity> temp = new ArrayList<ExcelExportEntity>();
temp.add(new ExcelExportEntity("姓名", "name"));
temp.add(new ExcelExportEntity("性別", "sex"));
excelentity.setList(temp);
entity.add(excelentity);

List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> map;
for (int i = 0; i < 10; i++) {
map = new HashMap<String, Object>();
map.put("name", "1" + i);
map.put("sex", "2" + i);

List<Map<String, Object>> tempList = new ArrayList<Map<String, Object>>();
tempList.add(map);
tempList.add(map);
map.put("students", tempList);

list.add(map);
}

ExportParams params = new ExportParams("2412312", "測試", ExcelType.XSSF);
params.setFreezeCol(2);
modelMap.put(MapExcelConstants.MAP_LIST, list); //數據集合
modelMap.put(MapExcelConstants.ENTITY_LIST, entity); //注解集合
modelMap.put(MapExcelConstants.PARAMS, params);//參數
modelMap.put(MapExcelConstants.FILE_NAME, "EasypoiMapExcelViewTest");//文件名稱
return MapExcelConstants.EASYPOI_MAP_EXCEL_VIEW;//View名稱

}

具體案例參考EasypoiMapExcelViewTest

7.5Excel模板導出View

模板導出提供的EasypoiTemplateExcelView以及對應的bean *TemplateExcelConstants* 案例

@RequestMapping()
   public String download(ModelMap modelMap) {
       Map<String, Object> map = new HashMap<String, Object>();
       TemplateExportParams params = new TemplateExportParams(
           "doc/foreach.xlsx");
       List<TemplateExcelExportEntity> list = new ArrayList<TemplateExcelExportEntity>();

       for (int i = 0; i < 4; i++) {
           TemplateExcelExportEntity entity = new TemplateExcelExportEntity();
           entity.setIndex(i + 1 + "");
           entity.setAccountType("開源項目");
           entity.setProjectName("EasyPoi " + i + "期");
           entity.setAmountApplied(i * 10000 + "");
           entity.setApprovedAmount((i + 1) * 10000 - 100 + "");
           list.add(entity);
      }
       map.put("entitylist", list);
       map.put("manmark", "1");
       map.put("letest", "12345678");
       map.put("fntest", "12345678.2341234");
       map.put("fdtest", null);
       List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();
       for (int i = 0; i < 1; i++) {
           Map<String, Object> testMap = new HashMap<String, Object>();

           testMap.put("id", "xman");
           testMap.put("name", "小明" + i);
           testMap.put("sex", "1");
           mapList.add(testMap);
      }
       map.put("maplist", mapList);

       mapList = new ArrayList<Map<String, Object>>();
       for (int i = 0; i < 6; i++) {
           Map<String, Object> testMap = new HashMap<String, Object>();

           testMap.put("si", "xman");
           mapList.add(testMap);
      }
       map.put("sitest", mapList);
       modelMap.put(TemplateExcelConstants.FILE_NAME, "用戶信息"); //文件名
       modelMap.put(TemplateExcelConstants.PARAMS, params);//參數
       modelMap.put(TemplateExcelConstants.MAP_DATA, map);//數據
       return TemplateExcelConstants.EASYPOI_TEMPLATE_EXCEL_VIEW;//view名稱

  }

具體案例EasypoiTemplateExcelViewTest

7.6 PoiBaseView.render view的補救

假如因為不可抗拒或者其他神奇的原因,view導出無法使用,作者遇到過好幾次了,各種神奇原因都有,提供一個統一的封裝,算是一個補救措施吧 上面的modelMap寫法和設置參數還是一樣,最后直接輸出就可以了 PoiBaseView.render(modelMap, request, response,View名稱);

看個簡單demo

 @RequestMapping("load")
   public void downloadByPoiBaseView(ModelMap map, HttpServletRequest request,
                                     HttpServletResponse response) {
       List<MsgClient> list = new ArrayList<MsgClient>();
       for (int i = 0; i < 100; i++) {
           MsgClient client = new MsgClient();
           client.setBirthday(new Date());
           client.setClientName("小明" + i);
           client.setClientPhone("18797" + i);
           client.setCreateBy("JueYue");
           client.setId("1" + i);
           client.setRemark("測試" + i);
           MsgClientGroup group = new MsgClientGroup();
           group.setGroupName("測試" + i);
           client.setGroup(group);
           list.add(client);
      }
       ExportParams params = new ExportParams("2412312", "測試", ExcelType.XSSF);
       params.setFreezeCol(2);
       map.put(NormalExcelConstants.DATA_LIST, list);
       map.put(NormalExcelConstants.CLASS, MsgClient.class);
       map.put(NormalExcelConstants.PARAMS, params);
       PoiBaseView.render(map, request, response, NormalExcelConstants.EASYPOI_EXCEL_VIEW);

  }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM