Excel工具类实现——基于poi5.2.3

news2024/12/28 4:10:35

初衷仅是因为网上的Excel工具类不是太老旧的方法,就是不是很满足我想法,就想要自己搞一个,不过还不支持合并单元格等复杂的操作,后续看看能不能优化,有大神有更好的方法,可以公众号联系我,让我学习学习
原文及源码链接:Excel工具类实现

Excel工具类实现

    • 整体设计
    • 使用方式
    • 需要依赖
    • 代码实现

整体设计

使用方式

// @Sheet 不是必须的, 如果需要设置字体颜色/背景色, 可以使用该注解
@Sheet(bgColor = "#CBCBCB")
public class Demo {

    @SheetColumn(name = "ID")
    public Integer id;

    @SheetColumn(name = "Name")
    public String name;

    @SheetColumn(name = "Description", width = 30) // 宽度接近英文字符数
    public String description;

    // 默认为: yyyy-MM-dd, 根据实际情况设置
    @SheetColumn(name = "Created At", dateFormat = "yyyy-MM-dd HH:mm:ss")
    public LocalDateTime createdAt;

}
//读群数据
ExcelReader<Demo> reader=Excel.createReader(Demo::new);
reader.setEvaluateFormula(true); // 如果需要计算公式, 请设置为 true
List<Demo> list=reader.read(inputStream);
//写入数据
ExcelWriter<Demo> writer=Excel.createWriter(Demo.class);
Demo demo=new Demo();
demo.name="哈哈";
demo.createdAt=LocalDateTime.now();
...
    writer.write(demo);// 也可以是 List
writer.save(new FileOutputStream("D:/a.xlsx"));

需要依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

代码实现

  1. 首先Excel类只定义了读写创建器和部分跟读写关系不大的方法,具体的实现由读写类内部实现,使用起来比较清晰

    /**
     * @author ZeroClian
     * @date 2023-01-10 15:35
     */
    public class Excel {
        private static final Logger logger = LoggerFactory.getLogger(Excel.class);
    
        public static ExcelReader<T> createReader(Supplier<T> supplier) {
            return new ExcelReader<>(supplier);
        }
    
        public static ExcelWriter<T> createWriter(Class<T> clazz) {
            return new ExcelWriter<>(clazz);
        }
    
    
        public static Object getCellValue(Cell cell, FormulaEvaluator formulaEvaluator) {
            switch (cell.getCellType()) {
                case ERROR:
                    return ErrorEval.getText(cell.getErrorCellValue());
                case BLANK:
                    return "";
                case BOOLEAN:
                    return cell.getBooleanCellValue();
                case FORMULA:
                    if (formulaEvaluator == null) {
                        return cell.getCellFormula();
                    } else {
                        CellValue cellValue = formulaEvaluator.evaluate(cell);
                        if (cellValue.getCellType() == CellType.NUMERIC) {
                            return cellValue.getNumberValue();
                        }
                        if (cellValue.getCellType() == CellType.STRING) {
                            return cellValue.getStringValue();
                        }
                        if (cellValue.getCellType() == CellType.BOOLEAN) {
                            return cellValue.getBooleanValue();
                        }
                        return cellValue.toString();
                    }
                case NUMERIC:
                    if (DateUtil.isCellDateFormatted(cell)) {
                        return cell.getDateCellValue();
                    }
                    return cell.getNumericCellValue();
                case STRING:
                    return cell.getRichStringCellValue().toString();
                default:
                    logger.error("Unknown Cell Type: {}", cell.getCellType());
                    return null;
            }
        }
    
        /**
         * 26进制转换为十进制, 然后减去一(列索引基于0)
         *
         * @param name
         * @return
         */
        public static int toColumnIndex(String name) {
            int n = 0;
            for (int i = name.length() - 1, j = 1; i >= 0; i--, j *= 26) {
                char c = name.charAt(i);
                if (c < 'A' || c > 'Z') return 0;
                n += ((int) c - 64) * j;
            }
            return n - 1;//index要减一
        }
    
        /**
         * @param nm 例如: #FFCCEE
         * @return
         */
        static XSSFColor createColor(String nm) {
            java.awt.Color color = java.awt.Color.decode(nm);
            byte r = (byte) color.getRed();
            byte g = (byte) color.getGreen();
            byte b = (byte) color.getBlue();
            return new XSSFColor(new byte[]{r, g, b});
        }
    
    }
    
  2. 读excel核心就是如何将获取的值与Field字段对应,这里采用的自定义注解的方式,通过excel文件的列名与注解的列名对应即可,之后就是将cell值转换为对应的对象,再为field赋值,注意如果是private修饰的,需要添加field.setAccessible(true)

    /**
     * @author ZeroClian
     * @date 2023-01-10 15:35
     */
    public class ExcelReader<T> {
    
        private static final Logger logger = LoggerFactory.getLogger(Excel.class);
        private static final DefaultConversionService convert = new DefaultConversionService();
    
        private final Supplier<T> supplier;
        private boolean evaluateFormula = false;
        private FormulaEvaluator formulaEvaluator = null;
    
        public ExcelReader(Supplier<T> supplier) {
            this.supplier = supplier;
        }
    
        /**
         * 设置自动计算公式
         *
         * @param evaluateFormula true表示执行计算
         */
        public void setEvaluateFormula(boolean evaluateFormula) {
            this.evaluateFormula = evaluateFormula;
        }
    
        /**
         * 读取Excel
         *
         * @param in 输入流
         * @return {@link List<T> objects} 对象列表
         * @throws IOException
         */
        public List<T> read(InputStream in) throws IOException {
            Workbook workbook = WorkbookFactory.create(in);
            if (evaluateFormula) {
                this.formulaEvaluator = workbook.getCreationHelper().createFormulaEvaluator();
            }
            //读取数据
            List<T> list = new ArrayList<>();
            for (Sheet sheet : workbook) {
                List<String> headers = getSheetHeaders(sheet);
                Iterator<Row> rowIterator = sheet.rowIterator();
                while (rowIterator.hasNext()) {
                    Row row = rowIterator.next();
                    if (row.getRowNum() == 0) {
                        continue;
                    }
                    T obj = rowToObject(headers, row, supplier);
                    if (null != obj) {
                        list.add(obj);
                    }
                }
            }
            return list;
        }
    
        /**
         * 获取列名(位于第一行)
         *
         * @param sheet 页
         * @return {@link List<String> headers} 列名列表
         */
        public List<String> getSheetHeaders(Sheet sheet) {
            Row row = sheet.getRow(0);
            List<String> headers = new ArrayList<>();
            for (Cell cell : row) {
                String cellValue = cell.getStringCellValue();
                headers.add(cellValue);
            }
            return headers;
        }
    
        /**
         * 单行转为对象
         *
         * @param headers  列名
         * @param row      行
         * @param supplier 要转换的对象
         * @return {@link Object}
         */
        private T rowToObject(List<String> headers, Row row, Supplier<T> supplier) {
            try {
                T obj = supplier.get();
                //获取字段
                Field[] fields = obj.getClass().getDeclaredFields();
                boolean isEmpty = true;
                for (Field field : fields) {
                    SheetColumn ec = field.getAnnotation(SheetColumn.class);
                    if (null == ec) continue;
                    int columnIndex = headers.indexOf(ec.name());
                    if (columnIndex >= 0) {
                        Cell cell = row.getCell(columnIndex);
                        if (null != cell) {
                            isEmpty = setValue(obj, field, cell, ec.dateFormat());
                        }
                    }
                }
                return isEmpty ? null : obj;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 向对象设置值
         *
         * @param obj        对象
         * @param field      字段
         * @param cell       单元格
         * @param dateFormat 日期格式
         */
        private boolean setValue(T obj, Field field, Cell cell, String dateFormat) throws IllegalAccessException, ParseException {
            Object value = Excel.getCellValue(cell, formulaEvaluator);
            if (null == value) return true;
            boolean emptyValue = ObjectUtils.isEmpty(value);
            Class<?> valueClass = value.getClass();
            Class<?> fieldClass = field.getType();
            field.setAccessible(true);
            if (fieldClass == valueClass) {
                field.set(obj, value);
                return false;
            }
            //java8 日期、时间
            if (Temporal.class.isAssignableFrom(fieldClass)) {
                if (emptyValue) {
                    return true;
                }
                String cellStr = value.toString();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
                if (LocalDateTime.class.isAssignableFrom(fieldClass)) {
                    field.set(obj, LocalDateTime.parse(cellStr, formatter));
                }
                if (LocalDate.class.isAssignableFrom(fieldClass)) {
                    field.set(obj, LocalDate.parse(cellStr, formatter));
                }
                if (OffsetDateTime.class.isAssignableFrom(fieldClass)) {
                    field.set(obj, OffsetDateTime.parse(cellStr, formatter));
                }
            }
            //java.util.Date
            if (Date.class.isAssignableFrom(fieldClass)) {
                if (emptyValue) {
                    return true;
                }
                String cellStr = value.toString();
                SimpleDateFormat format = new SimpleDateFormat(dateFormat);
                Date date = format.parse(cellStr);
                field.set(obj, date);
                return false;
            }
            if (convert.canConvert(valueClass, fieldClass)) {
                Object newValue = ExcelReader.convert.convert(value, fieldClass);
                if (null != newValue) {
                    field.set(obj, newValue);
                }
                return false;
            }
            throw new IllegalArgumentException("表数据类型与类字段类型不一致: " + value.getClass() + " -> " + field.getType());
        }
    }
    
  3. 导出则比较简单,只是将对象转换为对应列的值,跟其他人的区别是,增加了表头的注解,用于简单设置导出的颜色背景样式,

    /**
     * @author ZeroClian
     */
    public class ExcelWriter<T> implements AutoCloseable {
    
        private static final Logger logger = LoggerFactory.getLogger(Excel.class);
    
        private final Class<T> clazz;
        private final SXSSFWorkbook workbook;
        private SXSSFSheet sheet;
        private Map<Field, Header> fieldHeaderMap;
    
        public ExcelWriter(Class<T> clazz) {
            this.clazz = clazz;
            this.workbook = new SXSSFWorkbook(300);
            this.init();
            this.createSheet();
        }
    
        /**
         * 获取字段对应的列
         */
        public void init() {
            Map<Field, Header> map = new LinkedHashMap<>();
            Set<Integer> usedIndex = new HashSet<>();
            Queue<Integer> queue = new LinkedList<>();
            for (Field field : clazz.getDeclaredFields()) {
                SheetColumn ec = field.getAnnotation(SheetColumn.class);
                if (null == ec) continue;
                int columnIndex = -1;
                if (!ec.column().isEmpty()) {
                    columnIndex = Excel.toColumnIndex(ec.column());
                    if (!usedIndex.add(columnIndex)) {
                        logger.warn("重复列编号: {}", ec.column());
                    }
                }
                Header header = new Header(ec.name(), ec.column(), columnIndex, ec.dateFormat(), ec.width());
                map.put(field, header);
                queue.add(queue.size());
            }
            queue.removeAll(usedIndex);
            map.forEach((field, header) -> {
                if (header.getColumnIndex() < 0) {
                    header.setColumnIndex(queue.remove());
                }
            });
            this.fieldHeaderMap = map;
        }
    
        /**
         * 创建Sheet和表头
         */
        public void createSheet() {
            String name = "Sheet1";
            String color = "#000000";
            String bgColor = "#CBCBCB";
            Sheet sheetInfo = clazz.getAnnotation(Sheet.class);
            if (null != sheetInfo) {
                if (!sheetInfo.name().isBlank()) {
                    name = sheetInfo.name();
                }
                color = sheetInfo.color();
                bgColor = sheetInfo.bgColor();
            }
            this.sheet = workbook.createSheet(name);
            //表头
            Row headerRow = sheet.createRow(0);
            CellStyle cellStyle = workbook.createCellStyle();
            cellStyle.setFillForegroundColor(Excel.createColor(bgColor));
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cellStyle.setAlignment(HorizontalAlignment.CENTER);
            XSSFFont font = (XSSFFont) workbook.createFont();
            font.setColor(Excel.createColor(color));
            font.setBold(true);
            cellStyle.setFont(font);
            sheet.trackAllColumnsForAutoSizing();
            fieldHeaderMap.forEach((field, header) -> {
                if (header.getColumnWidth() > 0) {
                    int width = Math.min(16_384, header.getColumnWidth() * 256);
                    sheet.setColumnWidth(header.getColumnIndex(), width);
                } else if (header.getColumnWidth() == 0) {
                    sheet.setColumnHidden(header.getColumnIndex(), true);
                } else {
                    sheet.autoSizeColumn(header.getColumnIndex());
                }
            });
            fieldHeaderMap.forEach((field, header) -> {
                Cell cell = headerRow.createCell(header.getColumnIndex());
                cell.setCellStyle(cellStyle);
                cell.setCellValue(header.getName());
            });
        }
    
        /**
         * 写入单个对象
         *
         * @param obj 对象
         * @throws IOException
         */
        public void write(T obj) throws IOException {
            try {
                if (null == obj) {
                    return;
                }
                Row row = sheet.createRow(sheet.getLastRowNum() + 1);
                for (Map.Entry<Field, Header> entry : fieldHeaderMap.entrySet()) {
                    Cell cell = row.createCell(entry.getValue().getColumnIndex());
                    Field field = entry.getKey();
                    Header header = fieldHeaderMap.get(field);
                    Object cellValue = field.get(obj);
                    String value;
                    if (cellValue instanceof Date date) {
                        value = DateFormatUtils.format(date, header.getDateFormat());
                    } else if (cellValue instanceof Temporal temporal) {
                        value = DateTimeFormatter.ofPattern(header.getDateFormat()).format(temporal);
                    } else {
                        value = Objects.toString(cellValue, "");
                    }
                    cell.setCellValue(value);
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    
        public void write(List<T> list) throws IOException {
            for (T obj : list) {
                write(obj);
            }
        }
    
        public void save(OutputStream out) throws Exception {
            workbook.write(out);
            close();
        }
    
        @Override
        public void close() throws Exception {
            this.workbook.close();
        }
    
    }
    
  4. 剩余就是注解的定义,这里我只定义了常用的,可自行修改

    /**
     * Excel表整体格式配置(非必选)
     * 主要用于设置表格格式
     * 修改:字体颜色,背景颜色
     *
     * @author ZeroClian
     * @date 2023-01-06 20:08
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Sheet {
    
        /**
         * 表名
         */
        String name() default "";
    
        /**
         * 字体颜色
         */
        String color() default "#000000";
    
        /**
         * 背景颜色
         */
        String bgColor() default "#CBCBCB";
    }
    
    /**
     * 列注解
     *
     * @author ZeroClian
     * @date 2023-01-06 20:20
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface SheetColumn {
    
        String name() default "";
    
        String column() default "";
    
        int width() default 16;
    
        String dateFormat() default "yyyy-MM-dd";
    }
    
    /**
     * @author Justin
     */
    @Data
    public class Header {
    
        private String name;
        private String column;
        private int columnIndex;
        private String dateFormat;
        private int columnWidth;
        private XSSFColor color;
        private XSSFColor bgColor;
    
        public Header(String name, String column, int columnIndex, String dateFormat, int columnWidth) {
            this.name = name;
            this.column = column;
            this.columnIndex = columnIndex;
            this.dateFormat = dateFormat;
            this.columnWidth = columnWidth;
        }
    }
    

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

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

相关文章

基于jsp+mysql+Spring的SpringBoot美容院预约管理系统设计和实现

基于jspmysqlSpring的SpringBoot美容院预约管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文…

编制万年历的历程

初入编程之道的学子大都试编过万年历。万年历有二种&#xff1a;一为只有西历的月历&#xff0c;另一为有农历对照的月历或日历。编写万年历程序可以练练手&#xff0c;加深对编程语言的理解。记得我初入此道是在1994年&#xff0c;我那时刚买了486电脑&#xff0c;也刚开始有视…

概率论基本概念

全概率公式 P(B)P(BA1)P(BA2)...P(BAn) P(B) P(B|A1)P(A1) P(B|A2)P(A2) ... P(B|An)P(An)P(BA1)P(BA2)...P(BAn) 事件A1&#xff0c;A2&#xff0c;…构成一个完备事件组且都有正概率 某一个事件概率 这个事件*其他完备事件划分联合概率之和 联合概率 联合概率指的…

线程安全问题(2)

锁的相关知识&#xff1a; 1)这就是类似于说我们ATM机上面有一把锁&#xff0c;同一时刻&#xff0c;如果说人们之间不相互认识&#xff0c;那么通过这把锁就进行限制了说就限制了说一次只能有一个人来进来取钱&#xff0c;我们通过这样的锁&#xff0c;就可以来进行避免上述这…

ESP8266 Arduino开发 搭建web服务器与客户端开发

一、wifi 相关配置 1.1 无线终端 wifi 模式 此模式中&#xff0c;esp8266 会连接到指定 wifi 进行工作。 #include <ESP8266WiFi.h> // 本程序使用ESP8266WiFi库const char* ssid "home"; // 连接WiFi名&#xff08;此处使用home为示例&…

位运算相关

1.与运算 &#xff08;点我&#xff09; 这个题的大概意思&#xff1a;给222个数nnn和xxx&#xff0c;其中满足n&(n1)&(n2)&(n3)...&mxn\&(n1)\&(n2)\&(n3)...\&mxn&(n1)&(n2)&(n3)...&mx,求最小的mmm&#xff0c;只要满足m&g…

【异常】java11提示: Cannot find any provider supporting RSA/ECB/PKCS1Padding的问题

一、背景 项目中需要对敏感字段进行加密&#xff0c;但是加密方法中涉及到比较复杂的加密算法&#xff0c;这些算法都需要一个Provider&#xff0c;主要是用于初始化算法的。 以下是遇到的具体问题 二、报错截图 java.security.NoSuchAlgorithmException: Cannot find any pr…

【软件测试】软件测试模型

1. V模型 需求分析—计划—设计—编码—测试 ● 概要设计&#xff1a;设计整体架构&#xff0c;框架 ● 详细设计&#xff1a;模块和模块之间的详细设计 ● 集成测试&#xff0c;单元测试&#xff1a;通常由开发人员进行 特点&#xff1a; 明确标注了测试的多类型明确标注了测…

Introduction to Multi-Armed Bandits——01 Scope and Motivation

Introduction to Multi-Armed Bandits——01 Scope and Motivation 参考资料 Slivkins A. Introduction to multi-armed bandits[J]. Foundations and Trends in Machine Learning, 2019, 12(1-2): 1-286.项目地址 https://github.com/yijunquan-afk/bandit-learning Bandit…

LeetCode622.设计循环队列

设计循环队列1.题目描述2.思路3.代码实现以及分析3.1 创建结构体3.2创建一个具体的循环队列3.3判断是否为空 和 判断是否为满4. 进队列 和 出队列5.取队首和队尾元素6.释放空间7.总结1.题目描述 设计循环队列 2.思路 环形队列的抽象图 我们这里使用数组模拟实现循环队列&…

TransactionTemplate自动注入,只看这一篇文章就够了

标准的springboot接入mybatis步骤 1.引入了对应的依赖包 2.应用的properties下增加相应配置 3.根据配置进行自动装配 一般我们会配置这些信息&#xff0c;主要包括三类 1.数据库的连接信息 2.指定的数据源类型 3.mybatis的配置信息 配完以后&#xff0c;当你启动SpringBoot的主…

你是真的“C”——详解C语言数组模块知识

详解C语言数组模块知识&#x1f60e;前言&#x1f64c;一维数组的创建和初始化&#x1f64c;1.1 数组的创建&#x1f49e;1.2 数组的初始化&#x1f49e;1.3 一维数组的使用&#x1f49e;1.4 一维数组在内存中的存储&#x1f49e;二维数组的创建和初始化&#x1f64c;1.1 二维数…

【Python百日进阶-数据分析】Day225 - plotly的Ohlc图go.Ohlc()

文章目录一、语法二、参数三、返回值四、实例4.1 简单的OHLC图4.2 隐藏滑块的OHLC图4.3 添加自定义文本和注释4.4 自定义OHLC颜色4.5 带日期时间对象的简单的OHLC图4.6 自定义悬浮文本4.7 Dash中的应用一、语法 ohlc&#xff08;Open-High-Low-Close 的缩写&#xff09;是一种…

【C++逆向】虚表(Virtual table)逆向 | 安卓so虚函数逆向

什么是多态 定义一个虚基类ISpeaker class ISpeaker{ protected:size_t b; public:ISpeaker( size_t _v ): b(_v) {}virtual void speak() 0; };有两个子类&#xff0c;都实现了虚函数speak()&#xff1a; class Dog : public ISpeaker { public:Dog(): ISpeaker(0){}//vir…

1581_AURIX_TC275_SMU故障处理梳理

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 前面为了缓解自己的学习压力&#xff0c;一次学习笔记大概也就是看10页文档整理一下。这一次其实是看了几十页&#xff0c;但是里面过掉了一些信息&#xff0c;而且这部分内容不是很好拆分…

hive在IDEA中debug

一、hive在IDEA中debug 安装hadoop环境&#xff08;1和2替换顺序也可以&#xff09; 注&#xff1a;hadoop环境不需要从源码编译 https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/SingleCluster.html 按照官网教程编译源码 https://cwiki.apach…

软件工程专业课实验报告

一、结构化分析方法1.1需求描述教务管理子系统的需求描述&#xff1a;教务管理是一项需求周密计划、严谨安排的工作&#xff0c;要依据教师、学生信息进行合理安排。开学阶段&#xff0c;需要教师提交开课申请进行开课&#xff0c;学生根据老师的开课信息&#xff0c;选择课程&…

uview 使用遇到的问题。

uviewuniappvue&#xff0c;uView是uni-app生态专用的UI框架。 1. 注意uview版本&#xff0c;uview 2.0与uview1.0 官方提示&#xff1a;uView2.0是继1.0以来的一次重大更新&#xff0c;2.0已全面兼容nvue。 因此在接手项目的时候首先得看清楚&#xff0c;之前开发的是uview…

【自学Python】Python获取字符串长度

Python获取字符串长度 Python获取字符串长度教程 在 Python 中要想获取 字符串 长度可以使用 len() 函数。 Python len()函数详解 定义 我们将要获取的字符串的长度&#xff0c;传进 len() 函数&#xff0c;即可实现获取字符串的长度。 语法 len(string)参数 参数描述s…

【7】K8s_Ingress | Service的统一网关入口

目录 1、Ingress简介 2、安装ingress 【1】制作ingress.yaml文件并执行 【2】测试&#xff0c;创建一个test.yaml文件并执行 【3】设置域名访问&#xff0c;用yaml文件 【4】路径重写 【5】流量限制 1、Ingress简介 Ingress: Service的统一网关入口是k8s中的一个api对象&…