一、定义导出行DTO
这是最基础的。
参考现成的例子:
java
@Data
public class OrgBranchExportRowDTO implements Serializable {
@ExcelProperty(value = "网点名称")
private String orgName;
@ExcelProperty(value = "网点ID")
private String orgCode;
@ExcelProperty(value = "网点地址")
private String address;
}
这里的规则很简单:
- 一个字段对应Excel一列
ExcelProperty(value = "...")就是表头名- 字段顺序通常就是导出顺序
二、查询业务数据并组长导出行
不要直接把数据库实体拿去导出,要先转成导出DTO
java
List<OrgBranchExportRowDTO> rows = orgIds.stream()
.map(orgById::get)
.map(org -> {
OrgBranchExportRowDTO row = new OrgBranchExportRowDTO();
row.setOrgName(org.getOrgName());
row.setOrgCode(org.getOrgCode());
row.setAddress(org.getAddress());
return row;
})
.toList();
三、创建临时目录
生成文件前,先准备目录
java
File materialDirectory = FileUtil.mkdir(
FileUtil.getTmpDirPath() + File.separator + "org_branch_export_" + UUID.randomUUID()
);
意思是:
- 放到系统临时目录
- 每次生成一个唯一目录
- 避免同名冲突
四、调用GenerateExcelUtil生成文件
简单导出直接用:
java
GenerateExcelUtil.exportExcelToFile(rows, sheetName, clazz, materialDirectory, fileName, generateType);
完整实例:
java
String fileName = "司机信息报表_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
GenerateExcelUtil.GenerateType generateType = GenerateExcelUtil.GenerateType.XLSX;
GenerateExcelUtil.exportExcelToFile(
rows,
"司机信息报表",
DriverExportRowDTO.class,
materialDirectory,
fileName,
generateType
);
这一步做完后,磁盘上就已经有文件了。
五、 拿到生成后的文件
可以像现有代码这样封装一个返回对象:
java
OrgBranchExportFileDTO exportFile = new OrgBranchExportFileDTO();
exportFile.setFile(FileUtil.file(materialDirectory, fileName + "." + generateType.getExtension()));
exportFile.setFileName(fileName + "." + generateType.getExtension());
如果你不想包DTO,也可以直接:
java
File excelFile = FileUtil.file(materialDirectory, fileName + "." + generateType.getExtension());
六、返回给前端下载
如果你是Controller里直接下载,最简单就是:
java
GenerateExcelUtil.writeExcelToResponse(excelFile);
他已经帮你做了:
- 设置附件下载响应头
- 写出文件
- 删除临时文件
如果你要多个文件打包下载:
java
GenerateExcelUtil.writeZipToResponse(materialDirectory);
java
@Slf4j
public class GenerateExcelUtil {
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ExcelWriteDto<T extends ExcelSheet> {
File materialDirectory;
@Builder.Default
GenerateType generateType = GenerateType.XLSX;
String excelFileName;
List<T> data;
CaseExcelImportContext context;
List<Double> caseNos;
Class<T> headerClass;
List<WriteHandler> writeHandlers;
@Builder.Default
List<List<String>> dynamicHeader = new ArrayList<>();
@Builder.Default
List<Map<String, Object>> customList = new ArrayList<>();
@Builder.Default
List<String> excludeColumnFieldNames = new ArrayList<>();
}
@Getter
@AllArgsConstructor
public enum GenerateType {
XLSX("xlsx"), CSV("csv");
private final String extension;
public static GenerateType getByExtension(String extension) {
for (GenerateType value : values()) {
if (value.extension.equals(extension)) {
return value;
}
}
return null;
}
}
public static File getExcelFile(File materialDirectory, String excelFileName, GenerateType generateType) {
if (StringUtils.isBlank(excelFileName)) {
excelFileName = "导出信息";
}
File parentFile = materialDirectory.getParentFile();
return FileUtil.file(parentFile, excelFileName + "." + generateType.extension);
}
public static <T extends ExcelSheet> File writeToExcelWithCustomDataTo(ExcelWriteDto<T> excelWriteDto) {
return writeToExcelWithCustomDataTo(excelWriteDto, 1);
}
public static <T extends ExcelSheet> File writeToExcelWithCustomDataTo(ExcelWriteDto<T> excelWriteDto, int headRowNum) {
File materialDirectory = excelWriteDto.getMaterialDirectory();
String excelFileName = excelWriteDto.getExcelFileName();
List<T> data = excelWriteDto.getData();
// CaseExcelImportContext context = excelWriteDto.getContext();
List<Double> caseNos = excelWriteDto.getCaseNos();
//按一级表头排序
List<List<String>> dynamicHeader = excelWriteDto.getDynamicHeader().stream()
.sorted(Comparator.comparing(List::getFirst))
.collect(Collectors.toList());
List<Map<String, Object>> customList = excelWriteDto.getCustomList();
List<String> excludeColumnFieldNames = excelWriteDto.getExcludeColumnFieldNames();
Class<T> headerClass = excelWriteDto.getHeaderClass();
// List<WriteHandler> writeHandlers = excelWriteDto.getWriteHandlers();
//写入表头
Map<String, List<List<String>>> head = toList(dynamicHeader, "序号", new HashMap<>(), excludeColumnFieldNames, headerClass);
List<Integer> excludeColumnIndexes = excludeColumnIndexes(excludeColumnFieldNames, headerClass);
ExcelWriterSheetBuilder mainSheetBuilder = FesodSheet.writerSheet(SheetNameConstant.EXPORT_INFO)
.head(head.get("result"))
// .head(headerClass)
.excludeColumnIndexes(excludeColumnIndexes)
// .registerWriteHandler(new HeadCellWriteHandler<>(context)) //头处理器
.registerWriteHandler(new DynamicHeadCellWriteHandler(headerClass, head.get("excludeResult"), excludeColumnFieldNames, customList, headRowNum)) //动态头处理器
.registerWriteHandler(new CustomCellMergeStrategy(data.size(), caseNos)) //自定义合并策略
// .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())//自动适配
.registerConverter(new ExcelBigNumberConvert());// 大数值自动转换 防止失真
//注册写处理器
// for (WriteHandler handler : writeHandlers) mainSheetBuilder.registerWriteHandler(handler);
File excelFile = getExcelFile(materialDirectory, excelFileName, excelWriteDto.getGenerateType());
try (ExcelWriter excelWriter = FesodSheet.write(excelFile).build()) {
excelWriter.write(Collections.emptyList(), FesodSheet.writerSheet(SheetNameConstant.OPTIONAL_DATA).build());//写入下拉框选择sheet
excelWriter.write(data, mainSheetBuilder.build());//写入主Sheet
Workbook workbook = excelWriter.writeContext().writeWorkbookHolder().getWorkbook();
workbook.getSheet(SheetNameConstant.EXPORT_INFO).createFreezePane(0, headRowNum);//冻结表头
Sheet optionalData = workbook.getSheet(SheetNameConstant.OPTIONAL_DATA);
optionalData.protectSheet("kxsjLock");
workbook.setSheetHidden(workbook.getSheetIndex(optionalData), true);//隐藏下拉选择Sheet
}
return excelFile;
}
private static Map<String, List<List<String>>> toList(List<List<String>> dynamicHeader, String noColName
, Map<String, String> headRenameMap, List<String> excludeFields, Class<? extends ExcelSheet> headerClass) {
Map<String, List<List<String>>> resultMap = new HashMap<>();
Field[] fields = ReflectUtil.getFields(headerClass);
//初始化一个fields长度的集合
List<List<String>> result = new ArrayList<>();
while (result.size() != fields.length) result.add(null);
List<List<String>> excludeResult = new ArrayList<>(result);
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if (Objects.nonNull(excelProperty)) {
int index = excelProperty.index();
List<String> value = Arrays.stream(excelProperty.value()).collect(Collectors.toList());
// 自定义序号表头不为空,替换序号列表头
if (StringUtils.isNotBlank(noColName) && "no".equals(field.getName())) {
value.set(0, noColName);
}
if (CollUtil.isNotEmpty(headRenameMap)
&& value.size() >= 2
&& headRenameMap.containsKey(value.get(1))) {
value.set(1, headRenameMap.get(value.get(1)));
}
//如果注解设置了index,设置index的值
if (index != -1) {
result.add(index, value);
} else {
result.add(i, value);
}
result.remove(null);
//排除字段后结果
if (!excludeFields.contains(field.getName())) {
if (index != -1) {
excludeResult.add(index, value);
} else {
excludeResult.add(i, value);
}
excludeResult.remove(null);
}
}
}
if (!CollectionUtils.isEmpty(dynamicHeader)) {
result.addAll(dynamicHeader);
excludeResult.addAll(dynamicHeader);
}
resultMap.put("result", result);
resultMap.put("excludeResult", excludeResult);
return resultMap;
}
private static List<Integer> excludeColumnIndexes(List<String> excludeColumnFieldNames, Class<? extends ExcelSheet> headerClass) {
Field[] fields = ReflectUtil.getFields(headerClass);
//初始化一个fields长度的集合
List<String> result = new ArrayList<>();
while (result.size() != fields.length) result.add(null);
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if (Objects.nonNull(excelProperty)) {
int index = excelProperty.index();
// 自定义序号表头不为空,替换序号列表头
//如果注解设置了index,设置index的值
if (index != -1) {
result.add(index, field.getName());
} else {
result.add(i, field.getName());
}
result.remove(null);
}
}
return excludeColumnFieldNames.stream()
.map(result::indexOf)
.collect(Collectors.toList());
}
public static void writeZipToResponse(File materialDirectory) {
File zip = null;
File parentFile = null;
try {
parentFile = materialDirectory.getParentFile();
//压缩目录并写回响应体
zip = ZipUtil.zip(parentFile);
FileUtil.del(materialDirectory);
HttpServletResponse response = ServletUtils.getResponse();
FileUtils.setAttachmentResponseHeader(response, zip.getName());
response.addHeader("Content-Length", "" + zip.length());
response.addHeader("Access-Control-Allow-Origin", "*");
JakartaServletUtil.write(response, zip);
} catch (Exception e) {
log.error("文件下载失败: " + (zip != null ? zip.getName() : "未知文件"), e);
AssertUtil.throwException("文件下载失败");
} finally {
FileUtil.del(zip);
FileUtil.del(parentFile);
}
}
public static void writeExcelToResponse(File excelFile) {
if (excelFile == null || !excelFile.exists()){
AssertUtil.throwException("excel文件不存在");
}
try {
HttpServletResponse response = ServletUtils.getResponse();
FileUtils.setAttachmentResponseHeader(response, excelFile.getName());
response.addHeader("Content-Length", "" + excelFile.length());
response.addHeader("Access-Control-Allow-Origin", "*");
JakartaServletUtil.write(response, excelFile);
} catch (Exception e) {
log.error("文件下载失败: " + excelFile.getName(), e);
AssertUtil.throwException("文件下载失败");
} finally {
FileUtil.del(excelFile);
}
}
/**
* 导出 Excel 到指定文件路径
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param materialDirectory 目标文件路径
* @param merge 是否合并单元格
*/
public static <T> File exportExcelToFile(List<T> list, String sheetName, Class<T> clazz, File materialDirectory, String excelFileName, boolean merge, GenerateType generateType) {
if (StringUtils.isBlank(excelFileName)) {
excelFileName = "案件信息";
}
File excelFile = FileUtil.file(materialDirectory, excelFileName + "." + generateType.getExtension());
ExcelWriterSheetBuilder builder = FesodSheet.write(excelFile, clazz)
// .autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.sheet(sheetName);
if (merge) {
// 合并处理器
builder.registerWriteHandler(new CellMergeStrategy(list, true));
}
builder.doWrite(list);
return excelFile;
}
/**
* 导出 Excel 到指定文件(默认不合并单元格)
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param materialDirectory 目标文件路径
*/
public static <T> File exportExcelToFile(List<T> list, String sheetName, Class<T> clazz, File materialDirectory, String excelFileName, GenerateType generateType) {
return exportExcelToFile(list, sheetName, clazz, materialDirectory, excelFileName, false, generateType);
}
}