From 34ea1db5854dc3acbe8c610a962e7b54676d4242 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 10 Jul 2022 00:00:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=20type=20=E8=AF=86=E5=88=AB=E4=B8=8E=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/util/io/FileUtils.java | 23 ++++++++++++++ .../file/core/utils/FileTypeUtils.java | 31 +++++++++++++++---- .../infra/dal/dataobject/file/FileDO.java | 6 +--- .../infra/service/file/FileServiceImpl.java | 7 ++--- .../infra/service/file/FileServiceTest.java | 6 ++-- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java index 63732f1b3..2f870d738 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java @@ -1,9 +1,14 @@ package cn.iocoder.yudao.framework.common.util.io; +import cn.hutool.core.io.FileTypeUtil; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; import lombok.SneakyThrows; +import java.io.ByteArrayInputStream; import java.io.File; /** @@ -58,4 +63,22 @@ public class FileUtils { return file; } + /** + * 生成文件路径 + * + * @param content 文件内容 + * @param originalName 原始文件名 + * @return path,唯一不可重复 + */ + public static String generatePath(byte[] content, String originalName) { + String sha256Hex = DigestUtil.sha256Hex(content); + // 情况一:如果存在 name,则优先使用 name 的后缀 + if (StrUtil.isNotBlank(originalName)) { + String extName = FileNameUtil.extName(originalName); + return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName; + } + // 情况二:基于 content 计算 + return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content)); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/utils/FileTypeUtils.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/utils/FileTypeUtils.java index d21b4879a..b623d364a 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/utils/FileTypeUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/utils/FileTypeUtils.java @@ -4,8 +4,6 @@ import com.alibaba.ttl.TransmittableThreadLocal; import lombok.SneakyThrows; import org.apache.tika.Tika; -import java.io.ByteArrayInputStream; - /** * 文件类型 Utils * @@ -16,14 +14,35 @@ public class FileTypeUtils { private static final ThreadLocal TIKA = TransmittableThreadLocal.withInitial(Tika::new); /** - * 获得文件的 mineType + * 获得文件的 mineType,对于doc,jar等文件会有误差 * - * @param data 文件内容 - * @return mineType + * @param data 包含文件开头几千个字节的字节数组 + * @return mineType 无法识别时会返回“application/octet-stream” */ @SneakyThrows public static String getMineType(byte[] data) { - return TIKA.get().detect(new ByteArrayInputStream(data)); + return TIKA.get().detect(data); + } + + /** + * 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用jar文件时,通过名字更为准确 + * + * @param name 文件名 + * @return mineType 无法识别时会返回“application/octet-stream” + */ + public static String getMineType(String name) { + return TIKA.get().detect(name); + } + + /** + * 在拥有文件和数据的情况下,最好使用此方法,最为准确 + * + * @param data 包含文件开头几千个字节的字节数组 + * @param name 文件名 + * @return mineType 无法识别时会返回“application/octet-stream” + */ + public static String getMineType(byte[] data, String name) { + return TIKA.get().detect(data, name); } } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java index 7e81280da..c0fb007e1 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java @@ -5,8 +5,6 @@ import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; -import java.io.InputStream; - /** * 文件表 * 每次文件上传,都会记录一条记录到该表中 @@ -46,9 +44,7 @@ public class FileDO extends BaseDO { */ private String url; /** - * 文件类型 - * - * 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取 + * 文件的 MIME 类型,例如 "application/octet-stream" */ private String type; /** diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 98af005f7..493a9ed1f 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.digest.DigestUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.io.FileUtils; import cn.iocoder.yudao.framework.file.core.client.FileClient; import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; @@ -40,10 +40,9 @@ public class FileServiceImpl implements FileService { @SneakyThrows public String createFile(String name, String path, byte[] content) { // 计算默认的 path 名 - String type = FileTypeUtils.getMineType(content); + String type = FileTypeUtils.getMineType(content, name); if (StrUtil.isEmpty(path)) { - path = DigestUtil.md5Hex(content) - + '.' + StrUtil.subAfter(type, '/', true); // 文件的后缀 + path = FileUtils.generatePath(content, name); } // 如果 name 为空,则使用 path 填充 if (StrUtil.isEmpty(name)) { diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java index e61039385..b5acbb8a4 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java @@ -40,7 +40,7 @@ public class FileServiceTest extends BaseDbUnitTest { // mock 数据 FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到 o.setPath("yunai"); - o.setType("jpg"); + o.setType("image/jpg"); o.setCreateTime(buildTime(2021, 1, 15)); }); fileMapper.insert(dbFile); @@ -48,7 +48,7 @@ public class FileServiceTest extends BaseDbUnitTest { fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou"))); // 测试 type 不匹配 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { - o.setType("png"); + o.setType("image/png"); })); // 测试 createTime 不匹配 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { @@ -90,7 +90,7 @@ public class FileServiceTest extends BaseDbUnitTest { assertEquals(10L, file.getConfigId()); assertEquals(path, file.getPath()); assertEquals(url, file.getUrl()); - assertEquals("jpg", file.getType()); + assertEquals("image/jpg", file.getType()); assertEquals(content.length, file.getSize()); }