huang
2 天以前 ab389a5a6b329b15a655340ba7b87bce7fd7871d
添加新增设备自动生成编码
6个文件已修改
166 ■■■■■ 已修改文件
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceConfigMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceConfigServiceImpl.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/device/DeviceEditDialog.vue 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java
@@ -50,9 +50,11 @@
                // 创建成功后,重新获取设备对象
                DeviceConfig created = deviceConfigService.getDeviceByCode(deviceConfig.getDeviceCode());
                return Result.success(created);
            } else {
                return Result.error("设备配置已存在");
            }
            return Result.error("设备配置已存在");
        } catch (IllegalArgumentException e) {
            log.warn("创建设备配置参数校验失败: {}", e.getMessage());
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("创建设备配置失败", e);
            return Result.error("创建设备配置失败");
mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceConfigMapper.java
@@ -13,6 +13,12 @@
public interface DeviceConfigMapper extends BaseMapper<DeviceConfig> {
    /**
     * 获取设备编码最大序号(DEV_xxxxxx 中的数字部分)
     */
    @Select("SELECT IFNULL(MAX(CAST(SUBSTRING(device_code, 5) AS UNSIGNED)), 0) FROM device_config WHERE device_code LIKE 'DEV_%'")
    Long selectMaxDeviceCodeNumber();
    /**
     * 根据项目ID和设备类型查询设备配置列表
     * @param projectId 项目ID
     * @param deviceType 设备类型
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceConfigServiceImpl.java
@@ -11,6 +11,7 @@
import com.mes.device.vo.DeviceConfigVO;
import com.mes.device.vo.StatisticsVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.io.IOException;
@@ -32,10 +33,27 @@
    @Override
    public boolean createDevice(DeviceConfig deviceConfig) {
        try {
            // 若未传则生成设备编码
            String code = StringUtils.trimToEmpty(deviceConfig.getDeviceCode());
            if (StringUtils.isBlank(code)) {
                code = generateDeviceCode();
                deviceConfig.setDeviceCode(code);
            } else {
                deviceConfig.setDeviceCode(code);
            }
            // 检查设备编号是否已存在
            if (isDeviceCodeExists(deviceConfig.getDeviceCode(), null)) {
                log.warn("设备编号已存在: {}", deviceConfig.getDeviceCode());
                return false;
            if (isDeviceCodeExists(code, null)) {
                log.warn("设备编号已存在: {}", code);
                throw new IllegalArgumentException("设备编码已存在");
            }
            // 兼容旧字段:统一将 device_id 填为 deviceCode,避免非空/唯一约束问题
            deviceConfig.setDeviceId(code);
            // 项目ID未传则使用默认项目(单项目场景可用),避免非空约束
            if (deviceConfig.getProjectId() == null) {
                deviceConfig.setProjectId(1L);
            }
            
            // 初始化设备状态为离线
@@ -50,7 +68,7 @@
            return result;
        } catch (Exception e) {
            log.error("创建设备配置失败", e);
            return false;
            throw e;
        }
    }
@@ -61,6 +79,16 @@
            if (isDeviceCodeExists(deviceConfig.getDeviceCode(), deviceConfig.getId())) {
                log.warn("设备编号已存在: {}", deviceConfig.getDeviceCode());
                return false;
            }
            // 同步 device_id 与 deviceCode,保持一致
            if (StringUtils.isNotBlank(deviceConfig.getDeviceCode())) {
                deviceConfig.setDeviceId(deviceConfig.getDeviceCode().trim());
            }
            // 若项目ID缺失,使用默认项目
            if (deviceConfig.getProjectId() == null) {
                deviceConfig.setProjectId(1L);
            }
            
            boolean result = updateById(deviceConfig);
@@ -341,8 +369,13 @@
    @Override
    public boolean isDeviceCodeExists(String deviceCode, Long excludeId) {
        if (StringUtils.isBlank(deviceCode)) {
            return false;
        }
        String trimmed = deviceCode.trim();
        LambdaQueryWrapper<DeviceConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DeviceConfig::getDeviceCode, deviceCode);
        wrapper.eq(DeviceConfig::getDeviceCode, trimmed);
        wrapper.eq(DeviceConfig::getIsDeleted, 0);
        
        if (excludeId != null) {
@@ -352,6 +385,16 @@
        return count(wrapper) > 0;
    }
    /**
     * 简单的设备编码生成器:DEV_前缀
     */
    private String generateDeviceCode() {
        Long maxNo = getBaseMapper().selectMaxDeviceCodeNumber();
        long next = (maxNo == null ? 0 : maxNo) + 1;
        // 左侧补零到6位,例如 DEV_000123
        return String.format("DEV_%06d", next);
    }
    @Override
    public boolean updateDeviceStatus(Long id, String status) {
        try {
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
@@ -641,7 +641,7 @@
            // 与导入规则保持一致:glassId 前加工程号前缀,数量>1时追加序号
            String baseGlassId = engineeringId.trim() + glassId;
            for (int idx = 0; idx < qty; idx++) {
                String finalGlassId = qty > 1 ? baseGlassId + "_" + (idx + 1) : baseGlassId;
                String finalGlassId = qty > 1 ? baseGlassId + (idx + 1) : baseGlassId;
                GlassInfo glassInfo = new GlassInfo();
                glassInfo.setGlassId(finalGlassId);
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -32,6 +32,8 @@
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -3154,5 +3156,65 @@
            return summary;
        }
    }
    /**
     * 应用关闭时清理资源
     */
    @PreDestroy
    public void destroy() {
        log.info("开始清理TaskExecutionEngine资源...");
        // 停止所有定时任务
        for (Map.Entry<String, List<ScheduledFuture<?>>> entry : taskScheduledTasks.entrySet()) {
            String taskId = entry.getKey();
            List<ScheduledFuture<?>> futures = entry.getValue();
            if (futures != null) {
                for (ScheduledFuture<?> future : futures) {
                    if (future != null && !future.isDone()) {
                        future.cancel(true);
                    }
                }
            }
            log.debug("已停止任务 {} 的所有定时器", taskId);
        }
        taskScheduledTasks.clear();
        // 关闭定时器线程池
        shutdownExecutor(scheduledExecutor, "定时器线程池");
        // 关闭并行执行线程池
        shutdownExecutor(executorService, "并行执行线程池");
        // 清空运行上下文
        runningTaskContexts.clear();
        log.info("TaskExecutionEngine资源清理完成");
    }
    /**
     * 关闭线程池
     */
    private void shutdownExecutor(ExecutorService executor, String name) {
        if (executor == null || executor.isShutdown()) {
            return;
        }
        executor.shutdown();
        try {
            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                log.warn("{} 在10秒内未能正常关闭,强制关闭", name);
                executor.shutdownNow();
                if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                    log.error("{} 强制关闭失败", name);
                }
            } else {
                log.info("{} 已正常关闭", name);
            }
        } catch (InterruptedException e) {
            log.warn("{} 关闭过程中被中断", name, e);
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
mes-web/src/views/device/DeviceEditDialog.vue
@@ -31,12 +31,16 @@
            </el-form-item>
            <el-form-item label="设备编码" prop="deviceCode">
              <template v-if="isEdit">
              <el-input
                v-model="deviceForm.deviceCode"
                placeholder="请输入设备编码"
                maxlength="50"
                :disabled="isEdit"
                  disabled
              />
              </template>
              <template v-else>
                <el-tag type="info">保存后自动生成</el-tag>
              </template>
            </el-form-item>
            <el-form-item label="设备类型" prop="deviceType">
@@ -373,7 +377,8 @@
  description: '',
  isPrimary: false,
  enabled: true,
  extraParams: null
  extraParams: null,
  projectId: 1
})
const deviceForm = reactive(getDefaultForm())
@@ -382,14 +387,35 @@
const isEdit = computed(() => !!props.deviceData)
// 表单验证规则
const validateDeviceCode = async (rule, value, callback) => {
  if (!value) {
    // 允许留空,后台自动生成
    return callback()
  }
  try {
    const res = await deviceConfigApi.checkCode(
      value.trim(),
      isEdit.value ? props.deviceData?.id : null
    )
    if (res?.data === true) {
      callback(new Error('设备编码已存在,请更换'))
    } else {
      callback()
    }
  } catch (err) {
    console.error('检查设备编码失败', err)
    callback(new Error('设备编码校验失败,请稍后重试'))
  }
}
const deviceRules = {
  deviceName: [
    { required: true, message: '请输入设备名称', trigger: 'blur' },
    { min: 1, max: 50, message: '设备名称长度在 1 到 50 个字符', trigger: 'blur' }
  ],
  deviceCode: [
    { required: true, message: '请输入设备编码', trigger: 'blur' },
    { pattern: /^[A-Z0-9_]+$/, message: '设备编码只能包含大写字母、数字和下划线', trigger: 'blur' }
    { pattern: /^[A-Z0-9_]+$/, message: '设备编码只能包含大写字母、数字和下划线', trigger: 'blur' },
    { validator: validateDeviceCode, trigger: 'blur' }
  ],
  deviceType: [
    { required: true, message: '请选择设备类型', trigger: 'change' }
@@ -771,6 +797,7 @@
      isPrimary: deviceForm.isPrimary,
      enabled: deviceForm.enabled,
      description: deviceForm.description,
      projectId: deviceForm.projectId,
      configJson: configJsonValue,  // 保存配置参数JSON
      extraParams: JSON.stringify(extraObj)
    }