1、使用EasyExcel进行写操作(下载Excel)
1. 在pom文件中添加对应的依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.27</version>
</dependency>
2. 创建实体类,和excel数据对应
@Data
public class TestInfoExcel {
//ceshi
@ExcelIgnore
private Long id;
//用户ID自生成
@ExcelProperty("用户排序")
@ColumnWidth(20)
private Integer sort;
//名称
@ExcelProperty("用户名称")
@ColumnWidth(20)
private String name;
//时间
@ExcelProperty("时间")
@ColumnWidth(20)
/**
* 按照指定的格式对日期进行格式化;
* */
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date insertTime;
//身高
@ExcelProperty("身高(米)")
@NumberFormat("#.##")
@ColumnWidth(20)
private Double height;
//性别(0男1女)
/**
* 自定义内容转换器
* */
@ExcelProperty(value = "性别", converter = GenderConverter.class)
@ColumnWidth(10)
private int sex;
}
常用注解有:
@ExcelProperty 指定当前字段对应excel中的哪一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。
@ExcelIgnore EasyExcel默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
@DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
@NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
3. converter自定义转换器
通过自定义转换器,比如将数据库中表示性别的1、0转换成男、女的实例:
性别转换器:
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.example.wenxin.enums.GenderEnum;
/**
* 性别转换器
* */
public class GenderConverter implements Converter<Integer> {
@Override
public Class<?> supportJavaTypeKey() {
// 实体类中对象属性类型
return Integer.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
// Excel中对应的CellData(单元格数据)属性类型
return CellDataTypeEnum.STRING;
}
/**
* 将单元格里的数据转为java对象,也就是女转成2,男转成1,用于导入excel时对性别字段进行转换
* */
@Override
public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {
// 从CellData中读取数据,判断Excel中的值,将其转换为预期的数值
return GenderEnum.convert(context.getReadCellData().getStringValue()).getValue();
}
/**
* 将java对象转为单元格数据,也就是2转成女,1转成男,用于导出excel时对性别字段进行转换
* */
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception {
// 判断实体类中获取的值,转换为Excel预期的值,并封装为CellData对象
return new WriteCellData<>(GenderEnum.convert(context.getValue()).getDescription());
}
}
4、性别枚举类
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.stream.Stream;
/**
* @program: wenxin
* @ClassName GenderEnum
* @description:
* @author: XZY
* @create: 2024-05-27 13:54
* @Version 1.0
**/
@Getter
@AllArgsConstructor
public enum GenderEnum {
/**
* 未知
*/
UNKNOWN(0, "未知"),
/**
* 男性
*/
MALE(1, "男性"),
/**
* 女性
*/
FEMALE(2, "女性");
private final Integer value;
@JsonFormat
private final String description;
public static GenderEnum convert(Integer value) {
// 用于为给定元素创建顺序流
// values:获取枚举类型的对象数组
return Stream.of(values())
.filter(bean -> bean.value.equals(value))
.findAny()
.orElse(UNKNOWN);
}
public static GenderEnum convert(String description) {
return Stream.of(values())
.filter(bean -> bean.description.equals(description))
.findAny()
.orElse(UNKNOWN);
}
}
5.普通导出
/**
* 测试(TestInfo)表控制层
*
* @author makejava
* @since 2024-05-27 13:34:15
*/
@RestController
@RequestMapping("testInfo")
public class TestInfoController {
/**
* 服务对象
*/
@Autowired
private TestInfoService testInfoService;
/**
* 设置响应结果
*
* @param response 响应结果对象
* @param rawFileName 文件名
* @throws UnsupportedEncodingException 不支持编码异常
*/
private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {
//设置内容类型
response.setContentType("application/vnd.vnd.ms-excel");
//设置编码格式
response.setCharacterEncoding("utf-8");
//设置导出文件名称(避免乱码)
String fileName = URLEncoder.encode(rawFileName.concat(".xlsx"), "UTF-8");
// 设置响应头
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
}
private Date getBirthday(int year, int month, int day){
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, day);
return calendar.getTime();
}
/**
* 导出数据
* */
@PostMapping("/export/testUser")
public void exportUserExcel(HttpServletResponse response,@RequestBody TestInfo info) throws IOException {
OutputStream outputStream=response.getOutputStream();
try {
this.setExcelResponseProp(response, "用户列表");
// 模拟根据条件在数据库查询数据
List<TestInfoExcel> userList = new ArrayList<>();
userList = testInfoService.getAllList(info);
//这个实现方式非常简单直接,使用EasyExcel的write方法将查询到的数据进行处理,以流的形式写出即可
EasyExcel.write(outputStream,TestInfoExcel.class)//对应的导出实体类
.excelType(ExcelTypeEnum.XLSX)//excel文件类型,包括CSV、XLS、XLSX
.sheet("用户列表")//导出sheet页名称
.doWrite(userList); //查询获取的数据集合List<T>,转成excel
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
outputStream.flush();
outputStream.close();
}
}
}
6.多sheet表导出
/**
* 测试(TestInfo)表控制层
*
* @author makejava
* @since 2024-05-27 13:34:15
*/
@RestController
@RequestMapping("testInfo")
public class TestInfoController {
/**
* 服务对象
*/
@Autowired
private TestInfoService testInfoService;
/**
* 设置响应结果
*
* @param response 响应结果对象
* @param rawFileName 文件名
* @throws UnsupportedEncodingException 不支持编码异常
*/
private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {
//设置内容类型
response.setContentType("application/vnd.vnd.ms-excel");
//设置编码格式
response.setCharacterEncoding("utf-8");
//设置导出文件名称(避免乱码)
String fileName = URLEncoder.encode(rawFileName.concat(".xlsx"), "UTF-8");
// 设置响应头
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
}
private Date getBirthday(int year, int month, int day){
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, day);
return calendar.getTime();
}
/**
* 多sheet导出数据
* */
@PostMapping("/export/manySheet")
public void exportManySheet(HttpServletResponse response,@RequestBody TestInfo info)throws IOException{
OutputStream outputStream=response.getOutputStream();
ExcelWriter writer = EasyExcel.write(outputStream, TestInfoExcel.class).excelType(ExcelTypeEnum.XLSX).build();
try {
this.setExcelResponseProp(response, "用户列表");
// 模拟根据条件在数据库分页查询数据
List<TestInfoExcel> userList = new ArrayList<>();
userList = testInfoService.getAllList(info);
//创建新的sheet页
WriteSheet writeSheet = EasyExcel.writerSheet("用户信息" + "一").build();
//将list集合中的对象写到对应的sheet中去
writer.write(userList,writeSheet);
info.setName("啊");
List<TestInfoExcel> allList = testInfoService.getAllList(info);
WriteSheet writeSheet2 = EasyExcel.writerSheet("用户信息" + "二").build();
writer.write(allList,writeSheet2);
} catch (IOException e) {
throw new RuntimeException(e);
//给提示todo
}finally {
writer.finish();
outputStream.flush();
outputStream.close();
}
}
}
2.使用EasyExcel进行读操作(导入excel)
1、文件读取配置
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.example.wenxin.entity.TestInfo;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义监听器,对下载的excel中的数据进行校验
* */
public class UserListener extends AnalysisEventListener {
List<String> names = new ArrayList<>();
/**
* 每解析一行,回调该方法
*
* @param data
* @param context
*/
@Override
public void invoke(Object data, AnalysisContext context) {
//校验名称
String name = ((TestInfo) data).getName();
if (StrUtil.isBlank(name)) {
throw new RuntimeException(String.format("第%s行名称为空,请核实", context.readRowHolder().getRowIndex() + 1));
}
if (names.contains(name)) {
throw new RuntimeException(String.format("第%s行名称已重复,请核实", context.readRowHolder().getRowIndex() + 1));
} else {
names.add(name);
}
}
/**
* 出现异常回调
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
if (exception instanceof ExcelDataConvertException) {
/**从0开始计算*/
Integer columnIndex = ((ExcelDataConvertException) exception).getColumnIndex() + 1;
Integer rowIndex = ((ExcelDataConvertException) exception).getRowIndex() + 1;
String message = "第" + rowIndex + "行,第" + columnIndex + "列" + "数据格式有误,请核实";
throw new RuntimeException(message);
} else if (exception instanceof RuntimeException) {
throw exception;
} else {
super.onException(exception, context);
}
}
/**
* 解析完,全部回调
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
//解析完,全部回调逻辑实现
names.clear();
}
}
2、导入
/**
* 测试(TestInfo)表控制层
*
* @author makejava
* @since 2024-05-27 13:34:15
*/
@RestController
@RequestMapping("testInfo")
public class TestInfoController {
/**
* 服务对象
*/
@Autowired
private TestInfoService testInfoService;
/**
* 导入数据
* */
@PostMapping(value = "/importData")
@Transactional
public void importData(MultipartFile file){
try {
//获取文件的输入流
InputStream inputStream = file.getInputStream();
List<TestInfo> lst = EasyExcel.read(inputStream) //调用read方法
//注册自定义监听器,字段校验可以在监听器内实现
.registerReadListener(new UserListener())
.head(TestInfo.class) //对应导入的实体类
.sheet(0) //导入数据的sheet页编号,0代表第一个sheet页,如果不填,则会导入所有sheet页的数据
.headRowNumber(1) //列表头行数,1代表列表头有1行,第二行开始为数据行
.doReadSync(); //开始读Excel,返回一个List<T>集合,继续后续入库操作
//模拟导入数据库操作
for (TestInfo userDO:lst){
System.out.println(userDO.toString());
}
boolean b = testInfoService.saveBatch(lst);
}catch (IOException exception){
throw new RuntimeException(exception);
}
}
}