SceneProServiceImpl.java 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446
  1. package com.fdkankan.scene.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.date.DateField;
  4. import cn.hutool.core.date.DateUtil;
  5. import cn.hutool.core.io.FileUtil;
  6. import cn.hutool.core.util.StrUtil;
  7. import cn.hutool.core.util.XmlUtil;
  8. import cn.hutool.core.util.ZipUtil;
  9. import com.alibaba.fastjson.JSON;
  10. import com.alibaba.fastjson.JSONArray;
  11. import com.alibaba.fastjson.JSONObject;
  12. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  13. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  14. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  15. import com.fdkankan.common.constant.*;
  16. import com.fdkankan.common.exception.BusinessException;
  17. import com.fdkankan.common.util.FileUtils;
  18. import com.fdkankan.model.constants.ConstantFileName;
  19. import com.fdkankan.model.constants.UploadFilePath;
  20. import com.fdkankan.model.utils.ComputerUtil;
  21. import com.fdkankan.model.utils.ConvertUtils;
  22. import com.fdkankan.model.utils.CreateObjUtil;
  23. import com.fdkankan.redis.constant.RedisKey;
  24. import com.fdkankan.redis.constant.RedisLockKey;
  25. import com.fdkankan.redis.util.RedisLockUtil;
  26. import com.fdkankan.redis.util.RedisUtil;
  27. import com.fdkankan.scene.bean.IconBean;
  28. import com.fdkankan.scene.bean.LaserSceneBean;
  29. import com.fdkankan.scene.bean.SceneBean;
  30. import com.fdkankan.scene.bean.TagBean;
  31. import com.fdkankan.scene.config.FdkkLaserConfig;
  32. import com.fdkankan.scene.constant.ConstantFileLocPath;
  33. import com.fdkankan.scene.entity.*;
  34. import com.fdkankan.scene.mapper.ISceneProMapper;
  35. import com.fdkankan.scene.oss.OssUtil;
  36. import com.fdkankan.scene.service.*;
  37. import com.fdkankan.scene.util.CmdBuildUtil;
  38. import com.fdkankan.scene.util.SystemUtil;
  39. import com.fdkankan.scene.vo.*;
  40. import com.fdkankan.web.response.ResultData;
  41. import com.google.common.collect.Lists;
  42. import com.google.common.collect.Sets;
  43. import lombok.extern.slf4j.Slf4j;
  44. import org.springframework.beans.factory.annotation.Autowired;
  45. import org.springframework.beans.factory.annotation.Value;
  46. import org.springframework.stereotype.Service;
  47. import org.springframework.transaction.annotation.Transactional;
  48. import org.springframework.web.multipart.MultipartFile;
  49. import org.w3c.dom.Document;
  50. import org.w3c.dom.Element;
  51. import java.io.File;
  52. import java.io.IOException;
  53. import java.nio.charset.StandardCharsets;
  54. import java.nio.file.FileSystemException;
  55. import java.util.*;
  56. import java.util.Map.Entry;
  57. import java.util.concurrent.CompletableFuture;
  58. import java.util.stream.Collectors;
  59. /**
  60. * <p>
  61. * pro场景表 服务实现类
  62. * </p>
  63. *
  64. * @author dengsixing
  65. * @since 2021-12-23
  66. */
  67. @Slf4j
  68. @Service
  69. public class SceneProServiceImpl extends ServiceImpl<ISceneProMapper, ScenePro> implements ISceneProService {
  70. @Value("${main.url}")
  71. private String mainUrl;
  72. @Value("${scene.url}")
  73. private String sceneUrl;
  74. @Value("${scene.pro.url}")
  75. private String sceneProUrl;
  76. @Value("${scene.pro.new.url}")
  77. private String sceneProNewUrl;
  78. @Value("${ecs.checkFile.maxTimes:5}")
  79. private int maxCheckTimes;
  80. @Value("${ecs.checkFile.waitTime:5000}")
  81. private int waitTime;
  82. @Autowired
  83. private RedisLockUtil redisLockUtil;
  84. @Autowired
  85. private RedisUtil redisUtil;
  86. @Autowired
  87. private ISceneDataDownloadService sceneDataDownloadService;
  88. @Autowired
  89. private ISceneProService sceneProService;
  90. @Autowired
  91. private ISceneEditInfoService sceneEditInfoService;
  92. @Autowired
  93. private ISceneEditControlsService sceneEditControlsService;
  94. @Autowired
  95. private IScenePlusService scenePlusService;
  96. @Autowired
  97. private IScenePlusExtService scenePlusExtService;
  98. @Autowired
  99. private ISceneUploadService sceneUploadService;
  100. @Autowired
  101. private OssUtil ossUtil;
  102. @Autowired
  103. private ILaserService laserService;
  104. @Autowired
  105. private ISceneService sceneService;
  106. @Autowired
  107. private ISceneAsynOperLogService sceneAsynOperLogService;
  108. @Autowired
  109. private FdkkLaserConfig fdkkLaserConfig;
  110. @Transactional
  111. @Override
  112. public ResultData saveInitialPage(FileNameAndDataParamVO param) throws Exception{
  113. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  114. if(scenePlus == null){
  115. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  116. }
  117. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  118. //更新缩略图url
  119. String thumbUrl = String.format(UploadFilePath.USER_EDIT_PATH, param.getNum()) + param.getFileName();
  120. scenePlusExt.setThumb(thumbUrl);
  121. scenePlusExtService.updateById(scenePlusExt);
  122. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  123. if(sceneEditInfo == null){
  124. sceneEditInfo = new SceneEditInfo();
  125. sceneEditInfo.setScenePlusId(scenePlus.getId());
  126. sceneEditInfo.setEntry(param.getData());
  127. sceneEditInfoService.save(sceneEditInfo);
  128. }else{
  129. sceneEditInfoService.update(
  130. new LambdaUpdateWrapper<SceneEditInfo>()
  131. .set(SceneEditInfo::getEntry, param.getData())
  132. .setSql("version = version + 1")
  133. .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
  134. }
  135. //修改laser场景主表的缩略图地址
  136. laserService.editScene(param.getNum(), LaserSceneBean.builder().thumb(thumbUrl).build());
  137. return ResultData.ok();
  138. }
  139. @Override
  140. public ResultData addOrUpdateTag(SaveTagsParamVO param) throws Exception {
  141. // ScenePro scenePro = this.findBySceneNum(param.getNum());
  142. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  143. if (scenePlus == null)
  144. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  145. this.addOrUpdateHotData(param.getNum(), param.getHotDataList());
  146. this.addOrUpdateIcons(param.getNum(), param.getIcons());
  147. //写入本地文件,作为备份
  148. this.writeHotJson(param.getNum());
  149. //保存数据库
  150. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  151. sceneEditInfoService.saveTagsToSceneEditInfo(param.getNum(), sceneEditInfo);
  152. sceneEditInfoService.updateById(sceneEditInfo);
  153. return ResultData.ok();
  154. }
  155. @Override
  156. public ResultData uploadTagImg(String num, MultipartFile file, String sid, Integer size, Integer tileSize) throws Exception {
  157. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  158. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  159. String extName = FileUtil.extName(file.getOriginalFilename());
  160. String fragmentPath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.SCENE_USER_PATH_V4, num).replace("/",File.separator) + "hot_img_fragment" + File.separator;
  161. String workPath = scenePlusExt.getDataSource() + fragmentPath + sid;
  162. String dziPath = workPath + ".dzi";
  163. String outFilesPath = workPath + "_files" + File.separator;
  164. Integer height = null;
  165. Integer width = null;
  166. try {
  167. FileUtil.mkdir(workPath);
  168. //保存到本地
  169. String origFilePath = workPath + File.separator + sid + "." + extName;
  170. file.transferTo(new File(origFilePath));
  171. String ossPath = String.format(UploadFilePath.USER_EDIT_PATH, num) + "hotspot" + File.separator + sid + File.separator;
  172. Map<String, String> uploadMap = new HashMap<>();
  173. //切图
  174. CmdBuildUtil.vips(tileSize, origFilePath, workPath);
  175. if(!ComputerUtil.checkComputeCompleted(dziPath, 5, 200)){
  176. throw new BusinessException(ErrorCode.FAILURE_CODE_5052);
  177. }
  178. List<File> files = FileUtil.loopFiles(outFilesPath);
  179. files.stream().forEach(v->{
  180. uploadMap.put(v.getAbsolutePath(), v.getAbsolutePath().replace(outFilesPath, ossPath));
  181. });
  182. //是否缩放缩略图,判断:读取dzi中的w h,只要有一个超过320,则进行缩放,如果前端传参,则以传参值为准, 缩放默认值320,否则拿原图作为缩略图,
  183. String thumbnailName = "thumbnail." + extName;
  184. Document document = XmlUtil.readXML(new File(dziPath));
  185. Element rootElement = XmlUtil.getRootElement(document);
  186. Element sizeElement = XmlUtil.getElement(rootElement, "Size");
  187. height = Integer.valueOf(sizeElement.getAttribute("Height"));
  188. width = Integer.valueOf(sizeElement.getAttribute("Width"));
  189. Integer maxSize = height > width ? height : width;
  190. if(maxSize > size){
  191. String thumbnailPath = workPath + File.separator + thumbnailName;
  192. CmdBuildUtil.vipsThumbnail(origFilePath, size, thumbnailPath);
  193. if(!ComputerUtil.checkComputeCompleted(thumbnailPath, 5, 200)){
  194. throw new BusinessException(ErrorCode.FAILURE_CODE_5052);
  195. }
  196. uploadMap.put(thumbnailPath, ossPath + thumbnailName);
  197. }else{
  198. uploadMap.put(origFilePath, ossPath + thumbnailName);
  199. }
  200. //上传
  201. uploadMap.entrySet().stream().forEach(entry->{
  202. ossUtil.uploadFile(scenePlusExt.getYunFileBucket(), entry.getValue(), entry.getKey(), false);
  203. });
  204. //上传原图
  205. ossUtil.uploadFile(scenePlusExt.getYunFileBucket(), ossPath + sid + "." + extName, origFilePath, false);
  206. }finally {
  207. FileUtil.del(workPath);
  208. FileUtil.del(dziPath);
  209. FileUtil.del(outFilesPath);
  210. }
  211. Map<String, Object> hw = new HashMap<>();
  212. hw.put("height",height);
  213. hw.put("width",width);
  214. hw.put("sid",sid);
  215. hw.put("tileSize",tileSize);
  216. return ResultData.ok(hw);
  217. }
  218. @Override
  219. public ResultData deleteTagImg(DeleteSidListParamVO param) {
  220. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  221. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  222. param.getSidList().stream().forEach(sid->{
  223. String ossPath = String.format(UploadFilePath.USER_EDIT_PATH, param.getNum()) + "hotspot" + File.separator + sid + File.separator;
  224. if(CollUtil.isNotEmpty(ossUtil.listFiles(scenePlusExt.getYunFileBucket(), ossPath))){
  225. try {
  226. ossUtil.deleteObject(scenePlusExt.getYunFileBucket(), ossPath);
  227. } catch (FileSystemException e) {
  228. throw new RuntimeException(e);
  229. }
  230. }
  231. });
  232. return ResultData.ok();
  233. }
  234. private void addOrUpdateHotData(String num, List<HotParamVO> hotDataList) throws Exception{
  235. Map<String, String> addOrUpdateMap = new HashMap<>();
  236. int i = 0;
  237. for (HotParamVO hotParamVO : hotDataList) {
  238. JSONObject jsonObject = JSON.parseObject(hotParamVO.getHotData());
  239. jsonObject.put("createTime", Calendar.getInstance().getTimeInMillis() + i++);
  240. addOrUpdateMap.put(hotParamVO.getSid(), jsonObject.toJSONString());
  241. }
  242. this.syncHotFromFileToRedis(num);
  243. //处理新增和修改数据
  244. this.addOrUpdateHotDataHandler(num, addOrUpdateMap);
  245. }
  246. private void addOrUpdateIcons(String num, List<String> icons) throws Exception{
  247. if(CollUtil.isEmpty(icons)){
  248. return;
  249. }
  250. this.syncIconsFromFileToRedis(num);
  251. String key = String.format(RedisKey.SCENE_HOT_ICONS, num);
  252. redisUtil.sSet(key, icons.toArray());
  253. }
  254. @Override
  255. public ResultData deleteTag(DeleteHotParamVO param) throws Exception {
  256. // ScenePro scenePro = this.findBySceneNum(param.getNum());
  257. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  258. if (scenePlus == null)
  259. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  260. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  261. String bucket = scenePlusExt.getYunFileBucket();
  262. List<String> deleteSidList = param.getSidList();
  263. //处理删除状态数据
  264. this.deleteHotData(param.getNum(), deleteSidList, bucket);
  265. //删除导览中的热点数据
  266. this.deleteHotDataFromTourJson(param.getNum(), param.getSidList(), bucket);
  267. //写入本地文件,作为备份
  268. this.writeHotJson(param.getNum());
  269. //保存数据库
  270. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  271. sceneEditInfoService.saveTagsToSceneEditInfo(param.getNum(), sceneEditInfo);
  272. sceneEditInfoService.updateById(sceneEditInfo);
  273. return ResultData.ok();
  274. }
  275. private void deleteHotDataFromTourJson(String num, List<String> sidList, String bucket){
  276. String key = String.format(UploadFilePath.USER_EDIT_PATH, num) + "tour.json";
  277. String tourJson = ossUtil.getFileContent(bucket, key);
  278. if(StrUtil.isEmpty(tourJson)){
  279. return;
  280. }
  281. JSONArray jsonArray = JSON.parseArray(tourJson);
  282. if(CollUtil.isEmpty(jsonArray)){
  283. return;
  284. }
  285. jsonArray.stream().forEach(tour->{
  286. JSONObject obj = (JSONObject) tour;
  287. JSONArray itemArra = obj.getJSONArray("list");
  288. itemArra.stream().forEach(item->{
  289. JSONObject itemObj = (JSONObject) item;
  290. String tagId = itemObj.getString("tagId");
  291. if(tagId != null && sidList.contains(tagId)){
  292. itemObj.remove("tagId");
  293. }
  294. });
  295. });
  296. ossUtil.uploadFileBytes(bucket, key, jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8));
  297. }
  298. @Override
  299. public ResultData deleteIcons(DeleteHotIconParamVO param) throws Exception {
  300. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  301. if (scenePlus == null)
  302. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  303. List<String> fileNameList = param.getFileNameList();
  304. this.syncIconsFromFileToRedis(param.getNum());
  305. String key = String.format(RedisKey.SCENE_HOT_ICONS, param.getNum());
  306. redisUtil.setRemove(key, fileNameList.toArray());
  307. //写入本地文件,作为备份
  308. this.writeHotJson(param.getNum());
  309. //删除oss文件
  310. sceneUploadService.delete(
  311. DeleteFileParamVO.builder()
  312. .num(param.getNum())
  313. .fileNames(fileNameList)
  314. .bizType(FileBizType.TAG_ICON.code()).build());
  315. return ResultData.ok();
  316. }
  317. @Override
  318. public ResultData listTags(String num) throws Exception{
  319. //保证热点数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis
  320. this.syncHotFromFileToRedis(num);
  321. //保证icons数据安全性,当redis宕机导致icons数据丢失时,可以从文件中读取,恢复到redis
  322. this.syncIconsFromFileToRedis(num);
  323. JSONObject result = new JSONObject();
  324. //查询缓存是否包含热点数据
  325. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  326. Map<String, String> allTagsMap = redisUtil.hmget(key);
  327. List<JSONObject> tags = Lists.newArrayList();
  328. List<TagBean> tagBeanList = new ArrayList<>();
  329. if(CollUtil.isNotEmpty(allTagsMap)){
  330. allTagsMap.entrySet().stream().forEach(entry -> {
  331. JSONObject jsonObject = JSON.parseObject(entry.getValue());
  332. tagBeanList.add(
  333. TagBean.builder()
  334. .createTime(jsonObject.getLong("createTime"))
  335. .tag(jsonObject).build());
  336. });
  337. //按创建时间倒叙排序
  338. tagBeanList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed());
  339. //移除createTime字段
  340. tags = tagBeanList.stream().map(tagBean -> {
  341. JSONObject tag = tagBean.getTag();
  342. tag.remove("createTime");
  343. return tag;
  344. }).collect(Collectors.toList());
  345. }
  346. result.put("tags", tags);
  347. //查询缓存是否包含icons
  348. key = String.format(RedisKey.SCENE_HOT_ICONS, num);
  349. Set<String> icons = redisUtil.sGet(key);
  350. if(icons == null){
  351. icons = Sets.newHashSet();
  352. }
  353. List<String> iconList = this.sortIcons(tags, icons);
  354. result.put("icons", iconList);
  355. return ResultData.ok(result);
  356. }
  357. private List<String> sortIcons(List<JSONObject> tags, Set<String> icons){
  358. //统计使用频次
  359. List<IconBean> iconBeans = Lists.newArrayList();
  360. for (String icon : icons) {
  361. int count = 0;
  362. for (JSONObject tag : tags) {
  363. String sid = tag.getString("icon");
  364. if(StrUtil.isEmpty(sid) || !icon.equals(sid)){
  365. continue;
  366. }
  367. ++count;
  368. }
  369. iconBeans.add(IconBean.builder().icon(icon).count(count).build());
  370. }
  371. //排序
  372. List<String> iconList = iconBeans.stream().sorted(Comparator.comparing(IconBean::getCount).reversed())
  373. .map(item -> {
  374. return item.getIcon();
  375. }).collect(Collectors.toList());
  376. return iconList;
  377. }
  378. /**
  379. * <p>
  380. 保证热点数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis
  381. * </p>
  382. * @author dengsixing
  383. * @date 2022/3/3
  384. **/
  385. private void syncHotFromFileToRedis(String num) throws Exception{
  386. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  387. boolean exist = redisUtil.hasKey(key);
  388. if(exist){
  389. return;
  390. }
  391. String lockKey = String.format(RedisLockKey.LOCK_HOT_DATA_SYNC, num);
  392. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  393. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  394. if(!lock){
  395. throw new BusinessException(ErrorCode.SYSTEM_BUSY);
  396. }
  397. try{
  398. exist = redisUtil.hasKey(key);
  399. if(exist){
  400. return;
  401. }
  402. String tagsFilePath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.SCENE_USER_PATH_V4, num) + "hot.json";
  403. String tagsData = FileUtils.readUtf8String(tagsFilePath);
  404. if(StrUtil.isEmpty(tagsData)){
  405. return;
  406. }
  407. JSONObject jsonObject = JSON.parseObject(tagsData);
  408. JSONArray tagsArr = jsonObject.getJSONArray("tags");
  409. if(CollUtil.isEmpty(tagsArr)){
  410. return;
  411. }
  412. Map<String, String> map = new HashMap<>();
  413. for (Object o : tagsArr) {
  414. JSONObject jo = (JSONObject)o;
  415. map.put(jo.getString("sid"), jo.toJSONString());
  416. }
  417. redisUtil.hmset(key, map);
  418. }finally {
  419. redisLockUtil.unlockLua(lockKey, lockVal);
  420. }
  421. }
  422. /**
  423. * <p>
  424. 保证icons数据安全性,当redis宕机导致icons数据丢失时,可以从文件中读取,恢复到redis
  425. * </p>
  426. * @author dengsixing
  427. * @date 2022/3/3
  428. **/
  429. private void syncIconsFromFileToRedis(String num) throws Exception{
  430. String key = String.format(RedisKey.SCENE_HOT_ICONS, num);
  431. boolean exist = redisUtil.hasKey(key);
  432. if(exist){
  433. return;
  434. }
  435. String lockKey = String.format(RedisLockKey.LOCK_HOT_ICONS_SYNC, num);
  436. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  437. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  438. if(!lock){
  439. throw new BusinessException(ErrorCode.SYSTEM_BUSY);
  440. }
  441. try{
  442. exist = redisUtil.hasKey(key);
  443. if(exist){
  444. return;
  445. }
  446. String tagsFilePath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.SCENE_USER_PATH_V4, num) + "hot.json";
  447. String tagsData = FileUtils.readUtf8String(tagsFilePath);
  448. if(StrUtil.isEmpty(tagsData)){
  449. return;
  450. }
  451. JSONObject jsonObject = JSON.parseObject(tagsData);
  452. JSONArray iconArr = jsonObject.getJSONArray("icons");
  453. if(CollUtil.isEmpty(iconArr)){
  454. return;
  455. }
  456. redisUtil.sSet(key, iconArr.toJavaList(String.class).toArray());
  457. }finally {
  458. redisLockUtil.unlockLua(lockKey, lockVal);
  459. }
  460. }
  461. /**
  462. * <p>
  463. 热点数据保存
  464. * </p>
  465. * @author dengsixing
  466. * @date 2022/3/3
  467. **/
  468. private void writeHotJson(String num) throws Exception{
  469. String dataKey = String.format(RedisKey.SCENE_HOT_DATA, num);
  470. Map<String, String> tagMap = redisUtil.hmget(dataKey);
  471. List<String> tagList = Lists.newArrayList();
  472. tagMap.entrySet().stream().forEach(entry->{
  473. if(StrUtil.isNotEmpty(entry.getValue())){
  474. tagList.add(entry.getValue());
  475. }
  476. });
  477. JSONObject jsonObject = new JSONObject();
  478. JSONArray tagJsonArr = new JSONArray();
  479. if(CollUtil.isNotEmpty(tagList)){
  480. tagList.stream().forEach(hot->{
  481. tagJsonArr.add(JSONObject.parseObject(hot));
  482. });
  483. }
  484. jsonObject.put("tags", tagJsonArr);
  485. String iconsKey = String.format(RedisKey.SCENE_HOT_ICONS, num);
  486. Set<String> iconList = redisUtil.sGet(iconsKey);
  487. jsonObject.put("icons", iconList);
  488. String hotJsonPath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.SCENE_USER_PATH_V4, num) + "hot.json";
  489. String lockKey = String.format(RedisLockKey.LOCK_HOT_JSON, num);
  490. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  491. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  492. if(!lock){
  493. return;
  494. }
  495. try{
  496. FileUtils.writeFile(hotJsonPath, jsonObject.toJSONString());
  497. }finally {
  498. redisLockUtil.unlockLua(lockKey, lockVal);
  499. }
  500. }
  501. private void addOrUpdateHotDataHandler(String num, Map<String, String> addOrUpdateMap){
  502. if(CollUtil.isEmpty(addOrUpdateMap))
  503. return;
  504. //数据验证,新增、修改状态,hotdata不能为空
  505. Set<String> linkSids = new HashSet<>();
  506. for (String sid : addOrUpdateMap.keySet()) {
  507. String hotData = addOrUpdateMap.get(sid);
  508. if(StrUtil.isEmpty(hotData)){
  509. throw new BusinessException(ErrorCode.FAILURE_CODE_7004);
  510. }
  511. JSONObject jsonObject = JSON.parseObject(hotData);
  512. String type = jsonObject.getString("type");
  513. if("link".equals(type)){
  514. linkSids.add(sid);
  515. }
  516. }
  517. //如果是修改,且由多媒体改成link的方式,将原有的多媒体文件删除
  518. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  519. List<String> updateList = redisUtil.hMultiGet(key, new ArrayList<>(linkSids));
  520. try {
  521. this.deleteHotMediaFile(num, updateList, false);
  522. }catch (Exception e){
  523. log.error("删除多媒体文件失败", e);
  524. }
  525. //批量写入缓存
  526. redisUtil.hmset(key, addOrUpdateMap);
  527. }
  528. private void deleteHotData(String num, List<String> deleteSidList, String bucket) throws Exception {
  529. this.syncHotFromFileToRedis(num);
  530. if(CollUtil.isEmpty(deleteSidList)){
  531. return;
  532. }
  533. //从redis中加载热点数据
  534. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  535. List<String> deletDataList = redisUtil.hMultiGet(key, deleteSidList);
  536. if(CollUtil.isEmpty(deletDataList))
  537. return;
  538. //从redis中移除热点数据
  539. redisUtil.hdel(key, deleteSidList.toArray());
  540. //删除图片音频视频等资源文件
  541. this.deleteHotMediaFile(num, deletDataList, true);
  542. }
  543. private void deleteHotMediaFile(String num, List<String> hotdataList, boolean deleteBgm) throws Exception {
  544. if(CollUtil.isEmpty(hotdataList)){
  545. return;
  546. }
  547. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  548. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  549. //删除图片音频视频等资源文件
  550. List<String> deleteFileList = new ArrayList<>();
  551. List<String> deleteKeys = new ArrayList<>();
  552. for (String data : hotdataList) {
  553. if(StrUtil.isBlank(data)){
  554. continue;
  555. }
  556. JSONObject jsonObject = JSON.parseObject(data);
  557. //删除背景音乐
  558. if(deleteBgm){
  559. JSONObject bgm = jsonObject.getJSONObject("bgm");
  560. if(Objects.nonNull(bgm) && StrUtil.isNotEmpty(bgm.getString("src"))){
  561. String bgmSrc = bgm.getString("src");
  562. deleteFileList.add(bgmSrc);
  563. }
  564. }
  565. String type = jsonObject.getString("type");
  566. if("media".equals(type)){//V4.13.0版本改成这种方式
  567. //删除图片、视频
  568. JSONArray media = jsonObject.getJSONArray("media");
  569. media.stream().forEach(v->{
  570. JSONObject o = (JSONObject) v;
  571. String fileSid = o.getString("sid");
  572. if(o.containsKey("tileSize")){//4.14.0版本,图片类型热点不调用通用上传接口上传,改为切图接口上传,所以这里直接删除整个文件目录
  573. String ossPath = String.format(UploadFilePath.USER_EDIT_PATH, num) + "hotspot/" + fileSid + "/";
  574. deleteKeys.add(ossPath);
  575. }else{
  576. String src = o.getString("src");
  577. if(StrUtil.isNotEmpty(src)){
  578. deleteFileList.add(src);
  579. }
  580. }
  581. });
  582. }
  583. if("image".equals(type) || "audio".equals(type) || "video".equals(type)){
  584. //删除图片、视频
  585. JSONObject media = jsonObject.getJSONObject("media");
  586. JSONArray jsonArray = media.getJSONArray(type);
  587. jsonArray.stream().forEach(v->{
  588. JSONObject o = (JSONObject) v;
  589. String src = o.getString("src");
  590. if(StrUtil.isNotEmpty(src)){
  591. deleteFileList.add(src);
  592. }
  593. });
  594. }
  595. }
  596. if(CollUtil.isNotEmpty(deleteFileList)){
  597. sceneUploadService.delete(
  598. DeleteFileParamVO.builder()
  599. .num(num)
  600. .fileNames(deleteFileList)
  601. .bizType("tag-media").build());
  602. }
  603. if(CollUtil.isNotEmpty(deleteKeys)){
  604. deleteKeys.stream().forEach(key->{
  605. try {
  606. ossUtil.deleteObject(scenePlusExt.getYunFileBucket(), key);
  607. } catch (FileSystemException e) {
  608. throw new RuntimeException(e);
  609. }
  610. });
  611. }
  612. }
  613. @Override
  614. public ResultData saveTagsVisible(SaveTagsVisibleParamVO param) throws Exception {
  615. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  616. if (scenePlus == null ) {
  617. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  618. }
  619. JSONArray visiblePanos = JSONArray.parseArray(param.getData());
  620. //如果redis找不到,就从本地文件中reload
  621. this.syncHotFromFileToRedis(param.getNum());
  622. //从缓存中获取热点数据,如果为空,抛出异常
  623. String key = String.format(RedisKey.SCENE_HOT_DATA, param.getNum());
  624. Map<String, String> map = redisUtil.hmget(key);
  625. if (CollUtil.isEmpty(map)) {
  626. throw new BusinessException(ErrorCode.FAILURE_CODE_7005);
  627. }
  628. List<Entry<String, String>> allTags = map.entrySet().stream().filter(item -> {
  629. if (StrUtil.isBlank(item.getValue())) {
  630. return false;
  631. }
  632. return true;
  633. }).collect(Collectors.toList());
  634. if (CollUtil.isEmpty(allTags)) {
  635. throw new BusinessException(ErrorCode.FAILURE_CODE_7005);
  636. }
  637. allTags.stream().forEach(entry->{
  638. JSONObject hot = JSON.parseObject(entry.getValue());
  639. visiblePanos.stream().forEach(item->{
  640. if (hot.getString("sid").equals(((JSONObject) item).getString("sid"))) {
  641. hot.put("visiblePanos", ((JSONObject) item).getJSONArray("value"));
  642. hot.put("isHidden", ((JSONObject) item).getBoolean("isHidden"));
  643. entry.setValue(hot.toJSONString());
  644. }
  645. });
  646. });
  647. //更新版本号
  648. SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  649. sceneEditInfoService.upgradeVersionById(editInfo.getId());
  650. //放入缓存
  651. Map<String, String> finalMap = new HashMap<>();
  652. allTags.stream().forEach(entry->{
  653. finalMap.put(entry.getKey(), entry.getValue());
  654. });
  655. redisUtil.hmset(key, finalMap);
  656. //写入本地文件,作为备份,以防redis数据丢失
  657. this.writeHotJson(param.getNum());
  658. return ResultData.ok();
  659. }
  660. @Override
  661. public ResultData saveRoam(BaseDataParamVO param) throws Exception {
  662. // ScenePro scenePro = this.findBySceneNum(param.getNum());
  663. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  664. if (scenePlus == null ) {
  665. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  666. }
  667. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  668. String bucket = scenePlusExt.getYunFileBucket();
  669. JSONArray inputData = JSONObject.parseArray(param.getData());
  670. String localDataPath = scenePlusExt.getDataSource() + File.separator + String.format(ConstantFileLocPath.SCENE_DATA_PATH_V4, param.getNum());
  671. File directory = new File(localDataPath);
  672. if (!directory.exists()) {
  673. directory.mkdirs();
  674. }
  675. //如果是云存储,将vision.modeldata下载到本地,如果是本地存储,场景计算完就已经将这个文件拷贝到编辑目录了存在这个文件了,不需要再下载
  676. String viewImagesPath = String.format(UploadFilePath.IMG_VIEW_PATH, param.getNum());
  677. ossUtil.downloadFile(bucket,viewImagesPath + "vision.modeldata", localDataPath + "vision.modeldata");
  678. //检查vision.modeldata本地是否存在,不存在抛出异常
  679. File file = new File(localDataPath + "vision.modeldata");
  680. if(!file.exists()) {
  681. return ResultData.error(ErrorCode.FAILURE_CODE_5012);
  682. }
  683. //将vision.modeldata解压缩至vision.json
  684. ConvertUtils.convertVisionModelDataToTxt(localDataPath + "vision.modeldata", localDataPath + "vision.json");
  685. String str = FileUtils.readFile(localDataPath + "vision.json");
  686. JSONObject json = JSONObject.parseObject(str);
  687. JSONArray panos = json.getJSONArray("sweepLocations");
  688. for (int i = 0; i < panos.size(); ++i) {
  689. JSONObject pano = panos.getJSONObject(i);
  690. for (int j = 0; j < inputData.size(); ++j) {
  691. JSONObject jo = inputData.getJSONObject(j);
  692. String currentPanoId = jo.getString("panoID");
  693. JSONArray visibles = jo.getJSONArray("visibles");
  694. JSONArray visibles3 = jo.getJSONArray("visibles3");
  695. if (pano.getString("uuid").replaceAll("-", "").equals(currentPanoId)) {
  696. pano.put("visibles", visibles);
  697. pano.put("visibles3", visibles3);
  698. }
  699. }
  700. }
  701. FileUtils.deleteFile(localDataPath + "vision.json");
  702. FileUtils.deleteFile(localDataPath + "vision.modeldata");
  703. FileUtils.writeFile(localDataPath + "vision.json", json.toString());
  704. ConvertUtils.convertTxtToVisionModelData(localDataPath + "vision.json", localDataPath + "vision.modeldata");
  705. ossUtil.uploadFile(bucket,viewImagesPath + "vision.modeldata", localDataPath + "vision.modeldata", false);
  706. //更新版本号
  707. SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  708. if(Objects.isNull(editInfo)){
  709. editInfo = new SceneEditInfo();
  710. editInfo.setScenePlusId(scenePlus.getId());
  711. sceneEditInfoService.save(editInfo);
  712. }else{
  713. sceneEditInfoService.upgradeVersionAndImgVersionById(editInfo.getId());
  714. //更新scenejson缓存和oss文件版本号
  715. sceneEditInfoService.upgradeSceneJsonVersion(param.getNum(), editInfo.getVersion() + 1, editInfo.getImgVersion() + 1, bucket);
  716. }
  717. return ResultData.ok();
  718. }
  719. @Override
  720. public void updateUserIdByCameraId(Long userId, Long cameraId) {
  721. this.update(new LambdaUpdateWrapper<ScenePro>()
  722. .eq(ScenePro::getCameraId, cameraId)
  723. .set(ScenePro::getUserId, userId));
  724. }
  725. @Override
  726. public ResultData uploadModel(String num, MultipartFile file) throws Exception{
  727. if(StrUtil.isEmpty(num)){
  728. throw new BusinessException(ServerCode.PARAM_REQUIRED, "num");
  729. }
  730. if(!file.getOriginalFilename().endsWith(".zip")){
  731. throw new BusinessException(ErrorCode.FAILURE_CODE_7015);
  732. }
  733. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  734. if(scenePlus == null){
  735. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  736. }
  737. //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
  738. sceneAsynOperLogService.checkSceneAsynOper(num, null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.MODEL.code());
  739. //清除全景图异步操作记录,防止再次下载的时候请求到旧的压缩包
  740. sceneAsynOperLogService.cleanLog(num, SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.MODEL.code());
  741. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  742. String bucket = scenePlusExt.getYunFileBucket();
  743. String path = sceneService.getDataSource(num, scenePlus.getSceneSource(), scenePlusExt.getDataSource());
  744. //全景图计算根目录
  745. String dataSource = path.substring(0, path.lastIndexOf(File.separator));
  746. if(ModelKind.THREE_D_TILE.code().equals(scenePlusExt.getModelKind())){
  747. this.buildModel43dtiles(num, bucket, dataSource, file);
  748. }else{
  749. this.buildModel4Dam(num, bucket, dataSource, scenePlusExt.getBuildType(), file);
  750. }
  751. return ResultData.ok();
  752. }
  753. /**
  754. * 老算法(dam)上传模型逻辑
  755. * @param num
  756. * @param bucket
  757. * @param dataSource
  758. * @param buildType
  759. * @throws Exception
  760. */
  761. private void buildModel4Dam(String num, String bucket, String dataSource, String buildType, MultipartFile file) throws Exception {
  762. //文件上传的位置可以自定义
  763. String path = dataSource + "_obj2txt";
  764. String zipPath = path + "/zip/";
  765. String filePath = path + "/extras/";
  766. String resultPath = path + "/results/";
  767. //压缩文件处理:解压缩,解压缩后复制等操作
  768. this.objAndImgFileHandler(resultPath, filePath, zipPath, file);
  769. //创建data.json
  770. this.writeDataJson(path);
  771. CompletableFuture.runAsync(() -> {
  772. SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
  773. sceneAsynOperLog.setNum(num);
  774. sceneAsynOperLog.setOperType(SceneAsynOperType.UPLOAD.code());
  775. sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
  776. sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
  777. sceneAsynOperLogService.save(sceneAsynOperLog);
  778. try {
  779. //调用算法,不同的类型调用不同的算法
  780. CmdBuildUtil.BuildModelCommand(path);
  781. //算法计算完后,生成压缩文件,上传到oss
  782. uploadFileofterBuildDamModel(path, filePath, num, bucket);
  783. //更新版本信息
  784. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  785. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  786. sceneEditInfoService.update(
  787. new LambdaUpdateWrapper<SceneEditInfo>()
  788. .setSql("version = version + 1")
  789. .setSql("floor_edit_ver = floor_edit_ver + 1")
  790. .setSql("floor_publish_ver = floor_publish_ver + 1")
  791. .setSql("img_version = img_version + 1")
  792. .set(SceneEditInfo::getIsUploadObj, CommonStatus.YES.code())
  793. .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
  794. sceneEditInfoService.upgradeSceneJsonVersion(num, sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1, bucket);
  795. sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
  796. } catch (Exception e) {
  797. log.error("上传dam模型,num:" + num, e);
  798. sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
  799. }finally {
  800. FileUtil.del(path);
  801. }
  802. sceneAsynOperLogService.updateById(sceneAsynOperLog);
  803. });
  804. }
  805. /**
  806. * 新算法(3dtiles)上传模型逻辑
  807. * @param num
  808. * @param bucket
  809. * @param dataSource
  810. * @throws Exception
  811. */
  812. private void buildModel43dtiles(String num, String bucket, String dataSource, MultipartFile file) throws Exception {
  813. //文件上传的位置可以自定义
  814. String path = dataSource + "_obj2Tiles" + File.separator;
  815. String meshPath = path + "mesh";
  816. String zipPath = path + "zip" + File.separator;
  817. String zipFilePath = zipPath + file.getOriginalFilename();
  818. //压缩文件处理:解压缩,解压缩后复制等操作
  819. FileUtil.del(path);
  820. FileUtil.mkdir(zipPath);
  821. File zipFile = new File(zipFilePath);
  822. file.transferTo(zipFile);
  823. ZipUtil.unzip(zipFilePath, meshPath);
  824. String jsonName = "";
  825. if(ossUtil.doesObjectExist(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/floors.json")){
  826. jsonName = "floors.json";
  827. }
  828. if(ossUtil.doesObjectExist(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/mesh.json")){
  829. jsonName = "mesh.json";
  830. }
  831. //检测文件
  832. String floorsJsonPath = meshPath + File.separator + jsonName;
  833. if(!FileUtil.exist(floorsJsonPath)){
  834. throw new BusinessException(ErrorCode.FAILURE_CODE_5068.code(), "json file is not exist!");
  835. }
  836. String floorsJsonStr = FileUtil.readUtf8String(floorsJsonPath);
  837. JSONObject floorsJsonObj = JSON.parseObject(floorsJsonStr);
  838. JSONArray floorArr = floorsJsonObj.getJSONArray("floors");
  839. if(CollUtil.isEmpty(floorArr)){
  840. throw new BusinessException(ErrorCode.FAILURE_CODE_5069.code(), "json content is error");
  841. }
  842. Set<String> floorNameSet = new HashSet<>();
  843. String finalJsonName = jsonName;
  844. floorArr.stream().forEach(item->{
  845. JSONObject itemObj = (JSONObject) item;
  846. //楼层目录是否存在
  847. String name = itemObj.getString("name");
  848. if(StrUtil.isEmpty(name) || !FileUtil.exist(meshPath + File.separator + name)){
  849. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  850. }
  851. //检测obj文件是否存在
  852. if("floors.json".equals(finalJsonName)){
  853. String objPath = itemObj.getString("objPath");
  854. if(StrUtil.isEmpty(objPath) || !FileUtil.exist(path + objPath)){
  855. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  856. }
  857. }else{
  858. JSONArray lods = itemObj.getJSONArray("lods");
  859. if(CollUtil.isEmpty(lods)){
  860. throw new BusinessException(ErrorCode.FAILURE_CODE_5069.code(), "json content is error");
  861. }
  862. for (Object lod : lods) {
  863. JSONObject lodObj = (JSONObject) lod;
  864. String objPath = lodObj.getString("objPath");
  865. if(StrUtil.isEmpty(objPath) || !FileUtil.exist(path + objPath)){
  866. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  867. }
  868. }
  869. }
  870. if(floorNameSet.contains(name)){
  871. throw new BusinessException(ErrorCode.FAILURE_CODE_5069);
  872. }
  873. floorNameSet.add(name);
  874. });
  875. //读取oss上的floors.jsoon用于校验用户上传的模型楼层数是否一一对应
  876. String ossFloorsJson = ossUtil.getFileContent(bucket,String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/" + jsonName);
  877. JSONObject orginFloorsJsonObj = JSON.parseObject(ossFloorsJson);
  878. JSONArray orginFloorArr = orginFloorsJsonObj.getJSONArray("floors");
  879. Set<String> orginFloorNameSet = orginFloorArr.stream().map(item -> {
  880. JSONObject itemObj = (JSONObject) item;
  881. return itemObj.getString("name");
  882. }).collect(Collectors.toSet());
  883. if(floorNameSet.size() != orginFloorNameSet.size()){
  884. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  885. }
  886. orginFloorNameSet.stream().forEach(orginName->{
  887. if(!floorNameSet.contains(orginName)){
  888. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  889. }
  890. });
  891. SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
  892. sceneAsynOperLog.setNum(num);
  893. sceneAsynOperLog.setOperType(SceneAsynOperType.UPLOAD.code());
  894. sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
  895. sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
  896. sceneAsynOperLogService.save(sceneAsynOperLog);
  897. CompletableFuture.runAsync(() -> {
  898. try {
  899. //调用算法
  900. log.info("上传3dtiles模型开始, num:{}, targetPath:{}", num, path);
  901. CmdBuildUtil.Obj2Tiles(path);
  902. log.info("上传3dtiles模型结束, num:{}, targetPath:{}", num, path);
  903. //检测计算结果
  904. String tilesPath = path + "3dtiles";
  905. String tilesetJsonPath = tilesPath + File.separator + "tileset.json";
  906. boolean success = ComputerUtil.checkComputeCompleted(tilesetJsonPath, maxCheckTimes, waitTime);
  907. if(!success){
  908. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  909. }
  910. //删除logs
  911. FileUtil.del(tilesPath + File.separator + "logs");
  912. //算法计算完后,生成压缩文件,上传到oss
  913. //上传3dtiles
  914. ossUtil.deleteObject(bucket, String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles");
  915. ossUtil.uploadFileDirCmd(bucket, tilesPath, String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles", false);
  916. //上传mesh
  917. ossUtil.deleteObject(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh");
  918. ossUtil.uploadFileDirCmd(bucket, meshPath, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh",false);
  919. //更新版本信息
  920. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  921. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  922. sceneEditInfoService.update(
  923. new LambdaUpdateWrapper<SceneEditInfo>()
  924. .setSql("version = version + 1")
  925. .setSql("floor_edit_ver = floor_edit_ver + 1")
  926. .setSql("floor_publish_ver = floor_publish_ver + 1")
  927. .setSql("img_version = img_version + 1")
  928. .set(SceneEditInfo::getIsUploadObj, CommonStatus.YES.code())
  929. .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
  930. sceneEditInfoService.upgradeSceneJsonVersion(num, sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1, bucket);
  931. sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
  932. } catch (Exception e) {
  933. log.error("上传全景图报错,num:" + num, e);
  934. sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
  935. }finally{
  936. FileUtil.del(path);
  937. }
  938. sceneAsynOperLogService.updateById(sceneAsynOperLog);
  939. });
  940. }
  941. private void uploadFileofterBuildDamModel(String path, String filePath, String sceneNum, String bucket) throws Exception {
  942. //因为共享目录有延迟,这里循环检测算法是否计算完毕3次,每次隔五秒
  943. String uploadJsonPath = path + File.separator + "results" +File.separator+"upload.json";
  944. boolean exist = ComputerUtil.checkComputeCompleted(uploadJsonPath, maxCheckTimes, waitTime);
  945. if(!exist){
  946. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  947. }
  948. String uploadData = FileUtils.readFile(uploadJsonPath);
  949. JSONObject uploadJson = null;
  950. JSONArray array = null;
  951. if(uploadData!=null) {
  952. uploadJson = JSONObject.parseObject(uploadData);
  953. array = uploadJson.getJSONArray("upload");
  954. }
  955. Map<String,String> map = new HashMap<String,String>();
  956. JSONObject fileJson = null;
  957. String fileName = "";
  958. String imgViewPath = String.format(UploadFilePath.IMG_VIEW_PATH, sceneNum);
  959. for(int i = 0, len = array.size(); i < len; i++) {
  960. fileJson = array.getJSONObject(i);
  961. fileName = fileJson.getString("file");
  962. //文件不存在抛出异常
  963. if (!new File(path + File.separator + "results" + File.separator + fileName).exists()) {
  964. throw new Exception(path + File.separator + "results" + File.separator + fileName + "文件不存在");
  965. }
  966. //tex文件夹
  967. if (fileJson.getIntValue("clazz") == 15) {
  968. map.put(path + File.separator + "results" + File.separator + fileName,
  969. imgViewPath + ConstantFileName.modelUUID + "_50k_texture_jpg_high1/" + fileName.replace("tex/", ""));
  970. continue;
  971. }
  972. }
  973. String damPath = path + File.separator + "results" +File.separator+ ConstantFileName.modelUUID+"_50k.dam";
  974. CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", damPath);
  975. boolean existDam = ComputerUtil.checkComputeCompleted(damPath, 5, 2);
  976. if(!existDam){
  977. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  978. }
  979. // CreateObjUtil.convertDamToLzma(path + File.separator + "results");
  980. // CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", path + File.separator + "results" + File.separator+ConstantFileName.modelUUID+"_50k.dam");
  981. // map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam.lzma", imgViewPath +ConstantFileName.modelUUID+"_50k.dam.lzma");
  982. map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam", imgViewPath+ConstantFileName.modelUUID+"_50k.dam");
  983. String ossMeshPath = String.format(UploadFilePath.DATA_VIEW_PATH, sceneNum) + "mesh";
  984. //删除oss中的mesh
  985. ossUtil.deleteObject(bucket, ossMeshPath);
  986. //上传obj相关文件
  987. List<String> fileNames = FileUtil.listFileNames(filePath);
  988. fileNames.stream().forEach(name->{
  989. ossUtil.uploadFile(bucket,ossMeshPath + File.separator + name, filePath + name, false);
  990. });
  991. ossUtil.uploadMulFiles(bucket, map);
  992. }
  993. private void writeDataJson(String path) throws IOException {
  994. JSONObject dataJson = new JSONObject();
  995. dataJson.put("obj2txt", true);
  996. dataJson.put("split_type", "SPLIT_V6");
  997. dataJson.put("data_describe", "double spherical");
  998. dataJson.put("skybox_type", "SKYBOX_V5");
  999. FileUtils.writeFile(path + "/data.json", dataJson.toString());
  1000. }
  1001. private void objAndImgFileHandler(String resultPath, String filePath, String zipPath, MultipartFile file)
  1002. throws Exception {
  1003. FileUtils.delAllFile(resultPath);
  1004. File targetFile = new File(filePath);
  1005. if (!targetFile.exists()) {
  1006. targetFile.mkdirs();
  1007. }else {
  1008. FileUtils.delAllFile(filePath);
  1009. }
  1010. targetFile = new File(zipPath);
  1011. if (!targetFile.exists()) {
  1012. targetFile.mkdirs();
  1013. }else {
  1014. FileUtils.delAllFile(zipPath);
  1015. }
  1016. targetFile = new File(zipPath + file.getOriginalFilename());
  1017. if(!targetFile.getParentFile().exists()){
  1018. targetFile.getParentFile().mkdirs();
  1019. }
  1020. // 保存压缩包到本地
  1021. if(targetFile.exists())
  1022. {
  1023. FileUtils.deleteFile(zipPath + file.getOriginalFilename());
  1024. }
  1025. file.transferTo(targetFile);
  1026. ZipUtil.unzip(zipPath + file.getOriginalFilename(), zipPath + "data/");
  1027. //源文件数据,判断是否有多个文件夹,有多个就提示错误,有一个就将文件夹里数据迁移到extras目录,无直接迁移到extras目录
  1028. boolean flag = false;
  1029. //目录名称,如果不为空,则压缩文件第一层是目录
  1030. String targetName = "";
  1031. File dataFile = new File(zipPath + "data/");
  1032. for(File data : dataFile.listFiles()){
  1033. if(data.isDirectory() && flag){
  1034. throw new BusinessException(ErrorCode.FAILURE_CODE_5018);
  1035. }
  1036. if(data.isDirectory() && !flag){
  1037. flag = true;
  1038. targetName = data.getName();
  1039. }
  1040. }
  1041. //是否包含obj文件
  1042. boolean objFlag = false;
  1043. //是否包含mtl文件
  1044. boolean mtlFlag = false;
  1045. File[] files = null;
  1046. String dataPath = null;
  1047. if(StrUtil.isEmpty(targetName)){
  1048. files = dataFile.listFiles();
  1049. dataPath = zipPath + "data/";
  1050. }else{
  1051. files = new File(zipPath + "data/" + targetName).listFiles();
  1052. dataPath = zipPath + "data/" + targetName + File.separator;
  1053. }
  1054. for(File data : files){
  1055. if(data.isDirectory()){
  1056. throw new BusinessException(ErrorCode.FAILURE_CODE_5018);
  1057. }
  1058. if(data.getName().endsWith(".jpg") || data.getName().endsWith(".png")){
  1059. if(!FileUtils.checkFileSizeIsLimit(data.length(), 1.5, "M")){
  1060. throw new BusinessException(ErrorCode.FAILURE_CODE_5020);
  1061. }
  1062. }
  1063. if(data.getName().endsWith(".obj")){
  1064. if(objFlag){
  1065. throw new BusinessException(ErrorCode.FAILURE_CODE_5019);
  1066. }
  1067. if(!data.getName().equals("mesh.obj")){
  1068. throw new BusinessException(ErrorCode.FAILURE_CODE_5060);
  1069. }
  1070. if(!FileUtils.checkFileSizeIsLimit(data.length(), 20, "M")){
  1071. throw new BusinessException(ErrorCode.FAILURE_CODE_5020);
  1072. }
  1073. objFlag = true;
  1074. FileUtils.copyFile(dataPath + data.getName(), filePath + "mesh.obj", true);
  1075. continue;
  1076. }
  1077. if(data.getName().endsWith(".mtl")){
  1078. mtlFlag = true;
  1079. }
  1080. FileUtils.copyFile(dataPath + data.getName(), filePath + data.getName(), true);
  1081. }
  1082. //压缩文件中必须有且仅有一个obj和mtl文件,否则抛出异常
  1083. if(!mtlFlag || !objFlag){
  1084. throw new BusinessException(ErrorCode.FAILURE_CODE_5059);
  1085. }
  1086. }
  1087. public ResultData downloadModel(String num) throws Exception {
  1088. if(StrUtil.isEmpty(num)){
  1089. throw new BusinessException(ErrorCode.PARAM_REQUIRED);
  1090. }
  1091. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  1092. if(scenePlus == null){
  1093. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  1094. }
  1095. //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
  1096. sceneAsynOperLogService.checkSceneAsynOper(num,null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.MODEL.code());
  1097. //清除旧的下载记录
  1098. sceneAsynOperLogService.cleanLog(num, SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.MODEL.code(), SceneAsynOperType.DOWNLOAD.code());
  1099. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  1100. String bucket = scenePlusExt.getYunFileBucket();
  1101. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  1102. //开始异步执行下载全景图压缩包操作
  1103. CompletableFuture.runAsync(() -> {
  1104. SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
  1105. sceneAsynOperLog.setNum(num);
  1106. sceneAsynOperLog.setOperType(SceneAsynOperType.DOWNLOAD.code());
  1107. sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
  1108. sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
  1109. sceneAsynOperLog.setVersion(sceneEditInfo.getImgVersion());
  1110. sceneAsynOperLogService.save(sceneAsynOperLog);
  1111. try {
  1112. String url = null;
  1113. if(ModelKind.THREE_D_TILE.code().equals(scenePlusExt.getModelKind())){
  1114. url = downloadModel43dtiles(num, bucket, scenePlusExt, sceneEditInfo);
  1115. }else{
  1116. url = downloadModel4Dam(num, bucket);
  1117. }
  1118. sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
  1119. sceneAsynOperLog.setUrl(url);
  1120. }catch (Exception e){
  1121. sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
  1122. log.error("下载模型压缩包失败,num:" + num, e);
  1123. }
  1124. sceneAsynOperLogService.saveOrUpdate(sceneAsynOperLog);
  1125. });
  1126. return ResultData.ok();
  1127. }
  1128. @Override
  1129. public ScenePro getByNum(String num) {
  1130. return this.getOne(new LambdaQueryWrapper<ScenePro>().eq(ScenePro::getNum, num));
  1131. }
  1132. private String downloadModel43dtiles(String num, String bucket, ScenePlusExt scenePlusExt, SceneEditInfo sceneEditInfo){
  1133. //下载mesh到本地
  1134. String meshOssPath = String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh" + File.separator;
  1135. String meshLocalPath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.SCENE_DATA_PATH_V4, num) + "mesh" + File.separator;
  1136. String zipName = num + "_mesh.zip";
  1137. String zipFilePath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.SCENE_DATA_PATH_V4, num) + zipName;
  1138. List<String> keys = ossUtil.listFiles(bucket, meshOssPath);
  1139. //下载
  1140. //下载
  1141. keys.stream().filter(v->{
  1142. if((v.contains("lod_") && !v.contains("lod_0")) || v.endsWith(File.separator)){
  1143. return false;
  1144. }
  1145. return true;
  1146. }).forEach(v->{
  1147. ossUtil.downloadFile(bucket, v, v.replace(SystemUtil.transPath(meshOssPath), meshLocalPath));
  1148. });
  1149. String meshJsonPath = meshLocalPath + "mesh.json";
  1150. if(FileUtil.exist(meshJsonPath)){
  1151. JSONObject meshJson = JSON.parseObject(FileUtil.readUtf8String(meshJsonPath));
  1152. JSONArray floors = meshJson.getJSONArray("floors");
  1153. for (Object floor : floors) {
  1154. JSONObject floorObj = (JSONObject)floor;
  1155. JSONArray lods = floorObj.getJSONArray("lods");
  1156. List<Object> list = lods.stream().filter(v -> {
  1157. JSONObject lodObj = (JSONObject) v;
  1158. String name = lodObj.getString("name");
  1159. if ("lod_0".equals(name)) {
  1160. return true;
  1161. }
  1162. return false;
  1163. }).collect(Collectors.toList());
  1164. floorObj.put("lods", list);
  1165. }
  1166. FileUtil.writeUtf8String(meshJson.toJSONString(), meshJsonPath);
  1167. }
  1168. //打包
  1169. ZipUtil.zip(meshLocalPath ,zipFilePath);
  1170. //上传压缩包
  1171. ossUtil.uploadFile(bucket,"downloads/extras/" + zipName, zipFilePath, false);
  1172. //删除本地文件
  1173. FileUtil.del(meshLocalPath);
  1174. FileUtil.del(zipFilePath);
  1175. String url = "downloads/extras/" + zipName;
  1176. return url;
  1177. }
  1178. // public static void main(String[] args) {
  1179. // File copy = FileUtil.copy(new File("D:\\4DMega\\4DKK_PROGRAM_STATIC\\scene_view_data/1680825957743071232/data/mesh/"),
  1180. // new File("/mnt/4Dkankan/scene_v4/1680825957743071232/data"), true);
  1181. //
  1182. // Element rootElement = XmlUtil.getRootElement(document);
  1183. // Element size = XmlUtil.getElement(rootElement, "Size");
  1184. // String Height = size.getAttribute("Height");
  1185. // String Width = size.getAttribute("Width");
  1186. //
  1187. // System.out.println(123);
  1188. // }
  1189. private String downloadModel4Dam(String num, String bucket){
  1190. String localImagePath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.IMAGESBUFFER_FORMAT, num);
  1191. if(!new File(localImagePath).exists()){
  1192. new File(localImagePath).mkdirs();
  1193. }
  1194. String zipName = num + "_extras.zip";
  1195. String zipPath = localImagePath + zipName;
  1196. String dataViewPath = String.format(UploadFilePath.DATA_VIEW_PATH, num);
  1197. //V3版本去oss下载2048模型
  1198. String meshPath = scenePlusService.getDataSource(num) + File.separator + String.format(ConstantFileLocPath.DATABUFFER_FORMAT, num) + "mesh";
  1199. FileUtils.deleteDirectory(meshPath);
  1200. ossUtil.downloadFile(bucket, dataViewPath + "mesh", meshPath);
  1201. log.info("meshPath="+meshPath);
  1202. if(!new File(meshPath).exists() || new File(meshPath).listFiles().length < 1){
  1203. throw new BusinessException(ErrorCode.FAILURE_CODE_7006);
  1204. }
  1205. for(File file : new File(meshPath).listFiles()){
  1206. if(file.isDirectory()){
  1207. for (File item : file.listFiles()) {
  1208. if(item.getName().endsWith(".obj") && !"output.house.obj".equals(item.getName()) &&
  1209. !"mesh.obj".equals(item.getName())){
  1210. item.delete();
  1211. }
  1212. if(item.getName().endsWith(".mtl") && !"output.house.mtl".equals(item.getName()) &&
  1213. !"mesh.mtl".equals(item.getName())){
  1214. item.delete();
  1215. }
  1216. }
  1217. continue;
  1218. }
  1219. if(file.getName().endsWith(".obj") && !"output.house.obj".equals(file.getName()) &&
  1220. !"mesh.obj".equals(file.getName())){
  1221. file.delete();
  1222. }
  1223. if(file.getName().endsWith(".mtl") && !"output.house.mtl".equals(file.getName()) &&
  1224. !"mesh.mtl".equals(file.getName())){
  1225. file.delete();
  1226. }
  1227. }
  1228. //打包
  1229. ZipUtil.zip(meshPath, zipPath);
  1230. //上传压缩包
  1231. ossUtil.uploadFile(bucket,"downloads/extras/" + zipName, zipPath, false);
  1232. String url = "downloads/extras/" + zipName;
  1233. FileUtil.del(zipPath);
  1234. return url;
  1235. }
  1236. @Override
  1237. public List<SceneBean> listCleanOrigScene(int cleanOrigMonth) {
  1238. Date time = Calendar.getInstance().getTime();
  1239. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -cleanOrigMonth));
  1240. return this.baseMapper.selectCleanOrigScene(time);
  1241. }
  1242. @Override
  1243. public List<SceneBean> listCleanOss4DeletedScene(int month) {
  1244. Date time = Calendar.getInstance().getTime();
  1245. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -month));
  1246. return this.baseMapper.listCleanOss4DeletedScene(time);
  1247. }
  1248. @Override
  1249. public List<SceneBean> listCleanOss4TestCamera(Set<Long> cameraIds, int month) {
  1250. Date time = Calendar.getInstance().getTime();
  1251. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -month));
  1252. return this.baseMapper.listCleanOss4TestCamera(cameraIds, time);
  1253. }
  1254. @Override
  1255. public List<SceneBean> listColdStorageScene(int cleanOrigMonth) {
  1256. Date time = Calendar.getInstance().getTime();
  1257. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -cleanOrigMonth));
  1258. return this.baseMapper.selectColdStorageScene(time);
  1259. }
  1260. }