package com.fdkankan.scene.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.RuntimeUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.ZipUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fdkankan.common.constant.*; import com.fdkankan.model.constants.ConstantFileName; import com.fdkankan.model.constants.ConstantFilePath; import com.fdkankan.model.constants.UploadFilePath; import com.fdkankan.common.exception.BusinessException; import com.fdkankan.scene.bean.SceneBean; import com.fdkankan.web.response.ResultData; import com.fdkankan.model.utils.ComputerUtil; import com.fdkankan.model.utils.ConvertUtils; import com.fdkankan.model.utils.CreateObjUtil; import com.fdkankan.common.util.FileUtils; import com.fdkankan.common.util.OkHttpUtils; import com.fdkankan.fyun.constant.FYunTypeEnum; import com.fdkankan.fyun.face.FYunFileServiceInterface; import com.fdkankan.redis.constant.RedisKey; import com.fdkankan.redis.constant.RedisLockKey; import com.fdkankan.redis.util.RedisLockUtil; import com.fdkankan.redis.util.RedisUtil; import com.fdkankan.scene.bean.IconBean; import com.fdkankan.scene.bean.TagBean; import com.fdkankan.scene.entity.SceneEditInfo; import com.fdkankan.scene.entity.ScenePlus; import com.fdkankan.scene.entity.ScenePlusExt; import com.fdkankan.scene.entity.ScenePro; import com.fdkankan.scene.mapper.ISceneProMapper; import com.fdkankan.scene.service.ISceneDataDownloadService; import com.fdkankan.scene.service.ISceneEditControlsService; import com.fdkankan.scene.service.ISceneEditInfoService; import com.fdkankan.scene.service.IScenePlusExtService; import com.fdkankan.scene.service.IScenePlusService; import com.fdkankan.scene.service.ISceneProService; import com.fdkankan.scene.service.ISceneUploadService; import com.fdkankan.scene.vo.BaseDataParamVO; import com.fdkankan.scene.vo.DeleteFileParamVO; import com.fdkankan.scene.vo.DeleteHotIconParamVO; import com.fdkankan.scene.vo.DeleteHotParamVO; import com.fdkankan.scene.vo.FileNameAndDataParamVO; import com.fdkankan.scene.vo.HotParamVO; import com.fdkankan.scene.vo.SaveTagsParamVO; import com.fdkankan.scene.vo.SaveTagsVisibleParamVO; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.RedissonLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; /** *

* pro场景表 服务实现类 *

* * @author dengsixing * @since 2021-12-23 */ @Slf4j @Service public class SceneProServiceImpl extends ServiceImpl implements ISceneProService { @Value("${fyun.host}") private String ossUrlPrefix; @Value("${fyun.type}") private String fyunType; @Value("${main.url}") private String mainUrl; @Value("${scene.url}") private String sceneUrl; @Value("${scene.pro.url}") private String sceneProUrl; @Value("${scene.pro.new.url}") private String sceneProNewUrl; @Value("${ecs.checkFile.maxTimes:5}") private int maxCheckTimes; @Value("${ecs.checkFile.waitTime:5000}") private int waitTime; @Autowired private FYunFileServiceInterface fYunFileService; @Autowired private RedisLockUtil redisLockUtil; @Autowired private RedisUtil redisUtil; @Autowired private ISceneDataDownloadService sceneDataDownloadService; @Autowired private ISceneProService sceneProService; @Autowired private ISceneEditInfoService sceneEditInfoService; @Autowired private ISceneEditControlsService sceneEditControlsService; @Autowired private IScenePlusService scenePlusService; @Autowired private IScenePlusExtService scenePlusExtService; @Autowired private ISceneUploadService sceneUploadService; @Transactional @Override public ResultData saveInitialPage(FileNameAndDataParamVO param) throws Exception{ ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); if(scenePlus == null){ throw new BusinessException(ErrorCode.FAILURE_CODE_5005); } ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId()); //更新缩略图url String thumbUrl = this.ossUrlPrefix + String.format(UploadFilePath.USER_EDIT_PATH, param.getNum()) + param.getFileName(); scenePlusExt.setThumb(thumbUrl); scenePlusExtService.updateById(scenePlusExt); SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); if(sceneEditInfo == null){ sceneEditInfo = new SceneEditInfo(); sceneEditInfo.setScenePlusId(scenePlus.getId()); sceneEditInfo.setEntry(param.getData()); sceneEditInfoService.save(sceneEditInfo); }else{ sceneEditInfoService.update( new LambdaUpdateWrapper() .set(SceneEditInfo::getEntry, param.getData()) .setSql("version = version + 1") .eq(SceneEditInfo::getId, sceneEditInfo.getId())); } return ResultData.ok(); } @Override public ResultData addOrUpdateTag(SaveTagsParamVO param) throws Exception { // ScenePro scenePro = this.findBySceneNum(param.getNum()); ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); if (scenePlus == null) return ResultData.error(ErrorCode.FAILURE_CODE_5005); this.addOrUpdateHotData(param.getNum(), param.getHotDataList()); this.addOrUpdateIcons(param.getNum(), param.getIcons()); //写入本地文件,作为备份 this.writeHotJson(param.getNum()); //保存数据库 SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); sceneEditInfoService.saveTagsToSceneEditInfo(param.getNum(), sceneEditInfo); sceneEditInfoService.updateById(sceneEditInfo); return ResultData.ok(); } private void addOrUpdateHotData(String num, List hotDataList) throws Exception{ Map addOrUpdateMap = new HashMap<>(); int i = 0; for (HotParamVO hotParamVO : hotDataList) { JSONObject jsonObject = JSON.parseObject(hotParamVO.getHotData()); jsonObject.put("createTime", Calendar.getInstance().getTimeInMillis() + i++); addOrUpdateMap.put(hotParamVO.getSid(), jsonObject.toJSONString()); } this.syncHotFromFileToRedis(num); //处理新增和修改数据 this.addOrUpdateHotDataHandler(num, addOrUpdateMap); } private void addOrUpdateIcons(String num, List icons) throws Exception{ if(CollUtil.isEmpty(icons)){ return; } this.syncIconsFromFileToRedis(num); String key = String.format(RedisKey.SCENE_HOT_ICONS, num); redisUtil.sSet(key, icons.toArray()); } @Override public ResultData deleteTag(DeleteHotParamVO param) throws Exception { // ScenePro scenePro = this.findBySceneNum(param.getNum()); ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); if (scenePlus == null) throw new BusinessException(ErrorCode.FAILURE_CODE_5005); ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId()); String bucket = scenePlusExt.getYunFileBucket(); List deleteSidList = param.getSidList(); //处理删除状态数据 this.deleteHotData(param.getNum(), deleteSidList, bucket); //删除导览中的热点数据 this.deleteHotDataFromTourJson(param.getNum(), param.getSidList(), bucket); //写入本地文件,作为备份 this.writeHotJson(param.getNum()); //保存数据库 SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); sceneEditInfoService.saveTagsToSceneEditInfo(param.getNum(), sceneEditInfo); sceneEditInfoService.updateById(sceneEditInfo); return ResultData.ok(); } private void deleteHotDataFromTourJson(String num, List sidList, String bucket){ String key = String.format(UploadFilePath.USER_EDIT_PATH, num) + "tour.json"; String tourJson = fYunFileService.getFileContent(bucket, key); if(StrUtil.isEmpty(tourJson)){ return; } JSONArray jsonArray = JSON.parseArray(tourJson); if(CollUtil.isEmpty(jsonArray)){ return; } jsonArray.stream().forEach(tour->{ JSONObject obj = (JSONObject) tour; JSONArray itemArra = obj.getJSONArray("list"); itemArra.stream().forEach(item->{ JSONObject itemObj = (JSONObject) item; String tagId = itemObj.getString("tagId"); if(tagId != null && sidList.contains(tagId)){ itemObj.remove("tagId"); } }); }); fYunFileService.uploadFile(bucket, jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8), key); } @Override public ResultData deleteIcons(DeleteHotIconParamVO param) throws Exception { ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); if (scenePlus == null) throw new BusinessException(ErrorCode.FAILURE_CODE_5005); List fileNameList = param.getFileNameList(); this.syncIconsFromFileToRedis(param.getNum()); String key = String.format(RedisKey.SCENE_HOT_ICONS, param.getNum()); redisUtil.setRemove(key, fileNameList.toArray()); //写入本地文件,作为备份 this.writeHotJson(param.getNum()); //删除oss文件 sceneUploadService.delete( DeleteFileParamVO.builder() .num(param.getNum()) .fileNames(fileNameList) .bizType(FileBizType.TAG_ICON.code()).build()); return ResultData.ok(); } @Override public ResultData listTags(String num) throws Exception{ //保证热点数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis this.syncHotFromFileToRedis(num); //保证icons数据安全性,当redis宕机导致icons数据丢失时,可以从文件中读取,恢复到redis this.syncIconsFromFileToRedis(num); JSONObject result = new JSONObject(); //查询缓存是否包含热点数据 String key = String.format(RedisKey.SCENE_HOT_DATA, num); Map allTagsMap = redisUtil.hmget(key); List tags = Lists.newArrayList(); List tagBeanList = new ArrayList<>(); if(CollUtil.isNotEmpty(allTagsMap)){ allTagsMap.entrySet().stream().forEach(entry -> { JSONObject jsonObject = JSON.parseObject(entry.getValue()); tagBeanList.add( TagBean.builder() .createTime(jsonObject.getLong("createTime")) .tag(jsonObject).build()); }); //按创建时间倒叙排序 tagBeanList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed()); //移除createTime字段 tags = tagBeanList.stream().map(tagBean -> { JSONObject tag = tagBean.getTag(); tag.remove("createTime"); return tag; }).collect(Collectors.toList()); } result.put("tags", tags); //查询缓存是否包含icons key = String.format(RedisKey.SCENE_HOT_ICONS, num); Set icons = redisUtil.sGet(key); if(icons == null){ icons = Sets.newHashSet(); } List iconList = this.sortIcons(tags, icons); result.put("icons", iconList); return ResultData.ok(result); } private List sortIcons(List tags, Set icons){ //统计使用频次 List iconBeans = Lists.newArrayList(); for (String icon : icons) { int count = 0; for (JSONObject tag : tags) { String sid = tag.getString("icon"); if(StrUtil.isEmpty(sid) || !icon.equals(sid)){ continue; } ++count; } iconBeans.add(IconBean.builder().icon(icon).count(count).build()); } //排序 List iconList = iconBeans.stream().sorted(Comparator.comparing(IconBean::getCount).reversed()) .map(item -> { return item.getIcon(); }).collect(Collectors.toList()); return iconList; } /** *

保证热点数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis *

* @author dengsixing * @date 2022/3/3 **/ private void syncHotFromFileToRedis(String num) throws Exception{ String key = String.format(RedisKey.SCENE_HOT_DATA, num); boolean exist = redisUtil.hasKey(key); if(exist){ return; } String lockKey = String.format(RedisLockKey.LOCK_HOT_DATA_SYNC, num); String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString(); boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE); if(!lock){ throw new BusinessException(ErrorCode.SYSTEM_BUSY); } try{ exist = redisUtil.hasKey(key); if(exist){ return; } String tagsFilePath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "hot.json"; String tagsData = FileUtils.readUtf8String(tagsFilePath); if(StrUtil.isEmpty(tagsData)){ return; } JSONObject jsonObject = JSON.parseObject(tagsData); JSONArray tagsArr = jsonObject.getJSONArray("tags"); if(CollUtil.isEmpty(tagsArr)){ return; } Map map = new HashMap<>(); for (Object o : tagsArr) { JSONObject jo = (JSONObject)o; map.put(jo.getString("sid"), jo.toJSONString()); } redisUtil.hmset(key, map); }finally { redisLockUtil.unlockLua(lockKey, lockVal); } } /** *

保证icons数据安全性,当redis宕机导致icons数据丢失时,可以从文件中读取,恢复到redis *

* @author dengsixing * @date 2022/3/3 **/ private void syncIconsFromFileToRedis(String num) throws Exception{ String key = String.format(RedisKey.SCENE_HOT_ICONS, num); boolean exist = redisUtil.hasKey(key); if(exist){ return; } String lockKey = String.format(RedisLockKey.LOCK_HOT_ICONS_SYNC, num); String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString(); boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE); if(!lock){ throw new BusinessException(ErrorCode.SYSTEM_BUSY); } try{ exist = redisUtil.hasKey(key); if(exist){ return; } String tagsFilePath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "hot.json"; String tagsData = FileUtils.readUtf8String(tagsFilePath); if(StrUtil.isEmpty(tagsData)){ return; } JSONObject jsonObject = JSON.parseObject(tagsData); JSONArray iconArr = jsonObject.getJSONArray("icons"); if(CollUtil.isEmpty(iconArr)){ return; } redisUtil.sSet(key, iconArr.toJavaList(String.class).toArray()); }finally { redisLockUtil.unlockLua(lockKey, lockVal); } } /** *

热点数据保存 *

* @author dengsixing * @date 2022/3/3 **/ private void writeHotJson(String num) throws Exception{ String dataKey = String.format(RedisKey.SCENE_HOT_DATA, num); Map tagMap = redisUtil.hmget(dataKey); List tagList = Lists.newArrayList(); tagMap.entrySet().stream().forEach(entry->{ if(StrUtil.isNotEmpty(entry.getValue())){ tagList.add(entry.getValue()); } }); JSONObject jsonObject = new JSONObject(); JSONArray tagJsonArr = new JSONArray(); if(CollUtil.isNotEmpty(tagList)){ tagList.stream().forEach(hot->{ tagJsonArr.add(JSONObject.parseObject(hot)); }); } jsonObject.put("tags", tagJsonArr); String iconsKey = String.format(RedisKey.SCENE_HOT_ICONS, num); Set iconList = redisUtil.sGet(iconsKey); jsonObject.put("icons", iconList); String hotJsonPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "hot.json"; String lockKey = String.format(RedisLockKey.LOCK_HOT_JSON, num); String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString(); boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE); if(!lock){ return; } try{ FileUtils.writeFile(hotJsonPath, jsonObject.toJSONString()); }finally { redisLockUtil.unlockLua(lockKey, lockVal); } } private void addOrUpdateHotDataHandler(String num, Map addOrUpdateMap){ if(CollUtil.isEmpty(addOrUpdateMap)) return; //数据验证,新增、修改状态,hotdata不能为空 for (String sid : addOrUpdateMap.keySet()) { String hotData = addOrUpdateMap.get(sid); if(StrUtil.isEmpty(hotData)){ throw new BusinessException(ErrorCode.FAILURE_CODE_7004); } } //批量写入缓存 String key = String.format(RedisKey.SCENE_HOT_DATA, num); redisUtil.hmset(key, addOrUpdateMap); } private void deleteHotData(String num, List deleteSidList, String bucket) throws Exception { this.syncHotFromFileToRedis(num); if(CollUtil.isEmpty(deleteSidList)){ return; } //从redis中加载热点数据 String key = String.format(RedisKey.SCENE_HOT_DATA, num); List deletDataList = redisUtil.hMultiGet(key, deleteSidList); if(CollUtil.isEmpty(deletDataList)) return; String userDataPath = String.format(UploadFilePath.USER_EDIT_PATH, num); //删除图片音频视频等资源文件 for (String data : deletDataList) { if(StrUtil.isBlank(data)){ continue; } JSONObject jsonObject = JSON.parseObject(data); String sid = jsonObject.getString("sid"); if(jsonObject.containsKey("media")){ String fileType = jsonObject.getString("media"); if(fileType.contains("photo")) { fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".jpg"); } if(fileType.contains("audio") || fileType.contains("voice")) { fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".mp3"); } if(fileType.contains("video")) { fYunFileService.deleteFile(bucket,userDataPath + "hot"+sid+".mp4"); } } } //从redis中移除热点数据 redisUtil.hdel(key, deleteSidList.toArray()); } @Override public ResultData saveTagsVisible(SaveTagsVisibleParamVO param) throws Exception { ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); if (scenePlus == null ) { return ResultData.error(ErrorCode.FAILURE_CODE_5005); } JSONArray visiblePanos = JSONArray.parseArray(param.getData()); //如果redis找不到,就从本地文件中reload this.syncHotFromFileToRedis(param.getNum()); //从缓存中获取热点数据,如果为空,抛出异常 String key = String.format(RedisKey.SCENE_HOT_DATA, param.getNum()); Map map = redisUtil.hmget(key); if (CollUtil.isEmpty(map)) { throw new BusinessException(ErrorCode.FAILURE_CODE_7005); } List> allTags = map.entrySet().stream().filter(item -> { if (StrUtil.isBlank(item.getValue())) { return false; } return true; }).collect(Collectors.toList()); if (CollUtil.isEmpty(allTags)) { throw new BusinessException(ErrorCode.FAILURE_CODE_7005); } allTags.stream().forEach(entry->{ JSONObject hot = JSON.parseObject(entry.getValue()); visiblePanos.stream().forEach(item->{ if (hot.getString("sid").equals(((JSONObject) item).getString("sid"))) { hot.put("visiblePanos", ((JSONObject) item).getJSONArray("value")); hot.put("isHidden", ((JSONObject) item).getBoolean("isHidden")); entry.setValue(hot.toJSONString()); } }); }); //更新版本号 SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); sceneEditInfoService.upgradeVersionById(editInfo.getId()); //放入缓存 Map finalMap = new HashMap<>(); allTags.stream().forEach(entry->{ finalMap.put(entry.getKey(), entry.getValue()); }); redisUtil.hmset(key, finalMap); //写入本地文件,作为备份,以防redis数据丢失 this.writeHotJson(param.getNum()); return ResultData.ok(); } @Override public ResultData saveRoam(BaseDataParamVO param) throws Exception { // ScenePro scenePro = this.findBySceneNum(param.getNum()); ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); if (scenePlus == null ) { return ResultData.error(ErrorCode.FAILURE_CODE_5005); } ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId()); String bucket = scenePlusExt.getYunFileBucket(); JSONArray inputData = JSONObject.parseArray(param.getData()); String localDataPath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, param.getNum()); File directory = new File(localDataPath); if (!directory.exists()) { directory.mkdirs(); } String viewImagesPath = String.format(UploadFilePath.IMG_VIEW_PATH, param.getNum()); String modeldataUrl = ossUrlPrefix + viewImagesPath + "vision.modeldata?t=" + System.currentTimeMillis(); //如果是云存储,将vision.modeldata下载到本地,如果是本地存储,场景计算完就已经将这个文件拷贝到编辑目录了存在这个文件了,不需要再下载 fYunFileService.downloadFile(bucket, viewImagesPath + "vision.modeldata", localDataPath + "vision.modeldata"); //检查vision.modeldata本地是否存在,不存在抛出异常 File file = new File(localDataPath + "vision.modeldata"); if(!file.exists()) { return ResultData.error(ErrorCode.FAILURE_CODE_5012); } //将vision.modeldata解压缩至vision.json ConvertUtils.convertVisionModelDataToTxt(localDataPath + "vision.modeldata", localDataPath + "vision.json"); String str = FileUtils.readFile(localDataPath + "vision.json"); JSONObject json = JSONObject.parseObject(str); JSONArray panos = json.getJSONArray("sweepLocations"); for (int i = 0; i < panos.size(); ++i) { JSONObject pano = panos.getJSONObject(i); for (int j = 0; j < inputData.size(); ++j) { JSONObject jo = inputData.getJSONObject(j); String currentPanoId = jo.getString("panoID"); JSONArray visibles = jo.getJSONArray("visibles"); JSONArray visibles3 = jo.getJSONArray("visibles3"); if (pano.getString("uuid").replaceAll("-", "").equals(currentPanoId)) { pano.put("visibles", visibles); pano.put("visibles3", visibles3); } } } FileUtils.deleteFile(localDataPath + "vision.json"); FileUtils.deleteFile(localDataPath + "vision.modeldata"); FileUtils.writeFile(localDataPath + "vision.json", json.toString()); ConvertUtils.convertTxtToVisionModelData(localDataPath + "vision.json", localDataPath + "vision.modeldata"); fYunFileService.uploadFile(bucket, localDataPath + "vision.modeldata", viewImagesPath + "vision.modeldata"); //更新版本号 SceneEditInfo editInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); if(Objects.isNull(editInfo)){ editInfo = new SceneEditInfo(); editInfo.setScenePlusId(scenePlus.getId()); sceneEditInfoService.save(editInfo); }else{ sceneEditInfoService.upgradeVersionAndImgVersionById(editInfo.getId()); //更新scenejson缓存和oss文件版本号 sceneEditInfoService.upgradeSceneJsonVersion(param.getNum(), editInfo.getVersion() + 1, editInfo.getImgVersion() + 1, bucket); } return ResultData.ok(); } @Override public void updateUserIdByCameraId(Long userId, Long cameraId) { this.update(new LambdaUpdateWrapper() .eq(ScenePro::getCameraId, cameraId) .set(ScenePro::getUserId, userId)); } @Override public ResultData uploadObjAndImg(String num, MultipartFile file) throws Exception{ if(StrUtil.isEmpty(num)){ throw new BusinessException(ServerCode.PARAM_REQUIRED, "num"); } if(!file.getOriginalFilename().endsWith(".zip")){ throw new BusinessException(ErrorCode.FAILURE_CODE_7015); } ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num); if(scenePlus == null){ throw new BusinessException(ErrorCode.FAILURE_CODE_5005); } ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId()); String bucket = scenePlusExt.getYunFileBucket(); if(ModelKind.THREE_D_TILE.code().equals(scenePlusExt.getModelKind())){ this.buildModel43dtiles(num, bucket, scenePlusExt.getDataSource(), file); }else{ this.buildModel4Dam(num, bucket, scenePlusExt.getDataSource(), scenePlusExt.getBuildType(), file); } //更新版本信息 SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); if(Objects.isNull(sceneEditInfo)){ sceneEditInfo = new SceneEditInfo(); sceneEditInfo.setScenePlusId(scenePlus.getId()); sceneEditInfo.setFloorPublishVer(1); sceneEditInfo.setFloorEditVer(1); sceneEditInfo.setIsUploadObj(CommonStatus.YES.code()); sceneEditInfoService.save(sceneEditInfo); }else{ sceneEditInfoService.update( new LambdaUpdateWrapper() .setSql("version = version + 1") .setSql("floor_edit_ver = floor_edit_ver + 1") .setSql("floor_publish_ver = floor_publish_ver + 1") .setSql("img_version = img_version + 1") .set(SceneEditInfo::getIsUploadObj, CommonStatus.YES.code()) .eq(SceneEditInfo::getId, sceneEditInfo.getId())); sceneEditInfoService.upgradeSceneJsonVersion(num, sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1, bucket); } return ResultData.ok(); } /** * 老算法(dam)上传模型逻辑 * @param num * @param bucket * @param dataSource * @param buildType * @throws Exception */ private void buildModel4Dam(String num, String bucket, String dataSource, String buildType, MultipartFile file) throws Exception { //文件上传的位置可以自定义 String path = dataSource + "_obj2txt"; String zipPath = path + "/zip/"; String filePath = path + "/extras/"; String resultPath = path + "/results/"; //压缩文件处理:解压缩,解压缩后复制等操作 this.objAndImgFileHandler(resultPath, filePath, zipPath, file); //创建data.json this.writeDataJson(path); //调用算法,不同的类型调用不同的算法 if("V2".equals(buildType)){ CreateObjUtil.objToTxt(path , "1"); } if("V3".equals(buildType)){ CreateObjUtil.build3dModel(path , "1"); } //算法计算完后,生成压缩文件,上传到oss this.uploadFileofterRebuildPanoram(path, filePath, num, bucket); } /** * 新算法(3dtiles)上传模型逻辑 * @param num * @param bucket * @param dataSource * @throws Exception */ private void buildModel43dtiles(String num, String bucket, String dataSource, MultipartFile file) throws Exception { //文件上传的位置可以自定义 String path = dataSource + "_obj2Tiles" + File.separator; String meshPath = path + "mesh"; String zipPath = path + "zip" + File.separator; String zipFilePath = zipPath + file.getOriginalFilename(); //压缩文件处理:解压缩,解压缩后复制等操作 FileUtil.del(path); FileUtil.mkdir(zipPath); File zipFile = new File(zipFilePath); file.transferTo(zipFile); ZipUtil.unzip(zipFilePath, meshPath); //检测文件 String floorsJsonPath = meshPath + File.separator + "floors.json"; if(!FileUtil.exist(floorsJsonPath)){ throw new BusinessException(ErrorCode.FAILURE_CODE_5068); } String floorsJsonStr = FileUtil.readUtf8String(floorsJsonPath); JSONObject floorsJsonObj = JSON.parseObject(floorsJsonStr); JSONArray floorArr = floorsJsonObj.getJSONArray("floors"); if(CollUtil.isEmpty(floorArr)){ throw new BusinessException(ErrorCode.FAILURE_CODE_5069); } Set floorNameSet = new HashSet<>(); floorArr.stream().forEach(item->{ JSONObject itemObj = (JSONObject) item; //楼层目录是否存在 String name = itemObj.getString("name"); if(StrUtil.isEmpty(name) || !FileUtil.exist(meshPath + File.separator + name)){ throw new BusinessException(ErrorCode.FAILURE_CODE_5070); } //检测obj文件是否存在 String objPath = itemObj.getString("objPath"); if(StrUtil.isEmpty(objPath) || !FileUtil.exist(path + objPath)){ throw new BusinessException(ErrorCode.FAILURE_CODE_5070); } if(floorNameSet.contains(name)){ throw new BusinessException(ErrorCode.FAILURE_CODE_5069); } floorNameSet.add(name); }); //读取oss上的floors.jsoon用于校验用户上传的模型楼层数是否一一对应 String ossFloorsJson = fYunFileService.getFileContent(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/floors.json"); JSONObject orginFloorsJsonObj = JSON.parseObject(ossFloorsJson); JSONArray orginFloorArr = orginFloorsJsonObj.getJSONArray("floors"); Set orginFloorNameSet = orginFloorArr.stream().map(item -> { JSONObject itemObj = (JSONObject) item; return itemObj.getString("name"); }).collect(Collectors.toSet()); if(floorNameSet.size() != orginFloorNameSet.size()){ throw new BusinessException(ErrorCode.FAILURE_CODE_5070); } orginFloorNameSet.stream().forEach(orginName->{ if(!floorNameSet.contains(orginName)){ throw new BusinessException(ErrorCode.FAILURE_CODE_5070); } }); //调用算法 String command = "bash /home/ubuntu/bin/Obj2Tiles.sh " + path; log.info("上传3dtiles模型开始, num:{}, targetPath:{}", num, path); // RuntimeUtil.exec(command); CreateObjUtil.callshell(command); log.info("上传3dtiles模型结束, num:{}, targetPath:{}", num, path); //检测计算结果 String tilesPath = path + "3dtiles"; String tilesetJsonPath = tilesPath + File.separator + "tileset.json"; boolean success = ComputerUtil.checkComputeCompleted(tilesetJsonPath, maxCheckTimes, waitTime); if(!success){ throw new BusinessException(ErrorCode.FAILURE_CODE_7013); } //删除logs FileUtil.del(tilesPath + File.separator + "logs"); //算法计算完后,生成压缩文件,上传到oss //上传3dtiles fYunFileService.deleteFolder(bucket, String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles"); fYunFileService.uploadFileByCommand(bucket, tilesPath, String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles"); //上传mesh fYunFileService.deleteFolder(bucket, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh"); fYunFileService.uploadFileByCommand(bucket, meshPath, String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh"); } private void uploadFileofterRebuildPanoram(String path, String filePath, String sceneNum, String bucket) throws Exception { //因为共享目录有延迟,这里循环检测算法是否计算完毕3次,每次隔五秒 String uploadJsonPath = path + File.separator + "results" +File.separator+"upload.json"; boolean exist = ComputerUtil.checkComputeCompleted(uploadJsonPath, maxCheckTimes, waitTime); if(!exist){ throw new BusinessException(ErrorCode.FAILURE_CODE_7013); } String uploadData = FileUtils.readFile(uploadJsonPath); JSONObject uploadJson = null; JSONArray array = null; if(uploadData!=null) { uploadJson = JSONObject.parseObject(uploadData); array = uploadJson.getJSONArray("upload"); } Map map = new HashMap(); JSONObject fileJson = null; String fileName = ""; String imgViewPath = String.format(UploadFilePath.IMG_VIEW_PATH, sceneNum); for(int i = 0, len = array.size(); i < len; i++) { fileJson = array.getJSONObject(i); fileName = fileJson.getString("file"); //文件不存在抛出异常 if (!new File(path + File.separator + "results" + File.separator + fileName).exists()) { throw new Exception(path + File.separator + "results" + File.separator + fileName + "文件不存在"); } //tex文件夹 if (fileJson.getIntValue("clazz") == 15) { map.put(path + File.separator + "results" + File.separator + fileName, imgViewPath + ConstantFileName.modelUUID + "_50k_texture_jpg_high1/" + fileName.replace("tex/", "")); continue; } } String damPath = path + File.separator + "results" +File.separator+ ConstantFileName.modelUUID+"_50k.dam"; CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", damPath); boolean existDam = ComputerUtil.checkComputeCompleted(damPath, 5, 2); if(!existDam){ throw new BusinessException(ErrorCode.FAILURE_CODE_7013); } // CreateObjUtil.convertDamToLzma(path + File.separator + "results"); // CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", path + File.separator + "results" + File.separator+ConstantFileName.modelUUID+"_50k.dam"); // map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam.lzma", imgViewPath +ConstantFileName.modelUUID+"_50k.dam.lzma"); map.put(path + File.separator + "results" +File.separator+ConstantFileName.modelUUID+"_50k.dam", imgViewPath+ConstantFileName.modelUUID+"_50k.dam"); String ossMeshPath = String.format(UploadFilePath.DATA_VIEW_PATH, sceneNum) + "mesh"; //删除oss中的mesh fYunFileService.deleteFolder(bucket, ossMeshPath); //上传obj相关文件 List fileNames = FileUtil.listFileNames(filePath); fileNames.stream().forEach(name->map.put(filePath + name, ossMeshPath + File.separator + name)); fYunFileService.uploadMulFiles(bucket, map); } private void writeDataJson(String path) throws IOException { JSONObject dataJson = new JSONObject(); dataJson.put("obj2txt", true); dataJson.put("split_type", "SPLIT_V6"); dataJson.put("data_describe", "double spherical"); dataJson.put("skybox_type", "SKYBOX_V5"); FileUtils.writeFile(path + "/data.json", dataJson.toString()); } private void objAndImgFileHandler(String resultPath, String filePath, String zipPath, MultipartFile file) throws Exception { FileUtils.delAllFile(resultPath); File targetFile = new File(filePath); if (!targetFile.exists()) { targetFile.mkdirs(); }else { FileUtils.delAllFile(filePath); } targetFile = new File(zipPath); if (!targetFile.exists()) { targetFile.mkdirs(); }else { FileUtils.delAllFile(zipPath); } targetFile = new File(zipPath + file.getOriginalFilename()); if(!targetFile.getParentFile().exists()){ targetFile.getParentFile().mkdirs(); } // 保存压缩包到本地 if(targetFile.exists()) { FileUtils.deleteFile(zipPath + file.getOriginalFilename()); } file.transferTo(targetFile); ZipUtil.unzip(zipPath + file.getOriginalFilename(), zipPath + "data/"); //源文件数据,判断是否有多个文件夹,有多个就提示错误,有一个就将文件夹里数据迁移到extras目录,无直接迁移到extras目录 boolean flag = false; //目录名称,如果不为空,则压缩文件第一层是目录 String targetName = ""; File dataFile = new File(zipPath + "data/"); for(File data : dataFile.listFiles()){ if(data.isDirectory() && flag){ throw new BusinessException(ErrorCode.FAILURE_CODE_5018); } if(data.isDirectory() && !flag){ flag = true; targetName = data.getName(); } } //是否包含obj文件 boolean objFlag = false; //是否包含mtl文件 boolean mtlFlag = false; File[] files = null; String dataPath = null; if(StrUtil.isEmpty(targetName)){ files = dataFile.listFiles(); dataPath = zipPath + "data/"; }else{ files = new File(zipPath + "data/" + targetName).listFiles(); dataPath = zipPath + "data/" + targetName + File.separator; } for(File data : files){ if(data.isDirectory()){ throw new BusinessException(ErrorCode.FAILURE_CODE_5018); } if(data.getName().endsWith(".jpg") || data.getName().endsWith(".png")){ if(!FileUtils.checkFileSizeIsLimit(data.length(), 1.5, "M")){ throw new BusinessException(ErrorCode.FAILURE_CODE_5020); } } if(data.getName().endsWith(".obj")){ if(objFlag){ throw new BusinessException(ErrorCode.FAILURE_CODE_5019); } if(!data.getName().equals("mesh.obj")){ throw new BusinessException(ErrorCode.FAILURE_CODE_5060); } if(!FileUtils.checkFileSizeIsLimit(data.length(), 20, "M")){ throw new BusinessException(ErrorCode.FAILURE_CODE_5020); } objFlag = true; FileUtils.copyFile(dataPath + data.getName(), filePath + "mesh.obj", true); continue; } if(data.getName().endsWith(".mtl")){ mtlFlag = true; } FileUtils.copyFile(dataPath + data.getName(), filePath + data.getName(), true); } //压缩文件中必须有且仅有一个obj和mtl文件,否则抛出异常 if(!mtlFlag || !objFlag){ throw new BusinessException(ErrorCode.FAILURE_CODE_5059); } } public ResultData downloadTexData(String num) throws Exception { if(StrUtil.isEmpty(num)){ throw new BusinessException(ErrorCode.PARAM_REQUIRED); } ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num); if(scenePlus == null){ throw new BusinessException(ErrorCode.FAILURE_CODE_5005); } ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId()); String bucket = scenePlusExt.getYunFileBucket(); SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); if(ModelKind.THREE_D_TILE.code().equals(scenePlusExt.getModelKind())){ return this.downloadModel43dtiles(num, bucket, scenePlusExt, sceneEditInfo); } return this.downloadModel4Dam(num, bucket); } @Override public ScenePro getByNum(String num) { return this.getOne(new LambdaQueryWrapper().eq(ScenePro::getNum, num)); } private ResultData downloadModel43dtiles(String num, String bucket, ScenePlusExt scenePlusExt, SceneEditInfo sceneEditInfo){ //下载mesh到本地 String meshOssPath = String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh/"; String meshLocalPath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, num) + "mesh"; String zipName = num + "_mesh.zip"; String zipFilePath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, num) + zipName; //下载 fYunFileService.downloadFileByCommand(bucket, meshLocalPath, meshOssPath); //打包 ZipUtil.zip(meshLocalPath,zipFilePath); //上传压缩包 fYunFileService.uploadFile(bucket, zipFilePath, "downloads/extras/" + zipName); //删除本地文件 FileUtil.del(meshLocalPath); FileUtil.del(zipFilePath); String url = ossUrlPrefix + "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis(); return ResultData.ok(url); } public static void main(String[] args) throws Exception { ConvertUtils.convertVisionModelDataToTxt( "D:\\test\\vision.modeldata", "D:\\test\\vision.json"); } private ResultData downloadModel4Dam(String num, String bucket){ String localImagePath = String.format(ConstantFilePath.IMAGESBUFFER_FORMAT, num); if(!new File(localImagePath).exists()){ new File(localImagePath).mkdirs(); } String zipName = num + "_extras.zip"; String zipPath = localImagePath + zipName; String dataViewPath = String.format(UploadFilePath.DATA_VIEW_PATH, num); //V3版本去oss下载2048模型 String meshPath = String.format(ConstantFilePath.DATABUFFER_FORMAT, num) + "mesh"; FileUtils.deleteDirectory(meshPath); fYunFileService.downloadFileByCommand(bucket, meshPath, dataViewPath + "mesh"); log.info("meshPath="+meshPath); if(!new File(meshPath).exists() || new File(meshPath).listFiles().length < 1){ throw new BusinessException(ErrorCode.FAILURE_CODE_7006); } for(File file : new File(meshPath).listFiles()){ if(file.isDirectory()){ for (File item : file.listFiles()) { if(item.getName().endsWith(".obj") && !"output.house.obj".equals(item.getName()) && !"mesh.obj".equals(item.getName())){ item.delete(); } if(item.getName().endsWith(".mtl") && !"output.house.mtl".equals(item.getName()) && !"mesh.mtl".equals(item.getName())){ item.delete(); } } continue; } if(file.getName().endsWith(".obj") && !"output.house.obj".equals(file.getName()) && !"mesh.obj".equals(file.getName())){ file.delete(); } if(file.getName().endsWith(".mtl") && !"output.house.mtl".equals(file.getName()) && !"mesh.mtl".equals(file.getName())){ file.delete(); } } //打包 ZipUtil.zip(meshPath, zipPath); //上传压缩包 fYunFileService.uploadFile(bucket, zipPath, "downloads/extras/" + zipName); String url = ossUrlPrefix + "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis(); FileUtil.del(zipPath); return ResultData.ok(url); } @Override public List listCleanOrigScene(int cleanOrigMonth) { Date time = Calendar.getInstance().getTime(); time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -cleanOrigMonth)); return this.baseMapper.selectCleanOrigScene(time); } @Override public List listCleanOss4DeletedScene(int month) { Date time = Calendar.getInstance().getTime(); time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -month)); return this.baseMapper.listCleanOss4DeletedScene(time); } @Override public List listCleanOss4TestCamera(Set cameraIds, int month) { Date time = Calendar.getInstance().getTime(); time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -month)); return this.baseMapper.listCleanOss4TestCamera(cameraIds, time); } @Override public List listColdStorageScene(int cleanOrigMonth) { Date time = Calendar.getInstance().getTime(); time = DateUtil.beginOfDay(DateUtil.offset(time, DateField.MONTH, -cleanOrigMonth)); return this.baseMapper.selectColdStorageScene(time); } }