disgare 的博客
首页
博客
分类
标签
首页
博客
分类
标签
  • 网络

    • 计算机网络学习笔记
    • 网络安全相关
    • 域名和子网掩码
    • CORS 跨域资源共享
    • DNS、HTTP 与 HTTPS
    • Server-Sent Events (SSE)
    • WebSocket 长连接
  • 计算机基础

    • 操作系统 IO 相关知识
    • 操作系统学习笔记
    • 程序的机器级表示
    • 音频文件基础
    • 正则表达式相关概念
    • ffmpeg 的安装以及实现音频切分功能
    • Hex 和 Base64 编码
    • XML 的使用
  • 数据结构与算法

    • 动态规划算法学习笔记
    • 基于比较的排序算法的最坏情况下的最优下界为什么是O(nlogn)
    • 集合与数据结构学习笔记
    • 面试常见算法总结
    • 算法导论第二部分排序学习笔记
    • 算法导论第一部分学习笔记
  • Java

    • 对象之间的映射与转换
    • 反射学习笔记
    • 泛型相关概念
    • 关于 boolean 类型的坑
    • 如何使用 lambda 表达式实现排序
    • CompletableFuture 相关用法
    • CompletableFuture 源码浅要阅读
    • FutureTask 源码阅读
    • Guava 常用 API
    • Guava 源码阅读:Multimap 相关
    • Jackson 的各种使用
    • Java 的 Excel 相关操作
      • Excel 的页面下载操作
      • HSSFCellStyle
      • EasyExcal
        • 自定义格式转换
        • 上传
        • 下载
        • 依赖
        • 注意事项
    • java 的常见性能问题分析以及出现场景
    • java 基础知识
    • JAVA 枚举的基础和原理
    • Java 图片文件上传下载处理
    • Java 序列化
    • Java 异常
    • Java 语法糖
    • Java 中关于字符串处理的常用方法
    • Java 中强、软、弱、虚引用
    • JAVA 注解小结
    • Java Http 访问框架
    • Java Stream 的使用
    • Java8 新特性
    • netty 学习笔记
    • Scanner 的各种用法
    • Servlet 学习笔记
    • String、StringBuffer、StringBuilder 学习笔记
  • JVM

    • 虚拟机执行子系统
    • JVM 自动内存管理
    • Linux 中 JVM 常用工具以及常见问题解决思路
  • Linux

    • crontab 表达式
    • Linux 常见命令
    • Linux 文件系统
  • 中间件

    • 关于定时任务原理
    • 详解 kafka
    • ES 搜索引擎
    • flink 提交流程
    • Grape-RAG
    • Hadoop 基础原理
  • 多线程

    • 多线程基础学习笔记
    • 简单了解并发集合
    • 如何手写单例
    • 深入理解 java 多线程安全
    • 生产者消费者问题
    • 线程池作用、用法以及原理
    • AQS 组件
    • ThreadLocal 原理以及使用
  • 非关系型数据库

    • Redis 集群
    • Redis 数据结构、对象与数据库
    • Redis 学习笔记
  • 关系型数据库

    • B+ 树的插入、删除和数据页分裂机制
    • MySQL 的 binglog、redolog、undolog
    • MySQL 的记录存储结构、存储引擎与 Buffer Pool
    • MySQL 基本的特性
    • MySQL 开发规范
    • MySQL 事务与锁与 MVCC
    • MySQL 数据类型、字符集相关内容
    • MySQL 索引与索引优化
    • PostgreSQL 更新数据时 HOT优化
    • PostgreSQL 相关用法
  • Python

    • Python 基础语法
    • Python 学习
  • Spring 项目

    • Lombok 的常用注解
    • maven 小结
    • MyBatis 框架的使用
    • MyBatis 重要知识点总结
    • MybatisPlus 的使用
    • Spring 框架基础使用
    • Spring 事务相关
    • Spring IOC 的原理及源码
    • Spring AOP 的使用和原理
    • SpringBoot 的原理
    • SpringBoot 基础使用
    • SpringWeb 重要知识点
  • 分布式

    • 初步了解 docker
    • 从 ACID 到 BASE 事务处理的实现
    • 访问远程服务
    • 分布式 id
    • 分布式缓存相关问题
    • 分布式集群理论和分布式事务协议
    • 分布式架构的观测
    • 分布式一致性算法
    • 负载均衡 Load Balancing
    • 关于分布式系统 RPC 中高可用功能的实现
    • 集群间数据同步的目的
    • 三高问题下的系统优化
    • 数据库分库分表
    • 详解 Spring Cloud
    • Dubbo 基础概念
    • Gossip 协议
    • nginx 学习笔记
    • Protobuf 通信协议
    • Zookeeper 基础学习
  • 架构设计

    • 参数校验与异常处理
    • 抽象方法与设计模式
    • 代码整洁之道
    • 权限系统设计
    • 用低内存处理大量数据
    • 设计模式——策略模式
    • 设计模式——过滤器模式在 Spring 中的实践
    • 状态模式
    • 统一结果返回
    • 为什么要打日志?怎么打日志?打什么日志?
    • 运维监控常见指标含义
    • 资深研发进阶
    • DDD 架构学习笔记
    • Java 常用的规则引擎
    • MVC 架构学习笔记
  • AI

    • 如何编写 Prompt
    • Agent 工程架构
    • LLM 相关内容
    • NLP 相关知识
    • vibe coding 最佳实践
    • windows 下 ollama 迁移到 D 盘
  • 开发工具

    • 如何画时序图、流程图、状态流转图
    • excel 关于 =vlookup 的用法
    • git 的学习以及使用
    • IDEA 插件推荐
    • IDEA 常用快捷键以及调试
    • Shell 脚本
    • swagger 的使用
  • 前端

    • 简单了解前端页面开发
    • 伪静态是什么
    • GitHub Pages 部署教程
    • Vercel 部署教程
    • vue-admin-template 简单使用
    • VuePress 博客搭建指南
  • 项目

    • 面试刷题网——技术方案
    • 影视资源聚合站——技术方案
  • 问题记录

    • 定时任务单线程消费 redis 中数据导致消费能力不足
    • 提供可传递的易受攻击的依赖项
    • Liteflow 在 SpringBoot 启动时无法注入组件问题 couldn‘t find chain with the id[THEN(NodeComponent)]
  • 金融

    • 股票分析——关于电力
    • 股票技术面——量价关系
    • 股票技术面——盘口
    • 股票技术面——基础
    • 基础的金融知识
    • 基金与股票
    • 韭菜的自我总结
    • 聊聊价值投资
  • 其他

    • 程序员职场工作需要注意什么
    • 创业全链路SOP:从灵光一现到系统化增长的实战指南
    • 观罗翔讲刑法随笔
    • 价格和价值
    • 立直麻将牌效益理论
    • 梅花易数学习笔记
    • 压力管理
2023-04-03
Java
目录

Java 的 Excel 相关操作

# Excel 的页面下载操作

在 response 中设置浏览器接受的样式为 application/vnd.ms-excal,即可返回 excal 的下载。然后需要定义一些其他设置

        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
		// 额外的设置
        String agent = request.getHeader("User-Agent");
        String filename = "xxx.xls";
        filename = filename.replaceAll("filename=","");
        
        if (agent != null && agent.contains("Windows"))
            filename = new String(filename.getBytes("GB2312"), "ISO_8859_1");
        else
            filename = new String(filename.getBytes("UTF-8"), "ISO_8859_1");

		// 设置下载的文件名称
        response.addHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
        // 编码
        response.setCharacterEncoding("UTF-8");
        // 设置二进制传输文件
        response.setHeader("Content-Transfer-Encoding","binary");
        response.setHeader("Pragma", "public");
        response.setHeader("Cache-Control", "public" );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# HSSFCellStyle

用 HSSFWorkbook 代表一个 excal 文件,HSSFSheet 表示文件中的每个 sheet,HSSFRow 代表 sheet 中的每一行,HSSFCell 就表示每一行中的每一格数据了

            // 渲染 excel 文件
            HSSFWorkbook wb = new HSSFWorkbook();
            HSSFSheet sheet = wb.createSheet("酒店信息");
            HSSFRow row = sheet.createRow(0);
            HSSFCell cell = row.createCell(0);
1
2
3
4
5

HSSFCellStyle 是一个实现了 CellStyle 接口的类,用于工作簿的工作页中每个单元格的高级样式展示

HSSFCellStyle 与 HSSFFont 都是通过 HSSFWorkbook 创造出来的

// 生成一个样式
HSSFCellStyle style = workbook.createCellStyle();
// 设置这些样式
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中 
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中

 // 背景色
style.setFillForegroundColor(HSSFColor.YELLOW.index);
style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); 
style.setFillBackgroundColor(HSSFColor.YELLOW.index); 

// 设置边框
style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style.setBorderRight(HSSFCellStyle.BORDER_THIN);
style.setBorderTop(HSSFCellStyle.BORDER_THIN);  

// 自动换行  
style.setWrapText(true);  

// 生成一个字体
HSSFFont font = workbook.createFont();
font.setFontHeightInPoints((short) 10);
font.setColor(HSSFColor.RED.index);
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
font.setFontName("宋体");

// 把字体应用到当前样式
style.setFont(font);

// style 设置好后,为 cell 设置样式
cell.setCellStyle(style);// cell 为已有的单元格
// 为单元格设置数据
cell.setCellValue("hello world");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# EasyExcal

直接使用 HSSFCellStyle 未免太麻烦了一些,阿里为我们提供了更加简单的 Excal 操作,官方文档如下:

https://easyexcel.opensource.alibaba.com/docs/current/

EasyExcal 的输出是根据流来的,内容是使用 list 确定的,表格的结构是从 pojo 的注解来确定的。因此我们只需要在 write 方法中对流就行操作就可以了

比如下面这个流,是将文件输出到服务器的磁盘中

FileOutputStream outputStream = new FileOutputStream(new File(excelFilePath));
EasyExcel.write(outputStream, DestinationDimensionStopOrder.class).sheet().doWrite(destinationDimensionStopOrder);
1
2

下面的 pojo 展示了如何使用 easyExcal 提供的注解

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

以下是一些特殊使用

# 自定义格式转换

有时候我们需要将一些类中自定义的属性放进 excal,这时候我们需要转换器。easyExcal 提供的特殊注解为我们处理了一些常用的类型转换

@Getter
@Setter
@EqualsAndHashCode
public class ConverterData {
    /**
     * 我想所有的 字符串起前面加上"自定义:"三个字
     */
    @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
    @NotNull(message = "字符串标题不能为空")
    private String string;
    /**
     * 我想写到excel 用年月日的格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty("日期标题")
    private Date date;
    /**
     * 我想写到excel 用百分比表示
     */
    @NumberFormat("#.##%")
    @ExcelProperty(value = "数字标题")
    private Double doubleData;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

转换器的写法如下:

package com.alibaba.easyexcel.test.demo.write;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

/**
 * String and string converter
 *
 * @author Jiaju Zhuang
 */
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里是读的时候会调用
     *
     * @param cellData            NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return
     */
    @Override
    public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return cellData.getStringValue();
    }

    /**
     * 这里是写的时候会调用 
     *
     * @param value               NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return
     */
    @Override
    public WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return new WriteCellData<>("自定义:" + value);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

supportJavaTypeKey 方法指定该 Converter 转换的目标类型是 String,而 easyexcel 在将 excel 单元格内容转换为 java 中的 string 时调用的就是 convertToJavaData 方法

# 上传

    @RequestMapping("/secondKill/uploadConfig.json")
    @ResponseBody
    public JsonData uploadConfig(@RequestParam(value = "file") MultipartFile file) {
            ReadAndValidatorListener<CrmUserInfoVO> listener = new ReadAndValidatorListener<>();
            EasyExcel.read(file.getInputStream(), CrmUserInfoVO.class, listener)
                    .excelType(ExcelTypeEnum.XLSX)
                    .autoCloseStream(false)
                    .autoTrim(true)
                    .sheet()
                    .doRead();

            if (listener.hasViolation()) {
                return JsonResult.error(listener.getViolationMessageString());
            }
			// 这里 getDataList 拿到 excal 中的数据,推荐做一次 vo 到 po 的转换
            return crmUserInfoService.importAllUser(listener.getDataList(), user.getUserName());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

监听器可以这么写

    @Override
    public void importEmployeExcel(MultipartFile file) throws Exception {
        EasyExcel.read(file.getInputStream(), Employe.class, new AnalysisEventListener<Employe>() {

            /**
             * 批处理阈值,作用:减轻数据库的压力
             */
            private static final int BATCH_COUNT = 2;

            /**
             * 存储员工对象
             */
            List<Employe> list = new ArrayList<Employe>(BATCH_COUNT);

            //easyExcel每次从Excel中读取一行数据就会调用一次invoke方法
            @Override
            public void invoke(Employe employe, AnalysisContext analysisContext) {
            	// 这里也可以将错误信息提取出来
            	if (Strings.isNotBlank(ExcelCellUtils.validateEntity(employe))) {
            		list.add(employe);
            	}
                
                if (list.size() >= BATCH_COUNT) {
                 	employeDao.addBatch(list);
                    list.clear();
                }
            }

            //easyExcel在将Excel表中数据读取完毕后,最终执行此方法
            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                //最后,如果size<BATCH_COUNT就在这里进行数据的处理
                if (list.size() > 0) {
                    employeDao.addBatch(list);
                }
                // 或者在 invoke 中不使用 BATCH_COUNT 判断,在结尾处一起修改
                // Lists.partition(list, BATCH_COUNT).stream().forEach(employeDao::addBatch);
            }
			
			// 在此匿名内部类的方法里,throw new ExcelAnalysisStopException()就会终止easyExcel的运行。
			
			/**
		     *
		     * @param exception
		     * @param context
		     * @throws Exception
		     */
		    @Override
		    public void onException(Exception exception, AnalysisContext context) {
		        // 此方法能接住,在此匿名内部类的方法里抛出的异常,并进行处理,然后继续invoke方法。
		    }
		
        }).sheet().doRead();//sheet()参数指定,默认读取第一张工作表
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

validateEntity 是校验参数方法,旨在减少重复的校验代码

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;

    public static <T> String validateEntity(T obj) {
        StringBuilder result = new StringBuilder();
        Set<ConstraintViolation<T>> set = VALIDATOR.validate(obj, Default.class);
        if (set != null && !set.isEmpty()) {
            for (ConstraintViolation<T> cv : set) {
                //拼接错误信息
                result.append(" ").append(cv.getMessage());
            }
        }
        return result.toString();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 下载

        // 查询线索
        List<SalesClue> salesClues = salesClueRouteService.selectByCondition(
                CrmSalesClueFilter.builder().startTime(DateUtil.str2date(date, DateUtil.DATE_FORMAT)).saleNames(usernames).build()
        );

        List<SalesClueDownloadDetail> salesClueDownloadDetail = salesClueService.toDownloadDetail(salesClues);

		// 设置为流
        response.setContentType("application/octet-stream");
        response.setHeader("Content-disposition", "attachment;filename=" + "detail.xlsx");

        try {
            EasyExcel.write(response.getOutputStream(), SalesClueDownloadDetail.class)
                    .sheet().doWrite(salesClueDownloadDetail);
        } catch (IOException e) {
            log.error("downloadDetail出现异常", e);
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

除了提供给前端,根据传入流的不同,可以输出到不同的地方,比如输出到文件中

        FileOutputStream outputStream = new FileOutputStream(new File(excelFilePath));
        EasyExcel.write(outputStream, DestinationDimensionStopOrder.class).sheet().doWrite(destinationDimensionStopOrder);
1
2

# 依赖

            <dependency>
                <groupId>org.apache.xmlbeans</groupId>
                <artifactId>xmlbeans</artifactId>
                <version>3.1.0</version>
            </dependency>
            <!-- 4.1.2 -->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>${org.apache.poi.version}</version>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${org.apache.poi.version}</version>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml-schemas</artifactId>
                <version>${org.apache.poi.version}</version>
            </dependency>
            <!-- 3.2.1 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${com.alibaba.easyexcel.version}</version>
            </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 注意事项

EasyExcel 有很多坑,他封装了很多东西导致了可扩张性不是很好,下面来列一些项目中常见的事项:

1,在监听器中如果不想继续解析数据了,我们只能抛出异常来结束方法。记得在监听器外围做好拦截,否则这个我们可预知的错误会直接抛给用户 2,在所有的方法中抛出异常的话,都会走到 onException 方法中,比如在 invokeHead(访问首行的方法)、invoke(访问各种数据的方法),因此在这个方法中需要兼容好各种情况 3,在监听器中无论抛出什么异常,都会被包装成 ExcelAnalysisException 异常。这个需要额外注意,并且异常的 message 和 code 都会被改写

#Excel
最后更新: 1/17/2026, 2:51:21 AM
Jackson 的各种使用
java 的常见性能问题分析以及出现场景

← Jackson 的各种使用 java 的常见性能问题分析以及出现场景→

最近更新
01
vibe coding 最佳实践
02-24
02
立直麻将牌效益理论
02-23
03
伪静态是什么
02-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式