基于poi和javabean的excel读取

news2024/12/27 8:05:42

写在前面

  • 示例写出时间: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"}]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一键生成后端服务,MemFire Cloud重新定义开发效率

作为开发者&#xff0c;特别是独立开发者和小团队成员&#xff0c;大家都知道开发的最大难题之一就是搭建后端服务。要让一个应用从零开始&#xff0c;除了前端的开发工作外&#xff0c;还需要考虑数据库、接口、认证、存储等等一系列繁琐的后台工作。而MemFire Cloud这款神器&…

Maven、JAVAWeb、Servlet

知识点目标 1、MavenMaven是什么Maven项目的目录结构Maven的Pom文件Maven的命令Maven依赖管理Maven仓库JavaWeb项目 2.网络基础知识 3、ServletMaven Maven是什么 Maven是Java的项目管理工具&#xff0c;可以构建&#xff0c;打包&#xff0c;部署项目&#xff0c;还可以管理…

controller中的参数注解@Param @RequestParam和@RequestBody的不同

现在controller中有个方法&#xff1a;&#xff08;LoginUserRequest是一个用户类对象&#xff09; PostMapping("/test/phone")public Result validPhone(LoginUserRequest loginUserRequest) {return Result.success(loginUserRequest);}现在讨论Param("login…

Linux 内核系统架构

Linux 内核是一个复杂且高度模块化的系统&#xff0c;负责操作硬件资源、管理进程和内存、提供网络服务、执行文件系统操作、进行设备驱动程序的管理等。它为用户空间提供了一个抽象层&#xff0c;并为应用程序提供了底层服务。本文将深入探讨 Linux 内核的系统架构&#xff0c…

AI开发:逻辑回归 - 实战演练- 垃圾邮件的识别(二)

接上一篇AI开发&#xff1a;逻辑回归 - 实战演练- 垃圾邮件的识别&#xff08;一&#xff09; new_email 无论为什么文本&#xff0c;识别结果几乎都是垃圾邮件,因此我们需要对源码的逻辑进行梳理一下&#xff1a; 在代码中&#xff0c;new_email 无论赋值为何内容都被识别为…

WPF+MVVM案例实战与特效(三十)- 封装一个系统日志显示控件

文章目录 1、运行效果2、日志控件封装1、文件创建2、DisplayLogPanel.xaml 代码3、DisplayLogPanel.cs 代码4、数据模型5、枚举类型3、自定义控件使用1、LogPanelWindow.xaml2、LogPanelViewModel.cs4、总结1、运行效果 2、日志控件封装 1、文件创建 打开 Wpf_Examples ,在 …

VideoBooth: Diffusion-based Video Generation with Image Prompts

VideoBooth: Diffusion-based Video Generation with Image Prompts 概括 文章提出了一个视频生成模型VideoBooth&#xff0c;输入一张图片和一个文本提示词&#xff0c;即可输出保持图片中物体且符合文本提示词要求的视频。 方法 粗-细两阶段设计&#xff1a;1&#xff09;…

电子电气架构 --- 面向服务的汽车诊断架构

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所有人的看法和评价都是暂时的,只有自己的经历是伴随一生的,几乎所有的担忧和畏惧,都是来源于自己的想象,只有你真的去做了,才会发现有多快乐。…

生成树详解(STP、RSTP、MSTP)

目录 1、STP 1.概述 2.基本概念 3.端口角色及其作用 4.报文结构 5.STP的端口状态 6.三种定时器 7.STP选举步骤 8.配置BPDU的比较原则 9.TCN BPDU 10.临时环路的问题 11.传统STP的不足 拓扑变更处理过程 2、RSTP 1.端口角色 2.端口状态 3.P/A&#xff08;Propo…

基于Python制作一个简易UI界面

基于Python制作一个简易UI界面 目录 基于Python制作一个简易UI界面1 原理简介2 编写程序3 程序测试 1 原理简介 这里用到了Python自带的UI库tkinter。 tkinter 是 Python 的标准 GUI&#xff08;图形用户界面&#xff09;库&#xff0c;用于创建和管理图形界面。它提供了一个简…

emp.dll丢失导致游戏/软件无法继续运行:详细描述emp.dll丢失原因并提供解决方案

emp.dll 并不是一个标准的 Windows 系统文件&#xff0c;也不是一个广泛认知的第三方库。因此&#xff0c;它可能是一个特定于某个应用程序或游戏的自定义 DLL 文件。如果 emp.dll 丢失导致了你的软件或游戏无法运行&#xff0c;这通常意味着该文件是程序正常运作所必需的。下面…

IDEA使用HotSwapHelper进行热部署

目录 前言JDK1.8特殊准备DECVM安装插件安装与配置参考文档相关下载 前言 碰到了一个项目&#xff0c;用jrebel启动项目时一直报错&#xff0c;不用jrebel时又没问题&#xff0c;找不到原因&#xff0c;又不想放弃热部署功能 因此思考能否通过其他方式进行热部署&#xff0c;找…

droppath

DropPath 是一种用于正则化深度学习模型的技术&#xff0c;它在训练过程中随机丢弃路径&#xff08;或者说随机让某些部分的输出变为零&#xff09;&#xff0c;从而增强模型的鲁棒性和泛化能力。 代码解释&#xff1a; import torch import torch.nn as nn # 定义 DropPath…

机器学习算法(六)---逻辑回归

常见的十大机器学习算法&#xff1a; 机器学习算法&#xff08;一&#xff09;—决策树 机器学习算法&#xff08;二&#xff09;—支持向量机SVM 机器学习算法&#xff08;三&#xff09;—K近邻 机器学习算法&#xff08;四&#xff09;—集成算法 机器学习算法&#xff08;五…

Ubuntu24.04初始化教程(包含基础优化、ros2)

将会不断更新。但是所有都是基础且必要的操作。 为重装系统之后的环境配置提供便捷信息来源。记录一些错误的解决方案。 目录 构建系统建立系统备份**Timeshift: 系统快照和备份工具****安装 Timeshift****使用 Timeshift 创建快照****还原快照****自动创建快照** 最基本配置换…

【Maven】Nexus私服

6. Maven的私服 6.1 什么是私服 Maven 私服是一种特殊的远程仓库&#xff0c;它是架设在局域网内的仓库服务&#xff0c;用来代理位于外部的远程仓库&#xff08;中央仓库、其他远程公共仓库&#xff09;。一些无法从外部仓库下载到的构件&#xff0c;如项目组其他人员开发的…

Gradle vs. Maven: 到底哪个更适合java 项目?

ApiHug ApiHug - API Design & Develop New Paradigm.ApiHug - API Design & Develop New Paradigm.https://apihug.com/ 首先 ApiHug 整个工具链是基于 gradle 构建,包括项目模版&#xff0c; 插件&#xff1b; 说到 Java 项目管理&#xff0c;有两个巨头脱颖而出&a…

Dubbo的集群容错策略有哪些?它们的工作原理是什么?

大家好&#xff0c;我是锋哥。今天分享关于【Dubbo的集群容错策略有哪些&#xff1f;它们的工作原理是什么&#xff1f;】面试题。希望对大家有帮助&#xff1b; Dubbo的集群容错策略有哪些&#xff1f;它们的工作原理是什么&#xff1f; 1000道 互联网大厂Java工程师 精选面试…

分治的思想(力扣965、力扣144、牛客KY11)

引言 分治思想是将问题分解为更小子问题&#xff0c;分别解决后再合并结果。二叉树中常用此思想&#xff0c;因其结构递归&#xff0c;易分解为左右子树问题&#xff0c;递归解决后合并结果。 这篇文章会讲解用分治的思想去解决二叉树的一些题目&#xff0c;顺便会强调在做二…

中国电信张宝玉:城市数据基础设施建设运营探索与实践

11月28日&#xff0c;2024新型智慧城市发展创新大会在山东青岛召开&#xff0c;中国电信数字政府研究院院长张宝玉在大会发表主旨演讲《城市数据基础设施运营探索与实践》。报告内容包括城市数据基础设施的概述、各地典型做法及发展趋势建议三个方面展开。 篇幅限制&#xff0…