SceneProServiceImpl.java 53 KB

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