SpringBoot+poi实现excel文档导出(三)(附源码)
使用SpringBoot+poi实现excel文档导出,打算分为三篇书写,第一篇基于if-else导出,第二篇提取部分方法作为抽象方法导出,第三篇为全抽象导出。
三、全抽象导出
本篇将用三个模板导出三个不同样式的excel表格。
首先理一下思路 (可以类比一个两头和中间有开口的物体,按照基本的三种颜色的流体往下灌,中间的口不动的情况下,灌红色的流体出红色,灌绿色的流体出绿色;如果上口灌红色的流体,中间灌绿色的流体,出来的颜色就是黄色。把开口的物体作为抽象层的业务代码,流体作为参数,传不同的参数,出不同的结果,但是物体不变):
- 将第二章Service层的业务层代码全部抽取出来作为抽象方法,使用工厂生产impl层和不同类型数据处理结果;
- 使用“T”“V”“B”三个泛型分别代表实体(业务中暂时用不到,但是使用BaseService查询肯定会用到)、导出表的Vo、查询条件。
1. 接口工厂
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 通用Service
* @param <B> 查询条件泛型
*/
public interface IExportServiceFactory<V, B> {
/**
* @Description 通用导出/模板下载
*/
void exportExcel(B b, HttpServletResponse response) throws Exception;
/**
* 通用查询
* @param b
* @return
*/
List<V> findScheduleByConditions(B b);
2. 导出的抽象方法
import com.example.export.util.DataExportUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* 模板/数据导出抽象类
* @param <T> 实体
* @param <V> Vo
*/
public abstract class ExcelDispose<T, V> {
/**
* 反射
* @param valueList
* @param v
* @throws IllegalAccessException
*/
public void resultDispose(List<String> valueList, V v) throws IllegalAccessException {
// 将实体中的数据提取出来放到list容器中
Class<?> aClass = v.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (int o = 1; o < declaredFields.length; o++) {
Field declaredField = declaredFields[o];
declaredField.setAccessible(true);
Object value = declaredField.get(v);
String s = null != value ? value.toString() : "";
valueList.add(s);
// 关闭除public外的访问权限
declaredField.setAccessible(false);
}
}
/**
* 导出主逻辑
* @param path excel文件路径
* @param list 查询出来的数据
* @param cellNameList 表头
* @param i 对应文件的新行索引
* @param j 对应文件的表头索引
* @param response
* @param width 列宽
* @throws Exception
*/
public void excelPathDispose(String path, List<T> list, List<String> cellNameList,
int i, int j, HttpServletResponse response,
Integer width, IDispose<T, V> dispose) throws Exception {
InputStream inputStream = DataExportUtils.getStream(path);
XSSFWorkbook wb = DataExportUtils.getwb(inputStream);
SXSSFWorkbook swb = DataExportUtils.getswb(wb);
Sheet sheet = DataExportUtils.getSheet(wb, 0);
Row row = DataExportUtils.getRow(sheet, j);
CellStyle cellStyle = DataExportUtils.getCellStyle(swb);
int cells = row.getPhysicalNumberOfCells();
for (Cell cell : row) {
String stringCellValue = cell.getStringCellValue();
cellNameList.add(stringCellValue);
}
dataDispose(list, cellNameList, i, width, dispose, sheet, cellStyle, cells);
DataExportUtils.sxssfWriteAndClose(response, inputStream, wb, swb);
}
/**
* 对数据进行处理
* @param list
* @param cellNameList
* @param i
* @param width
* @param dispose
* @param sheet
* @param cellStyle
* @param cells
* @throws IllegalAccessException
*/
private void dataDispose(List<T> list, List<String> cellNameList, int i, Integer width, IDispose<T, V> dispose, Sheet sheet, CellStyle cellStyle, int cells) throws IllegalAccessException {
Row row;
for (int k = 0; k < list.size(); k++) {
List<String> valueList = new ArrayList<>();
// 创建新行
V v = dispose.getType(list, k);
resultDispose(valueList, v);
row = sheet.createRow(i++);
int xh = k + 1;
valueList.add(0, Integer.toString(xh));
// 做一些数据上的处理
valueListDispose(valueList, v);
dispose.dataDispose(row, sheet, cellNameList,valueList, cellStyle, width, cells);
}
}
/**
* 子类必须重写,比如对应字段的编码翻译成文字
* @param valueList
* @param v
*/
public abstract void valueListDispose(List<String> valueList, V v);
}
3. 实现类
3.1. 实现类1(用户)
import com.example.export.dispose.ExcelDispose;
import com.example.export.dispose.IDispose;
import com.example.export.dispose.datadispose.UserExport;
import com.example.export.service.IExportServiceFactory;
import com.example.export.vo.UserVo;
import com.example.export.vo.ScheduleVo;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserExportServiceImpl implements IExportServiceFactory<UserVo, ScheduleVo> {
@Override
public void exportExcel(ScheduleVo vo, HttpServletResponse response) throws Exception {
// 新行的索引
int i = 1;
// 表头索引
int j = 0;
List<String> cellNameList = new ArrayList<>();
Integer width = null;
IDispose<UserVo, UserVo> dispose = new UserExport();
ExcelDispose<UserVo, UserVo> zhqJtsyjcExcelDispose = new UserExport();
// vo.setYljgLb(DicYljgUtils.findByOrganId(UserUtils.getUser(vo.getUserId()).getOrganId()).getYljgLb());
// 导出excel模板根据传入的参数置空list,1:模板导出;2:全量导出
// List<UserVo> users = vo.getMb().equals("1") ? new ArrayList<>() : query(vo);
List<UserVo> scheduleByConditions = findScheduleByConditions(vo);
String path = "excel/user.xlsx";
zhqJtsyjcExcelDispose.excelPathDispose(path, scheduleByConditions, cellNameList, i, j, response, width, dispose);
}
/**
* 假设这里是一个查询语句,得到查询结果
* @param vo
* @return
*/
public List<UserVo> findScheduleByConditions(ScheduleVo vo) {
UserVo userVo1 = new UserVo();
UserVo userVo2 = new UserVo();
UserVo userVo3 = new UserVo();
UserVo userVo4 = new UserVo();
UserVo userVo5 = new UserVo();
List<UserVo> userVos = new ArrayList<>();
userVo1.setName("账伞").setAge("17").setSex("男").setAddress("北京市朝阳区上雕刻技法").setContactInformation("2342893472374");
userVo2.setName("里斯").setAge("43").setSex("女").setAddress("上海市黄浦区的发射点").setContactInformation("34534453");
userVo3.setName("汪芜").setAge("23").setSex("男").setAddress("陕西省西安市瓦基尔克").setContactInformation("3131231");
userVo4.setName("马尔扎哈").setAge("534").setSex("男").setAddress("山西省太原市昆仑山地方").setContactInformation("23544343");
userVo5.setName("古力娜扎").setAge("43").setSex("女").setAddress("广东省广州市的快速减肥").setContactInformation("1213124242");
userVos.add(userVo1);
userVos.add(userVo2);
userVos.add(userVo3);
userVos.add(userVo4);
userVos.add(userVo5);
return userVos;
}
}
3.2. 实现类2(汽车)
import com.example.export.dispose.ExcelDispose;
import com.example.export.dispose.IDispose;
import com.example.export.dispose.datadispose.CarExport;
import com.example.export.service.IExportServiceFactory;
import com.example.export.vo.CarVo;
import com.example.export.vo.ScheduleVo;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@Service
public class CarExportServiceImpl implements IExportServiceFactory<CarVo, ScheduleVo> {
@Override
public void exportExcel(ScheduleVo vo, HttpServletResponse response) throws Exception {
// 新行的索引
int i = 1;
// 表头索引
int j = 0;
List<String> cellNameList = new ArrayList<>();
Integer width = null;
IDispose<CarVo, CarVo> dispose = new CarExport();
ExcelDispose<CarVo, CarVo> zhqJtsyjcExcelDispose = new CarExport();
// vo.setYljgLb(DicYljgUtils.findByOrganId(UserUtils.getUser(vo.getUserId()).getOrganId()).getYljgLb());
// 导出excel模板根据传入的参数置空list,1:模板导出;2:全量导出
// List<UserVo> users = vo.getMb().equals("1") ? new ArrayList<>() : query(vo);
List<CarVo> scheduleByConditions = findScheduleByConditions(vo);
String path = "excel/car.xlsx";
zhqJtsyjcExcelDispose.excelPathDispose(path, scheduleByConditions, cellNameList, i, j, response, width, dispose);
}
@Override
public List<CarVo> findScheduleByConditions(ScheduleVo vo) {
CarVo carVo1 = new CarVo();
CarVo carVo2 = new CarVo();
CarVo carVo3 = new CarVo();
CarVo carVo4 = new CarVo();
CarVo carVo5 = new CarVo();
CarVo carVo6 = new CarVo();
CarVo carVo7 = new CarVo();
List<CarVo> carVos = new ArrayList<>();
carVo1.setBrand("奥迪").setColor("红色").setLength("6.2").setWidth("2.4").setKilometre("600公里").setSpeed("150km/h").setLifetime("3年");
carVo2.setBrand("宝马").setColor("蓝色").setLength("6.3").setWidth("2.5").setKilometre("700公里").setSpeed("160km/h").setLifetime("4年");
carVo3.setBrand("比亚迪").setColor("黑色").setLength("6.4").setWidth("2.6").setKilometre("800公里").setSpeed("170km/h").setLifetime("5年");
carVo4.setBrand("长安").setColor("橙色").setLength("6.5").setWidth("2.7").setKilometre("900公里").setSpeed("180km/h").setLifetime("6年");
carVo5.setBrand("长城").setColor("渐变红").setLength("6.6").setWidth("2.8").setKilometre("1000公里").setSpeed("190km/h").setLifetime("7年");
carVo6.setBrand("五菱之光").setColor("青色").setLength("6.7").setWidth("2.9").setKilometre("1100公里").setSpeed("200km/h").setLifetime("8年");
carVo7.setBrand("奥托").setColor("玫瑰红").setLength("6.8").setWidth("2.0").setKilometre("11200公里").setSpeed("140km/h").setLifetime("9年");
carVos.add(carVo1);
carVos.add(carVo2);
carVos.add(carVo3);
carVos.add(carVo4);
carVos.add(carVo5);
carVos.add(carVo6);
carVos.add(carVo7);
return carVos;
}
}
3.3. 实现类3(公司)
import com.example.export.dispose.ExcelDispose;
import com.example.export.dispose.IDispose;
import com.example.export.dispose.datadispose.CompanyExport;
import com.example.export.service.IExportServiceFactory;
import com.example.export.vo.CompanyVo;
import com.example.export.vo.ScheduleVo;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@Service
public class CompanyExportServiceImpl implements IExportServiceFactory<CompanyVo, ScheduleVo> {
@Override
public void exportExcel(ScheduleVo vo, HttpServletResponse response) throws Exception {
// 新行的索引
int i = 2;
// 表头索引
int j = 1;
List<String> cellNameList = new ArrayList<>();
Integer width = null;
IDispose<CompanyVo, CompanyVo> dispose = new CompanyExport();
ExcelDispose<CompanyVo, CompanyVo> zhqJtsyjcExcelDispose = new CompanyExport();
// vo.setYljgLb(DicYljgUtils.findByOrganId(UserUtils.getUser(vo.getUserId()).getOrganId()).getYljgLb());
// 导出excel模板根据传入的参数置空list,1:模板导出;2:全量导出
// List<UserVo> users = vo.getMb().equals("1") ? new ArrayList<>() : query(vo);
List<CompanyVo> scheduleByConditions = findScheduleByConditions(vo);
String path = "excel/company.xlsx";
zhqJtsyjcExcelDispose.excelPathDispose(path, scheduleByConditions, cellNameList, i, j, response, width, dispose);
}
@Override
public List<CompanyVo> findScheduleByConditions(ScheduleVo vo) {
CompanyVo companyVo1 = new CompanyVo();
CompanyVo companyVo2 = new CompanyVo();
CompanyVo companyVo3 = new CompanyVo();
CompanyVo companyVo4 = new CompanyVo();
List<CompanyVo> companyVos = new ArrayList<>();
companyVo1.setName("北京市律师事务所").setAddress("北京市朝阳区觉得还是开发").setPeopleNum("59").setAverageSalary("12300元").setBusiness("打官司").setScale("200平");
companyVo2.setName("上海市美食加工厂").setAddress("上海市浦东新区登记是否").setPeopleNum("519").setAverageSalary("12400元").setBusiness("做食品").setScale("2000平");
companyVo3.setName("上海市机械加工厂").setAddress("上海市闵行区健身房的和").setPeopleNum("5119").setAverageSalary("12500元").setBusiness("加工零件").setScale("20000平");
companyVo4.setName("陕西旅游公司").setAddress("陕西省西安市扣税的风格").setPeopleNum("58").setAverageSalary("12600元").setBusiness("旅游推荐").setScale("200000平");
companyVos.add(companyVo1);
companyVos.add(companyVo2);
companyVos.add(companyVo3);
companyVos.add(companyVo4);
return companyVos;
}
}
4. 抽象数据处理
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import java.util.List;
/**
* 抽象数据处理
* @param <T> 实体泛型
*/
public interface IDispose<T, V> {
/**
* 获取索引K下的的对象
* @param list
* @param k
* @return
*/
V getType(List<T> list, int k);
/**
* 对表数据处理并写入到单元格,每张表必须实现
* @param row
* @param sheet
* @param cellNameList
* @param valueList
* @param cellStyle
* @param width
* @param cells
*/
void dataDispose(Row row, Sheet sheet, List<String> cellNameList,
List<String> valueList, CellStyle cellStyle, Integer width, int cells);
4.1. 数据处理实现1(用户,全样式)
import com.example.export.dispose.ExcelDispose;
import com.example.export.dispose.IDispose;
import com.example.export.util.DataExportUtils;
import com.example.export.vo.UserVo;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import java.util.List;
/**
* 军团菌水环境样本监测管理导出实例
* @author wp
*/
public class UserExport extends ExcelDispose<UserVo, UserVo> implements IDispose<UserVo, UserVo> {
@Override
public void dataDispose(Row row, Sheet sheet, List<String> cellNameList,
List<String> valueList, CellStyle cellStyle, Integer width, int cells) {
for (int j = 0; j < cells; j++) {
Cell cell = row.createCell(j);
for (int z = 0; z < valueList.size(); z++) {
if (j == z) {
if (j == 0) {
// 插入序号,并按照表头对序号进行列宽设置
width = DataExportUtils.checkNum(cellNameList.get(0).length());
sheet.setColumnWidth(j, width);
cell.setCellValue(valueList.get(z));
cell.setCellStyle(cellStyle);
break;
}
cell.setCellValue(valueList.get(z));
cell.setCellStyle(cellStyle);
break;
}
}
}
}
@Override
public UserVo getType(List<UserVo> list, int k) {
// 对特定数据处理方法
return list.get(k);
}
@Override
public void valueListDispose(List<String> valueList, UserVo userVo) {
// 例如:valueList.set(2, 调用翻译方法翻译字典项);
}
}
4.2. 数据处理实现2(汽车,去掉所有样式)
import com.example.export.dispose.ExcelDispose;
import com.example.export.dispose.IDispose;
import com.example.export.util.DataExportUtils;
import com.example.export.vo.CarVo;
import com.example.export.vo.UserVo;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import java.util.List;
/**
* 军团菌水环境样本监测管理导出实例
* @author wp
*/
public class CarExport extends ExcelDispose<CarVo, CarVo> implements IDispose<CarVo, CarVo> {
@Override
public void dataDispose(Row row, Sheet sheet, List<String> cellNameList,
List<String> valueList, CellStyle cellStyle, Integer width, int cells) {
for (int j = 0; j < cells; j++) {
Cell cell = row.createCell(j);
for (int z = 0; z < valueList.size(); z++) {
if (j == z) {
if (j == 0) {
// 插入序号,并按照表头对序号进行列宽设置
// 删除样式和列宽设置
// width = DataExportUtils.checkNum(cellNameList.get(0).length());
// sheet.setColumnWidth(j, width);
cell.setCellValue(valueList.get(z));
// cell.setCellStyle(cellStyle);
break;
}
cell.setCellValue(valueList.get(z));
// cell.setCellStyle(cellStyle);
break;
}
}
}
}
@Override
public CarVo getType(List<CarVo> list, int k) {
// 对特定数据处理方法
return list.get(k);
}
@Override
public void valueListDispose(List<String> valueList, CarVo carVo) {
// 例如:valueList.set(2, 调用翻译方法翻译字典项);
// 给车长和车宽都加上单位
valueList.set(3, valueList.get(3) + "米");
valueList.set(4, valueList.get(4) + "米");
}
}
4.3. 数据处理实现3(公司, 去掉样式,保留动态列宽设置)
import com.example.export.dispose.ExcelDispose;
import com.example.export.dispose.IDispose;
import com.example.export.util.DataExportUtils;
import com.example.export.vo.CompanyVo;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import java.util.List;
/**
* 军团菌水环境样本监测管理导出实例
* @author wp
*/
public class CompanyExport extends ExcelDispose<CompanyVo, CompanyVo> implements IDispose<CompanyVo, CompanyVo> {
@Override
public void dataDispose(Row row, Sheet sheet, List<String> cellNameList,
List<String> valueList, CellStyle cellStyle, Integer width, int cells) {
for (int j = 0; j < cells; j++) {
Cell cell = row.createCell(j);
for (int z = 0; z < valueList.size(); z++) {
if (j == z) {
if (j == 0) {
// 插入序号,并按照表头对序号进行列宽设置
// 删除样式但不删除格式
width = DataExportUtils.checkNum(cellNameList.get(0).length());
sheet.setColumnWidth(j, width);
cell.setCellValue(valueList.get(z));
// cell.setCellStyle(cellStyle);
break;
}
cell.setCellValue(valueList.get(z));
// cell.setCellStyle(cellStyle);
break;
}
}
}
}
@Override
public CompanyVo getType(List<CompanyVo> list, int k) {
// 对特定数据处理方法
return list.get(k);
}
@Override
public void valueListDispose(List<String> valueList, CompanyVo companyVo) {
// 例如:valueList.set(2, 调用翻译方法翻译字典项);
}
}
5. 实体Vo
5.1. 用户
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class UserVo {
private String id;
private String name;
private String age;
private String sex;
private String address;
private String contactInformation;
}
5.2. 汽车
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class CarVo {
private String id;
private String brand;
private String color;
private String length;
private String width;
private String kilometre;
private String speed;
private String lifetime;
}
5.3. 公司
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class CompanyVo {
private String id;
private String name;
private String address;
private String peopleNum;
private String scale;
private String averageSalary;
private String business;
}
6. 表格模板
6.1. 用户模板
6.2. 汽车模板
6.3. 公司模板
7. Controller层(其他两层参考如下用户层)
import com.example.export.service.IExportServiceFactory;
import com.example.export.vo.ScheduleVo;
import com.example.export.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IExportServiceFactory<UserVo, ScheduleVo> exportService;
@GetMapping("/export")
public void export(ScheduleVo scheduleVo, HttpServletResponse response) throws Exception {
exportService.exportExcel(scheduleVo, response);
}
}
8. 输出结果(注意:postman导出需要更改后缀:zip->xlsx)
8.1. 用户保留所有样式
8.2. 汽车删除所有样式
8.3. 公司除自适应列宽外样式全部删除
9. 项目层级
本章内容解决了项目内繁琐的if-else开发模式,并且抽取抽象方法,使用工厂模式自动生成对象,简单的静态代理模式增强代码块,强化各个类型的私有属性,这样就可以自定义一个通用的导出方法,无论业务怎么变,抽象方法自身不会发生改变,如果有需求需要强制改变所有类型的结构,我们只需要将抽象方法继承过来,使用简单的代理就可以达到要求,不会动原有的代码,符合设计模式的开闭原则,并且代码的灵动性非常高。
如果有新增其他类型的导出,我们只需要继承抽象类,实现抽象数据处理工厂的方法,就可以达到导出功能。
纯手打,技术有限,请各位大佬批评指正,谢谢!
项目已放在git上。
导出项目git地址:
https://gitee.com/table-tennis-king/export_test.git