写在前面
- 示例写出时间:2024-12-02
这仅仅是excel读取的一个示例, 记录一下,这里也改了一下之前的导出,主要是为了兼容读取
之前的博客地址 基于poi和JavaBean的excel导出
poi依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
工具类
日期工具类DateUtil
package com.littlehow.util;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
public class DateUtil {
public static final int HOUR = 1000 * 60 * 60;
public static final long DAY = HOUR * 24;
public static final int NANO = 1000000;
private static final DateTimeFormatter DATE_FORMAT_NO_SPLIT = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter DATE_FORMAT_DOT_SPLIT = DateTimeFormatter.ofPattern("yyyy.MM.dd");
public static final DateTimeFormatter DATE_FORMAT_HAS_SPLIT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter ALL = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static int zoneOffsetMilliseconds = TimeZone.getDefault().getRawOffset();
private static ZoneOffset zoneOffset = ZoneOffset.ofHours(zoneOffsetMilliseconds / HOUR);
/**
* 设置全局时区
*
* @param hour -
*/
public static void setZoneOffset(int hour) {
zoneOffset = ZoneOffset.ofHours(hour);
zoneOffsetMilliseconds = hour * HOUR;
}
public static long getTime(LocalDateTime localDateTime) {
return localDateTime.toEpochSecond(zoneOffset) * 1000L + localDateTime.getNano() / NANO;
}
public static long getTime(LocalDate localDate) {
return localDate.toEpochDay() * DAY - zoneOffsetMilliseconds;
}
public static LocalDateTime toLocalDateTime(long milliseconds) {
long second = milliseconds / 1000;
long na = (milliseconds % 1000) * NANO;
return LocalDateTime.ofEpochSecond(second, (int) na, zoneOffset);
}
public static LocalDate toLocalDate(long milliseconds) {
return LocalDate.ofEpochDay((milliseconds + zoneOffsetMilliseconds) / DAY);
}
public static LocalDateTime toLocalDateTime(String datetime) {
return LocalDateTime.parse(datetime, ALL);
}
public static String formatDate(LocalDate localDate) {
return localDate.format(DATE_FORMAT_NO_SPLIT);
}
public static String formatDate(LocalDateTime localDateTime) {
return localDateTime.format(ALL);
}
public static boolean isYesterday(LocalDate localDate) {
return localDate != null && localDate.plusDays(1).compareTo(LocalDate.now()) == 0;
}
public static LocalDateTime toZone(LocalDateTime time, ZoneId fromZone, ZoneId toZone) {
return time.atZone(fromZone).withZoneSameInstant(toZone).toLocalDateTime();
}
public static LocalDateTime toZone(LocalDateTime time, ZoneId toZone) {
return toZone(time, zoneOffset, toZone);
}
public static LocalDateTime toZone(LocalDateTime time, String zoneId) {
try {
if (StringUtils.hasText(zoneId)) {
return toZone(time, ZoneId.of(zoneId));
} else {
return time;
}
} catch (Exception e) {
return time;
}
}
public static LocalDate parseDot(String date) {
if (StringUtils.hasText(date)) {
return LocalDate.parse(date, DATE_FORMAT_DOT_SPLIT);
}
return null;
}
public static void main(String[] argv) {
System.out.println(toLocalDateTime(1629300273000L));
System.out.println(getTime(LocalDate.now()));
LocalDate date = toLocalDate(1631116800000L);
LocalDate end = LocalDate.now();
System.out.println(date);
// checkDateRange(date, end, 30);
// System.out.println(ZoneId.of("+08:00"));
// System.out.println(ZoneId.of("-11:00"));
}
}
核心类
注解ExcelColumn
package com.littlehow.excel;
import java.lang.annotation.*;
/**
* @author littlehow
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ExcelColumn {
/**
* @return - 表头
*/
String title();
/**
* 第几列
* @return - 从1开始, 如果是读取excel,这个列很重要
*/
int column();
/**
* @return 日期格式化
*/
String format() default "";
/**
* 转义值 格式如 1=发送中,2=完成
* @return -
*/
String transfer() default "";
/**
* @return 行高
*/
short height() default 450;
/**
* @return - 列宽
*/
int width() default 4800;
}
列注解属性ExcelColumnMeta
package com.littlehow.excel;
import com.littlehow.util.DateUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Map;
/**
* @author littlehow
*/
@Setter
@Getter
@Accessors(chain = true)
public class ExcelColumnMeta {
/**
* 表头
*/
private String title;
/**
* 行高
*/
private short height;
/**
* 列宽
*/
private int width;
/**
* 日期格式化
*/
private DateTimeFormatter formatter;
private Map<String, String> transfer;
private Map<String, String> transferRevert;
public String getDateString(TemporalAccessor accessor) {
if (formatter != null) {
return formatter.format(accessor);
}
return accessor.toString();
}
public String getDateString(Long time) {
if (time == null) {
return "";
} else if (formatter == null) {
return time.toString();
} else {
return formatter.format(DateUtil.toLocalDateTime(time));
}
}
public String getValue(Object obj) {
String value = obj.toString();
if (transfer != null) {
String temp = transfer.get(value);
if (temp != null) {
value = temp;
}
}
return value;
}
public String getTransferValue(String value) {
if (transferRevert != null) {
String revert = transferRevert.get(value);
if (revert != null) {
return revert;
}
}
return value;
}
public DateTimeFormatter getFormatter() {
if (formatter == null) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd");
}
return formatter;
}
}
列字段属性ExcelMetaData
package com.littlehow.excel;
import com.littlehow.excel.support.IExcelFieldExecute;
import com.littlehow.util.DateUtil;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAccessor;
/**
* @author littlehow
*/
@Setter
@Getter
public class ExcelMetaData {
/**
* 字段
*/
private Field field;
/**
* 对应的get方法
*/
private Method getMethod;
/**
* 对应的set方法
*/
private Method setMethod;
/**
* 处理方式
*/
private String[] processIds;
/**
* 列标
*/
private int column;
/**
* 日期顶级接口
*/
private boolean isTemporalAccessor = false;
/**
* 是否为decimal类型
*/
private boolean isDecimal = false;
/**
* 是否为long的日期类型
*/
private boolean isLongDate = false;
/**
* 字段属性
*/
private ExcelColumnMeta columnMeta;
/**
* 获取对象的值,做简单类型的格式化
* @param obj - 对象
* @return - obj对应该字段的格式化值
*/
public String getString(Object obj) {
if (obj == null) {
return "";
}
Object value = getValue(obj);
if (value == null) {
return "";
}
if (processIds != null && processIds.length > 0) {
for (String processId : processIds) {
IExcelFieldExecute execute = IExcelFieldExecute.resolve.get(processId);
if (execute != null) {
value = execute.process(value);
if (execute.canBreak()) {
return value.toString();
}
}
}
}
if (isTemporalAccessor) {
return columnMeta.getDateString((TemporalAccessor) value);
} else if (isLongDate) {
return columnMeta.getDateString((Long) value);
} else if (isDecimal) {
return ((BigDecimal) value).stripTrailingZeros().toPlainString();
} else {
return columnMeta.getValue(value.toString());
}
}
/**
* 设置列值
* @param obj - 实例
* @param value - 值
*/
public void setColumnValue(Object obj, String value) {
Object objValue = getColumnValue(value);
if (objValue != null) {
setValue(obj, objValue);
}
}
/**
* 支持字符串、日期、int、long、boolean
* 后续需要增加支持类型,在此继续追加解析即可
* @param value - excel内容
* @return - 值
*/
private Object getColumnValue(String value) {
if (value == null) {
return null;
}
if (processIds != null && processIds.length > 0) {
for (String processId : processIds) {
IExcelFieldExecute execute = IExcelFieldExecute.resolve.get(processId);
if (execute != null) {
value = execute.convert(value);
}
}
}
// 如果有transfer的需要进行转换
value = columnMeta.getTransferValue(value);
Class<?> type = field.getType();
if (type == String.class) {
return value;
}
// 如果是非字符串类型的,又没有内容的就直接返回null
if (!StringUtils.hasText(value)) {
return null;
}
if (isTemporalAccessor) {
// 转换为日期或者时间
if (type == LocalDate.class) {
return LocalDate.parse(value, columnMeta.getFormatter());
} else if (type == LocalDateTime.class) {
return LocalDateTime.parse(value, columnMeta.getFormatter());
} else {
return LocalTime.parse(value, columnMeta.getFormatter());
}
} else if (isLongDate) {
// 智能转换为时间
LocalDateTime time = LocalDateTime.parse(value, columnMeta.getFormatter());
return DateUtil.getTime(time);
} else if (isDecimal) {
return new BigDecimal(value);
} else if (type == Integer.class || type == int.class){
return Integer.valueOf(value);
} else if (type == Long.class || type == long.class) {
return Long.valueOf(value);
} else if (type == Boolean.class || type == boolean.class) {
return Boolean.valueOf(value);
} else {
return value;
}
}
/**
* 获取对象的值, 优先get方法获取,没有get方法的将使用field获取,所以如果没有提供get方法,需要将字段访问属性设置为public
* 这里就不对字段进行field.setAccessible设置
* @param obj - 对象
* @return - 对应对应的值
*/
private Object getValue(Object obj) {
try {
if (getMethod != null) {
return getMethod.invoke(obj);
} else {
return field.get(obj);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setValue(Object obj, Object value) {
try {
if (setMethod != null) {
setMethod.invoke(obj, value);
} else {
field.set(obj, value);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
excel导出属性ExcelMeta
package com.littlehow.excel;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.function.Consumer;
/**
* 记录表宽度、表头、行高以及excel字段信息
* @author littlehow
*/
@Setter
@Getter
public class ExcelMeta {
private Class clazz;
/**
* 列宽
*/
private List<Integer> width;
/**
* 行高
*/
private short height;
/**
* 表头
*/
private List<String> header;
/**
* 字段属性集
*/
private List<ExcelMetaData> excelMetaData;
public Object instance() {
try {
return clazz.newInstance();
} catch (InstantiationException|IllegalAccessException e) {
throw new RuntimeException("class [" + clazz + "] must have a public non-param constructor function");
}
}
public void foreach(Consumer<ExcelMetaData> action) {
excelMetaData.forEach(action);
}
}
excel属性缓存类ExcelClassCache
package com.littlehow.excel;
import com.littlehow.excel.support.ExcelFilterProcess;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author littlehow
*/
public class ExcelClassCache {
private static Map<Class, ExcelMeta> EXCEL_MATE_CACHE = new HashMap<>();
/**
* 获取meta配置数据
* @param clazz - 类
* @return -
*/
static ExcelMeta getMeta(Class clazz) {
ExcelMeta meta = EXCEL_MATE_CACHE.get(clazz);
if (meta != null) {
return meta;
}
meta = new ExcelMeta();
meta.setClazz(clazz);
// 解析class
List<Field> fields = new ArrayList<>();
getAllField(fields, clazz);
List<ExcelMetaData> metaData = new ArrayList<>();
fields.forEach(o -> {
if (o.isAnnotationPresent(ExcelColumn.class)) {
ExcelMetaData excelMetaData = new ExcelMetaData();
ExcelColumn excelColumn = o.getAnnotation(ExcelColumn.class);
ExcelColumnMeta columnMeta = new ExcelColumnMeta()
.setHeight(excelColumn.height())
.setTitle(excelColumn.title())
.setWidth(excelColumn.width());
if (StringUtils.hasText(excelColumn.transfer())) {
columnMeta.setTransfer(getTransfer(excelColumn.transfer()));
columnMeta.setTransferRevert(getTransferRevert(columnMeta.getTransfer()));
}
if (TemporalAccessor.class.isAssignableFrom(o.getType())) {
excelMetaData.setTemporalAccessor(true);
String format = excelColumn.format();
if (StringUtils.hasText(format)) {
columnMeta.setFormatter(DateTimeFormatter.ofPattern(format));
}
} else if (o.getType() == Long.class && StringUtils.hasText(excelColumn.format())) {
columnMeta.setFormatter(DateTimeFormatter.ofPattern(excelColumn.format()));
excelMetaData.setLongDate(true);
} else if (o.getType() == BigDecimal.class) {
excelMetaData.setDecimal(true);
}
excelMetaData.setField(o);
excelMetaData.setGetMethod(getGetMethod(o, clazz));
excelMetaData.setSetMethod(getSetMethod(o, clazz));
excelMetaData.setColumnMeta(columnMeta);
excelMetaData.setColumn(excelColumn.column());
// 判断是否存在特殊处理器
if (o.isAnnotationPresent(ExcelFilterProcess.class)) {
ExcelFilterProcess process = o.getAnnotation(ExcelFilterProcess.class);
excelMetaData.setProcessIds(process.value());
}
metaData.add(excelMetaData);
}
});
Assert.notEmpty(metaData, "excel config not found");
metaData.sort(Comparator.comparing(ExcelMetaData::getColumn));
meta.setExcelMetaData(metaData);
meta.setWidth(metaData.stream().map(o -> o.getColumnMeta().getWidth()).collect(Collectors.toList()));
meta.setHeight(metaData.get(0).getColumnMeta().getHeight());
meta.setHeader(metaData.stream().map(o -> o.getColumnMeta().getTitle()).collect(Collectors.toList()));
EXCEL_MATE_CACHE.put(clazz, meta);
return meta;
}
/**
* 获取所有字段,包括父类的字段
* @param fieldList -
* @param clazz -
*/
private static void getAllField(List<Field> fieldList, Class clazz) {
if (clazz == Object.class) {
return;
}
Field[] fields = clazz.getDeclaredFields();
fieldList.addAll(Arrays.asList(fields));
getAllField(fieldList, clazz.getSuperclass());
}
/**
* 获取字段对应的get方法
* @param field -
* @param clazz -
* @return -
*/
private static Method getGetMethod(Field field, Class<?> clazz) {
try {
String name = field.getName();
if (name.length() > 1) {
return clazz.getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
} else {
return clazz.getMethod("get" + name.toUpperCase());
}
} catch (Exception e) {
// skip;
return null;
}
}
private static Method getSetMethod(Field field, Class<?> clazz) {
try {
String name = field.getName();
if (name.length() > 1) {
return clazz.getMethod("set" + name.substring(0, 1).toUpperCase() + name.substring(1), field.getType());
} else {
return clazz.getMethod("set" + name.toUpperCase(), field.getType());
}
} catch (Exception e) {
// skip;
return null;
}
}
private static Map<String, String> getTransfer(String value) {
String[] values = value.split(",");
Map<String, String> transfer = new HashMap<>();
for (String v : values) {
String[] info = v.split("=");
transfer.put(info[0], info[1]);
}
return transfer;
}
private static Map<String, String> getTransferRevert(Map<String, String> map) {
Map<String, String> revert = new HashMap<>();
map.forEach((key, value) -> revert.put(value, key));
return revert;
}
}
excel导出类ExcelWrite
package com.littlehow.excel;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @author littlehow
*/
public class ExcelWrite<T> {
private SXSSFWorkbook workbook;
private SXSSFSheet sheet;
private int rowIndex = 0;
private boolean create = false;
private ExcelMeta excelMeta;
private XSSFCellStyle cellStyle;
private ExcelWrite() {
// 不开放
}
/**
* 构建excel导出类
* @param clazz - 导出类型
* @param <T> -
* @return - 导出类
*/
public static <T> ExcelWrite<T> build(Class<T> clazz) {
ExcelWrite<T> excel = new ExcelWrite<>();
excel.excelMeta = ExcelClassCache.getMeta(clazz);
excel.workbook = new SXSSFWorkbook(500);
excel.sheet = excel.workbook.createSheet();
excel.cellStyle = (XSSFCellStyle) excel.workbook.createCellStyle();
excel.cellStyle.setAlignment(HorizontalAlignment.CENTER);
excel.cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置列头以及列宽
excel.fullHeader();
return excel;
}
/**
* 填充类容
* @param content - 类容
*/
public ExcelWrite<T> write(List<T> content) {
Assert.isTrue(!create, "excel already created");
if (CollectionUtils.isEmpty(content)) {
return this;
}
content.forEach(t -> {
// 创建表头
SXSSFRow row = sheet.createRow(this.rowIndex++);
row.setHeight(excelMeta.getHeight());
setContent(row, getContent(t));
});
return this;
}
/**
* 输出 本方法没有关闭外部输出流
* @param os - 输出流
* @throws IOException - io异常
*/
public void create(OutputStream os) throws IOException {
workbook.write(os);
create = true;
}
private void fullHeader() {
setColumnWidth(this.sheet, excelMeta.getWidth());
// 设置表头
SXSSFRow header = this.sheet.createRow(this.rowIndex++);
header.setHeight(excelMeta.getHeight());
setContent(header, excelMeta.getHeader());
}
private List<String> getContent(T t) {
List<ExcelMetaData> excelMetaData = excelMeta.getExcelMetaData();
List<String> values = new ArrayList<>();
excelMetaData.forEach(data -> values.add(data.getString(t)));
return values;
}
private void setColumnWidth(SXSSFSheet sheet, List<Integer> columnWidths) {
for (int i = 0, len = columnWidths.size(); i < len; i++) {
sheet.setColumnWidth(i, columnWidths.get(i));
}
}
private void setContent(SXSSFRow row, List<String> content) {
// 设置第一行
for (int i = 0, len = content.size(); i < len; i++) {
SXSSFCell cell = row.createCell(i);
cell.setCellValue(content.get(i));
// 创建一个单元格样式
cell.setCellStyle(cellStyle);
}
}
}
此次新增的类
excel读取类ExcelRead
package com.littlehow.excel;
import com.littlehow.excel.support.IReadExcelExecute;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author littlehow
* @since 11/30/24 12:35
*/
public class ExcelRead<T> {
private ExcelMeta excelMeta;
private IReadExcelExecute<T> execute;
private int startRowIndex;
private ExcelRead() {
// 不开放
}
public static <T> ExcelRead<T> build(Class<T> clazz, IReadExcelExecute<T> execute) {
ExcelRead<T> excel = new ExcelRead<>();
excel.excelMeta = ExcelClassCache.getMeta(clazz);
excel.execute = execute;
excel.startRowIndex = 0;
return excel;
}
public ExcelRead<T> setStartRowIndex(int startRowIndex) {
this.startRowIndex = startRowIndex;
return this;
}
/**
* 读取本地文件
* @param file - 文件
*/
public void read(File file) {
try {
read(new FileInputStream(file));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 读取本地文件
* @param file - 文件
*/
public void read(String file) {
try {
read(new FileInputStream(file));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 读取本地文件
* @param is - 输入流
*/
public void read(InputStream is) {
try {
OPCPackage pkg = OPCPackage.open(is);
XSSFReader reader = new XSSFReader(pkg);
SharedStringsTable sst = reader.getSharedStringsTable();
XMLReader parser = XMLReaderFactory.createXMLReader();
ExcelHandler handler = new ExcelHandler(sst);
parser.setContentHandler(handler);
Iterator<InputStream> sheetData = reader.getSheetsData();
while (sheetData.hasNext()) {
InputStream sheetStream = sheetData.next();
parser.parse(new InputSource(sheetStream));
sheetStream.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 自定义 SAX 事件处理器
private class ExcelHandler extends DefaultHandler {
private final SharedStringsTable sst;
// 单元格内容
private String lastContents;
// 是否为共享字符串
private boolean isString;
// 是否在单元格中
private boolean isCellOpen;
// 当前行标
private int rowIndex;
// 当前行的内容
private List<String> currentRow;
// 当要以list输出时,缓存还没输出的数据
private List<T> dataCache;
ExcelHandler(SharedStringsTable sst) {
this.sst = sst;
if (execute.isList()) {
dataCache = new ArrayList<>();
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if ("row".equals(qName)) {
// 行的开始
currentRow = new ArrayList<>();
rowIndex += 1;
} else if ("c".equals(qName)) {
// 单元格为字母c
String cellType = attributes.getValue("t");
// 判断是否为共享字符串
isString = "s".equals(cellType);
isCellOpen = true;
}
// 清空内容
lastContents = "";
}
@Override
public void characters(char[] ch, int start, int length) {
if (isCellOpen) {
lastContents += new String(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) {
if ("c".equals(qName)) {
// 单元格结束
String value = lastContents.trim();
if (isString) {
// 如果是共享字符串,该内容是数字索引
int idx = Integer.parseInt(value);
value = sst.getItemAt(idx).getString();
}
// 添加到当前行, 列数据最后可以按照下标读取
currentRow.add(value);
isCellOpen = false;
} else if ("row".equals(qName) && rowIndex >= startRowIndex) {
// 行结束
T t = convert();
if (execute.isList()) {
dataCache.add(t);
if (dataCache.size() >= execute.getPageSize()) {
execute.processList(dataCache);
dataCache = new ArrayList<>();
}
} else {
execute.process(t);
}
}
}
@Override
public void startDocument() {
// 设置初始值
rowIndex = -1;
}
@Override
public void endDocument() {
if (dataCache != null && dataCache.size() > 0) {
execute.processList(dataCache);
dataCache = new ArrayList<>();
}
}
private T convert() {
Object data = excelMeta.instance();
excelMeta.foreach(o -> {
int column = o.getColumn();
if (column <= currentRow.size()) {
o.setColumnValue(data, currentRow.get(column - 1));
}
});
return (T)data;
}
}
}
支撑类IExcelFieldExecute
package com.littlehow.excel.support;
import org.springframework.beans.factory.InitializingBean;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* littlehow
*/
public interface IExcelFieldExecute extends InitializingBean {
Map<String, IExcelFieldExecute> resolve = new ConcurrentHashMap<>();
Object process(Object value);
default String convert(String value) {
return value;
}
default String getId() {
return this.getClass().getSimpleName();
}
default boolean canBreak() {
return false;
}
default void afterPropertiesSet() {
IExcelFieldExecute old = resolve.get(getId());
if (old != null) {
throw new IllegalArgumentException("duplicate serialize key " + old.getId() + ", class " + old.getClass());
}
resolve.put(getId(), this);
}
}
支撑类IReadExcelExecute
package com.littlehow.excel.support;
import java.util.List;
/**
* @author littlehow
* @since 12/2/24 11:30
*/
public interface IReadExcelExecute<T> {
void process(T data);
default void processList(List<T> data) {
// do nothing
}
default boolean isList() {
return false;
}
default int getPageSize() {
return 1000;
}
}
支撑注解ExcelFilterProcess
package com.littlehow.excel.support;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ExcelFilterProcess {
String[] value();
}
测试类
测试实体类:
package com.littlehow.excel.test.bo;
import com.littlehow.excel.ExcelColumn;
import com.littlehow.excel.support.ExcelFilterProcess;
import com.littlehow.util.DateUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* @author littlehow
* @since 11/30/24 12:31
*/
@Getter
@Setter
@Accessors(chain = true)
public class Product {
@ExcelColumn(title = "产品名称", column = 1)
private String name;
@ExcelColumn(title = "价格", column = 2)
private BigDecimal price;
@ExcelColumn(title = "状态", column = 3, transfer = "1=上架,0=下架")
private Integer status;
@ExcelColumn(title = "手续费", column = 4)
@ExcelFilterProcess("FeeExcelFilterProcess")
private BigDecimal fee;
@ExcelColumn(title = "生产日期", column = 5, format = "yyyy-MM-dd")
private LocalDate birthday;
@ExcelColumn(title = "更新时间", column = 6, format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
@Override
public String toString() {
return "{\"name\":\"" + name + "\",\"price\":"
+ (price == null ? "null" : price.stripTrailingZeros().toPlainString())
+ ",\"status\":" + status + ",\"fee\":"
+ (fee == null ? "null" : fee.stripTrailingZeros().toPlainString())
+ ",\"birthday\":\""
+ (birthday == null ? "" : DateUtil.formatDate(birthday))
+ "\",\"updateTime\":\""
+ (updateTime == null ? "" : DateUtil.formatDate(updateTime))
+ "\"}";
}
}
测试excel手续费字段特别处理类:FeeExcelFilterProcess
package com.littlehow.excel.test;
import com.littlehow.excel.support.IExcelFieldExecute;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @author littlehow
* @since 12/2/24 13:26
*/
public class FeeExcelFilterProcess implements IExcelFieldExecute {
private static final BigDecimal HUNDRED = new BigDecimal("100");
@Override
public Object process(Object value) {
if (value == null) {
return null;
}
if (value instanceof BigDecimal) {
BigDecimal decimal = (BigDecimal) value;
return decimal.multiply(HUNDRED).stripTrailingZeros().toPlainString() + "%";
}
return value;
}
@Override
public boolean canBreak() {
return true;
}
@Override
public String convert(String value) {
if (value == null || !value.contains("%")) {
return value;
}
return new BigDecimal(value.replace("%", "")).divide(HUNDRED, 6, RoundingMode.DOWN)
.stripTrailingZeros().toPlainString();
}
}
测试excel导出类:WriteExcelTest
package com.littlehow.excel.test;
import com.littlehow.excel.ExcelWrite;
import com.littlehow.excel.test.bo.Product;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* @author littlehow
* @since 11/30/24 12:09
*/
public class WriteExcelTest {
public static void main(String[] args) {
testWrite();
}
private static void testWrite() {
FeeExcelFilterProcess process = new FeeExcelFilterProcess();
process.afterPropertiesSet();
List<Product> data = new ArrayList<>();
data.add(new Product().setBirthday(LocalDate.now().minusDays(65))
.setFee(new BigDecimal("0.015"))
.setName("汇源果汁")
.setPrice(new BigDecimal("2.5"))
.setStatus(1)
.setUpdateTime(LocalDateTime.now()));
data.add(new Product().setBirthday(LocalDate.now().minusDays(365))
.setFee(new BigDecimal("0.03"))
.setName("农夫山泉")
.setPrice(new BigDecimal("2"))
.setStatus(0)
.setUpdateTime(LocalDateTime.now().minusMinutes(35)));
data.add(new Product().setBirthday(LocalDate.now().minusDays(365))
.setFee(new BigDecimal("0.03"))
.setName("娃哈哈")
.setPrice(new BigDecimal("2"))
.setStatus(0)
.setUpdateTime(LocalDateTime.now().minusMinutes(35)));
data.add(new Product().setBirthday(LocalDate.now().minusDays(365))
.setFee(new BigDecimal("0.03"))
.setName("清凉油")
.setPrice(new BigDecimal("2"))
.setStatus(0)
.setUpdateTime(LocalDateTime.now().minusMinutes(35)));
data.add(new Product().setBirthday(LocalDate.now().minusDays(365))
.setFee(new BigDecimal("0.03"))
.setName("脉动")
.setPrice(new BigDecimal("2"))
.setStatus(1)
.setUpdateTime(LocalDateTime.now().minusMinutes(35)));
data.add(new Product().setFee(new BigDecimal("0.03"))
.setName("秘制水哦")
.setPrice(new BigDecimal("2"))
.setStatus(1)
.setUpdateTime(LocalDateTime.now().minusMinutes(35)));
ExcelWrite<Product> excel = ExcelWrite.build(Product.class);
try {
excel.write(data).create(new FileOutputStream("/usr/local/excel/test.xlsx"));
System.out.println(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
导出效果如下
对相同的excel进行读取(这里读取是可以多sheet读入的, 每个sheet保持格式一致即可)
测试读取处理类(该类主要演示list):MyReadExcelExecute
package com.littlehow.excel.test;
import com.littlehow.excel.support.IReadExcelExecute;
import com.littlehow.excel.test.bo.Product;
import java.util.List;
/**
* @author littlehow
* @since 12/2/24 13:32
*/
public class MyReadExcelExecute implements IReadExcelExecute<Product> {
@Override
public void process(Product data) {
// do nothing
}
public void processList(List<Product> data) {
System.out.println(data);
}
public boolean isList() {
return true;
}
}
测试读取类:ReadExcelTest
package com.littlehow.excel.test;
import com.littlehow.excel.ExcelRead;
import com.littlehow.excel.test.bo.Product;
/**
* @author littlehow
* @since 11/30/24 12:35
*/
public class ReadExcelTest {
public static void main(String[] args) {
testRead();
}
private static void testRead() {
FeeExcelFilterProcess process = new FeeExcelFilterProcess();
process.afterPropertiesSet();
// 这里进行单行读取,将每行读取结果打印
ExcelRead<Product> excel = ExcelRead.build(Product.class, System.out::println);
excel.setStartRowIndex(1).read("/usr/local/excel/test.xlsx");
// 通过list读取
excel = ExcelRead.build(Product.class, new MyReadExcelExecute());
excel.setStartRowIndex(1).read("/usr/local/excel/test.xlsx");
}
}
执行结果:
{"name":"汇源果汁","price":2.5,"status":1,"fee":0.015,"birthday":"20240928","updateTime":"2024-12-02 13:38:01"}
{"name":"农夫山泉","price":2,"status":0,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}
{"name":"娃哈哈","price":2,"status":0,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}
{"name":"清凉油","price":2,"status":0,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}
{"name":"脉动","price":2,"status":1,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}
{"name":"秘制水哦","price":2,"status":1,"fee":0.03,"birthday":"","updateTime":"2024-12-02 13:03:01"}
========================== 下面是list处理结果哦 =====================
[{"name":"汇源果汁","price":2.5,"status":1,"fee":0.015,"birthday":"20240928","updateTime":"2024-12-02 13:38:01"}, {"name":"农夫山泉","price":2,"status":0,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}, {"name":"娃哈哈","price":2,"status":0,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}, {"name":"清凉油","price":2,"status":0,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}, {"name":"脉动","price":2,"status":1,"fee":0.03,"birthday":"20231203","updateTime":"2024-12-02 13:03:01"}, {"name":"秘制水哦","price":2,"status":1,"fee":0.03,"birthday":"","updateTime":"2024-12-02 13:03:01"}]