SceneProServiceImpl.java 41 KB

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