优化文件的 type 识别与存储

This commit is contained in:
YunaiV 2022-07-10 00:00:56 +08:00
parent fec84f2682
commit 34ea1db585
5 changed files with 55 additions and 18 deletions

View File

@ -1,9 +1,14 @@
package cn.iocoder.yudao.framework.common.util.io; 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.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
/** /**
@ -58,4 +63,22 @@ public class FileUtils {
return file; 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));
}
} }

View File

@ -4,8 +4,6 @@ import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.tika.Tika; import org.apache.tika.Tika;
import java.io.ByteArrayInputStream;
/** /**
* 文件类型 Utils * 文件类型 Utils
* *
@ -16,14 +14,35 @@ public class FileTypeUtils {
private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new); private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new);
/** /**
* 获得文件的 mineType * 获得文件的 mineType对于docjar等文件会有误差
* *
* @param data 文件内容 * @param data 包含文件开头几千个字节的字节数组
* @return mineType * @return mineType 无法识别时会返回application/octet-stream
*/ */
@SneakyThrows @SneakyThrows
public static String getMineType(byte[] data) { 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);
} }
} }

View File

@ -5,8 +5,6 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*; import lombok.*;
import java.io.InputStream;
/** /**
* 文件表 * 文件表
* 每次文件上传都会记录一条记录到该表中 * 每次文件上传都会记录一条记录到该表中
@ -46,9 +44,7 @@ public class FileDO extends BaseDO {
*/ */
private String url; private String url;
/** /**
* 文件类型 * 文件的 MIME 类型例如 "application/octet-stream"
*
* 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取
*/ */
private String type; private String type;
/** /**

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.infra.service.file;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; 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.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.client.FileClient;
import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils; import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
@ -40,10 +40,9 @@ public class FileServiceImpl implements FileService {
@SneakyThrows @SneakyThrows
public String createFile(String name, String path, byte[] content) { public String createFile(String name, String path, byte[] content) {
// 计算默认的 path // 计算默认的 path
String type = FileTypeUtils.getMineType(content); String type = FileTypeUtils.getMineType(content, name);
if (StrUtil.isEmpty(path)) { if (StrUtil.isEmpty(path)) {
path = DigestUtil.md5Hex(content) path = FileUtils.generatePath(content, name);
+ '.' + StrUtil.subAfter(type, '/', true); // 文件的后缀
} }
// 如果 name 为空则使用 path 填充 // 如果 name 为空则使用 path 填充
if (StrUtil.isEmpty(name)) { if (StrUtil.isEmpty(name)) {

View File

@ -40,7 +40,7 @@ public class FileServiceTest extends BaseDbUnitTest {
// mock 数据 // mock 数据
FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到 FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到
o.setPath("yunai"); o.setPath("yunai");
o.setType("jpg"); o.setType("image/jpg");
o.setCreateTime(buildTime(2021, 1, 15)); o.setCreateTime(buildTime(2021, 1, 15));
}); });
fileMapper.insert(dbFile); fileMapper.insert(dbFile);
@ -48,7 +48,7 @@ public class FileServiceTest extends BaseDbUnitTest {
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou"))); fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou")));
// 测试 type 不匹配 // 测试 type 不匹配
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> {
o.setType("png"); o.setType("image/png");
})); }));
// 测试 createTime 不匹配 // 测试 createTime 不匹配
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> {
@ -90,7 +90,7 @@ public class FileServiceTest extends BaseDbUnitTest {
assertEquals(10L, file.getConfigId()); assertEquals(10L, file.getConfigId());
assertEquals(path, file.getPath()); assertEquals(path, file.getPath());
assertEquals(url, file.getUrl()); assertEquals(url, file.getUrl());
assertEquals("jpg", file.getType()); assertEquals("image/jpg", file.getType());
assertEquals(content.length, file.getSize()); assertEquals(content.length, file.getSize());
} }