本篇文章内容是针对于接口开发~~
背景
老项目主要采用的POI框架来进行Excel数据的导入和导出,但经常会出现OOM的情况,导致整个服务不可用。后续逐步转移到EasyExcel。
EasyExcel是阿里巴巴开源poi插件之一,主要解决了poi框架使用复杂,sax解析模式不容易操作,数据量大起来容易OOM,解决了POI并发造成的报错。
主要解决方式:通过解压文件的方式加载,一行一行的加载,并且抛弃样式字体等不重要的数据,降低内存的占用。
EasyExcel优势:
其他详细介绍参考官网
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>ooxml-schemasartifactId>
<version>1.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>2.2.11version>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.4.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
其他所需依赖自行添加
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Member {
/**
* EasyExcel使用:导出时忽略该字段
*/
@ExcelIgnore
private Integer id;
@ExcelProperty("用户名")
@ColumnWidth(20)
private String username;
/**
* EasyExcel使用:日期的格式化
*/
@ColumnWidth(20)
@ExcelProperty("出生日期")
@DateTimeFormat("yyyy-MM-dd")
private Date birthday;
/**
* EasyExcel使用:自定义转换器
*/
@ColumnWidth(10)
@ExcelProperty(value = "性别", converter = GenderConverter.class)
private Integer gender;
}
常用注解:
@ExcelProperty(index = 0, value = “姓名”) :
- 用于设置Excel表头,其中index用户表头的编号,从0开始;value为表头对应的内容。
@ExcelProperty(value = “性别”, converter = GenderConverter.class)
- 自定义转换器
@DateTimeFormat(“yyyy-MM-dd”)
- 用于日期的格式化。
@ContentStyle(dataFormat = 2)
- 保留两位小数
排除指定列的三种方式:
- 方式1:在类上添加 @ExcelIgnoreUnannotated
- 方式2:指定字段加@ExcelIgnore注解
- 方式3:EasyExcel.write(fileName, UserData.class).sheet(“学生信息表”)
.excludeColumnFiledNames(Arrays.asList(“remark”)).doWrite(getData());
这种方法的好处是:同一Excel可以在调用方法时排除不同的数据列。
自定义内容转换器,类似枚举的实现,将“男”、“女”转换成“0”、“1”的数值。
package com.daniel.utils;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
/**
* @author daniel
* @createTime 2022/9/16 0016 10:25
* @description
*/
public class GenderConverter implements Converter<Integer> {
private static final String MAN = "男";
private static final String WOMAN = "女";
@Override
public Class<?> supportJavaTypeKey() {
// 实体类中对象属性类型
return Integer.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
// Excel中对应的CellData属性类型
return CellDataTypeEnum.STRING;
}
@Override
public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty,
GlobalConfiguration globalConfiguration) {
// 从Cell中读取数据
String gender = cellData.getStringValue();
// 判断Excel中的值,将其转换为预期的数值
if (MAN.equals(gender)) {
return 0;
} else if (WOMAN.equals(gender)) {
return 1;
}
return null;
}
@Override
public CellData<?> convertToExcelData(Integer integer, ExcelContentProperty excelContentProperty,
GlobalConfiguration globalConfiguration) {
// 判断实体类中获取的值,转换为Excel预期的值,并封装为CellData对象
if (integer == null) {
return new CellData<>("");
} else if (integer == 0) {
return new CellData<>(MAN);
} else if (integer == 1) {
return new CellData<>(WOMAN);
}
return new CellData<>("");
}
}
package com.daniel.utils;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author daniel
* @createTime 2022/9/16 0016 11:04
* @description 解析监听器
* 每解析一行会回调invoke()方法。
* 整个excel解析结束会执行doAfterAllAnalysed()方法
*
* 没有考虑合并单元格的情况
*/
public class ExcelListener extends AnalysisEventListener<Object> {
//定义一个保存Excel所有记录的集合
private List<Object> datas = new ArrayList<>();
public List<Object> getDatas() {
return datas;
}
public void setDatas(List<Object> datas) {
this.datas = datas;
}
/**
* 逐行解析
* object : 当前行的数据
* 这个每一条数据解析都会来调用
* 我们将每一条数据都保存到list集合中
* @param object one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param analysisContext
*/
@Override
public void invoke(Object object, AnalysisContext analysisContext) {
System.out.println("读取object=" + object);
//当前行
// context.getCurrentRowNum()
//数据存储到list,供批量处理,或后续自己业务逻辑处理。
if (object != null) {
datas.add(object);
// handleBusinessLogic();
}
/*
如数据过大,可以进行定量分批处理
if(datas.size() >= 200){
handleBusinessLogic();
datas.clear();
}
*/
}
/**
* 读取表头内容
* @param headMap 表头
* @param analysisContext
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext analysisContext) {
System.out.println("表头" + headMap);
}
/**
* 解析完所有数据后会调用该方法
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//解析结束销毁不用的资源,非必要语句,查看导入的数据
System.out.println("读取Excel完毕" + datas.size());
}
//根据业务自行实现该方法,例如将解析好的dataList存储到数据库中
private void handleBusinessLogic() {}
}
package com.daniel.utils;
import cn.hutool.core.convert.Convert;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
/**
* @author daniel
* @createTime 2022/9/16 0016 9:47
* @description
*/
public class ExcelUtils {
private static final Logger log = LoggerFactory.getLogger(ExcelUtils.class);
/**
* 读取Excel(多个sheet可以用同一个实体类解析)
* @param excelInputStream
* @param fileName
* @param clazz
* @param
* @return
*/
public static <T> List<T> readExcel(InputStream excelInputStream, String fileName, Class<T> clazz) {
ExcelListener excelListener = new ExcelListener();
ExcelReader excelReader = getReader(excelInputStream, fileName,clazz, excelListener);
if (excelReader == null) {
return new ArrayList<>();
}
List<ReadSheet> readSheetList = excelReader.excelExecutor().sheetList();
for (ReadSheet readSheet : readSheetList) {
excelReader.read(readSheet);
}
excelReader.finish();
return Convert.toList(clazz, excelListener.getDatas());
}
/**
* 导出数据
*
* @param head 类名
* @param excelname excel名字
* @param data 数据
*/
public static void getExcelimporttemplate(Class head, String excelname, List data) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = requestAttributes.getResponse();
// 这里注意 有人反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
//response.setContentType("application/vnd.ms-excel");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
try {
//表格头部样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置背景颜色
//headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
//设置头字体
//WriteFont headWriteFont = new WriteFont();
//headWriteFont.setFontHeightInPoints((short)13);
//headWriteFont.setBold(true);
//headWriteCellStyle.setWriteFont(headWriteFont);
//设置头居中
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//表格内容样式
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置 水平居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
String fileName = URLEncoder.encode(excelname, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), head).registerWriteHandler(horizontalCellStyleStrategy).sheet("Sheet1").doWrite(data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 导出数据
*
* @param head 类名
* @param excelname excel名字
* @param data 数据
*/
public static void excelExport(Class head, String excelname, List data) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = requestAttributes.getResponse();
// 这里注意 有人反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
//response.setContentType("application/vnd.ms-excel");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
try {
String fileName = URLEncoder.encode(excelname, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), head).sheet("Sheet1").doWrite(data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 导出数据二级表头
* (合并第一行单元格,第二行开始是业务字段)
* @param bigTitle 合并单元格的名字
* @param head 类名
* @param excelname 导出的excel名字
* @param data 数据
* @param header 导出的字段列名
*/
public static void getBigTitleExcel(String bigTitle, Class head, String excelname, List data, List<String> header) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = requestAttributes.getResponse();
// 这里注意 有人反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
//response.setContentType("application/vnd.ms-excel");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
try {
//表格头部样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置背景颜色
//headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
//设置头字体
//WriteFont headWriteFont = new WriteFont();
//headWriteFont.setFontHeightInPoints((short)13);
//headWriteFont.setBold(true);
//headWriteCellStyle.setWriteFont(headWriteFont);
//设置头居中
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//表格内容样式
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置 水平居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
String fileName = URLEncoder.encode(excelname, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream())
.head(getExcelHeader(header, bigTitle))
.inMemory(true)
.registerWriteHandler(new WaterMarkHandler("wwwwwwwwwwwwwwww"))
.registerWriteHandler(horizontalCellStyleStrategy).sheet("Sheet1")
.doWrite(data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static List<List<String>> getExcelHeader(List<String> header, String bigTitle) {
List<List<String>> head = new ArrayList<>();
List<String> head0 = null;
for (String h : header) {
head0 = new ArrayList<>();
head0.add(bigTitle);
head0.add(h);
head.add(head0);
}
return head;
}
/**
* 返回ExcelReader
*
* @param excel 文件
* @param clazz 实体类
* @param excelListener
*/
private static <T> ExcelReader getReader(InputStream inputStream, String filename, Class<T> clazz, ExcelListener excelListener) {
try {
if (filename == null ||
(!filename.toLowerCase().endsWith(".xls") && !filename.toLowerCase().endsWith(".xlsx"))) {
return null;
}
ExcelReader excelReader = EasyExcel.read(inputStream, clazz, excelListener).build();
inputStream.close();
return excelReader;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.daniel.demo;
import com.alibaba.excel.EasyExcel;
import com.daniel.entity.Member;
import com.daniel.utils.ExcelListener;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/**
* @author daniel
* @createTime 2022/9/16 0016 15:39
* @description easyexcel 几种读取文件的方式
* 方式一:同步读取,将解析结果返回,比如返回List,业务再进行相应的数据集中处理
* 方式二:对照doReadSync()方法的是最后调用doRead()方法,不进行结果返回,而是在MemberImportExcelListener中进行一条条数据的处理;
*/
@Controller
@RequestMapping("read")
public class ReadController {
/**
* 1. 从Excel导入内容
* 基于同步获取结果列表的形式进行导入
*/
@PostMapping("/import1")
@ResponseBody
public void importMemberList(@RequestPart("file") MultipartFile file) throws IOException {
List<Member> list = EasyExcel.read(file.getInputStream())
.head(Member.class)
.sheet()
.doReadSync();
//打印读取的每条数据
for (Member member : list) {
System.out.println(member);
}
}
/**
* 2. 基于Listener方式从Excel导入会员列表
*/
@RequestMapping(value = "/import2", method = RequestMethod.POST)
@ResponseBody
public void importMemberList2(@RequestPart("file") MultipartFile file) throws IOException {
// 此处示例为方式二,读取监听器
EasyExcel.read(file.getInputStream(), Member.class, new ExcelListener()).sheet().doRead();
}
/**
* 基于ExcelUtils工具类导入
*/
@PostMapping(value = "/import3")
@ResponseBody
public List<Member> read(MultipartFile excel) throws IOException {
return ExcelUtils.readExcel(excel.getInputStream(), excel.getOriginalFilename(), Member.class);
}
}
业务逻辑实现
为方便验证功能,DAO层的逻辑便不再实现,可以直接通过Service层来封装数据,或者使用本类最底部的data()方法模拟数据(代码中两个方法都有,注意区分使用),先来看导出功能的业务类实现。
public interface MemberService {
/**
* 获取所有的成员信息
* @return 成员信息列表
*/
List<Member> getAllMember();
}
@Service("memberService")
public class MemberServiceImpl implements MemberService {
@Override
public List<Member> getAllMember() {
// 这里构造一些测试数据,具体业务场景可从数据库等其他地方获取
List<Member> list = new ArrayList<>();
Member member = new Member();
member.setUsername("张三");
member.setBirthday(getDate(1990, 10, 11));
member.setGender(0);
list.add(member);
Member member1 = new Member();
member1.setUsername("王红");
member1.setBirthday(getDate(1999, 3, 29));
member1.setGender(1);
list.add(member1);
Member member2 = new Member();
member2.setUsername("李四");
member2.setBirthday(getDate(2000, 2, 9));
member2.setGender(0);
list.add(member2);
return list;
}
private Date getDate(int year, int month, int day) {
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, day);
return calendar.getTime();
}
}
/**
* @author daniel
* @createTime 2022/9/16 0016 15:44
* @description
*/
@Controller
@RequestMapping("write")
public class WriteController {
/**
* 普通导出方式,不使用工具类
*/
@PostMapping("/export1")
public void exportMembers1(HttpServletResponse response,HttpServletRequest request) throws IOException {
//从数据库获取数据
//List members = memberService.getAllMember();
try {
String filenames = "demo";
String userAgent = request.getHeader("User-Agent");
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
filenames = URLEncoder.encode(filenames, "UTF-8");
} else {
filenames = new String(filenames.getBytes("UTF-8"), "ISO-8859-1");
}
response.setContentType("application/vnd.ms-exce");
response.setCharacterEncoding("utf-8");
response.addHeader("Content-Disposition", "attachment;filename=" + filenames + ".xlsx");
//从数据库获取数据,更换data()即可
EasyExcel.write(response.getOutputStream(), Member.class).sheet("sheet").doWrite(data());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 基于策略及拦截器导出(带样式)
*/
@RequestMapping("/export2")
public void exportMembers2(HttpServletResponse response, HttpServletRequest request) throws IOException {
// List members = memberService.getAllMember();
try {
String filenames = "demo2";
String userAgent = request.getHeader("User-Agent");
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
filenames = URLEncoder.encode(filenames, "UTF-8");
} else {
filenames = new String(filenames.getBytes("UTF-8"), "ISO-8859-1");
}
response.setContentType("application/vnd.ms-exce");
response.setCharacterEncoding("utf-8");
response.addHeader("Content-Disposition", "attachment;filename=" + filenames + ".xlsx");
EasyExcel.write(response.getOutputStream(), Member.class)
.sheet("sheet")
// 注册通用格式策略
.registerWriteHandler(CommonCellStyleStrategy.getHorizontalCellStyleStrategy())
// 设置自定义格式策略
// .registerWriteHandler(new CustomCellWriteHandler())
.doWrite(data());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 封装工具类导出方式
*/
@PostMapping("/export3")
@ResponseStatus(HttpStatus.CREATED)
public void inputExcel(@RequestBody MultipartFile file) {
//工具类方法1:
// ExcelUtils.getExcelimporttemplate(Member.class,"test1",data());
//工具类方法2:
// ExcelUtils.excelExport(Member.class,"test1",data());
//工具类方法3:
List<String> list = new ArrayList<>();
//列名
list.add("111");
list.add("222");
list.add("333");
ExcelUtils.getBigTitleExcel("测试big",Member.class,"test3",data(),list);
}
//辅助方法
private List<Member> data() {
List<Member> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Member data = new Member();
data.setUsername("Helen" + i);
data.setBirthday(new Date());
if (i%2 == 0) {
data.setGender(1);
}else {
data.setGender(0);
}
list.add(data);
}
return list;
}
}
小知识:
- 03版本的文件是.xls,最大支持6万多行数据,文件里可以带宏
算上标题,做多可写65536行,超出会报错:java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
- 07版本的文件分了好多种,都能支持104万行,其中.xlsx是最常用的后缀,里面不能保存宏,这样可以提高安全性,不容易被宏病毒感染
在EasyExcel执行write方法之后,获得ExcelWriterBuilder类,通过该类的registerWriteHandler方法可以设置一些处理策略。
package com.daniel.utils;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* @author daniel
* @createTime 2022/9/16 0016 10:42
* @description 通用的格式策略工具类,设置excel基础格式
*/
public class CommonCellStyleStrategy {
/**
* 设置单元格样式(仅用于示例)
* 其他业务需求样式参考封装
* @return 样式策略
*/
public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {
// 表头策略
WriteCellStyle headerCellStyle = new WriteCellStyle();
// 表头水平对齐居中
headerCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 背景色
headerCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
WriteFont headerFont = new WriteFont();
headerFont.setFontHeightInPoints((short) 15);
headerCellStyle.setWriteFont(headerFont);
// 自动换行
headerCellStyle.setWrapped(Boolean.FALSE);
// 内容策略
WriteCellStyle contentCellStyle = new WriteCellStyle();
// 设置数据允许的数据格式,这里49代表所有可以都允许设置
contentCellStyle.setDataFormat((short) 49);
// 设置背景色: 需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
contentCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
contentCellStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
// 设置内容靠左对齐
contentCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
// 设置字体
WriteFont contentFont = new WriteFont();
contentFont.setFontHeightInPoints((short) 12);
contentCellStyle.setWriteFont(contentFont);
// 设置自动换行
contentCellStyle.setWrapped(Boolean.FALSE);
// 设置边框样式和颜色
contentCellStyle.setBorderLeft(BorderStyle.MEDIUM);
contentCellStyle.setBorderTop(BorderStyle.MEDIUM);
contentCellStyle.setBorderRight(BorderStyle.MEDIUM);
contentCellStyle.setBorderBottom(BorderStyle.MEDIUM);
contentCellStyle.setTopBorderColor(IndexedColors.RED.getIndex());
contentCellStyle.setBottomBorderColor(IndexedColors.RED.getIndex());
contentCellStyle.setLeftBorderColor(IndexedColors.RED.getIndex());
contentCellStyle.setRightBorderColor(IndexedColors.RED.getIndex());
// 将格式加入单元格样式策略
return new HorizontalCellStyleStrategy(headerCellStyle, contentCellStyle);
}
}
该类中示例设置了Excel的基础格式。
再来实现一个精细化控制单元格内容CellWriteHandler的实现类
package com.daniel.utils;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.ss.usermodel.*;
import java.util.List;
/**
* @author daniel
* @createTime 2022/9/16 0016 10:44
* @description 实现CellWriteHandler接口, 实现对单元格样式的精确控制
*/
public class CustomCellWriteHandler implements CellWriteHandler {
/**
* 创建单元格之前的操作
*/
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
Head head, Integer integer, Integer integer1, Boolean aBoolean) {
}
/**
* 创建单元格之后的操作
*/
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
Head head, Integer integer, Boolean aBoolean) {
}
/**
* 单元格内容转换之后的操作
*/
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
}
/**
* 单元格处理后(已写入值)的操作
*/
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<CellData> list, Cell cell, Head head, Integer integer, Boolean isHead) {
// 设置超链接
if (isHead && cell.getRowIndex() == 0 && cell.getColumnIndex() == 0) {
CreationHelper helper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
Hyperlink hyperlink = helper.createHyperlink(HyperlinkType.URL);
hyperlink.setAddress("https://blog.csdn.net/qq1808814025?spm=1000.2115.3001.5343");
cell.setHyperlink(hyperlink);
}
// 精确设置单元格格式
boolean bool = isHead && cell.getRowIndex() == 1;
if (bool) {
// 获取工作簿
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
CellStyle cellStyle = workbook.createCellStyle();
Font cellFont = workbook.createFont();
cellFont.setBold(Boolean.TRUE);
cellFont.setFontHeightInPoints((short) 14);
cellFont.setColor(IndexedColors.SEA_GREEN.getIndex());
cellStyle.setFont(cellFont);
cell.setCellStyle(cellStyle);
}
}
}
package com.daniel.utils;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.Color;
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author daniel
* @createTime 2022/9/16 0030 16:37
* @description
* excel添加水印,只支持XSSFWorkbook,其余类别:SXSSFWorkbook、SXSSFWorkbook请另寻他法
*
* easyExcel使用时需要设置inMemory(true),否者默认使用的是SXSSFWorkbook,会报错!
*
* 打印水印
*/
@RequiredArgsConstructor
public class WaterMarkHandler implements SheetWriteHandler {
private final String WATER_MARK;
public static ByteArrayOutputStream createWaterMark(String content) throws IOException {
int width = 200;
int height = 150;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// 获取bufferedImage对象
String fontType = "微软雅黑";
int fontStyle = Font.BOLD;
int fontSize = 20;
Font font = new Font(fontType, fontStyle, fontSize);
Graphics2D g2d = image.createGraphics(); // 获取Graphics2d对象
image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
g2d.dispose();
g2d = image.createGraphics();
g2d.setColor(new Color(0, 0, 0, 20)); //设置字体颜色和透明度,最后一个参数为透明度
g2d.setStroke(new BasicStroke(1)); // 设置字体
g2d.setFont(font); // 设置字体类型 加粗 大小
g2d.rotate(-0.5, (double) image.getWidth() / 2, (double) image.getHeight() / 2);//设置倾斜度
FontRenderContext context = g2d.getFontRenderContext();
Rectangle2D bounds = font.getStringBounds(content, context);
double x = (width - bounds.getWidth()) / 2;
double y = (height - bounds.getHeight()) / 2;
double ascent = -bounds.getY();
double baseY = y + ascent;
// 写入水印文字原定高度过小,所以累计写水印,增加高度
g2d.drawString(content, (int) x, (int) baseY);
// 设置透明度
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
// 释放对象
g2d.dispose();
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(image, "png", os);
return os;
}
/**
* 为Excel打上水印工具函数
*
* @param sheet excel sheet
* @param bytes 水印图片字节数组
*/
public static void putWaterRemarkToExcel(XSSFSheet sheet, byte[] bytes) {
//add relation from sheet to the picture data
XSSFWorkbook workbook = sheet.getWorkbook();
int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG);
String rID = sheet.addRelation(null, XSSFRelation.IMAGES, workbook.getAllPictures().get(pictureIdx))
.getRelationship().getId();
//设置水印图片
sheet.getCTWorksheet().addNewPicture().setId(rID);
}
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@SneakyThrows
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
try (ByteArrayOutputStream waterMark = createWaterMark(WATER_MARK)) {
XSSFSheet sheet = (XSSFSheet) writeSheetHolder.getSheet();
putWaterRemarkToExcel(sheet, waterMark.toByteArray());
}
}
}
使用方式参考ExcelUtils工具类的 【导出数据二级表头(getBigTitleExcel)】 方法
此处导出的水印不可打印,只是背景图格式!!!
以上就是EasyExcel的基础使用过程,欢迎点赞关注交流。