refactor(report): 优化报告生成服务的安全性和文档处理
- 添加 FilePathSanitizer 工具类,防止路径穿越和非法字符问题 - 在报告目录路径构建中使用安全的文件名处理 - 为 BookmarkEnum 添加排序字段和注释说明 - 改进书签处理排序逻辑,使用 sort 字段而非依赖枚举声明顺序 - 添加批量处理的语义说明文档 - 优化数据处理流程中的哨兵标记机制 - 为 WordReportService 接口添加详细的资源管理契约文档 - 改进 dealDataLine 方法的职责分离和参数命名 - 修复测试结果详情书签键值引用错误
This commit is contained in:
@@ -14,10 +14,17 @@ public interface IWordReportService {
|
||||
|
||||
/**
|
||||
* 替换Word文档中的占位符
|
||||
*
|
||||
*
|
||||
* @param templateInputStream 模板文档输入流
|
||||
* @param placeholderMap 占位符替换映射表,key为占位符标识,value为替换值
|
||||
* @return 处理后的文档输入流,调用方可根据需要进行下载、上传等操作
|
||||
* @return 处理后的文档输入流,调用方可根据需要进行下载、上传等操作。
|
||||
* <p>
|
||||
* <b>资源管理契约:</b>当前实现返回 {@link java.io.ByteArrayInputStream},
|
||||
* 其 {@code close()} 为空操作、内部仅持有 byte 数组、不占用文件句柄等 native 资源,
|
||||
* 调用方<strong>不强制</strong>用 try-with-resources 包裹该返回流;
|
||||
* 若未来该方法的实现改为返回底层依赖文件 / 网络 / 临时文件的真实流,
|
||||
* 必须先改造所有调用方按 try-with-resources 关流后再合并实现,
|
||||
* 以避免句柄泄漏。
|
||||
* @throws Exception 处理异常
|
||||
*/
|
||||
InputStream replacePlaceholders(InputStream templateInputStream, Map<String, String> placeholderMap) throws Exception;
|
||||
|
||||
@@ -40,6 +40,10 @@ public class WordReportServiceImpl implements IWordReportService {
|
||||
PlaceholderUtil.replaceAllPlaceholders(mainDocumentPart, placeholderMap);
|
||||
|
||||
// 将处理后的文档转换为字节数组输入流
|
||||
// 注:返回类型必须是 ByteArrayInputStream 或 close() 为空操作的等价流,
|
||||
// 以满足 IWordReportService.replacePlaceholders 接口契约
|
||||
// (多个调用方未对返回流做 try-with-resources 兜底)。
|
||||
// 若需改为依赖文件 / 网络 / 临时文件的真实流,必须先改造所有调用方再合并。
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
wordPackage.save(outputStream);
|
||||
byte[] documentBytes = outputStream.toByteArray();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.njcn.gather.tools.report.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 文件路径片段净化工具。
|
||||
* <p>
|
||||
* 用于把外部输入(数据库中的设备型号名、计划名等)作为目录或文件名片段拼入磁盘路径前的兜底清洗,
|
||||
* 防止 {@code /} {@code \} {@code ..} {@code :} 等路径敏感字符导致:
|
||||
* <ul>
|
||||
* <li>报告文件被写到预期目录之外(路径穿越)</li>
|
||||
* <li>名字含 {@code /} 时被操作系统解释成多级子目录(即使无恶意,自然命名如"高/中压"也会触发)</li>
|
||||
* <li>Windows 上 {@code :} 触发 NTFS 备用数据流</li>
|
||||
* <li>文件创建失败抛 IO 异常</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public final class FilePathSanitizer {
|
||||
|
||||
/**
|
||||
* Windows + Linux 共同非法字符 + 控制字符:替换为下划线
|
||||
*/
|
||||
private static final String UNSAFE_CHAR_PATTERN = "[\\\\/:*?\"<>|\\x00-\\x1F]";
|
||||
|
||||
private FilePathSanitizer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一段字符串净化成可安全拼入磁盘路径的片段。
|
||||
* <p>
|
||||
* 规则:
|
||||
* <ol>
|
||||
* <li>{@code null} 或全空白 → 返回 {@code "_"}(保证拼出来的路径仍合法)</li>
|
||||
* <li>{@code /} {@code \} {@code :} {@code *} {@code ?} {@code "} {@code <} {@code >} {@code |}
|
||||
* 及 ASCII 控制字符替换为 {@code _}</li>
|
||||
* <li>把 {@code ..} 折叠为 {@code _},防止路径穿越</li>
|
||||
* <li>连续 {@code _} 合并为单个 {@code _}</li>
|
||||
* <li>首尾空白与 {@code .} 去掉(Windows 不允许文件名以 {@code .} 结尾)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param raw 原始字符串
|
||||
* @return 净化后的片段;当输入合法且无危险字符时与原值一致
|
||||
*/
|
||||
public static String toSafeFileName(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return "_";
|
||||
}
|
||||
String result = raw.replaceAll(UNSAFE_CHAR_PATTERN, "_");
|
||||
// 折叠路径穿越序列
|
||||
while (result.contains("..")) {
|
||||
result = result.replace("..", "_");
|
||||
}
|
||||
// 合并连续下划线
|
||||
result = result.replaceAll("_+", "_");
|
||||
// 去掉首尾空白和点
|
||||
result = result.replaceAll("^[\\s.]+|[\\s.]+$", "");
|
||||
return result.isEmpty() ? "_" : result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user