All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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