|
|
@@ -9,6 +9,7 @@ import org.springframework.stereotype.Component;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
import org.springframework.web.multipart.MultipartRequest;
|
|
|
import shop.alien.entity.store.vo.StoreImgVo;
|
|
|
+import shop.alien.store.entity.VideoUploadResult;
|
|
|
import shop.alien.store.service.StoreImgService;
|
|
|
import shop.alien.util.ali.AliOSSUtil;
|
|
|
import shop.alien.util.common.RandomCreateUtil;
|
|
|
@@ -18,12 +19,14 @@ import shop.alien.util.file.FileUtil;
|
|
|
import java.io.File;
|
|
|
import java.io.FileInputStream;
|
|
|
import java.io.FileOutputStream;
|
|
|
+import java.io.InputStream;
|
|
|
import java.nio.file.Files;
|
|
|
import java.nio.file.Path;
|
|
|
import java.nio.file.Paths;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
import java.util.stream.Collectors;
|
|
|
+import java.util.stream.Stream;
|
|
|
import shop.alien.store.config.BaseRedisService;
|
|
|
import shop.alien.util.md5.FileMd5Util;
|
|
|
import shop.alien.store.util.ai.AiContentModerationUtil;
|
|
|
@@ -133,89 +136,302 @@ public class FileUploadUtil {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 上传多个文件
|
|
|
+ * 高性能视频上传(快速上传,截图异步处理)
|
|
|
+ *
|
|
|
+ * 性能优化点:
|
|
|
+ * 1. 【关键优化】不需要截图时:直接使用 InputStream 流式上传,避免先保存到本地(减少一次IO操作)
|
|
|
+ * 2. 需要截图时:先保存文件,然后从文件上传(因为需要文件用于截图)
|
|
|
+ * 3. 截图异步处理,不阻塞主流程
|
|
|
+ * 4. 立即返回视频URL,提升响应速度
|
|
|
+ *
|
|
|
+ * @param multipartFile 视频文件
|
|
|
+ * @param needScreenshot 是否需要截图(默认false,截图会异步处理)
|
|
|
+ * @return 视频上传结果
|
|
|
+ */
|
|
|
+ public VideoUploadResult uploadVideoFast(MultipartFile multipartFile, Boolean needScreenshot) {
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ File tempVideoFile = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 参数验证
|
|
|
+ if (multipartFile == null || multipartFile.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("视频文件不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
|
|
|
+ String fileType = fileNameAndType.get("type").toLowerCase();
|
|
|
+
|
|
|
+ // 验证是否为视频文件
|
|
|
+ if (!videoFileType.contains(fileType)) {
|
|
|
+ throw new IllegalArgumentException("文件不是视频格式,支持的格式: " + videoFileType);
|
|
|
+ }
|
|
|
+
|
|
|
+ String prefix = "video/";
|
|
|
+ String videoFileName = fileNameAndType.get("name").replaceAll(",", "") + RandomCreateUtil.getRandomNum(6);
|
|
|
+ String ossFilePath = prefix + videoFileName + "." + fileNameAndType.get("type");
|
|
|
+
|
|
|
+ log.info("开始高性能视频上传: {}, 文件大小: {} bytes, 需要截图: {}",
|
|
|
+ multipartFile.getOriginalFilename(), multipartFile.getSize(), needScreenshot);
|
|
|
+
|
|
|
+ String uploadDir = this.uploadDir.replace("file:///", "").replace("\\", "/") + "/video/";
|
|
|
+ String videoUrl;
|
|
|
+
|
|
|
+ // 性能优化:根据是否需要截图选择不同的上传策略
|
|
|
+ if (Boolean.TRUE.equals(needScreenshot)) {
|
|
|
+ // 需要截图:先保存文件,然后从文件上传(因为截图需要文件)
|
|
|
+ String tempPath = copyFile(uploadDir, multipartFile);
|
|
|
+ tempVideoFile = new File(tempPath);
|
|
|
+ if (!tempVideoFile.exists() || tempVideoFile.length() == 0) {
|
|
|
+ throw new RuntimeException("临时文件保存失败: " + tempPath);
|
|
|
+ }
|
|
|
+ // 从文件上传
|
|
|
+ videoUrl = aliOSSUtil.uploadFile(tempVideoFile, ossFilePath);
|
|
|
+ } else {
|
|
|
+ // 不需要截图:直接流式上传(最快路径,避免磁盘IO)
|
|
|
+ // 关键优化:直接使用 InputStream,不经过本地文件系统
|
|
|
+ try (InputStream inputStream = multipartFile.getInputStream()) {
|
|
|
+ videoUrl = aliOSSUtil.uploadFile(inputStream, ossFilePath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (videoUrl == null || videoUrl.isEmpty()) {
|
|
|
+ throw new RuntimeException("视频上传到OSS失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ long uploadTime = System.currentTimeMillis() - startTime;
|
|
|
+ log.info("视频上传成功: {}, 耗时: {} ms", videoUrl, uploadTime);
|
|
|
+
|
|
|
+ // 创建返回结果
|
|
|
+ VideoUploadResult result = new VideoUploadResult(
|
|
|
+ videoUrl,
|
|
|
+ multipartFile.getOriginalFilename(),
|
|
|
+ multipartFile.getSize(),
|
|
|
+ uploadTime
|
|
|
+ );
|
|
|
+
|
|
|
+ // 如果需要截图,异步处理(不阻塞返回)
|
|
|
+ if (Boolean.TRUE.equals(needScreenshot) && tempVideoFile != null && tempVideoFile.exists()) {
|
|
|
+ processVideoScreenshotAsync(tempVideoFile, videoFileName, prefix, uploadDir);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("高性能视频上传失败: {}", e.getMessage(), e);
|
|
|
+ // 清理临时文件
|
|
|
+ if (tempVideoFile != null && tempVideoFile.exists()) {
|
|
|
+ tempVideoFile.delete();
|
|
|
+ }
|
|
|
+ throw new RuntimeException("视频上传失败: " + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 异步处理视频截图
|
|
|
+ *
|
|
|
+ * @param tempVideoFile 临时视频文件
|
|
|
+ * @param videoFileName 视频文件名(不含扩展名)
|
|
|
+ * @param prefix OSS路径前缀
|
|
|
+ * @param uploadDir 上传目录
|
|
|
+ */
|
|
|
+ @Async
|
|
|
+ protected void processVideoScreenshotAsync(File tempVideoFile, String videoFileName, String prefix, String uploadDir) {
|
|
|
+ try {
|
|
|
+ log.info("开始异步处理视频截图: {}", tempVideoFile.getName());
|
|
|
+
|
|
|
+ // 获取视频截图
|
|
|
+ String screenshotPath = videoUtils.getImg(uploadDir + tempVideoFile.getName());
|
|
|
+
|
|
|
+ if (screenshotPath != null && !screenshotPath.isEmpty()) {
|
|
|
+ File screenshotFile = new File(screenshotPath);
|
|
|
+ if (screenshotFile.exists()) {
|
|
|
+ try {
|
|
|
+ // 上传截图到OSS
|
|
|
+ Map<String, String> screenshotInfo = FileUtil.getFileNameAndType(screenshotFile);
|
|
|
+ String screenshotUrl = aliOSSUtil.uploadFile(screenshotFile, prefix + videoFileName + "." + screenshotInfo.get("type"));
|
|
|
+
|
|
|
+ log.info("视频截图上传成功: {} -> {}", tempVideoFile.getName(), screenshotUrl);
|
|
|
+ } finally {
|
|
|
+ // 清理临时截图文件
|
|
|
+ screenshotFile.delete();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.warn("视频截图文件不存在: {}", screenshotPath);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.warn("视频截图失败: {}", tempVideoFile.getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("异步处理视频截图失败: {}", tempVideoFile.getName(), e);
|
|
|
+ } finally {
|
|
|
+ // 清理临时视频文件
|
|
|
+ if (tempVideoFile != null && tempVideoFile.exists()) {
|
|
|
+ tempVideoFile.delete();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上传多个文件(高性能优化版)
|
|
|
+ *
|
|
|
+ * 性能优化点:
|
|
|
+ * 1. 并行处理多个文件,充分利用多核CPU
|
|
|
+ * 2. 直接使用 InputStream 流式上传,避免先保存到本地(减少一次IO操作)
|
|
|
+ * 3. 视频截图异步处理,不阻塞主流程
|
|
|
+ * 4. 立即返回文件URL列表,提升响应速度
|
|
|
*
|
|
|
* @param multipartRequest 多文件
|
|
|
* @return List<String>
|
|
|
*/
|
|
|
public List<String> uploadMoreFile(MultipartRequest multipartRequest) {
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
try {
|
|
|
log.info("FileUpload.uploadMoreFile multipartRequest={}", multipartRequest.getFileNames());
|
|
|
- Set<String> fileNameSet = multipartRequest.getMultiFileMap().keySet();
|
|
|
- List<String> filePathList = new ArrayList<>();
|
|
|
- for (String s : fileNameSet) {
|
|
|
- MultipartFile multipartFile = multipartRequest.getFileMap().get(s);
|
|
|
- log.info("FileUpload.uploadMoreFile fileName={}", multipartFile.getOriginalFilename());
|
|
|
- String uploadDir = this.uploadDir.replace("file:///", "").replace("\\", "/");
|
|
|
- String prefix;
|
|
|
- Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
|
|
|
- //区分文件类型
|
|
|
- if (imageFileType.contains(fileNameAndType.get("type").toLowerCase())) {
|
|
|
- uploadDir += "/image";
|
|
|
- prefix = "image/";
|
|
|
- log.info("FileUpload.uploadMoreFile 获取到图片文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
|
|
|
- // 去除文件名中的逗号,避免URL拼接时被错误分割
|
|
|
- String imageFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
- filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + imageFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
|
|
|
- ;
|
|
|
- } else if (videoFileType.contains(fileNameAndType.get("type").toLowerCase())) {
|
|
|
- uploadDir += "/video/";
|
|
|
- prefix = "video/";
|
|
|
- //上传视频文件
|
|
|
- log.info("FileUpload.uploadMoreFile 获取到视频文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
|
|
|
- // 去除文件名中的逗号,避免URL拼接时被错误分割
|
|
|
- String videoFileName = fileNameAndType.get("name").replaceAll(",", "") + RandomCreateUtil.getRandomNum(6);
|
|
|
- String cacheVideoPath = copyFile(uploadDir, multipartFile);
|
|
|
- filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + videoFileName + "." + fileNameAndType.get("type")));
|
|
|
- //缓存视频截图使用
|
|
|
-// File videoFile = new File(cacheVideoPath);
|
|
|
-// //获取视频某帧截图
|
|
|
-// log.info("FileUpload.uploadMoreFile 视频文件复制完毕, 获取第一秒图片 {}", videoFile.getName());
|
|
|
-// String videoPath = videoUtils.getImg(uploadDir + videoFile.getName());
|
|
|
-// log.info("FileUpload.uploadMoreFile 视频文件复制完毕, 图片位置 {}", videoPath);
|
|
|
-// if (!videoPath.isEmpty()) {
|
|
|
-// File videoImgFile = new File(videoPath);
|
|
|
-// Map<String, String> videoImg = FileUtil.getFileNameAndType(videoImgFile);
|
|
|
-// filePathList.add(aliOSSUtil.uploadFile(videoImgFile, prefix + videoFileName + "." + videoImg.get("type")));
|
|
|
-// videoImgFile.delete();
|
|
|
-// videoFile.delete();
|
|
|
-// } else {
|
|
|
-// throw new RuntimeException("视频截图失败");
|
|
|
-// }
|
|
|
- } else if (voiceFileType.contains(fileNameAndType.get("type").toLowerCase())) {
|
|
|
- uploadDir += "/voice";
|
|
|
- prefix = "voice/";
|
|
|
- log.info("FileUpload.uploadMoreFile 获取到语音文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
|
|
|
- // 去除文件名中的逗号,避免URL拼接时被错误分割
|
|
|
- String voiceFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
- filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + voiceFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
|
|
|
- } else if (privacyFileType.contains(fileNameAndType.get("type").toLowerCase())) {
|
|
|
- uploadDir += "/privacy/";
|
|
|
- prefix = "privacy/";
|
|
|
- log.info("FileUpload.uploadMoreFile 获取到隐私文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
|
|
|
- // 去除文件名中的逗号,避免URL拼接时被错误分割
|
|
|
- String privacyFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
- filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + privacyFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
|
|
|
- } else if (pdfFileType.contains(fileNameAndType.get("type").toLowerCase())) {
|
|
|
- uploadDir += "/pdf";
|
|
|
- prefix = "pdf/";
|
|
|
- log.info("FileUpload.uploadMoreFile 获取到PDF文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
|
|
|
- // 去除文件名中的逗号,避免URL拼接时被错误分割
|
|
|
- String pdfFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
- filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + pdfFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
|
|
|
- } else if (ohterFileType.contains(fileNameAndType.get("type").toLowerCase())) {
|
|
|
- uploadDir += "/other/";
|
|
|
- prefix = "other/";
|
|
|
- log.info("FileUpload.uploadMoreFile 获取到其他文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
|
|
|
- // 去除文件名中的逗号,避免URL拼接时被错误分割
|
|
|
- String otherFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
- filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + otherFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
|
|
|
+ Collection<MultipartFile> files = multipartRequest.getFileMap().values();
|
|
|
+
|
|
|
+ if (files.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ String baseUploadDir = this.uploadDir.replace("file:///", "").replace("\\", "/");
|
|
|
+
|
|
|
+ // 如果只有一个文件,直接处理(避免线程池开销)
|
|
|
+ if (files.size() == 1) {
|
|
|
+ MultipartFile file = files.iterator().next();
|
|
|
+ return processSingleFileForUploadMore(file, baseUploadDir);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 多个文件使用并行流处理
|
|
|
+ List<String> filePathList = files.parallelStream()
|
|
|
+ .flatMap(file -> {
|
|
|
+ try {
|
|
|
+ return processSingleFileForUploadMore(file, baseUploadDir).stream();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理文件失败: {}", file.getOriginalFilename(), e);
|
|
|
+ return Stream.empty();
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ long totalTime = System.currentTimeMillis() - startTime;
|
|
|
+ log.info("批量文件上传完成,文件数: {}, 总耗时: {} ms", files.size(), totalTime);
|
|
|
+
|
|
|
+ return filePathList;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("FileUpload.uploadMoreFile ERROR Msg={}", e.getMessage(), e);
|
|
|
+ throw new RuntimeException("批量文件上传失败: " + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理单个文件(用于 uploadMoreFile)
|
|
|
+ *
|
|
|
+ * @param multipartFile 文件
|
|
|
+ * @param baseUploadDir 基础上传目录
|
|
|
+ * @return 文件URL列表(视频会包含视频URL和截图URL)
|
|
|
+ */
|
|
|
+ private List<String> processSingleFileForUploadMore(MultipartFile multipartFile, String baseUploadDir) {
|
|
|
+ List<String> filePathList = new ArrayList<>();
|
|
|
+ File tempVideoFile = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
|
|
|
+ String fileType = fileNameAndType.get("type").toLowerCase();
|
|
|
+ String prefix;
|
|
|
+ String uploadDir = baseUploadDir;
|
|
|
+
|
|
|
+ // 区分文件类型并处理
|
|
|
+ if (imageFileType.contains(fileType)) {
|
|
|
+ prefix = "image/";
|
|
|
+ String imageFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
+ String ossFilePath = prefix + imageFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
|
|
|
+ // 直接流式上传
|
|
|
+ try (InputStream inputStream = multipartFile.getInputStream()) {
|
|
|
+ String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
|
|
|
+ if (url != null) {
|
|
|
+ filePathList.add(url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (videoFileType.contains(fileType)) {
|
|
|
+ prefix = "video/";
|
|
|
+ uploadDir += "/video/";
|
|
|
+ String videoFileName = fileNameAndType.get("name").replaceAll(",", "") + RandomCreateUtil.getRandomNum(6);
|
|
|
+ String ossFilePath = prefix + videoFileName + "." + fileNameAndType.get("type");
|
|
|
+
|
|
|
+ // 视频需要截图,先保存文件
|
|
|
+ String tempPath = copyFile(uploadDir, multipartFile);
|
|
|
+ tempVideoFile = new File(tempPath);
|
|
|
+ if (!tempVideoFile.exists() || tempVideoFile.length() == 0) {
|
|
|
+ throw new RuntimeException("视频文件保存失败: " + tempPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从文件上传视频
|
|
|
+ String videoUrl = aliOSSUtil.uploadFile(tempVideoFile, ossFilePath);
|
|
|
+ if (videoUrl != null) {
|
|
|
+ filePathList.add(videoUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 异步处理视频截图
|
|
|
+ processVideoScreenshotAsync(tempVideoFile, videoFileName, prefix, uploadDir);
|
|
|
+
|
|
|
+ } else if (voiceFileType.contains(fileType)) {
|
|
|
+ prefix = "voice/";
|
|
|
+ String voiceFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
+ String ossFilePath = prefix + voiceFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
|
|
|
+ // 直接流式上传
|
|
|
+ try (InputStream inputStream = multipartFile.getInputStream()) {
|
|
|
+ String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
|
|
|
+ if (url != null) {
|
|
|
+ filePathList.add(url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (privacyFileType.contains(fileType)) {
|
|
|
+ prefix = "privacy/";
|
|
|
+ String privacyFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
+ String ossFilePath = prefix + privacyFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
|
|
|
+ // 直接流式上传
|
|
|
+ try (InputStream inputStream = multipartFile.getInputStream()) {
|
|
|
+ String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
|
|
|
+ if (url != null) {
|
|
|
+ filePathList.add(url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (pdfFileType.contains(fileType)) {
|
|
|
+ prefix = "pdf/";
|
|
|
+ String pdfFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
+ String ossFilePath = prefix + pdfFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
|
|
|
+ // 直接流式上传
|
|
|
+ try (InputStream inputStream = multipartFile.getInputStream()) {
|
|
|
+ String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
|
|
|
+ if (url != null) {
|
|
|
+ filePathList.add(url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (ohterFileType.contains(fileType)) {
|
|
|
+ prefix = "other/";
|
|
|
+ String otherFileName = fileNameAndType.get("name").replaceAll(",", "");
|
|
|
+ String ossFilePath = prefix + otherFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
|
|
|
+ // 直接流式上传
|
|
|
+ try (InputStream inputStream = multipartFile.getInputStream()) {
|
|
|
+ String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
|
|
|
+ if (url != null) {
|
|
|
+ filePathList.add(url);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
return filePathList;
|
|
|
+
|
|
|
} catch (Exception e) {
|
|
|
- log.error("FileUpload.uploadMoreFile ERROR Msg={}", e.getMessage());
|
|
|
- throw new RuntimeException(e);
|
|
|
+ log.error("处理文件失败: {}", multipartFile.getOriginalFilename(), e);
|
|
|
+ throw new RuntimeException("处理文件失败: " + multipartFile.getOriginalFilename(), e);
|
|
|
+ } finally {
|
|
|
+ // 注意:视频文件的临时文件在异步截图处理完成后才会删除,这里不删除
|
|
|
}
|
|
|
}
|
|
|
|