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.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.common.exception.BusinessException;
import com.fdkankan.common.util.FileUtils;
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.scene.config.FdkkLaserConfig;
import com.fdkankan.scene.util.CmdBuildUtil;
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.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.LaserSceneBean;
import com.fdkankan.scene.bean.TagBean;
import com.fdkankan.scene.constant.ConstantFileLocPath;
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.oss.OssUtil;
import com.fdkankan.scene.service.*;
import com.fdkankan.scene.vo.*;
import com.fdkankan.web.response.ResultData;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
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;
/**
*
* pro场景表 服务实现类
*
*
* @author dengsixing
* @since 2021-12-23
*/
@Slf4j
@Service
public class SceneProServiceImpl extends ServiceImpl implements ISceneProService {
@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 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;
@Autowired
private OssUtil ossUtil;
@Autowired
private ILaserService laserService;
@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 = 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()));
}
//修改laser场景主表的缩略图地址
laserService.editScene(param.getNum(), LaserSceneBean.builder().thumb(thumbUrl).build());
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 = ossUtil.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");
}
});
});
ossUtil.uploadFileBytes(bucket, key, jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8));
}
@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"))
{
ossUtil.deleteObject(bucket, userDataPath + "hot"+sid+".jpg");
}
if(fileType.contains("audio") || fileType.contains("voice"))
{
ossUtil.deleteObject(bucket,userDataPath + "hot"+sid+".mp3");
}
if(fileType.contains("video"))
{
ossUtil.deleteObject(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(scenePlusExt.getDataSource()+ ConstantFileLocPath.SCENE_DATA_PATH_V4, param.getNum());
File directory = new File(localDataPath);
if (!directory.exists()) {
directory.mkdirs();
}
//如果是云存储,将vision.modeldata下载到本地,如果是本地存储,场景计算完就已经将这个文件拷贝到编辑目录了存在这个文件了,不需要再下载
String viewImagesPath = String.format(UploadFilePath.IMG_VIEW_PATH, param.getNum());
ossUtil.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").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");
ossUtil.uploadFile(bucket,viewImagesPath + "vision.modeldata", localDataPath + "vision.modeldata", false);
//更新版本号
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)){
CmdBuildUtil.BuildModelCommand(path);
}
//算法计算完后,生成压缩文件,上传到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 = ossUtil.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);
CmdBuildUtil.Build3dtilesModel(path);
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
ossUtil.deleteObject(bucket,String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles");
FileUtil.copyContent(
FileUtil.file(tilesPath),
FileUtil.file(FdkkLaserConfig.getProfile(bucket) + File.separator + String.format(UploadFilePath.IMG_VIEW_PATH, num) + "3dtiles"),
true);
//上传mesh
ossUtil.deleteObject(bucket,String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh");
FileUtil.copyContent(
FileUtil.file(meshPath),
FileUtil.file(FdkkLaserConfig.getProfile(bucket) + File.separator + String.format(UploadFilePath.DATA_VIEW_PATH, num) + "mesh"),
true);
}
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
ossUtil.deleteObject(bucket, ossMeshPath);
//上传obj相关文件
List fileNames = FileUtil.listFileNames(filePath);
fileNames.stream().forEach(name->{
ossUtil.uploadFile(bucket,ossMeshPath + File.separator + name, filePath + name, false);
});
}
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);
String zipName = num + "_mesh.zip";
String zipFilePath = String.format(ConstantFilePath.SCENE_DATA_PATH_V4, num) + zipName;
//下载
ossUtil.downloadFile(bucket, meshOssPath, meshLocalPath);
//打包
ZipUtil.zip(meshLocalPath.concat("mesh") ,zipFilePath);
//上传压缩包
ossUtil.uploadFile(bucket,"downloads/extras/" + zipName, zipFilePath, false);
//删除本地文件
FileUtil.del(meshLocalPath.concat("mesh"));
FileUtil.del(zipFilePath);
String url = "downloads/extras/" + zipName + "?t=" + Calendar.getInstance().getTimeInMillis();
return ResultData.ok(url);
}
public static void main(String[] args) {
File copy = FileUtil.copy(new File("D:\\4DMega\\4DKK_PROGRAM_STATIC\\scene_view_data/1680825957743071232/data/mesh/"),
new File("/mnt/4Dkankan/scene_v4/1680825957743071232/data"), true);
}
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);
ossUtil.downloadFile(bucket, dataViewPath + "mesh", meshPath);
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);
//上传压缩包
ossUtil.uploadFile(bucket,"downloads/extras/" + zipName, zipPath, false);
String url = "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);
}
}