SceneProServiceImpl.java 57 KB

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