Java实现Excel导入和导出,看这一篇就够了(珍藏版)

您所在的位置:网站首页 导出excel文件失败 Java实现Excel导入和导出,看这一篇就够了(珍藏版)

Java实现Excel导入和导出,看这一篇就够了(珍藏版)

2023-12-02 02:53| 来源: 网络整理| 查看: 265

目录

目录

前言

1. 功能测试

1.1 测试准备

1.2 数据导入

1.2.1 导入解析为JSON

1.2.2 导入解析为对象(基础)

1.2.3 导入解析为对象(字段自动映射)

1.2.4 导入解析为对象(获取行号)

1.2.5 导入解析为对象(获取原始数据)

1.2.6 导入解析为对象(获取错误提示)

1.2.7 导入解析为对象(限制字段长度)

1.2.8 导入解析为对象(必填字段验证)

1.2.9 导入解析为对象(数据唯一性验证)

1.2.10 导入多Sheet页

1.3 数据导出

1.3.1 动态导出(基础)

1.3.2 动态导出(导出图片)

1.3.3 动态导出(实现下拉列表)

1.3.4 动态导出(横向合并)

1.3.5 动态导出(纵向合并)

1.3.6 导出模板(基础)

1.3.7 导出模板(附示例数据)

1.3.8 按对象导出(基础)

1.3.9 按对象导出(数据映射)

1.3.10 按对象导出(调整表头顺序)

1.3.11 多Sheet页导出

2. 环境准备

2.1 Maven 依赖

2.2 类文件

ExcelUtils

ExcelImport

ExcelExport

ExcelClassField

3. 更新优化

3.1 优化身份证导出显示为科学计数法的问题(2022-07-04)

4. 疑问解答

4.1 关于身份证导入显示科学计数法的问题(2022-07-04)

前言

最近抽了两天时间,把Java实现表格的相关操作进行了封装,本次封装是基于 POI 的二次开发,最终使用只需要调用一个工具类中的方法,就能满足业务中绝大部门的导入和导出需求。

1. 功能测试 1.1 测试准备

在做测试前,我们需要將【2. 环境准备】中的四个文件拷贝在工程里(如:我这里均放在了com.zyq.util.excel 包下)。

1.2 数据导入 1.2.1 导入解析为JSON

比如,我们有下面一个表格:

Controller 代码:

@PostMapping("/import") public JSONArray importUser(@RequestPart("file")MultipartFile file) throws Exception { JSONArray array = ExcelUtils.readMultipartFile(file); System.out.println("导入数据为:" + array); return array; }

测试效果:

1.2.2 导入解析为对象(基础)

首先,你需要创建一个与导入表格对应的Java实体对象,并打上对应的Excel解析的导入注解,@ExcelImport注解的value则为表头名称。

 Controller 代码:

@PostMapping("/import") public void importUser(@RequestPart("file")MultipartFile file) throws Exception { List users = ExcelUtils.readMultipartFile(file, User.class); for (User user : users) { System.out.println(user.toString()); } }

测试效果:

1.2.3 导入解析为对象(字段自动映射)

对于有的枚举数据,通常我们导入的时候,表格中的数据是值,而在数据保存时,往往用的是键,比如:我们用sex=1可以表示为男,sex=2表示为女,那么我们通过配置也可以达到导入时,数据的自动映射。

那么,我们只需要将Java实体中的对象sex字段的类型改为对应的数字类型Integer,然后再注解中配置好 kv 属性(属性格式为:键1-值1;键2-值2;键3-值3;.....)

Cotroller 代码略(和 1.2.2 完全一致)。

测试效果:可以看到已经自动映射成功了。

1.2.4 导入解析为对象(获取行号)

我们在做页面数据导入时,有时候可能需要获取行号,好追踪导入的数据。

那么,我们只需要在对应的实体中加入一个 int 类型的 rowNum 字段即可。

Cotroller 代码略(和 1.2.2 完全一致)。

测试效果:

1.2.5 导入解析为对象(获取原始数据)

在做页面数据导入的时候,如果某行存在错误,一般我们会将原始的数据拿出来分析,为什么会造成数据错误。那么,我们在实体类中,增加一个 String 类型的 rowData 字段即可。

 Cotroller 代码略(和 1.2.2 完全一致)。

测试效果:

1.2.6 导入解析为对象(获取错误提示)

当我们在导入数据的时候,如果某行数据存在,字段类型不正确,长度超过最大限制(详见1.2.7),必填字段验证(1.2.8),数据唯一性验证(1.2.9)等一些错误时候,我们可以往对象中添加一个 String 类型的 rowTips 字段,则可以直接拿到对应的错误信息。

 比如,我们将表格中赵子龙的性别改为F(F并不是映射数据),将大乔的性别改为二十八(不能转换为Integer类型数据)。

 

Cotroller 代码略(和 1.2.2 完全一致)。

测试效果:可以看到,我们可以通过 rowTips 直接拿到对应的错误数据提示。

1.2.7 导入解析为对象(限制字段长度)

比如,我们手机通常为11为长度,那么不妨限制电话的最大长度位数为11位。

对应的做法,就是在 @ExcelImport 注解中,设置 maxLength = 11 即可。

比如,我们将诸葛孔明的电话长度设置为超过11位数的一个字符串。

 Cotroller 代码略(和 1.2.2 完全一致)。

 测试效果:

1.2.8 导入解析为对象(必填字段验证)

我们在做数据导入的时候,往往还会有一些必填字段,比如用户的名称,电话。

那么,我们只需要在 @ExcelImport 注解属性中,加上 required = true 即可。

我们将诸葛孔明的电话,以及第4行的姓名去掉,进行测试。

Cotroller 代码略(和 1.2.2 完全一致)。

测试效果:

1.2.9 导入解析为对象(数据唯一性验证)

(1) 单字段唯一性验证

我们在导入数据的时候,某个字段是具有唯一性的,比如我们这里假设规定姓名不能重复,那么则可以在对应字段的 @ExcelImport 注解上加上 unique = true 属性。

 这里我们构建2条姓名一样的数据进行测试。

 Cotroller 代码略(和 1.2.2 完全一致)。

测试效果:

(2)多字段唯一性验证

如果你导入的数据存在多字段唯一性验证这种情况,只需要将每个对应字段的 @ExcelImport 注解属性中,都加上 unique = true 即可。

比如:我们将姓名和电话两个字段进行联合唯一性验证(即不能存在有名称和电话都一样的数据,单个字段属性重复允许)。

首先,我们将刚刚(1)的数据进行导入。

 测试效果:可以看到,虽然名称有相同,但电话不相同,所以这里并没有提示唯一性验证错误。

现在,我们将最后一行的电话也改为和第1行一样的,于是,现在就存在了违背唯一性的两条数据。

测试效果:可以看到,我们的联合唯一性验证生效了。 

1.2.10 导入多Sheet页

如果你的导入表格存在多个sheet页(如下图),并想解析每个sheet页的数据,那么也是可以的。

 

 

 Controller 代码:

@PostMapping("/import") public void upload(@RequestPart("file") MultipartFile file) throws Exception { Map map = ExcelUtils.readFileManySheet(file); map.forEach((key, value) -> { System.out.println("Sheet名称:" + key); System.out.println("Sheet数据:" + value); System.out.println("----------------------"); }); }

代码截图:

测试效果:

1.3 数据导出 1.3.1 动态导出(基础)

这种方式十分灵活,表中的数据,完全自定义设置。

Controller 代码:

@GetMapping("/export") public void export(HttpServletResponse response) { // 表头数据 List head = Arrays.asList("姓名","年龄","性别","头像"); // 用户1数据 List user1 = new ArrayList(); user1.add("诸葛亮"); user1.add(60); user1.add("男"); user1.add("https://profile.csdnimg.cn/A/7/3/3_sunnyzyq"); // 用户2数据 List user2 = new ArrayList(); user2.add("大乔"); user2.add(28); user2.add("女"); user2.add("https://profile.csdnimg.cn/6/1/9/0_m0_48717371"); // 将数据汇总 List sheetDataList = new ArrayList(); sheetDataList.add(head); sheetDataList.add(user1); sheetDataList.add(user2); // 导出数据 ExcelUtils.export(response,"用户表", sheetDataList); }

代码截图:

 由于是 get 请求,我们直接在浏览器上输入请求地址即可触发下载。

打开下载表格,我们可以看到,表中的数据和我们代码组装的顺序一致。

1.3.2 动态导出(导出图片)

如果你的导出中,需要将对应图片链接直接显示为图片的话,那么,这里也是可以的,只需要将对应的类型转为 java.net.URL 类型即可(注意:转的时候有异常处理,为了方便演示,我这里直接抛出)

测试效果:

1.3.3 动态导出(实现下拉列表)

我们在做一些数据导出的时候,可能要对某一行的下拉数据进行约束限制。

比如,当我们下载一个导入模版的时候,我们可以将性别,城市对应的列设置为下拉选择。

测试效果:

1.3.4 动态导出(横向合并)

比如,我们将表头横向合并,只需要将合并的单元格设置为 ExcelUtils.COLUMN_MERGE 即可。

测试效果:可以看到表头的地址已经被合并了。

1.3.5 动态导出(纵向合并)

除了横向合并,我们还可以进行纵向合并,只需要将合并的单元格设置为 ExcelUtils.ROW_MERGE 即可。

测试效果:

1.3.6 导出模板(基础)

我们在做数据导入的时候,往往首先会提供一个模版供其下载,这样用户在导入的时候才知道如何去填写数据。导出模板除了可以用上面的动态导出,这里还提供了一种更加便捷的写法。只需要创建一个类,然后再对应字段上打上 @ExcelExport 注解类即可。

 Controller 代码:

@GetMapping("/export") public void export(HttpServletResponse response) { ExcelUtils.exportTemplate(response, "用户表", User.class); }

代码截图:

测试效果:

1.3.7 导出模板(附示例数据)

我们在做模版下载时候,有时往往会携带一条样本数据,好提示用户数据格式是什么,那么我们只需要在对应字段上进行配置即可。

Controller代码:

测试效果:

1.3.8 按对象导出(基础)

我们还可以通过 List 对象,对数据直接进行导出。首先,同样需要在对应类的字段上,设置导出名称。

Controller 代码:

测试效果:

1.3.9 按对象导出(数据映射)

在上面 1.3.8 的导出中,我们可以看到,性别数据导出来是1和2,这个不利于用户体验,应该需要转换为对应的中文,我们可以在字段注解上进行对应的配置。

Controller 代码略(和1.3.8完全一致)

测试效果:可以看到1和2显示为了对应的男和女

1.3.10 按对象导出(调整表头顺序)

如果你需要对表头字段进行排序,有两种方式:

第一种:按照表格的顺序,排列Java类中的字段;

第二种:在 @ExcelExport 注解中,指定 sort 属性,其值越少,排名越靠前。 

Controller 代码略(和1.3.8完全一致)

测试效果:可以看到,此时导出数据的表头顺序,和我们指定的顺序完全一致。

1.3.11 多Sheet页导出

Controller 代码(示例):

/** * 导出多 Sheet 页实现 */ @GetMapping("/exportManySheet") public void exportManySheet(HttpServletResponse response) { // 第 1 个 Sheet 页 List sheet1 = new ArrayList(); List sheet1Head = new ArrayList(); sheet1Head.add("姓名"); sheet1Head.add("数学"); sheet1Head.add("英语"); sheet1.add(sheet1Head); List row1 = new ArrayList(); row1.add("Jack"); row1.add(85); row1.add(100); sheet1.add(row1); List row2 = new ArrayList(); row2.add("Marry"); row2.add(85); row2.add(100); sheet1.add(row2); // 第 2 个 Sheet 页 List sheet2 = new ArrayList(); List sheet2Head = new ArrayList(); sheet2Head.add("姓名"); sheet2Head.add("音乐"); sheet2Head.add("美术"); sheet2.add(sheet2Head); List row01 = new ArrayList(); row01.add("Jack"); row01.add(77); row01.add(66); sheet2.add(row01); List row02 = new ArrayList(); row02.add("Marry"); row02.add(99); row02.add(88); sheet2.add(row02); // 将两个 Sheet 页添加到集合中 Map sheets = new LinkedHashMap(); sheets.put("文化课", sheet1); sheets.put("艺术课", sheet2); // 导出数据 ExcelUtils.exportManySheet(response, "学生成绩表", sheets); }

测试效果:

第一个 Sheet 页 

 

 第二个 Sheet 页

2. 环境准备 2.1 Maven 依赖

本次工具类的封装主要依赖于阿里巴巴的JSON包,以及表格处理的POI包,所以我们需要导入这两个库的依赖包,另外,我们还需要文件上传的相关包,毕竟我们在浏览器页面,做Excel导入时,是上传的Excel文件。

org.apache.httpcomponents httpmime 4.5.7 com.alibaba fastjson 1.2.41 org.apache.poi poi-ooxml 3.16 2.2 类文件 ExcelUtils package com.sonar.data.utils.excel; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.apache.poi.hssf.usermodel.HSSFDataValidation; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.RoundingMode; import java.net.URL; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; /** * Excel导入导出工具类 * 原文链接(不定时增加新功能): https://zyqok.blog.csdn.net/article/details/121994504 * * @author sunnyzyq * @date 2021/12/17 */ @SuppressWarnings("unused") public class ExcelUtils { private static final String XLSX = ".xlsx"; private static final String XLS = ".xls"; public static final String ROW_MERGE = "row_merge"; public static final String COLUMN_MERGE = "column_merge"; private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static final String ROW_NUM = "rowNum"; private static final String ROW_DATA = "rowData"; private static final String ROW_TIPS = "rowTips"; private static final int CELL_OTHER = 0; private static final int CELL_ROW_MERGE = 1; private static final int CELL_COLUMN_MERGE = 2; private static final int IMG_HEIGHT = 30; private static final int IMG_WIDTH = 30; private static final char LEAN_LINE = '/'; private static final int BYTES_DEFAULT_LENGTH = 10240; private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(); public static List readFile(File file, Class clazz) throws Exception { JSONArray array = readFile(file); return getBeanList(array, clazz); } public static List readMultipartFile(MultipartFile mFile, Class clazz) throws Exception { JSONArray array = readMultipartFile(mFile); return getBeanList(array, clazz); } public static JSONArray readFile(File file) throws Exception { return readExcel(null, file); } public static JSONArray readMultipartFile(MultipartFile mFile) throws Exception { return readExcel(mFile, null); } public static Map readFileManySheet(File file) throws Exception { return readExcelManySheet(null, file); } public static Map readFileManySheet(MultipartFile file) throws Exception { return readExcelManySheet(file, null); } private static List getBeanList(JSONArray array, Class clazz) throws Exception { List list = new ArrayList(); Map uniqueMap = new HashMap(16); for (int i = 0; i < array.size(); i++) { list.add(getBean(clazz, array.getJSONObject(i), uniqueMap)); } return list; } /** * 获取每个对象的数据 */ private static T getBean(Class c, JSONObject obj, Map uniqueMap) throws Exception { T t = c.newInstance(); Field[] fields = c.getDeclaredFields(); List errMsgList = new ArrayList(); boolean hasRowTipsField = false; StringBuilder uniqueBuilder = new StringBuilder(); int rowNum = 0; for (Field field : fields) { // 行号 if (field.getName().equals(ROW_NUM)) { rowNum = obj.getInteger(ROW_NUM); field.setAccessible(true); field.set(t, rowNum); continue; } // 是否需要设置异常信息 if (field.getName().equals(ROW_TIPS)) { hasRowTipsField = true; continue; } // 原始数据 if (field.getName().equals(ROW_DATA)) { field.setAccessible(true); field.set(t, obj.toString()); continue; } // 设置对应属性值 setFieldValue(t, field, obj, uniqueBuilder, errMsgList); } // 数据唯一性校验 if (uniqueBuilder.length() > 0) { if (uniqueMap.containsValue(uniqueBuilder.toString())) { Set rowNumKeys = uniqueMap.keySet(); for (Integer num : rowNumKeys) { if (uniqueMap.get(num).equals(uniqueBuilder.toString())) { errMsgList.add(String.format("数据唯一性校验失败,(%s)与第%s行重复)", uniqueBuilder, num)); } } } else { uniqueMap.put(rowNum, uniqueBuilder.toString()); } } // 失败处理 if (errMsgList.isEmpty() && !hasRowTipsField) { return t; } StringBuilder sb = new StringBuilder(); int size = errMsgList.size(); for (int i = 0; i < size; i++) { if (i == size - 1) { sb.append(errMsgList.get(i)); } else { sb.append(errMsgList.get(i)).append(";"); } } // 设置错误信息 for (Field field : fields) { if (field.getName().equals(ROW_TIPS)) { field.setAccessible(true); field.set(t, sb.toString()); } } return t; } private static void setFieldValue(T t, Field field, JSONObject obj, StringBuilder uniqueBuilder, List errMsgList) { // 获取 ExcelImport 注解属性 ExcelImport annotation = field.getAnnotation(ExcelImport.class); if (annotation == null) { return; } String cname = annotation.value(); if (cname.trim().length() == 0) { return; } // 获取具体值 String val = null; if (obj.containsKey(cname)) { val = getString(obj.getString(cname)); } if (val == null) { return; } field.setAccessible(true); // 判断是否必填 boolean require = annotation.required(); if (require && val.isEmpty()) { errMsgList.add(String.format("[%s]不能为空", cname)); return; } // 数据唯一性获取 boolean unique = annotation.unique(); if (unique) { if (uniqueBuilder.length() > 0) { uniqueBuilder.append("--").append(val); } else { uniqueBuilder.append(val); } } // 判断是否超过最大长度 int maxLength = annotation.maxLength(); if (maxLength > 0 && val.length() > maxLength) { errMsgList.add(String.format("[%s]长度不能超过%s个字符(当前%s个字符)", cname, maxLength, val.length())); } // 判断当前属性是否有映射关系 LinkedHashMap kvMap = getKvMap(annotation.kv()); if (!kvMap.isEmpty()) { boolean isMatch = false; for (String key : kvMap.keySet()) { if (kvMap.get(key).equals(val)) { val = key; isMatch = true; break; } } if (!isMatch) { errMsgList.add(String.format("[%s]的值不正确(当前值为%s)", cname, val)); return; } } // 其余情况根据类型赋值 String fieldClassName = field.getType().getSimpleName(); try { if ("String".equalsIgnoreCase(fieldClassName)) { field.set(t, val); } else if ("boolean".equalsIgnoreCase(fieldClassName)) { field.set(t, Boolean.valueOf(val)); } else if ("int".equalsIgnoreCase(fieldClassName) || "Integer".equals(fieldClassName)) { try { field.set(t, Integer.valueOf(val)); } catch (NumberFormatException e) { errMsgList.add(String.format("[%s]的值格式不正确(当前值为%s)", cname, val)); } } else if ("double".equalsIgnoreCase(fieldClassName)) { field.set(t, Double.valueOf(val)); } else if ("long".equalsIgnoreCase(fieldClassName)) { field.set(t, Long.valueOf(val)); } else if ("BigDecimal".equalsIgnoreCase(fieldClassName)) { field.set(t, new BigDecimal(val)); } else if ("Date".equalsIgnoreCase(fieldClassName)) { try { field.set(t, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(val)); } catch (Exception e) { field.set(t, new SimpleDateFormat("yyyy-MM-dd").parse(val)); } } } catch (Exception e) { e.printStackTrace(); } } private static Map readExcelManySheet(MultipartFile mFile, File file) throws IOException { Workbook book = getWorkbook(mFile, file); if (book == null) { return Collections.emptyMap(); } Map map = new LinkedHashMap(); for (int i = 0; i < book.getNumberOfSheets(); i++) { Sheet sheet = book.getSheetAt(i); JSONArray arr = readSheet(sheet); map.put(sheet.getSheetName(), arr); } book.close(); return map; } private static JSONArray readExcel(MultipartFile mFile, File file) throws IOException { Workbook book = getWorkbook(mFile, file); if (book == null) { return new JSONArray(); } JSONArray array = readSheet(book.getSheetAt(0)); book.close(); return array; } private static Workbook getWorkbook(MultipartFile mFile, File file) throws IOException { boolean fileNotExist = (file == null || !file.exists()); if (mFile == null && fileNotExist) { return null; } // 解析表格数据 InputStream in; String fileName; if (mFile != null) { // 上传文件解析 in = mFile.getInputStream(); fileName = getString(mFile.getOriginalFilename()).toLowerCase(); } else { // 本地文件解析 in = new FileInputStream(file); fileName = file.getName().toLowerCase(); } Workbook book; if (fileName.endsWith(XLSX)) { book = new XSSFWorkbook(in); } else if (fileName.endsWith(XLS)) { POIFSFileSystem poifsFileSystem = new POIFSFileSystem(in); book = new HSSFWorkbook(poifsFileSystem); } else { return null; } in.close(); return book; } private static JSONArray readSheet(Sheet sheet) { // 首行下标 int rowStart = sheet.getFirstRowNum(); // 尾行下标 int rowEnd = sheet.getLastRowNum(); // 获取表头行 Row headRow = sheet.getRow(rowStart); if (headRow == null) { return new JSONArray(); } int cellStart = headRow.getFirstCellNum(); int cellEnd = headRow.getLastCellNum(); Map keyMap = new HashMap(); for (int j = cellStart; j < cellEnd; j++) { // 获取表头数据 String val = getCellValue(headRow.getCell(j)); if (val != null && val.trim().length() != 0) { keyMap.put(j, val); } } // 如果表头没有数据则不进行解析 if (keyMap.isEmpty()) { return (JSONArray) Collections.emptyList(); } // 获取每行JSON对象的值 JSONArray array = new JSONArray(); // 如果首行与尾行相同,表明只有一行,返回表头数据 if (rowStart == rowEnd) { JSONObject obj = new JSONObject(); // 添加行号 obj.put(ROW_NUM, 1); for (int i : keyMap.keySet()) { obj.put(keyMap.get(i), ""); } array.add(obj); return array; } for (int i = rowStart + 1; i 0) { array.add(obj); } } return array; } private static String getCellValue(Cell cell) { // 空白或空 if (cell == null || cell.getCellTypeEnum() == CellType.BLANK) { return ""; } // String类型 if (cell.getCellTypeEnum() == CellType.STRING) { String val = cell.getStringCellValue(); if (val == null || val.trim().length() == 0) { return ""; } return val.trim(); } // 数字类型 if (cell.getCellTypeEnum() == CellType.NUMERIC) { String s = cell.getNumericCellValue() + ""; // 去掉尾巴上的小数点0 if (Pattern.matches(".*\\.0*", s)) { return s.split("\\.")[0]; } else { return s; } } // 布尔值类型 if (cell.getCellTypeEnum() == CellType.BOOLEAN) { return cell.getBooleanCellValue() + ""; } // 错误类型 return cell.getCellFormula(); } public static void exportTemplate(HttpServletResponse response, String fileName, Class clazz) { exportTemplate(response, fileName, fileName, clazz, false); } public static void exportTemplate(HttpServletResponse response, String fileName, String sheetName, Class clazz) { exportTemplate(response, fileName, sheetName, clazz, false); } public static void exportTemplate(HttpServletResponse response, String fileName, Class clazz, boolean isContainExample) { exportTemplate(response, fileName, fileName, clazz, isContainExample); } public static void exportTemplate(HttpServletResponse response, String fileName, String sheetName, Class clazz, boolean isContainExample) { // 获取表头字段 List headFieldList = getExcelClassFieldList(clazz); // 获取表头数据和示例数据 List sheetDataList = new ArrayList(); List headList = new ArrayList(); List exampleList = new ArrayList(); Map selectMap = new LinkedHashMap(); for (int i = 0; i < headFieldList.size(); i++) { ExcelClassField each = headFieldList.get(i); headList.add(each.getName()); exampleList.add(each.getExample()); LinkedHashMap kvMap = each.getKvMap(); if (kvMap != null && kvMap.size() > 0) { selectMap.put(i, new ArrayList(kvMap.values())); } } sheetDataList.add(headList); if (isContainExample) { sheetDataList.add(exampleList); } // 导出数据 export(response, fileName, sheetName, sheetDataList, selectMap); } private static List getExcelClassFieldList(Class clazz) { // 解析所有字段 Field[] fields = clazz.getDeclaredFields(); boolean hasExportAnnotation = false; Map map = new LinkedHashMap(); List sortList = new ArrayList(); for (Field field : fields) { ExcelClassField cf = getExcelClassField(field); if (cf.getHasAnnotation() == 1) { hasExportAnnotation = true; } int sort = cf.getSort(); if (map.containsKey(sort)) { map.get(sort).add(cf); } else { List list = new ArrayList(); list.add(cf); sortList.add(sort); map.put(sort, list); } } Collections.sort(sortList); // 获取表头 List headFieldList = new ArrayList(); if (hasExportAnnotation) { for (Integer sort : sortList) { for (ExcelClassField cf : map.get(sort)) { if (cf.getHasAnnotation() == 1) { headFieldList.add(cf); } } } } else { headFieldList.addAll(map.get(0)); } return headFieldList; } private static ExcelClassField getExcelClassField(Field field) { ExcelClassField cf = new ExcelClassField(); String fieldName = field.getName(); cf.setFieldName(fieldName); ExcelExport annotation = field.getAnnotation(ExcelExport.class); // 无 ExcelExport 注解情况 if (annotation == null) { cf.setHasAnnotation(0); cf.setName(fieldName); cf.setSort(0); return cf; } // 有 ExcelExport 注解情况 cf.setHasAnnotation(1); cf.setName(annotation.value()); String example = getString(annotation.example()); if (!example.isEmpty()) { if (isNumeric(example) && example.length() < 8) { cf.setExample(Double.valueOf(example)); } else { cf.setExample(example); } } else { cf.setExample(""); } cf.setSort(annotation.sort()); // 解析映射 String kv = getString(annotation.kv()); cf.setKvMap(getKvMap(kv)); return cf; } private static LinkedHashMap getKvMap(String kv) { LinkedHashMap kvMap = new LinkedHashMap(); if (kv.isEmpty()) { return kvMap; } String[] kvs = kv.split(";"); if (kvs.length == 0) { return kvMap; } for (String each : kvs) { String[] eachKv = getString(each).split("-"); if (eachKv.length != 2) { continue; } String k = eachKv[0]; String v = eachKv[1]; if (k.isEmpty() || v.isEmpty()) { continue; } kvMap.put(k, v); } return kvMap; } /** * 导出表格到本地 * * @param file 本地文件对象 * @param sheetData 导出数据 */ public static void exportFile(File file, List sheetData) { if (file == null) { System.out.println("文件创建失败"); return; } if (sheetData == null) { sheetData = new ArrayList(); } Map map = new HashMap(); map.put(file.getName(), sheetData); export(null, file, file.getName(), map, null); } /** * 导出表格到本地 * * @param 导出数据类似,和K类型保持一致 * @param filePath 文件父路径(如:D:/doc/excel/) * @param fileName 文件名称(不带尾缀,如:学生表) * @param list 导出数据 * @throws IOException IO异常 */ public static File exportFile(String filePath, String fileName, List list) throws IOException { File file = getFile(filePath, fileName); List sheetData = getSheetData(list); exportFile(file, sheetData); return file; } /** * 获取文件 * * @param filePath filePath 文件父路径(如:D:/doc/excel/) * @param fileName 文件名称(不带尾缀,如:用户表) * @return 本地File文件对象 */ private static File getFile(String filePath, String fileName) throws IOException { String dirPath = getString(filePath); String fileFullPath; if (dirPath.isEmpty()) { fileFullPath = fileName; } else { // 判定文件夹是否存在,如果不存在,则级联创建 File dirFile = new File(dirPath); if (!dirFile.exists()) { boolean mkdirs = dirFile.mkdirs(); if (!mkdirs) { return null; } } // 获取文件夹全名 if (dirPath.endsWith(String.valueOf(LEAN_LINE))) { fileFullPath = dirPath + fileName + XLSX; } else { fileFullPath = dirPath + LEAN_LINE + fileName + XLSX; } } System.out.println(fileFullPath); File file = new File(fileFullPath); if (!file.exists()) { boolean result = file.createNewFile(); if (!result) { return null; } } return file; } private static List getSheetData(List list) { // 获取表头字段 List excelClassFieldList = getExcelClassFieldList(list.get(0).getClass()); List headFieldList = new ArrayList(); List headList = new ArrayList(); Map headFieldMap = new HashMap(); for (ExcelClassField each : excelClassFieldList) { String fieldName = each.getFieldName(); headFieldList.add(fieldName); headFieldMap.put(fieldName, each); headList.add(each.getName()); } // 添加表头名称 List sheetDataList = new ArrayList(); sheetDataList.add(headList); // 获取表数据 for (T t : list) { Map fieldDataMap = getFieldDataMap(t); Set fieldDataKeys = fieldDataMap.keySet(); List rowList = new ArrayList(); for (String headField : headFieldList) { if (!fieldDataKeys.contains(headField)) { continue; } Object data = fieldDataMap.get(headField); if (data == null) { rowList.add(""); continue; } ExcelClassField cf = headFieldMap.get(headField); // 判断是否有映射关系 LinkedHashMap kvMap = cf.getKvMap(); if (kvMap == null || kvMap.isEmpty()) { rowList.add(data); continue; } String val = kvMap.get(data.toString()); if (isNumeric(val)) { rowList.add(Double.valueOf(val)); } else { rowList.add(val); } } sheetDataList.add(rowList); } return sheetDataList; } private static Map getFieldDataMap(T t) { Map map = new HashMap(); Field[] fields = t.getClass().getDeclaredFields(); try { for (Field field : fields) { String fieldName = field.getName(); field.setAccessible(true); Object object = field.get(t); map.put(fieldName, object); } } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); } return map; } public static void exportEmpty(HttpServletResponse response, String fileName) { List sheetDataList = new ArrayList(); List headList = new ArrayList(); headList.add("导出无数据"); sheetDataList.add(headList); export(response, fileName, sheetDataList); } public static void export(HttpServletResponse response, String fileName, List sheetDataList) { export(response, fileName, fileName, sheetDataList, null); } public static void exportManySheet(HttpServletResponse response, String fileName, Map sheetMap) { export(response, null, fileName, sheetMap, null); } public static void export(HttpServletResponse response, String fileName, String sheetName, List sheetDataList) { export(response, fileName, sheetName, sheetDataList, null); } public static void export(HttpServletResponse response, String fileName, String sheetName, List sheetDataList, Map selectMap) { Map map = new HashMap(); map.put(sheetName, sheetDataList); export(response, null, fileName, map, selectMap); } public static void export(HttpServletResponse response, String fileName, List list, Class template) { // list 是否为空 boolean lisIsEmpty = list == null || list.isEmpty(); // 如果模板数据为空,且导入的数据为空,则导出空文件 if (template == null && lisIsEmpty) { exportEmpty(response, fileName); return; } // 如果 list 数据,则导出模板数据 if (lisIsEmpty) { exportTemplate(response, fileName, template); return; } // 导出数据 List sheetDataList = getSheetData(list); export(response, fileName, sheetDataList); } public static void export(HttpServletResponse response, String fileName, List sheetDataList, Map selectMap) { export(response, fileName, fileName, sheetDataList, selectMap); } private static void export(HttpServletResponse response, File file, String fileName, Map sheetMap, Map selectMap) { // 整个 Excel 表格 book 对象 SXSSFWorkbook book = new SXSSFWorkbook(); // 每个 Sheet 页 Set entries = sheetMap.entrySet(); for (Entry entry : entries) { List sheetDataList = entry.getValue(); Sheet sheet = book.createSheet(entry.getKey()); Drawing patriarch = sheet.createDrawingPatriarch(); // 设置表头背景色(灰色) CellStyle headStyle = book.createCellStyle(); headStyle.setFillForegroundColor(IndexedColors.GREY_80_PERCENT.index); headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); headStyle.setAlignment(HorizontalAlignment.CENTER); headStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.index); // 设置表身背景色(默认色) CellStyle rowStyle = book.createCellStyle(); rowStyle.setAlignment(HorizontalAlignment.CENTER); rowStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 设置表格列宽度(默认为15个字节) sheet.setDefaultColumnWidth(15); // 创建合并算法数组 int rowLength = sheetDataList.size(); int columnLength = sheetDataList.get(0).size(); int[][] mergeArray = new int[rowLength][columnLength]; for (int i = 0; i < sheetDataList.size(); i++) { // 每个 Sheet 页中的行数据 Row row = sheet.createRow(i); List rowList = sheetDataList.get(i); for (int j = 0; j < rowList.size(); j++) { // 每个行数据中的单元格数据 Object o = rowList.get(j); int v = 0; if (o instanceof URL) { // 如果要导出图片的话, 链接需要传递 URL 对象 setCellPicture(book, row, patriarch, i, j, (URL) o); } else { Cell cell = row.createCell(j); if (i == 0) { // 第一行为表头行,采用灰色底背景 v = setCellValue(cell, o, headStyle); } else { // 其他行为数据行,默认白底色 v = setCellValue(cell, o, rowStyle); } } mergeArray[i][j] = v; } } // 合并单元格 mergeCells(sheet, mergeArray); // 设置下拉列表 setSelect(sheet, selectMap); } // 写数据 if (response != null) { // 前端导出 try { write(response, book, fileName); } catch (IOException e) { e.printStackTrace(); } } else { // 本地导出 FileOutputStream fos; try { fos = new FileOutputStream(file); ByteArrayOutputStream ops = new ByteArrayOutputStream(); book.write(ops); fos.write(ops.toByteArray()); fos.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * 合并当前Sheet页的单元格 * * @param sheet 当前 sheet 页 * @param mergeArray 合并单元格算法 */ private static void mergeCells(Sheet sheet, int[][] mergeArray) { // 横向合并 for (int x = 0; x < mergeArray.length; x++) { int[] arr = mergeArray[x]; boolean merge = false; int y1 = 0; int y2 = 0; for (int y = 0; y < arr.length; y++) { int value = arr[y]; if (value == CELL_COLUMN_MERGE) { if (!merge) { y1 = y; } y2 = y; merge = true; } else { merge = false; if (y1 > 0) { sheet.addMergedRegion(new CellRangeAddress(x, x, (y1 - 1), y2)); } y1 = 0; y2 = 0; } } if (y1 > 0) { sheet.addMergedRegion(new CellRangeAddress(x, x, (y1 - 1), y2)); } } // 纵向合并 int xLen = mergeArray.length; int yLen = mergeArray[0].length; for (int y = 0; y < yLen; y++) { boolean merge = false; int x1 = 0; int x2 = 0; for (int x = 0; x < xLen; x++) { int value = mergeArray[x][y]; if (value == CELL_ROW_MERGE) { if (!merge) { x1 = x; } x2 = x; merge = true; } else { merge = false; if (x1 > 0) { sheet.addMergedRegion(new CellRangeAddress((x1 - 1), x2, y, y)); } x1 = 0; x2 = 0; } } if (x1 > 0) { sheet.addMergedRegion(new CellRangeAddress((x1 - 1), x2, y, y)); } } } private static void write(HttpServletResponse response, SXSSFWorkbook book, String fileName) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String name = new String(fileName.getBytes("GBK"), "ISO8859_1") + XLSX; response.addHeader("Content-Disposition", "attachment;filename=" + name); ServletOutputStream out = response.getOutputStream(); book.write(out); out.flush(); out.close(); } private static int setCellValue(Cell cell, Object o, CellStyle style) { // 设置样式 cell.setCellStyle(style); // 数据为空时 if (o == null) { cell.setCellType(CellType.STRING); cell.setCellValue(""); return CELL_OTHER; } // 是否为字符串 if (o instanceof String) { String s = o.toString(); // 当数字类型长度超过8位时,改为字符串类型显示(Excel数字超过一定长度会显示为科学计数法) if (isNumeric(s) && s.length() < 8) { cell.setCellType(CellType.NUMERIC); cell.setCellValue(Double.parseDouble(s)); return CELL_OTHER; } else { cell.setCellType(CellType.STRING); cell.setCellValue(s); } if (s.equals(ROW_MERGE)) { return CELL_ROW_MERGE; } else if (s.equals(COLUMN_MERGE)) { return CELL_COLUMN_MERGE; } else { return CELL_OTHER; } } // 是否为字符串 if (o instanceof Integer || o instanceof Long || o instanceof Double || o instanceof Float) { cell.setCellType(CellType.NUMERIC); cell.setCellValue(Double.parseDouble(o.toString())); return CELL_OTHER; } // 是否为Boolean if (o instanceof Boolean) { cell.setCellType(CellType.BOOLEAN); cell.setCellValue((Boolean) o); return CELL_OTHER; } // 如果是BigDecimal,则默认3位小数 if (o instanceof BigDecimal) { cell.setCellType(CellType.NUMERIC); cell.setCellValue(((BigDecimal) o).setScale(3, RoundingMode.HALF_UP).doubleValue()); return CELL_OTHER; } // 如果是Date数据,则显示格式化数据 if (o instanceof Date) { cell.setCellType(CellType.STRING); cell.setCellValue(formatDate((Date) o)); return CELL_OTHER; } // 如果是其他,则默认字符串类型 cell.setCellType(CellType.STRING); cell.setCellValue(o.toString()); return CELL_OTHER; } private static void setCellPicture(SXSSFWorkbook wb, Row sr, Drawing patriarch, int x, int y, URL url) { // 设置图片宽高 sr.setHeight((short) (IMG_WIDTH * IMG_HEIGHT)); // (jdk1.7版本try中定义流可自动关闭) try (InputStream is = url.openStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { byte[] buff = new byte[BYTES_DEFAULT_LENGTH]; int rc; while ((rc = is.read(buff, 0, BYTES_DEFAULT_LENGTH)) > 0) { outputStream.write(buff, 0, rc); } // 设置图片位置 XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, y, x, y + 1, x + 1); // 设置这个,图片会自动填满单元格的长宽 anchor.setAnchorType(AnchorType.MOVE_AND_RESIZE); patriarch.createPicture(anchor, wb.addPicture(outputStream.toByteArray(), HSSFWorkbook.PICTURE_TYPE_JPEG)); } catch (Exception e) { e.printStackTrace(); } } private static String formatDate(Date date) { if (date == null) { return ""; } SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); return format.format(date); } private static void setSelect(Sheet sheet, Map selectMap) { if (selectMap == null || selectMap.isEmpty()) { return; } Set entrySet = selectMap.entrySet(); for (Entry entry : entrySet) { int y = entry.getKey(); List list = entry.getValue(); if (list == null || list.isEmpty()) { continue; } String[] arr = new String[list.size()]; for (int i = 0; i < list.size(); i++) { arr[i] = list.get(i); } DataValidationHelper helper = sheet.getDataValidationHelper(); CellRangeAddressList addressList = new CellRangeAddressList(1, 65000, y, y); DataValidationConstraint dvc = helper.createExplicitListConstraint(arr); DataValidation dv = helper.createValidation(dvc, addressList); if (dv instanceof HSSFDataValidation) { dv.setSuppressDropDownArrow(false); } else { dv.setSuppressDropDownArrow(true); dv.setShowErrorBox(true); } sheet.addValidationData(dv); } } private static boolean isNumeric(String str) { if (Objects.nonNull(str) && "0.0".equals(str)) { return true; } for (int i = str.length(); --i >= 0; ) { if (!Character.isDigit(str.charAt(i))) { return false; } } return true; } private static String getString(String s) { if (s == null) { return ""; } if (s.isEmpty()) { return s; } return s.trim(); } } ExcelImport package com.zyq.util.excel; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author sunnyzyq * @date 2021/12/17 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ExcelImport { /** 字段名称 */ String value(); /** 导出映射,格式如:0-未知;1-男;2-女 */ String kv() default ""; /** 是否为必填字段(默认为非必填) */ boolean required() default false; /** 最大长度(默认255) */ int maxLength() default 255; /** 导入唯一性验证(多个字段则取联合验证) */ boolean unique() default false; } ExcelExport package com.zyq.util.excel; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author sunnyzyq * @date 2021/12/17 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ExcelExport { /** 字段名称 */ String value(); /** 导出排序先后: 数字越小越靠前(默认按Java类字段顺序导出) */ int sort() default 0; /** 导出映射,格式如:0-未知;1-男;2-女 */ String kv() default ""; /** 导出模板示例值(有值的话,直接取该值,不做映射) */ String example() default ""; } ExcelClassField package com.zyq.util.excel; import java.util.LinkedHashMap; /** * @author sunnyzyq * @date 2021/12/17 */ public class ExcelClassField { /** 字段名称 */ private String fieldName; /** 表头名称 */ private String name; /** 映射关系 */ private LinkedHashMap kvMap; /** 示例值 */ private Object example; /** 排序 */ private int sort; /** 是否为注解字段:0-否,1-是 */ private int hasAnnotation; public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getName() { return name; } public void setName(String name) { this.name = name; } public LinkedHashMap getKvMap() { return kvMap; } public void setKvMap(LinkedHashMap kvMap) { this.kvMap = kvMap; } public Object getExample() { return example; } public void setExample(Object example) { this.example = example; } public int getSort() { return sort; } public void setSort(int sort) { this.sort = sort; } public int getHasAnnotation() { return hasAnnotation; } public void setHasAnnotation(int hasAnnotation) { this.hasAnnotation = hasAnnotation; } } 3. 更新优化 3.1 优化身份证导出显示为科学计数法的问题(2022-07-04)

优化前:身份证号码显示为科学计数法。

优化后:身份证号码正常显示。

4. 疑问解答 4.1 关于身份证导入显示科学计数法的问题(2022-07-04)

首先,当我们把身份证输入到表格中的时候,表格会默认为数字类型,由于数字太大,这时候Excel 会自动显示为科学计数法。

那么这个时候,我们进行导入的话,确实会显示为科学计数法,如下图所示:

那么,如果要解决这个问题,需要从根源解决掉,即让 Excel 表格不能显示为科学计数法。 

这个时候,我们需要将该列设置为文本格式:

然后设置为文本格式。

 当设置完成后,你再重新输入身份证后,就会原封不动的显示了。

这个时候,你再导入就会完全显示了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3