SceneProServiceImpl.java 61 KB

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