跳转到正文

文件与图片

FileBase64UtilFileUtilImageCompressorUtil 位于 com.own.constant.util。它们处理文件内容和本地路径,不维护业务文件记录,也不自动接入对象存储;持久化资源管理见资源文件

要完成的任务从哪里开始
将文件内容转成 Base64,或还原为流文件 Base64
限制图片宽高、文件大小或转换格式图片压缩
精确复制文件或保存输入流使用 JDK Files.copy
维护已有 FileUtil 调用阅读目录副作用下载、写入限制

文件 Base64

从本地普通文件读取并编码,再解码为流:

java
import com.own.constant.util.FileBase64Util;
import java.nio.file.Path;

String encoded = FileBase64Util.fileToBase64(Path.of("input/report.pdf"));
if (encoded.isEmpty()) {
    throw new IllegalStateException("文件为空或编码失败");
}
try (var input = FileBase64Util.base64ToInputStream(encoded)) {
    System.out.println("字节数=" + input.readAllBytes().length);
}

代码放在允许抛出 IOException 的方法中,运行前准备输入文件。空文件和失败都可能得到空字符串;需要区分时应先检查文件或使用返回明确异常的读写 API。

方法行为与资源归属
toBase64(path)根据 HTTP/HTTPS scheme 选择 URL 或本地路径
pathToBase64(path) / fileToBase64(Path)读取普通文件;字符串路径会 trim,失败通常返回空字符串
inputStreamToBase64(input)将剩余内容编码到内存,不关闭调用方输入流
urlToBase64(url[, timeout])GET 请求,仅编码 2xx 响应;由工具关闭响应流
base64ToInputStream(text)MIME Base64 解码;空内容或解码失败返回空流
base64ToFile(text)解码到系统临时文件 base64-*.tmp;空内容或失败返回 null

网络连接超时固定为 5 秒,请求超时默认 5 秒,可传 Duration;null、零或负数回退默认值。客户端跟随普通重定向,非 2xx、I/O、非法 URL 或中断返回空字符串,中断会恢复线程中断标记。请求超时设置不应当作完整响应体读取的独立限时保证。

解码使用 MIME Decoder,可能忽略非 Base64 字符,不适合严格验证输入。Data URL 只识别 data: 前缀与逗号,不验证媒体类型或 base64 声明。编码结果和解码内容都在内存中,网络读取没有大小上限;调用方控制来源与大小。base64ToFile 成功返回的文件需要调用方删除。

图片压缩

以下示例将已有图片等比例压到最大 1024 × 1024,目标不超过 300 KiB,并写到指定 JPEG 文件:

java
import com.own.constant.ConstantFile;
import com.own.constant.util.ImageCompressorUtil;
import java.io.File;

var options = ImageCompressorUtil.Options.maxDimension(1024, 1024)
        .withMaxSizeKB(300)
        .withQuality(0.85f)
        .withOutputFormat(ConstantFile.Format.JPEG);
File result = ImageCompressorUtil.compress(
        new File("input/avatar.png"),
        new File("output/avatar.jpg"),
        options);
System.out.println(result.length());

输入必须是存在且可读的普通文件,并能被当前 ImageIO Reader 识别。输出覆盖指定路径,父目录自动创建;调用方确认目标路径。成功时输出字节数不超过 300 * 1024,尺寸受最大宽高约束。

Options 默认值与调整

Options 是 record,所有 with... 方法返回新对象,不修改原配置。

字段默认值调整入口与约束
maxWidth/maxHeight0/0withMaxDimension;0 表示不限制,不能负数
maxSizeBytes0withMaxSizeBytes / withMaxSizeKB;0 不限制,不能负数
quality0.85withQuality;应为有限值且满足 0 < quality <= 1
outputFormatnullwithOutputFormat;裁剪空白、转小写、移除前导点,jpg 归一为 jpeg
allowUpscalefalsewithAllowUpscale;默认不放大小图
scalingModeBALANCEDwithScalingMode;null 回退 BALANCED

可用 Options.defaults()maxDimension(width,height)maxSize(bytes) 开始组合。withMaxSizeKB 直接乘 1024,不检查算术溢出;构造器的质量比较也没有主动拒绝 NaN,应用需限制输入。

缩放模式 FAST、BALANCED、QUALITY 分别使用最近邻、双线性、双三次插值。输出格式按“显式配置 → 目标后缀 → 源后缀 → 透明图 PNG / 其他 JPEG”选择;JPEG/BMP 会把透明区合成白色背景。

ConstantFile 列出某个格式不表示当前 ImageIO 有对应编解码器;缺少 Writer 时压缩失败。实现只读取、写出单张 BufferedImage,不保留动画帧或原文件元数据。

大小限制与失败

设置目标大小后,JPEG 会尝试调整质量,仍超限则最多进行 12 轮缩小尺寸;其他格式主要依靠尺寸缩小。最终仍不满足目标时抛异常,不返回超限文件。

compress 将 I/O 与参数异常包装为 BusinessSimpleException,原错误保存在 getError()。在调用前构造 Options 时发生的参数异常则直接抛出,不经过压缩方法包装。

不传目标文件的两个重载 compress(source)compress(source, options) 会创建系统临时文件,成功后由调用方清理;在创建文件后压缩失败也没有自动清理保证。设置明确 outputFormat 可以避免从文件名推断格式;否则推断过程会调用 FileUtil,触发下述类初始化副作用。

本地目录与路径工具

FileUtil 首次主动使用会在进程工作目录下创建 files/uploadfiles/backupfiles/temp。即使只是调用文件名解析方法,也可能触发这些目录创建和临时文件清理。

这些目录不是按请求或租户隔离的存储空间;不要将仍在使用的文件放入会被自动清理的 temp 目录。只需要自行管理的本地路径时,可使用 JDK Path / Files,无需引入此处的目录约定。

FileUtil 目录与路径方法速查
方法当前行为
getStaticFileFolder/getUploadFolder/getBackupFolder返回工作目录下对应路径
getTempFolder()返回路径前触发清理;根目录普通文件超过 20 时按创建时间清到 19 个,删除失败可能留存更多
createTempFile(prefix, suffix)只返回 files/temp/<prefix><suffix> 对应 File,不创建文件、不保证唯一;也会触发清理
generateFolder(path)不存在时 mkdirs,不检查成功结果
deleteFile(file)递归删除,null/不存在时忽略,删除失败静默
randomName(extension)去横线 UUID + 点 + extension;参数应使用不带点的 ConstantFile.Extension
extractUriExtension(text)返回带点小写后缀,会截去其后的查询参数;不完整处理 fragment 或 URL 结构
extractFileNameFromUrl/Uri(...[, suffix])默认保留后缀,false 去最后后缀;非法 URI/编码可抛异常
getOriginFromUri(text)http 前缀和字符串位置截取来源,不是严格 URI 验证

下载、摘要与写入

FileUtil.write(folder, fileName, InputStream) 的写循环忽略每次实际读取的字节数,可能写入额外字节。需要精确保存输入流时,使用 Files.copy 并自行关闭流。例如,将下列片段放在允许抛出 IOException 的方法中,运行前准备 input/report.pdf

java
import java.nio.file.Files;
import java.nio.file.Path;

Path target = Path.of("output/report-copy.pdf");
Files.createDirectories(target.getParent());
try (var input = Files.newInputStream(Path.of("input/report.pdf"))) {
    long bytes = Files.copy(input, target);
    System.out.println("已复制字节数=" + bytes);
}

成功时目标内容与输入一致,输出实际复制的字节数。目标已存在时抛异常;确实需要覆盖时再传入 StandardCopyOption.REPLACE_EXISTING

FileUtil.downloadFile 没有设置超时、不检查 HTTP 状态且默认不跟随重定向,错误响应体也可能保存为文件。新下载流程需要由应用明确处理超时、状态与内容大小,不能以“返回 File”判断下载成功。

已有 FileUtil 下载、摘要与写入调用的行为
入口返回与限制
getFile(text)空白为 null,http 前缀触发下载,其他文本仅构造 File,不检查存在性
getFileMd5(String/File)返回小写 MD5;I/O 失败为空字符串。字符串 URL 会先下载,临时文件不会自动删除
downloadFile(url[, folder[, fileName[, forceUpdateExtension]]])保存到指定或模块临时目录;异常包装为 BusinessSimpleException
write(fileName, content) / write(fileName, suffix, content)UTF-8 文本写到模块 temp,前者默认 .txt,注册 deleteOnExit;I/O 失败返回 null
copyFile(source, target)复制字节;目标流先于父目录创建而打开,父目录不存在时会失败

downloadFile 显式传 fileName 时,forceUpdateExtension=true(默认)会追加 URL 后缀,例如 a.jpg.png 得到 a.jpg.png;false 时已有点的名称保留,否则仍追加后缀。

FileUtil.write(folder, fileName, InputStream) 会关闭输入流;前述写入限制同样影响文本流,不只影响图片。