
shz.resource.DefaultResourceManager Maven / Gradle / Ivy
package shz.resource;
import shz.*;
import shz.msg.ServerFailureMsg;
import java.io.*;
import java.util.*;
import java.util.function.Function;
public abstract class DefaultResourceManager implements ResourceManager {
protected final String basePath;
protected DefaultResourceManager(String basePath) {
if (Validator.isBlank(basePath)
|| (System.getProperty("os.name").toLowerCase().startsWith("win")
&& !basePath.contains(":\\")
&& !basePath.contains(":/"))
) this.basePath = FileHelp.formatPath(new File(System.getProperty("user.home"), "resources").getAbsolutePath());
else this.basePath = FileHelp.formatPath(basePath);
}
protected DefaultResourceManager() {
this(null);
}
@Override
public final String save(ResourceDetail resource, byte[] data) {
if (Validator.isEmpty(data)) return null;
ServerFailureMsg.requireNon(Validator.isBlank(resource.getType()), "资源类型为空");
ServerFailureMsg.requireNon(Validator.isBlank(resource.getFilename()), "资源名为空");
String md5 = Coder.md5(data);
ResourceDetail select = selectByMd5(md5);
File file = null;
if (select != null) {
ServerFailureMsg.requireNon(Validator.isBlank(select.getId()), "查找资源未返回id");
file = existFile(select);
}
if (file != null) {
//获取资源偏移
long offset = select.getOffset();
//不存在偏移直接返回
if (offset == 0L) return select.getId();
select.setOffset(copyTo(data, file, offset));
if (select.getOffset() != offset) updateById(select);
checkAfterSave(select);
return select.getId();
}
//资源表不存在该资源
resource.setMd5(md5);
//获取资源目录
File folder = resourceFolder(resource);
ServerFailureMsg.requireNon(
!folder.mkdirs() && !folder.exists(),
"创建资源目录失败:%s", folder.getAbsoluteFile()
);
//获取资源文件
file = resourceFile(folder, resource);
String path = FileHelp.formatPath(file.getAbsolutePath());
//设置资源相对路径
resource.setPath(path.substring(basePath.length()));
//设置资源大小
resource.setSize(data.length);
long offset = 0L;
//资源文件存在,在上传成功但保存资源表失败的情况
if (file.exists()) offset = file.length();
//设置资源偏移
resource.setOffset(offset == data.length ? 0L : copyTo(data, file, offset));
//保存资源信息
String id = insert(resource);
checkAfterSave(resource);
return id;
}
/**
* 资源目录
*/
private File resourceFolder(ResourceDetail resource) {
return new File(basePath, resource.getType());
}
/**
* 资源文件
*/
private File resourceFile(File folder, ResourceDetail resource) {
String filename = resource.getFilename();
int dot = filename.lastIndexOf('.');
if (dot != -1) filename = resource.getMd5() + filename.substring(dot);
else filename = resource.getMd5();
return new File(folder, filename);
}
/**
* 检查文件是否存在
*/
private File existFile(ResourceDetail resource) {
//获取资源目录
File folder = resourceFolder(resource);
File file = null;
if (folder.exists() && folder.isDirectory()) {
file = resourceFile(folder, resource);
if (!file.exists() || !file.isFile()) file = null;
}
//资源已删除,删除资源表中对应的数据
if (file == null) deleteById(resource.getId());
return file;
}
/**
* 拷贝数据到指定文件
*/
private long copyTo(byte[] data, File file, long offset) {
BufferedOutputStream bos;
try {
bos = new BufferedOutputStream(new FileOutputStream(file, offset > 0L), 1024);
} catch (FileNotFoundException e) {
throw PRException.of(e);
}
return IOHelp.read(new ByteArrayInputStream(data), offset, 0L, bos, IOHelp.DEFAULT_DATA_SIZE, null, null);
}
private void checkAfterSave(ResourceDetail resource) {
ServerFailureMsg.requireNon(
resource.getOffset() == -1L || resource.getOffset() > 0L,
"上传失败,请重新上传"
);
}
@Override
public final ResourceDetail getDetailById(String id) {
ResourceDetail resource = selectById(id);
return resource == null || existFile(resource) == null ? null : resource;
}
@Override
public final File getFileById(String id) {
ResourceDetail resource = selectById(id);
return resource == null ? null : existFile(resource);
}
@Override
public final void deleteByIds(Collection ids) {
Collection details = selectByIds(ToSet.explicitCollect(ids.stream().filter(Validator::nonBlank), ids.size()));
if (Validator.isEmpty(details)) return;
Set existIds = ToSet.explicitCollect(details.stream().map(ResourceDetail::getId), details.size());
batchDeleteById(existIds);
idFile(details).values().stream().filter(Objects::nonNull).forEach(File::delete);
}
/**
* id-资源文件
*/
private Map idFile(Collection details) {
if (Validator.isEmpty(details)) return Collections.emptyMap();
File baseFolder = new File(basePath);
if (!baseFolder.exists() || !baseFolder.isDirectory()) return Collections.emptyMap();
Map typeExistMap = new HashMap<>();
Map typeFolderMap = new HashMap<>();
return ToMap.explicitCollect(
details.stream().filter(detail -> Validator.nonBlank(detail.getId())),
ResourceDetail::getId,
detail -> {
String type = detail.getType();
Boolean typeExist = typeExistMap.get(type);
File folder;
if (typeExist == null) {
folder = new File(basePath, type);
typeExist = folder.exists() && folder.isDirectory();
typeExistMap.put(type, typeExist);
typeFolderMap.put(type, folder);
}
if (!typeExist) return null;
else folder = typeFolderMap.get(type);
File file = resourceFile(folder, detail);
return !file.exists() || !file.isFile() ? null : file;
},
details.size()
);
}
@Override
public final void clearTable(Collection details) {
Map idFileMap = idFile(details);
batchDeleteById(ToSet.collect(details.stream().map(ResourceDetail::getId).filter(Validator::nonBlank).filter(id -> idFileMap.get(id) == null)));
}
@Override
public final void clearFolder(Collection types) {
Set allTypes = ToSet.explicitCollect(types.stream().filter(Validator::nonBlank), types.size());
if (allTypes.isEmpty()) return;
File baseFolder = new File(basePath);
if (!baseFolder.exists() || !baseFolder.isDirectory()) return;
Collection details = selectByTypes(allTypes);
Set existTypes;
if (Validator.isEmpty(details)) existTypes = Collections.emptySet();
else existTypes = ToSet.explicitCollect(details.stream().map(ResourceDetail::getType), allTypes.size());
if (!existTypes.isEmpty()) allTypes.removeAll(existTypes);
if (!allTypes.isEmpty()) deleteTypes(allTypes, type -> File::isFile);
if (existTypes.isEmpty()) return;
//资源表存在的路径
Map> existPaths = ToMap.get(existTypes.size()).build();
Map idTypeMap = ToMap.explicitCollect(
details.stream(),
ResourceDetail::getId,
ResourceDetail::getType,
details.size()
);
idFile(details).forEach((id, file) -> {
if (file == null) return;
String type = idTypeMap.get(id);
if (type == null) return;
Set set = existPaths.computeIfAbsent(type, k -> new HashSet<>());
set.add(file.getAbsolutePath());
});
deleteTypes(existTypes, type -> {
Set paths = existPaths.get(type);
if (Validator.isEmpty(paths)) return File::isFile;
return f -> f.isFile() && !paths.contains(f.getAbsolutePath());
});
}
/**
* 删除指定类型的文件
*/
private void deleteTypes(Set types, Function func) {
Map typeFolderMap = new HashMap<>();
types.forEach(type -> {
File folder = typeFolderMap.computeIfAbsent(type, k -> new File(basePath, k));
if (!folder.isDirectory()) return;
File[] files = folder.listFiles(func.apply(type));
if (Validator.nonEmpty(files)) for (File file : files) file.delete();
});
}
protected abstract ResourceDetail selectByMd5(String md5);
protected abstract void deleteById(String id);
protected abstract void updateById(ResourceDetail resource);
protected abstract String insert(ResourceDetail resource);
protected abstract ResourceDetail selectById(String id);
protected abstract Collection selectByIds(Set ids);
protected abstract void batchDeleteById(Set ids);
protected abstract Collection selectByTypes(Set types);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy