业务场景
甲方要求,导入一张年度预算表(如下图),部门和项目不固定,存到数据库,申请预算通过后,修改数据值,并可以导出预算表:年底,超出预算表进行核验。
总结一下,就是:导入一张动态的数据EXCEL表:行、列不固定;导入后存入数据库中,并对数据进行查询,修改。
实现思路
设想一下,把excel表格看作一个X轴Y轴的坐标系;每个单元格看作一个坐标系是一个值,那么每个单元格=X坐标+Y坐标+Value;通过这个思路,我们就可以设计出数据表。
代码
数据表
sql
CREATE TABLE `custom_excel_data` (
`id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`business_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '业务名称',
`business_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '业务id',
`x` int DEFAULT NULL COMMENT 'x坐标',
`y` int DEFAULT NULL COMMENT 'y坐标',
`value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '值',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='自定义excel数据表';
使用jar包
使用jar包是阿里巴巴EasyExcel,3.3.2版本。
java
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
导入核心代码
java
package com.demo.common.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 创建一个自定义的监听器
*
* @author yangzhiwen
* @since 2024年3月11日
*/
@Slf4j
public class DynamicDataListener extends AnalysisEventListener<Map<Integer, Object>> {
private List<Map<Integer, Object>> allData = new ArrayList<>();
@Override
public void invoke(Map<Integer, Object> data, AnalysisContext context) {
// 每一行的数据都在这里处理
// data是一个Map,键是列索引(从0开始),值是对应单元格的值
log.info("Row Data: " + data);
allData.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 所有数据解析完成后的回调
log.info("数据解析完毕");
}
public List<Map<Integer, Object>> getAllData() {
return allData;
}
public static void main(String[] args) throws IOException {
URL url = new URL("http://10.16.95.68:8000/test/上传视频记录.xlsx");
File tempFile = File.createTempFile("temp_excel", ".xlsx");
// 下载远程文件到本地临时文件
try (InputStream in = url.openStream()) {
Files.copy(in, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
String tempFilePath = tempFile.getAbsolutePath();
DynamicDataListener dynamicDataListener = new DynamicDataListener();
// 使用EasyExcel读取Excel
EasyExcel.read(tempFilePath, dynamicDataListener)
// 指定读取哪个sheet,如果不需要指定,则会默认读取第一个sheet
.sheet("Sheet1")
// 开始读取
.doRead();
// 获取所有读取到的原始数据
List<Map<Integer, Object>> rawExcelData = dynamicDataListener.getAllData();
rawExcelData.forEach(e->{
log.info(String.valueOf(e.size()));
log.info(e.get(0).toString());
log.info(e.get(1).toString());
});
}
}
导出核心代码
这里是重新生成新的excel表格,并非去改原excel表格的值。
swift
@GetMapping("export")
public void export(HttpServletResponse response) throws Exception {
// 定义待导出数据集
List<List<String>> eList = new ArrayList<>();
// ..... 组装list数据进行导出
EasyExcelUtil.exportExcel(response, eList, (Class<List<String>>) null,"xxx.xls","xxx.xls");
}
scss
/**
* 根据模板导出excel
* @param response
* @param excelList 数据列表
* @param fileName 文件名称
* @param filePath 模版路径
* @param exportTitle 标题对象(标题可以为空)
* @throws Exception
*/
public static <T> void exportExcel(HttpServletResponse response, List<T> excelList,String fileName, String filePath,Object exportTitle) throws Exception {
fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
ServletOutputStream out = response.getOutputStream();
response.setContentType("multipart/form-data");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
InputStream inputStream = EasyExcelUtil.class.getClassLoader().getResourceAsStream(filePath);
ExcelWriter writer = EasyExcel.write(out).withTemplate(inputStream).build();
WriteSheet sheet = EasyExcel.writerSheet(0).build();
//填充列表开启自动换行,自动换行表示每次写入一条list数据是都会重新生成一行空行,此选项默认是关闭的,需要提前设置为true
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
if (ObjectUtils.isNotEmpty(exportTitle))writer.fill(exportTitle, sheet);//填充标题
writer.fill(excelList, fillConfig, sheet);//填充数据
//填充完成
writer.finish();
out.flush();
}
查询
传入首行、首列的值,就可以定位,目标坐标,从而拿到数据。
perl
@Override
public CustomExcelData getByXValueAndYValueAndBusinessId(String businessId, String xValue, String yValue) {
CustomExcelData xData = this.getOneByBusinessIdAndValue(businessId, xValue);
CustomExcelData yData = this.getOneByBusinessIdAndValue(businessId, yValue);
if (Objects.isNull(xData) || Objects.isNull(yData)) {
return null;
}
if (!xData.getY().equals(0) || !yData.getX().equals(0)) {
return null;
}
return this.getOneByBusinessIdAndXY(businessId, xData.getX(),yData.getY());
}
private CustomExcelData getOneByBusinessIdAndValue(String businessId, String value) {
return this.getOne(Wrappers.<CustomExcelData>lambdaQuery()
.eq(CustomExcelData::getBusinessId, businessId).eq(CustomExcelData::getValue, value));
}
private CustomExcelData getOneByBusinessIdAndXY(String businessId, Integer x, Integer y) {
return this.getOne(Wrappers.<CustomExcelData>lambdaQuery()
.eq(CustomExcelData::getBusinessId, businessId)
.eq(CustomExcelData::getX, x)
.eq(CustomExcelData::getY, y));
}
结语
这是一个巧妙的思路:把一个excel抽象成一个坐标系;可以完成一些特定场景的业务。
缺陷也很明显,Excel数据必须严格按照横纵方式进行排列;其他奇怪的格式,就不支持了。
抛砖引玉,或许这个思路对你有用。
不定时更新,欢迎订阅、评论、转发;
希望能让你得到启发。