開發環境
jdk 1.8
Maven 3.6
Tomcat 8.5
SpringBoot 2.1.4.RELEASE
Apache-POI 3.6
Idea
注意: 我是在現有的基於SpringBoot的分模塊項目中集成的文件導出功能,所以就不再從項目構建開始介紹了,如有對SpringBoot項目構建不熟悉,或對構建分模塊項目感興趣的同學,可移步[SpringBoot構建分模塊項目...(還沒寫呢)]
Maven依賴
在pom.xml中添加依賴,一下兩種方式,根據個人習慣,任選其一;
<!-- 引入方式一 --> <properties> <!-- ↓↓↓↓↓↓ 此處省略項目中用到的基本jar包的版本 ↑↑↑↑↑↑--> <!-- 版本 --> <apache.poi.version>3.6</apache.poi.version> </properties> <dependencies> <!-- ↓↓↓↓↓↓ 此處省略項目中用到的基本jar包 ↑↑↑↑↑↑--> <!-- Apache-POI --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${apache.poi.version}</version> </dependency> </dependencies>
<!-- 引入方式二 --> <dependencies> <!-- ↓↓↓↓↓↓ 此處省略項目中用到的基本jar包 ↑↑↑↑↑↑--> <!-- Apache-POI --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.6</version> </dependency> </dependencies>
當pom配置好之后,我們查看右側Maven,檢查是否下載成功;
有的同學可能會遇Maven中顯示Apache-POI確實已經被引入了,但是仍然在項目中無法使用。其實此時jar確實已經被下載到本地倉庫了,只是沒有添加到我們項目中。添加方式參照[解決Idea項目啟動報錯:程序包javax.servlet.http不存在],只是此時的jar來源不再是Tomcat目錄下,而是我們的本地倉庫;
前面都做好的話,就可以編寫我們的代碼了;
工具類
先說下我設計這個接口之前想到的問題,以及對該接口的應用場景
該工具類設計好之后,可對本項目提供服務,也可包裝成接口,對外暴露服務,調用者只需按照我們要求的參數格式給出參數即可
該項目部署之后,將來其他項目需要用到導出功能,只需調用我們的接口即可,
根據以上兩點,在工具類的設計上要遵循低耦合的原則,使其高可用、高復用
package com.wayne.utils; import com.alibaba.fastjson.JSON; import com.google.common.collect.Lists; import com.wayne.common.exception.ServiceException; import org.apache.poi.hssf.usermodel.*; import java.io.File; import java.io.FileOutputStream; import java.util.List; import java.util.Map; /** * 文件導出工具類 * @author Wayne * @date 2019/5/15 */ public class ExportUtil { /** * 無模塊導出Excel方法, * 參數data格式: [ * { * "姓名": "張三"; * "年齡": "23"; * "性別": "男" * }, * { * "姓名": "李四"; * "年齡": "24"; * "性別": "男" * } * ] * * @param data 需要導出的數據 * @param fileName 導出后保存到本地的文件名 * @return 創建好的Excel文件,用於前端下載 * @throws ServiceException 自定義RunTimeException子類異常 */ public static HSSFWorkbook exportExcel(List<Map<String, Object>> data, String fileName) throws ServiceException { // 從參數data中解析出打印的每列標題,放入title中 List<String> title = Lists.newArrayList(); for(Map.Entry<String, Object> entry : data.get(0).entrySet()) { title.add(entry.getKey()); } // 新建一個Excel文件 HSSFWorkbook wb = new HSSFWorkbook(); // Excel中的sheet HSSFSheet sheet = wb.createSheet(); // sheet中的行,0表示第一行 HSSFRow row = sheet.createRow(0); // 設置標題居中 HSSFCellStyle cellStyle = wb.createCellStyle(); cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); // sheet中的單元格 HSSFCell cell = null; // 給第一行賦值,值為我們從參數中解析出的標題,因此需要我們在傳參的時候需要嚴格按照約定 for(int i = 0; i < title.size(); i++) { cell = row.createCell(i); cell.setCellValue(title.get(i)); cell.setCellStyle(cellStyle); } // 根據參數內容數量,創建表格行數 for(int i = 0; i < data.size(); i++) { row = sheet.createRow(i + 1); Map<String, Object> values = data.get(i); // 將參數插入每一個單元格 for(int k = 0; k < title.size(); k++) { Object value = values.get(title.get(k)); if(null == value) { value = ""; } String val = JSON.toJSONString(value); row.createCell(k).setCellValue(val); } } // 將文件寫到硬盤中,將來根據需求,或寫到服務器中,因此在實際開發中,最好將"E:/Temp/"配置在.properties配置文件中,儀表項目上線事更換方便 try { FileOutputStream fileOutputStream = new FileOutputStream(new File("E:/Temp/" + fileName)); wb.write(fileOutputStream); fileOutputStream.flush(); fileOutputStream.close(); } catch (Exception e) { throw new ServiceException("文件導出失敗"); } return wb; } }
接口層和業務層
接口為傳統的接口
在業務層對查詢到的data做了處理,已達到符合工具類規范
package com.wayne.impl; import com.alibaba.fastjson.JSON; import com.wayne.common.dto.ResponseBean; import com.wayne.common.entity.CmsUser; import com.wayne.mapper.CmsUserMapper; import com.wayne.service.ExportService; import com.wayne.utils.ExportUtil; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.UUID; /** * 文件導出Service實現類 * @author Wayne * @date 2019/5/15 */ @Service public class ExportServiceImpl implements ExportService { @Autowired private CmsUserMapper userMapper; /** * 無模板導出Excel * @param request 前端傳來參數,格式為Json字符串的對象,因為在實際開發中可能要用到根據查詢參數導出,比如: 導出年齡在18 --- 26之間的、性別為女的用戶 * @return */ @Override public ResponseBean exportExcel(String request) { ResponseBean responseBean; // 將前端參數轉換為對象 CmsUser user = JSON.parseObject(request, CmsUser.class); // 根據條件查詢需要導出的數據(后附sql內容) List<Map<String, Object>> userList = userMapper.selectByParams(user); try { // 生成文件名 String fileName = UUID.randomUUID() + ".xls"; // 調用工具類生成Excel,此時接收到的wb即是完整的Excel HSSFWorkbook wb = ExportUtil.exportExcel(userList, fileName); //TODO 此處可根據實際需要添加是否寫到前端,供用戶下載 responseBean = ResponseBean.createInstance(Boolean.TRUE, "導出成功", fileName); } catch (Exception e) { responseBean = ResponseBean.createInstance(false, 500, e.getMessage()); System.out.println(e.getMessage()); } return responseBean; } }
package com.wayne.web.comtroller; import com.wayne.common.dto.ResponseBean; import com.wayne.service.ExportService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 文件導出接口 * 接口層不做業務處理 * @author Wayne * @date 2019/5/15 */ @RestController @RequestMapping("/export") public class ExportController { @Autowired private ExportService exportService; /** * 導出Excel * @param request 導出條件 */ @PostMapping("/excel") public ResponseBean exportExcel(String request) { return exportService.exportExcel(request); } }
其他區相關代碼
Service
package com.wayne.service; import com.wayne.common.dto.ResponseBean; /** * @author Wayne * @date 2019/5/15 */ public interface ExportService { ResponseBean exportExcel(String request); }
Mapper
Mapper沒什么好貼出來的吧~~~
Mapper.xml
<!-- 此查詢方法是從視圖中查,目的還是為了降低耦合性,在視圖中已經對data做了格式調整 --> <select id="selectByParams" resultType="Map"> SELECT * FROM v_user WHERE `用戶名` != 'admin' <where> <if test="username != null and username != ''"> and `用戶名` = #{username, jdbcType=VARCHAR} </if> <if test="mobile != null and mobile != ''"> and `手機號` = #{mobile,jdbcType=VARCHAR} </if> </where> ORDER BY `用戶名` </select>
視圖
SELECT `cms_user`.`USERNAME` AS `用戶名`, `cms_user`.`REALNAME` AS `真實姓名`, `cms_user`.`SEX` AS `性別`, `cms_user`.`EMAIL` AS `郵箱`, `cms_user`.`MOBILE` AS `手機號`, `cms_user`.`STATUS` AS `狀態`, `cms_user`.`CREATE_TIME` AS `創建時間`, `cms_user`.`CREATE_USER_ID` AS `創建者` FROM `cms_user`
其它
本項目中用到的ResponsBean為自定義返回到前端的實體類,ServiceException為自定義RunTimeException異常
前端代碼就不再展示了,因為此功能在前端只是一個按鈕和點擊事件
效果展示
因為不確定時間在第幾列,所以無法動態將時間列設置為日期格式,需要后續手動處理
預留占位
開發怎能不留擴展字段 (¬_¬)…