优化文件的 type 识别与存储
This commit is contained in:
parent
fec84f2682
commit
34ea1db585
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,对于doc,jar等文件会有误差
|
||||||
*
|
*
|
||||||
* @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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
/**
|
/**
|
||||||
|
@ -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)) {
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user