SceneProServiceImpl.java 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. package com.fdkankan.scene.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.io.FileUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import cn.hutool.core.util.ZipUtil;
  6. import com.alibaba.fastjson.JSON;
  7. import com.alibaba.fastjson.JSONArray;
  8. import com.alibaba.fastjson.JSONObject;
  9. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  10. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  11. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  12. import com.fdkankan.common.constant.CommonStatus;
  13. import com.fdkankan.model.constants.ConstantFileName;
  14. import com.fdkankan.model.constants.ConstantFilePath;
  15. import com.fdkankan.common.constant.ErrorCode;
  16. import com.fdkankan.common.constant.FileBizType;
  17. import com.fdkankan.common.constant.ServerCode;
  18. import com.fdkankan.model.constants.UploadFilePath;
  19. import com.fdkankan.common.exception.BusinessException;
  20. import com.fdkankan.web.response.ResultData;
  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.common.util.FileUtils;
  25. import com.fdkankan.common.util.OkHttpUtils;
  26. import com.fdkankan.fyun.constant.FYunTypeEnum;
  27. import com.fdkankan.fyun.face.FYunFileServiceInterface;
  28. import com.fdkankan.redis.constant.RedisKey;
  29. import com.fdkankan.redis.constant.RedisLockKey;
  30. import com.fdkankan.redis.util.RedisLockUtil;
  31. import com.fdkankan.redis.util.RedisUtil;
  32. import com.fdkankan.scene.bean.IconBean;
  33. import com.fdkankan.scene.bean.TagBean;
  34. import com.fdkankan.scene.entity.SceneEditInfo;
  35. import com.fdkankan.scene.entity.ScenePlus;
  36. import com.fdkankan.scene.entity.ScenePlusExt;
  37. import com.fdkankan.scene.entity.ScenePro;
  38. import com.fdkankan.scene.mapper.ISceneProMapper;
  39. import com.fdkankan.scene.service.ISceneDataDownloadService;
  40. import com.fdkankan.scene.service.ISceneEditControlsService;
  41. import com.fdkankan.scene.service.ISceneEditInfoService;
  42. import com.fdkankan.scene.service.IScenePlusExtService;
  43. import com.fdkankan.scene.service.IScenePlusService;
  44. import com.fdkankan.scene.service.ISceneProService;
  45. import com.fdkankan.scene.service.ISceneUploadService;
  46. import com.fdkankan.scene.vo.BaseDataParamVO;
  47. import com.fdkankan.scene.vo.DeleteFileParamVO;
  48. import com.fdkankan.scene.vo.DeleteHotIconParamVO;
  49. import com.fdkankan.scene.vo.DeleteHotParamVO;
  50. import com.fdkankan.scene.vo.FileNameAndDataParamVO;
  51. import com.fdkankan.scene.vo.HotParamVO;
  52. import com.fdkankan.scene.vo.SaveTagsParamVO;
  53. import com.fdkankan.scene.vo.SaveTagsVisibleParamVO;
  54. import com.google.common.collect.Lists;
  55. import com.google.common.collect.Sets;
  56. import java.io.File;
  57. import java.io.IOException;
  58. import java.util.ArrayList;
  59. import java.util.Calendar;
  60. import java.util.Comparator;
  61. import java.util.HashMap;
  62. import java.util.List;
  63. import java.util.Map;
  64. import java.util.Map.Entry;
  65. import java.util.Objects;
  66. import java.util.Set;
  67. import java.util.stream.Collectors;
  68. import lombok.extern.slf4j.Slf4j;
  69. import org.redisson.Redisson;
  70. import org.redisson.RedissonLock;
  71. import org.springframework.beans.factory.annotation.Autowired;
  72. import org.springframework.beans.factory.annotation.Qualifier;
  73. import org.springframework.beans.factory.annotation.Value;
  74. import org.springframework.stereotype.Service;
  75. import org.springframework.transaction.annotation.Transactional;
  76. import org.springframework.web.multipart.MultipartFile;
  77. /**
  78. * <p>
  79. * pro场景表 服务实现类
  80. * </p>
  81. *
  82. * @author dengsixing
  83. * @since 2021-12-23
  84. */
  85. @Slf4j
  86. @Service
  87. public class SceneProServiceImpl extends ServiceImpl<ISceneProMapper, ScenePro> implements ISceneProService {
  88. @Value("${fyun.host}")
  89. private String ossUrlPrefix;
  90. @Value("${fyun.type}")
  91. private String fyunType;
  92. @Value("${ecs.checkFile.maxTimes:5}")
  93. private int maxCheckTimes;
  94. @Value("${ecs.checkFile.waitTime:5000}")
  95. private int waitTime;
  96. @Autowired
  97. private FYunFileServiceInterface fYunFileService;
  98. @Autowired
  99. private RedisLockUtil redisLockUtil;
  100. @Autowired
  101. private RedisUtil redisUtil;
  102. @Autowired
  103. private ISceneEditInfoService sceneEditInfoService;
  104. @Autowired
  105. private IScenePlusService scenePlusService;
  106. @Autowired
  107. private IScenePlusExtService scenePlusExtService;
  108. @Autowired
  109. private ISceneUploadService sceneUploadService;
  110. @Transactional
  111. @Override
  112. public ResultData saveInitialPage(FileNameAndDataParamVO param) throws Exception{
  113. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  114. if(scenePlus == null){
  115. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  116. }
  117. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  118. String thumbUrl = String.format(UploadFilePath.USER_EDIT_PATH, param.getNum()) + param.getFileName();
  119. //更新缩略图url
  120. if(!FYunTypeEnum.LOCAL.code().equals(fyunType)){
  121. thumbUrl = this.ossUrlPrefix + thumbUrl;
  122. }
  123. scenePlusExt.setThumb(thumbUrl);
  124. scenePlusExtService.updateById(scenePlusExt);
  125. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  126. if(sceneEditInfo == null){
  127. sceneEditInfo = new SceneEditInfo();
  128. sceneEditInfo.setScenePlusId(scenePlus.getId());
  129. sceneEditInfo.setEntry(param.getData());
  130. sceneEditInfoService.save(sceneEditInfo);
  131. }else{
  132. sceneEditInfoService.update(
  133. new LambdaUpdateWrapper<SceneEditInfo>()
  134. .set(SceneEditInfo::getEntry, param.getData())
  135. .setSql("version = version + 1")
  136. .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
  137. }
  138. return ResultData.ok();
  139. }
  140. @Override
  141. public ResultData addOrUpdateTag(SaveTagsParamVO param) throws Exception {
  142. // ScenePro scenePro = this.findBySceneNum(param.getNum());
  143. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  144. if (scenePlus == null)
  145. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  146. this.addOrUpdateHotData(param.getNum(), param.getHotDataList());
  147. this.addOrUpdateIcons(param.getNum(), param.getIcons());
  148. //写入本地文件,作为备份
  149. this.writeHotJson(param.getNum());
  150. //保存数据库
  151. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  152. sceneEditInfoService.saveTagsToSceneEditInfo(param.getNum(), sceneEditInfo);
  153. sceneEditInfoService.updateById(sceneEditInfo);
  154. return ResultData.ok();
  155. }
  156. private void addOrUpdateHotData(String num, List<HotParamVO> hotDataList) throws Exception{
  157. Map<String, String> addOrUpdateMap = new HashMap<>();
  158. int i = 0;
  159. for (HotParamVO hotParamVO : hotDataList) {
  160. JSONObject jsonObject = JSON.parseObject(hotParamVO.getHotData());
  161. jsonObject.put("createTime", Calendar.getInstance().getTimeInMillis() + i++);
  162. addOrUpdateMap.put(hotParamVO.getSid(), jsonObject.toJSONString());
  163. }
  164. this.syncHotFromFileToRedis(num);
  165. //处理新增和修改数据
  166. this.addOrUpdateHotDataHandler(num, addOrUpdateMap);
  167. }
  168. private void addOrUpdateIcons(String num, List<String> icons) throws Exception{
  169. if(CollUtil.isEmpty(icons)){
  170. return;
  171. }
  172. this.syncIconsFromFileToRedis(num);
  173. String key = String.format(RedisKey.SCENE_HOT_ICONS, num);
  174. redisUtil.sSet(key, icons.toArray());
  175. }
  176. @Override
  177. public ResultData deleteTag(DeleteHotParamVO param) throws Exception {
  178. // ScenePro scenePro = this.findBySceneNum(param.getNum());
  179. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  180. if (scenePlus == null)
  181. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  182. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  183. List<String> deleteSidList = param.getSidList();
  184. //处理删除状态数据
  185. this.deleteHotData(param.getNum(), deleteSidList, scenePlusExt.getYunFileBucket());
  186. //写入本地文件,作为备份
  187. this.writeHotJson(param.getNum());
  188. //保存数据库
  189. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  190. sceneEditInfoService.saveTagsToSceneEditInfo(param.getNum(), sceneEditInfo);
  191. sceneEditInfoService.updateById(sceneEditInfo);
  192. return ResultData.ok();
  193. }
  194. @Override
  195. public ResultData deleteIcons(DeleteHotIconParamVO param) throws Exception {
  196. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  197. if (scenePlus == null)
  198. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  199. List<String> fileNameList = param.getFileNameList();
  200. this.syncIconsFromFileToRedis(param.getNum());
  201. String key = String.format(RedisKey.SCENE_HOT_ICONS, param.getNum());
  202. redisUtil.setRemove(key, fileNameList.toArray());
  203. //写入本地文件,作为备份
  204. this.writeHotJson(param.getNum());
  205. //删除oss文件
  206. sceneUploadService.delete(
  207. DeleteFileParamVO.builder()
  208. .num(param.getNum())
  209. .fileNames(fileNameList)
  210. .bizType(FileBizType.TAG_ICON.code()).build());
  211. return ResultData.ok();
  212. }
  213. @Override
  214. public ResultData listTags(String num) throws Exception{
  215. //保证热点数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis
  216. this.syncHotFromFileToRedis(num);
  217. //保证icons数据安全性,当redis宕机导致icons数据丢失时,可以从文件中读取,恢复到redis
  218. this.syncIconsFromFileToRedis(num);
  219. JSONObject result = new JSONObject();
  220. //查询缓存是否包含热点数据
  221. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  222. Map<String, String> allTagsMap = redisUtil.hmget(key);
  223. List<JSONObject> tags = Lists.newArrayList();
  224. List<TagBean> tagBeanList = new ArrayList<>();
  225. if(CollUtil.isNotEmpty(allTagsMap)){
  226. allTagsMap.entrySet().stream().forEach(entry -> {
  227. JSONObject jsonObject = JSON.parseObject(entry.getValue());
  228. tagBeanList.add(
  229. TagBean.builder()
  230. .createTime(jsonObject.getLong("createTime"))
  231. .tag(jsonObject).build());
  232. });
  233. //按创建时间倒叙排序
  234. tagBeanList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed());
  235. //移除createTime字段
  236. tags = tagBeanList.stream().map(tagBean -> {
  237. JSONObject tag = tagBean.getTag();
  238. tag.remove("createTime");
  239. return tag;
  240. }).collect(Collectors.toList());
  241. }
  242. result.put("tags", tags);
  243. //查询缓存是否包含icons
  244. key = String.format(RedisKey.SCENE_HOT_ICONS, num);
  245. Set<String> icons = redisUtil.sGet(key);
  246. if(icons == null){
  247. icons = Sets.newHashSet();
  248. }
  249. List<String> iconList = this.sortIcons(tags, icons);
  250. result.put("icons", iconList);
  251. return ResultData.ok(result);
  252. }
  253. private List<String> sortIcons(List<JSONObject> tags, Set<String> icons){
  254. //统计使用频次
  255. List<IconBean> iconBeans = Lists.newArrayList();
  256. for (String icon : icons) {
  257. int count = 0;
  258. for (JSONObject tag : tags) {
  259. String sid = tag.getString("icon");
  260. if(StrUtil.isEmpty(sid) || !icon.equals(sid)){
  261. continue;
  262. }
  263. ++count;
  264. }
  265. iconBeans.add(IconBean.builder().icon(icon).count(count).build());
  266. }
  267. //排序
  268. List<String> iconList = iconBeans.stream().sorted(Comparator.comparing(IconBean::getCount).reversed())
  269. .map(item -> {
  270. return item.getIcon();
  271. }).collect(Collectors.toList());
  272. return iconList;
  273. }
  274. /**
  275. * <p>
  276. 保证热点数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis
  277. * </p>
  278. * @author dengsixing
  279. * @date 2022/3/3
  280. **/
  281. private void syncHotFromFileToRedis(String num) throws Exception{
  282. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  283. boolean exist = redisUtil.hasKey(key);
  284. if(exist){
  285. return;
  286. }
  287. String lockKey = String.format(RedisLockKey.LOCK_HOT_DATA_SYNC, num);
  288. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  289. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  290. if(!lock){
  291. throw new BusinessException(ErrorCode.SYSTEM_BUSY);
  292. }
  293. try{
  294. exist = redisUtil.hasKey(key);
  295. if(exist){
  296. return;
  297. }
  298. String tagsFilePath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "hot.json";
  299. String tagsData = FileUtils.readUtf8String(tagsFilePath);
  300. if(StrUtil.isEmpty(tagsData)){
  301. return;
  302. }
  303. JSONObject jsonObject = JSON.parseObject(tagsData);
  304. JSONArray tagsArr = jsonObject.getJSONArray("tags");
  305. if(CollUtil.isEmpty(tagsArr)){
  306. return;
  307. }
  308. Map<String, String> map = new HashMap<>();
  309. for (Object o : tagsArr) {
  310. JSONObject jo = (JSONObject)o;
  311. map.put(jo.getString("sid"), jo.toJSONString());
  312. }
  313. redisUtil.hmset(key, map);
  314. }finally {
  315. redisLockUtil.unlockLua(lockKey, lockVal);
  316. }
  317. }
  318. /**
  319. * <p>
  320. 保证icons数据安全性,当redis宕机导致icons数据丢失时,可以从文件中读取,恢复到redis
  321. * </p>
  322. * @author dengsixing
  323. * @date 2022/3/3
  324. **/
  325. private void syncIconsFromFileToRedis(String num) throws Exception{
  326. String key = String.format(RedisKey.SCENE_HOT_ICONS, num);
  327. boolean exist = redisUtil.hasKey(key);
  328. if(exist){
  329. return;
  330. }
  331. String lockKey = String.format(RedisLockKey.LOCK_HOT_ICONS_SYNC, num);
  332. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  333. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  334. if(!lock){
  335. throw new BusinessException(ErrorCode.SYSTEM_BUSY);
  336. }
  337. try{
  338. exist = redisUtil.hasKey(key);
  339. if(exist){
  340. return;
  341. }
  342. String tagsFilePath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "hot.json";
  343. String tagsData = FileUtils.readUtf8String(tagsFilePath);
  344. if(StrUtil.isEmpty(tagsData)){
  345. return;
  346. }
  347. JSONObject jsonObject = JSON.parseObject(tagsData);
  348. JSONArray iconArr = jsonObject.getJSONArray("icons");
  349. if(CollUtil.isEmpty(iconArr)){
  350. return;
  351. }
  352. redisUtil.sSet(key, iconArr.toJavaList(String.class).toArray());
  353. }finally {
  354. redisLockUtil.unlockLua(lockKey, lockVal);
  355. }
  356. }
  357. /**
  358. * <p>
  359. 热点数据保存
  360. * </p>
  361. * @author dengsixing
  362. * @date 2022/3/3
  363. **/
  364. private void writeHotJson(String num) throws Exception{
  365. String dataKey = String.format(RedisKey.SCENE_HOT_DATA, num);
  366. Map<String, String> tagMap = redisUtil.hmget(dataKey);
  367. List<String> tagList = Lists.newArrayList();
  368. tagMap.entrySet().stream().forEach(entry->{
  369. if(StrUtil.isNotEmpty(entry.getValue())){
  370. tagList.add(entry.getValue());
  371. }
  372. });
  373. JSONObject jsonObject = new JSONObject();
  374. JSONArray tagJsonArr = new JSONArray();
  375. if(CollUtil.isNotEmpty(tagList)){
  376. tagList.stream().forEach(hot->{
  377. tagJsonArr.add(JSONObject.parseObject(hot));
  378. });
  379. }
  380. jsonObject.put("tags", tagJsonArr);
  381. String iconsKey = String.format(RedisKey.SCENE_HOT_ICONS, num);
  382. Set<String> iconList = redisUtil.sGet(iconsKey);
  383. jsonObject.put("icons", iconList);
  384. String hotJsonPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "hot.json";
  385. String lockKey = String.format(RedisLockKey.LOCK_HOT_JSON, num);
  386. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  387. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  388. if(!lock){
  389. return;
  390. }
  391. try{
  392. FileUtils.writeFile(hotJsonPath, jsonObject.toJSONString());
  393. }finally {
  394. redisLockUtil.unlockLua(lockKey, lockVal);
  395. }
  396. }
  397. private void addOrUpdateHotDataHandler(String num, Map<String, String> addOrUpdateMap){
  398. if(CollUtil.isEmpty(addOrUpdateMap))
  399. return;
  400. //数据验证,新增、修改状态,hotdata不能为空
  401. for (String sid : addOrUpdateMap.keySet()) {
  402. String hotData = addOrUpdateMap.get(sid);
  403. if(StrUtil.isEmpty(hotData)){
  404. throw new BusinessException(ErrorCode.FAILURE_CODE_7004);
  405. }
  406. }
  407. //批量写入缓存
  408. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  409. redisUtil.hmset(key, addOrUpdateMap);
  410. }
  411. private void deleteHotData(String num, List<String> deleteSidList, String bucket) throws Exception {
  412. this.syncHotFromFileToRedis(num);
  413. if(CollUtil.isEmpty(deleteSidList)){
  414. return;
  415. }
  416. //从redis中加载热点数据
  417. String key = String.format(RedisKey.SCENE_HOT_DATA, num);
  418. List<String> deletDataList = redisUtil.hMultiGet(key, deleteSidList);
  419. if(CollUtil.isEmpty(deletDataList))
  420. return;
  421. String userDataPath = String.format(UploadFilePath.USER_EDIT_PATH, num);
  422. //删除图片音频视频等资源文件
  423. for (String data : deletDataList) {
  424. if(StrUtil.isBlank(data)){
  425. continue;
  426. }
  427. JSONObject jsonObject = JSON.parseObject(data);
  428. String sid = jsonObject.getString("sid");
  429. if(jsonObject.containsKey("media")){
  430. String fileType = jsonObject.getString("media");
  431. if(fileType.contains("photo"))
  432. {
  433. fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".jpg");
  434. }
  435. if(fileType.contains("audio") || fileType.contains("voice"))
  436. {
  437. fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".mp3");
  438. }
  439. if(fileType.contains("video"))
  440. {
  441. fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".mp4");
  442. }
  443. }
  444. }
  445. //从redis中移除热点数据
  446. redisUtil.hdel(key, deleteSidList.toArray());
  447. }
  448. @Override
  449. public ResultData saveTagsVisible(SaveTagsVisibleParamVO param) throws Exception {
  450. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  451. if (scenePlus == null ) {
  452. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  453. }
  454. JSONArray visiblePanos = JSONArray.parseArray(param.getData());
  455. //如果redis找不到,就从本地文件中reload
  456. this.syncHotFromFileToRedis(param.getNum());
  457. //从缓存中获取热点数据,如果为空,抛出异常
  458. String key = String.format(RedisKey.SCENE_HOT_DATA, param.getNum());
  459. Map<String, String> map = redisUtil.hmget(key);
  460. if (CollUtil.isEmpty(map)) {
  461. throw new BusinessException(ErrorCode.FAILURE_CODE_7005);
  462. }
  463. List<Entry<String, String>> allTags = map.entrySet().stream().filter(item -> {
  464. if (StrUtil.isBlank(item.getValue())) {
  465. return false;
  466. }
  467. return true;
  468. }).collect(Collectors.toList());
  469. if (CollUtil.isEmpty(allTags)) {
  470. throw new BusinessException(ErrorCode.FAILURE_CODE_7005);
  471. }
  472. allTags.stream().forEach(entry->{
  473. JSONObject hot = JSON.parseObject(entry.getValue());
  474. visiblePanos.stream().forEach(item->{
  475. if (hot.getString("sid").equals(((JSONObject) item).getString("sid"))) {
  476. hot.put("visiblePanos", ((JSONObject) item).getJSONArray("value"));
  477. hot.put("isHidden", ((JSONObject) item).getBoolean("isHidden"));
  478. entry.setValue(hot.toJSONString());
  479. }
  480. });
  481. });
  482. //更新版本号
  483. SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  484. sceneEditInfoService.upgradeVersionById(editInfo.getId());
  485. //放入缓存
  486. Map<String, String> finalMap = new HashMap<>();
  487. allTags.stream().forEach(entry->{
  488. finalMap.put(entry.getKey(), entry.getValue());
  489. });
  490. redisUtil.hmset(key, finalMap);
  491. //写入本地文件,作为备份,以防redis数据丢失
  492. this.writeHotJson(param.getNum());
  493. return ResultData.ok();
  494. }
  495. @Override
  496. public ResultData saveRoam(BaseDataParamVO param) throws Exception {
  497. // ScenePro scenePro = this.findBySceneNum(param.getNum());
  498. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  499. if (scenePlus == null ) {
  500. return ResultData.error(ErrorCode.FAILURE_CODE_5005);
  501. }
  502. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  503. String bucket = scenePlusExt.getYunFileBucket();
  504. JSONArray inputData = JSONObject.parseArray(param.getData());
  505. String localDataPath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, param.getNum());
  506. File directory = new File(localDataPath);
  507. if (!directory.exists()) {
  508. directory.mkdirs();
  509. }
  510. String viewImagesPath = String.format(UploadFilePath.IMG_VIEW_PATH, param.getNum());
  511. //如果是云存储,将vision.modeldata下载到本地,如果是本地存储,场景计算完就已经将这个文件拷贝到编辑目录了存在这个文件了,不需要再下载
  512. fYunFileService.downloadFile(bucket, viewImagesPath + "vision.modeldata", localDataPath + "vision.modeldata");
  513. //检查vision.modeldata本地是否存在,不存在抛出异常
  514. File file = new File(localDataPath + "vision.modeldata");
  515. if(!file.exists()) {
  516. return ResultData.error(ErrorCode.FAILURE_CODE_5012);
  517. }
  518. //将vision.modeldata解压缩至vision.json
  519. ConvertUtils.convertVisionModelDataToTxt(localDataPath + "vision.modeldata", localDataPath + "vision.json");
  520. String str = FileUtils.readFile(localDataPath + "vision.json");
  521. JSONObject json = JSONObject.parseObject(str);
  522. JSONArray panos = json.getJSONArray("sweepLocations");
  523. for (int i = 0; i < panos.size(); ++i) {
  524. JSONObject pano = panos.getJSONObject(i);
  525. for (int j = 0; j < inputData.size(); ++j) {
  526. JSONObject jo = inputData.getJSONObject(j);
  527. String currentPanoId = jo.getString("panoID");
  528. JSONArray visibles = jo.getJSONArray("visibles");
  529. JSONArray visibles3 = jo.getJSONArray("visibles3");
  530. if (pano.getString("uuid").equals(currentPanoId)) {
  531. pano.put("visibles", visibles);
  532. pano.put("visibles3", visibles3);
  533. }
  534. }
  535. }
  536. FileUtils.deleteFile(localDataPath + "vision.json");
  537. FileUtils.deleteFile(localDataPath + "vision.modeldata");
  538. FileUtils.writeFile(localDataPath + "vision.json", json.toString());
  539. ConvertUtils.convertTxtToVisionModelData(localDataPath + "vision.json", localDataPath + "vision.modeldata");
  540. fYunFileService.uploadFile(bucket, localDataPath + "vision.modeldata", viewImagesPath + "vision.modeldata");
  541. //更新版本号
  542. SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  543. if(Objects.isNull(editInfo)){
  544. editInfo = new SceneEditInfo();
  545. editInfo.setScenePlusId(scenePlus.getId());
  546. sceneEditInfoService.save(editInfo);
  547. }else{
  548. sceneEditInfoService.upgradeVersionById(editInfo.getId());
  549. //更新scenejson缓存和oss文件版本号
  550. sceneEditInfoService.upgradeSceneJsonVersion(param.getNum(), editInfo.getVersion() + 1, null, bucket);
  551. }
  552. //更新scene.json版本号
  553. // sceneEditInfoService.upgradeVersionToSceneJson(param.getNum());
  554. return ResultData.ok();
  555. }
  556. @Override
  557. public void updateUserIdByCameraId(Long userId, Long cameraId) {
  558. this.update(new LambdaUpdateWrapper<ScenePro>()
  559. .eq(ScenePro::getCameraId, cameraId)
  560. .set(ScenePro::getUserId, userId));
  561. }
  562. @Override
  563. public ResultData uploadObjAndImg(String num, MultipartFile file) throws Exception{
  564. if(StrUtil.isEmpty(num)){
  565. throw new BusinessException(ServerCode.PARAM_REQUIRED, "num");
  566. }
  567. if(!file.getOriginalFilename().endsWith(".zip")){
  568. throw new BusinessException(ErrorCode.FAILURE_CODE_7015);
  569. }
  570. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  571. if(scenePlus == null){
  572. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  573. }
  574. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  575. String bucket = scenePlusExt.getYunFileBucket();
  576. //文件上传的位置可以自定义
  577. String path = scenePlusExt.getDataSource() + "_obj2txt";
  578. String zipPath = path + "/zip/";
  579. String filePath = path + "/extras/";
  580. String resultPath = path + "/results/";
  581. //压缩文件处理:解压缩,解压缩后复制等操作
  582. this.objAndImgFileHandler(resultPath, filePath, zipPath, file);
  583. //创建data.json
  584. this.writeDataJson(path);
  585. //调用算法,不同的类型调用不同的算法
  586. if("V2".equals(scenePlusExt.getBuildType())){
  587. CreateObjUtil.objToTxt(path , "1");
  588. }
  589. if("V3".equals(scenePlusExt.getBuildType())){
  590. CreateObjUtil.build3dModel(path , "1");
  591. }
  592. //算法计算完后,生成压缩文件,上传到oss
  593. this.uploadFileofterRebuildPanoram(path, num, bucket);
  594. //更新版本信息
  595. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  596. if(Objects.isNull(sceneEditInfo)){
  597. sceneEditInfo = new SceneEditInfo();
  598. sceneEditInfo.setScenePlusId(scenePlus.getId());
  599. sceneEditInfo.setFloorPublishVer(1);
  600. sceneEditInfo.setFloorEditVer(1);
  601. sceneEditInfo.setIsUploadObj(CommonStatus.YES.code());
  602. sceneEditInfoService.save(sceneEditInfo);
  603. }else{
  604. sceneEditInfoService.update(
  605. new LambdaUpdateWrapper<SceneEditInfo>()
  606. .setSql("version = version + 1")
  607. .setSql("floor_edit_ver = floor_edit_ver + 1")
  608. .setSql("floor_publish_ver = floor_publish_ver + 1")
  609. .setSql("img_version = img_version + 1")
  610. .set(SceneEditInfo::getIsUploadObj, CommonStatus.YES.code())
  611. .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
  612. sceneEditInfoService.upgradeSceneJsonVersion(num, sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1, bucket); }
  613. return ResultData.ok();
  614. }
  615. private void uploadFileofterRebuildPanoram(String path, String sceneNum, String bucket) throws Exception {
  616. //因为共享目录有延迟,这里循环检测算法是否计算完毕3次,每次隔五秒
  617. String uploadJsonPath = path + File.separator + "results" +File.separator+"upload.json";
  618. boolean exist = ComputerUtil.checkComputeCompleted(uploadJsonPath, maxCheckTimes, waitTime);
  619. if(!exist){
  620. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  621. }
  622. String uploadData = FileUtils.readFile(uploadJsonPath);
  623. JSONObject uploadJson = null;
  624. JSONArray array = null;
  625. if(uploadData!=null) {
  626. uploadJson = JSONObject.parseObject(uploadData);
  627. array = uploadJson.getJSONArray("upload");
  628. }
  629. Map<String,String> map = new HashMap<String,String>();
  630. JSONObject fileJson = null;
  631. String fileName = "";
  632. String imgViewPath = String.format(UploadFilePath.IMG_VIEW_PATH, sceneNum);
  633. for(int i = 0, len = array.size(); i < len; i++) {
  634. fileJson = array.getJSONObject(i);
  635. fileName = fileJson.getString("file");
  636. //文件不存在抛出异常
  637. if (!new File(path + File.separator + "results" + File.separator + fileName).exists()) {
  638. throw new Exception(path + File.separator + "results" + File.separator + fileName + "文件不存在");
  639. }
  640. //tex文件夹
  641. if (fileJson.getIntValue("clazz") == 15) {
  642. map.put(path + File.separator + "results" + File.separator + fileName,
  643. imgViewPath + ConstantFileName.modelUUID + "_50k_texture_jpg_high1/" + fileName.replace("tex/", ""));
  644. continue;
  645. }
  646. }
  647. CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", path + File.separator + "results" +File.separator+ ConstantFileName.modelUUID+"_50k.dam");
  648. CreateObjUtil.convertDamToLzma(path + File.separator + "results");
  649. CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", path + File.separator + "results" + File.separator+ConstantFileName.modelUUID+"_50k.dam");
  650. map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam.lzma", imgViewPath +ConstantFileName.modelUUID+"_50k.dam.lzma");
  651. map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam", imgViewPath+ConstantFileName.modelUUID+"_50k.dam");
  652. fYunFileService.uploadMulFiles(bucket, map);
  653. }
  654. private void writeDataJson(String path) throws IOException {
  655. JSONObject dataJson = new JSONObject();
  656. dataJson.put("obj2txt", true);
  657. dataJson.put("split_type", "SPLIT_V6");
  658. dataJson.put("data_describe", "double spherical");
  659. dataJson.put("skybox_type", "SKYBOX_V5");
  660. FileUtils.writeFile(path + "/data.json", dataJson.toString());
  661. }
  662. private void objAndImgFileHandler(String resultPath, String filePath, String zipPath, MultipartFile file)
  663. throws Exception {
  664. FileUtils.delAllFile(resultPath);
  665. File targetFile = new File(filePath);
  666. if (!targetFile.exists()) {
  667. targetFile.mkdirs();
  668. }else {
  669. FileUtils.delAllFile(filePath);
  670. }
  671. targetFile = new File(zipPath);
  672. if (!targetFile.exists()) {
  673. targetFile.mkdirs();
  674. }else {
  675. FileUtils.delAllFile(zipPath);
  676. }
  677. targetFile = new File(zipPath + file.getOriginalFilename());
  678. if(!targetFile.getParentFile().exists()){
  679. targetFile.getParentFile().mkdirs();
  680. }
  681. // 保存压缩包到本地
  682. if(targetFile.exists())
  683. {
  684. FileUtils.deleteFile(zipPath + file.getOriginalFilename());
  685. }
  686. file.transferTo(targetFile);
  687. ZipUtil.unzip(zipPath + file.getOriginalFilename(), zipPath + "data/");
  688. //源文件数据,判断是否有多个文件夹,有多个就提示错误,有一个就将文件夹里数据迁移到extras目录,无直接迁移到extras目录
  689. boolean flag = false;
  690. //目录名称,如果不为空,则压缩文件第一层是目录
  691. String targetName = "";
  692. File dataFile = new File(zipPath + "data/");
  693. for(File data : dataFile.listFiles()){
  694. if(data.isDirectory() && flag){
  695. throw new BusinessException(ErrorCode.FAILURE_CODE_5018);
  696. }
  697. if(data.isDirectory() && !flag){
  698. flag = true;
  699. targetName = data.getName();
  700. }
  701. }
  702. //是否包含obj文件
  703. boolean objFlag = false;
  704. //是否包含mtl文件
  705. boolean mtlFlag = false;
  706. File[] files = null;
  707. String dataPath = null;
  708. if(StrUtil.isEmpty(targetName)){
  709. files = dataFile.listFiles();
  710. dataPath = zipPath + "data/";
  711. }else{
  712. files = new File(zipPath + "data/" + targetName).listFiles();
  713. dataPath = zipPath + "data/" + targetName + File.separator;
  714. }
  715. for(File data : files){
  716. if(data.isDirectory()){
  717. throw new BusinessException(ErrorCode.FAILURE_CODE_5018);
  718. }
  719. if(data.getName().endsWith(".jpg") || data.getName().endsWith(".png")){
  720. if(!FileUtils.checkFileSizeIsLimit(data.length(), 1.5, "M")){
  721. throw new BusinessException(ErrorCode.FAILURE_CODE_5020);
  722. }
  723. }
  724. if(data.getName().endsWith(".obj")){
  725. if(objFlag){
  726. throw new BusinessException(ErrorCode.FAILURE_CODE_5019);
  727. }
  728. if(!data.getName().equals("mesh.obj")){
  729. throw new BusinessException(ErrorCode.FAILURE_CODE_5060);
  730. }
  731. if(!FileUtils.checkFileSizeIsLimit(data.length(), 20, "M")){
  732. throw new BusinessException(ErrorCode.FAILURE_CODE_5020);
  733. }
  734. objFlag = true;
  735. FileUtils.copyFile(dataPath + data.getName(), filePath + "mesh.obj", true);
  736. continue;
  737. }
  738. if(data.getName().endsWith(".mtl")){
  739. mtlFlag = true;
  740. }
  741. FileUtils.copyFile(dataPath + data.getName(), filePath + data.getName(), true);
  742. }
  743. //压缩文件中必须有且仅有一个obj和mtl文件,否则抛出异常
  744. if(!mtlFlag || !objFlag){
  745. throw new BusinessException(ErrorCode.FAILURE_CODE_5059);
  746. }
  747. }
  748. public ResultData downloadTexData(String num) throws Exception {
  749. if(StrUtil.isEmpty(num)){
  750. throw new BusinessException(ErrorCode.PARAM_REQUIRED);
  751. }
  752. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  753. if(scenePlus == null){
  754. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  755. }
  756. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  757. String bucket = scenePlusExt.getYunFileBucket();
  758. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  759. String localImagePath = String.format(ConstantFilePath.IMAGESBUFFER_FORMAT, num);
  760. if(!new File(localImagePath).exists()){
  761. new File(localImagePath).mkdirs();
  762. }
  763. String newData = scenePlusExt.getDataSource() + "_obj2txt/extras";
  764. String newResultData = scenePlusExt.getDataSource() + "_obj2txt/results/upload.json";
  765. String zipName = num + "_extras.zip";
  766. String zipPath = localImagePath + zipName;
  767. //如果用户上传过模型,就打包上传到oss,直接返回
  768. if(CommonStatus.YES.code().equals(sceneEditInfo.getIsUploadObj())
  769. && new File(newData).exists()
  770. && new File(newResultData).exists()){
  771. //打包
  772. ZipUtil.zip(newData, zipPath);
  773. //上传压缩包
  774. fYunFileService.uploadFile(bucket, zipPath, "downloads/extras/" + zipName);
  775. String url = "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis();
  776. if(!FYunTypeEnum.LOCAL.code().equals(fyunType)){
  777. url = ossUrlPrefix + url;
  778. }
  779. return ResultData.ok(url);
  780. }
  781. String buildType = scenePlusExt.getBuildType();
  782. if("V3".equals(buildType)){
  783. String dataViewPath = String.format(UploadFilePath.DATA_VIEW_PATH, num);
  784. //V3版本去oss下载2048模型
  785. String meshPath = String.format(ConstantFilePath.DATABUFFER_FORMAT, num) + "mesh";
  786. FileUtils.deleteDirectory(meshPath);
  787. fYunFileService.downloadFileByCommand(bucket, meshPath, dataViewPath + "mesh");
  788. log.info("meshPath="+meshPath);
  789. if(!new File(meshPath).exists()){
  790. throw new BusinessException(ErrorCode.FAILURE_CODE_7006);
  791. }
  792. log.info(new File(meshPath).listFiles().toString());
  793. if(new File(meshPath).listFiles().length > 0){
  794. for(File file : new File(meshPath).listFiles()){
  795. if(file.isDirectory()){
  796. for (File item : file.listFiles()) {
  797. if(item.getName().endsWith(".obj") && !"output.house.obj".equals(item.getName()) &&
  798. !"mesh.obj".equals(item.getName())){
  799. item.delete();
  800. }
  801. if(item.getName().endsWith(".mtl") && !"output.house.mtl".equals(item.getName()) &&
  802. !"mesh.mtl".equals(item.getName())){
  803. item.delete();
  804. }
  805. }
  806. continue;
  807. }
  808. if(file.getName().endsWith(".obj") && !"output.house.obj".equals(file.getName()) &&
  809. !"mesh.obj".equals(file.getName())){
  810. file.delete();
  811. }
  812. if(file.getName().endsWith(".mtl") && !"output.house.mtl".equals(file.getName()) &&
  813. !"mesh.mtl".equals(file.getName())){
  814. file.delete();
  815. }
  816. }
  817. //打包
  818. ZipUtil.zip(meshPath, zipPath);
  819. //上传压缩包
  820. fYunFileService.uploadFile(bucket, zipPath, "downloads/extras/" + zipName);
  821. String url = "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis();
  822. if(!FYunTypeEnum.LOCAL.code().equals(fyunType)){
  823. url = ossUrlPrefix + url;
  824. }
  825. // FileUtil.del(zipPath);
  826. return ResultData.ok(url);
  827. }
  828. }
  829. //V2版本在本地获取模型资源
  830. //修改过的资源
  831. String editData = scenePlusExt.getDataSource() + "_edit/caches/tex";
  832. String results = scenePlusExt.getDataSource() + "_edit/results";
  833. if (new File(editData).exists() && new File(results).exists()){
  834. for(File file : new File(editData).listFiles()){
  835. if(file.getName().endsWith(".obj") && !"output.house.obj".equals(file.getName()) &&
  836. !"mesh.obj".equals(file.getName())){
  837. file.delete();
  838. }
  839. if(file.getName().endsWith(".mtl") && !"output.house.mtl".equals(file.getName()) &&
  840. !"mesh.mtl".equals(file.getName())){
  841. file.delete();
  842. }
  843. }
  844. ZipUtil.zip(editData, zipPath);
  845. //上传压缩包
  846. fYunFileService.uploadFile(bucket, zipPath, "downloads/extras/" + zipName);
  847. String url = "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis();
  848. if(!FYunTypeEnum.LOCAL.code().equals(fyunType)){
  849. url = ossUrlPrefix + url;
  850. }
  851. return ResultData.ok(url);
  852. }
  853. //没上传过返回源资源
  854. String dataPath = scenePlusExt.getDataSource() + "/caches/tex";
  855. File dataFile = new File(dataPath);
  856. if(!dataFile.exists()){
  857. throw new BusinessException(ErrorCode.FAILURE_CODE_3018);
  858. }
  859. for(File file : dataFile.listFiles()){
  860. if(file.getName().endsWith(".obj") && !"output.house.obj".equals(file.getName()) &&
  861. !"mesh.obj".equals(file.getName())){
  862. file.delete();
  863. }
  864. if(file.getName().endsWith(".mtl") && !"output.house.mtl".equals(file.getName()) &&
  865. !"mesh.mtl".equals(file.getName())){
  866. file.delete();
  867. }
  868. }
  869. ZipUtil.zip(dataPath, zipPath);
  870. //上传压缩包
  871. fYunFileService.uploadFile(bucket, zipPath, "downloads/extras/" + zipName);
  872. String url = "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis();
  873. if(!FYunTypeEnum.LOCAL.code().equals(fyunType)){
  874. url = ossUrlPrefix + url;
  875. }
  876. return ResultData.ok(url);
  877. }
  878. @Override
  879. public ScenePro getByNum(String num) {
  880. return this.getOne(new LambdaQueryWrapper<ScenePro>().eq(ScenePro::getNum, num));
  881. }
  882. }