package io.loli.sc.server.redirect.file; import java.io.IOException; import java.io.InputStream; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.google.common.collect.Lists; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; /** * <p> * 使用内存文件系统实现的缓存类 * <p> * 使用方法: * * <pre> * 一个最多能缓存20个文件的类 * Cache cache = new SimpleCache(20); * 从缓存中获取该url所对应的文件,如果不存在会自动下载该文件 * Path path = cache.getFile("http://1.loli.io/xxxx"); * 往缓存中以指定的文件名存储一个url文件 * byte[] bytes = cache.saveFile("xxxxxx","http://1.loli.io/xxxx"); * </pre> * * @author choco (loli@linux.com) */ public class SimpleCache implements Cache { private FileSystem fs; private Path root; private static final Logger logger = LogManager.getLogger(SimpleCache.class); /** * 最大文件数 */ private int maxNum = 10; /** * 非默认构造方法 * * @param maxNum 指定的最大文件数 */ public SimpleCache(int maxNum) { this.maxNum = maxNum; } public SimpleCache() { } { init(); } /** * 初始化 */ private void init() { logger.info("初始化缓存"); fs = Jimfs.newFileSystem(Configuration.unix()); root = fs.getPath("/"); try { if (!Files.exists(root)) Files.createDirectory(root); } catch (IOException e) { logger.error(e); throw new RuntimeException(e); } } /** * 将某个path的文件保存到文件缓存中 * * @param path 需要保存的文件path * @return 文件数组 */ @Override public byte[] saveFile(String path) { // 生成文件名 String fileName = path.substring(path.lastIndexOf("/") + 1); Path filePath = root.resolve(fileName); byte[] bytes = null; try (InputStream is = get(path).getValue();) { bytes = inputStreamToByte(is); Files.write(filePath, bytes); logger.info("将" + path + "写入缓存"); refreshCache(); } catch (IOException e) { logger.error(e); } return bytes; } /** * * <p> * 从缓存中获取指定path的文件 并将其保存至缓存中 <br> * 仅当文件缓存中不存在这个文件时 才从远端获取 * * <p> * 文件名是根据path自动取得的<br> * 如下<br> * http://1.loli.io/xxx.png to xxx<br> * http://1.loli.io/xxxx to xxxx * * @param path 需要获取的path * @return 如果存在 则返回该path 如果不存在 必须返回null */ public Path getFile(String path) { // 生成文件名 String fileName = path.substring(path.lastIndexOf("/") + 1); if (fileName.contains(".")) { fileName = fileName.substring(0, fileName.indexOf(".")); } Path p = root.resolve(fileName); // 当该文件不存在时,就调用saveFile方法下载 if (!Files.exists(p)) { logger.info(path + "没有在缓存中找到"); this.saveFile(path); } else { logger.info("从缓存中找到" + path); } return p; } /** * * <p> * 从缓存中获取指定path的文件 并将其保存至缓存中 <br> * 仅当文件缓存中不存在这个文件时 才从远端获取 * * <p> * 文件名是根据path自动取得的<br> * 如下<br> * http://1.loli.io/xxx.png to xxx<br> * http://1.loli.io/xxxx to xxxx * * @param path 需要获取的path * @return 如果存在 则返回byte数组 */ @Override public byte[] getBytes(String path) { // 生成文件名 String fileName = path.substring(path.lastIndexOf("/") + 1); if (fileName.contains(".")) { fileName = fileName.substring(0, fileName.indexOf(".")); } Path p = root.resolve(fileName); byte[] bytes = null; // 当该文件不存在时,就调用saveFile方法下载 if (!Files.exists(p)) { logger.info(path + "没有在缓存中找到"); bytes = this.saveFile(path); } else { logger.info("从缓存中找到" + path); } // 再从文件系统中读取一次,不直接使用上面的数组 try (InputStream is = Files.newInputStream(p);) { bytes = inputStreamToByte(is); } catch (IOException e) { logger.error(e); } return bytes; } /** * 刷新缓存,当缓存的文件大于10个时,将最旧的那个文件删除 * * @throws IOException 当IO出现问题时抛出异常 */ private void refreshCache() throws IOException { // 获取根目录下的所有文件 DirectoryStream<Path> stream = Files.newDirectoryStream(root, entry -> !Files.isDirectory(entry)); List<Path> pathList = Lists.newArrayList(stream); // 对这些文件根据最后修改时间排序 Collections.sort(pathList, (path1, path2) -> { int result = 0; try { result = Files.getLastModifiedTime(path1).compareTo(Files.getLastModifiedTime(path2)); } catch (Exception e) { logger.error(e); result = 0; } return result; }); logger.info("当前缓存文件数:" + pathList.size()); // 当文件数大于10时,删除最早的文件 if (pathList.size() > maxNum) { Path pathToDelete = pathList.get(0); Files.delete(pathToDelete); logger.info("从缓存中删除" + pathToDelete.getName(0)); } } }