SceneProServiceImpl.java 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
  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. for (String sid : addOrUpdateMap.keySet()) {
  416. String hotData = addOrUpdateMap.get(sid);
  417. if(StrUtil.isEmpty(hotData)){
  418. throw new BusinessException(ErrorCode.FAILURE_CODE_7004);
  419. }
  420. }
  421. //批量写入缓存
  422. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  423. redisUtil.hmset(key, addOrUpdateMap);
  424. }
  425. private void deleteHotData(String num, List<String> deleteSidList, String bucket) throws Exception {
  426. this.syncHotFromFileToRedis(num);
  427. if(CollUtil.isEmpty(deleteSidList)){
  428. return;
  429. }
  430. //从redis中加载热点数据
  431. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  432. List<String> deletDataList = redisUtil.hMultiGet(key, deleteSidList);
  433. if(CollUtil.isEmpty(deletDataList))
  434. return;
  435. String userDataPath = String.format(UploadFilePath.USER_EDIT_PATH, num);
  436. //删除图片音频视频等资源文件
  437. for (String data : deletDataList) {
  438. if(StrUtil.isBlank(data)){
  439. continue;
  440. }
  441. JSONObject jsonObject = JSON.parseObject(data);
  442. String sid = jsonObject.getString("sid");
  443. if(jsonObject.containsKey("media")){
  444. String fileType = jsonObject.getString("media");
  445. if(fileType.contains("photo"))
  446. {
  447. fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".jpg");
  448. }
  449. if(fileType.contains("audio") || fileType.contains("voice"))
  450. {
  451. fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".mp3");
  452. }
  453. if(fileType.contains("video"))
  454. {
  455. fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".mp4");
  456. }
  457. }
  458. }
  459. //从redis中移除热点数据
  460. redisUtil.hdel(key, deleteSidList.toArray());
  461. }
  462. @Override
  463. public ResultData saveTagsVisible(SaveTagsVisibleParamVO param) throws Exception {
  464. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  465. if (scenePlus == null ) {
  466. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  467. }
  468. JSONArray visiblePanos = JSONArray.parseArray(param.getData());
  469. //如果redis找不到,就从本地文件中reload
  470. this.syncHotFromFileToRedis(param.getNum());
  471. //从缓存中获取热点数据,如果为空,抛出异常
  472. String key = String.format(RedisKey.SCENE_HOT_DATA, param.getNum());
  473. Map<String, String> map = redisUtil.hmget(key);
  474. if (CollUtil.isEmpty(map)) {
  475. throw new BusinessException(ErrorCode.FAILURE_CODE_7005);
  476. }
  477. List<Entry<String, String>> allTags = map.entrySet().stream().filter(item -> {
  478. if (StrUtil.isBlank(item.getValue())) {
  479. return false;
  480. }
  481. return true;
  482. }).collect(Collectors.toList());
  483. if (CollUtil.isEmpty(allTags)) {
  484. throw new BusinessException(ErrorCode.FAILURE_CODE_7005);
  485. }
  486. allTags.stream().forEach(entry->{
  487. JSONObject hot = JSON.parseObject(entry.getValue());
  488. visiblePanos.stream().forEach(item->{
  489. if (hot.getString("sid").equals(((JSONObject) item).getString("sid"))) {
  490. hot.put("visiblePanos", ((JSONObject) item).getJSONArray("value"));
  491. hot.put("isHidden", ((JSONObject) item).getBoolean("isHidden"));
  492. entry.setValue(hot.toJSONString());
  493. }
  494. });
  495. });
  496. //更新版本号
  497. SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  498. sceneEditInfoService.upgradeVersionById(editInfo.getId());
  499. //放入缓存
  500. Map<String, String> finalMap = new HashMap<>();
  501. allTags.stream().forEach(entry->{
  502. finalMap.put(entry.getKey(), entry.getValue());
  503. });
  504. redisUtil.hmset(key, finalMap);
  505. //写入本地文件,作为备份,以防redis数据丢失
  506. this.writeHotJson(param.getNum());
  507. return ResultData.ok();
  508. }
  509. @Override
  510. public ResultData saveRoam(BaseDataParamVO param) throws Exception {
  511. // ScenePro scenePro = this.findBySceneNum(param.getNum());
  512. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  513. if (scenePlus == null ) {
  514. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  515. }
  516. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  517. String bucket = scenePlusExt.getYunFileBucket();
  518. JSONArray inputData = JSONObject.parseArray(param.getData());
  519. String localDataPath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, param.getNum());
  520. File directory = new File(localDataPath);
  521. if (!directory.exists()) {
  522. directory.mkdirs();
  523. }
  524. String viewImagesPath = String.format(UploadFilePath.IMG_VIEW_PATH, param.getNum());
  525. String modeldataUrl = ossUrlPrefix + viewImagesPath + "vision.modeldata?t=" + System.currentTimeMillis();
  526. //如果是云存储,将vision.modeldata下载到本地,如果是本地存储,场景计算完就已经将这个文件拷贝到编辑目录了存在这个文件了,不需要再下载
  527. fYunFileService.downloadFile(bucket, viewImagesPath + "vision.modeldata", localDataPath + "vision.modeldata");
  528. //检查vision.modeldata本地是否存在,不存在抛出异常
  529. File file = new File(localDataPath + "vision.modeldata");
  530. if(!file.exists()) {
  531. return ResultData.error(ErrorCode.FAILURE_CODE_5012);
  532. }
  533. //将vision.modeldata解压缩至vision.json
  534. ConvertUtils.convertVisionModelDataToTxt(localDataPath + "vision.modeldata", localDataPath + "vision.json");
  535. String str = FileUtils.readFile(localDataPath + "vision.json");
  536. JSONObject json = JSONObject.parseObject(str);
  537. JSONArray panos = json.getJSONArray("sweepLocations");
  538. for (int i = 0; i < panos.size(); ++i) {
  539. JSONObject pano = panos.getJSONObject(i);
  540. for (int j = 0; j < inputData.size(); ++j) {
  541. JSONObject jo = inputData.getJSONObject(j);
  542. String currentPanoId = jo.getString("panoID");
  543. JSONArray visibles = jo.getJSONArray("visibles");
  544. JSONArray visibles3 = jo.getJSONArray("visibles3");
  545. if (pano.getString("uuid").replaceAll("-", "").equals(currentPanoId)) {
  546. pano.put("visibles", visibles);
  547. pano.put("visibles3", visibles3);
  548. }
  549. }
  550. }
  551. FileUtils.deleteFile(localDataPath + "vision.json");
  552. FileUtils.deleteFile(localDataPath + "vision.modeldata");
  553. FileUtils.writeFile(localDataPath + "vision.json", json.toString());
  554. ConvertUtils.convertTxtToVisionModelData(localDataPath + "vision.json", localDataPath + "vision.modeldata");
  555. fYunFileService.uploadFile(bucket, localDataPath + "vision.modeldata", viewImagesPath + "vision.modeldata");
  556. //更新版本号
  557. SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  558. if(Objects.isNull(editInfo)){
  559. editInfo = new SceneEditInfo();
  560. editInfo.setScenePlusId(scenePlus.getId());
  561. sceneEditInfoService.save(editInfo);
  562. }else{
  563. sceneEditInfoService.upgradeVersionAndImgVersionById(editInfo.getId());
  564. //更新scenejson缓存和oss文件版本号
  565. sceneEditInfoService.upgradeSceneJsonVersion(param.getNum(), editInfo.getVersion() + 1, editInfo.getImgVersion() + 1, bucket);
  566. }
  567. return ResultData.ok();
  568. }
  569. @Override
  570. public void updateUserIdByCameraId(Long userId, Long cameraId) {
  571. this.update(new LambdaUpdateWrapper<ScenePro>()
  572. .eq(ScenePro::getCameraId, cameraId)
  573. .set(ScenePro::getUserId, userId));
  574. }
  575. @Override
  576. public ResultData uploadModel(String num, MultipartFile file) throws Exception{
  577. if(StrUtil.isEmpty(num)){
  578. throw new BusinessException(ServerCode.PARAM_REQUIRED, "num");
  579. }
  580. if(!file.getOriginalFilename().endsWith(".zip")){
  581. throw new BusinessException(ErrorCode.FAILURE_CODE_7015);
  582. }
  583. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  584. if(scenePlus == null){
  585. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  586. }
  587. //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
  588. sceneAsynOperLogService.checkSceneAsynOper(num, null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.MODEL.code());
  589. //清除全景图异步操作记录,防止再次下载的时候请求到旧的压缩包
  590. sceneAsynOperLogService.cleanLog(num, SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.MODEL.code());
  591. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  592. String bucket = scenePlusExt.getYunFileBucket();
  593. if(ModelKind.THREE_D_TILE.code().equals(scenePlusExt.getModelKind())){
  594. this.buildModel43dtiles(num, bucket, scenePlusExt.getDataSource(), file);
  595. }else{
  596. this.buildModel4Dam(num, bucket, scenePlusExt.getDataSource(), scenePlusExt.getBuildType(), file);
  597. }
  598. return ResultData.ok();
  599. }
  600. /**
  601. * 老算法(dam)上传模型逻辑
  602. * @param num
  603. * @param bucket
  604. * @param dataSource
  605. * @param buildType
  606. * @throws Exception
  607. */
  608. private void buildModel4Dam(String num, String bucket, String dataSource, String buildType, MultipartFile file) throws Exception {
  609. //文件上传的位置可以自定义
  610. String path = dataSource + "_obj2txt";
  611. String zipPath = path + "/zip/";
  612. String filePath = path + "/extras/";
  613. String resultPath = path + "/results/";
  614. //压缩文件处理:解压缩,解压缩后复制等操作
  615. this.objAndImgFileHandler(resultPath, filePath, zipPath, file);
  616. //创建data.json
  617. this.writeDataJson(path);
  618. CompletableFuture.runAsync(() -> {
  619. SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
  620. sceneAsynOperLog.setNum(num);
  621. sceneAsynOperLog.setOperType(SceneAsynOperType.UPLOAD.code());
  622. sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
  623. sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
  624. sceneAsynOperLogService.save(sceneAsynOperLog);
  625. try {
  626. //调用算法,不同的类型调用不同的算法
  627. if("V2".equals(buildType)){
  628. CreateObjUtil.objToTxt(path , "1");
  629. }
  630. if("V3".equals(buildType)){
  631. CreateObjUtil.build3dModel(path , "1");
  632. }
  633. //算法计算完后,生成压缩文件,上传到oss
  634. uploadFileofterBuildDamModel(path, filePath, num, bucket);
  635. //更新版本信息
  636. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  637. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  638. sceneEditInfoService.update(
  639. new LambdaUpdateWrapper<SceneEditInfo>()
  640. .setSql("version = version + 1")
  641. .setSql("floor_edit_ver = floor_edit_ver + 1")
  642. .setSql("floor_publish_ver = floor_publish_ver + 1")
  643. .setSql("img_version = img_version + 1")
  644. .set(SceneEditInfo::getIsUploadObj, CommonStatus.YES.code())
  645. .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
  646. sceneEditInfoService.upgradeSceneJsonVersion(num, sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1, bucket);
  647. sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
  648. } catch (Exception e) {
  649. log.error("上传dam模型,num:" + num, e);
  650. sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
  651. }
  652. sceneAsynOperLogService.updateById(sceneAsynOperLog);
  653. });
  654. }
  655. /**
  656. * 新算法(3dtiles)上传模型逻辑
  657. * @param num
  658. * @param bucket
  659. * @param dataSource
  660. * @throws Exception
  661. */
  662. private void buildModel43dtiles(String num, String bucket, String dataSource, MultipartFile file) throws Exception {
  663. //文件上传的位置可以自定义
  664. String path = dataSource + "_obj2Tiles" + File.separator;
  665. String meshPath = path + "mesh";
  666. String zipPath = path + "zip" + File.separator;
  667. String zipFilePath = zipPath + file.getOriginalFilename();
  668. //压缩文件处理:解压缩,解压缩后复制等操作
  669. FileUtil.del(path);
  670. FileUtil.mkdir(zipPath);
  671. File zipFile = new File(zipFilePath);
  672. file.transferTo(zipFile);
  673. ZipUtil.unzip(zipFilePath, meshPath);
  674. String jsonName = "";
  675. if(fYunFileService.fileExist(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/floors.json")){
  676. jsonName = "floors.json";
  677. }
  678. if(fYunFileService.fileExist(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/mesh.json")){
  679. jsonName = "mesh.json";
  680. }
  681. //检测文件
  682. String floorsJsonPath = meshPath + File.separator + jsonName;
  683. if(!FileUtil.exist(floorsJsonPath)){
  684. throw new BusinessException(ErrorCode.FAILURE_CODE_5068.code(), "json file is not exist!");
  685. }
  686. String floorsJsonStr = FileUtil.readUtf8String(floorsJsonPath);
  687. JSONObject floorsJsonObj = JSON.parseObject(floorsJsonStr);
  688. JSONArray floorArr = floorsJsonObj.getJSONArray("floors");
  689. if(CollUtil.isEmpty(floorArr)){
  690. throw new BusinessException(ErrorCode.FAILURE_CODE_5069.code(), "json content is error");
  691. }
  692. Set<String> floorNameSet = new HashSet<>();
  693. String finalJsonName = jsonName;
  694. floorArr.stream().forEach(item->{
  695. JSONObject itemObj = (JSONObject) item;
  696. //楼层目录是否存在
  697. String name = itemObj.getString("name");
  698. if(StrUtil.isEmpty(name) || !FileUtil.exist(meshPath + File.separator + name)){
  699. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  700. }
  701. //检测obj文件是否存在
  702. if("floors.json".equals(finalJsonName)){
  703. String objPath = itemObj.getString("objPath");
  704. if(StrUtil.isEmpty(objPath) || !FileUtil.exist(path + objPath)){
  705. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  706. }
  707. }else{
  708. JSONArray lods = itemObj.getJSONArray("lods");
  709. if(CollUtil.isEmpty(lods)){
  710. throw new BusinessException(ErrorCode.FAILURE_CODE_5069.code(), "json content is error");
  711. }
  712. for (Object lod : lods) {
  713. JSONObject lodObj = (JSONObject) lod;
  714. String objPath = lodObj.getString("objPath");
  715. if(StrUtil.isEmpty(objPath) || !FileUtil.exist(path + objPath)){
  716. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  717. }
  718. }
  719. }
  720. if(floorNameSet.contains(name)){
  721. throw new BusinessException(ErrorCode.FAILURE_CODE_5069);
  722. }
  723. floorNameSet.add(name);
  724. });
  725. //读取oss上的floors.jsoon用于校验用户上传的模型楼层数是否一一对应
  726. String ossFloorsJson = fYunFileService.getFileContent(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/" + jsonName);
  727. JSONObject orginFloorsJsonObj = JSON.parseObject(ossFloorsJson);
  728. JSONArray orginFloorArr = orginFloorsJsonObj.getJSONArray("floors");
  729. Set<String> orginFloorNameSet = orginFloorArr.stream().map(item -> {
  730. JSONObject itemObj = (JSONObject) item;
  731. return itemObj.getString("name");
  732. }).collect(Collectors.toSet());
  733. if(floorNameSet.size() != orginFloorNameSet.size()){
  734. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  735. }
  736. orginFloorNameSet.stream().forEach(orginName->{
  737. if(!floorNameSet.contains(orginName)){
  738. throw new BusinessException(ErrorCode.FAILURE_CODE_5070);
  739. }
  740. });
  741. SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
  742. sceneAsynOperLog.setNum(num);
  743. sceneAsynOperLog.setOperType(SceneAsynOperType.UPLOAD.code());
  744. sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
  745. sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
  746. sceneAsynOperLogService.save(sceneAsynOperLog);
  747. CompletableFuture.runAsync(() -> {
  748. try {
  749. //调用算法
  750. String command = "bash /home/ubuntu/bin/Obj2Tiles.sh " + path;
  751. log.info("上传3dtiles模型开始, num:{}, targetPath:{}", num, path);
  752. CreateObjUtil.callshell(command);
  753. log.info("上传3dtiles模型结束, num:{}, targetPath:{}", num, path);
  754. //检测计算结果
  755. String tilesPath = path + "3dtiles";
  756. String tilesetJsonPath = tilesPath + File.separator + "tileset.json";
  757. boolean success = ComputerUtil.checkComputeCompleted(tilesetJsonPath, maxCheckTimes, waitTime);
  758. if(!success){
  759. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  760. }
  761. //删除logs
  762. FileUtil.del(tilesPath + File.separator + "logs");
  763. //算法计算完后,生成压缩文件,上传到oss
  764. //上传3dtiles
  765. fYunFileService.deleteFolder(bucket, String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles");
  766. fYunFileService.uploadFileByCommand(bucket, tilesPath, String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles");
  767. //上传mesh
  768. fYunFileService.deleteFolder(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh");
  769. fYunFileService.uploadFileByCommand(bucket, meshPath, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh");
  770. //更新版本信息
  771. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  772. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  773. sceneEditInfoService.update(
  774. new LambdaUpdateWrapper<SceneEditInfo>()
  775. .setSql("version = version + 1")
  776. .setSql("floor_edit_ver = floor_edit_ver + 1")
  777. .setSql("floor_publish_ver = floor_publish_ver + 1")
  778. .setSql("img_version = img_version + 1")
  779. .set(SceneEditInfo::getIsUploadObj, CommonStatus.YES.code())
  780. .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
  781. sceneEditInfoService.upgradeSceneJsonVersion(num, sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1, bucket);
  782. sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
  783. } catch (Exception e) {
  784. log.error("上传全景图报错,num:" + num, e);
  785. sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
  786. }
  787. sceneAsynOperLogService.updateById(sceneAsynOperLog);
  788. });
  789. }
  790. private void uploadFileofterBuildDamModel(String path, String filePath, String sceneNum, String bucket) throws Exception {
  791. //因为共享目录有延迟,这里循环检测算法是否计算完毕3次,每次隔五秒
  792. String uploadJsonPath = path + File.separator + "results" +File.separator+"upload.json";
  793. boolean exist = ComputerUtil.checkComputeCompleted(uploadJsonPath, maxCheckTimes, waitTime);
  794. if(!exist){
  795. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  796. }
  797. String uploadData = FileUtils.readFile(uploadJsonPath);
  798. JSONObject uploadJson = null;
  799. JSONArray array = null;
  800. if(uploadData!=null) {
  801. uploadJson = JSONObject.parseObject(uploadData);
  802. array = uploadJson.getJSONArray("upload");
  803. }
  804. Map<String,String> map = new HashMap<String,String>();
  805. JSONObject fileJson = null;
  806. String fileName = "";
  807. String imgViewPath = String.format(UploadFilePath.IMG_VIEW_PATH, sceneNum);
  808. for(int i = 0, len = array.size(); i < len; i++) {
  809. fileJson = array.getJSONObject(i);
  810. fileName = fileJson.getString("file");
  811. //文件不存在抛出异常
  812. if (!new File(path + File.separator + "results" + File.separator + fileName).exists()) {
  813. throw new Exception(path + File.separator + "results" + File.separator + fileName + "文件不存在");
  814. }
  815. //tex文件夹
  816. if (fileJson.getIntValue("clazz") == 15) {
  817. map.put(path + File.separator + "results" + File.separator + fileName,
  818. imgViewPath + ConstantFileName.modelUUID + "_50k_texture_jpg_high1/" + fileName.replace("tex/", ""));
  819. continue;
  820. }
  821. }
  822. String damPath = path + File.separator + "results" +File.separator+ ConstantFileName.modelUUID+"_50k.dam";
  823. CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", damPath);
  824. boolean existDam = ComputerUtil.checkComputeCompleted(damPath, 5, 2);
  825. if(!existDam){
  826. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  827. }
  828. // CreateObjUtil.convertDamToLzma(path + File.separator + "results");
  829. // CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", path + File.separator + "results" + File.separator+ConstantFileName.modelUUID+"_50k.dam");
  830. // map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam.lzma", imgViewPath +ConstantFileName.modelUUID+"_50k.dam.lzma");
  831. map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam", imgViewPath+ConstantFileName.modelUUID+"_50k.dam");
  832. String ossMeshPath = String.format(UploadFilePath.DATA_VIEW_PATH, sceneNum) + "mesh";
  833. //删除oss中的mesh
  834. fYunFileService.deleteFolder(bucket, ossMeshPath);
  835. //上传obj相关文件
  836. List<String> fileNames = FileUtil.listFileNames(filePath);
  837. fileNames.stream().forEach(name->map.put(filePath + name, ossMeshPath + File.separator + name));
  838. fYunFileService.uploadMulFiles(bucket, map);
  839. }
  840. private void writeDataJson(String path) throws IOException {
  841. JSONObject dataJson = new JSONObject();
  842. dataJson.put("obj2txt", true);
  843. dataJson.put("split_type", "SPLIT_V6");
  844. dataJson.put("data_describe", "double spherical");
  845. dataJson.put("skybox_type", "SKYBOX_V5");
  846. FileUtils.writeFile(path + "/data.json", dataJson.toString());
  847. }
  848. private void objAndImgFileHandler(String resultPath, String filePath, String zipPath, MultipartFile file)
  849. throws Exception {
  850. FileUtils.delAllFile(resultPath);
  851. File targetFile = new File(filePath);
  852. if (!targetFile.exists()) {
  853. targetFile.mkdirs();
  854. }else {
  855. FileUtils.delAllFile(filePath);
  856. }
  857. targetFile = new File(zipPath);
  858. if (!targetFile.exists()) {
  859. targetFile.mkdirs();
  860. }else {
  861. FileUtils.delAllFile(zipPath);
  862. }
  863. targetFile = new File(zipPath + file.getOriginalFilename());
  864. if(!targetFile.getParentFile().exists()){
  865. targetFile.getParentFile().mkdirs();
  866. }
  867. // 保存压缩包到本地
  868. if(targetFile.exists())
  869. {
  870. FileUtils.deleteFile(zipPath + file.getOriginalFilename());
  871. }
  872. file.transferTo(targetFile);
  873. ZipUtil.unzip(zipPath + file.getOriginalFilename(), zipPath + "data/");
  874. //源文件数据,判断是否有多个文件夹,有多个就提示错误,有一个就将文件夹里数据迁移到extras目录,无直接迁移到extras目录
  875. boolean flag = false;
  876. //目录名称,如果不为空,则压缩文件第一层是目录
  877. String targetName = "";
  878. File dataFile = new File(zipPath + "data/");
  879. for(File data : dataFile.listFiles()){
  880. if(data.isDirectory() && flag){
  881. throw new BusinessException(ErrorCode.FAILURE_CODE_5018);
  882. }
  883. if(data.isDirectory() && !flag){
  884. flag = true;
  885. targetName = data.getName();
  886. }
  887. }
  888. //是否包含obj文件
  889. boolean objFlag = false;
  890. //是否包含mtl文件
  891. boolean mtlFlag = false;
  892. File[] files = null;
  893. String dataPath = null;
  894. if(StrUtil.isEmpty(targetName)){
  895. files = dataFile.listFiles();
  896. dataPath = zipPath + "data/";
  897. }else{
  898. files = new File(zipPath + "data/" + targetName).listFiles();
  899. dataPath = zipPath + "data/" + targetName + File.separator;
  900. }
  901. for(File data : files){
  902. if(data.isDirectory()){
  903. throw new BusinessException(ErrorCode.FAILURE_CODE_5018);
  904. }
  905. if(data.getName().endsWith(".jpg") || data.getName().endsWith(".png")){
  906. if(!FileUtils.checkFileSizeIsLimit(data.length(), 1.5, "M")){
  907. throw new BusinessException(ErrorCode.FAILURE_CODE_5020);
  908. }
  909. }
  910. if(data.getName().endsWith(".obj")){
  911. if(objFlag){
  912. throw new BusinessException(ErrorCode.FAILURE_CODE_5019);
  913. }
  914. if(!data.getName().equals("mesh.obj")){
  915. throw new BusinessException(ErrorCode.FAILURE_CODE_5060);
  916. }
  917. if(!FileUtils.checkFileSizeIsLimit(data.length(), 20, "M")){
  918. throw new BusinessException(ErrorCode.FAILURE_CODE_5020);
  919. }
  920. objFlag = true;
  921. FileUtils.copyFile(dataPath + data.getName(), filePath + "mesh.obj", true);
  922. continue;
  923. }
  924. if(data.getName().endsWith(".mtl")){
  925. mtlFlag = true;
  926. }
  927. FileUtils.copyFile(dataPath + data.getName(), filePath + data.getName(), true);
  928. }
  929. //压缩文件中必须有且仅有一个obj和mtl文件,否则抛出异常
  930. if(!mtlFlag || !objFlag){
  931. throw new BusinessException(ErrorCode.FAILURE_CODE_5059);
  932. }
  933. }
  934. public ResultData downloadModel(String num) throws Exception {
  935. if(StrUtil.isEmpty(num)){
  936. throw new BusinessException(ErrorCode.PARAM_REQUIRED);
  937. }
  938. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  939. if(scenePlus == null){
  940. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  941. }
  942. //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
  943. sceneAsynOperLogService.checkSceneAsynOper(num,null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.MODEL.code());
  944. //清除旧的下载记录
  945. sceneAsynOperLogService.cleanLog(num, SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.MODEL.code(), SceneAsynOperType.DOWNLOAD.code());
  946. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  947. String bucket = scenePlusExt.getYunFileBucket();
  948. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  949. //开始异步执行下载全景图压缩包操作
  950. CompletableFuture.runAsync(() -> {
  951. SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
  952. sceneAsynOperLog.setNum(num);
  953. sceneAsynOperLog.setOperType(SceneAsynOperType.DOWNLOAD.code());
  954. sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
  955. sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
  956. sceneAsynOperLog.setVersion(sceneEditInfo.getImgVersion());
  957. sceneAsynOperLogService.save(sceneAsynOperLog);
  958. try {
  959. String url = null;
  960. if(ModelKind.THREE_D_TILE.code().equals(scenePlusExt.getModelKind())){
  961. url = downloadModel43dtiles(num, bucket, scenePlusExt, sceneEditInfo);
  962. }else{
  963. url = downloadModel4Dam(num, bucket);
  964. }
  965. sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
  966. sceneAsynOperLog.setUrl(url);
  967. }catch (Exception e){
  968. sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
  969. log.error("下载模型压缩包失败,num:" + num, e);
  970. }
  971. sceneAsynOperLogService.saveOrUpdate(sceneAsynOperLog);
  972. });
  973. return ResultData.ok();
  974. }
  975. @Override
  976. public ScenePro getByNum(String num) {
  977. return this.getOne(new LambdaQueryWrapper<ScenePro>().eq(ScenePro::getNum, num));
  978. }
  979. private String downloadModel43dtiles(String num, String bucket, ScenePlusExt scenePlusExt, SceneEditInfo sceneEditInfo){
  980. //下载mesh到本地
  981. String meshOssPath = String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/";
  982. String meshLocalPath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, num) + "mesh";
  983. String zipName = num + "_mesh.zip";
  984. String zipFilePath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, num) + zipName;
  985. //下载
  986. fYunFileService.downloadFileByCommand(bucket, meshLocalPath, meshOssPath);
  987. //打包
  988. ZipUtil.zip(meshLocalPath,zipFilePath);
  989. //上传压缩包
  990. fYunFileService.uploadFile(bucket, zipFilePath, "downloads/extras/" + zipName);
  991. //删除本地文件
  992. FileUtil.del(meshLocalPath);
  993. FileUtil.del(zipFilePath);
  994. String url = "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis();
  995. return url;
  996. }
  997. public static void main(String[] args) throws Exception {
  998. ConvertUtils.convertVisionModelDataToTxt( "D:\\test\\vision.modeldata", "D:\\test\\vision.json");
  999. }
  1000. private String downloadModel4Dam(String num, String bucket){
  1001. String localImagePath = String.format(ConstantFilePath.IMAGESBUFFER_FORMAT, num);
  1002. if(!new File(localImagePath).exists()){
  1003. new File(localImagePath).mkdirs();
  1004. }
  1005. String zipName = num + "_extras.zip";
  1006. String zipPath = localImagePath + zipName;
  1007. String dataViewPath = String.format(UploadFilePath.DATA_VIEW_PATH, num);
  1008. //V3版本去oss下载2048模型
  1009. String meshPath = String.format(ConstantFilePath.DATABUFFER_FORMAT, num) + "mesh";
  1010. FileUtils.deleteDirectory(meshPath);
  1011. fYunFileService.downloadFileByCommand(bucket, meshPath, dataViewPath + "mesh");
  1012. log.info("meshPath="+meshPath);
  1013. if(!new File(meshPath).exists() || new File(meshPath).listFiles().length < 1){
  1014. throw new BusinessException(ErrorCode.FAILURE_CODE_7006);
  1015. }
  1016. for(File file : new File(meshPath).listFiles()){
  1017. if(file.isDirectory()){
  1018. for (File item : file.listFiles()) {
  1019. if(item.getName().endsWith(".obj") && !"output.house.obj".equals(item.getName()) &&
  1020. !"mesh.obj".equals(item.getName())){
  1021. item.delete();
  1022. }
  1023. if(item.getName().endsWith(".mtl") && !"output.house.mtl".equals(item.getName()) &&
  1024. !"mesh.mtl".equals(item.getName())){
  1025. item.delete();
  1026. }
  1027. }
  1028. continue;
  1029. }
  1030. if(file.getName().endsWith(".obj") && !"output.house.obj".equals(file.getName()) &&
  1031. !"mesh.obj".equals(file.getName())){
  1032. file.delete();
  1033. }
  1034. if(file.getName().endsWith(".mtl") && !"output.house.mtl".equals(file.getName()) &&
  1035. !"mesh.mtl".equals(file.getName())){
  1036. file.delete();
  1037. }
  1038. }
  1039. //打包
  1040. ZipUtil.zip(meshPath, zipPath);
  1041. //上传压缩包
  1042. fYunFileService.uploadFile(bucket, zipPath, "downloads/extras/" + zipName);
  1043. String url = "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis();
  1044. FileUtil.del(zipPath);
  1045. return url;
  1046. }
  1047. @Override
  1048. public List<SceneBean> listCleanOrigScene(int cleanOrigMonth) {
  1049. Date time = Calendar.getInstance().getTime();
  1050. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -cleanOrigMonth));
  1051. return this.baseMapper.selectCleanOrigScene(time);
  1052. }
  1053. @Override
  1054. public List<SceneBean> listCleanOss4DeletedScene(int month) {
  1055. Date time = Calendar.getInstance().getTime();
  1056. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -month));
  1057. return this.baseMapper.listCleanOss4DeletedScene(time);
  1058. }
  1059. @Override
  1060. public List<SceneBean> listCleanOss4TestCamera(Set<Long> cameraIds, int month) {
  1061. Date time = Calendar.getInstance().getTime();
  1062. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -month));
  1063. return this.baseMapper.listCleanOss4TestCamera(cameraIds, time);
  1064. }
  1065. @Override
  1066. public List<SceneBean> listColdStorageScene(int cleanOrigMonth) {
  1067. Date time = Calendar.getInstance().getTime();
  1068. time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -cleanOrigMonth));
  1069. return this.baseMapper.selectColdStorageScene(time);
  1070. }
  1071. }