前言
這幾天在學習如何使用自定義注解實作Excel格式資料匯入匯出,參考的還是若依框架里面的代碼,由于是初學,所以照貓畫虎唄,但是難受的是需要復制并根據自己專案修改作者自定義的工具類以及匯入這些工具類的依賴包,由于吃了這個苦,我決定把這個艱辛的CV操作通過一張邏輯圖來表達,方便我以后復用,下面證實開始介紹這個功能的實作,但是由于對專案中的只是很不了解,我這里簡單實作,并簡單講解,深層次的代碼我會給出,后續會繼續運用講解,整篇博客分為兩個部分,一部分是資料的匯出,一部分是資料的匯入,本文專案鏈接:WomPlus: 結合若依專案對原始工單專案內容進行增強 (gitee.com)
1.所需要的依賴
在進行專案前,我們需要匯入依賴才能參考具體的功能,因此第一步就是匯入依賴了,這里除了Excel需要的依賴,一些工具類的依賴也需要匯入,為什么呢?因為作者的專案寫的很細致,比如在咋們的java中自帶有StringUtils工具類,但是作者細致地自己寫了一個,可見其基礎功能之深呀!
<!--Excel工具類依賴--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency> <!--常用工具類依賴--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!--io常用工具依賴--> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> <!--解決@ConfigurationProperties(prefix = "wo")的 Springboot Configuration Annotation Processor not found in classPath問題依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>所需依賴
2.修改組態檔
引入依賴是第一步,修改組態檔就是我們的第二步了,在這里修改組態檔是因為在該功能的資料匯入,作者是在自己自定義的RuoYiConfig中通過
@ConfigurationProperties(prefix = "wo")注解獲取組態檔中profile屬性值,即我們上傳下載檔案的地址,我這里使用的是properties為后綴的檔案,組態檔為yaml的請根據自己檔案型別修改
//添加自己的專案配置類WoConfig需要配置的專案名和匯出Excel形式資料下載路徑
#專案相關配置
wo.name=WO
wo.profile=G:/Desktop/base_study/project/myself_springboot_project/wom-plus/uploadPath
3.資料以Excel格式匯出的Controller層
@PostMapping("/export")
@ResponseBody
public AjaxResult export(@RequestParam(value = "https://www.cnblogs.com/kzf-99/archive/2023/04/07/name", required = false) String username){
List<SysUser> list = userDetailsService.getUserListByUsername(username);
ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
return util.exportExcel(list, "用戶資料");
}
在UserController的export()方法中,首先傳入一個Class.class作為ExcelUtils類的引數來new一個ExcelUtils物件util,然后傳遞一個Class的list物件和表名稱到util的exportExcel()方法中回傳Class的Excel資訊,
3.1 ExcelUtils的有參構造
//1.傳入需要Excel匯出類的Class.class,回傳一個ExcelUtil<Class>物件 public ExcelUtil(Class<T> clazz) { this.clazz = clazz; }
這里就是傳入一個Class類來創建該類的Excel物件,本篇博客圍繞著SysUser類實作該功能的,因此這里傳入SysUser.class
3.2 exportExcel(List<T>list, String sheetName)
public AjaxResult exportExcel(List<T> list, String sheetName) { return exportExcel(list, sheetName, StringUtils.EMPTY); }
在方法中,呼叫exportExcel(List<SysUser>list, String sheetName, String title)封裝Excel格式資料匯出
3.3 exportExcel(List<T> list, String sheetName, String title)
public AjaxResult exportExcel(List<T> list, String sheetName, String title) { this.init(list, sheetName, title, Type.EXPORT); return exportExcel(); }
本文方法首先初始化要創建的Excel表格,然后回傳exportExcel()方法來講要匯出的資料寫進前面的profile路徑中
3.3.1 init(list, sheetName, title, Type.EXPORT)
public void init(List<T> list, String sheetName, String title, Type type) { if (list == null) { list = new ArrayList<T>(); } this.list = list; this.sheetName = sheetName; this.type = type; this.title = title; //根據要匯出Excel的物體類的欄位創建欄位Class類所需要的Excel欄位 createExcelField(); createWorkbook();//創建一個作業薄 createTitle();//創建Excel第一行標題 createSubHead();//創建物件子串列名稱 }
3.3.2 exportExcel()
public AjaxResult exportExcel() { OutputStream out = null; try { writeSheet();//寫入資料到Sheet String filename = encodingFilename(sheetName);//編輯檔案名 //getAbsoluteFile(filename)根據檔案名稱獲取下載路徑 //創建一個輸出流 out = new FileOutputStream(getAbsoluteFile(filename)); wb.write(out);//寫入Excel資訊到該路徑 return AjaxResult.success(filename);//回傳匯出成功資訊 } catch (Exception e) { log.error("匯出Excel例外{}", e.getMessage()); throw new UtilException("匯出Excel失敗,請聯系網站管理員!"); } finally { IOUtils.closeQuietly(wb);//關閉作業薄物件輸出流 IOUtils.closeQuietly(out);//關閉輸出流 } }匯出資料庫資料到指定路徑
3.4 @Excel和@Excels注解
package com.ku.wo.framework.aspectj.lang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.math.BigDecimal; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import com.ku.wo.common.utils.poi.ExcelHandlerAdapter; /** * 自定義匯出Excel資料注解 * * @author ruoyi */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)//作用在什么地方 public @interface Excel { /** * 匯出時在excel中排序 */ public int sort() default Integer.MAX_VALUE; /** * 匯出到Excel中的名字. */ public String name() default ""; /** * 日期格式, 如: yyyy-MM-dd */ public String dateFormat() default ""; /** * 如果是字典型別,請設定字典的type值 (如: sys_user_sex) */ public String dictType() default ""; /** * 讀取內容轉運算式 (如: 0=男,1=女,2=未知) */ public String readConverterExp() default ""; /** * 分隔符,讀取字串組內容 */ public String separator() default ","; /** * BigDecimal 精度 默認:-1(默認不開啟BigDecimal格式化) */ public int scale() default -1; /** * BigDecimal 舍入規則 默認:BigDecimal.ROUND_HALF_EVEN */ public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; /** * 匯出時在excel中每個列的高度 單位為字符 */ public double height() default 14; /** * 匯出時在excel中每個列的寬 單位為字符 */ public double width() default 16; /** * 文字后綴,如% 90 變成90% */ public String suffix() default ""; /** * 當值為空時,欄位的默認值 */ public String defaultValue() default ""; /** * 提示資訊 */ public String prompt() default ""; /** * 設定只能選擇不能輸入的列內容. */ public String[] combo() default {}; /** * 是否需要縱向合并單元格,應對需求:含有list集合單元格) */ public boolean needMerge() default false; /** * 是否匯出資料,應對需求:有時我們需要匯出一份模板,這是標題需要但內容需要用戶手工填寫. */ public boolean isExport() default true; /** * 另一個類中的屬性名稱,支持多級獲取,以小數點隔開 */ public String targetAttr() default ""; /** * 是否自動統計資料,在最后追加一行統計資料總和 */ public boolean isStatistics() default false; /** * 匯出型別(0數字 1字串 2圖片) */ public ColumnType cellType() default ColumnType.STRING; /** * 匯出列頭背景顏色 */ public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; /** * 匯出列頭字體顏色 */ public IndexedColors headerColor() default IndexedColors.WHITE; /** * 匯出單元格背景顏色 */ public IndexedColors backgroundColor() default IndexedColors.WHITE; /** * 匯出單元格字體顏色 */ public IndexedColors color() default IndexedColors.BLACK; /** * 匯出欄位對齊方式 */ public HorizontalAlignment align() default HorizontalAlignment.CENTER; /** * 自定義資料處理器 */ public Class<?> handler() default ExcelHandlerAdapter.class; /** * 自定義資料處理器引數 */ public String[] args() default {}; /** * 欄位型別(0:匯出匯入;1:僅匯出;2:僅匯入) */ Type type() default Type.ALL; public enum Type { ALL(0), EXPORT(1), IMPORT(2); private final int value; Type(int value) { this.value =https://www.cnblogs.com/kzf-99/archive/2023/04/07/ value; } public int value() { return this.value; } } public enum ColumnType { NUMERIC(0), STRING(1), IMAGE(2); private final int value; ColumnType(int value) { this.value =https://www.cnblogs.com/kzf-99/archive/2023/04/07/ value; } public int value() { return this.value; } } } //@Excels package com.ku.wo.framework.aspectj.lang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Excel注解集 * * @author ruoyi */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Excels { Excel[] value(); }@Excel和@Excels
3.5 運行結果


4.資料以Excel格式匯出的Controller層
@PostMapping("/importData")
@ResponseBody
public AjaxResult importData(MultipartFile file) throws Exception {
ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
List<SysUser> userList = util.importExcel(file.getInputStream());
String message = userDetailsService.importUser(userList);
return AjaxResult.success(message);
}
在UserController的importData()方法中,首先傳入一個Class.class作為ExcelUtils類的引數來new一個ExcelUtils物件util,然后傳入一個要匯入的Excel形式的表格資料作為utils.importData()函式的輸入流is,importData()方法根據is回傳Excel表格資料中的Class類的list物件,接著傳入一個Class的list物件和一個關于是否支持更新資料庫已有資料的布爾變數到importUser()方法中,該方法根據這兩個引數來回傳插入成功或失敗的資訊message,最后傳入message到AjaxResult.success()方法回傳匯入資料成功訊息,
4.1 importExcel(InputStream)
public List<T> importExcel(InputStream is) throws Exception { return importExcel(is, 0); } public List<T> importExcel(InputStream is, int titleNum) throws Exception { return importExcel(StringUtils.EMPTY, is, titleNum); } //核心實作代碼 public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception { this.type = Type.IMPORT; this.wb = WorkbookFactory.create(is); List<T> list = new ArrayList<T>(); // 如果指定sheet名,則取指定sheet中的內容 否則默認指向第1個sheet Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); if (sheet == null) { throw new IOException("檔案sheet不存在"); } boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); Map<String, PictureData> pictures; if (isXSSFWorkbook) { pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); } else { pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); } // 獲取最后一個非空行的行下標,比如總行數為n,則回傳的為n-1 int rows = sheet.getLastRowNum(); if (rows > 0) { // 定義一個map用于存放excel列的序號和field. Map<String, Integer> cellMap = new HashMap<String, Integer>(); // 獲取表頭 Row heard = sheet.getRow(titleNum); for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) { Cell cell = heard.getCell(i); if (StringUtils.isNotNull(cell)) { String value = this.getCellValue(heard, i).toString(); cellMap.put(value, i); } else { cellMap.put(null, i); } } // 有資料時才處理 得到類的所有field. List<Object[]> fields = this.getFields(); Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>(); for (Object[] objects : fields) { Excel attr = (Excel) objects[1]; Integer column = cellMap.get(attr.name()); if (column != null) { fieldsMap.put(column, objects); } } for (int i = titleNum + 1; i <= rows; i++) { // 從第2行開始取資料,默認第一行是表頭. Row row = sheet.getRow(i); // 判斷當前行是否是空行 if (isRowEmpty(row)) { continue; } T entity = null; for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()) { Object val = this.getCellValue(row, entry.getKey()); // 如果不存在實體則新建. entity = (entity == null ? clazz.newInstance() : entity); // 從map中得到對應列的field. Field field = (Field) entry.getValue()[0]; Excel attr = (Excel) entry.getValue()[1]; // 取得型別,并根據物件型別設定值. Class<?> fieldType = field.getType(); if (String.class == fieldType) { String s = Convert.toStr(val); if (StringUtils.endsWith(s, ".0")) { val = StringUtils.substringBefore(s, ".0"); } else { String dateFormat = field.getAnnotation(Excel.class).dateFormat(); if (StringUtils.isNotEmpty(dateFormat)) { val = parseDateToStr(dateFormat, val); } else { val = Convert.toStr(val); } } } else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) { val = Convert.toInt(val); } else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) { val = Convert.toLong(val); } else if (Double.TYPE == fieldType || Double.class == fieldType) { val = Convert.toDouble(val); } else if (Float.TYPE == fieldType || Float.class == fieldType) { val = Convert.toFloat(val); } else if (BigDecimal.class == fieldType) { val = Convert.toBigDecimal(val); } else if (Date.class == fieldType) { if (val instanceof String) { val = DateUtils.parseDate(val); } else if (val instanceof Double) { val = DateUtil.getJavaDate((Double) val); } } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) { val = Convert.toBool(val, false); } if (StringUtils.isNotNull(fieldType)) { String propertyName = field.getName(); if (StringUtils.isNotEmpty(attr.targetAttr())) { propertyName = field.getName() + "." + attr.targetAttr(); } else if (StringUtils.isNotEmpty(attr.readConverterExp())) { val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); } else if (StringUtils.isNotEmpty(attr.dictType())) { val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) { val = dataFormatHandlerAdapter(val, attr); } else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) { PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey()); if (image == null) { val = ""; } else { byte[] data =https://www.cnblogs.com/kzf-99/archive/2023/04/07/ image.getData(); val = FileUtils.writeImportBytes(data); } } ReflectUtils.invokeSetter(entity, propertyName, val); } } list.add(entity); } } return list; }importExcel
該方法主要是通過傳入的檔案獲取Excel表格中的資料,這里是真正匯入資料的核心,我暫時還沒想清楚,
4.2 importUser(InputStream)
String importUser(List<SysUser> userList); @Override public String importUser(List<SysUser> userList) { //此處的isNull判斷的是一個物件,此時我傳入的是一個用戶list物件 if (StringUtils.isNull(userList) || userList.size() == 0){ throw new ServiceException("匯入用戶資料不能為空!"); } int successNum = 0; //StringBuilder字符拼接工具類,是一個可變類不安全,StringBuffer是一個不可變的安全字符拼接工具類 StringBuilder successMsg = new StringBuilder(); for (SysUser user: userList) { //因為前面驗證了該list物件不為空,故直接插入 userMapper.insertUser(user); successNum++; successMsg.append("<br/>" + successNum + "、賬號 " + user.getUsername() + " 匯入成功"); } successMsg.insert(0, "恭喜你,資料已經全部匯入成功!共" + successNum + "條,資料如下:"); return successMsg.toString(); }Excel表格資料插入到對應資料庫的資料表
該方法實作將Excel中獲取的資料匯入到對應資料庫的資料表中,并回傳匯入成功的回應資訊,
4.3 運行結果

5.參考專案連鏈接
RuoYi: ?? 基于SpringBoot的權限管理系統 易讀易懂、界面簡潔美觀, 核心技術采用Spring、MyBatis、Shiro沒有任何其它重度依賴,直接運行即可用 (gitee.com)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549388.html
標籤:其他
上一篇:JUC并發編程第一章之行程/并發/異步的概念[理解基本概念]
下一篇:Spring注解實作回圈重試功能
