本文介绍springboot中将前端展示的数据导出为EXCEL表格文件的方法,方法来自若依ruoyi
目录
1.相关配置
1.1 引入环境
1.2配置用注解
1.3添加注解所用字段
1.4添加注解
2.Excel方法类
导出方法路线图编辑
2.1定义变量字段
2.2 Controller层
2.3 exportExcel(List list, String sheetName)方法
2.3.1 init方法(初始化)
2.3.2 调用creteExcelField()方法
2.3.3 createWorkbook()
3. exportExcel()
3.1createSheet()方法
3.1.1createStyles()
3.2createCell()
3.3fillExcelData()
3.3.1addCell()
3.4 addStatisticsRow()
exportExcel方法的后半部分
导出方法路线图
1.相关配置
1.1 引入环境
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
将数据导出为EXCEL文件时有许许多多的配置,例如行高,日期格式,导出类型等,为实现代码的复用和功能的多样性,应使用注解的方法为不同的实体类,不同的数据类型进行单个配置。
1.2配置用注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel
{
@Rentention 用于定义该注解如何保持,即生命周期
RetentionPolicy.SOURCE | 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃; |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期; |
RetentionPolicy.RUNTIME | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在; |
@Target 用于描述注解的使用范围,被描述的注解可以用在什么地方,参数
1.3添加注解所用字段
EXCEL表格所需要的配置
例如:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel
{
/**
* 导出时在excel中排序
*/
public int sort() default Integer.MAX_VALUE;
/**
* 导出到Excel中的名字.
*/
public String name() default "";
/**
* 日期格式, 如: yyyy-MM-dd
*/
public String dateFormat() default "";
/**
* 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
*/
public String dictType() default "";
/**
* 读取内容转表达式 (如: 0=男,1=女,2=未知)
*/
public String readConverterExp() default "";
/**
* 分隔符,读取字符串组内容
*/
public String separator() default ",";
/**
* BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
*/
public int scale() default -1;
/**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/**
* 导出类型(0数字 1字符串)
*/
public ColumnType cellType() default ColumnType.STRING;
/**
* 导出时在excel中每个列的高度 单位为字符
*/
public double height() default 14;
/**
* 导出时在excel中每个列的宽 单位为字符
*/
public double width() default 16;
/**
* 文字后缀,如% 90 变成90%
*/
public String suffix() default "";
/**
* 当值为空时,字段的默认值
*/
public String defaultValue() default "";
/**
* 提示信息
*/
public String prompt() default "";
/**
* 设置只能选择不能输入的列内容.
*/
public String[] combo() default {};
/**
* 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
*/
public boolean isExport() default true;
/**
* 另一个类中的属性名称,支持多级获取,以小数点隔开
*/
public String targetAttr() default "";
/**
* 是否自动统计数据,在最后追加一行统计数据总和
*/
public boolean isStatistics() default false;
/**
* 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右)
*/
Align align() default Align.AUTO;
public enum Align
{
AUTO(0), LEFT(1), CENTER(2), RIGHT(3);
private final int value;
Align(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
/**
* 字段类型(0:导出导入;1:仅导出;2:仅导入)
*/
Type type() default Type.ALL;
public enum Type
{
ALL(0), EXPORT(1), IMPORT(2);
private final int value;
Type(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
public enum ColumnType
{
NUMERIC(0), STRING(1), IMAGE(2);
private final int value;
ColumnType(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
具体配置根据自己需求来定,其中的枚举类型为后面判断使用方法所用
1.4添加注解
然后应为需要导出的实体类的字段添加注解
例如:
/** 订单编号 */
@Excel(name = "订单编号")
private String orderId;
/** 客户编号 */
@Excel(name = "客户编号")
private Long customerId;
/** 姓名 */
@Excel(name = "姓名")
private String Person;
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date Time;
2.Excel方法类
导出方法路线图
2.1定义变量字段
泛型ExcleUtil类来处理导出
/**
* Excel相关处理
*
* @author ruoyi
*/
public class ExcelUtil<T>
{
private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
/**
* Excel sheet最大行数,默认65536
*/
public static final int sheetSize = 65536;
/**
* 工作表名称
*/
private String sheetName;
/**
* 导出类型(EXPORT:导出数据;IMPORT:导入模板)
*/
private Type type;
/**
* 工作薄对象
*/
private Workbook wb;
/**
* 工作表对象
*/
private Sheet sheet;
/**
* 样式列表
*/
private Map<String, CellStyle> styles;
/**
* 导入导出数据列表
*/
private List<T> list;
/**
* 注解列表
*/
private List<Object[]> fields;
/**
* 最大高度
*/
private short maxHeight;
/**
* 统计列表
*/
private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
/**
* 数字格式
*/
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
/**
* 实体对象
*/
public Class<T> clazz;
public ExcelUtil(Class<T> clazz)
{
this.clazz = clazz;
}
工作簿和工资表的区别:
新建一个excel后系统默认一个工作簿,在这个簿中一般会友三张工作表,sheet1、sheet2、sheet3。就这么简单。一张工作簿中最多可容255张工作表,一张表最多有65536行
2.2 Controller层
在controller层需要先查询出数据,再初始化ExcelUtil并调用方法
List<Order> list=service.selectOrder(ids);
ExcelUtil<Order> util = new ExcelUtil<Order>(Order.class);
return util.exportExcel(list, "Excel数据表");
构造方法的参数是要导出的实体类class,赋值给clazz变量,因为所用的是泛型方法,实体对象也需要使用
public ExcelUtil(Class<T> clazz)
{
this.clazz = clazz;
}
2.3 exportExcel(List<T> list, String sheetName)方法
进入到exportExcel(List<T> list, String sheetName)方法中
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @return 结果
*/
public AjaxResult exportExcel(List<T> list, String sheetName)
{
this.init(list, sheetName, Type.EXPORT);
return exportExcel();
}
2.3.1 init方法(初始化)
可以看见,会先调用一个init方法(初始化),传入Type.EXPORT表示为导出数据
public void init(List<T> list, String sheetName, Type type)
{
if (list == null)
{
list = new ArrayList<T>();
}
this.list = list;
this.sheetName = sheetName;
this.type = type;
createExcelField();
createWorkbook();
}
逻辑就是将需要的导出的数据和文件名,及区分导出还是导入的Type赋值给变量。
2.3.2 调用creteExcelField()方法
整体代码
/**
* 得到所有定义字段
*/
private void createExcelField()
{ this.fields = new ArrayList<Object[]>();
List<Field> tempFields = new ArrayList<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
for (Field field : tempFields)
{
// 单注解
if (field.isAnnotationPresent(Excel.class))
{
putToField(field, field.getAnnotation(Excel.class));
}
// 多注解
if (field.isAnnotationPresent(Excels.class))
{
Excels attrs = field.getAnnotation(Excels.class);
Excel[] excels = attrs.value();
for (Excel excel : excels)
{
putToField(field, excel);
}
}
}
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
this.maxHeight = getRowHeight();
}
逐行介绍
/**
* fields字段定义为存储所有注解的字段,为Object[]的数组
* */
this.fields = new ArrayList<Object[]>();
List<Field> tempFields = new ArrayList<>();
/**
* clazz字段存储的是实体类字段
* clazz.getSuperclass()为得到实体类的父类方法
* getDeclaredFields()为反射中的方法,获得某个类的所有声明的字段,即包括public,private
* 和proteced,
* 但是不包括父类的申明字段,所以先获取了父类的声明字段,再获取当前类的声明字段
*
* Array.List()是将数组转化成List集合的方法,用此方法得到的List的长度是不可改变的,
*/
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
/**
*遍历得到的所有字段
*/
for (Field field : tempFields)
{
// 单注解
/**
* isAnnotationPresent() 判断该字段是否标注该注解
*/
if (field.isAnnotationPresent(Excel.class))
{
/**
* 如果标注有@Excel注解,则调用该方法
*/
putToField(field, field.getAnnotation(Excel.class));
}
// 多注解
/**
* 如果字段标注有多个注解,则会进入循环
*/
if (field.isAnnotationPresent(Excels.class))
{
Excels attrs = field.getAnnotation(Excels.class);
Excel[] excels = attrs.value();
for (Excel excel : excels)
{
putToField(field, excel);
}
}
}
/**
* 根据注解中定义的顺序int大小来排序,默认为Integer.MAX_VALUE;
*/
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
this.maxHeight = getRowHeight();
其中又调用了 putToField()方法
将循环if得到的字段和注解参数和放在了fileds<Object[]>中,Object[0]为字段,Object[1]为注解参数
/**
* 放到字段集合中
*/
private void putToField(Field field, Excel attr)
{
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
{
this.fields.add(new Object[] { field, attr });
}
}
和getRowHeight()方法
逐个比较注解参数中的height值,取最大值为最终确定的行高
/**
* 根据注解获取最大行高
*/
public short getRowHeight()
{
double maxHeight = 0;
for (Object[] os : this.fields)
{
Excel excel = (Excel) os[1];
maxHeight = maxHeight > excel.height() ? maxHeight : excel.height();
}
return (short) (maxHeight * 20);
}
2.3.3 createWorkbook()
inti方法的最后一步,调用 createWorkbook()方法
/**
* 创建一个工作簿
*/
public void createWorkbook()
{
this.wb = new SXSSFWorkbook(500);
}
3. exportExcel()
查看路线图
调用完init方法,调用
前半部分
public AjaxResult exportExcel()
{
OutputStream out = null;
try
{
// 取出一共有多少个sheet.
double sheetNo = Math.ceil(list.size() / sheetSize);
for (int index = 0; index <= sheetNo; index++)
{
createSheet(sheetNo, index);
// 产生一行
Row row = sheet.createRow(0);
int column = 0;
// 写入各个字段的列头名称
for (Object[] os : fields)
{
Excel excel = (Excel) os[1];
this.createCell(excel, row, column++);
}
if (Type.EXPORT.equals(type))
{
fillExcelData(index, row);
addStatisticsRow();
}
}
至少有一个sheet,会按顺序执行四个方法createSheet(),createCell,fillExcelData(),addStatisticsRow()
3.1createSheet()方法
/**
* 创建工作表
*
* @param sheetNo sheet数量
* @param index 序号
*/
public void createSheet(double sheetNo, int index)
{
this.sheet = wb.createSheet();
this.styles = createStyles(wb);
// 设置工作表的名称.
if (sheetNo == 0)
{
wb.setSheetName(index, sheetName);
}
else
{
wb.setSheetName(index, sheetName + index);
}
}
createStyles用来创建表格样式,所用方法都是HSSFCellStyle类的方法,理解尚浅解释不了
3.1.1createStyles()
/**
* 创建表格样式
*
* @param wb 工作薄对象
* @return 样式列表
*/
private Map<String, CellStyle> createStyles(Workbook wb)
{
// 写入各条记录,每条记录对应excel表中的一行
Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
CellStyle style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(BorderStyle.THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(BorderStyle.THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
style.setFont(dataFont);
styles.put("data", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font headerFont = wb.createFont();
headerFont.setFontName("Arial");
headerFont.setFontHeightInPoints((short) 10);
headerFont.setBold(true);
headerFont.setColor(IndexedColors.WHITE.getIndex());
style.setFont(headerFont);
styles.put("header", style);
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font totalFont = wb.createFont();
totalFont.setFontName("Arial");
totalFont.setFontHeightInPoints((short) 10);
style.setFont(totalFont);
styles.put("total", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.LEFT);
styles.put("data1", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.CENTER);
styles.put("data2", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(HorizontalAlignment.RIGHT);
styles.put("data3", style);
return styles;
}
3.2createCell()
建立完工作表,写入列头的属性和名称,从注解获得的属性,在这个时候添加
/**
* 创建单元格
*/
public Cell createCell(Excel attr, Row row, int column)
{
// 创建列
Cell cell = row.createCell(column);
// 写入列信息
cell.setCellValue(attr.name());
if (attr.name().indexOf("注:") >= 0)
{
sheet.setColumnWidth(column, 6000);
}
else
{
// 设置列宽
sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
}
// 如果设置了提示信息则鼠标放上去提示.
if (StringUtils.isNotEmpty(attr.prompt()))
{
// 这里默认设了2-101列提示.
setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column);
}
// 如果设置了combo属性则本列只能选择不能输入
if (attr.combo().length > 0)
{
// 这里默认设了2-101列只能选择不能输入.
setXSSFValidation(sheet, attr.combo(), 1, 100, column, column);
}
//创建工作表时构造的Map对象styles
cell.setCellStyle(styles.get("header"));
return cell;
}
3.3fillExcelData()
/**
* 填充excel数据
*
* @param index 序号
* @param row 单元格行
*/
public void fillExcelData(int index, Row row)
{
int startNo = index * sheetSize;
int endNo = Math.min(startNo + sheetSize, list.size());
for (int i = startNo; i < endNo; i++)
{
row = sheet.createRow(i + 1 - startNo);
// 得到导出对象.
T vo = (T) list.get(i);
int column = 0;
for (Object[] os : fields)
{
Field field = (Field) os[0];
Excel excel = (Excel) os[1];
// 设置实体类私有属性可访问
field.setAccessible(true);
this.addCell(excel, row, vo, field, column++);
}
}
}
其中的添加单元格方法
3.3.1addCell()
/**
* 添加单元格
*/
public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
{
Cell cell = null;
try
{
// 设置行高
row.setHeight(maxHeight);
// 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
if (attr.isExport())
{
// 创建cell
cell = row.createCell(column);
int align = attr.align().value();
cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
// 用于读取对象中的属性
Object value = getTargetValue(vo, field, attr);
String dateFormat = attr.dateFormat();
String readConverterExp = attr.readConverterExp();
String separator = attr.separator();
String dictType = attr.dictType();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{
cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
}
else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
{
cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
}
else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value))
{
cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator));
}
else if (value instanceof BigDecimal && -1 != attr.scale())
{
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
}
else
{
// 设置列类型
setCellVo(value, attr, cell);
}
addStatisticsData(column, Convert.toStr(value), attr);
}
}
catch (Exception e)
{
log.error("导出Excel失败{}", e);
}
return cell;
}
3.4 addStatisticsRow()
/**
* 创建统计行
*/
public void addStatisticsRow()
{
if (statistics.size() > 0)
{
Cell cell = null;
Row row = sheet.createRow(sheet.getLastRowNum() + 1);
Set<Integer> keys = statistics.keySet();
cell = row.createCell(0);
cell.setCellStyle(styles.get("total"));
cell.setCellValue("合计");
for (Integer key : keys)
{
cell = row.createCell(key);
cell.setCellStyle(styles.get("total"));
cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
}
statistics.clear();
}
}
exportExcel方法的后半部分
随机生成文件名,并将表格数据写入文件,返回给前端文件名
String filename = encodingFilename(sheetName);
out = new FileOutputStream(getAbsoluteFile(filename));
wb.write(out);
return AjaxResult.success(filename);