SceneProServiceImpl.java 54 KB

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