前言

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

1. 功能测试

1.1 测试准备

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

链接:http://ljh520.vip/%E8%BD%AF%E4%BB%B6/excelUtil.zip

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 <= rowEnd; i++) {

Row eachRow = sheet.getRow(i);

JSONObject obj = new JSONObject();

// 添加行号

obj.put(ROW_NUM, i + 1);

StringBuilder sb = new StringBuilder();

for (int k = cellStart; k < cellEnd; k++) {

if (eachRow != null) {

String val = getCellValue(eachRow.getCell(k));

// 所有数据添加到里面,用于判断该行是否为空

sb.append(val);

obj.put(keyMap.get(k), val);

}

}

if (sb.length() > 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 © 2088 国风网游活动大全 - 古韵新活动每日更新 All Rights Reserved.
友情链接