FileServiceImpl.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. package jnpf.service.impl;
  2. import cn.hutool.core.util.URLUtil;
  3. import jnpf.base.UserInfo;
  4. import jnpf.config.ConfigValueUtil;
  5. import jnpf.constant.FileTypeConstant;
  6. import jnpf.constant.GlobalConst;
  7. import jnpf.constant.MsgCode;
  8. import jnpf.consts.DeviceType;
  9. import jnpf.entity.FileParameter;
  10. import jnpf.exception.DataException;
  11. import jnpf.model.*;
  12. import jnpf.service.FileService;
  13. import jnpf.util.*;
  14. import lombok.Cleanup;
  15. import lombok.extern.slf4j.Slf4j;
  16. import org.apache.commons.codec.binary.Base64;
  17. import org.apache.commons.io.FileUtils;
  18. import org.dromara.x.file.storage.core.FileInfo;
  19. import org.springframework.beans.factory.annotation.Autowired;
  20. import org.springframework.stereotype.Service;
  21. import org.springframework.web.multipart.MultipartFile;
  22. import java.io.File;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.time.LocalDate;
  27. import java.time.format.DateTimeFormatter;
  28. import java.util.*;
  29. import java.util.stream.Collectors;
  30. @Slf4j
  31. @Service
  32. public class FileServiceImpl implements FileService {
  33. private List<String> whiteImageFolder = Arrays.asList(FileTypeConstant.USERAVATAR.toLowerCase(), FileTypeConstant.BIVISUALPATH.toLowerCase());
  34. @Autowired
  35. private ConfigValueUtil configValueUtil;
  36. // @Autowired
  37. // private YozoUtils yozoUtils;
  38. @Override
  39. public String getPath(String type) {
  40. return FilePathUtil.getFilePath(type);
  41. }
  42. @Override
  43. public String getLocalBasePath() {
  44. return FileUploadUtils.getLocalBasePath();
  45. }
  46. @Override
  47. public UploaderVO uploadFile(MergeChunkDto mergeChunkDto, MultipartFile file) {
  48. String fileType = UpUtil.getFileType(file);
  49. if (StringUtil.isEmpty(fileType)) {
  50. fileType = mergeChunkDto.getFileType();
  51. }
  52. //验证类型
  53. if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), fileType)) {
  54. throw new DataException(MsgCode.FA017.get());
  55. }
  56. PathTypeModel pathTypeModel = new PathTypeModel();
  57. pathTypeModel.setPathType(mergeChunkDto.getPathType());
  58. pathTypeModel.setTimeFormat(mergeChunkDto.getTimeFormat());
  59. pathTypeModel.setSortRule(mergeChunkDto.getSortRule());
  60. pathTypeModel.setFolder(mergeChunkDto.getFolder());
  61. // String orgFileName = file.getOriginalFilename();
  62. // if (OptimizeUtil.fileSize(file.getSize(), 1024000)) {
  63. // return ActionResult.fail("上传失败,文件大小超过1M");
  64. // }
  65. // if ("mail".equals(type)) {
  66. // type = "temporary";
  67. // }
  68. if ("selfPath".equals(pathTypeModel.getPathType())) {
  69. if (StringUtil.isNotEmpty(pathTypeModel.getFolder())) {
  70. String folder = pathTypeModel.getFolder();
  71. folder = folder.replaceAll("\\\\", "/");
  72. // String regex = "^[a-z0-9A-Z\\u4e00-\\u9fa5\\\\\\/]+$";
  73. //文件夹名以字母或数字开头,由字母、数字、下划线和连字符组成,长度不超过100个字符
  74. String regex = "^[a-zA-Z0-9][a-zA-Z0-9_\\-\\\\\\/]{0,99}$";
  75. if (!folder.matches(regex)) {
  76. throw new DataException(MsgCode.FA038.get());
  77. }
  78. }
  79. }
  80. //实际文件名
  81. String fileName = DateUtil.dateNow("yyyyMMdd") + "_" + RandomUtil.uuId() + "." + fileType;
  82. //文件上传路径
  83. // String filePath = FilePathUtil.getFilePath(type.toLowerCase());
  84. String type = mergeChunkDto.getType();
  85. //文件自定义路径相对路径
  86. String relativeFilePath = "";
  87. if (pathTypeModel != null && "selfPath".equals(pathTypeModel.getPathType()) && pathTypeModel.getSortRule() != null) {
  88. // 按路径规则顺序构建生成目录
  89. String sortRule = pathTypeModel.getSortRule();
  90. List<String> rules = null;
  91. if (sortRule.contains("[")) {
  92. rules = JsonUtil.getJsonToList(sortRule, String.class);
  93. } else {
  94. rules = Arrays.asList(pathTypeModel.getSortRule().split(","));
  95. }
  96. for (String rule : rules) {
  97. // 按用户存储
  98. if ("1".equals(rule)) {
  99. UserInfo userInfo = UserProvider.getUser();
  100. relativeFilePath += userInfo.getUserAccount() + "/";
  101. }
  102. // 按照时间格式
  103. else if (StringUtil.isNotEmpty(pathTypeModel.getTimeFormat()) && "2".equals(rule)) {
  104. String timeFormat = pathTypeModel.getTimeFormat();
  105. timeFormat = timeFormat.replaceAll("YYYY", "yyyy");
  106. timeFormat = timeFormat.replaceAll("DD", "dd");
  107. LocalDate currentDate = LocalDate.now();
  108. DateTimeFormatter formatter = DateTimeFormatter.ofPattern(timeFormat);
  109. String currentDateStr = currentDate.format(formatter);
  110. relativeFilePath += currentDateStr;
  111. if (!currentDateStr.endsWith("/")) {
  112. relativeFilePath += "/";
  113. }
  114. }
  115. // 按自定义目录
  116. else if (StringUtil.isNotEmpty(pathTypeModel.getFolder()) && "3".equals(rule)) {
  117. String folder = pathTypeModel.getFolder();
  118. folder = folder.replaceAll("\\\\", "/");
  119. relativeFilePath += folder;
  120. if (!folder.endsWith("/")) {
  121. relativeFilePath += "/";
  122. }
  123. }
  124. }
  125. if (StringUtil.isNotEmpty(relativeFilePath)) {
  126. relativeFilePath = StringUtil.replaceMoreStrToOneStr(relativeFilePath, "/");
  127. if (relativeFilePath.startsWith("/")) {
  128. relativeFilePath = relativeFilePath.substring(1);
  129. }
  130. //filePath += relativeFilePath;
  131. fileName = relativeFilePath.replaceAll("/", ",") + fileName;
  132. }
  133. }
  134. UploaderVO vo = UploaderVO.builder().fileSize(file.getSize()).fileExtension(fileType).build();
  135. // //上传文件
  136. // if ("im".equalsIgnoreCase(type)){
  137. // type = "imfile";
  138. // }
  139. FileInfo fileInfo = FileUploadUtils.uploadFile(new FileParameter(type, fileName).setThumbnail(true), file);
  140. fileName = fileInfo.getFilename();
  141. String thFilename = fileInfo.getThFilename();
  142. if (!StringUtil.isNotEmpty(thFilename)) {
  143. //小图没有压缩直接用原图
  144. thFilename = fileName;
  145. }
  146. //自定义文件实际文件名
  147. if (StringUtil.isNotEmpty(relativeFilePath)) {
  148. fileName = relativeFilePath.replaceAll("/", ",") + fileName;
  149. thFilename = relativeFilePath.replaceAll("/", ",") + thFilename;
  150. }
  151. vo.setName(fileName);
  152. vo.setUrl(UploaderUtil.uploaderImg("/api/file/Image/" + type + "/", fileName));
  153. vo.setThumbUrl(UploaderUtil.uploaderImg("/api/file/Image/" + type + "/", thFilename));
  154. //上传到永中
  155. if ("yozo".equals(configValueUtil.getPreviewType())) {
  156. // try {
  157. // @Cleanup InputStream inputStream = file.getInputStream();
  158. // String s = yozoUtils.uploadFileInPreview(inputStream, orgFileName);
  159. // Map<String, Object> map = JsonUtil.stringToMap(s);
  160. // if ("操作成功".equals(map.get("message"))) {
  161. // Map<String, Object> dataMap = JsonUtil.stringToMap(String.valueOf(map.get("data")));
  162. // String verId = String.valueOf(dataMap.get("fileVersionId"));
  163. // vo.setFileVersionId(verId);
  164. // }
  165. // } catch (Exception e) {
  166. // System.out.println("上传到永中失败");
  167. // e.printStackTrace();
  168. // }
  169. }
  170. return vo;
  171. }
  172. @Override
  173. public ChunkRes checkChunk(Chunk chunk) {
  174. String type = chunk.getExtension();
  175. if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), type)) {
  176. throw new DataException(MsgCode.FA017.get());
  177. }
  178. String identifier = chunk.getIdentifier();
  179. String path = getPath(FileTypeConstant.TEMPORARY);
  180. String filePath = XSSEscape.escapePath(path + identifier);
  181. List<File> chunkFiles = FileUtil.getFile(new File(FileUploadUtils.getLocalBasePath() + filePath));
  182. List<Integer> existsChunk = chunkFiles.stream().filter(f -> {
  183. if (f.getName().endsWith(".tmp")) {
  184. FileUtils.deleteQuietly(f);
  185. return false;
  186. } else {
  187. return f.getName().startsWith(identifier);
  188. }
  189. }).map(f -> Integer.parseInt(f.getName().replace(chunk.getIdentifier().concat("-"), ""))).collect(Collectors.toList());
  190. ChunkRes chunkRes = ChunkRes.builder().merge(chunk.getTotalChunks().equals(existsChunk.size())).chunkNumbers(existsChunk).build();
  191. return chunkRes;
  192. }
  193. @Override
  194. public ChunkRes uploadChunk(Chunk chunk, MultipartFile file) {
  195. String type = chunk.getExtension();
  196. if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), type)) {
  197. throw new DataException(MsgCode.FA017.get());
  198. }
  199. ChunkRes chunkRes = ChunkRes.builder().build();
  200. chunkRes.setMerge(false);
  201. File chunkFile = null;
  202. File chunkTmpFile = null;
  203. try {
  204. String filePath = FileUploadUtils.getLocalBasePath() + getPath(FileTypeConstant.TEMPORARY);
  205. Integer chunkNumber = chunk.getChunkNumber();
  206. String identifier = XSSEscape.escapePath(chunk.getIdentifier());
  207. String chunkTempPath = XSSEscape.escapePath(filePath + identifier);
  208. File path = new File(chunkTempPath);
  209. if (!path.exists()) {
  210. path.mkdirs();
  211. }
  212. String chunkName = XSSEscape.escapePath(identifier.concat("-") + chunkNumber);
  213. String chunkTmpName = XSSEscape.escapePath(chunkName.concat(".tmp"));
  214. chunkFile = new File(chunkTempPath, chunkName);
  215. chunkTmpFile = new File(chunkTempPath, chunkTmpName);
  216. if (chunkFile.exists() && chunkFile.length() == chunk.getCurrentChunkSize()) {
  217. System.out.println("该分块已经上传:" + chunkFile.getName());
  218. } else {
  219. @Cleanup InputStream inputStream = file.getInputStream();
  220. FileUtils.copyInputStreamToFile(inputStream, chunkTmpFile);
  221. chunkTmpFile.renameTo(chunkFile);
  222. }
  223. int existsSize = (int) FileUtil.getFile(new File(chunkTempPath)).stream().filter(f ->
  224. f.getName().startsWith(identifier) && !f.getName().endsWith(".tmp")
  225. ).count();
  226. chunkRes.setMerge(Objects.equals(existsSize, chunk.getTotalChunks()));
  227. } catch (Exception e) {
  228. try {
  229. FileUtils.deleteQuietly(chunkTmpFile);
  230. FileUtils.deleteQuietly(chunkFile);
  231. } catch (Exception ee) {
  232. e.printStackTrace();
  233. }
  234. System.out.println("上传异常:" + e);
  235. throw new DataException(MsgCode.FA033.get());
  236. }
  237. return chunkRes;
  238. }
  239. @Override
  240. public UploaderVO mergeChunk(MergeChunkDto mergeChunkDto) {
  241. String identifier = XSSEscape.escapePath(mergeChunkDto.getIdentifier());
  242. String path = FileUploadUtils.getLocalBasePath() + getPath(FileTypeConstant.TEMPORARY);
  243. String filePath = XSSEscape.escapePath(path + identifier);
  244. String uuid = RandomUtil.uuId();
  245. String partFile = XSSEscape.escapePath(path + uuid + "." + mergeChunkDto.getExtension());
  246. UploaderVO vo = UploaderVO.builder().build();
  247. try {
  248. List<File> mergeFileList = FileUtil.getFile(new File(filePath));
  249. @Cleanup FileOutputStream destTempfos = new FileOutputStream(partFile, true);
  250. for (int i = 0; i < mergeFileList.size(); i++) {
  251. String chunkName = identifier.concat("-") + (i + 1);
  252. File files = new File(filePath, chunkName);
  253. if (files.exists()) {
  254. FileUtils.copyFile(files, destTempfos);
  255. }
  256. }
  257. File partFiles = new File(partFile);
  258. if (partFiles.exists()) {
  259. MultipartFile multipartFile = FileUtil.createFileItem(partFiles);
  260. vo = uploadFile(mergeChunkDto, multipartFile);
  261. FileUtil.deleteTmp(multipartFile);
  262. }
  263. } catch (Exception e) {
  264. log.error("合并分片失败: {}", e.getMessage());
  265. throw new DataException(MsgCode.FA033.get());
  266. } finally {
  267. FileUtils.deleteQuietly(new File(filePath));
  268. FileUtils.deleteQuietly(new File(partFile));
  269. }
  270. return vo;
  271. }
  272. @Override
  273. public void downloadFile(String encryption, String downName) {
  274. String fileNameAll = DesUtil.aesDecode(encryption);
  275. if (!StringUtil.isEmpty(fileNameAll)) {
  276. fileNameAll = fileNameAll.replaceAll("\n", "");
  277. String[] data = fileNameAll.split("#");
  278. String cacheKEY = data.length > 0 ? data[0] : "";
  279. String fileName = XSSEscape.escapePath(data.length > 1 ? data[1] : "");
  280. String type = data.length > 2 ? data[2] : "";
  281. Object ticketObj = TicketUtil.parseTicket(cacheKEY);
  282. //下载文件
  283. // String typePath = FilePathUtil.getFilePath(type.toLowerCase());
  284. // if (fileName.indexOf(",") >= 0) {
  285. // typePath += fileName.substring(0, fileName.lastIndexOf(",") + 1).replaceAll(",", "/");
  286. // fileName = fileName.substring(fileName.lastIndexOf(",") + 1);
  287. // }
  288. fileName = URLUtil.decode(fileName, GlobalConst.DEFAULT_CHARSET);
  289. FileParameter fileParameter = new FileParameter(type, fileName);
  290. //验证缓存
  291. if (ticketObj != null) {
  292. boolean nodelete = false;
  293. //某些手机浏览器下载后会有提示窗口, 会访问两次下载地址
  294. if (UserProvider.getDeviceForAgent().equals(DeviceType.APP) && "".equals(ticketObj)) {
  295. TicketUtil.updateTicket(cacheKEY, "1", 30L);
  296. nodelete = true;
  297. } else {
  298. TicketUtil.deleteTicket(cacheKEY);
  299. }
  300. FileUploadUtils.downloadFile(fileParameter, inputStream -> {
  301. FileDownloadUtil.outFile(inputStream, downName);
  302. });
  303. if (FileTypeConstant.FILEZIPDOWNTEMPPATH.equals(type) && !nodelete) { //删除打包的临时文件,释放存储
  304. FileUploadUtils.deleteFileByPathAndFileName(fileParameter);
  305. }
  306. } else {
  307. if (FileTypeConstant.FILEZIPDOWNTEMPPATH.equals(type)) { //删除打包的临时文件,释放存储
  308. FileUploadUtils.deleteFileByPathAndFileName(fileParameter);
  309. }
  310. throw new DataException(MsgCode.FA039.get());
  311. }
  312. }
  313. }
  314. @Override
  315. public boolean fileExists(String path, String fileName) {
  316. return FileUploadUtils.exists(new FileParameter(path, fileName));
  317. }
  318. @Override
  319. public String previewFile(PreviewParams previewParams) {
  320. //读取允许文件预览类型
  321. String allowPreviewType = configValueUtil.getAllowPreviewFileType();
  322. String[] fileType = allowPreviewType.split(",");
  323. String fileName = XSSEscape.escape(previewParams.getFileName());
  324. //文件预览类型检验
  325. String docType = fileName.substring(fileName.lastIndexOf(".") + 1);
  326. String s = Arrays.asList(fileType).stream().filter(type -> type.equals(docType)).findFirst().orElse(null);
  327. if (StringUtil.isEmpty(s)) {
  328. throw new DataException(MsgCode.FA040.get());
  329. }
  330. //解析文件url 获取类型
  331. String type = null;
  332. String fileNameAll = previewParams.getFileDownloadUrl();
  333. if (!StringUtil.isEmpty(fileNameAll)) {
  334. String[] data = fileNameAll.split("/");
  335. if (data.length > 4) {
  336. type = data[4];
  337. } else {
  338. type = "";
  339. }
  340. }
  341. // type = getPath(FileTypeConstant.ANNEXPIC);
  342. String url;
  343. //文件预览策略
  344. if ("yozo".equals(configValueUtil.getPreviewType())) {
  345. // if (StringUtil.isEmpty(previewParams.getFileVersionId())) {
  346. // throw new DataException(MsgCode.FA041.get());
  347. // }
  348. //
  349. // String fileVersionId = XSSEscape.escape(previewParams.getFileVersionId());
  350. //
  351. // //获取签名
  352. // Map<String, String[]> parameter = new HashMap<String, String[]>();
  353. // parameter.put("appId", new String[]{YozoParams.APP_ID});
  354. // parameter.put("fileVersionId", new String[]{fileVersionId});
  355. // String sign = yozoUtils.generateSign(YozoParams.APP_ID, YozoParams.APP_KEY, parameter).getData();
  356. // url = "http://eic.yozocloud.cn/api/view/file?fileVersionId="
  357. // + fileVersionId
  358. // + "&appId="
  359. // + YozoParams.APP_ID
  360. // + "&sign="
  361. // + sign;
  362. url = "";
  363. } else {
  364. // String[] split = fileNameAll.split("/");
  365. // if (split.length > 5) {
  366. // type = FilePathUtil.getFilePath(type);
  367. // String fName = split[5];
  368. // if (fName.contains(",")) {
  369. // fName = fName.replaceAll(",", "/");
  370. // type += fName.substring(0, fName.lastIndexOf("/") + 1);
  371. // fileName = fName.substring(fName.lastIndexOf("/") + 1);
  372. // }
  373. // }
  374. // url = configValueUtil.getApiDomain() + UploaderUtil.uploaderFile(fileName + "#" + type) + "&name=" + fileName + "&fullfilename=" + fileName;
  375. String downFileName = fileName;
  376. if (downFileName.contains(",")) {
  377. downFileName = downFileName.substring(downFileName.lastIndexOf(",") + 1);
  378. }
  379. url = configValueUtil.getApiDomain() + "/api/file/Image/" + type + "/" + fileName + "?fullfilename=" + downFileName + "&s=" + UserProvider.getUser().getSecurityKey();// + "&name=" + fileName + "&fullfilename=" + fileName;
  380. //encode编码
  381. String fileUrl = Base64.encodeBase64String(url.getBytes());
  382. url = configValueUtil.getKkFileUrl() + "onlinePreview?url=" + fileUrl;
  383. }
  384. return url;
  385. }
  386. @Override
  387. public void flushFile(String type, String fileName, String securityKey, boolean redirect) {
  388. //目录校验, 开放UserAvatar、BiVisualPath
  389. if (!whiteImageFolder.contains(type.toLowerCase())) {
  390. if (StringUtil.isEmpty(securityKey)) {
  391. throw new DataException(MsgCode.FA039.get());
  392. }
  393. String ticket = DesUtil.aesOrDecode(securityKey, false, true);
  394. String token = TicketUtil.parseTicket(ticket);
  395. if (token == null) {
  396. throw new DataException(MsgCode.FA039.get());
  397. }
  398. UserInfo user = UserProvider.getUser(token);
  399. if (user.getUserId() == null) {
  400. TicketUtil.deleteTicket(ticket);
  401. throw new DataException(MsgCode.FA039.get());
  402. }
  403. }
  404. if (StringUtil.isNotEmpty(securityKey) && redirect) {
  405. String downName = fileName;
  406. if (downName.contains(",")) {
  407. downName = downName.substring(downName.lastIndexOf(",") + 1);
  408. }
  409. String urlEncodeFileName = URLUtil.encode(fileName, GlobalConst.DEFAULT_CHARSET);
  410. String urlEncodeDownName = URLUtil.encode(downName, GlobalConst.DEFAULT_CHARSET);
  411. String url = configValueUtil.getApiDomain() + UploaderUtil.uploaderFile(urlEncodeFileName + "#" + type) + "&name=" + urlEncodeDownName + "&fullfilename=" + urlEncodeDownName;
  412. try {
  413. ServletUtil.getResponse().sendRedirect(url);
  414. } catch (IOException e) {
  415. throw new RuntimeException(e);
  416. }
  417. } else {
  418. FileUploadUtils.downloadFile(new FileParameter(type, fileName), inputStream -> {
  419. FileDownloadUtil.flushFile(inputStream, fileName);
  420. });
  421. }
  422. }
  423. }