192.168.9.165 недель назад: 3
Родитель
Сommit
9471ead962

+ 100 - 37
src/main/java/com/fdkankan/scene/AppListener.java

@@ -1,6 +1,5 @@
 package com.fdkankan.scene;
 
-
 import cn.hutool.system.SystemUtil;
 import com.fdkankan.redis.util.RedisUtil;
 import com.fdkankan.scene.config.FdkkLaserConfig;
@@ -11,9 +10,7 @@ import org.springframework.boot.ApplicationRunner;
 import org.springframework.boot.SpringApplication;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.stereotype.Component;
-import org.springframework.web.context.WebApplicationContext;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
@@ -23,72 +20,137 @@ import java.io.InputStreamReader;
 public class AppListener implements ApplicationRunner {
 
     @Autowired
-    FdkkLaserConfig fdkkLaserConfig;
+    private FdkkLaserConfig fdkkLaserConfig;
+
     @Resource
-    private WebApplicationContext applicationContext;
+    private ConfigurableApplicationContext applicationContext;
+
     @Autowired
     private RedisUtil redisUtil;
 
     @Override
     public void run(ApplicationArguments args) {
-        if (fdkkLaserConfig.getPid() <= 0) {
-           log.info("未配置 app.monitorPid,跳过 PID 监听。");
+        long pid = fdkkLaserConfig.getPid();
+        if (pid <= 0) {
+            log.info("未配置 app.monitorPid,跳过 PID 监听。");
             return;
         }
 
-        log.info("启动监听 PID: " + fdkkLaserConfig.getPid());
+        log.info("启动监听 PID: {}", pid);
 
-        Thread monitorThread = new Thread(() -> {
-            while (true) {
-                try {
-                    if (!isProcessAlive(fdkkLaserConfig.getPid())) {
-                        log.info("目标 PID 不存在,准备退出 SpringBoot 服务...");
-                        shutdownApplication();
-                        break;
-                    }
-                    if (redisUtil.hasKey("QUIT_JOB_MESH")) {
-                        log.info("收到推出通知,准备退出 SpringBoot 服务...");
-                        redisUtil.del("QUIT_JOB_MESH");
-                        shutdownApplication();
-                        break;
-                    }
-                    Thread.sleep(2000);
-                } catch (Exception e) {
-                    e.printStackTrace();
+        Thread monitorThread = new Thread(() -> monitorLoop(pid), "pid-monitor-thread");
+        monitorThread.setDaemon(true);
+        monitorThread.start();
+    }
+
+    /**
+     * 监控循环:
+     * 1. 监听 Redis 中的 QUIT_JOB_MESH
+     * 2. 检测目标 PID 是否存活
+     * 任一条件触发则优雅关闭应用
+     */
+    private void monitorLoop(long pid) {
+        while (!Thread.currentThread().isInterrupted()) {
+            try {
+                // 监听退出通知
+                if (redisUtil.hasKey("QUIT_JOB_MESH")) {
+                    log.info("收到退出通知,准备退出 SpringBoot 服务...");
+                    redisUtil.del("QUIT_JOB_MESH");
+                    shutdownApplication();
+                    break;
+                }
+
+                // 检查目标 PID 是否存活
+                if (!checkPidExist(pid, 3, 200L)) {
+                    log.info("目标 PID 不存在,准备退出 SpringBoot 服务... pid={}", pid);
+                    shutdownApplication();
+                    break;
                 }
+            } catch (Exception e) {
+                log.error("PID 监听线程异常", e);
             }
-        });
 
-        monitorThread.setDaemon(true);
-        monitorThread.start();
+            // 无论是否异常,都休眠一段时间,避免忙等
+            try {
+                Thread.sleep(2000L);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("PID 监听线程被中断,停止监听。");
+                break;
+            }
+        }
+    }
+
+    /**
+     * 多次重试检查 PID 是否存活
+     *
+     * @param pid            进程 PID
+     * @param maxCheckTimes  最大检查次数
+     * @param waitTimeMillis 每次检查间的等待时间(毫秒)
+     */
+    private boolean checkPidExist(long pid, int maxCheckTimes, long waitTimeMillis) throws InterruptedException {
+        if (pid <= 0) {
+            return false;
+        }
+
+        for (int i = 1; i <= maxCheckTimes; i++) {
+            if (isProcessAlive(pid)) {
+                return true;
+            }
+            if (i < maxCheckTimes) {
+                Thread.sleep(waitTimeMillis);
+            }
+        }
+        return false;
     }
 
     /**
-     * 判断 PID 是否存活 (Java 8 版本,使用系统命令)
+     * 判断 PID 是否存活(Java 8,使用系统命令)
      */
     private boolean isProcessAlive(long pid) {
         try {
+            String pidStr = String.valueOf(pid);
+
+            // 防御:PID 必须为纯数字
+            if (!pidStr.matches("\\d+")) {
+                log.error("非法 PID: {}", pidStr);
+                return false;
+            }
+
             if (SystemUtil.getOsInfo().isWindows()) {
-                // Windows 使用 tasklist
-                Process process = Runtime.getRuntime().exec("tasklist /FI \"PID eq " + pid + "\"");
+                // Windows 使用 tasklist,使用 ProcessBuilder 避免命令注入
+                Process process = new ProcessBuilder("cmd", "/c",
+                        "tasklist", "/FI", "PID eq " + pidStr)
+                        .redirectErrorStream(true)
+                        .start();
+
                 try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                     String line;
                     while ((line = reader.readLine()) != null) {
-                        if (line.contains(String.valueOf(pid))) {
+                        // 简单但更精确一点的匹配方式
+                        String trimmed = line.trim();
+                        if (trimmed.isEmpty()) {
+                            continue;
+                        }
+                        // 避免 123 和 9123 误匹配
+                        if (trimmed.matches(".*\\s" + pidStr + "\\s.*") || trimmed.endsWith(" " + pidStr)) {
                             return true;
                         }
                     }
                 }
+                return false;
             } else {
                 // Linux / Mac 使用 kill -0
-                Process process = Runtime.getRuntime().exec("kill -0 " + pid);
+                Process process = new ProcessBuilder("kill", "-0", pidStr)
+                        .redirectErrorStream(true)
+                        .start();
                 int exitCode = process.waitFor();
                 return exitCode == 0;
             }
         } catch (Exception e) {
+            log.error("检查进程存活状态失败,pid={}", pid, e);
             return false;
         }
-        return false;
     }
 
     /**
@@ -96,11 +158,12 @@ public class AppListener implements ApplicationRunner {
      */
     private void shutdownApplication() {
         try {
+            log.info("开始优雅关闭 Spring Boot 应用...");
             SpringApplication.exit(applicationContext, () -> 0);
-
-
-
+        } catch (Exception e) {
+            log.error("关闭 Spring Boot 应用时发生异常", e);
         } finally {
+            log.info("退出 JVM");
             System.exit(0);
         }
     }

+ 77 - 23
src/main/java/com/fdkankan/scene/ApplicationRunner.java

@@ -1,21 +1,30 @@
 package com.fdkankan.scene;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.extra.spring.SpringUtil;
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fdkankan.common.constant.CommonStatus;
 import com.fdkankan.common.constant.CommonSuccessStatus;
 import com.fdkankan.common.constant.DownloadStatus;
 import com.fdkankan.redis.constant.RedisKey;
 import com.fdkankan.redis.util.RedisUtil;
+import com.fdkankan.scene.bean.DownLoadTaskBean;
 import com.fdkankan.scene.config.FdkkLaserConfig;
 import com.fdkankan.scene.config.RedisKeyExt;
 import com.fdkankan.scene.constant.BuildType;
+import com.fdkankan.scene.entity.SceneDownloadLog;
 import com.fdkankan.scene.entity.SceneFileBuildEntity;
+import com.fdkankan.scene.entity.ScenePlus;
 import com.fdkankan.scene.service.ISceneDownloadLogService;
+import com.fdkankan.scene.service.IScenePlusService;
 import com.fdkankan.scene.service.SceneFileBuildService;
+import com.fdkankan.scene.vo.SceneDownloadParamVO;
+import com.google.api.client.json.Json;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.CommandLineRunner;
@@ -25,9 +34,11 @@ import javax.annotation.Resource;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author Xiewj
@@ -45,6 +56,8 @@ public class ApplicationRunner implements CommandLineRunner {
 
     private static String keyFormat = "touch:scene:download:num:%s";
 
+    @Autowired
+    private IScenePlusService scenePlusService;
     @Override
     public void run(String... args) throws Exception {
         initServer();
@@ -52,14 +65,7 @@ public class ApplicationRunner implements CommandLineRunner {
     private  void initServer() throws IOException {
 
         FdkkLaserConfig fdkkLaserConfig = SpringUtil.getBean(FdkkLaserConfig.class);
-        String stateConfigOne = fdkkLaserConfig.getBinPath() + File.separator + ".v4state";
-        if (FileUtil.exist(stateConfigOne)) {
-            log.info("state文件存在");
-            FileWriter writer = new FileWriter(FileUtil.file(stateConfigOne));
-            writer.write("1");
-            writer.flush();
-            writer.close();
-        }
+
         String setting = fdkkLaserConfig.getSettingJson();
         String data = FileUtil.readString(setting, "UTF-8");
         JSONObject config = JSONObject.parseObject(data);
@@ -67,13 +73,53 @@ public class ApplicationRunner implements CommandLineRunner {
         fdkkLaserConfig.setLaserPort(javaPort);
 
         //项目重启,正在下载中的场景要改为失败状态,并删除redis缓存
-        List<String> downloadIngNums = redisUtil.lGet(RedisKey.SCENE_DOWNLOAD_ING, 0, -1);
-        if(CollUtil.isNotEmpty(downloadIngNums)){
-            for (String downloadIngNum : downloadIngNums) {
-                sceneDownloadLogService.updateStatusSceneNum(downloadIngNum, DownloadStatus.FAILD.code());
+//        List<String> downloadIngNums = redisUtil.lGet(RedisKey.SCENE_DOWNLOAD_ING, 0, -1);
+//        if(CollUtil.isNotEmpty(downloadIngNums)){
+//            for (String downloadIngNum : downloadIngNums) {
+//                sceneDownloadLogService.updateStatusSceneNum(downloadIngNum, DownloadStatus.FAILD.code());
+//            }
+//            redisUtil.del(RedisKey.SCENE_DOWNLOAD_ING);
+//        }
+
+//        //兜底,ing队列没有,表记录有下载中的,但是排队队列中没有,就改为失败(应对的特殊情况:下载逻辑走到finally删除了ing队列还没来得及去改状态时,程序推出了)
+//        List<String> taskStrs = redisUtil.lGet(RedisKey.SCENE_DOWNLOADS_TASK_V4, 0, -1);
+//        List<SceneDownloadLog> list = sceneDownloadLogService.list(new LambdaQueryWrapper<SceneDownloadLog>().eq(SceneDownloadLog::getStatus, DownloadStatus.DOWNLOADING.code()));
+//        if(CollUtil.isNotEmpty(list)){
+//            f1: for (SceneDownloadLog sceneDownloadLog : list) {
+//                if(CollUtil.isEmpty(taskStrs)){
+//                    sceneDownloadLog.setStatus(DownloadStatus.FAILD.code());
+//                    sceneDownloadLog.setUpdateTime(null);
+//                    sceneDownloadLogService.updateById(sceneDownloadLog);
+//                    continue;
+//                }
+//                for (String taskStr : taskStrs) {
+//                    if(taskStr.contains(sceneDownloadLog.getSceneNum())){
+//                        sceneDownloadLog.setStatus(DownloadStatus.FAILD.code());
+//                        sceneDownloadLog.setUpdateTime(null);
+//                        sceneDownloadLogService.updateById(sceneDownloadLog);
+//                        continue f1;
+//                    }
+//                }
+//            }
+//
+//        }
+
+        //删除正在下载队列
+        redisUtil.del(RedisKey.SCENE_DOWNLOAD_ING);
+        //检测如若状态为0的直接改为失败
+        redisUtil.del(RedisKey.SCENE_DOWNLOADS_TASK_V4);
+        List<SceneDownloadLog> list = sceneDownloadLogService.list(new LambdaQueryWrapper<SceneDownloadLog>().eq(SceneDownloadLog::getStatus, DownloadStatus.DOWNLOADING.code()));
+        if(CollUtil.isNotEmpty(list)){
+            for (SceneDownloadLog sceneDownloadLog : list) {
+                sceneDownloadLog.setStatus(DownloadStatus.FAILD.code());
+                sceneDownloadLog.setUpdateTime(null);
+                String key = String.format(RedisKey.PREFIX_DOWNLOAD_PROGRESS_V4, sceneDownloadLog.getSceneNum());
+                redisUtil.del(key);//清楚进度条缓存
             }
-            redisUtil.del(RedisKey.SCENE_DOWNLOAD_ING);
+            sceneDownloadLogService.updateBatchById(list);
         }
+
+
         Set keyFormatSet = redisUtil.keys(String.format(keyFormat, "*"));
         Set PREFIX_DOWNLOAD_PROGRESS_V4_SET = redisUtil.keys(String.format(RedisKey.PREFIX_DOWNLOAD_PROGRESS_V4, "*"));
         for (Object o : keyFormatSet) {
@@ -87,6 +133,7 @@ public class ApplicationRunner implements CommandLineRunner {
 
 
 
+        //状态,0-未建模,1-等待建模(队列中),2-建模中,3-建模暂停,4-建模成功,-1-建模失败
         List<SceneFileBuildEntity> offlineBuildList = sceneFileBuildService.findBuildListByStatusAndBuildType(Arrays.asList(1),Arrays.asList(BuildType.BUILD_MESH_OFFLINE));
         for (SceneFileBuildEntity sceneFileBuildEntity : offlineBuildList) {
             log.info("处理排队离线包{}",sceneFileBuildEntity);
@@ -94,19 +141,19 @@ public class ApplicationRunner implements CommandLineRunner {
                 //路径不存在改为失败
                 sceneFileBuildEntity.setBuildStatus(-1);
                 sceneFileBuildService.updateById(sceneFileBuildEntity);
-                if (sceneFileBuildEntity.getBuildType().equals(BuildType.BUILD_MESH_OFFLINE)){
-                    //路径不存在改为失败
-//                    sceneDownloadLogService.updateStatusSceneNum(sceneFileBuildEntity.getSceneNum(), DownloadStatus.FAILD.code());
-                    String key = String.format(RedisKey.PREFIX_DOWNLOAD_PROGRESS_V4, sceneFileBuildEntity.getSceneNum());
-                    redisUtil.del(key);
-                    redisUtil.lRemove(RedisKey.SCENE_DOWNLOAD_ING, 1, sceneFileBuildEntity.getSceneNum());
-                }
+                continue;
             }
             switch (sceneFileBuildEntity.getBuildType()){
                 case BuildType.BUILD_MESH_OFFLINE:
                     if (FileUtil.exist(sceneFileBuildEntity.getResultPath())){
-                        JSONObject jsonObject = JSONObject.parseObject(sceneFileBuildEntity.getExt());
-                        redisUtil.lRightPush(RedisKey.SCENE_DOWNLOADS_TASK_V4, jsonObject.toJSONString());
+                        SceneDownloadParamVO sceneDownloadParamVO = JSON.parseObject(sceneFileBuildEntity.getExt(), SceneDownloadParamVO.class);
+                        //{"sceneCode":"1993286825330085888","lang":"zh","fusion":false,"dir":"C:\\Users\\Administrator\\Downloads\\offline\\oooobbbbjjjj\\202512011650091000","type":"local",
+                        // "num":"1993286825330085888","resultPath":"C:\\Users\\Administrator\\Downloads\\offline\\oooobbbbjjjj\\202512011650091000"}
+//                        redisUtil.lRightPush(RedisKey.SCENE_DOWNLOADS_TASK_V4, jsonObject.toJSONString());
+                        ScenePlus scenePlusByNum = scenePlusService.getScenePlusByNum(sceneDownloadParamVO.getSceneCode());
+                        if (scenePlusByNum!=null){
+                            sceneDownloadLogService.downloadScene(sceneDownloadParamVO);
+                        }
                     }
                     break;
             }
@@ -115,7 +162,14 @@ public class ApplicationRunner implements CommandLineRunner {
         //查询排队中的离线包,重新入队列
         //   laser  public static final String SCENE_DOWNLOADS_TASK = "scene:downloads:task:";
         //   mesh public static final String SCENE_DOWNLOADS_TASK_V4 = "scene:downloads:task:v4";
-
+        String stateConfigOne = fdkkLaserConfig.getBinPath() + File.separator + ".v4state";
+        if (FileUtil.exist(stateConfigOne)) {
+            log.info("state文件存在");
+            FileWriter writer = new FileWriter(FileUtil.file(stateConfigOne));
+            writer.write("1");
+            writer.flush();
+            writer.close();
+        }
 
     }
 }

+ 15 - 5
src/main/java/com/fdkankan/scene/service/impl/SceneDownloadHandlerServiceImpl.java

@@ -136,6 +136,7 @@ public class SceneDownloadHandlerServiceImpl {
         //场景码
         String num = null;
 
+        boolean success = false;
         try {
             num = downLoadTaskBean.getNum();
 
@@ -150,10 +151,15 @@ public class SceneDownloadHandlerServiceImpl {
             //耗时
             long consumeTime = Calendar.getInstance().getTimeInMillis() - startTime;
 
+            success = true;
+
             log.info("场景下载结束 - num[{}] - threadName[{}] - consumeTime[{}]", num, Thread.currentThread().getName(), consumeTime);
 
 
         }catch (Exception e){
+
+            success = false;
+
             sceneDownloadLogService.update(
                     new LambdaUpdateWrapper<SceneDownloadLog>()
                     .eq(SceneDownloadLog::getSceneNum,num)
@@ -166,12 +172,20 @@ public class SceneDownloadHandlerServiceImpl {
             }
             //失败请求
         }finally {
+            int status = -1;
+            if(success){
+                status = 4;
+            }
+
             if(StrUtil.isNotEmpty(num)){
                 //本地正在下载任务出队
                 CurrentDownloadNumUtil.removeSceneNum(num, "v4");
                 //删除正在下载任务
                 redisUtil.lRemove(RedisKey.SCENE_DOWNLOAD_ING, 1, num);
             }
+
+            this.updateStatusAndLastEditTime(downLoadTaskBean, status);
+
             sceneDownloadLogService.setBuildSceneOffline(num);
         }
     }
@@ -258,16 +272,11 @@ public class SceneDownloadHandlerServiceImpl {
 
             sceneDownloadLogService.updateStatusSceneNum(num, DownloadStatus.SUCCESS.code());
 
-            //成功请求
-            updateStatusAndLastEditTime(downLoadTaskBean,4);
-
-
 
         }catch (Exception e){
             //更新进度为下载失败
             this.updateProgress( null, num, SceneDownloadProgressStatus.DOWNLOAD_FAILED.code(), null, "v4");
             sceneDownloadLogService.updateStatusSceneNum(num, DownloadStatus.FAILD.code());
-            updateStatusAndLastEditTime(downLoadTaskBean,-1);
             throw e;
         }
 //        finally {
@@ -428,6 +437,7 @@ public class SceneDownloadHandlerServiceImpl {
             }catch (Exception e){
                 throw e;
             }
+//            Thread.sleep(500);
             //更新进度
             this.updateProgress(
                 new BigDecimal(count.incrementAndGet()).divide(new BigDecimal(total), 6, BigDecimal.ROUND_HALF_UP),

+ 1 - 0
src/main/java/com/fdkankan/scene/service/impl/SceneFileBuildServiceImpl.java

@@ -78,6 +78,7 @@ public class SceneFileBuildServiceImpl extends ServiceImpl<SceneFileBuildMapper,
         LambdaQueryWrapper<SceneFileBuildEntity> wrapper = Wrappers.lambdaQuery();
         wrapper.in(SceneFileBuildEntity::getBuildStatus, status);
         wrapper.in(SceneFileBuildEntity::getBuildType, buildType);
+        wrapper.isNull(SceneFileBuildEntity::getDeleteFlag);
         wrapper.orderByAsc(SceneFileBuildEntity::getSort);
         return list(wrapper);
     }