dengsixing 1 неделя назад
Родитель
Сommit
ebcdb26d94

+ 22 - 1
src/main/java/com/fdkankan/modeling/ModelingApplication.java

@@ -1,13 +1,19 @@
 package com.fdkankan.modeling;
 
 import cn.hutool.core.io.FileUtil;
+import com.fdkankan.modeling.job.SerialTaskExecutor;
+import com.fdkankan.modeling.service.IModelingScheduleService;
 import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
 
+import javax.annotation.Resource;
 import java.util.UUID;
 
 @SpringBootApplication
@@ -15,11 +21,26 @@ import java.util.UUID;
 @ComponentScan(basePackages = {"com.fdkankan.*"})
 @MapperScan("com.fdkankan.**.mapper")
 @EnableDiscoveryClient
-public class ModelingApplication {
+public class ModelingApplication implements CommandLineRunner {
 
 	public static void main(String[] args) {
 		FileUtil.writeUtf8String("interrupt-calling-" + UUID.randomUUID().toString().replace("-",""), "/opt/queue-name-interrupt-calling.txt");
 		SpringApplication.run(ModelingApplication.class, args);
 	}
 
+	@Resource
+	private SerialTaskExecutor serialTaskExecutor;
+	@Autowired
+	private IModelingScheduleService modelingScheduleService;
+
+	@Override
+	public void run(String... args) throws Exception {
+
+		//恢复由于重启导致中断的任务
+		modelingScheduleService.recoverTask();
+
+		// 应用启动后启动定时任务
+		serialTaskExecutor.startScheduledTask();
+	}
+
 }

+ 20 - 0
src/main/java/com/fdkankan/modeling/constants/TaskConstants.java

@@ -0,0 +1,20 @@
+package com.fdkankan.modeling.constants;
+
+import java.util.concurrent.TimeUnit;
+
+// 核心常量类
+public class TaskConstants {
+    // 定时任务触发间隔:5秒
+    public static final long TASK_TRIGGER_INTERVAL = 5;
+    public static final TimeUnit TRIGGER_TIME_UNIT = TimeUnit.SECONDS;
+
+    // 任务执行超时时间:48小时
+    public static final long TASK_TIMEOUT = 48;
+
+    public static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.HOURS;
+    // 分布式锁前缀
+    public static final String TASK_LOCK_PREFIX = "task:exec:lock:";
+    // 分布式锁超时时间(防止死锁,需大于任务超时时间)
+    public static final long LOCK_TIMEOUT = 49;
+    public static final TimeUnit LOCK_TIME_UNIT = TimeUnit.HOURS;
+}

+ 25 - 0
src/main/java/com/fdkankan/modeling/constants/TaskStatus.java

@@ -0,0 +1,25 @@
+package com.fdkankan.modeling.constants;
+
+import lombok.Getter;
+import java.sql.Timestamp;
+import java.util.concurrent.TimeUnit;
+
+// 任务状态枚举
+@Getter
+public enum TaskStatus {
+    FAILED(-1, "执行失败"),
+    WAIT(0, "等待执行"),
+    RUNNING(1, "执行中"),
+    SUCCESS(2, "任务结束"),
+    DISCARD(3, "任务被合并"),
+    TIMEOUT(4, "执行超时"); // 新增超时状态
+
+    private final int code;
+    private final String desc;
+
+    TaskStatus(int code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}
+

+ 21 - 0
src/main/java/com/fdkankan/modeling/controller/ModelingScheduleController.java

@@ -0,0 +1,21 @@
+package com.fdkankan.modeling.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 计算任务调度表 前端控制器
+ * </p>
+ *
+ * @author 
+ * @since 2026-03-11
+ */
+@RestController
+@RequestMapping("/modeling/modelingSchedule")
+public class ModelingScheduleController {
+
+}
+

+ 98 - 0
src/main/java/com/fdkankan/modeling/entity/ModelingSchedule.java

@@ -0,0 +1,98 @@
+package com.fdkankan.modeling.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 计算任务调度表
+ * </p>
+ *
+ * @author 
+ * @since 2026-03-11
+ */
+@Getter
+@Setter
+@TableName("t_modeling_schedule")
+public class ModelingSchedule implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("uuid")
+    private String uuid;
+
+    @TableField("unicode")
+    private String unicode;
+
+    /**
+     * 场景码
+     */
+    @TableField("num")
+    private String num;
+
+    /**
+     * 1-失败,0-等待,1-进行中,2-退出计算,3-丢弃
+     */
+    @TableField("status")
+    private Integer status;
+
+    /**
+     * hx-海鑫压缩包,jm-江门小文件,manage-管理后台,rebuild-重算,local-局域网
+     */
+    @TableField("source")
+    private String source;
+
+    /**
+     * 点位数量
+     */
+    @TableField("shoot_count")
+    private Integer shootCount;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+
+    @TableField("rec_status")
+    @TableLogic(value = "A", delval = "I")
+    private String recStatus;
+
+    /**
+     * 备注
+     */
+    @TableField("remark")
+    private String remark;
+
+    /**
+     * 扩展信息
+     */
+    @TableField("ext")
+    private String ext;
+
+    //状态(-1-失败,1-成功)
+    @TableField("modeling_status")
+    private Integer modelingStatus;
+
+    @TableField("exec_server")
+    private String execServer;
+
+    @TableField("parent_uuid")
+    private String parentUuid;
+
+
+
+
+
+
+}

+ 4 - 3
src/main/java/com/fdkankan/modeling/generate/AutoGenerate.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.generator.FastAutoGenerator;
 import com.baomidou.mybatisplus.generator.config.OutputFile;
 import com.baomidou.mybatisplus.generator.config.rules.DateType;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -17,7 +18,7 @@ public class AutoGenerate {
         String path =System.getProperty("user.dir");
 
         generate(path,"modeling", getTables(new String[]{
-                "t_scene_build_process_log"
+                "t_modeling_schedule"
         }));
 
 //        generate(path,"goods", getTables(new String[]{
@@ -45,8 +46,8 @@ public class AutoGenerate {
 
 
     public static void  generate(String path,String moduleName,  List<String> tables){
-        FastAutoGenerator.create("jdbc:mysql://120.24.144.164:3306/4dkankan_v4",
-                "root","4Dage@4Dage#@168")
+        FastAutoGenerator.create("jdbc:mysql://192.168.0.125:13306/4dkankan_v4",
+            "root","4dkk2023cuikuan%")
                 .globalConfig(builder -> {
                     builder.author("")               //作者
                             .outputDir(path+"\\src\\main\\java")    //输出路径(写到java目录)

+ 0 - 81
src/main/java/com/fdkankan/modeling/job/EcsJob.java

@@ -1,81 +0,0 @@
-package com.fdkankan.modeling.job;
-
-import com.fdkankan.modeling.constants.SysConstants;
-import com.fdkankan.rubbersheeting.ScalingService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
-
-import javax.annotation.PostConstruct;
-import java.util.Random;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-@Component
-public class EcsJob {
-
-    private static final Logger log = LoggerFactory.getLogger(EcsJob.class);
-
-    @Autowired
-    @Lazy
-    private ScalingService scalingService;
-
-    @PostConstruct
-    public void deleteEcs() {
-        if (SysConstants.isResidenceService) {
-            log.info("此服务是常驻服务,不启动关闭弹性伸缩定时任务!");
-            return;
-        }
-        log.info("此服务非常驻服务,开始启动关闭弹性伸缩定时任务");
-        Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(() -> {
-                    // 判断是否有任务执行
-                    if (SysConstants.SYSTEM_BUILDING) {
-                        log.error("{} 服务构建中,退出停止服务请求!", SysConstants.hostName);
-                        return;
-                    }
-                    log.info("服务未构建,准备停止服务!");
-                    if (!SysConstants.executorService.isShutdown()) {
-                        SysConstants.SYSTEM_OFFING = true;
-                        if (!SysConstants.SYSTEM_BUILDING) {
-                            SysConstants.executorService.shutdown();
-                            log.error("{} 线程池关闭,不接受新的构建请求!", SysConstants.hostName);
-                        }else{
-                            SysConstants.SYSTEM_OFFING = false;
-                            log.error("{} 服务构建中,退出停止服务请求!", SysConstants.hostName);
-                        }
-                    }
-                    // 因为 阿里云同一时间不能删除多台实例,因此做随机睡眠,错开时间。
-                    try {
-                        Thread.sleep((new Random().nextInt(30) + 10) * 1000);
-                    } catch (InterruptedException e) {
-                        e.printStackTrace();
-                    }
-                    // 没有任务执行时,则退出
-                    if (SysConstants.executorService.isShutdown() && SysConstants.executorService.isTerminated()) {
-                        log.error("{} 请求阿里云关闭弹性伸缩", SysConstants.hostName);
-                        String result = null;
-                        int tryTimes = -1;
-                        do {
-                            tryTimes++;
-                            result = scalingService.deleteEcs(SysConstants.hostName);
-                            if (ObjectUtils.isEmpty(result)) {
-                                int time = new Random().nextInt(10) + 30;
-                                log.error("{} 关闭弹性伸缩失败,{}秒后重试", SysConstants.hostName, time);
-                                try {
-                                    Thread.sleep(time * 1000);
-                                } catch (InterruptedException e) {
-                                    e.printStackTrace();
-                                }
-                            }
-                        } while (ObjectUtils.isEmpty(result) && tryTimes < 5);
-                        log.error("{} 关闭弹性伸缩成功!", SysConstants.hostName);
-                    } else {
-                        log.error("{} 服务构建中,退出删除程序失败!", SysConstants.hostName);
-                    }
-                },
-                50, 60, TimeUnit.MINUTES);
-    }
-}

+ 180 - 0
src/main/java/com/fdkankan/modeling/job/SerialTaskExecutor.java

@@ -0,0 +1,180 @@
+package com.fdkankan.modeling.job;
+
+import cn.hutool.core.collection.CollUtil;
+import com.fdkankan.modeling.constants.TaskConstants;
+import com.fdkankan.modeling.constants.TaskStatus;
+import com.fdkankan.modeling.entity.ModelingSchedule;
+import com.fdkankan.modeling.exception.BuildException;
+import com.fdkankan.modeling.mapper.IModelingScheduleMapper;
+import com.fdkankan.modeling.service.IModelingScheduleService;
+import com.fdkankan.modeling.util.ServerUtils;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * 单服务器串行执行控制器:保证5秒触发、串行执行、48小时超时
+ */
+@Slf4j
+@Component
+public class SerialTaskExecutor {
+    // 定时任务线程池(单线程,保证定时触发串行)
+    private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
+    // 任务执行线程池(单线程,保证任务串行)
+    private final ExecutorService taskExecutor = Executors.newSingleThreadExecutor();
+    // 标记当前是否有任务在执行(volatile保证可见性)
+    private volatile boolean isTaskRunning = false;
+
+    @Autowired
+    private IModelingScheduleService modelingScheduleService;
+
+    @Resource
+    private RedissonClient redissonClient;
+    @Autowired
+    private ServerUtils serverUtils;
+
+    /**
+     * 启动定时任务(应用启动时调用)
+     */
+    public void startScheduledTask() {
+        // 延迟0秒启动,每隔5秒触发一次
+        scheduledExecutor.scheduleAtFixedRate(
+                this::executeSingleTask,
+                0,
+                TaskConstants.TASK_TRIGGER_INTERVAL,
+                TaskConstants.TRIGGER_TIME_UNIT
+        );
+        log.info("串行定时任务已启动,每隔5秒触发一次");
+    }
+
+    /**
+     * 执行单个任务(核心逻辑)
+     */
+    private void executeSingleTask(){
+        // 检查:上一个任务未执行完成则跳过
+        if (isTaskRunning) {
+            log.info("上一个任务仍在执行,跳过本次触发");
+            return;
+        }
+
+        //有新的计算任务进来时,会先去查同一个unicode是否有待执行的任务,
+        // 如果有,会把待执行的标记为3(合并)并把parent_uuid赋值为当前进入任务列表的uuid,
+        // 所以这里只需要找出状态为待处理任务即可,再根据待处理任务找到子任务,把子任务文件依次下载替换,进行合并计算即可
+        ModelingSchedule modelingSchedule = modelingScheduleService.selectOneWaitTask();// 只查1条
+        if (modelingSchedule == null) {
+            System.out.println("获取不到等待执行的任务,跳过本次触发");
+            return;
+        }
+
+        // 步骤3:标记为执行中,开始执行(带超时控制)
+        isTaskRunning = true;
+        //开始执行计算业务
+        executeTaskWithTimeout(modelingSchedule);
+    }
+
+    /**
+     * 执行任务并添加48小时超时控制
+     */
+    private void executeTaskWithTimeout(ModelingSchedule modelingSchedule) {
+        // 提交任务到执行线程池,返回Future用于超时控制
+        Future<?> future = taskExecutor.submit(() -> {
+            try {
+                // 执行核心业务逻辑(替换为你的实际任务代码)
+                doBusinessTask(modelingSchedule);
+                // 执行成功:更新状态为「退出计算」(或自定义「完成」状态)
+                modelingScheduleService.updateTaskStatus(modelingSchedule.getId(), TaskStatus.SUCCESS.getCode(), "任务执行成功", serverUtils.getLocalServerId());
+            } catch (Exception e) {
+                // 执行失败:更新状态为「失败」
+                modelingScheduleService.updateTaskStatus(modelingSchedule.getId(), TaskStatus.FAILED.getCode(), "执行失败:" + e.getMessage(), serverUtils.getLocalServerId());
+                log.error("计算失败,modelingScheduleId:{}", modelingSchedule.getId(), e);
+            }
+        });
+
+        // 超时控制:等待任务执行,最多48小时
+        try {
+            future.get(TaskConstants.TASK_TIMEOUT, TaskConstants.TIMEOUT_TIME_UNIT);
+        } catch (TimeoutException e) {
+            // 任务超时:中断任务+更新状态
+            future.cancel(true); // 中断任务线程
+            modelingScheduleService.updateTaskStatus(modelingSchedule.getId(), TaskStatus.TIMEOUT.getCode(), "任务执行超时(48小时)", serverUtils.getLocalServerId());
+            System.err.println("任务[" + modelingSchedule.getUuid() + "]执行超时,已中断");
+        } catch (InterruptedException | ExecutionException | BuildException e) {
+            // 其他异常:标记为失败
+            String remark = "任务执行异常";
+            if (e.getCause() instanceof BuildException) {
+                remark = "算法异常";
+            }
+            modelingScheduleService.updateTaskStatus(modelingSchedule.getId(), TaskStatus.FAILED.getCode(), remark + e.getMessage(), serverUtils.getLocalServerId());
+            log.error("任务执行异常", e);
+        } finally {
+            // 释放分布式锁+重置执行状态
+            String lockKey = TaskConstants.TASK_LOCK_PREFIX + modelingSchedule.getUuid();
+            RLock lock = redissonClient.getLock(lockKey);
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+            isTaskRunning = false;
+            log.info("任务[" + modelingSchedule.getUuid() + "]执行完成(/超时/失败),释放锁并重置状态");
+        }
+    }
+
+    /**
+     * 实际业务任务逻辑(替换为你的代码)
+     */
+    private void doBusinessTask(ModelingSchedule task) throws Exception {
+        log.info("开始执行任务[" + task.getUuid() + "],场景码:" + task.getNum());
+        //前置处理
+        //查询子任务
+        List<ModelingSchedule> subTasks = modelingScheduleService.selectSubTask(task.getUuid());
+        if(CollUtil.isNotEmpty(subTasks)){
+            for (ModelingSchedule subTask : subTasks) {
+                String source = subTask.getSource();
+                if(source.equals("hx")){//海鑫
+
+                }else if(source.equals("jm")){//江门
+
+                }else if(source.equals("manage")){//管理后台
+
+                }else if(source.equals("local")){//局域网版方式
+
+                }else if(source.equals("rebuild")){//重算
+
+                }
+            }
+        }
+
+
+
+        //调用算法
+
+        //后置处理
+
+    }
+
+    /**
+     * 关闭线程池(应用停止时调用)
+     */
+    public void shutdown() {
+        scheduledExecutor.shutdown();
+        taskExecutor.shutdown();
+        try {
+            if (!scheduledExecutor.awaitTermination(1, TimeUnit.MINUTES)) {
+                scheduledExecutor.shutdownNow();
+            }
+            if (!taskExecutor.awaitTermination(1, TimeUnit.MINUTES)) {
+                taskExecutor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            scheduledExecutor.shutdownNow();
+            taskExecutor.shutdownNow();
+        }
+    }
+}

+ 20 - 0
src/main/java/com/fdkankan/modeling/mapper/IModelingScheduleMapper.java

@@ -0,0 +1,20 @@
+package com.fdkankan.modeling.mapper;
+
+import com.fdkankan.modeling.entity.ModelingSchedule;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 计算任务调度表 Mapper 接口
+ * </p>
+ *
+ * @author 
+ * @since 2026-03-11
+ */
+@Mapper
+public interface IModelingScheduleMapper extends BaseMapper<ModelingSchedule> {
+
+    ModelingSchedule selectOneWaitTask();
+
+}

+ 1 - 0
src/main/java/com/fdkankan/modeling/service/IBuildService.java

@@ -8,4 +8,5 @@ public interface IBuildService {
 
     Map<String, String> getTypeString(String cameraType, String algorithm, String resolution, JSONObject fdageData);
 
+
 }

+ 27 - 0
src/main/java/com/fdkankan/modeling/service/IModelingScheduleService.java

@@ -0,0 +1,27 @@
+package com.fdkankan.modeling.service;
+
+import com.fdkankan.modeling.entity.ModelingSchedule;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 计算任务调度表 服务类
+ * </p>
+ *
+ * @author 
+ * @since 2026-03-11
+ */
+public interface IModelingScheduleService extends IService<ModelingSchedule> {
+
+    void execute();
+
+    void recoverTask();
+
+    void updateTaskStatus(Long taskId, int status, String remark, String serverId);
+
+    ModelingSchedule selectOneWaitTask();
+
+    List<ModelingSchedule> selectSubTask(String uuid);
+}

+ 125 - 0
src/main/java/com/fdkankan/modeling/service/impl/ModelingScheduleServiceImpl.java

@@ -0,0 +1,125 @@
+package com.fdkankan.modeling.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.fdkankan.modeling.constants.TaskConstants;
+import com.fdkankan.modeling.constants.TaskStatus;
+import com.fdkankan.modeling.entity.ModelingSchedule;
+import com.fdkankan.modeling.mapper.IModelingScheduleMapper;
+import com.fdkankan.modeling.service.IModelingScheduleService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fdkankan.modeling.util.ServerUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * <p>
+ * 计算任务调度表 服务实现类
+ * </p>
+ *
+ * @author 
+ * @since 2026-03-11
+ */
+@Slf4j
+@Service
+public class ModelingScheduleServiceImpl extends ServiceImpl<IModelingScheduleMapper, ModelingSchedule> implements IModelingScheduleService {
+
+    @Resource
+    private ServerUtils serverUtils;
+    @Resource
+    private RedissonClient redissonClient;
+
+    @Override
+    public void execute() {
+
+
+
+
+
+
+    }
+
+    @Override
+    public void recoverTask() {
+        //获取本机id
+        log.info("服务启动, 恢复停");
+        String localServerId = serverUtils.getLocalServerId();
+        List<ModelingSchedule> list = lambdaQuery().eq(ModelingSchedule::getExecServer, localServerId).eq(ModelingSchedule::getStatus, TaskStatus.RUNNING.getCode()).list();
+        if(CollUtil.isEmpty(list)){
+            return;
+        }
+        list.stream().forEach(v->{
+            v.setStatus(TaskStatus.WAIT.getCode());
+            v.setUpdateTime(new Date());
+            // 释放分布式锁+重置执行状态
+            String lockKey = TaskConstants.TASK_LOCK_PREFIX + v.getUuid();
+            RLock lock = redissonClient.getLock(lockKey);
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        });
+        this.updateBatchById(list);
+    }
+
+    @Override
+    @Transactional
+    public ModelingSchedule selectOneWaitTask(){
+        // 步骤1:从数据库取1条「等待中」的任务,优先挑选计算级别高的场景
+        ModelingSchedule modelingSchedule = this.getBaseMapper().selectOneWaitTask();
+        if(modelingSchedule == null){
+            return null;
+        }
+        // 步骤2:分布式锁锁定任务(防跨机重复)
+        String lockKey = TaskConstants.TASK_LOCK_PREFIX + modelingSchedule.getUuid();
+        RLock lock = redissonClient.getLock(lockKey);
+        // 非阻塞获取锁:获取不到说明其他服务器在执行
+        boolean locked = false;
+        try {
+            locked = lock.tryLock(0, TaskConstants.LOCK_TIMEOUT, TaskConstants.LOCK_TIME_UNIT);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        if (!locked) {
+            log.warn("任务:{}已被其他服务器:{}锁定,跳过", modelingSchedule.getUuid(), modelingSchedule.getExecServer());
+            return null;
+        }
+
+        this.updateTaskStatus(modelingSchedule.getId(), TaskStatus.RUNNING.getCode(), "开始执行", serverUtils.getLocalServerId());
+
+        return modelingSchedule;
+    }
+
+    /**
+     * 更新任务状态
+     */
+    @Override
+    public void updateTaskStatus(Long taskId, int status, String remark, String serverId) {
+        try {
+            ModelingSchedule updateTask = new ModelingSchedule();
+            updateTask.setId(taskId);
+            updateTask.setStatus(status);
+            updateTask.setRemark(remark);
+            updateTask.setUpdateTime(new Timestamp(System.currentTimeMillis()));
+            updateTask.setExecServer(serverId);
+            this.updateById(updateTask);
+        } catch (Exception e) {
+            log.error("更新任务[" + taskId + "]状态失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public List<ModelingSchedule> selectSubTask(String uuid) {
+        return lambdaQuery()
+                .eq(ModelingSchedule::getParentUuid, uuid)
+                .eq(ModelingSchedule::getStatus, TaskStatus.DISCARD)
+                .orderByAsc(ModelingSchedule::getId).list();
+    }
+}

+ 23 - 0
src/main/java/com/fdkankan/modeling/util/ServerUtils.java

@@ -0,0 +1,23 @@
+package com.fdkankan.modeling.util;
+
+import cn.hutool.core.io.FileUtil;
+import org.springframework.stereotype.Component;
+
+import java.net.UnknownHostException;
+
+@Component
+public class ServerUtils {
+
+    private static final String hostPath = "/opt/host/host.txt";
+
+
+    // 获取本机IP作为唯一标识(也可以用主机名/自定义标识)
+    public String getLocalServerId() {
+        try {
+            return FileUtil.readUtf8String(hostPath);
+        } catch (Exception e) {
+            // 兜底:用随机字符串,避免空值
+            return "unknown_" + System.currentTimeMillis();
+        }
+    }
+}

+ 19 - 0
src/main/resources/mapper/modeling/ModelingScheduleMapper.xml

@@ -0,0 +1,19 @@
+<?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.modeling.mapper.IModelingScheduleMapper">
+    <select id="selectOneWaitTask" resultType="com.fdkankan.modeling.entity.ModelingSchedule">
+        SELECT id, uuid, unicode, num, status, source, shoot_count, create_time, update_time, rec_status, remark, ext
+        FROM t_modeling_schedule
+        WHERE status = 0 AND rec_status = 'A'
+        AND unicode NOT IN (
+            SELECT unicode
+            FROM t_modeling_schedule
+            WHERE
+            STATUS = 1
+            AND rec_status = 'A'
+        )
+        ORDER BY priority DESC, create_time ASC
+        LIMIT 1
+        FOR UPDATE
+    </select>
+</mapper>