SceneProServiceImpl.java 58 KB

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