package com.yanzuoguang.cloud.file; import com.yanzuoguang.cloud.CloudConfig; import com.yanzuoguang.cloud.helper.HttpFileHelper; import com.yanzuoguang.cloud.vo.YzgFileUploadReqVo; import com.yanzuoguang.util.MediaHelper; import com.yanzuoguang.util.MediaParameter; import com.yanzuoguang.util.YzgError; import com.yanzuoguang.util.cache.MemoryCache; import com.yanzuoguang.util.exception.CodeException; import com.yanzuoguang.util.helper.FileHelper; import com.yanzuoguang.util.helper.StringHelper; import com.yanzuoguang.util.vo.file.*; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.*; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Date; import java.util.List; /** * 文件上传服务实现 * * @author 颜佐光 */ @Component public class YzgFileServiceImpl implements YzgFileService, ApplicationContextAware { private CloudConfig cloudConfig; private YzgFileConfig fileConfig; private YzgFileProcedure procedure; private final MemoryCache<Boolean> cacheRemoveTempFolder = new MemoryCache<>(10 * 60); /** * Set the ApplicationContext that this object runs in. * Normally this call will be used to initialize the object. * <p>Invoked after population of normal bean properties but before an init callback such * as {@link InitializingBean#afterPropertiesSet()} * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader}, * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and * {@link MessageSourceAware}, if applicable. * * @param applicationContext the ApplicationContext object to be used by this object * @throws ApplicationContextException in case of context initialization errors * @throws BeansException if thrown by application context methods * @see BeanInitializationException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { cloudConfig = applicationContext.getBean(CloudConfig.class); fileConfig = applicationContext.getBean(YzgFileConfig.class); procedure = applicationContext.getBean(YzgFileProcedure.class); } /** * 压缩文件 * * @param req 文件上传请求参数 * @return */ @Override public YzgFileUploadResVo upload(YzgFileUploadReqVo req) { YzgFileUploadResVo res = new YzgFileUploadResVo(); if (req.getFile() == null || req.getFile().length == 0) { throw YzgError.getRuntimeException("062"); } String folder = checkFolder(req.getFolder()); for (MultipartFile uploadFile : req.getFile()) { // 获取上传源文件名和扩展名 String originalFilename = uploadFile.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); // 保存的文件名 String saveFileName = StringHelper.getNewID() + ext; // 获取临时文件路径 String tempFolder = cloudConfig.getTempFolder(folder, new Date()); String serverFolder = String.format("%s/%s", cloudConfig.getServerUrl(), tempFolder); // 创建服务器路径 FileHelper.createDirectory(serverFolder); // 删除目录 procedure.removeTempFolder(serverFolder); // 设置保存文件的路径 String serverFile = String.format("%s/%s", serverFolder, saveFileName); File file = new File(serverFile); try { uploadFile.transferTo(file); } catch (Exception ex) { throw YzgError.getRuntimeException(ex, "063", file.getName()); } // 视频上传结果 YzgFileUploadItemResVo item = new YzgFileUploadItemResVo(); item.setLocalFile(originalFilename); fileConfig.init(item, serverFile); res.getList().add(item); if (item.getType() == FileHelper.FILE_TYPE_VIDEO) { // 转换后的文件路径 String zipTo = serverFile + ".jpg"; // 生成转换参数 YzgFileConvertVideoFirstReqVo firstReq = new YzgFileConvertVideoFirstReqVo(); firstReq.setFrom(serverFile); firstReq.setTo(zipTo); // 转换视频首帧 this.convertVideoFirst(firstReq); // 转换后的结果 YzgFileBaseVo firstRes = fileConfig.init(zipTo); item.setFirst(firstRes); } } return res; } /** * 立即删除临时路径 * * @param tempFolder */ @Override public void removeTempFolder(String tempFolder) { tempFolder = checkFolder(tempFolder); String toFolder = fileConfig.getServerFullPath(tempFolder); // 设置标记 synchronized (cacheRemoveTempFolder) { if (StringHelper.toBoolean(cacheRemoveTempFolder.get(toFolder))) { return; } cacheRemoveTempFolder.put(toFolder, true); } try { // 删除当前临时目录+子文件 FileHelper.deleteFolder(toFolder); // 父文件夹不存在子文件时则删除 File file = new File(toFolder); File parentFile = file.getParentFile(); if (parentFile.listFiles() == null || parentFile.listFiles().length == 0) { removeTempFolder(parentFile.getAbsolutePath()); } } finally { // 设置已经删除完成 cacheRemoveTempFolder.remove(toFolder); } } /** * 下载文件 * * @param fromUrl * @param response */ @Override public void down(String fromUrl, HttpServletResponse response) throws IOException { checkFolder(fromUrl); String fromFullUrl = fileConfig.getServerFullPath(fromUrl); // 父文件夹不存在子文件时则删除 File file = new File(fromFullUrl); if (!file.exists()) { throw YzgError.getRuntimeException("064", file.getName()); } if (file.isDirectory()) { throw YzgError.getRuntimeException("065", file.getName()); } HttpFileHelper.localToDown(fromFullUrl, StringHelper.EMPTY, response); } /** * 移动文件或文件夹 * * @param req */ @Override public void moveFile(YzgFileMoveReqVo req) { if (req.getList() == null || req.getList().isEmpty()) { throw YzgError.getException("066"); } // 先检查一遍参数 for (YzgFileMoveItemReqVo item : req.getList()) { checkMove(item); } // 然后再移动 for (YzgFileMoveItemReqVo item : req.getList()) { move(item); } } private void checkMove(YzgFileMoveItemReqVo item) { if (StringHelper.isEmpty(item.getFrom())) { throw YzgError.getRuntimeException("067"); } if (StringHelper.isEmpty(item.getTo())) { throw YzgError.getRuntimeException("068"); } checkFolder(item.getFrom()); checkFolder(item.getTo()); String fullFrom = fileConfig.getServerFullPath(item.getFrom()); String fullTo = fileConfig.getServerFullPath(item.getTo()); File fileFrom = new File(fullFrom); File fileTo = new File(fullTo); if (fileFrom.exists() && fileTo.exists()) { if (fileFrom.isFile() && fileTo.isDirectory()) { throw YzgError.getRuntimeException("069", item.getFrom(), item.getTo()); } } } private void move(YzgFileMoveItemReqVo item) { String fullFrom = fileConfig.getServerFullPath(item.getFrom()); String fullTo = fileConfig.getServerFullPath(item.getTo()); // 路径相等,不移动 if (StringHelper.compare(fullFrom, fullTo)) { return; } File fileFrom = new File(fullFrom); File fileTo = new File(fullTo); // 防止另外一个线程移动中 if (fileFrom.exists() && fileTo.exists()) { if (fileFrom.isFile() && fileTo.isDirectory()) { throw YzgError.getRuntimeException("069", item.getFrom(), item.getTo()); } } else if (!fileFrom.exists()) { return; } // 创建父文件夹 File parentFile = fileTo.getParentFile(); FileHelper.createDirectory(parentFile); try { Files.move(Paths.get(fullFrom), Paths.get(fullTo), StandardCopyOption.REPLACE_EXISTING); } catch (Exception ex) { throw YzgError.getRuntimeException("070", item.getFrom(), item.getTo(), ex.getMessage()); } } /** * 获取文件信息 * * @param fromUrl * @return */ @Override public YzgFileVideoImageInfoVo getInfo(String fromUrl) { YzgFileVideoImageInfoVo info = new YzgFileVideoImageInfoVo(); fileConfig.init(info, fromUrl); String serverFullPath = fileConfig.getServerFullPath(fromUrl); try { switch (info.getType()) { case FileHelper.FILE_TYPE_AUDIO: { MediaParameter parameter = MediaHelper.getGrabberParameter(serverFullPath); info.setBitrate(parameter.getAudioBitRate()); break; } case FileHelper.FILE_TYPE_VIDEO: { MediaParameter parameter = MediaHelper.getGrabberParameter(serverFullPath); info.setBitrate(parameter.getVideoBitRate()); info.setWidth(parameter.getVideoWidth()); info.setHeight(parameter.getVideoHeight()); break; } case FileHelper.FILE_TYPE_IMAGE: { BufferedImage sourceImg = ImageIO.read(new FileInputStream(serverFullPath)); info.setWidth(sourceImg.getWidth()); info.setHeight(sourceImg.getHeight()); break; } case FileHelper.FILE_TYPE_NONE: default: { break; } } } catch (IOException e) { throw new RuntimeException(e); } return info; } /** * 移动文件或文件夹 * * @param req */ @Override public void convertImage(YzgFileVideoImageReqVo req) { this.convert(req, FileHelper.FILE_TYPE_IMAGE, "图片", new YzgConvert() { @Override public void run(YzgFileVideoImageItemReqVo item, String from, String toFileTemp) throws IOException { // 压缩视频到临时文件 MediaHelper.compressPic(from, toFileTemp, item.getQuote(), item.getSize()); } }); } /** * 转换视频第一帧 * * @param req */ @Override public void convertVideoFirst(YzgFileConvertVideoFirstReqVo req) { checkFolder(req.getFrom()); checkFolder(req.getTo()); String fullFrom = fileConfig.getServerFullPath(req.getFrom()); YzgFileBaseVo fromInfo = fileConfig.init(fullFrom); if (StringHelper.isEmpty(fromInfo.getMime())) { throw YzgError.getRuntimeException("071"); } if (fromInfo.getType() != FileHelper.FILE_TYPE_VIDEO) { throw YzgError.getRuntimeException("072"); } try { String fullTo = fileConfig.getServerFullPath(req.getTo()); File fileTo = new File(fullTo); if (fileTo.exists()) { fileTo.delete(); } // 获取第一帧做为缩略图 MediaHelper.getVideoFirstImage(fullFrom, fullTo); } catch (CodeException e) { throw YzgError.getRuntimeException(e, "073"); } } /** * 移动文件或文件夹 * * @param req */ @Override public void convertVideo(YzgFileVideoImageReqVo req) { this.convert(req, FileHelper.FILE_TYPE_VIDEO, "视频", new YzgConvert() { @Override public void run(YzgFileVideoImageItemReqVo item, String from, String toFileTemp) throws IOException { // 压缩视频的参数 MediaParameter parameter = new MediaParameter(item.getSize(), item.getQuote()); // 设置最小宽度,指的是宽度和高度中的大值,宽度>高度 parameter.setMinVideoWidth(StringHelper.getFirst(item.getWidth(), parameter.getMinVideoWidth())); // 设置最小高度,指的是宽度和高度中的小值,宽度>高度 parameter.setMinVideoHeight(StringHelper.getFirst(item.getHeight(), parameter.getMinVideoHeight())); // 设置视频最小比特率 parameter.setMinVideoBitRate(StringHelper.getFirst(item.getBitrate(), parameter.getMinVideoBitRate())); // 压缩视频到临时文件 MediaHelper.zipVideoMp4(from, toFileTemp, parameter); } }); } /** * 转换文件函数 * * @param req * @param type * @param tag * @param run */ private void convert(YzgFileVideoImageReqVo req, int type, String tag, YzgConvert run) { List<YzgFileVideoImageItemReqVo> toFiles = req.getList(); if (toFiles == null || toFiles.isEmpty()) { return; } // 转换文件基础处理 convertBase(req); // 来源文件路径 String from = fileConfig.getServerFullPath(req.getFrom()); YzgFileBaseVo fromInfo = fileConfig.init(from); if (fromInfo.getType() != type) { throw YzgError.getRuntimeException("074", tag); } // 循环转换所有文件 for (YzgFileVideoImageItemReqVo item : req.getList()) { String to = fileConfig.getServerFullPath(item.getTo()); File file = new File(to); String ext = file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(".")); // 先压缩到临时文件 String toFileTemp = to + StringHelper.getNewID() + ".tmp" + ext; // 将临时文件改成具体的文件 File fileTemp = new File(toFileTemp); try { if (fileTemp.exists()) { fileTemp.delete(); } // 转换文件 run.run(item, from, toFileTemp); // 判断文件是否存在,不存在则更改临时文件名 if (file.exists()) { file.delete(); } // 判断文件是否存在,文件不存在则压缩,文件存在则不压缩 fileTemp.renameTo(file); } catch (IOException ex) { throw new CodeException(ex); } finally { // 删除临时文件 if (fileTemp.exists()) { fileTemp.delete(); } } } } private void convertBase(YzgFileVideoImageReqVo req) { // 先循环检测参数 checkFolder(req.getFrom()); for (YzgFileVideoImageItemReqVo item : req.getList()) { checkFolder(item.getTo()); } // 移动文件,并且确定只移动一次 if (!StringHelper.isEmpty(req.getTo())) { checkMove(req); move(req); req.setFrom(req.getTo()); req.setTo(StringHelper.EMPTY); } } private String checkFolder(String folderFrom) { String folder = StringHelper.getFirst(folderFrom, "temp"); if (folder.contains("..")) { throw YzgError.getRuntimeException("075"); } if (folder.contains("\\u")) { throw YzgError.getRuntimeException("076"); } return folder; } }