dengsixing 7 months ago
parent
commit
2e3a69f980
37 changed files with 15594 additions and 28 deletions
  1. 37 0
      src/main/java/com/fdkankan/common/constant/CommonOperStatus.java
  2. 61 0
      src/main/java/com/fdkankan/common/constant/ConstantCmd.java
  3. 3 1
      src/main/java/com/fdkankan/common/constant/ErrorCode.java
  4. 27 0
      src/main/java/com/fdkankan/common/constant/ModelKind.java
  5. 25 0
      src/main/java/com/fdkankan/common/constant/SceneAsynFuncType.java
  6. 23 0
      src/main/java/com/fdkankan/common/constant/SceneAsynModuleType.java
  7. 24 0
      src/main/java/com/fdkankan/common/constant/SceneAsynOperType.java
  8. 7340 0
      src/main/java/com/fdkankan/common/proto/BigSceneProto.java
  9. 4345 0
      src/main/java/com/fdkankan/common/proto/Visionmodeldata.java
  10. 1603 0
      src/main/java/com/fdkankan/common/proto/format/JsonFormat.java
  11. 99 0
      src/main/java/com/fdkankan/common/util/CmdUtils.java
  12. 81 0
      src/main/java/com/fdkankan/common/util/CreateObjUtil.java
  13. 36 0
      src/main/java/com/fdkankan/common/util/FileUtils.java
  14. 79 0
      src/main/java/com/fdkankan/common/util/StreamGobblerLine.java
  15. 138 2
      src/main/java/com/fdkankan/scene/controller/SceneEditController.java
  16. 111 0
      src/main/java/com/fdkankan/scene/entity/SceneAsynOperLog.java
  17. 1 1
      src/main/java/com/fdkankan/scene/generator/AutoGenerate.java
  18. 18 0
      src/main/java/com/fdkankan/scene/mapper/SceneAsynOperLogMapper.java
  19. 5 0
      src/main/java/com/fdkankan/scene/service/FYunFileService.java
  20. 9 0
      src/main/java/com/fdkankan/scene/service/ICommonService.java
  21. 22 0
      src/main/java/com/fdkankan/scene/service/ICutModelService.java
  22. 5 4
      src/main/java/com/fdkankan/scene/service/ISceneProService.java
  23. 26 0
      src/main/java/com/fdkankan/scene/service/SceneAsynOperLogService.java
  24. 13 13
      src/main/java/com/fdkankan/scene/service/SceneEditInfoService.java
  25. 4 0
      src/main/java/com/fdkankan/scene/service/SceneFileMappingService.java
  26. 49 0
      src/main/java/com/fdkankan/scene/service/impl/CommonServiceImpl.java
  27. 181 0
      src/main/java/com/fdkankan/scene/service/impl/CutModelServiceImpl.java
  28. 13 0
      src/main/java/com/fdkankan/scene/service/impl/FYunFileServiceImpl.java
  29. 152 0
      src/main/java/com/fdkankan/scene/service/impl/SceneAsynOperLogServiceImpl.java
  30. 552 0
      src/main/java/com/fdkankan/scene/service/impl/SceneEditInfoServiceImpl.java
  31. 9 0
      src/main/java/com/fdkankan/scene/service/impl/SceneFileMappingServiceImpl.java
  32. 346 6
      src/main/java/com/fdkankan/scene/service/impl/SceneProServiceImpl.java
  33. 0 1
      src/main/java/com/fdkankan/scene/vo/BaseJsonArrayParamVO.java
  34. 88 0
      src/main/java/com/fdkankan/scene/vo/FileParamVO.java
  35. 35 0
      src/main/java/com/fdkankan/scene/vo/SceneAsynOperLogParamVO.java
  36. 29 0
      src/main/java/com/fdkankan/scene/vo/UploadPanoramaVO.java
  37. 5 0
      src/main/resources/mapper/scene.generator/SceneAsynOperLogMapper.xml

+ 37 - 0
src/main/java/com/fdkankan/common/constant/CommonOperStatus.java

@@ -0,0 +1,37 @@
+package com.fdkankan.common.constant;
+
+public enum CommonOperStatus {
+
+    WAITING(0, "等待中"),
+    SUCCESS(1, "成功"),
+    FAILD(-1, "失败");
+
+    private Integer code;
+    private String message;
+
+    private CommonOperStatus(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public Integer code() {
+        return code;
+    }
+
+    public String message() {
+        return message;
+    }
+
+    public static CommonOperStatus get(Integer code){
+        CommonOperStatus[] values = CommonOperStatus.values();
+        Integer enumValue = null;
+        for(CommonOperStatus eachValue : values){
+            enumValue = eachValue.code();
+            if(enumValue.equals(code)){
+                return eachValue;
+            }
+        }
+        return null;
+    }
+
+}

+ 61 - 0
src/main/java/com/fdkankan/common/constant/ConstantCmd.java

@@ -0,0 +1,61 @@
+package com.fdkankan.common.constant;
+
+public class ConstantCmd {
+
+	//生成模型的命令
+	public static final String BUILD_MODEL_COMMAND = "sudo bash /home/ubuntu/bin/Launcher.sh ";
+
+	//生成模型的命令
+	public static final String BUILD_MODEL_COMMAND2 = "bash /opt/ossutil/sshoss.sh ";
+
+	public static final String BUILD_MODEL_OLD_COMMAND = "bash /home/ubuntu/bin_old/Launcher.sh ";
+	public static final String BUILD_MODEL_SFM_COMMAND = "bash /home/ubuntu/run_sfm.sh ";
+
+	public static final String OBJ_TO_TXT = "sudo bash /home/ubuntu/bin_old/obj2txt.sh ";
+
+	public static final String REBUILD_MODEL_FLLOR = "bash /home/ubuntu/bin/Panoramix_Floorplan.sh ";
+	//切图命令
+	public static final String CUT_IMG_COMMAND = "bash /home/ubuntu/OpenSfM/bin/run_cube.sh ";
+	//调整图片的命令
+	public static final String ADJUST_IMG_COMMAND = "/home/ubuntu/OpenSfM/bin/run_skybox ";
+
+
+
+	//转台拼图命令
+	public static final String BUILD_PANORAMA = "AutopanoGiga /home/ubuntu/data/";
+	//六目,拼图,计算,切图(二代)
+	public static final String BUILD_FOR_SIX = "bash /home/ubuntu/run_all_m6.sh ";
+
+	//合并音频
+	public static final String MERGE_VIDEO = "bash /monchickey/ffmpeg/bin/ff_synthesis.sh ";
+
+	//生成一段静音音频
+	public static final String CREATE_MUTE_VIDEO = "bash /monchickey/ffmpeg/bin/ff_mtue.sh ";
+
+	//将mp4文件转换成flv
+	public static final String MP4_TO_FLV = "sudo bash /monchickey/ffmpeg/bin/ff_mp4TOflv.sh ";
+
+	public static final String FORMAT_MP4 = "bash /monchickey/ffmpeg/bin/ff_formatMp4.sh ";
+
+	//删除/mnt/data/下的数据脚本
+	public static final String DELETE_FILE = "bash /monchickey/ffmpeg/bin/delete.sh ";
+
+	public static final String OSS_UTIL_CP ="bash /opt/ossutil/oss.sh ";
+
+	public static final String OSS_FILE_CP = "bash /opt/ossutil/file.sh ";
+
+	public static final String MATTERPRO_CUT_IMG = "node /opt/4dkankan_scene/index.js ";
+
+	//激光相机 extra迁移
+	public static final String CP_JG_EXTRA = "bash /opt/ossutil/laser-copy.sh ";
+
+	public static final String CP_JG_ALL = "bash /opt/ossutil/laser-cp-r.sh ";
+
+	// 修改户型图json文件
+	public static final String TRANSLATE_HOUST_FLOOR = "/opt/Robin/JsonRead.out ";
+
+	public static final String FYUN_DOWNLOAD = "sudo bash /opt/ossutil/fyun-download.sh %s /%s %s %s %s";
+
+	public static final String FYUN_UPLOAD = "sudo bash /opt/ossutil/fyun-upload.sh %s %s /%s %s %s";
+
+}

+ 3 - 1
src/main/java/com/fdkankan/common/constant/ErrorCode.java

@@ -211,7 +211,9 @@ public enum ErrorCode {
     FAILURE_CODE_10001(10001, "appkey未生效"),
     FAILURE_CODE_10002(10002, "账号已存在,请勿重复创建"),
     FAILURE_CODE_10003(10003, "账号不存在"),
-    FAILURE_CODE_10004(10004, "api次数不能为空")
+    FAILURE_CODE_10004(10004, "api次数不能为空"),
+
+    FAILURE_CODE_15059(15059, "该压缩包无可用obj或者mtl文件"),
     //-----------------openApi----------------------end
 
 

+ 27 - 0
src/main/java/com/fdkankan/common/constant/ModelKind.java

@@ -0,0 +1,27 @@
+package com.fdkankan.common.constant;
+
+/**
+ * 算法计算生成模型类型
+ */
+public enum ModelKind {
+
+    DAM("dam", "dam"),
+    THREE_D_TILE("3dtiles", "3dtiles");
+
+    private String code;
+    private String message;
+
+    private ModelKind(String code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public String code() {
+        return code;
+    }
+
+    public String message() {
+        return message;
+    }
+
+}

+ 25 - 0
src/main/java/com/fdkankan/common/constant/SceneAsynFuncType.java

@@ -0,0 +1,25 @@
+package com.fdkankan.common.constant;
+
+public enum SceneAsynFuncType {
+
+    PANORAMIC_IMAGE("panorama", "全景图"),
+
+    MODEL("model", "模型");
+
+    private String code;
+    private String message;
+
+    private SceneAsynFuncType(String code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public String code() {
+        return code;
+    }
+
+    public String message() {
+        return message;
+    }
+
+}

+ 23 - 0
src/main/java/com/fdkankan/common/constant/SceneAsynModuleType.java

@@ -0,0 +1,23 @@
+package com.fdkankan.common.constant;
+
+public enum SceneAsynModuleType {
+
+    UPLOAD_DOWNLOAD("repair", "上传下载");
+
+    private String code;
+    private String message;
+
+    private SceneAsynModuleType(String code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public String code() {
+        return code;
+    }
+
+    public String message() {
+        return message;
+    }
+
+}

+ 24 - 0
src/main/java/com/fdkankan/common/constant/SceneAsynOperType.java

@@ -0,0 +1,24 @@
+package com.fdkankan.common.constant;
+
+public enum SceneAsynOperType {
+
+    UPLOAD("upload", "上传"),
+    DOWNLOAD("download", "下载");
+
+    private String code;
+    private String message;
+
+    private SceneAsynOperType(String code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public String code() {
+        return code;
+    }
+
+    public String message() {
+        return message;
+    }
+
+}

File diff suppressed because it is too large
+ 7340 - 0
src/main/java/com/fdkankan/common/proto/BigSceneProto.java


File diff suppressed because it is too large
+ 4345 - 0
src/main/java/com/fdkankan/common/proto/Visionmodeldata.java


File diff suppressed because it is too large
+ 1603 - 0
src/main/java/com/fdkankan/common/proto/format/JsonFormat.java


+ 99 - 0
src/main/java/com/fdkankan/common/util/CmdUtils.java

@@ -0,0 +1,99 @@
+package com.fdkankan.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Created by Xiewj on 2021/1/4 0004 14:53
+ */
+@Slf4j
+public class CmdUtils {
+
+
+    /**
+     * 调用算法 xx.sh 脚本
+     * @param command
+     */
+//    public static void callshell(String command){
+//        try {
+//            String[] cmd = new String[]{"/bin/sh", "-c", command};
+//            Process process = Runtime.getRuntime().exec(cmd);
+//            StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR");
+//            errorGobbler.start();
+//            StreamGobbler outGobbler = new StreamGobbler(process.getInputStream(), "STDOUT");
+//            outGobbler.start();
+//            process.waitFor();
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+//    }
+
+
+
+    public static void callLineSh(String command) throws Exception {
+        callLineSh(command, null);
+
+    }
+
+    /**
+     * 直接java调用命令
+     * @param command
+     */
+    public static void callLine(String command) throws Exception {
+        callLine(command, null);
+
+    }
+
+    /**
+     * 直接java调用命令
+     * @param command
+     */
+    public static void callLine(String command, Integer lineSize) throws Exception {
+        log.info("cmd: " + command);
+        Process process = Runtime.getRuntime().exec(command);
+        log.info("开始运行");
+        StreamGobblerLine errorGobbler = new StreamGobblerLine(process.getErrorStream(), "ERROR");
+        errorGobbler.start();
+        // 200行打印一次日志
+        StreamGobblerLine outGobbler = new StreamGobblerLine(process.getInputStream(), "STDOUT", lineSize);
+        outGobbler.start();
+        process.waitFor();
+    }
+
+    /**
+     *
+     * @param command 命令
+     * @param lineSize 日志输出行数 ,可以为null
+     */
+    public static void callLineSh(String command, Integer lineSize) throws Exception {
+        log.info("cmd: " + command);
+        String[] cmd = new String[]{"/bin/sh", "-c", command};
+        Process process = Runtime.getRuntime().exec(cmd);
+        log.info("开始运行");
+        StreamGobblerLine errorGobbler = new StreamGobblerLine(process.getErrorStream(), "ERROR");
+        errorGobbler.start();
+        // 200行打印一次日志
+        StreamGobblerLine outGobbler = new StreamGobblerLine(process.getInputStream(), "STDOUT", lineSize);
+        outGobbler.start();
+        process.waitFor();
+    }
+
+    /**
+     * 调用sh脚本上传oss
+     */
+//    public static void ossUploadDir(String sceneCode, String uploadDir, String target){
+//
+//        String cmd = CmdConstant.OSSUTIL_UPLOAD_DIR;
+//        cmd = cmd.replaceAll("@sceneCode", sceneCode);
+//        cmd = cmd.replaceAll("@uploadDir", uploadDir);
+//        cmd = cmd.replaceAll("@target", target);
+//
+//        log.info("ossCmd: " + cmd);
+//        long start = System.currentTimeMillis();
+//        CmdUtils.callLineSh(cmd);
+//        long end = System.currentTimeMillis();
+//        log.info("场景码目录:{} 上传完成, 耗时:{} s" , sceneCode, (end-start)/1000 );
+//    }
+
+
+
+}

+ 81 - 0
src/main/java/com/fdkankan/common/util/CreateObjUtil.java

@@ -0,0 +1,81 @@
+package com.fdkankan.common.util;
+
+import com.fdkankan.common.constant.ConstantCmd;
+import com.fdkankan.model.proto.BigSceneProto;
+import com.google.protobuf.TextFormat;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+
+@Slf4j
+public class CreateObjUtil {
+
+	//开始建模
+	public static void build3dModel(String folderName,String isModel) throws Exception{
+		log.info("开始建模");
+		String command = ConstantCmd.BUILD_MODEL_COMMAND+folderName;
+		CmdUtils.callLineSh(command);
+		log.info("计算完毕:" + command);
+	}
+
+	/**
+	 * modeldata.txt 转 dam
+	 * @param srcpath 源文件全路径,如D:\test\modeldata.txt
+	 * @param despath 目标文件全路径,如D:\test\tieta.dam
+	 * @throws Exception
+	 */
+	public static void convertTxtToDam(String srcpath,String despath)throws Exception{
+		InputStream inputStream = null;
+		InputStreamReader reader = null;
+		ByteArrayInputStream stream = null;
+		BufferedOutputStream bos = null;
+		BufferedInputStream bis = null;
+		try{
+			BigSceneProto.binary_mesh.Builder builder = BigSceneProto.binary_mesh.newBuilder();
+			inputStream = new FileInputStream(srcpath);
+			reader = new InputStreamReader(inputStream, "ASCII");
+			TextFormat.merge(reader, builder);
+			byte[] buf = builder.build().toByteArray();
+
+			//把序列化后的数据写入本地磁盘
+			stream = new ByteArrayInputStream(buf);
+			bos = new BufferedOutputStream(new FileOutputStream(despath));//设置输出路径
+			bis = new BufferedInputStream(stream);
+			int b = -1;
+			while ((b = bis.read()) != -1) {
+				bos.write(b);
+			}
+		}catch (Exception e){
+			log.error("txt转dam出错,srcpath:{}, despath:{}",srcpath, despath);
+			throw e;
+		}finally {
+			if(inputStream != null){
+				inputStream.close();
+			}
+			if(reader != null){
+				reader.close();
+			}
+			if(stream != null){
+				stream.close();
+			}
+			if(bos != null){
+				bos.close();
+			}
+			if(bis != null){
+				bis.close();
+			}
+		}
+
+	}
+
+	//mp4文件转换成flv文件
+	public static void mp4ToFlv(String oldVideo, String newVideo) throws Exception{
+		String command = ConstantCmd.MP4_TO_FLV + " " + oldVideo + " " + newVideo;
+		log.info("mp4文件转换成flv文件");
+		CmdUtils.callLineSh(command);
+		log.info("mp4文件转换成flv文件完毕:" + command);
+	}
+
+
+
+}

+ 36 - 0
src/main/java/com/fdkankan/common/util/FileUtils.java

@@ -1,8 +1,13 @@
 package com.fdkankan.common.util;
 
+import cn.hutool.core.collection.CollUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
 public class FileUtils {
 
     private static Logger log = LoggerFactory.getLogger(FileUtils.class);
@@ -35,5 +40,36 @@ public class FileUtils {
 
     }
 
+    /**
+     * 获取目录下所有文件
+     */
+    public static List<String> getFileList(String directory) {
+
+        File f = new File(directory);
+
+        File[] files = f.listFiles();
+
+        if(files == null || files.length == 0){
+            return null;
+        }
+
+        List<String> list = new ArrayList<>();
+        for (int i = 0; i < files.length; i++) {
+
+            if (files[i].isFile()) {
+                list.add(files[i].getAbsolutePath());
+            } else {
+                System.out.println("目录:" + files[i]);
+                List<String> fileList = getFileList(files[i].getAbsolutePath());
+                if(CollUtil.isEmpty(fileList)){
+                    continue;
+                }
+                list.addAll(fileList);
+            }
+        }
+
+        return list;
+    }
+
 
 }

+ 79 - 0
src/main/java/com/fdkankan/common/util/StreamGobblerLine.java

@@ -0,0 +1,79 @@
+package com.fdkankan.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+
+@Slf4j
+public class StreamGobblerLine extends Thread {
+
+	InputStream is;
+    String type;
+    OutputStream os;
+    Integer lineSize;  // 多少行打印日志一次
+
+    public StreamGobblerLine(InputStream is, String type) {
+        this(is, type, null, null);
+    }
+
+    public StreamGobblerLine(InputStream is, String type, Integer lineSize) {
+        this(is, type, null, lineSize);
+    }
+
+    StreamGobblerLine(InputStream is, String type, OutputStream redirect, Integer lineSize) {
+        this.is = is;
+        this.type = type;
+        this.os = redirect;
+        this.lineSize = lineSize;
+    }
+
+    public void run() {
+        log.info("run StreamGobblerLine");
+
+        InputStreamReader isr = null;
+        BufferedReader br = null;
+        PrintWriter pw = null;
+        try {
+            if (os != null)
+                pw = new PrintWriter(os);
+
+            isr = new InputStreamReader(is);
+            br = new BufferedReader(isr);
+            String line=null;
+            int i = 1;
+            while ( (line = br.readLine()) != null) {
+                if (lineSize != null) {
+                    if (i % lineSize == 0) {
+                        log.info(type + "," + i +" : >" + line);
+                    }
+                } else {
+                    log.info(type + ","  + i +" : >" + line);
+                }
+                i++;
+
+            }
+
+            if (pw != null)
+                pw.flush();
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        } finally{
+            try {
+            	if(pw!=null)
+            	{
+            		 pw.close();
+            	}
+            	if(br!=null)
+            	{
+            		br.close();
+            	}
+            	if(isr!=null)
+            	{
+            		isr.close();
+            	}
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 138 - 2
src/main/java/com/fdkankan/scene/controller/SceneEditController.java

@@ -45,8 +45,8 @@ public class SceneEditController extends BaseController{
 //    private ISceneAsynOperLogService sceneAsynOperLogService;
     @Autowired
     private SceneEditInfoExtService sceneEditInfoExtService;
-//    @Autowired
-//    private ICutModelService cutModelService;
+    @Autowired
+    private ICutModelService cutModelService;
 
     /**
      * <p>
@@ -269,6 +269,64 @@ public class SceneEditController extends BaseController{
         return sceneEditInfoService.getSceneInfo(param);
     }
 
+    /**
+     * <p>
+            上传全景图
+     * </p>
+     * @author dengsixing
+     * @date 2022/2/16
+     * @param num
+     * @param file
+     * @return java.util.List<java.lang.String>
+     **/
+    @PostMapping(value = "/uploadPanorama")
+    public ResultData uploadPanorama(@RequestParam(value = "num") String num,
+        @RequestParam("file") MultipartFile file) throws Exception {
+        return sceneEditInfoService.uploadPanorama(num, this.getSubgroup(), this.getUpTime(), file);
+    }
+
+    /**
+     * <p>
+            下载全景图
+     * </p>
+     * @author dengsixing
+     * @date 2022/2/16
+     * @return java.util.List<java.lang.String>
+     **/
+    @PostMapping(value = "/downloadPanorama")
+    public ResultData downloadPanorama(@RequestBody @Validated FileParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return sceneEditInfoService.downloadPanorama(param);
+    }
+
+    /**
+     * <p>
+     下载模型
+     * </p>
+     * @author dengsixing
+     * @param num
+     * @return com.fdkankan.web.response.ResultData
+     **/
+    @PostMapping(value = "/downloadModel")
+    public ResultData downloadModel(@RequestParam("num") String num) throws Exception {
+        return sceneProService.downloadModel(num, this.getSubgroup(), this.getUpTime());
+    }
+
+    /**
+     * <p>
+     上传模型
+     * </p>
+     * @author dengsixing
+     * @param num
+     * @param file
+     * @return com.fdkankan.web.response.ResultData
+     **/
+    @PostMapping(value = "/uploadModel")
+    public ResultData uploadModel(@RequestParam("num") String num, @RequestParam("file") MultipartFile file) throws Exception {
+        return sceneProService.uploadModel(num, this.getSubgroup(), this.getUpTime(), file);
+    }
+
     @RequestMapping(value = "/upload/files", method = RequestMethod.POST)
     public String uploads(@RequestParam(value = "base64",required = false) String base64,
         @RequestParam(value = "fileName",required = false) String fileName,
@@ -534,6 +592,84 @@ public class SceneEditController extends BaseController{
         return ResultData.ok(sceneEditInfoExtService.deleteBillboardsStyles(param));
     }
 
+        /**
+     * <p>
+        保存视频盒子
+     * </p>
+     * @author dengsixing
+     **/
+    @PostMapping(value = "/video/box/save")
+    public ResultData saveVideoBox(@RequestBody @Validated FileNameAndDataParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return sceneEditInfoService.saveVideoBox(param);
+    }
+
+    /**
+     * <p>
+        删除视频盒子
+     * </p>
+     **/
+    @PostMapping(value = "/video/box/delete")
+    public ResultData deleteVideoBox(@RequestBody @Validated DeleteSidParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return sceneEditInfoService.deleteVideoBox(param);
+    }
+    /**
+     * <p>
+     保存空间贴图
+     * </p>
+     **/
+    @PostMapping(value = "/photo/box/save")
+    public ResultData savePhotoBox(@RequestBody @Validated BaseDataParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return sceneEditInfoService.saveBoxPhoto(param);
+    }
+
+    /**
+     * <p>
+     删除空间贴图
+     * </p>
+     **/
+    @PostMapping(value = "/photo/box/delete")
+    public ResultData deletePhotoBox(@RequestBody @Validated DeleteSidParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return sceneEditInfoService.deleteBoxPhoto(param);
+    }
+
+    /**
+     * 保存裁剪模型
+     */
+    @PostMapping(value = "/cutModel/save")
+    public ResultData saveCutModel(@RequestBody @Validated BaseJsonArrayParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return cutModelService.saveCutModel(param);
+    }
+
+    /**
+     * 裁剪模型列表
+     */
+    @PostMapping(value = "/cutModel/list")
+    public ResultData listCutModel(@RequestBody @Validated BaseSceneParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return ResultData.ok(cutModelService.listCutModel(param));
+    }
+
+    /**
+     * 删除裁剪模型
+     */
+    @PostMapping(value = "/cutModel/delete")
+    public ResultData deleteCutModel(@RequestBody @Validated DeleteSidListParamVO param) throws Exception {
+        param.setSubgroup(this.getSubgroup());
+        param.setUpTimeKey(this.getUpTime());
+        return cutModelService.deleteCutModel(param);
+    }
+
 
 
 }

+ 111 - 0
src/main/java/com/fdkankan/scene/entity/SceneAsynOperLog.java

@@ -0,0 +1,111 @@
+package com.fdkankan.scene.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ * 场景异步操作记录表
+ * </p>
+ *
+ * @author
+ * @since 2025-02-17
+ */
+@Getter
+@Setter
+@TableName("t_scene_asyn_oper_log")
+public class SceneAsynOperLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 场景码
+     */
+    @TableField("num")
+    private String num;
+
+    /**
+     * 操作类型(upload-上传,download-下载)
+     */
+    @TableField("oper_type")
+    private String operType;
+
+    /**
+     * 模块名称
+     */
+    @TableField("module")
+    private String module;
+
+    /**
+     * 功能
+     */
+    @TableId(value = "func", type = IdType.AUTO)
+    private String func;
+
+    /**
+     * 版本号
+     */
+    @TableField("version")
+    private Integer version;
+
+    /**
+     * 状态(0-处理中,1-处理完成,2-处理失败)
+     */
+    @TableField("state")
+    private Integer state;
+
+    /**
+     * 下载链接
+     */
+    @TableField("url")
+    private String url;
+
+    /**
+     * 是否需要弹窗(0-否,1-是)
+     */
+    @TableField("pop")
+    private Integer pop;
+
+    /**
+     * 扩展信息
+     */
+    @TableField("ext_data")
+    private String extData;
+
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private Date createTime;
+
+    /**
+     * 修改时间
+     */
+    @TableField("update_time")
+    private Date updateTime;
+
+    /**
+     * 状态(A-有效,I-无效)
+     */
+    @TableField("rec_status")
+    @TableLogic(value = "A", delval = "I")
+    private String recStatus;
+
+    /**
+     * 场景id
+     */
+    @TableField("scene_id")
+    private Long sceneId;
+
+
+}

+ 1 - 1
src/main/java/com/fdkankan/scene/generator/AutoGenerate.java

@@ -21,7 +21,7 @@ public class AutoGenerate {
         String path =System.getProperty("user.dir");
 
         generate(path,"scene.generator", getTables(new String[]{
-                "t_scene_convert_log"
+                "t_scene_asyn_oper_log"
         }));
 
 //        generate(path,"goods", getTables(new String[]{

+ 18 - 0
src/main/java/com/fdkankan/scene/mapper/SceneAsynOperLogMapper.java

@@ -0,0 +1,18 @@
+package com.fdkankan.scene.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fdkankan.scene.entity.SceneAsynOperLog;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 场景异步操作记录表 Mapper 接口
+ * </p>
+ *
+ * @author
+ * @since 2025-02-17
+ */
+@Mapper
+public interface SceneAsynOperLogMapper extends BaseMapper<SceneAsynOperLog> {
+
+}

+ 5 - 0
src/main/java/com/fdkankan/scene/service/FYunFileService.java

@@ -1,6 +1,7 @@
 package com.fdkankan.scene.service;
 
 import java.io.IOException;
+import java.util.Map;
 
 public interface FYunFileService {
 
@@ -14,4 +15,8 @@ public interface FYunFileService {
 
     String downloadFile(String num, Integer subgroup, String upTime, String key, String dir, String fileName);
 
+    void uploadMulFiles(String num, Integer subgroup, String upTime, Map<String, String> map);
+
+
+
 }

+ 9 - 0
src/main/java/com/fdkankan/scene/service/ICommonService.java

@@ -0,0 +1,9 @@
+package com.fdkankan.scene.service;
+
+public interface ICommonService {
+
+    void transferToFlv(String num, Integer subgroup, String upTime, Integer cacheKeyHasTime, String fileName) throws Exception;
+
+
+
+}

+ 22 - 0
src/main/java/com/fdkankan/scene/service/ICutModelService.java

@@ -0,0 +1,22 @@
+package com.fdkankan.scene.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fdkankan.scene.bean.ResultData;
+import com.fdkankan.scene.vo.BaseJsonArrayParamVO;
+import com.fdkankan.scene.vo.BaseSceneParamVO;
+import com.fdkankan.scene.vo.DeleteSidListParamVO;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface ICutModelService {
+
+    ResultData saveCutModel(BaseJsonArrayParamVO param) throws Exception;
+
+    List<JSONObject> listCutModel(BaseSceneParamVO param) throws Exception;
+
+    ResultData deleteCutModel(DeleteSidListParamVO param) throws Exception;
+
+    void publicCutModel(String sceneNum, Integer subgroup, String upTimeKey) throws IOException;
+
+}

+ 5 - 4
src/main/java/com/fdkankan/scene/service/ISceneProService.java

@@ -2,6 +2,7 @@ package com.fdkankan.scene.service;
 
 import com.fdkankan.scene.bean.ResultData;
 import com.fdkankan.scene.vo.*;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * <p>
@@ -28,10 +29,10 @@ public interface ISceneProService{
     ResultData saveTagsVisible(SaveTagsVisibleParamVO param) throws Exception;
 //
 //    void updateUserIdByCameraId(Long userId, Long cameraId);
-//
-//    ResultData uploadModel(String num, MultipartFile file) throws Exception;
-//
-//    ResultData downloadModel(String num) throws Exception;
+
+    ResultData uploadModel(String num,  Integer subgroup, String upTime, MultipartFile file) throws Exception;
+
+    ResultData downloadModel(String num, Integer subgroup, String upTimeKey) throws Exception;
 //
 //    ScenePro getByNum(String num);
 //

+ 26 - 0
src/main/java/com/fdkankan/scene/service/SceneAsynOperLogService.java

@@ -0,0 +1,26 @@
+package com.fdkankan.scene.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fdkankan.scene.bean.ResultData;
+import com.fdkankan.scene.entity.SceneAsynOperLog;
+import com.fdkankan.scene.vo.SceneAsynOperLogParamVO;
+
+/**
+ * <p>
+ * 场景异步操作记录表 服务类
+ * </p>
+ *
+ * @author
+ * @since 2025-02-17
+ */
+public interface SceneAsynOperLogService extends IService<SceneAsynOperLog> {
+
+    ResultData getAsynOperLog(SceneAsynOperLogParamVO param);
+
+    void cleanDownloadOssPage(String asynFuncType, int preMonth);
+
+    void checkSceneAsynOper(Long sceneId, String operType, String module, String function);
+
+    void cleanLog(Long sceneId, String moduleType, String funcType, String... operTypes);
+
+}

+ 13 - 13
src/main/java/com/fdkankan/scene/service/SceneEditInfoService.java

@@ -41,26 +41,26 @@ public interface SceneEditInfoService extends IService<SceneEditInfo> {
     ResultData resetCad(String num, Integer subgroup, String upTime) throws IOException;
 
     ResultData renameCad(RenameCadParamVO param) throws IOException;
-//
+
     void upgradeVersionById(Long id);
 
     void upgradeVersionAndImgVersionById(Long id);
 
     void upgradeSceneJsonVersion(String num, Integer subgroup, String upTime, Integer cacheKeyHasTime, int version, Integer imgVersion) throws IOException ;
-//
-//    ResultData uploadPanorama(String num, MultipartFile file) throws Exception;
-//
-//    ResultData downloadPanorama(FileParamVO param) throws Exception;
+
+    ResultData uploadPanorama(String num, Integer subgroup, String upTime, MultipartFile file) throws Exception;
+
+    ResultData downloadPanorama(FileParamVO param) throws Exception;
 
     void saveTagsToSceneEditInfo(String num, Integer subgroup, String upTime, Integer cacheKeyHasTime, SceneEditInfo sceneEditInfo);
-//
-//    ResultData saveVideoBox(FileNameAndDataParamVO param) throws Exception;
-//
-//    ResultData deleteVideoBox(DeleteSidParamVO param) throws Exception;
-//
-//    ResultData saveBoxPhoto(BaseDataParamVO param) throws Exception;
-//
-//    ResultData deleteBoxPhoto(DeleteSidParamVO param) throws Exception;
+
+    ResultData saveVideoBox(FileNameAndDataParamVO param) throws Exception;
+
+    ResultData deleteVideoBox(DeleteSidParamVO param) throws Exception;
+
+    ResultData saveBoxPhoto(BaseDataParamVO param) throws Exception;
+
+    ResultData deleteBoxPhoto(DeleteSidParamVO param) throws Exception;
 //
 //    DownloadVO downloadBallScreenVideo(BallScreenVideoParamVO param);
 //

+ 4 - 0
src/main/java/com/fdkankan/scene/service/SceneFileMappingService.java

@@ -3,6 +3,8 @@ package com.fdkankan.scene.service;
 import com.fdkankan.scene.entity.SceneFileMapping;
 import com.baomidou.mybatisplus.extension.service.IService;
 
+import java.util.List;
+
 /**
  * <p>
  *  服务类
@@ -17,4 +19,6 @@ public interface SceneFileMappingService extends IService<SceneFileMapping> {
 
     void delByNumAndKey(String num, Integer subgroup, String upTime, String key);
 
+    List<SceneFileMapping> getByScene(String num, Integer subgroup, String upTime);
+
 }

+ 49 - 0
src/main/java/com/fdkankan/scene/service/impl/CommonServiceImpl.java

@@ -0,0 +1,49 @@
+package com.fdkankan.scene.service.impl;
+
+import cn.hutool.core.io.FileUtil;
+import com.fdkankan.common.constant.ConstantFilePath;
+import com.fdkankan.common.constant.UploadFilePath;
+import com.fdkankan.common.util.CreateObjUtil;
+import com.fdkankan.redis.constant.RedisKey;
+import com.fdkankan.scene.service.FYunFileService;
+import com.fdkankan.scene.service.ICommonService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.File;
+
+@Service
+public class CommonServiceImpl implements ICommonService {
+
+    @Resource
+    private FYunFileService fYunFileService;
+
+    @Override
+    public void transferToFlv(String num, Integer subgroup, String upTime, Integer cacheKeyHasTime, String fileName) throws Exception {
+        String userEditPath = String.format(UploadFilePath.USER_VIEW_PATH, num);
+        String numStr = RedisKey.getNumStr(num, subgroup, upTime, cacheKeyHasTime);
+        String localImagesPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, numStr);
+        String localFilePath = localImagesPath + fileName;
+
+        File targetFile = new File(localImagesPath);
+        if (!targetFile.exists()){
+            targetFile.mkdirs();
+        }
+
+        targetFile = new File(localFilePath);
+        if (targetFile.exists()){
+            FileUtil.del(localFilePath);
+        }
+
+        //从用户编辑目录中下载视频到本地
+        String filePath = userEditPath + fileName;
+        fYunFileService.downloadFile(num, subgroup, upTime, filePath, localImagesPath, fileName);
+
+        //视频格式转换
+        CreateObjUtil.mp4ToFlv(localFilePath, localFilePath.replace("mp4", "flv"));
+
+        //上传
+        String flvFileName = fileName.replace("mp4", "flv");
+        fYunFileService.uploadFile(num, subgroup, upTime, localFilePath.replace("mp4", "flv"), userEditPath + flvFileName);
+    }
+}

+ 181 - 0
src/main/java/com/fdkankan/scene/service/impl/CutModelServiceImpl.java

@@ -0,0 +1,181 @@
+package com.fdkankan.scene.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.fdkankan.common.constant.CommonStatus;
+import com.fdkankan.common.constant.UploadFilePath;
+import com.fdkankan.redis.constant.RedisKey;
+import com.fdkankan.redis.util.RedisClient;
+import com.fdkankan.scene.bean.ResultData;
+import com.fdkankan.scene.bean.TagBean;
+import com.fdkankan.scene.entity.Scene;
+import com.fdkankan.scene.entity.SceneEditInfoExt;
+import com.fdkankan.scene.service.*;
+import com.fdkankan.scene.vo.BaseJsonArrayParamVO;
+import com.fdkankan.scene.vo.BaseSceneParamVO;
+import com.fdkankan.scene.vo.DeleteSidListParamVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class CutModelServiceImpl implements ICutModelService {
+
+    private final String CUT_MODEL_JSON_NAME = "cutModel.json";
+
+    @Autowired
+    private SceneService scenePlusService;
+    @Autowired
+    private SceneEditInfoExtService sceneEditInfoExtService;
+    @Autowired
+    private SceneEditInfoService sceneEditInfoService;
+    @Resource
+    private RedisClient redisClient;
+    @Autowired
+    private FYunFileService fYunFileService;
+    @Autowired
+    private ISceneUploadService sceneUploadService;
+
+
+    @Override
+    public ResultData saveCutModel(BaseJsonArrayParamVO param) throws Exception {
+        Scene scenePlus = scenePlusService.getByNum(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+
+        //更新缓存
+        this.addOrUpdate(param.getNum() ,param.getSubgroup(), param.getUpTimeKey(), param.getData());
+
+        //保存数据库
+        SceneEditInfoExt sceneEditInfoExt = sceneEditInfoExtService.getByScenePlusId(scenePlus.getId());
+        this.updateDb(param.getNum() ,param.getSubgroup(), param.getUpTimeKey(), scenePlus.getId());
+
+        sceneEditInfoService.upgradeVersionById(sceneEditInfoExt.getEditInfoId());
+
+        //发布
+        this.publicCutModel(param.getNum() ,param.getSubgroup(), param.getUpTimeKey());
+
+        return ResultData.ok();
+    }
+
+    @Override
+    public List<JSONObject> listCutModel(BaseSceneParamVO param) throws Exception {
+
+        List<JSONObject> tags = new ArrayList<>();
+        Scene scenePlus = scenePlusService.getByNum(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+
+        //获取裁剪模型数据
+        String numStr = RedisKey.getNumStr(param.getNum(), param.getSubgroup(), param.getUpTimeKey(), 1);
+        String key = String.format(RedisKey.SCENE_CUT_MODEL, numStr);
+        List<String> list = redisClient.hgetValues(key);
+        if(CollUtil.isNotEmpty(list)){
+            List<TagBean> sortList = list.stream().map(str -> {
+                JSONObject jsonObject = JSON.parseObject(str);
+                TagBean tagBean = new TagBean();
+                tagBean.setCreateTime(jsonObject.getLong("createTime"));
+                jsonObject.remove("createTime");
+                tagBean.setTag(jsonObject);
+                return tagBean;
+            }).collect(Collectors.toList());
+            sortList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed());
+            tags = sortList.stream().map(item -> item.getTag()).collect(Collectors.toList());
+        }
+
+        return tags;
+    }
+
+    @Override
+    public ResultData deleteCutModel(DeleteSidListParamVO param) throws Exception {
+        Scene scenePlus = scenePlusService.getByNum(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+
+        List<String> deleteSidList = param.getSidList();
+
+        //处理删除状态数据
+        List<String> deleteList = this.deleteCutModel(param.getNum(), param.getSubgroup(), param.getUpTimeKey(), deleteSidList);
+
+        //保存数据库
+        SceneEditInfoExt sceneEditInfoExt = sceneEditInfoExtService.getByScenePlusId(scenePlus.getId());
+        this.updateDb(param.getNum(), param.getSubgroup(), param.getUpTimeKey(), scenePlus.getId());
+        sceneEditInfoService.upgradeVersionById(sceneEditInfoExt.getEditInfoId());
+
+        //发布
+        this.publicCutModel(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+
+        return ResultData.ok();
+    }
+
+    @Override
+    public void publicCutModel(String sceneNum, Integer subgroup, String upTimeKey) throws IOException {
+        String numStr = RedisKey.getNumStr(sceneNum, subgroup, upTimeKey, 1);
+        String Key = String.format(RedisKey.SCENE_CUT_MODEL, numStr);
+        String userEditPath = String.format(UploadFilePath.USER_VIEW_PATH, sceneNum) + "cutModel.json";
+        List<String> list = redisClient.hgetValues(Key);
+        List<JSONObject> collect = list.stream().map(str -> JSON.parseObject(str)).collect(Collectors.toList());
+        fYunFileService.uploadFile(sceneNum, subgroup, upTimeKey, JSON.toJSONString(collect).getBytes(), userEditPath);
+    }
+
+    private List<String> deleteCutModel(String num, Integer subgroup, String upTimeKey, List<String> deleteSidList) throws Exception {
+        if(CollUtil.isEmpty(deleteSidList)){
+            return null;
+        }
+
+        //从redis中加载热点数据
+        String numStr = RedisKey.getNumStr(num, subgroup, upTimeKey, 1);
+        String key = String.format(RedisKey.SCENE_CUT_MODEL, numStr);
+        List<String> deletDataList = redisClient.hMultiGet(key, deleteSidList);
+        if(CollUtil.isNotEmpty(deletDataList)){
+            redisClient.hdel(key, deleteSidList);
+        }
+        return deletDataList;
+    }
+
+    private void updateDb(String num, Integer subgroup, String upTime,  Long scenePlusId){
+        //查询缓存是否包含热点数据
+        String numStr = RedisKey.getNumStr(num, subgroup, upTime, 1);
+        String key = String.format(RedisKey.SCENE_CUT_MODEL, numStr);
+        Map<String, String> cutModelMap = redisClient.hmget(key);
+        boolean hasCutModel= false;
+        for (Map.Entry<String, String> tagMap : cutModelMap.entrySet()) {
+            if(StrUtil.isEmpty(tagMap.getValue())){
+                continue;
+            }
+            hasCutModel = true;
+            break;
+        }
+
+        //更改热点状态
+        sceneEditInfoExtService.update(
+                new LambdaUpdateWrapper<SceneEditInfoExt>()
+                        .set(SceneEditInfoExt::getCutModel, hasCutModel ? CommonStatus.YES.code().intValue() : CommonStatus.NO.code().intValue())
+                        .eq(SceneEditInfoExt::getScenePlusId,scenePlusId));
+    }
+
+    private void addOrUpdate(String num, Integer subgroup, String upTimeKey, List<JSONObject> data) throws Exception{
+        Map<String, String> addOrUpdateMap = new HashMap<>();
+        int i = 0;
+        for (JSONObject jsonObject : data) {
+            jsonObject.put("createTime", Calendar.getInstance().getTimeInMillis() + i++);
+            addOrUpdateMap.put(jsonObject.getString("sid"), JSON.toJSONString(jsonObject));
+        }
+
+        //处理新增和修改数据
+        this.addOrUpdateHandler(num, subgroup, upTimeKey, addOrUpdateMap);
+    }
+
+    private void addOrUpdateHandler(String num, Integer subgroup, String upTimeKey, Map<String, String> addOrUpdateMap) {
+        if (CollUtil.isEmpty(addOrUpdateMap))
+            return;
+
+        //批量写入缓存
+        String key = String.format(RedisKey.SCENE_CUT_MODEL, RedisKey.getNumStr(num, subgroup, upTimeKey, 1));
+        redisClient.hmset(key, addOrUpdateMap);
+
+
+    }
+
+}

+ 13 - 0
src/main/java/com/fdkankan/scene/service/impl/FYunFileServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fdkankan.scene.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.lang.UUID;
@@ -103,4 +104,16 @@ public class FYunFileServiceImpl implements FYunFileService {
         }
          return dir + fileName;
     }
+
+    @Override
+    public void uploadMulFiles(String num, Integer subgroup, String upTime, Map<String, String> map) {
+        if(CollUtil.isEmpty(map)){
+            return;
+        }
+        map.keySet().stream().forEach(localPath->{
+            String  key = map.get(localPath);
+            this.uploadFile(num, subgroup, upTime, localPath, key);
+        });
+
+    }
 }

+ 152 - 0
src/main/java/com/fdkankan/scene/service/impl/SceneAsynOperLogServiceImpl.java

@@ -0,0 +1,152 @@
+package com.fdkankan.scene.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+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.scene.bean.ResultData;
+import com.fdkankan.scene.entity.SceneAsynOperLog;
+import com.fdkankan.scene.mapper.SceneAsynOperLogMapper;
+import com.fdkankan.scene.service.FYunFileService;
+import com.fdkankan.scene.service.SceneAsynOperLogService;
+import com.fdkankan.scene.vo.SceneAsynOperLogParamVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 场景异步操作记录表 服务实现类
+ * </p>
+ *
+ * @author
+ * @since 2025-02-17
+ */
+@Slf4j
+@Service
+public class SceneAsynOperLogServiceImpl extends ServiceImpl<SceneAsynOperLogMapper, SceneAsynOperLog> implements SceneAsynOperLogService {
+
+    @Autowired
+    private FYunFileService fYunFileService;
+
+    /**
+     * 根据条件获取特定业务类型异步操作记录
+     */
+    @Override
+    public ResultData getAsynOperLog(SceneAsynOperLogParamVO param) {
+
+        LambdaQueryWrapper<SceneAsynOperLog> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(SceneAsynOperLog::getNum, param.getNum());
+        if(StrUtil.isNotEmpty(param.getOperType())){
+            queryWrapper.eq(SceneAsynOperLog::getOperType, param.getOperType());
+        }
+        if(StrUtil.isNotEmpty(param.getModule())){
+            queryWrapper.eq(SceneAsynOperLog::getModule, param.getModule());
+        }
+        if(StrUtil.isNotEmpty(param.getFunc())){
+            queryWrapper.eq(SceneAsynOperLog::getFunc, param.getFunc());
+        }
+
+        //需要弹窗的异步操作列表
+        List<SceneAsynOperLog> list = this.list(queryWrapper);
+
+        //如果列表中有需要弹窗并且处理完毕的,需要把这些数据改为不需要弹窗了,因为前端请求到之后就会弹出提示,下次不需要弹窗了
+        if(CollUtil.isNotEmpty(list)){
+            List<Long> idList = list.stream().filter(log -> {
+                if(!log.getState().equals(CommonOperStatus.WAITING.code())){
+                    return true;
+                }
+                return false;
+            }).map(log->log.getId()).collect(Collectors.toList());
+            if(CollUtil.isNotEmpty(idList)){
+                this.update(new LambdaUpdateWrapper<SceneAsynOperLog>()
+                        .set(SceneAsynOperLog::getPop, CommonStatus.NO.code())
+                        .in(SceneAsynOperLog::getId, idList));
+            }
+        }
+
+        return ResultData.ok(list);
+    }
+
+    @Override
+    public void cleanDownloadOssPage(String asynFuncType, int preMonth) {
+
+        List<SceneAsynOperLog> downloadList = this.list(
+                new LambdaQueryWrapper<SceneAsynOperLog>()
+                        .eq(SceneAsynOperLog::getOperType, SceneAsynOperType.DOWNLOAD.code())
+                        .eq(SceneAsynOperLog::getModule, SceneAsynModuleType.UPLOAD_DOWNLOAD.code())
+                        .eq(SceneAsynOperLog::getFunc, asynFuncType));
+        if(CollUtil.isEmpty(downloadList)){
+            return;
+        }
+        DateTime preDate = DateUtil.offsetMonth(Calendar.getInstance().getTime(), -preMonth);
+        List<SceneAsynOperLog> deleteList = downloadList.parallelStream().filter(log -> {
+            if (log.getCreateTime().before(preDate)) {
+                return true;
+            }
+            return false;
+        }).collect(Collectors.toList());
+        if(CollUtil.isEmpty(deleteList)){
+            return;
+        }
+
+        //删除数据库记录
+        List<Long> deleteIdList = deleteList.parallelStream().map(item -> item.getId()).collect(Collectors.toList());
+        this.removeByIds(deleteIdList);
+
+        deleteList.parallelStream().forEach(item -> {
+            if(StrUtil.isNotEmpty(item.getUrl())){
+//                try {
+////                    fYunFileService.deleteFile(item.getUrl());
+//                } catch (IOException e) {
+//                    log.warn("删除oss下载压缩包失败,key:{}", item.getUrl());
+//                }
+            }
+        });
+
+    }
+
+    @Override
+    public void checkSceneAsynOper(Long sceneId, String operType, String module, String function) {
+        LambdaQueryWrapper<SceneAsynOperLog> queryWrapper =
+                new LambdaQueryWrapper<SceneAsynOperLog>()
+                        .eq(SceneAsynOperLog::getSceneId,sceneId)
+                        .eq(SceneAsynOperLog::getState, CommonOperStatus.WAITING.code());
+        if(StrUtil.isNotEmpty(operType)){
+            queryWrapper.eq(SceneAsynOperLog::getOperType, operType);
+        }
+        if(StrUtil.isNotEmpty(module)){
+            queryWrapper.eq(SceneAsynOperLog::getModule, module);
+        }
+        if(StrUtil.isNotEmpty(function)){
+            queryWrapper.eq(SceneAsynOperLog::getFunc, function);
+        }
+        List<SceneAsynOperLog> waittingLogList = this.list(queryWrapper);
+        if(CollUtil.isNotEmpty(waittingLogList)){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5066);
+        }
+    }
+
+    @Override
+    public void cleanLog(Long sceneId, String moduleType, String funcType, String... operTypes) {
+        LambdaQueryWrapper<SceneAsynOperLog> wrapper = new LambdaQueryWrapper<SceneAsynOperLog>()
+                .eq(SceneAsynOperLog::getSceneId, sceneId)
+                .eq(SceneAsynOperLog::getModule, moduleType)
+                .eq(SceneAsynOperLog::getFunc, funcType);
+        if(ArrayUtil.isNotEmpty(operTypes)){
+            wrapper.in(SceneAsynOperLog::getOperType, operTypes);
+        }
+        this.remove(wrapper);
+    }
+}

+ 552 - 0
src/main/java/com/fdkankan/scene/service/impl/SceneEditInfoServiceImpl.java

@@ -2,20 +2,26 @@ package com.fdkankan.scene.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.img.ImgUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.CharsetUtil;
 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.dynamic.datasource.annotation.DS;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 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.redis.constant.RedisKey;
 import com.fdkankan.redis.util.RedisClient;
+import com.fdkankan.scene.bean.BoxPhotoBean;
 import com.fdkankan.scene.bean.ResultData;
 import com.fdkankan.scene.bean.SceneJsonBean;
 import com.fdkankan.scene.bean.TagBean;
@@ -29,12 +35,15 @@ 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 javax.annotation.Resource;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 /**
@@ -65,6 +74,10 @@ public class SceneEditInfoServiceImpl extends ServiceImpl<SceneEditInfoMapper, S
     private SceneFileMappingService sceneFileMappingService;
     @Resource
     CustomHttpClient customHttpClient;
+    @Autowired
+    private SceneAsynOperLogService sceneAsynOperLogService;
+    @Autowired
+    private ICommonService commonService;
 
     /**
      * 保存场景基础设置
@@ -491,6 +504,288 @@ public class SceneEditInfoServiceImpl extends ServiceImpl<SceneEditInfoMapper, S
     }
 
     @Override
+    public ResultData uploadPanorama(String num, Integer subgroup, String upTime, MultipartFile file) throws Exception {
+
+        if(!file.getOriginalFilename().endsWith(".zip") && !file.getOriginalFilename().endsWith(".jpg")){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_7007, "jpg或者zip");
+        }
+
+        Scene scenePlus = sceneService.getByNum(num, subgroup, upTime);
+        if(scenePlus == null){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+        }
+
+        //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
+        sceneAsynOperLogService.checkSceneAsynOper(scenePlus.getId(), null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.PANORAMIC_IMAGE.code());
+
+        //清除全景图异步操作记录,防止再次下载的时候请求到旧的压缩包
+        sceneAsynOperLogService.cleanLog(scenePlus.getId(), SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.PANORAMIC_IMAGE.code());
+
+        //原始计算根目录
+        String numStr = RedisKey.getNumStr(num, subgroup, upTime, scenePlus.getCacheKeyHasTime());
+        String path = String.format(ConstantFilePath.SCENE_USER_PATH_V4, numStr);
+        //全景图计算根目录
+        String target = path + "_images";
+        //解压缩文件存放目录
+        String targetImagesPath = target + "/extras/images/";
+        //压缩文件保存目录
+        String zipTargetFilePath = targetImagesPath + file.getOriginalFilename();
+
+        //先删除本地文件
+        FileUtil.del(targetImagesPath);
+        File targetFile = new File(zipTargetFilePath);
+        if(!targetFile.getParentFile().exists()){
+            targetFile.getParentFile().mkdirs();
+        }
+        file.transferTo(targetFile);
+
+        //如果是压缩包上传,需要解压缩
+        int async = CommonStatus.NO.code();
+        if(file.getOriginalFilename().endsWith(".zip")){
+
+            //标记为异步处理
+            async = CommonStatus.YES.code();
+
+            //解压zip包
+            ZipUtil.unzip(zipTargetFilePath,targetImagesPath, CharsetUtil.CHARSET_GBK);
+            //删除压缩包
+            FileUtil.del(zipTargetFilePath);
+        }
+
+        //判断文件夹目录结构,图片必须放在压缩包根目录下,不支持空文件夹或其他格式文件上传
+        File[] files = new File(targetImagesPath).listFiles();
+        Arrays.stream(files).forEach(item->{
+            if(item.isDirectory()){
+                throw new BusinessException(ErrorCode.FAILURE_CODE_7018);
+            }
+        });
+
+        //获取解压后的文件列表
+        List<String> uploadFileList = FileUtils.getFileList(targetImagesPath);
+        if(CollUtil.isEmpty(uploadFileList)){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5062);
+        }
+
+        //判断是否有可用的jpg文件
+        boolean existJpg = false;
+        if(CollUtil.isNotEmpty(uploadFileList)){
+            existJpg = uploadFileList.stream().anyMatch(str -> {
+                if(str.endsWith(".jpg")){
+                    return true;
+                }
+                return false;
+            });
+        }
+        if(!existJpg){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5062);
+        }
+
+        //比对图片列表,不存在的要返回名称集合
+        List<SceneFileMapping> sceneFileMappings = sceneFileMappingService.getByScene(num, subgroup, upTime);
+        List<SceneFileMapping> panList = sceneFileMappings.stream().filter(v -> v.getKey().contains("images/pan/high/")).collect(Collectors.toList());
+        Map<String, SceneFileMapping> panMap = panList.stream().collect(Collectors.toMap(v -> FileUtil.getName(v.getKey()), v -> v));
+        List<String> panoramaImageList = panList.stream().map(v -> FileUtil.getName(v.getKey())).collect(Collectors.toList());
+
+        List<String> notExistFileList = uploadFileList.stream().filter(filePath -> {
+            filePath = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
+            if(CollUtil.isEmpty(panoramaImageList) || panoramaImageList.contains(filePath)){
+                return false;
+            }
+            return true;
+        }).collect(Collectors.toList());
+
+        if(CollUtil.isNotEmpty(notExistFileList)){
+            //删除错误文件
+            notExistFileList.parallelStream().forEach(filePath->{
+                FileUtil.del(filePath);
+            });
+        }
+
+        //判断成功的图片,如果成功图片为0,就直接返回,不需要执行算法
+        uploadFileList = FileUtils.getFileList(targetImagesPath);
+        if(CollUtil.isEmpty(uploadFileList)){
+            if(CollUtil.isNotEmpty(notExistFileList)){
+                notExistFileList = notExistFileList.stream().map(filePath -> {
+                    return filePath.substring(filePath.lastIndexOf(File.separator) + 1);
+                }).collect(Collectors.toList());
+            }
+            return ResultData.ok(new UploadPanoramaVO(0,0, notExistFileList));
+        }
+
+        //上传
+        Map<String, String> map = new HashMap<>();
+
+        String imgViewPath = String.format(UploadFilePath.IMG_VIEW_PATH, num);
+
+        //如果部分成功,则需要返回成功数量和失败列表
+        if(CollUtil.isNotEmpty(notExistFileList)){
+            notExistFileList = notExistFileList.stream().map(filePath -> {
+                return filePath.substring(filePath.lastIndexOf(File.separator) + 1);
+            }).collect(Collectors.toList());
+        }
+
+        UploadPanoramaVO uploadPanoramaVO = new UploadPanoramaVO();
+        uploadPanoramaVO.setAsyn(async);
+        if(async == CommonStatus.YES.code().intValue()){
+            List<String> finalUploadFileList = uploadFileList;
+            List<String> finalNotExistFileList = notExistFileList;
+            CompletableFuture.runAsync(() -> {
+                SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
+                sceneAsynOperLog.setNum(num);
+                sceneAsynOperLog.setOperType(SceneAsynOperType.UPLOAD.code());
+                sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
+                sceneAsynOperLog.setFunc(SceneAsynFuncType.PANORAMIC_IMAGE.code());
+                if(CollUtil.isNotEmpty(finalNotExistFileList)){
+                    Map<String, Object> extData = new HashMap<>();
+                    extData.put("successCnt", finalUploadFileList.size());
+                    extData.put("failList", finalNotExistFileList);
+                    sceneAsynOperLog.setExtData(JSON.toJSONString(extData));
+                }
+                sceneAsynOperLogService.save(sceneAsynOperLog);
+                try {
+                    this.uploadPanoramaHandler(num,subgroup,upTime,target,imgViewPath, finalUploadFileList,targetImagesPath);
+                    sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
+                } catch (Exception e) {
+                    log.error("上传全景图报错,num:" + num, e);
+                    sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
+                }
+                sceneAsynOperLogService.updateById(sceneAsynOperLog);
+            });
+        }else{
+            this.uploadPanoramaHandler(num,subgroup,upTime,target,imgViewPath,uploadFileList,targetImagesPath);
+            if(CollUtil.isNotEmpty(notExistFileList)){
+                uploadPanoramaVO.setSuccessCnt(uploadFileList.size());
+                uploadPanoramaVO.setFailList(notExistFileList);
+            }
+        }
+
+        FileUtil.del(target);
+        return ResultData.ok(uploadPanoramaVO);
+    }
+
+    public void uploadPanoramaHandler(String num, Integer subgroup, String upTime, String target, String imgViewPath, List<String> uploadFileList, String targetImagesPath) throws Exception {
+
+        Map<String, String> map = new HashMap<>();
+        String resultPath = target + File.separator + "results/";
+        FileUtil.mkdir(resultPath);
+
+        //4K
+        String highPath = resultPath + "pan/high/";
+        //512
+        String lowPath = resultPath + "pan/low/";
+        List<String> origImgs = FileUtil.listFileNames(targetImagesPath);
+        for (String origImg : origImgs) {
+            FileUtil.copy(targetImagesPath + origImg, highPath + origImg, true);
+            map.put(highPath + origImg, imgViewPath + "pan/high/" + origImg);
+            ImgUtil.scale(new File(highPath + origImg), new File(lowPath + origImg), 0.125f);
+            map.put(lowPath + origImg, imgViewPath + "pan/low/" + origImg);
+        }
+
+        if(map.size()>0) {
+            fYunFileService.uploadMulFiles(num, subgroup, upTime, map);
+        }
+
+        //更新数据库版本号
+        Scene scenePlus = sceneService.getByNum(num, subgroup, upTime);
+        SceneEditInfo sceneEditInfo = this.getByScenePlusId(scenePlus.getId());
+        this.upgradeVersionAndImgVersionById(sceneEditInfo.getId());
+        //更新scenejson缓存和oss文件版本号
+        this.upgradeSceneJsonVersion(num, subgroup, upTime, scenePlus.getCacheKeyHasTime(), sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1);
+    }
+
+    @Override
+    public ResultData downloadPanorama(FileParamVO param) throws Exception {
+
+        String num = param.getNum();
+        Integer subgroup = param.getSubgroup();
+        String upTimeKey = param.getUpTimeKey();
+        String fileName = param.getFileName();
+
+        Scene scenePlus = sceneService.getByNum(num, subgroup, upTimeKey);
+        if(Objects.isNull(scenePlus)){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+        }
+
+        //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
+        sceneAsynOperLogService.checkSceneAsynOper(scenePlus.getId(),null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.PANORAMIC_IMAGE.code());
+
+        String numStr = RedisKey.getNumStr(num, subgroup, upTimeKey, scenePlus.getCacheKeyHasTime());
+        String cachePath = String.format(ConstantFilePath.SCENE_CACHE, numStr);
+        String localImagesPath =  String.format(ConstantFilePath.SCENE_CACHE_IMAGES, numStr);
+
+        String cacheFormat = "downloads/scene/%s/caches/";
+        String cacheImageFormat = "downloads/scene/%s/caches/images/";
+
+        List<SceneFileMapping> sceneFileMappings = sceneFileMappingService.getByScene(num, subgroup, upTimeKey);
+        List<SceneFileMapping> panList = sceneFileMappings.stream().filter(v -> v.getKey().contains("images/pan/high/")).collect(Collectors.toList());
+        Map<String, SceneFileMapping> panMap = panList.stream().collect(Collectors.toMap(v -> FileUtil.getName(v.getKey()), v -> v));
+
+//        List<String> panoramaImageList = panList.stream().map(v -> FileUtil.getName(v.getKey())).collect(Collectors.toList());
+
+        Map<String, Object> map = new HashMap<>();
+
+        //标记是否是异步操作,默认是同步操作
+        //如果入参文件名不为空,则是单个文件下载,不需要打包
+        if(StrUtil.isNotEmpty(fileName)){
+            if(!panMap.keySet().contains(fileName)){
+                throw new BusinessException(ErrorCode.FAILURE_CODE_5063);
+            }
+            String url = panMap.get(fileName).getKey();
+            String downloadName = fileName;
+            map.put("asyn", CommonStatus.NO.code());
+            map.put("fileUrl", url);
+            map.put("fileName", downloadName);
+            return ResultData.ok(map);
+        }else{
+            //清除旧的下载记录
+            sceneAsynOperLogService.cleanLog(scenePlus.getId(), SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.PANORAMIC_IMAGE.code(), SceneAsynOperType.DOWNLOAD.code());
+
+            //开始异步执行下载全景图压缩包操作
+            CompletableFuture.runAsync(() -> {
+                SceneEditInfo sceneEditInfo = this.getByScenePlusId(scenePlus.getId());
+                SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
+                sceneAsynOperLog.setNum(num);
+                sceneAsynOperLog.setSceneId(scenePlus.getId());
+                sceneAsynOperLog.setOperType(SceneAsynOperType.DOWNLOAD.code());
+                sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
+                sceneAsynOperLog.setFunc(SceneAsynFuncType.PANORAMIC_IMAGE.code());
+                sceneAsynOperLog.setVersion(sceneEditInfo.getImgVersion());
+                sceneAsynOperLogService.save(sceneAsynOperLog);
+                try {
+
+                    //下载到本地目录
+                    FileUtil.del(localImagesPath);
+                    for (SceneFileMapping sceneFileMapping : panList) {
+                        customHttpClient.downloadFile(sceneFileMapping.getUrl(), localImagesPath, fileName);
+
+                    }
+
+                    String downloadName = num + "_images.zip";
+                    //打包
+                    String zipPath = cachePath + downloadName;
+                    ZipUtil.zip(localImagesPath, zipPath);
+                    //上传压缩包
+                    fYunFileService.uploadFile(num, subgroup,upTimeKey,zipPath, String.format(cacheFormat, num) + downloadName);
+                    String url = String.format(cacheFormat, num) + downloadName;
+                    //删除本地压缩包
+                    FileUtil.del(zipPath);
+                    //删除本地目录
+                    FileUtil.del(localImagesPath);
+                    sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
+                    sceneAsynOperLog.setUrl(url);
+                }catch (Exception e){
+                    sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
+                    log.error("下载全景图压缩包失败,num:" + num, e);
+                }
+                sceneAsynOperLogService.saveOrUpdate(sceneAsynOperLog);
+            });
+
+            map.put("asyn", CommonStatus.YES.code());
+            return ResultData.ok(map);
+        }
+    }
+
+    @Override
     public void saveTagsToSceneEditInfo(String num, Integer subgroup, String upTime, Integer cacheKeyHasTime, SceneEditInfo sceneEditInfo){
         //查询缓存是否包含热点数据
         String key = String.format(RedisKey.SCENE_HOT_DATA, RedisKey.getNumStr(num, subgroup, upTime, cacheKeyHasTime));
@@ -709,4 +1004,261 @@ public class SceneEditInfoServiceImpl extends ServiceImpl<SceneEditInfoMapper, S
                 list.stream().map(str -> JSON.parseObject(str)).collect(Collectors.toList());
         return ResultData.ok(collect);
     }
+
+
+    @Override
+    public ResultData saveVideoBox(FileNameAndDataParamVO param) throws Exception {
+
+        JSONObject boxVideo = JSONObject.parseObject(param.getData());
+        String sid = boxVideo.getString("sid");
+        if(StrUtil.isEmpty(sid)){
+            throw new BusinessException(ErrorCode.PARAM_REQUIRED);
+        }
+        Scene scenePlus = sceneService.getByNum(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+        if(Objects.isNull(scenePlus))
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+
+        SceneEditInfo sceneEditInfo = this.getByScenePlusId(scenePlus.getId());
+
+        //转换视频格式
+        commonService.transferToFlv(param.getNum(), param.getSubgroup(), param.getUpTimeKey(), scenePlus.getCacheKeyHasTime(), param.getFileName());
+
+        //生成boxVideos数据
+        String boxVideos = this.createBoxVideos(sid, boxVideo, sceneEditInfo, OperationType.ADDORUPDATE.code());
+
+        //更新数据库
+        this.updateBoxVideos(sceneEditInfo, scenePlus.getId(), boxVideos);
+
+        return ResultData.ok();
+    }
+
+    @Override
+    public ResultData deleteVideoBox(DeleteSidParamVO param) throws Exception {
+
+        Scene scenePlus = sceneService.getByNum(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+        if(Objects.isNull(scenePlus))
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+
+        SceneEditInfo sceneEditInfo = this.getByScenePlusId(scenePlus.getId());
+
+        //根据sid移除json
+        String boxVideos = this.createBoxVideos(param.getSid(), null, sceneEditInfo, OperationType.DELETE.code());
+
+        //写数据库
+        this.updateBoxVideos(sceneEditInfo,scenePlus.getId(),boxVideos);
+
+        return ResultData.ok();
+    }
+
+    private String createBoxVideos(String sid, JSONObject boxVideo,
+            SceneEditInfo sceneEditInfo, int type) throws Exception{
+
+        String boxVideos = null;
+        if(sceneEditInfo != null){
+            boxVideos = sceneEditInfo.getBoxVideos();
+        }
+        JSONArray boxVideosJson = null;
+        if (StrUtil.isNotEmpty(boxVideos)) {
+            boxVideosJson = JSONArray.parseArray(boxVideos);
+        }else {
+            boxVideosJson = new JSONArray();
+        }
+        if(boxVideosJson.size() > 0){
+            int i = 1;
+            long timeInMillis = Calendar.getInstance().getTimeInMillis();
+            for (Object o : boxVideosJson) {
+                JSONObject item = (JSONObject)o;
+                if(Objects.nonNull(item.getLong("createTime"))){
+                    continue;
+                }
+                item.put("createTime", timeInMillis - (i++));
+            }
+        }
+
+        String result = null;
+        //删除
+        if(type == OperationType.DELETE.code()){
+            Set<String> deleteVidoeFile = new HashSet<>();
+            Set<String> deletePicFile = new HashSet<>();
+            if(boxVideosJson.size() == 0)
+                return null;
+            for(int i=0;i<boxVideosJson.size();++i){
+                JSONObject ele = boxVideosJson.getJSONObject(i);
+                if(ele.getString("sid").equals(sid)){
+
+                    String poster = ele.getString("poster");
+                    if(StrUtil.isNotEmpty(poster))
+                        deletePicFile.add(poster);
+                    String url = ele.getString("url");
+                    if(StrUtil.isNotEmpty(url)){
+                        deleteVidoeFile.add(url);
+                        deleteVidoeFile.add(url.replace(".mp4",".flv"));
+                    }
+
+                    boxVideosJson.remove(i);
+                }
+            }
+        }else{
+            boxVideo.put("createTime", Calendar.getInstance().getTimeInMillis());
+
+            //更新
+            boolean exist = false;
+            for(int i=0;i<boxVideosJson.size();++i){
+                JSONObject ele = boxVideosJson.getJSONObject(i);
+                if(ele.getString("sid").equals(sid)){
+                    boxVideosJson.set(i, boxVideo);
+                    exist = true;
+                }
+            }
+            //新增
+            if(!exist){
+                boxVideosJson.add(boxVideo);
+            }
+
+            boxVideosJson.clear();
+            boxVideosJson.add(boxVideo);
+
+        }
+        if(boxVideosJson.size() != 0){
+            result = boxVideosJson.toJSONString();
+        }
+
+        return result;
+    }
+
+    private void updateBoxVideos(SceneEditInfo sceneEditInfo, Long scenePlusId, String boxVideos){
+        if(Objects.isNull(sceneEditInfo)){
+            sceneEditInfo = new SceneEditInfo();
+            sceneEditInfo.setScenePlusId(scenePlusId);
+            sceneEditInfo.setBoxVideos(boxVideos);
+            this.save(sceneEditInfo);
+        }else{
+            this.update(new UpdateWrapper<SceneEditInfo>()
+                    .setSql("version = version + 1")
+                    .set("box_videos", boxVideos)
+                    .eq("id", sceneEditInfo.getId()));
+        }
+    }
+
+    @Override
+    public ResultData saveBoxPhoto(BaseDataParamVO param) throws Exception {
+
+        JSONObject boxPhoto = JSONObject.parseObject(param.getData());
+        String sid = boxPhoto.getString("sid");
+        if(StrUtil.isEmpty(sid)){
+            throw new BusinessException(ErrorCode.PARAM_REQUIRED, sid);
+        }
+        Scene scenePlus = sceneService.getByNum(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+        if(Objects.isNull(scenePlus))
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+
+        SceneEditInfo sceneEditInfo = this.getByScenePlusId(scenePlus.getId());
+
+        //生成boxVideos数据
+        String boxPhotos = this.createBoxPhotos(sid, boxPhoto, sceneEditInfo, OperationType.ADDORUPDATE.code());
+
+        //更新数据库
+        this.updateBoxPhotos(sceneEditInfo, boxPhotos);
+
+        return ResultData.ok();
+    }
+
+    @Override
+    public ResultData deleteBoxPhoto(DeleteSidParamVO param) throws Exception {
+
+        Scene scenePlus = sceneService.getByNum(param.getNum(), param.getSubgroup(), param.getUpTimeKey());
+        if(Objects.isNull(scenePlus))
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+
+        SceneEditInfo sceneEditInfo = this.getByScenePlusId(scenePlus.getId());
+
+        //根据sid移除json
+        String boxPhotos = this.createBoxPhotos(param.getSid(), null, sceneEditInfo, OperationType.DELETE.code());
+
+        //写数据库
+        this.updateBoxPhotos(sceneEditInfo, boxPhotos);
+
+        return ResultData.ok();
+    }
+
+
+    private void updateBoxPhotos(SceneEditInfo sceneEditInfo, String boxPhotos){
+        this.update(new LambdaUpdateWrapper<SceneEditInfo>()
+                .set(SceneEditInfo::getBoxPhotos, boxPhotos)
+                .setSql("version = version + 1")
+                .eq(SceneEditInfo::getId, sceneEditInfo.getId()));
+    }
+
+    private String createBoxPhotos(String sid, JSONObject boxPhoto, SceneEditInfo sceneEditInfo, int type) throws Exception{
+
+        String boxPhotos = null;
+        if(sceneEditInfo != null){
+            boxPhotos = sceneEditInfo.getBoxPhotos();
+        }
+        JSONArray boxPhotosJson = null;
+        if (StrUtil.isNotEmpty(boxPhotos)) {
+            boxPhotosJson = JSONArray.parseArray(boxPhotos);
+        }else {
+            boxPhotosJson = new JSONArray();
+        }
+
+        String result = null;
+        //删除
+        if(type == OperationType.DELETE.code()){
+            Set<String> deleteFile = new HashSet<>();
+            if(boxPhotosJson.size() == 0)
+                return null;
+            for(int i=0;i<boxPhotosJson.size();++i){
+                JSONObject ele = boxPhotosJson.getJSONObject(i);
+                if(ele.getString("sid").equals(sid)){
+
+                    String poster = ele.getString("poster");
+                    if(StrUtil.isNotEmpty(poster))
+                        deleteFile.add(poster);
+                    String url = ele.getString("url");
+                    if(StrUtil.isNotEmpty(url))
+                        deleteFile.add(url);
+
+                    boxPhotosJson.remove(i);
+                }
+            }
+        }else{
+            //更新
+            boolean exist = false;
+            for(int i=0;i<boxPhotosJson.size();++i){
+                JSONObject ele = boxPhotosJson.getJSONObject(i);
+                if(ele.getString("sid").equals(sid)){
+                    boxPhoto.put("createTime", ele.getLong("createTime"));
+                    boxPhotosJson.set(i, boxPhoto);
+                    exist = true;
+                }
+            }
+            //新增
+            if(!exist){
+                boxPhoto.put("createTime", Calendar.getInstance().getTimeInMillis());
+                boxPhotosJson.add(boxPhoto);
+            }
+
+        }
+        if(boxPhotosJson.size() != 0){
+
+            List<BoxPhotoBean> list = Lists.newArrayList();
+            for (Object o : boxPhotosJson) {
+                JSONObject jsonObject = (JSONObject)o;
+                list.add(BoxPhotoBean.builder().createTime(jsonObject.getLong("createTime")).boxPhoto(jsonObject).build());
+            }
+            //按创建时间倒叙排序
+            list.sort(Comparator.comparingLong(BoxPhotoBean::getCreateTime).reversed());
+
+            // list转JSONArray
+            JSONArray array = new JSONArray();
+            list.stream().forEach(bean->{
+                array.add(bean.getBoxPhoto());
+            });
+
+            result = array.toJSONString();
+        }
+
+        return result;
+    }
 }

+ 9 - 0
src/main/java/com/fdkankan/scene/service/impl/SceneFileMappingServiceImpl.java

@@ -8,6 +8,8 @@ import com.fdkankan.scene.service.SceneFileMappingService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 /**
  * <p>
  *  服务实现类
@@ -30,10 +32,17 @@ public class SceneFileMappingServiceImpl extends ServiceImpl<SceneFileMappingMap
     }
 
     @Override
+    public List<SceneFileMapping> getByScene(String num, Integer subgroup, String upTime) {
+        return this.list(new LambdaQueryWrapper<SceneFileMapping>().eq(SceneFileMapping::getNum, num).eq(SceneFileMapping::getSubgroup, subgroup).eq(SceneFileMapping::getUpTime, upTime));
+    }
+
+    @Override
     public SceneFileMapping getByKey(String key, Integer subgroup, String upTime) {
         return this.getOne(new LambdaQueryWrapper<SceneFileMapping>()
                 .eq(SceneFileMapping::getKey, key)
                 .eq(SceneFileMapping::getSubgroup, subgroup)
                 .eq(SceneFileMapping::getUpTime, upTime));
     }
+
+
 }

+ 346 - 6
src/main/java/com/fdkankan/scene/service/impl/SceneProServiceImpl.java

@@ -3,13 +3,16 @@ package com.fdkankan.scene.service.impl;
 import cn.hutool.core.collection.CollUtil;
 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.update.LambdaUpdateWrapper;
-import com.fdkankan.common.constant.ErrorCode;
-import com.fdkankan.common.constant.UploadFilePath;
+import com.fdkankan.common.constant.*;
 import com.fdkankan.common.exception.BusinessException;
+import com.fdkankan.common.util.ComputerUtil;
+import com.fdkankan.common.util.CreateObjUtil;
+import com.fdkankan.common.util.FileUtils;
 import com.fdkankan.model.utils.ConvertUtils;
 import com.fdkankan.redis.constant.RedisKey;
 import com.fdkankan.redis.util.RedisClient;
@@ -17,11 +20,10 @@ import com.fdkankan.scene.bean.IconBean;
 import com.fdkankan.scene.bean.ResultData;
 import com.fdkankan.scene.bean.TagBean;
 import com.fdkankan.scene.entity.Scene;
+import com.fdkankan.scene.entity.SceneAsynOperLog;
 import com.fdkankan.scene.entity.SceneEditInfo;
-import com.fdkankan.scene.service.FYunFileService;
-import com.fdkankan.scene.service.ISceneProService;
-import com.fdkankan.scene.service.SceneEditInfoService;
-import com.fdkankan.scene.service.SceneService;
+import com.fdkankan.scene.entity.SceneFileMapping;
+import com.fdkankan.scene.service.*;
 import com.fdkankan.scene.vo.*;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -29,12 +31,14 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 /**
@@ -57,6 +61,10 @@ public class SceneProServiceImpl implements ISceneProService {
     private SceneEditInfoService sceneEditInfoService;
     @Autowired
     private SceneService scenePlusService;
+    @Autowired
+    private SceneAsynOperLogService sceneAsynOperLogService;
+    @Autowired
+    private SceneFileMappingService sceneFileMappingService;
 
     @Transactional
     @Override
@@ -442,4 +450,336 @@ public class SceneProServiceImpl implements ISceneProService {
 
         return ResultData.ok();
     }
+
+    public ResultData downloadModel(String num, Integer subgroup, String upTimeKey) throws Exception {
+
+        if(StrUtil.isEmpty(num)){
+            throw new BusinessException(ErrorCode.PARAM_REQUIRED);
+        }
+        Scene scenePlus = scenePlusService.getByNum(num, subgroup, upTimeKey);
+        if(scenePlus == null){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+        }
+
+        //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
+        sceneAsynOperLogService.checkSceneAsynOper(scenePlus.getId(), null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.MODEL.code());
+
+        //清除旧的下载记录
+        sceneAsynOperLogService.cleanLog(scenePlus.getId(), SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.MODEL.code(), SceneAsynOperType.DOWNLOAD.code());
+
+        SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
+
+        //开始异步执行下载全景图压缩包操作
+        CompletableFuture.runAsync(() -> {
+            SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
+            sceneAsynOperLog.setNum(num);
+            sceneAsynOperLog.setOperType(SceneAsynOperType.DOWNLOAD.code());
+            sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
+            sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
+            sceneAsynOperLog.setVersion(sceneEditInfo.getImgVersion());
+            sceneAsynOperLog.setSceneId(scenePlus.getId());
+            sceneAsynOperLogService.save(sceneAsynOperLog);
+            try {
+
+                String url = null;
+                if(ModelKind.THREE_D_TILE.code().equals(scenePlus.getModelkind())){
+//                    url = downloadModel43dtiles(num, bucket, scenePlusExt, sceneEditInfo);
+                }else{
+                    url = downloadModel4Dam(num, subgroup, upTimeKey, scenePlus.getCacheKeyHasTime());
+                }
+
+                sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
+                sceneAsynOperLog.setUrl(url);
+            }catch (Exception e){
+                sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
+                log.error("下载模型压缩包失败,num:" + num, e);
+            }
+            sceneAsynOperLogService.saveOrUpdate(sceneAsynOperLog);
+        });
+
+        return ResultData.ok();
+    }
+
+    private String downloadModel4Dam(String num, Integer subgroup, String upTime, Integer cacheKeyHasTime){
+        String numStr = RedisKey.getNumStr(num, subgroup, upTime, cacheKeyHasTime);
+        String localImagePath = String.format(ConstantFilePath.IMAGESBUFFER_FORMAT, numStr);
+        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, numStr) + "mesh";
+        FileUtil.del(meshPath);
+
+        //下载模型文件
+        String objKey = dataViewPath+ "mesh/mesh.obj";
+        String mtlKey = dataViewPath + "mesh/mesh.mtl";
+        fYunFileService.downloadFile(num, subgroup, upTime, objKey, meshPath, "mesh.obj");
+        fYunFileService.downloadFile(num, subgroup, upTime, objKey, mtlKey, "mesh.mtl");
+
+        log.info("meshPath="+meshPath);
+        if(!new File(meshPath).exists() || new File(meshPath).listFiles().length < 1){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_7006);
+        }
+        //打包
+        ZipUtil.zip(meshPath, zipPath);
+        //上传压缩包
+        fYunFileService.uploadFile(num, subgroup, upTime, zipPath, "downloads/extras/" + zipName);
+        String url = "downloads/extras/" + zipName;
+        FileUtil.del(zipPath);
+        FileUtil.del(meshPath);
+        return url;
+    }
+
+    @Override
+    public ResultData uploadModel(String num, Integer subgroup, String upTime, MultipartFile file) throws Exception{
+        if(StrUtil.isEmpty(num)){
+            throw new BusinessException(ErrorCode.PARAM_REQUIRED, "num");
+        }
+        if(!file.getOriginalFilename().endsWith(".zip")){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_7015);
+        }
+
+        Scene scenePlus = scenePlusService.getByNum(num, subgroup, upTime);
+        if(scenePlus == null){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
+        }
+
+        //查询是否存在等待中的异步操作记录,如果存在,抛出业务异常,终止操作
+        sceneAsynOperLogService.checkSceneAsynOper(scenePlus.getId(), null, SceneAsynModuleType.UPLOAD_DOWNLOAD.code() , SceneAsynFuncType.MODEL.code());
+
+        //清除全景图异步操作记录,防止再次下载的时候请求到旧的压缩包
+        sceneAsynOperLogService.cleanLog(scenePlus.getId(), SceneAsynModuleType.UPLOAD_DOWNLOAD.code(), SceneAsynFuncType.MODEL.code());
+
+
+        if(ModelKind.THREE_D_TILE.code().equals(scenePlus.getModelkind())){
+//            this.buildModel43dtiles(num, bucket, scenePlusExt.getDataSource(), file);
+        }else{
+            this.buildModel4Dam(num, subgroup, upTime, scenePlus.getCacheKeyHasTime(), file);
+        }
+
+        return ResultData.ok();
+    }
+
+    /**
+     * 老算法(dam)上传模型逻辑
+     * @param num
+     * @throws Exception
+     */
+    private void buildModel4Dam(String num, Integer subgroup, String upTime, Integer cacheKeyHasTime,  MultipartFile file) throws Exception {
+
+        Scene scenePlus = scenePlusService.getByNum(num, subgroup, upTime);
+
+        //文件上传的位置可以自定义
+        String numStr = RedisKey.getNumStr(num, subgroup, upTime, cacheKeyHasTime);
+        String dataSource = String.format(ConstantFilePath.SCENE_USER_PATH_V4, numStr);
+        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);
+
+        CompletableFuture.runAsync(() -> {
+            SceneAsynOperLog sceneAsynOperLog = new SceneAsynOperLog();
+            sceneAsynOperLog.setNum(num);
+            sceneAsynOperLog.setSceneId(scenePlus.getId());
+            sceneAsynOperLog.setOperType(SceneAsynOperType.UPLOAD.code());
+            sceneAsynOperLog.setModule(SceneAsynModuleType.UPLOAD_DOWNLOAD.code());
+            sceneAsynOperLog.setFunc(SceneAsynFuncType.MODEL.code());
+            sceneAsynOperLogService.save(sceneAsynOperLog);
+            try {
+                //调用算法,不同的类型调用不同的算法
+                CreateObjUtil.build3dModel(path , "1");
+
+                //算法计算完后,生成压缩文件,上传到oss
+                uploadFileofterBuildDamModel(path, filePath, num, subgroup, upTime);
+
+                //更新版本信息
+                SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
+                sceneEditInfoService.update(
+                        new LambdaUpdateWrapper<SceneEditInfo>()
+                                .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, subgroup, upTime, scenePlus.getCacheKeyHasTime(), sceneEditInfo.getVersion() + 1, sceneEditInfo.getImgVersion() + 1);
+
+                sceneAsynOperLog.setState(CommonOperStatus.SUCCESS.code());
+            } catch (Exception e) {
+                log.error("上传dam模型,num:" + num, e);
+                sceneAsynOperLog.setState(CommonOperStatus.FAILD.code());
+            }
+            sceneAsynOperLogService.updateById(sceneAsynOperLog);
+        });
+    }
+
+    private void objAndImgFileHandler(String resultPath, String filePath, String zipPath, MultipartFile file)
+            throws Exception {
+        FileUtil.del(resultPath);
+        File targetFile = new File(filePath);
+        if (!targetFile.exists()) {
+            targetFile.mkdirs();
+        }else {
+            FileUtil.del(filePath);
+        }
+
+        targetFile = new File(zipPath);
+        if (!targetFile.exists()) {
+            targetFile.mkdirs();
+        }else {
+            FileUtil.del(zipPath);
+        }
+
+        targetFile = new File(zipPath + file.getOriginalFilename());
+        if(!targetFile.getParentFile().exists()){
+            targetFile.getParentFile().mkdirs();
+        }
+        // 保存压缩包到本地
+        if(targetFile.exists()) {
+            FileUtil.del(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;
+                FileUtil.copy(dataPath + data.getName(), filePath + "mesh.obj", true);
+                continue;
+            }
+
+            if(data.getName().endsWith(".mtl")){
+                mtlFlag = true;
+            }
+
+            FileUtil.copy(dataPath + data.getName(), filePath + data.getName(), true);
+        }
+
+        //压缩文件中必须有且仅有一个obj和mtl文件,否则抛出异常
+        if(!objFlag){//!mtlFlag ||
+            throw new BusinessException(ErrorCode.FAILURE_CODE_15059);
+        }
+    }
+
+    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");
+        FileUtil.writeUtf8String(dataJson.toString(), path + "/data.json");
+    }
+
+    private void uploadFileofterBuildDamModel(String path, String filePath, String sceneNum, Integer subgroup, String upTime) throws Exception {
+        //因为共享目录有延迟,这里循环检测算法是否计算完毕3次,每次隔五秒
+        String uploadJsonPath = path + File.separator + "results" +File.separator+"upload.json";
+        boolean exist = ComputerUtil.checkComputeCompleted(uploadJsonPath, 5, 2000);
+        if(!exist){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
+        }
+        String uploadData = FileUtil.readUtf8String(uploadJsonPath);
+        JSONObject uploadJson = null;
+        JSONArray array = null;
+        if(uploadData!=null) {
+            uploadJson = JSONObject.parseObject(uploadData);
+            array = uploadJson.getJSONArray("upload");
+        }
+
+        Map<String,String> map = new HashMap<String,String>();
+        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 +  "tieta_texture/" + fileName.replace("tex/", ""));
+            }
+        }
+
+        String damKey = imgViewPath + "tieta.dam";
+        String damPath = path + File.separator + "results" +File.separator + "dam.txt";
+        CreateObjUtil.convertTxtToDam( path + File.separator + "results" +File.separator+"modeldata.txt", damPath);
+        boolean existDam = ComputerUtil.checkComputeCompleted(damPath, 5, 200);
+        if(!existDam){
+            throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
+        }
+        map.put(damPath, damKey);
+
+        String ossMeshPath = String.format(UploadFilePath.DATA_VIEW_PATH, sceneNum) + "mesh";
+        //上传obj相关文件
+        List<String> fileNames = FileUtil.listFileNames(filePath);
+        fileNames.stream().forEach(name->map.put(filePath + name, ossMeshPath + File.separator + name));
+
+        fYunFileService.uploadMulFiles(sceneNum, subgroup,upTime, map);
+    }
+
+
 }

+ 0 - 1
src/main/java/com/fdkankan/scene/vo/BaseJsonArrayParamVO.java

@@ -21,7 +21,6 @@ import java.util.List;
 public class BaseJsonArrayParamVO extends BaseSceneParamVO{
 
     @NotNull(message = "data不能为空")
-//    private List<LinkPanParamVO> linkPans;
     private List<JSONObject> data;
 
     private List<JSONObject> styles;

+ 88 - 0
src/main/java/com/fdkankan/scene/vo/FileParamVO.java

@@ -0,0 +1,88 @@
+package com.fdkankan.scene.vo;
+
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * <p>
+ * 文件接口参数
+ * </p>
+ *
+ * @author dengsixing
+ **/
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FileParamVO extends BaseSceneParamVO{
+
+
+    private String id;
+
+    private String type;
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 是否替换 0-不替换,1-替换
+     */
+    private String replace;
+
+    /**
+     * 视频或音频时长
+     */
+    private Double length;
+
+    private String times;
+
+    private String index;
+
+    private String sid;
+
+    private String ajkJson;
+
+    private String cameraJson;
+
+    private String floorPlanJson;
+
+    private String folderName;
+
+    private String toOss;
+
+    private String cadInfo;
+
+    private String filePath;
+
+    private String path;
+
+    private String status;
+
+    private String jsonData;
+
+    private String planId;
+
+    private String rect;
+
+    private String videoDirMatrix;
+
+    private String dir;
+
+    private String hfov;
+
+    private String vfov;
+
+    private String imagesName;
+
+    private String fileData;
+
+    private String floorJsonData;
+
+    private String soundFile;
+
+
+
+}

+ 35 - 0
src/main/java/com/fdkankan/scene/vo/SceneAsynOperLogParamVO.java

@@ -0,0 +1,35 @@
+package com.fdkankan.scene.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/12/7
+ **/
+@Data
+public class SceneAsynOperLogParamVO extends BaseSceneParamVO{
+
+    /**
+    * 操作类型(upload-上传,download-下载)
+    */
+    private String operType;
+
+    /**
+     * 模块名称
+     */
+    private String module;
+
+    /**
+     * 功能
+     */
+    private String func;
+
+
+}

+ 29 - 0
src/main/java/com/fdkankan/scene/vo/UploadPanoramaVO.java

@@ -0,0 +1,29 @@
+package com.fdkankan.scene.vo;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * 上传全景图出参
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/4/11
+ **/
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UploadPanoramaVO {
+
+    private int asyn;
+
+    private Integer successCnt;
+
+    private List<String> failList;
+
+}

+ 5 - 0
src/main/resources/mapper/scene.generator/SceneAsynOperLogMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fdkankan.scene.generator.mapper.SceneAsynOperLogMapper">
+
+</mapper>