DownloadServiceImpl.java 22 KB

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