SceneProServiceImpl.java 42 KB

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