| | |
| | | // 创建成功后,重新获取设备对象 |
| | | 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("创建设备配置失败"); |
| | |
| | | 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 设备类型 |
| | |
| | | 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; |
| | |
| | | @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); |
| | | } |
| | | |
| | | // 初始化设备状态为离线 |
| | |
| | | return result; |
| | | } catch (Exception e) { |
| | | log.error("创建设备配置失败", e); |
| | | return false; |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | |
| | | 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); |
| | |
| | | |
| | | @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) { |
| | |
| | | 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 { |
| | |
| | | // 与导入规则保持一致: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); |
| | |
| | | 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; |
| | |
| | | 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(); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="设备编码" prop="deviceCode"> |
| | | <el-input |
| | | v-model="deviceForm.deviceCode" |
| | | placeholder="请输入设备编码" |
| | | maxlength="50" |
| | | :disabled="isEdit" |
| | | /> |
| | | <template v-if="isEdit"> |
| | | <el-input |
| | | v-model="deviceForm.deviceCode" |
| | | maxlength="50" |
| | | disabled |
| | | /> |
| | | </template> |
| | | <template v-else> |
| | | <el-tag type="info">保存后自动生成</el-tag> |
| | | </template> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="设备类型" prop="deviceType"> |
| | |
| | | description: '', |
| | | isPrimary: false, |
| | | enabled: true, |
| | | extraParams: null |
| | | extraParams: null, |
| | | projectId: 1 |
| | | }) |
| | | |
| | | const deviceForm = reactive(getDefaultForm()) |
| | |
| | | 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' } |
| | |
| | | isPrimary: deviceForm.isPrimary, |
| | | enabled: deviceForm.enabled, |
| | | description: deviceForm.description, |
| | | projectId: deviceForm.projectId, |
| | | configJson: configJsonValue, // 保存配置参数JSON |
| | | extraParams: JSON.stringify(extraObj) |
| | | } |