refactor(report): 优化报告生成服务的安全性和文档处理

- 添加 FilePathSanitizer 工具类,防止路径穿越和非法字符问题
- 在报告目录路径构建中使用安全的文件名处理
- 为 BookmarkEnum 添加排序字段和注释说明
- 改进书签处理排序逻辑,使用 sort 字段而非依赖枚举声明顺序
- 添加批量处理的语义说明文档
- 优化数据处理流程中的哨兵标记机制
- 为 WordReportService 接口添加详细的资源管理契约文档
- 改进 dealDataLine 方法的职责分离和参数命名
- 修复测试结果详情书签键值引用错误
This commit is contained in:
2026-05-26 19:20:03 +08:00
parent 13677f21d9
commit 3cb4a46a16
6 changed files with 142 additions and 23 deletions

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}
}