声明,本文基于JAVA操作Excel(POI、easyPOI、easyExcel_我认不到你的博客-CSDN博客讲解,需要基础的可以看这篇,特别是@Excel注解
本篇最后有工具类和自定义的注解类,只需要傻瓜式复制粘贴应该就可以用
而且我感觉使用easyPOI做多Sheet导出没有easyExcel好用,但是easyPOI对于一对多数据的导出要比easyExcel友好
各有各的好处,目前就easyPOI导出多Sheet讲解,本篇用到的知识:easyPOI、反射、自定义注解
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-spring-boot-starterartifactId>
<version>4.4.0version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>4.1.2version>
dependency>
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SheetName {
/**
* @return SheetName
*/
String name();
}
ExportEmployeeTemplateVo.java
用于导出全部的信息,这个类是我随便定义的,主要是把每一个Sheet
的数据和@SheetName
放在一个对象里面@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
// 类名随意,主要需要下面的多个 对象 的数据
// Excel为从上到下导出,比如把 公司和部门信息 和 员工信息 换个位置 公司和部门信息 就是第一页了
public class ExportEmployeeTemplateVo {
// 这个第一页(Sheet)是模板所以没有放数据,到时候存个对象进去就可以了
@SheetName(name = "员工信息")
private EmployeesVo employeesVo;
// 这个第二页(Sheet)这个是需要导出的list数据
@SheetName(name = "公司和部门信息")
private List<CompanyAndDepartmentInfoVo> companyAndDepartmentInfoVos;
// 可以一直往下添加
//@SheetName(name = "随意")
//private List<随意> 随意;
}
EmployeesVo.java
这里面放的就是@Excel
用于导出,这里只展示一个,其他的@Excel都删了,代码多了没什么必要@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class EmployeesVo {
@Excel(name="*企业ID", width = 30,needMerge = true,orderNum = "3")
private String company_id;
}
CompanyAndDepartmentInfoVo.java
这里面放的就是@Excel
用于导出,这里只展示一个,其他的@Excel都删了,代码多了没什么必要@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class CompanyAndDepartmentInfoVo {
@Excel(name="id", width = 30,needMerge = true)
private String id;
}
四、controller
看 /**
* 多Sheet导出,不需要批注
* @param fileName 文件名
* @param exportDataSet 导出的类对象
* @param response
* @throws IllegalAccessException
* @throws IOException
*/
public static <T> void exportMultiSheetWorkbook(String fileName,T exportDataSet, HttpServletResponse response) throws IOException, IllegalAccessException {
exportMultiSheetWorkbook(fileName,exportDataSet,response,null);
}
/**
* 多Sheet导出,需要批注
* @param fileName 文件名
* @param exportDataSet 导出的类对象
* @param response
* @throws IllegalAccessException
* @throws IOException
*/
public static <T> void exportMultiSheetWorkbook(String fileName,
T exportDataSet,
HttpServletResponse response,
String remake)
throws IllegalAccessException, IOException {
/* =========================== 初始准备工作 =========================== */
// 多个sheet配置参数
final List<Map<String, Object>> sheetsList = Lists.newArrayList();
// 这里就是需要上述的ExportEmployeeTemplateVo类的class对象
Class<?> aClass = exportDataSet.getClass();
// 获取到全部属性
Field[] declaredFields = aClass.getDeclaredFields();
// 循环拿到的数据
for (Field field : declaredFields) {
// 可以拿到private的属性
field.setAccessible(true);
// 获取属性上面的SheetName注解(就是自己定义的)
SheetName annotation = field.getAnnotation(SheetName.class);
// 获取自定义注解的值,就是Sheet的名字
final String sheetName = annotation.name();
// 这个map中需要三个值 (title、entity、data),因为easyPOI的源码中需要这三个参数来遍历
Map<String, Object> exportMap = Maps.newHashMap();
// 设置sheet名字
final ExportParams exportParams = new ExportParams(null, sheetName);
// 以下3个参数为easyPOI的源码中写死的参数名 分别是sheet配置/导出类(注解定义)/数据集
exportMap.put("title", exportParams);
// 获取list>中?的泛型
if (field.getType().isAssignableFrom(List.class)){
// 如果是List类型,得到其Generic的类型
Type fc = field.getGenericType();
if(fc instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType) fc;
// 得到泛型里的class类型对象。
Class genericClazz = (Class)pt.getActualTypeArguments()[0];
// 把泛型中的class对象塞进map中
exportMap.put("entity", genericClazz);
}
// 这里面就是数据
exportMap.put("data", field.get(exportDataSet));
}else {
// 如果不是list数据说明是模板
exportMap.put("entity", field.getType());
// 虽然模板没有数据,但是easypoi源码中需要data类型为集合(Collection)所以需要装一下
// 又因为Collections.singletonList生成的集合对象,是一个内部类,需要用ArrayList装一下
exportMap.put("data", new ArrayList<>(Collections.singletonList(field.get(exportDataSet))));
}
// 加入多sheet配置列表
sheetsList.add(exportMap);
}
/* =========================== 导出文件 =========================== */
// 核心方法:导出含多个sheet的excel文件 【HSSF导出的对应.xls文件,XSSF导出对应.xlsx文件,其实这两种都可以导出,但是导入的时候.xlsx文件会报错,暂时我没找出问题所在,所以就用的HSSF导出.xls文件】
final Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
// 批注不为空的话,就写批注
if(StringUtils.isNotBlank(remake)){
// 添加批注格式 :0#姓名不能为空__1#学生性别 1:男 2:女__2#出生日期:yyyy-MM-dd__3#图片不能为空
// 说明 #前面数字是第几列,后面为批注的内容,__为隔断各个列的内容
buildComment(workbook,0,remake);
}
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
// 就像核心方法所示,导出.xlsx文件后,导入会报错,所以我就用写死.xls文件了,fileName,对应文件名
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName + ".xls", "UTF-8"));
workbook.write(response.getOutputStream());
// 写完数据关闭流
workbook.close();
}
/**
* 生成批注
*/
public static void buildComment(Workbook workbook, int titleRowsIndex, String commentStr) {
Sheet sheet = workbook.getSheetAt(0);
//创建一个图画工具
Drawing<?> drawing = sheet.createDrawingPatriarch();
Row row = sheet.getRow(titleRowsIndex);
if (StringUtils.isNotBlank(commentStr)) {
//解析批注,并传换成map
Map<Integer, String> commentMap = getCommentMap(commentStr);
for (Map.Entry<Integer, String> entry : commentMap.entrySet()) {
Cell cell = row.getCell(entry.getKey());
//创建批注
Comment comment = drawing.createCellComment(newClientAnchor(workbook));
//输入批注信息
comment.setString(newRichTextString(workbook, entry.getValue()));
//将批注添加到单元格对象中
cell.setCellComment(comment);
if (entry.getValue().contains("必填项")){
// 设置单元格背景颜色
CellStyle cellStyle = workbook.createCellStyle();
// 通过workbook获得文字处理类
Font font = workbook.createFont();
// 设为红色字体
font.setColor(Font.COLOR_RED);
// 水平居中
cellStyle.setAlignment(HorizontalAlignment.CENTER);
// 垂直居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
cellStyle.setFont(font);
cell.setCellStyle(cellStyle);
}
}
}
}
/**
* 解析批注信息的
* 批注信息,默认解析:批注#列索引,比如用户名不允许重复#0。可覆盖此方法,解析自定义的批注格式
*
* @param commentStr 当前行的所有批注信息
* @return key:列索引,value:对应列的所有批注信息
*/
protected static Map<Integer, String> getCommentMap(String commentStr) {
//每行的所有单元格的批注都在commentStr里,并用”__”分隔
String[] split = commentStr.split("__");
Map<Integer, String> commentMap = new HashMap<>();
for (String msg : split) {
String[] cellMsg = msg.split("#");
//如果当前列没有批注,会将该列的索引作为key存到map里;已有批注,以“,“分隔继续拼接
int cellIndex = Integer.parseInt(cellMsg[0]);
if (commentMap.get(cellIndex) == null) {
commentMap.put(cellIndex, cellMsg[1]);
} else {
commentMap.replace(cellIndex, commentMap.get(cellIndex) + "," + cellMsg[1]);
}
}
return commentMap;
}
/**
* 对应两种不同文件格式生成不同类但相同大小的框框
*/
private static ClientAnchor newClientAnchor(Workbook workbook) {
//xls
if (workbook instanceof HSSFWorkbook) {
return new HSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
}
//xlsx
else {
return new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
}
}
/**
* 对应两种不同文件格式的字符串类型
*/
private static RichTextString newRichTextString(Workbook workbook, String msg) {
//xls
if (workbook instanceof HSSFWorkbook) {
return new HSSFRichTextString(msg);
}
//xlsx
else {
return new XSSFRichTextString(msg);
}
}
/**
* 导出
*/
@RequestMapping("/?????随意随意")
public void exportEmployeeTemplate(String id, HttpServletResponse response){
try {
log.info("exportEmployeeTemplate 入参:id = {}",id);
// 这里数获取数据
List<CompanyAndDepartmentInfoVo> companyAndDepartmentInfoVos = ecpEmployeeManagementService.exportEmployeeTemplate(id);
// 这里是往ExportEmployeeTemplateVo对象中塞入 每个字段对应的数据
ExportEmployeeTemplateVo exportEmployeeTemplateVo = new ExportEmployeeTemplateVo()
.setCompanyAndDepartmentInfoVos(companyAndDepartmentInfoVos)
//EmployeesVo 为导出模板没有值
.setEmployeesVo(new EmployeesVo());
// 添加批注格式 :0#姓名不能为空__1#学生性别 1:男 2:女__2#出生日期:yyyy-MM-dd__3#图片不能为空
// 说明 #前面数字是第几列,后面为批注的内容,__为隔断各个列的内容
String remake = "0#此为必填项__1#此为必填项__2#此为必填项,具体值请参照第二页的企业ID__3#此为必填项,具体值请参照第二页的部门ID__4#此为必填项__6#在职、离职和退休三个状态__8#请填写中国或其他__10#有身份证、护照和军官证三种类型__12#格式为yyyy-MM-dd HH:mm:ss__13#格式为yyyy-MM-dd HH:mm:ss";
// 导出 Excel 为 员工管理.xls
EasyPoiUtil.exportMultiSheetWorkbook("员工管理.xls",exportEmployeeTemplateVo,response,remake);
} catch (Exception e) {
log.error("exportEmployeeTemplate 导出失败:",e);
}
}
/**
* 导入
*/
@RequestMapping("/随意随意")
public void importEmployeeTemplate(MultipartFile file){
try {
List<EmployeesVo> employeesVos = EasyPoiUtil.importExcel(file, 0, 1, EmployeesVo.class);
log.debug("employeesVos = {}",employeesVos);
} catch (Exception e) {
log.error("importEmployeeTemplate 导入失败:",e);
}
}
/**
* @author ZHAOPINGAN
* @Title: EasyPoiUtil
* @ProjectName
* @Description:
*/
public class EasyPoiUtil {
/**
* 导出excel
*
* @param list 数据list
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass 实体的class
* @param fileName 文件名称
* @param isCreateHeader 是否创建头
* @param response 响应
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) {
ExportParams exportParams = new ExportParams(title, sheetName);
exportParams.setCreateHeadRows(isCreateHeader);
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* 导出excel
*
* @param list 数据list
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass 实体class
* @param fileName 文件名
* @param response 响应
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=GBK");
try {
response.setHeader("content-disposition",
"attachment;filename=" + java.net.URLEncoder.encode(fileName, "GBK")
+ ";filename*=GBK''" + java.net.URLEncoder.encode(fileName, "GBK"));
} catch (UnsupportedEncodingException e) {
// ...
}
defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName));
}
/**
* 导出excel
*
* @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data
* @param fileName 标题
* @param response 响应
*/
public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
defaultExport(list, fileName, response);
}
/**
* 导出多个excel
*
* @param workbooks 多个excel文件 通过ExcelExportUtil.exportExcel往workbooks内放入excel
* @param fileNames 文件名 每个excel文件的名称顺序必须一致且名称请务必保证不重复
* @param fileName 压缩包文件名
* @param response 标题
*/
public static void exportExcels(List<Workbook> workbooks, List<String> fileNames, String fileName, HttpServletResponse response) {
try {
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".zip");
OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
ZipOutputStream zipOut = new ZipOutputStream(toClient);
for (int i = 0; i < workbooks.size(); i++) {
ZipEntry entry = new ZipEntry(fileNames.get(i) + ".xls");
zipOut.putNextEntry(entry);
workbooks.get(i).write(zipOut);
}
zipOut.flush();
zipOut.close();
} catch (IOException e) {
throw new ExcelExportException(e.getMessage());
}
}
/**
* 导出excel
*
* @param list 数据list
* @param pojoClass 实体class
* @param fileName 文件名
* @param response 响应
*/
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) {
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
downLoadExcel(fileName, response, workbook);
}
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
workbook.write(response.getOutputStream());
workbook.close();
} catch (IOException e) {
throw new ExcelImportException(e.getMessage());
}
}
private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
downLoadExcel(fileName, response, workbook);
}
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (StringUtils.isBlank(filePath)) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
} catch (NoSuchElementException e) {
throw new ExcelImportException("模板不能为空");
} catch (Exception e) {
e.printStackTrace();
throw new ExcelImportException(e.getMessage());
}
return list;
}
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (file == null) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);//标题占几行,从哪行开始读取
params.setHeadRows(headerRows);//header占几行
params.setSheetNum(1);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
} catch (NoSuchElementException e) {
throw new ExcelImportException("excel文件不能为空");
} catch (Exception e) {
throw new ExcelImportException(e.getMessage());
}
return list;
}
/**
* 多Sheet导出,不需要批注
* @param fileName 文件名
* @param exportDataSet 导出的类对象
* @param response
* @throws IllegalAccessException
* @throws IOException
*/
public static <T> void exportMultiSheetWorkbook(String fileName,T exportDataSet, HttpServletResponse response) throws IOException, IllegalAccessException {
exportMultiSheetWorkbook(fileName,exportDataSet,response,null);
}
/**
* 多Sheet导出,需要批注
* @param fileName 文件名
* @param exportDataSet 导出的类对象
* @param response
* @throws IllegalAccessException
* @throws IOException
*/
public static <T> void exportMultiSheetWorkbook(String fileName,T exportDataSet, HttpServletResponse response,String remake) throws IllegalAccessException, IOException {
// 多个sheet配置参数
final List<Map<String, Object>> sheetsList = Lists.newArrayList();
Class<?> aClass = exportDataSet.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
SheetName annotation = field.getAnnotation(SheetName.class);
final String sheetName = annotation.name();
Map<String, Object> exportMap = Maps.newHashMap();
final ExportParams exportParams = new ExportParams(null, sheetName);
// 以下3个参数为API中写死的参数名 分别是sheet配置/导出类(注解定义)/数据集
exportMap.put("title", exportParams);
// 获取list>中?的泛型
if (field.getType().isAssignableFrom(List.class)){
Type fc = field.getGenericType(); //如果是List类型,得到其Generic的类型
if(fc instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType) fc;
//得到泛型里的class类型对象。
Class genericClazz = (Class)pt.getActualTypeArguments()[0];
exportMap.put("entity", genericClazz);
}
exportMap.put("data", field.get(exportDataSet));
}else {
exportMap.put("entity", field.getType());
exportMap.put("data", new ArrayList<>(Collections.singletonList(field.get(exportDataSet))));
}
// 加入多sheet配置列表
sheetsList.add(exportMap);
}
// 导出文件
// 核心方法:导出含多个sheet的excel文件 【注意,该方法第二个参数必须与上述的ExportParams对象指定的导出类型一致,默认ExcelType.HSSF格式,否则执行此方法时会报错!!!】
final Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
// 添加批注格式 :0#姓名不能为空__1#学生性别 1:男 2:女__2#出生日期:yyyy-MM-dd__3#图片不能为空
if(StringUtils.isNotBlank(remake)){
buildComment(workbook,0,remake);
}
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName + ".xls", "UTF-8"));
workbook.write(response.getOutputStream());
// 写完数据关闭流
workbook.close();
}
public static void buildComment(Workbook workbook, int titleRowsIndex, String commentStr) {
Sheet sheet = workbook.getSheetAt(0);
//创建一个图画工具
Drawing<?> drawing = sheet.createDrawingPatriarch();
Row row = sheet.getRow(titleRowsIndex);
if (StringUtils.isNotBlank(commentStr)) {
//解析批注,并传换成map
Map<Integer, String> commentMap = getCommentMap(commentStr);
for (Map.Entry<Integer, String> entry : commentMap.entrySet()) {
Cell cell = row.getCell(entry.getKey());
//创建批注
Comment comment = drawing.createCellComment(newClientAnchor(workbook));
//输入批注信息
comment.setString(newRichTextString(workbook, entry.getValue()));
//将批注添加到单元格对象中
cell.setCellComment(comment);
if (entry.getValue().contains("必填项")){
//设置单元格背景颜色
CellStyle cellStyle = workbook.createCellStyle();
//通过workbook获得文字处理类
Font font = workbook.createFont();
font.setColor(Font.COLOR_RED);
//水平居中
cellStyle.setAlignment(HorizontalAlignment.CENTER);
//垂直居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
cellStyle.setFont(font);
cell.setCellStyle(cellStyle);
}
}
}
}
/**
* 批注信息,默认解析:批注#列索引,比如用户名不允许重复#0。可覆盖此方法,解析自定义的批注格式
*
* @param commentStr 当前行的所有批注信息
* @return key:列索引,value:对应列的所有批注信息
*/
protected static Map<Integer, String> getCommentMap(String commentStr) {
//每行的所有单元格的批注都在commentStr里,并用”__”分隔
String[] split = commentStr.split("__");
Map<Integer, String> commentMap = new HashMap<>();
for (String msg : split) {
String[] cellMsg = msg.split("#");
//如果当前列没有批注,会将该列的索引作为key存到map里;已有批注,以“,“分隔继续拼接
int cellIndex = Integer.parseInt(cellMsg[0]);
if (commentMap.get(cellIndex) == null) {
commentMap.put(cellIndex, cellMsg[1]);
} else {
commentMap.replace(cellIndex, commentMap.get(cellIndex) + "," + cellMsg[1]);
}
}
return commentMap;
}
private static ClientAnchor newClientAnchor(Workbook workbook) {
//xls
if (workbook instanceof HSSFWorkbook) {
return new HSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
}
//xlsx
else {
return new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
}
}
private static RichTextString newRichTextString(Workbook workbook, String msg) {
//xls
if (workbook instanceof HSSFWorkbook) {
return new HSSFRichTextString(msg);
}
//xlsx
else {
return new XSSFRichTextString(msg);
}
}
}
Body中放文件
Headers中写格式 Content-Type
|| multipart/form-data
,好像这个可有可无