feat(xlsx): 添加Excel导出工具类并重构导出功能

- 新增KitXlsxExportDataTransferRowHandler接口用于数据转换
- 新增KitXlsxExportUtil工具类提供统一的Excel导出功能
- 添加多个ExportRowVo类用于导出数据映射
- 重构部门、字典、岗位、菜单等模块的导出功能使用新工具类
- 添加SystemYesOrNoChConst和SystemZhConst常量接口
- 标记KitFileUtil.downloadExcel方法为废弃
- 优化文件名验证逻辑和字符串类型判断
This commit is contained in:
2025-12-30 18:01:24 +08:00
parent 88fbe36145
commit b15182afde
58 changed files with 709 additions and 554 deletions
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.odboy.util;
import static cn.odboy.constant.SystemConst.SYMBOL_ADD;
@@ -44,6 +43,7 @@ import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.IOUtils;
@@ -80,7 +80,6 @@ public final class KitFileUtil extends cn.hutool.core.io.FileUtil {
* 定义KB的计算常量
*/
private static final int KB = 1024;
/**
* 格式化小数
*/
@@ -185,6 +184,7 @@ public final class KitFileUtil extends cn.hutool.core.io.FileUtil {
/**
* 导出excel
*/
@Deprecated
public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {
String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
File file = new File(tempPath);
@@ -193,7 +193,8 @@ public final class KitFileUtil extends cn.hutool.core.io.FileUtil {
List<Map<String, Object>> sanitizedList = list.parallelStream().map(map -> {
Map<String, Object> sanitizedMap = new LinkedHashMap<>();
map.forEach((key, value) -> {
if (value instanceof String strValue) {
if (value instanceof String) {
String strValue = (String) value;
// 检查并处理以特殊字符开头的值
if (strValue.startsWith(SYMBOL_EQUAL) || strValue.startsWith(SYMBOL_ADD) ||
strValue.startsWith(SYMBOL_SUBTRACT) || strValue.startsWith(SYMBOL_AT)) {
@@ -206,7 +207,7 @@ public final class KitFileUtil extends cn.hutool.core.io.FileUtil {
}
});
return sanitizedMap;
}).toList();
}).collect(Collectors.toList());
// 一次性写出内容, 使用默认样式,强制输出标题
writer.write(sanitizedList, true);
SXSSFSheet sheet = (SXSSFSheet) writer.getSheet();
@@ -363,10 +364,8 @@ public final class KitFileUtil extends cn.hutool.core.io.FileUtil {
public static String verifyFilename(String fileName) {
// 过滤掉特殊字符
fileName = fileName.replaceAll("[\\\\/:*?\"<>|~\\s]", "");
// 去掉文件名开头和结尾的空格和点
fileName = fileName.trim().replaceAll("^[. ]+|[. ]+$", "");
// 不允许文件名超过255(在Mac和Linux中)或260(在Windows中)个字符
int maxFileNameLength = 255;
if (System.getProperty(SystemConst.PROPERTY_OS_NAME).startsWith(SystemConst.OS_NAME_WINDOWS)) {
@@ -375,20 +374,15 @@ public final class KitFileUtil extends cn.hutool.core.io.FileUtil {
if (fileName.length() > maxFileNameLength) {
fileName = fileName.substring(0, maxFileNameLength);
}
// 过滤掉控制字符
fileName = fileName.replaceAll("[\\p{Cntrl}]", "");
// 过滤掉 ".." 路径
fileName = fileName.replaceAll("\\.{2,}", "");
// 去掉文件名开头的 ".."
fileName = fileName.replaceAll("^\\.+/", "");
// 保留文件名中最后一个 "." 字符,过滤掉其他 "."
fileName = fileName.replaceAll("^(.*)(\\.[^.]*)$", "$1").replaceAll("\\.", "") +
fileName.replaceAll("^(.*)(\\.[^.]*)$", "$2");
return fileName;
}
@@ -0,0 +1,8 @@
package cn.odboy.util.xlsx;
import java.util.List;
public interface KitXlsxExportDataTransferRowHandler<O, T> {
List<T> doHandler(O o);
}
@@ -0,0 +1,62 @@
package cn.odboy.util.xlsx;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.FastExcel;
import cn.odboy.framework.exception.BadRequestException;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import lombok.experimental.UtilityClass;
/**
* xlsx文件导出工具
*
* @author odboy
* @date 2025-12-29
*/
@UtilityClass
public class KitXlsxExportUtil {
/**
* @param response HttpServletResponse
* @param title 标题
* @param dataObjectList 数据源,一般为从数据库直接查询的数据
* @param targetClazz 目标对象Class
* @param rowHandler 数据对象 -> 目标对象 映射逻辑
* @param <O> 数据源对象类型
* @param <T> 目标对象类型
* @throws IOException
*/
public static <O, T> void exportFile(HttpServletResponse response, String title, List<O> dataObjectList, Class<T> targetClazz, KitXlsxExportDataTransferRowHandler<O, T> rowHandler) throws IOException {
if (response == null) {
throw new BadRequestException("参数 HttpServletResponse(response) 必填");
}
if (CollUtil.isEmpty(dataObjectList)) {
throw new BadRequestException("参数 数据源(dataObjectList) 必填");
}
if (StrUtil.isBlank(title)) {
throw new BadRequestException("参数 标题(title) 必填");
}
String sheetName = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN);
List<T> resultList = new ArrayList<>();
for (O o : dataObjectList) {
List<T> rows = rowHandler.doHandler(o);
resultList.addAll(rows);
}
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(sheetName + "_" + title, StandardCharsets.UTF_8);
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
FastExcel.write(response.getOutputStream(), targetClazz)
.sheet(sheetName)
.doWrite(resultList);
}
}