EasyExcel 让Excel导入导出更简单
- 一、什么是EasyExcel?
- 二、对比ApachePoi 其他Excel框架 优势在于哪里
- 三、本质上对于原生做了哪些优化?
- 四、有哪些功能
- 五、如何实践?
- 5.1 常规文件读取
- 5.2 常规文件写入
- 5.3 常规WEB的上传和下载
一、什么是EasyExcel?
EasyExcel是对07版POI的提升和优化,能够有效解决内存占用大的问题,将内存处理部分转移到磁盘。
从功能上,就是poi 07版本能做的,它都能做,并且效率更高,门槛更低。
在互联网平台上获得广泛使用,在github上面star已经达到26700,可见人气之旺。
二、对比ApachePoi 其他Excel框架 优势在于哪里
- 内存优化
EasyExcel出现的原因主要是为了优化内存的占用,官方提供的数据就能够了解到 —> 16M内存23秒读取75M的Excel, 这个Excel包含46W行25列。对于极速模式,也会牺牲内存换取更快的速度,要看取舍了。
- 读写行数
针对Excel03版本,只能读取小于65535行。从Excel07版本开始,读写行数都能够超过65535行,但是由于框架性质的原因,读取数据越多,占用的内存就会增长迅速。速度也是较慢。
- jar引用
对于POI这种通常都要引入几个相关的jar包,jar包多了内部的版本就会和别的组件的依赖会出现更多冲突的可能。easyexcel目前只有一个jar。
三、本质上对于原生做了哪些优化?
以上都是描述了EasyExcel从内存的角度,对于解析Exccel07版本做了优化,具体的点有哪些呢?(待完善)
四、有哪些功能
- 简单读
- 模板读
- 注解表头读
- 注解自定义读
- 处理器自定义读
- 模板读
- 无对象读
- 固定多层表头读
- 自定义多层表头读
- 超链接、标注读
- 图片读
- 写也都有类似的写功能
- 写入有自适应列宽的实现
有大量Handler可扩展,非常灵活
五、如何实践?
简单使用官方教程的测试用例,来跟着代码一起了解功能。对于测试样例和文档都挺完备的,非常好理解。
简单操作也可以直接移步官网:https://easyexcel.opensource.alibaba.com/docs/current/
5.1 常规文件读取
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.converters.DefaultConverterLoader;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson2.JSON;
import com.learning.easyexcel.common.CellDataUser;
import com.learning.easyexcel.common.User;
import com.learning.easyexcel.common.UserExtra;
import com.learning.easyexcel.common.UserFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;import java.util.List;
import java.util.Map;/*** 功能简述* 〈读取测试〉** @author claire* @date 2023/2/20 - 11:40 上午* @since 1.0.0*/
@Slf4j
public class ReadTest {/*** 最简单的读* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void simpleRead1() {// 写法1:JDK8+ ,不用额外写一个DemoDataListener// since: 3.0.0-beta1String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行// 具体需要返回多少行可以在`PageReadListener`的构造函数设置EasyExcel.read(fileName, User.class, new PageReadListener<User>(dataList -> {for (User user : dataList) {log.info("读取到一条数据{}", JSON.toJSONString(user));}})).sheet().doRead();}public void simpleRead2() {// 写法2:// 匿名内部类 不用额外写一个DemoDataListenerString fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, User.class, new ReadListener<User>() {/*** 单次缓存的数据量*/public static final int BATCH_COUNT = 100;/*** 临时存储*/private List<User> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);@Overridepublic void invoke(User data, AnalysisContext context) {cachedDataList.add(data);if (cachedDataList.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listcachedDataList = Lists.newArrayList();}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {saveData();}/*** 加上存储数据库*/private void saveData() {log.info("{}条数据,开始存储数据库!", cachedDataList.size());log.info("存储数据库成功!");}}).sheet().doRead();}public void simpleRead3() {// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去// 写法3:String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, User.class, new UserDataListener()).sheet().doRead();}public void simpleRead4() {// 写法4String fileName = "demo.xlsx";// 一个文件一个readertry (ExcelReader excelReader = EasyExcel.read(fileName, User.class, new UserDataListener()).build()) {// 构建一个sheet 这里可以指定名字或者noReadSheet readSheet = EasyExcel.readSheet(0).build();// 读取一个sheetexcelReader.read(readSheet);}}/*** 指定列的下标或者列名** <p>* 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link }* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void indexOrNameRead() {String fileName ="demo.xlsx";// 这里默认读取第一个sheetEasyExcel.read(fileName, User.class, new UserWithAnnoDataListener()).sheet().doRead();}/*** 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserDataListener}* <p>* 3. 直接读即可*/public void multiSheetRead(){String fileName = "demo.xlsx";// 读取全部sheet// 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写EasyExcel.read(fileName, User.class, new UserDataListener()).doReadAll();// 读取部分sheetfileName = "demo.xlsx";// 写法1try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的ListenerReadSheet readSheet1 =EasyExcel.readSheet(0).head(User.class).registerReadListener(new UserDataListener()).build();ReadSheet readSheet2 =EasyExcel.readSheet(1).head(User.class).registerReadListener(new UserDataListener()).build();// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能excelReader.read(readSheet1, readSheet2);}}/*** 日期、数字或者自定义格式转换* <p>* 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}* <p>* 1. 创建excel对应的实体对象 参照{@link UserFormat}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void contentFormatWithAnnoRead(){String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, UserFormat.class, new UserFormatDataListener())// 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。// 如果就想单个字段使用请使用@ExcelProperty 指定converter// .registerConverter(new CustomStringStringConverter())// 读取sheet.sheet().doRead();}/*** 多行头** <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserDataListener}* <p>* 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,* 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。*/public void groupHeaderRead(){String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, User.class, new UserDataListener()).sheet()// 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行.headRowNumber(3).doRead();}/*** 读取表头数据** <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserDataListener}* <p>* 3. 直接读即可*/public void headerRead() {String fileName ="demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, User.class, new UserDataListener()).sheet().doRead();}/*** 额外信息(批注、超链接、合并单元格信息读取)* <p>* 由于是流式读取,没法在读取到单元格数据的时候直接读取到额外信息,所以只能最后通知哪些单元格有哪些额外信息** <p>* 1. 创建excel对应的实体对象 参照{@link UserExtra}* <p>* 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserExtraListener}* <p>* 3. 直接读即可** @since 2.2.0-beat1*/public void extraRead() {String fileName ="extra.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, UserExtra.class, new UserExtraListener())// 需要读取批注 默认不读取.extraRead(CellExtraTypeEnum.COMMENT)// 需要读取超链接 默认不读取.extraRead(CellExtraTypeEnum.HYPERLINK)// 需要读取合并单元格信息 默认不读取.extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();}/*** 读取公式和单元格类型** <p>* 1. 创建excel对应的实体对象 参照{@link CellDataUser}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link CellDataUserHeadListener}* <p>* 3. 直接读即可** @since 2.2.0-beat1*/public void cellDataRead() {String fileName = "cellDataDemo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, CellDataUser.class, new CellDataUserHeadListener()).sheet().doRead();}/*** 数据转换等异常处理** <p>* 1. 创建excel对应的实体对象 参照{@link }* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void exceptionConvertRead(){//遇到异常转换为有含义的数据,在listen中转换
// @Override
// public void onException(Exception exception, AnalysisContext context) {
// log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
// // 如果是某一个单元格的转换异常 能获取到具体行号
// // 如果要获取头的信息 配合invokeHeadMap使用
// if (exception instanceof ExcelDataConvertException) {
// ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
// log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
// excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
// }
// }}/*** 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面*/public void synchronousRead() {String fileName ="demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finishList<User> list = EasyExcel.read(fileName).head(User.class).sheet().doReadSync();for (User data : list) {log.info("读取到数据:{}", JSON.toJSONString(data));}// 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finishList<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync();for (Map<Integer, String> data : listMap) {// 返回每条数据的键值对 表示所在的列 和所在列的值log.info("读取到数据:{}", JSON.toJSONString(data));}}/*** 不创建对象的读,逻辑里面入DB或入缓存,用MAP来承载数据*/public void noModelRead() {String fileName = "demo.xlsx";// 这里 只要,然后读取第一个sheet 同步读取会自动finish
// EasyExcel.read(fileName, new NoModelDataListener()).sheet().doRead();}}
5.2 常规文件写入
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.*;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.util.FileUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.merge.LoopMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.learning.easyexcel.LongestMatchColumnWidthData;
import com.learning.easyexcel.common.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFSheet;import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.*;/*** 功能简述* 〈x写Excel〉** @author claire* @date 2023/2/20 - 2:53 下午* @since 1.0.0*/
public class WriteTest {/*** 最简单的写* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 直接写即可*/public void simpleWrite1() {// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入// 写法1 JDK8+// since: 3.0.0-beta1String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, User.class).sheet("模板").doWrite(() -> {// 分页查询数据return data();});}public void simpleWrite2() {// 写法2String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, User.class).sheet("模板").doWrite(data());}public void simpleWrite3() {// 写法3String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();excelWriter.write(data(), writeSheet);}}/*** 根据参数只导出指定列* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 根据自己或者排除自己需要的列* <p>* 3. 直接写即可** @since 2.1.1*/public void excludeOrIncludeWrite() {String fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";// 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。// 根据用户传入字段 假设我们要忽略 scoreSet<String> excludeColumnFieldNames = new HashSet<>();excludeColumnFieldNames.add("score");// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).excludeColumnFieldNames(excludeColumnFieldNames).sheet("模板").doWrite(data());fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";// 根据用户传入字段 假设我们只要导出 scoreSet<String> includeColumnFieldNames = new HashSet<>();includeColumnFieldNames.add("score");// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).includeColumnFieldNames(includeColumnFieldNames).sheet("模板").doWrite(data());}/*** 指定写入的列* <p>* 1. 创建excel对应的实体对象 参照{@link UserWriteFormat}* <p>* 2. 使用{@link ExcelProperty}注解指定写入的列* <p>* 3. 直接写即可*/public void indexWrite() {String fileName = "indexWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, UserWriteFormat.class).sheet("模板").doWrite(data());}/*** 复杂头写入,这个很实用,快速导出,不用纠结于表头的设计* <p>* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}* <p>* 2. 使用{@link ExcelProperty}注解指定复杂的头* <p>* 3. 直接写即可*/public void complexHeadWrite() {String fileName = "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());}/*** 重复多次写入* <p>* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}* <p>* 2. 使用{@link ExcelProperty}注解指定复杂的头* <p>* 3. 直接调用二次写入即可*/public void repeatedWrite1() {// 方法1: 如果写到同一个sheetString fileName = "repeatedWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {// 这里注意 如果同一个sheet只要创建一次WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来for (int i = 0; i < 5; i++) {// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<User> data = data();excelWriter.write(data, writeSheet);}}}public void repeatedWrite2() {// 方法2: 如果写到不同的sheet 同一个对象String fileName = "repeatedWrite" + System.currentTimeMillis() + ".xlsx";// 这里 指定文件try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面for (int i = 0; i < 5; i++) {// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<User> data = data();excelWriter.write(data, writeSheet);}}}public void repeatedWrite3() {// 方法3 如果写到不同的sheet 不同的对象String fileName = "repeatedWrite" + System.currentTimeMillis() + ".xlsx";// 这里 指定文件try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面for (int i = 0; i < 5; i++) {// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class// 实际上可以一直变WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(User.class).build();// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<User> data = data();excelWriter.write(data, writeSheet);}}}/*** 日期、数字或者自定义格式转换,这个是常见的指定格式导出Excel,自定义的convert可以做一些枚举转换、字段拆分等* <p>* 1. 创建excel对应的实体对象 参照{@link UserFormat}* <p>* 2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解* <p>* 3. 直接写即可*/public void converterWrite() {String fileName = "converterWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, UserFormat.class).sheet("模板").doWrite(data());}/*** 图片导出* <p>* 1. 创建excel对应的实体对象 参照{@link ImageDemoData}* <p>* 2. 直接写即可*/public void imageWrite() throws Exception {String fileName = "imageWrite" + System.currentTimeMillis() + ".xlsx";// 这里注意下 所有的图片都会放到内存 暂时没有很好的解法,大量图片的情况下建议 2选1:// 1. 将图片上传到oss 或者其他存储网站: https://www.aliyun.com/product/oss ,然后直接放链接// 2. 使用: https://github.com/coobird/thumbnailator 或者其他工具压缩图片String imagePath ="converter" + File.separator + "img.jpg";try (InputStream inputStream = FileUtils.openInputStream(new File(imagePath))) {List<ImageDemoData> list = ListUtils.newArrayList();ImageDemoData imageDemoData = new ImageDemoData();list.add(imageDemoData);// 放入五种类型的图片 实际使用只要选一种即可imageDemoData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));imageDemoData.setFile(new File(imagePath));imageDemoData.setString(imagePath);imageDemoData.setInputStream(inputStream);imageDemoData.setUrl(new URL("https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg"));// 这里演示// 需要额外放入文字// 而且需要放入2个图片// 第一个图片靠左// 第二个靠右 而且要额外的占用他后面的单元格WriteCellData<Void> writeCellData = new WriteCellData<>();imageDemoData.setWriteCellDataFile(writeCellData);// 这里可以设置为 EMPTY 则代表不需要其他数据了writeCellData.setType(CellDataTypeEnum.STRING);writeCellData.setStringValue("额外的放一些文字");// 可以放入多个图片List<ImageData> imageDataList = new ArrayList<>();ImageData imageData = new ImageData();imageDataList.add(imageData);writeCellData.setImageDataList(imageDataList);// 放入2进制图片imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));// 图片类型imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG);// 上 右 下 左 需要留空// 这个类似于 css 的 margin// 这里实测 不能设置太大 超过单元格原始大小后 打开会提示修复。暂时未找到很好的解法。imageData.setTop(5);imageData.setRight(40);imageData.setBottom(5);imageData.setLeft(5);// 放入第二个图片imageData = new ImageData();imageDataList.add(imageData);writeCellData.setImageDataList(imageDataList);imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG);imageData.setTop(5);imageData.setRight(5);imageData.setBottom(5);imageData.setLeft(50);// 设置图片的位置 假设 现在目标 是 覆盖 当前单元格 和当前单元格右边的单元格// 起点相对于当前单元格为0 当然可以不写imageData.setRelativeFirstRowIndex(0);imageData.setRelativeFirstColumnIndex(0);imageData.setRelativeLastRowIndex(0);// 前面3个可以不写 下面这个需要写 也就是 结尾 需要相对当前单元格 往右移动一格// 也就是说 这个图片会覆盖当前单元格和 后面的那一格imageData.setRelativeLastColumnIndex(1);// 写入数据EasyExcel.write(fileName, ImageDemoData.class).sheet().doWrite(list);}}/*** 超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式* <p>* 1. 创建excel对应的实体对象 参照{@link WriteCellDemoData}* <p>* 2. 直接写即可** @since 3.0.0-beta1*/public void writeCellDataWrite() {String fileName = "writeCellDataWrite" + System.currentTimeMillis() + ".xlsx";WriteCellDemoData writeCellDemoData = new WriteCellDemoData();// 设置超链接WriteCellData<String> hyperlink = new WriteCellData<>("官方网站");writeCellDemoData.setHyperlink(hyperlink);HyperlinkData hyperlinkData = new HyperlinkData();hyperlink.setHyperlinkData(hyperlinkData);hyperlinkData.setAddress("https://github.com/alibaba/easyexcel");hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);// 设置备注WriteCellData<String> comment = new WriteCellData<>("备注的单元格信息");writeCellDemoData.setCommentData(comment);CommentData commentData = new CommentData();comment.setCommentData(commentData);commentData.setAuthor("Jiaju Zhuang");commentData.setRichTextStringData(new RichTextStringData("这是一个备注"));// 备注的默认大小是按照单元格的大小 这里想调整到4个单元格那么大 所以向后 向下 各额外占用了一个单元格commentData.setRelativeLastColumnIndex(1);commentData.setRelativeLastRowIndex(1);// 设置公式WriteCellData<String> formula = new WriteCellData<>();writeCellDemoData.setFormulaData(formula);FormulaData formulaData = new FormulaData();formula.setFormulaData(formulaData);// 将 123456789 中的第一个数字替换成 2// 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");// 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。WriteCellData<String> writeCellStyle = new WriteCellData<>("单元格样式");writeCellStyle.setType(CellDataTypeEnum.STRING);writeCellDemoData.setWriteCellStyle(writeCellStyle);WriteCellStyle writeCellStyleData = new WriteCellStyle();writeCellStyle.setWriteCellStyle(writeCellStyleData);// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 背景绿色writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex());// 设置单个单元格多种样式// 这里需要设置 inMomery=true 不然会导致无法展示单个单元格多种样式,所以慎用WriteCellData<String> richTest = new WriteCellData<>();richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING);writeCellDemoData.setRichText(richTest);RichTextStringData richTextStringData = new RichTextStringData();richTest.setRichTextStringDataValue(richTextStringData);richTextStringData.setTextString("红色绿色默认");// 前2个字红色WriteFont writeFont = new WriteFont();writeFont.setColor(IndexedColors.RED.getIndex());richTextStringData.applyFont(0, 2, writeFont);// 接下来2个字绿色writeFont = new WriteFont();writeFont.setColor(IndexedColors.GREEN.getIndex());richTextStringData.applyFont(2, 4, writeFont);List<WriteCellDemoData> data = new ArrayList<>();data.add(writeCellDemoData);EasyExcel.write(fileName, WriteCellDemoData.class).inMemory(true).sheet("模板").doWrite(data);}/*** 根据模板写入,模板文件的方式可以动态变化模板,了解模板书写规范即可* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 使用{@link ExcelProperty}注解指定写入的列* <p>* 3. 使用withTemplate 写取模板* <p>* 4. 直接写即可*/public void templateWrite() {String templateFileName = "demo" + File.separator + "demo.xlsx";String fileName = "templateWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM// 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入EasyExcel.write(fileName, User.class).withTemplate(templateFileName).sheet().doWrite(data());}/*** 列宽、行高* <p>* 1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}* <p>* 2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度* <p>* 3. 直接写即可*/public void widthAndHeightWrite() {String fileName ="widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());}/*** 注解形式自定义样式* <p>* 1. 创建excel对应的实体对象 参照{@link FixStyleData}* <p>* 3. 直接写即可** @since 2.2.0-beta1*/public void annotationStyleWrite() {String fileName = "annotationStyleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, FixStyleData.class).sheet("模板").doWrite(data());}/*** 拦截器形式自定义样式* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 创建一个style策略 并注册* <p>* 3. 直接写即可*/public void handlerStyleWrite() {// 方法1 使用已有的策略 推荐// HorizontalCellStyleStrategy 每一行的样式都一样 或者隔行一样// AbstractVerticalCellStyleStrategy 每一列的样式都一样 需要自己回调每一页String fileName = "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";// 头的策略WriteCellStyle headWriteCellStyle = new WriteCellStyle();// 背景设置为红色headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());WriteFont headWriteFont = new WriteFont();headWriteFont.setFontHeightInPoints((short) 20);headWriteCellStyle.setWriteFont(headWriteFont);// 内容的策略WriteCellStyle contentWriteCellStyle = new WriteCellStyle();// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 背景绿色contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());WriteFont contentWriteFont = new WriteFont();// 字体大小contentWriteFont.setFontHeightInPoints((short) 20);contentWriteCellStyle.setWriteFont(contentWriteFont);// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现HorizontalCellStyleStrategy horizontalCellStyleStrategy =new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板").doWrite(data());// 方法2: 使用easyexcel的方式完全自己写 不太推荐 尽量使用已有策略// @since 3.0.0-beta2fileName = "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName, User.class).registerWriteHandler(new CellWriteHandler() {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 当前事件会在 数据设置到poi的cell里面才会回调// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {// 第一个单元格// 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellDataWriteCellData<?> cellData = context.getFirstCellData();// 这里需要去cellData 获取样式// 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat// ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了// 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDwriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了}}}).sheet("模板").doWrite(data());// 方法3: 使用poi的样式完全自己写 不推荐// @since 3.0.0-beta2// 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效// 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了fileName = "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName, User.class).registerWriteHandler(new CellWriteHandler() {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 当前事件会在 数据设置到poi的cell里面才会回调// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {Cell cell = context.getCell();// 拿到poi的workbookWorkbook workbook = context.getWriteWorkbookHolder().getWorkbook();// 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式// 不同单元格尽量传同一个 cellStyleCellStyle cellStyle = workbook.createCellStyle();cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDcellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);cell.setCellStyle(cellStyle);// 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到// cell里面去 会导致自己设置的不一样context.getFirstCellData().setWriteCellStyle(null);}}}).sheet("模板").doWrite(data());}/*** 合并单元格* <p>* 1. 创建excel对应的实体对象 参照{@link User} {@link RowMergeData}* <p>* 2. 创建一个merge策略 并注册* <p>* 3. 直接写即可** @since 2.2.0-beta1*/public void mergeWrite() {// 方法1 注解String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";// 在DemoStyleData里面加上ContentLoopMerge注解// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, RowMergeData.class).sheet("模板").doWrite(data());// 方法2 自定义合并单元格策略fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";// 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());}/*** 使用table去写入* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 然后写入table即可*/public void tableWrite() {String fileName = "tableWrite" + System.currentTimeMillis() + ".xlsx";// 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();// 第一次写入会创建头excelWriter.write(data(), writeSheet, writeTable0);// 第二次写如也会创建头,然后在第一次的后面写入数据excelWriter.write(data(), writeSheet, writeTable1);}}/*** 动态头,实时生成头写入* <p>* 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据** <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 然后写入table即可*/public void dynamicHeadWrite() {String fileName = "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName)// 这里放入动态头.head(head()).sheet("模板")// 当然这里数据也可以用 List<List<String>> 去传入.doWrite(data());}/*** 自动列宽(不太精确)* <p>* 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照 {@link LongestMatchColumnWidthStyleStrategy}* 重新实现.* <p>* poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。** <p>* 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData}* <p>* 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy}* <p>* 3. 直接写即可*/public void longestMatchColumnWidthWrite() {String fileName = "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, LongestMatchColumnWidthData.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());}/*** 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)* <p>* demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 注册拦截器 {@link CustomSheetWriteHandler}* <p>* 2. 直接写即可*/public void customHandlerWrite() {String fileName = "customHandlerWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).registerWriteHandler(new CustomSheetWriteHandler()).registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data());}/*** 插入批注* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 注册拦截器 {@link CommentWriteHandler}* <p>* 2. 直接写即可*/public void commentWrite() {String fileName = "commentWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。EasyExcel.write(fileName, User.class).inMemory(Boolean.TRUE).registerWriteHandler(new CommentWriteHandler()).sheet("模板").doWrite(data());}/*** 可变标题处理(包括标题国际化等)* <p>* 简单的说用List<List<String>>的标题 但是还支持注解* <p>* 1. 创建excel对应的实体对象 参照{@link UserFormat}* <p>* 2. 直接写即可*/public void variableTitleWrite() {// 写法1String fileName = "variableTitleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, UserFormat.class).head(variableTitleHead()).sheet("模板").doWrite(data());}/*** 不创建对象的写*/public void noModelWrite() {// 写法1String fileName = "noModelWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList());}private List<LongestMatchColumnWidthData> dataLong() {List<LongestMatchColumnWidthData> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {LongestMatchColumnWidthData data = new LongestMatchColumnWidthData();data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i);data.setDate(new Date());data.setDoubleData(1000000000000.0);list.add(data);}return list;}private List<List<String>> variableTitleHead() {List<List<String>> list = ListUtils.newArrayList();List<String> head0 = ListUtils.newArrayList();head0.add("string" + System.currentTimeMillis());List<String> head1 = ListUtils.newArrayList();head1.add("number" + System.currentTimeMillis());List<String> head2 = ListUtils.newArrayList();head2.add("date" + System.currentTimeMillis());list.add(head0);list.add(head1);list.add(head2);return list;}private List<List<String>> head() {List<List<String>> list = ListUtils.newArrayList();List<String> head0 = ListUtils.newArrayList();head0.add("字符串" + System.currentTimeMillis());List<String> head1 = ListUtils.newArrayList();head1.add("数字" + System.currentTimeMillis());List<String> head2 = ListUtils.newArrayList();head2.add("日期" + System.currentTimeMillis());list.add(head0);list.add(head1);list.add(head2);return list;}private List<List<Object>> dataList() {List<List<Object>> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {List<Object> data = ListUtils.newArrayList();data.add("字符串" + i);data.add(0.56);data.add(new Date());list.add(data);}return list;}private List<User> data() {List<User> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {User data = new User();data.setCname("字符串" + i);data.setCreateTime(new Date());data.setScore(0.56);list.add(data);}return list;}
}
以上代码取自官方内的实现自测,具体实现类可详见:
https://github.com/CzyerChen/springboot-forall/tree/master/boot-for-easyexcel/src/main/java/com/learning/easyexcel
5.3 常规WEB的上传和下载
这个文件的接口和回写基本就是引用过Read和Write的具体方法,参照官方的书写方式:
https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
/*** 文件下载(失败了会返回一个有部分数据的Excel)* <p>* 1. 创建excel对应的实体对象 参照{@link DownloadData}* <p>* 2. 设置返回的 参数* <p>* 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大*/@GetMapping("download")public void download(HttpServletResponse response) throws IOException {// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postmanresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());}/*** 文件上传* <p>1. 创建excel对应的实体对象 参照{@link UploadData}* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}* <p>3. 直接读即可*/@PostMapping("upload")@ResponseBodypublic String upload(MultipartFile file) throws IOException {EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();return "success";}
相关文章:
EasyExcel 让Excel导入导出更简单
一、什么是EasyExcel?二、对比ApachePoi 其他Excel框架 优势在于哪里三、本质上对于原生做了哪些优化?四、有哪些功能五、如何实践? 5.1 常规文件读取5.2 常规文件写入5.3 常规WEB的上传和下载 一、什么是EasyExcel? EasyExcel是对07版POI的提升和优…...
华为OD机试 - 需要广播的服务器数量 | 机试题算法思路 【2023】
最近更新的博客 华为OD机试 - 简易压缩算法(Python) | 机试题算法思路 【2023】 华为OD机试题 - 获取最大软件版本号(JavaScript) 华为OD机试 - 猜字谜(Python) | 机试题+算法思路 【2023】 华为OD机试 - 删除指定目录(Python) | 机试题算法思路 【2023】 华为OD机试 …...

三次握手四次挥手详细解析面试常问
文章目录1.第2次握手传回了ACK,为什么还要传回SYN?2.断开连接-TCP 四次挥手3.为什么要四次挥手?4.为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手?5.如果第二次挥手时服务器的 ACK 没有送达客户端&#x…...

组合由于继承
目录 前言: 1.什么是继承? 2.继承的劣势、问题? 3.组合相比继承有哪些优势? 4、如何判断该用组合还是继承? 参考资料 前言: 我们在平时日常开发设计的过程中,经常会有人提到一条经典的设…...

大学计算机基础 知识点总结
一/ 计算机的发展、类型及其应用领域。 1. 计算机(computer)是一种能自动、高速进行大量算术运算和逻辑运算的电子设备。 其特点为:速度快、精度高、存储容量大、通用性强、具有逻辑判断和自动控制能力。 2. 第一台计算机:ENIAC,美国&#…...

手撸React组件库前必须清楚的9个问题
1. 组件库文档问题 以前常用的组件库文档storybook,包括现在也有用dumi、vitepress做组件库文档等。storybook缺点不美观、webpack的热更新太慢,虽然新版本支持了vite提高了速度但还不算稳定。好在各种文档、mdx、测试等组件第三方工具很多集成进去能很…...

试用国内及国外AI绘图软件后的总结
最近AI很火,所以这几天抱着试试看的角度试用了多款AI绘图软件,大概测试了市面上的3款工具吧,3款国外的,1款国内的。因为有对比,波哥也不是专业的评测机构出身,所以这些比对无论是从角度,还是从对…...

DJI 无人机 Onboard SDK ROS 功能包demo运行
DJI 无人机 Onboard SDK ROS 功能包demo运行demo功能准备测试环境运行 dji sdk 节点运行 demo 节点自动飞行任务航点自动飞行兴趣点环绕自动飞行飞行控制本地坐标位置控制搭建好 Onboard SDK ROS 的开发环境后,功能包自身具备一些写好的demo功能案例 dji sdk 的节点…...

揭开JavaWeb中Cookie与Session的神秘面纱
文章目录1,会话跟踪技术的概述2,Cookie2.1 Cookie的基本使用2.2 Cookie的原理分析2.3 Cookie的使用细节2.3.1 Cookie的存活时间2.3.2 Cookie存储中文3,Session3.1 Session的基本使用3.2 Session的原理分析3.3 Session的使用细节3.3.1 Session…...

2023-02-20 Qt 5.13.1 + OpenCV 4.5.4环境编译
引言 OpenCV图像处理在Qt中编译记录。 之前一直是在Python中使用OpenCV,Python中使用某些模块使用pip工具很容易将对应的模块安装在系统中。根据项目需求项目都要转移在国产化中使用,为了适应国产化需求,将代码转移到Qt开发环境中,…...

波次分拣系统
一、系统架构: v1.2基站软件管理系统仓库标签v1.4仓库标签二、系统简介: 标签系统主要由标签服务器,基站,电子标签前三部分组成,操作界面借助于京东仓库已有的作业电脑来实现,标签服务器与WMS进行数据对接。…...

【Servlet篇】Request请求转发详细解读
文章目录1. 前言2. 实战案例3. 特点1. 前言 请求转发是一种在服务器内部的资源跳转方式,如图: 上图的大致过程为,浏览器发送请求给服务器,服务器中 a 资源接收到请求,资源 a 处理完请求后将请求发送给资源 bÿ…...

vector
目录 vector的成员函数: at: 编辑 size: assign:赋值 insert find? erase swap shrink_to_fit 编辑 vector的模拟实现: vector的框架: 构造函数: size和capacity r…...

LeetCode——104. 二叉树的最大深度
一、题目 给定一个二叉树,找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/maximum…...

漫画 | Python是一门烂语言?
这个电脑的主人是个程序员,他相继学习了C、Java、Python、Go, 但是似乎总是停留在Hello World的水平。 每天晚上,夜深人静的时候,这些Hello World程序都会热火朝天地聊天但是,这一天发生了可怕的事情随着各个Hello wor…...

2023.2 新方案 java代码混淆 java加密 字符串加密
Java字节码可以反编译,特别是创业公司,很好的项目很容易被别人破解反编译,造成很严重的损失,所以本混淆方案能很好的保护源码,而且在不断迭代,增强混淆效果,异常问题处理,达到保护项目的目的: 本次升级包括: 2023年02年19日 : ht-confusion-project-1.8…...

Swift 周报 第二十三期
前言 本期是 Swift 编辑组自主整理周报的第十四期,每个模块已初步成型。各位读者如果有好的提议,欢迎在文末留言。 欢迎投稿或推荐内容。目前计划每两周周一发布,欢迎志同道合的朋友一起加入周报整理。 勇敢是即便知道好结局不会每每降临在…...
android系统屏幕旋转角度,应用界面横竖屏,设备旋转角度,三者的区别以及使用。
注意区分以下三种概念的区别!!!。以及使用这三种方式判断横竖屏的方式。系统屏幕旋转角度fun getSystemRotation(): Int {val angle (getSystemService(WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation//系统屏幕旋转的角度值re…...

【华为云-开发者专属集市】DevCloud+ECS、MySQL搭建WordPress
文章目录AppBazaar官网选择与购买项目项目概况操作过程购买DevCloud服务创建项目添加制品库应用部署购买ECS添加部署模板并执行任务故障排除安装及访问WordPress登录网站管理后台访问网站完善部署模板资源释放使用总结AppBazaar官网 首先,我们来到AppBazaar的官网&…...

Milvus 群星闪耀时|又一个小目标达成 :社区正式突破 15,000 星!
如果把 Milvus 看作开源世界中的一束微光,那用户便是无垠宇宙中点点闪烁的星光。用户每一次点亮 star 之时,Milvus 就会迸发出更加耀眼的光芒。不知不觉,已有数以万计的 star 为 Milvus 而亮。2022 年 4 月,Milvus 在 GitHub 的 …...

如何以 9 种方式将照片从 iPhone 传输到笔记本电脑
您的 iPhone 可能充满了以照片和视频形式捕捉的珍贵回忆。无论您是想备份它们、在更大的屏幕上编辑它们,还是只是释放设备上的空间,您都需要将照片从 iPhone 传输到笔记本电脑。幸运的是,有 9 种方便的方法可供使用,同时满足 Wind…...
gitlib 常见命令
git clone <项目URL> # 从 GitLab 拉取代码到本地 git status 查看状态 git diff 文件路径 查看修改位置 git diff 文件路径 查看修改位置 black -l 180 路径 格式化文件 git add 路径 (可以多个) 添加修改到暂存区 git commit -m “提交说明…...

人工智能浪潮下,制造企业如何借力DeepSeek实现数字化转型?
一、DeepSeek技术概述 DeepSeek,凭借其强大的深度学习和自然语言处理能力,能够理解复杂问题并提供精准解决方案。它不仅能够作为学习、工作、生活的助手,满足用户在不同场景下的需求,更能在制造业中发挥重要作用。通过自然语言交…...
Java并发编程实战 Day 3:volatile关键字与内存可见性
【Java并发编程实战 Day 3】volatile关键字与内存可见性 开篇 欢迎来到《Java并发编程实战》系列的第3天!本系列旨在带领你从基础到高级逐步掌握Java并发编程的核心概念和最佳实践。 今天我们将重点探讨volatile关键字及其在多线程程序中确保内存可见性的作用。我…...

因泰立科技:镭眸T51激光雷达,打造智能门控新生态
在高端门控行业,安全与效率是永恒的追求。如今,随着科技的飞速发展,激光雷达与TOF相机技术的融合,为门控系统带来了前所未有的智能感知能力,开启了精准守护的新时代。因泰立科技的镭眸T51激光雷达,作为这一…...
python集成inotify-rsync实现跨服务器文件同步
1、实现功能 通过结合 Python 的 watchdog 库(类似 Linux 的 inotify 机制)和 rsync 命令,实现了文件系统变化的实时监控和增量同步。下面详细解释其工作原理和运行方式: 2、核心工作原理 2.1、文件监控 使用watchdog库监控源目…...

【论文精读】2024 ECCV--MGLD-VSR现实世界视频超分辨率(RealWorld VSR)
文章目录 一、摘要二、问题三、Method3.1 Latent Diffusion Model3.2 Motion-guided Diffusion Sampling3.3 Temporal-aware Decoder Fine-tuning 四、实验设置4.1 训练阶段4.2 训练数据 贡献总结 论文全称: Motion-Guided Latent Diffusion for Temporally Consis…...
PySpark 中使用 SQL 语句和表进行计算
PySpark 中使用 SQL 语句和表进行计算 PySpark 完全支持使用 SQL 语句和表进行 Spark 计算。以下是几种常见的使用方式: 1. 使用 Spark SQL from pyspark.sql import SparkSession# 创建 SparkSession spark SparkSession.builder.appName("SQLExample&quo…...
从0开始学vue:实现一个简单页面
Vue.js 是一个渐进式JavaScript框架,用于构建用户界面。下面我将带你从零开始学习Vue.js并创建一个简单的可运行页面。 1. 准备工作 首先,你需要了解几种学习Vue.js的方式: 方式一:使用CDN引入(最简单的方式&#x…...
基于 stm32 的农用车控制系统设计
一、系统功能需求分析 农用车控制系统需实现多方面功能,以满足农业生产多样化需求。在动力控制方面,要实现发动机转速调节、挡位自动切换;作业控制涵盖农机具升降、播种施肥量调节、喷洒农药的流量与压力控制等;同时,还需具备车辆状态监测功能,实时监控发动机温度、油压…...