DownloadServiceImpl.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. package com.fdkankan.download.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.collection.ConcurrentHashSet;
  4. import cn.hutool.core.date.DateUtil;
  5. import cn.hutool.core.date.TimeInterval;
  6. import cn.hutool.core.io.FileUtil;
  7. import cn.hutool.core.util.StrUtil;
  8. import cn.hutool.core.util.ZipUtil;
  9. import cn.hutool.http.HttpUtil;
  10. import com.alibaba.fastjson.JSON;
  11. import com.alibaba.fastjson.serializer.SerializerFeature;
  12. import com.fdkankan.common.constant.CommonStatus;
  13. import com.fdkankan.common.constant.ErrorCode;
  14. import com.fdkankan.common.constant.SceneKind;
  15. import com.fdkankan.common.exception.BusinessException;
  16. import com.fdkankan.common.util.CmdUtils;
  17. import com.fdkankan.common.util.DateExtUtil;
  18. import com.fdkankan.common.util.FileUtils;
  19. import com.fdkankan.download.constant.CommonConstant;
  20. import com.fdkankan.download.bean.ImageType;
  21. import com.fdkankan.download.bean.ImageTypeDetail;
  22. import com.fdkankan.download.bean.SceneEditControlsBean;
  23. import com.fdkankan.download.bean.SceneViewInfoBean;
  24. import com.fdkankan.download.entity.ScenePlus;
  25. import com.fdkankan.download.entity.ScenePlusExt;
  26. import com.fdkankan.download.service.IDownloadService;
  27. import com.fdkankan.download.service.IScenePlusExtService;
  28. import com.fdkankan.download.service.IScenePlusService;
  29. import com.fdkankan.redis.constant.RedisKey;
  30. import com.fdkankan.redis.util.RedisUtil;
  31. import com.google.common.collect.Lists;
  32. import lombok.extern.slf4j.Slf4j;
  33. import lombok.var;
  34. import org.springframework.beans.factory.annotation.Autowired;
  35. import org.springframework.beans.factory.annotation.Value;
  36. import org.springframework.stereotype.Service;
  37. import java.io.File;
  38. import java.io.FileInputStream;
  39. import java.io.IOException;
  40. import java.io.UnsupportedEncodingException;
  41. import java.net.URLEncoder;
  42. import java.nio.file.Files;
  43. import java.nio.file.Path;
  44. import java.nio.file.Paths;
  45. import java.util.*;
  46. import java.util.concurrent.Callable;
  47. import java.util.concurrent.ExecutorService;
  48. import java.util.concurrent.Executors;
  49. import java.util.concurrent.Future;
  50. import java.util.concurrent.atomic.AtomicInteger;
  51. import java.util.stream.Collectors;
  52. import com.fdkankan.model.constants.UploadFilePath;
  53. import com.fdkankan.fyun.face.FYunFileServiceInterface;
  54. import com.fdkankan.fyun.constant.FYunTypeEnum;
  55. import javax.annotation.Resource;
  56. @Slf4j(topic = "IDownloadService")
  57. @Service
  58. public class DownloadServiceImpl implements IDownloadService {
  59. // private static final List<ImageType> imageTypes = Lists.newArrayList();
  60. // static{
  61. // imageTypes.add(ImageType.builder().name("4k_face").size("4096").ranges(new String[]{"0", "511", "1023", "1535", "2047","2559","3071","3583"}).build());
  62. // imageTypes.add(ImageType.builder().name("2k_face").size("2048").ranges(new String[]{"0", "511", "1023", "1535"}).build());
  63. // imageTypes.add(ImageType.builder().name("1k_face").size("1024").ranges(new String[]{"0", "511"}).build());
  64. // imageTypes.add(ImageType.builder().name("512_face").size("512").ranges(new String[]{"0"}).build());
  65. // }
  66. @Value("${path.v4school}")
  67. private String v4localPath;
  68. @Value("${fyun.type:oss}")
  69. private String uploadType;
  70. @Value("${path.zip-local}")
  71. private String zipLocalFormat;
  72. @Value("${path.source-local}")
  73. private String sourceLocal;
  74. @Value("${fyun.bucket:4dkankan}")
  75. private String bucket;
  76. @Value("${download.config.public-url}")
  77. private String publicUrl;
  78. @Value("${path.v3school:#{null}}")
  79. private String v3localPath;
  80. @Value("${zip.nThreads}")
  81. private int zipNthreads;
  82. @Value("${download.config.resource-url}")
  83. private String resourceUrl;
  84. @Value("${path.zip-root}")
  85. private String wwwroot;
  86. @Value("${download.config.exe-content}")
  87. private String exeContent;
  88. @Value("${download.config.exe-content-v3:#{null}}")
  89. private String exeContentV3;
  90. @Value("${download.config.exe-name}")
  91. private String exeName;
  92. @Value("${path.zip-oss}")
  93. private String zipOssFormat;
  94. // @Value("${cutImgType}")
  95. // private String cutImgType;
  96. @Autowired
  97. private IScenePlusService scenePlusService;
  98. @Autowired
  99. private IScenePlusExtService scenePlusExtService;
  100. @Resource
  101. private FYunFileServiceInterface fYunFileService;
  102. @Autowired
  103. private RedisUtil redisUtil;
  104. @Override
  105. public String downloadHandler(String num) throws Exception {
  106. //zip包路径
  107. String zipPath = null;
  108. try {
  109. TimeInterval timer = DateUtil.timer();
  110. //删除资源目录
  111. FileUtil.del(String.format(this.sourceLocal, num, ""));
  112. ScenePlus scenePlus = scenePlusService.getByNum(num);
  113. if(Objects.isNull(scenePlus))
  114. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  115. ScenePlusExt scenePlusExt = scenePlusExtService.getByPlusId(scenePlus.getId());
  116. String bucket = scenePlusExt.getYunFileBucket();
  117. Set<String> cacheKeys = new ConcurrentHashSet<>();
  118. // Map<String, List<String>> allFiles = this.getAllFiles(num, v4localPath, bucket);
  119. // List<String> ossFilePaths = allFiles.get("ossFilePaths");
  120. // List<String> v4localFilePaths = allFiles.get("localFilePaths");
  121. //key总个数
  122. // int total = ossFilePaths.size() + v4localFilePaths.size();
  123. // AtomicInteger count = new AtomicInteger(0);
  124. //定义压缩包
  125. zipPath = String.format(this.zipLocalFormat, DateExtUtil.format(new Date(), DateExtUtil.dateStyle6), num);
  126. File zipFile = new File(zipPath);
  127. if(!zipFile.getParentFile().exists()){
  128. zipFile.getParentFile().mkdirs();
  129. }
  130. SceneViewInfoBean sceneViewInfo = this.getSceneJson(num);
  131. String resolution = sceneViewInfo.getSceneResolution();
  132. //国际版存在已经切好图的情况,下载时不需要再切图,只需要把文件直接下载下来打包就可以了
  133. if(SceneKind.FACE.code().equals(sceneViewInfo.getSceneKind())){
  134. resolution = "notNeadCut";
  135. }
  136. int imagesVersion = -1;
  137. Integer version = sceneViewInfo.getVersion();
  138. if(Objects.nonNull(version)){
  139. imagesVersion = version;
  140. }
  141. //固定文件写入
  142. this.zipLocalFiles(num, "v4");
  143. log.info("打包固定文件耗时, num:{}, time:{}", num, timer.intervalRestart());
  144. //oss文件写入
  145. this.zipOssFiles(num, resolution, imagesVersion, cacheKeys, "v4");
  146. log.info("打包oss文件耗时, num:{}, time:{}", num, timer.intervalRestart());
  147. //重新写入scene.json(去掉密码访问设置)
  148. this.zipSceneJson(num, sceneViewInfo);
  149. //写入启动命令
  150. this.zipBat(num, "v4");
  151. //打压缩包
  152. // ZipUtil.zip(String.format(this.sourceLocal, num, ""), zipPath);
  153. this.zip(String.format(this.sourceLocal, num, ""), zipPath);
  154. FileUtil.del(String.format(this.sourceLocal, num, ""));
  155. return zipPath;
  156. // TODO: 2024/1/4 生成的压缩包放哪里待定
  157. // String uploadPath = String.format(this.zipOssFormat, num);
  158. // fYunFileService.uploadFileByCommand(bucket, zipPath, uploadPath);
  159. }catch (Exception e){
  160. //更新进度为下载失败
  161. throw e;
  162. }
  163. }
  164. private void zipBat(String num, String version) throws Exception{
  165. String batContent = String.format(this.exeContent, num);
  166. if("v3".equals(version)){
  167. batContent = String.format(this.exeContentV3, num);
  168. }
  169. // this.zipBytes(out, exeName, batContent.getBytes());
  170. FileUtil.writeUtf8String(batContent, String.format(this.sourceLocal, num, exeName));
  171. }
  172. private void zipSceneJson(String num, SceneViewInfoBean sceneViewInfo) throws Exception{
  173. //访问密码置0
  174. SceneEditControlsBean controls = sceneViewInfo.getControls();
  175. controls.setShowLock(CommonStatus.NO.code().intValue());
  176. String sceneJsonPath = String.format(UploadFilePath.DATA_VIEW_PATH, num) + "scene.json";
  177. FileUtil.writeUtf8String(JSON.toJSONString(sceneViewInfo, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullNumberAsZero), String.format(this.sourceLocal, num, this.wwwroot + sceneJsonPath));
  178. }
  179. private void zipOssFiles(String num, String resolution, int imagesVersion, Set<String> cacheKeys, String version) throws Exception{
  180. fYunFileService.downloadFileByCommand(String.format(sourceLocal, num, this.wwwroot), String.format(UploadFilePath.VIEW_PATH, num));
  181. FileUtil.del(String.format(sourceLocal, num, this.wwwroot).concat(String.format(UploadFilePath.IMG_VIEW_PATH, num)).concat("tiles"));
  182. // List<String> strings = fYunFileService.listRemoteFiles(String.format(UploadFilePath.VIEW_PATH, num));
  183. // strings.stream().forEach(str->{
  184. // if(!str.contains("/tiles/4k/") && !str.contains("/tiles/2k/")){
  185. // fYunFileService.downloadFile(str, String.format(sourceLocal, num, this.wwwroot).concat(str));
  186. // }
  187. // });
  188. // fYunFileService.downloadFile(String.format(UploadFilePath.VIEW_PATH, num), String.format(sourceLocal, num, this.wwwroot));
  189. //特殊文件处理
  190. this.reWriteFile(num);
  191. //切图
  192. String filePath = String.format(sourceLocal, num, this.wwwroot + String.format(UploadFilePath.IMG_VIEW_PATH, num) + "tiles/");
  193. this.cutImg(num, filePath, resolution, "tiles");
  194. //切图-场景关联
  195. String panoramaPath = String.format(sourceLocal, num, this.wwwroot + String.format(UploadFilePath.IMG_VIEW_PATH, num) + "panorama/");
  196. if(FileUtil.exist(panoramaPath)){
  197. Path directoryPath = Paths.get(panoramaPath); // 替换为你要查询的目录路径
  198. // 获取目录下的第一层子目录
  199. List<String> panoramaIdList = Files.list(directoryPath).filter(Files::isDirectory).map(file -> file.getFileName().toString()).collect(Collectors.toList());
  200. if(CollUtil.isNotEmpty(panoramaIdList)){
  201. for (String panoramaId : panoramaIdList) {
  202. this.cutImg(num, panoramaPath.concat(panoramaId).concat("/tiles/"), resolution, "panorama/".concat(panoramaId).concat("/tiles"));
  203. }
  204. }
  205. }
  206. }
  207. public static void main(String[] args) {
  208. Path directoryPath = Paths.get("D:\\test"); // 替换为你要查询的目录路径
  209. try {
  210. // 获取目录下的第一层子目录
  211. Files.list(directoryPath)
  212. .filter(Files::isDirectory)
  213. .forEach(file -> {
  214. System.out.println(file.getFileName().toString());
  215. });
  216. } catch (IOException e) {
  217. e.printStackTrace();
  218. }
  219. }
  220. @Override
  221. public void cutImg(String num, String path, String resolution, String subPath) throws Exception {
  222. String imageNumPath = String.format(UploadFilePath.IMG_VIEW_PATH, num);
  223. String tilesOssPath = imageNumPath.concat(subPath + "/" + resolution);
  224. List<String> cubeList = fYunFileService.listRemoteFiles(tilesOssPath);
  225. ExecutorService executorService = Executors.newFixedThreadPool(this.zipNthreads);
  226. List<Future> futureList = new ArrayList<>();
  227. for (String key : cubeList) {
  228. Callable<Boolean> call = new Callable() {
  229. @Override
  230. public Boolean call() throws Exception {
  231. processImage(key, resolution, path);
  232. return true;
  233. }
  234. };
  235. futureList.add(executorService.submit(call));
  236. }
  237. for (Future future : futureList) {
  238. try {
  239. future.get();
  240. }catch (Exception e){
  241. log.error("切图失败", e);
  242. executorService.shutdownNow();
  243. throw new Exception("切图失败");
  244. }
  245. }
  246. }
  247. // private void zipOssFilesHandler(String num, String resolution,
  248. // int imagesVersion, Set<String> cacheKeys,
  249. // String filePath, String imageNumPath, String version) throws Exception{
  250. //
  251. // if(filePath.endsWith("/")){
  252. // return;
  253. // }
  254. //
  255. // //某个目录不需要打包
  256. // if(filePath.contains(imageNumPath + "panorama/panorama_edit/"))
  257. // return;
  258. //
  259. // //切图
  260. // if(!"notNeadCut".equals(resolution)){
  261. // if((filePath.contains(imageNumPath + "panorama/") && filePath.contains("tiles/" + resolution))
  262. // || filePath.contains(imageNumPath + "tiles/" + resolution + "/")) {
  263. // this.processImage(num, filePath, resolution, imagesVersion, cacheKeys);
  264. // return;
  265. // }
  266. // }
  267. //
  268. // //其他文件打包
  269. // this.ProcessFiles(num, filePath, this.wwwroot, cacheKeys);
  270. //
  271. // }
  272. // public void ProcessFiles(String num, String key, String prefix, Set<String> cacheKeys) throws Exception{
  273. // if(cacheKeys.contains(key)){
  274. // return;
  275. // }
  276. // if(key.equals(String.format(UploadFilePath.DATA_VIEW_PATH, num) + "scene.json")){
  277. // return;
  278. // }
  279. // cacheKeys.add(key);
  280. // String fileName = key.substring(key.lastIndexOf("/") + 1);
  281. // String url = this.resourceUrl + key.replace(fileName, URLEncoder.encode(fileName, "UTF-8")) + "?t=" + Calendar.getInstance().getTimeInMillis();
  282. // if(key.contains("hot.json") || key.contains("link-scene.json")){
  283. // String content = fYunFileService.getFileContent(key);
  284. // if(StrUtil.isEmpty(content)){
  285. // return;
  286. // }
  287. // content = content.replace(publicUrl, "")
  288. //// .replace(publicUrl+"v3/", "")
  289. // .replace("https://spc.html","spc.html")
  290. // .replace("https://smobile.html", "smobile.html");
  291. //
  292. // FileUtil.writeUtf8String(content, String.format(sourceLocal, num, prefix + key));
  293. // }else{
  294. // try {
  295. // this.downloadFile(url, String.format(sourceLocal, num, prefix + key));
  296. // }catch (Exception e){
  297. // log.info("下载文件报错,path:{}", String.format(sourceLocal, num, prefix + key));
  298. // }
  299. // }
  300. // }
  301. private void reWriteFile(String num){
  302. String filePath = String.format(sourceLocal, num, this.wwwroot + String.format(UploadFilePath.USER_VIEW_PATH, num) + "hot.json");
  303. if(FileUtil.exist(filePath)){
  304. String content = FileUtil.readUtf8String(filePath);
  305. content = content.replace(publicUrl, "")
  306. .replace("https://spc.html","spc.html")
  307. .replace("https://smobile.html", "smobile.html");
  308. FileUtil.writeUtf8String(content, filePath);
  309. }
  310. }
  311. private void processImage(String key, String resolution, String path) throws Exception{
  312. if(key.contains("x-oss-process") || key.endsWith("/")){
  313. return;
  314. }
  315. String fileName = key.substring(key.lastIndexOf("/")+1, key.indexOf("."));//0_skybox0.jpg
  316. String ext = key.substring(key.lastIndexOf("."));
  317. String[] arr = fileName.split("_skybox");
  318. String dir = arr[0]; //0
  319. String num = arr[1]; //0
  320. if(StrUtil.isEmpty(fileName)
  321. || StrUtil.isEmpty(ext)
  322. || (".jpg".equals(ext) && ".png".equals(ext))
  323. || StrUtil.isEmpty(dir)
  324. || StrUtil.isEmpty(num)){
  325. throw new Exception("本地下载图片资源不符合规则,key:" + key);
  326. }
  327. for (ImageType imageType : CommonConstant.imageTypes) {
  328. if(imageType.getName().equals("4k_face") && !"4k".equals(resolution)){
  329. continue;
  330. }
  331. List<ImageTypeDetail> items = Lists.newArrayList();
  332. String[] ranges = imageType.getRanges();
  333. for(int i = 0; i < ranges.length; i++){
  334. String x = ranges[i];
  335. for(int j = 0; j < ranges.length; j++){
  336. String y = ranges[j];
  337. items.add(
  338. ImageTypeDetail.builder()
  339. .i(String.valueOf(i))
  340. .j(String.valueOf(j))
  341. .x(x)
  342. .y(y)
  343. .build()
  344. );
  345. }
  346. }
  347. List<ImageTypeDetail> imageTypeDetails = Collections.synchronizedList(items);
  348. synchronized(imageTypeDetails){
  349. imageTypeDetails.parallelStream().forEach(item->{
  350. String par = "?x-oss-process=image/resize,m_lfit,w_" + imageType.getSize() + "/crop,w_512,h_512,x_" + item.getX() + ",y_" + item.getY();
  351. // if(FYunTypeEnum.AWS.code().equals(uploadType)){
  352. // par += "&imagesVersion="+ imagesVersion;
  353. // }
  354. var url = this.resourceUrl + key;
  355. FYunTypeEnum storageType = FYunTypeEnum.get(uploadType);
  356. switch (storageType){
  357. case OSS:
  358. url += par;
  359. break;
  360. case AWS:
  361. try {
  362. url += URLEncoder.encode(par.replace("/", "@"), "UTF-8");
  363. } catch (UnsupportedEncodingException e) {
  364. throw new RuntimeException(e);
  365. }
  366. break;
  367. }
  368. //key.split("/" + resolution + "/")[0] + "/" +
  369. var fky = dir + "/" + imageType.getName() + num + "_" + item.getI() + "_" + item.getJ() + ext;
  370. this.downloadFile(url, path + fky);
  371. // this.downloadFile(url, path);
  372. });
  373. }
  374. }
  375. }
  376. public void downloadFile(String url, String path){
  377. File file = new File(path);
  378. if(!file.getParentFile().exists()){
  379. file.getParentFile().mkdirs();
  380. }
  381. HttpUtil.downloadFile(url, path);
  382. }
  383. private void zipLocalFiles(String num, String version) throws Exception{
  384. String sourcePath = String.format(this.sourceLocal, num, "");
  385. String localPath = "v4".equals(version) ? this.v4localPath : this.v3localPath;
  386. FileUtil.copyContent(FileUtil.newFile(localPath), FileUtil.newFile(sourcePath), true);
  387. //写入code.txt
  388. FileUtil.writeUtf8String(num, String.format(sourceLocal, num, "code.txt"));
  389. }
  390. private SceneViewInfoBean getSceneJson(String num){
  391. String sceneJsonData = redisUtil.get(String.format(RedisKey.SCENE_JSON, num));
  392. if(StrUtil.isEmpty(sceneJsonData)){
  393. sceneJsonData = fYunFileService.getFileContent(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "scene.json");
  394. }
  395. sceneJsonData = sceneJsonData.replace(this.publicUrl, "");
  396. SceneViewInfoBean sceneInfoVO = JSON.parseObject(sceneJsonData, SceneViewInfoBean.class);
  397. sceneInfoVO.setScenePassword(null);
  398. if(Objects.isNull(sceneInfoVO.getFloorPlanAngle())){
  399. sceneInfoVO.setFloorPlanAngle(0f);
  400. }
  401. if(Objects.isNull(sceneInfoVO.getFloorPlanCompass())){
  402. sceneInfoVO.setFloorPlanCompass(0f);
  403. }
  404. SceneEditControlsBean controls = sceneInfoVO.getControls();
  405. if(Objects.isNull(controls.getShowShare())){
  406. controls.setShowShare(CommonStatus.YES.code().intValue());
  407. }
  408. if(Objects.isNull(controls.getShowCapture())){
  409. controls.setShowCapture(CommonStatus.YES.code().intValue());
  410. }
  411. if(Objects.isNull(controls.getShowBillboardTitle())){
  412. controls.setShowBillboardTitle(CommonStatus.YES.code().intValue());
  413. }
  414. return sceneInfoVO;
  415. }
  416. private Map<String, List<String>> getAllFiles(String num, String v4localPath, String bucket) throws Exception{
  417. //列出oss所有文件路径
  418. List<String> ossFilePaths = new ArrayList<>();
  419. for (String prefix : CommonConstant.prefixArr) {
  420. prefix = String.format(prefix, num);
  421. List<String> keys = fYunFileService.listRemoteFiles(bucket, prefix);
  422. if(CollUtil.isEmpty(keys)){
  423. continue;
  424. }
  425. if(FYunTypeEnum.AWS.code().equals(this.uploadType)){
  426. keys = keys.stream().filter(key->{
  427. if(key.contains("x-oss-process")){
  428. return false;
  429. }
  430. return true;
  431. }).collect(Collectors.toList());
  432. }
  433. ossFilePaths.addAll(keys);
  434. }
  435. //列出v3local所有文件路径
  436. File file = new File(v4localPath);
  437. List<String> localFilePaths = FileUtils.list(file);
  438. HashMap<String, List<String>> map = new HashMap<>();
  439. map.put("ossFilePaths", ossFilePaths);
  440. map.put("localFilePaths", localFilePaths);
  441. return map;
  442. }
  443. private void zip(String sourcePath, String zipPath) throws Exception {
  444. String cmd = "cd " + sourcePath + " && zip -r " + zipPath + " " + "*";
  445. CmdUtils.callLineSh(cmd, 200);
  446. if(!FileUtil.exist(zipPath)){
  447. throw new RuntimeException("打包失败");
  448. }
  449. }
  450. }