package com.mes.device.service.impl; 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.mes.device.entity.GlassInfo; import com.mes.device.mapper.DeviceGlassInfoMapper; import com.mes.device.service.GlassInfoService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.stream.IntStream.range; /** * 玻璃信息服务实现类 * * @author mes * @since 2024-11-20 */ @Slf4j @RefreshScope @Service("deviceGlassInfoService") public class GlassInfoServiceImpl extends ServiceImpl implements GlassInfoService { @Override public GlassInfo getGlassInfo(String glassId) { if (glassId == null || glassId.trim().isEmpty()) { return null; } try { return baseMapper.selectByGlassId(glassId.trim()); } catch (Exception e) { log.error("查询玻璃信息失败, glassId={}", glassId, e); return null; } } @Override public Integer getGlassLength(String glassId) { GlassInfo glassInfo = getGlassInfo(glassId); return glassInfo != null ? glassInfo.getGlassLength() : null; } @Override public List getGlassInfos(List glassIds) { if (glassIds == null || glassIds.isEmpty()) { return Collections.emptyList(); } try { // 过滤空值并去重 List validIds = glassIds.stream() .filter(id -> id != null && !id.trim().isEmpty()) .map(String::trim) .distinct() .collect(Collectors.toList()); if (validIds.isEmpty()) { return Collections.emptyList(); } return baseMapper.selectByGlassIds(validIds); } catch (Exception e) { log.error("批量查询玻璃信息失败, glassIds={}", glassIds, e); return Collections.emptyList(); } } @Override public Map getGlassLengthMap(List glassIds) { Map lengthMap = new HashMap<>(); if (glassIds == null || glassIds.isEmpty()) { return lengthMap; } try { List glassInfos = getGlassInfos(glassIds); for (GlassInfo glassInfo : glassInfos) { if (glassInfo.getGlassId() != null && glassInfo.getGlassLength() != null) { lengthMap.put(glassInfo.getGlassId(), glassInfo.getGlassLength()); } } } catch (Exception e) { log.error("获取玻璃长度映射失败, glassIds={}", glassIds, e); } return lengthMap; } @Override public boolean saveOrUpdateGlassInfo(GlassInfo glassInfo) { if (glassInfo == null || glassInfo.getGlassId() == null) { return false; } try { // 检查是否已存在 GlassInfo existing = baseMapper.selectByGlassId(glassInfo.getGlassId()); if (existing != null) { glassInfo.setId(existing.getId()); // 保留原始创建信息 if (glassInfo.getCreatedTime() == null) { glassInfo.setCreatedTime(existing.getCreatedTime()); } if (glassInfo.getCreatedBy() == null) { glassInfo.setCreatedBy(existing.getCreatedBy()); } // 更新为当前时间 if (glassInfo.getUpdatedTime() == null) { glassInfo.setUpdatedTime(new Date()); } if (glassInfo.getUpdatedBy() == null) { glassInfo.setUpdatedBy("system"); } return updateById(glassInfo); } else { Date now = new Date(); if (glassInfo.getCreatedTime() == null) { glassInfo.setCreatedTime(now); } if (glassInfo.getUpdatedTime() == null) { glassInfo.setUpdatedTime(now); } if (glassInfo.getCreatedBy() == null) { glassInfo.setCreatedBy("system"); } if (glassInfo.getUpdatedBy() == null) { glassInfo.setUpdatedBy("system"); } return save(glassInfo); } } catch (Exception e) { log.error("保存或更新玻璃信息失败, glassInfo={}", glassInfo, e); return false; } } @Override public boolean batchSaveOrUpdateGlassInfo(List glassInfos) { if (glassInfos == null || glassInfos.isEmpty()) { return true; } try { for (GlassInfo glassInfo : glassInfos) { saveOrUpdateGlassInfo(glassInfo); } return true; } catch (Exception e) { log.error("批量保存或更新玻璃信息失败", e); return false; } } @Override public List getRecentScannedGlassIds(Integer minutesAgo, Integer maxCount, String workLine) { try { // 默认查询最近2分钟内的记录,最多返回20条 int minutes = minutesAgo != null && minutesAgo > 0 ? minutesAgo : 2; int limit = maxCount != null && maxCount > 0 ? maxCount : 20; Date timeThreshold = new Date(System.currentTimeMillis() - minutes * 60 * 1000L); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(GlassInfo::getStatus, GlassInfo.Status.PENDING) .ge(GlassInfo::getCreatedTime, timeThreshold) .orderByDesc(GlassInfo::getCreatedTime) .last("LIMIT " + limit); // 如果指定了workLine,则过滤description if (workLine != null && !workLine.trim().isEmpty()) { wrapper.like(GlassInfo::getDescription, "workLine=" + workLine); } List recentGlasses = baseMapper.selectList(wrapper); // 提取玻璃ID列表 return recentGlasses.stream() .map(GlassInfo::getGlassId) .filter(id -> id != null && !id.trim().isEmpty()) .collect(Collectors.toList()); } catch (Exception e) { log.error("查询最近扫码的玻璃ID失败, minutesAgo={}, maxCount={}, workLine={}", minutesAgo, maxCount, workLine, e); return Collections.emptyList(); } } @Override public boolean updateGlassStatus(List glassIds, String status) { if (CollectionUtils.isEmpty(glassIds) || status == null) { return true; } try { LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.in(GlassInfo::getGlassId, glassIds); GlassInfo update = new GlassInfo(); update.setStatus(status); update.setUpdatedTime(new Date()); update.setUpdatedBy("system"); return this.update(update, wrapper); } catch (Exception e) { log.error("批量更新玻璃状态失败, glassIds={}, status={}", glassIds, status, e); return false; } } @Value("${mes.engineering.import-url}") private String mesEngineeringImportUrl; @Override public String getMesEngineeringImportUrl() { return mesEngineeringImportUrl; } @Override public Map buildEngineerImportPayload(List> excelRows) { Map result = new HashMap<>(); if (excelRows == null || excelRows.isEmpty()) { return result; } // 工程号生成:P + yyMMdd + 序号(2位) AtomicInteger seq = new AtomicInteger(1); final String engineerId = generateEngineerId(firstValue(excelRows, "glassId"), seq.getAndIncrement()); final String filmsIdDefault = firstValue(excelRows, "filmsId", "白玻"); final double thicknessDefault = parseDouble(firstValue(excelRows, "thickness"), 0d); // glassInfolList final String engineerIdFinal = engineerId; final String filmsIdDefaultFinal = filmsIdDefault; final double thicknessDefaultFinal = thicknessDefault; List> glassInfolList = excelRows.stream() .flatMap(row -> { int qty = (int) parseDouble(row.getOrDefault("quantity", 1), 1); if (qty <= 0) qty = 1; String glassId = str(row.get("glassId")); String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal); String flowCardId = str(row.get("flowCardId")); String productName = str(row.get("productName")); String customerName = str(row.get("customerName")); double width = parseDouble(row.get("width"), 0d); double height = parseDouble(row.get("height"), 0d); double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal); int finalQty = qty; return range(0, qty).mapToObj(idx -> { String finalGlassId = finalQty > 1 ? glassId + "_" + (idx + 1) : glassId; String finalFlowCardId = flowCardId.isEmpty() ? finalGlassId : flowCardId; Map m = new HashMap<>(); m.put("xAxis", 0); m.put("xCoordinate", 0); m.put("yAxis", 0); m.put("yCoordinate", 0); m.put("glassId", finalGlassId); m.put("engineerId", engineerIdFinal); m.put("flowCardId", finalFlowCardId); m.put("productSortNumber", idx + 1); m.put("hollowCombineDirection", "0"); m.put("width", width); m.put("height", height); m.put("thickness", thickness); m.put("filmsId", filmsId); m.put("layer", 0); m.put("totalLayer", 0); m.put("edgWidth", width); m.put("edgHeight", height); m.put("isMultiple", 0); m.put("maxWidth", width); m.put("maxHeight", height); m.put("isHorizontal", 0); m.put("rawSequence", 0); m.put("temperingLayoutId", 0); m.put("temperingFeedSequence", 0); m.put("angle", 0); m.put("ruleId", 0); m.put("combine", 0); m.put("markIcon", ""); m.put("filmRemove", 0); m.put("flowCardSequence", flowCardId + "/" + (idx + 1)); m.put("process", ""); m.put("rawAngle", 0); m.put("graphNo", 0); m.put("processParam", ""); return m; }); }) .collect(Collectors.toList()); // 原片信息去重 Map> rawGlassMap = new HashMap<>(); for (Map row : excelRows) { double width = parseDouble(row.get("width"), 0d); double height = parseDouble(row.get("height"), 0d); double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal); String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal); String key = width + "_" + height + "_" + thickness + "_" + filmsId; if (!rawGlassMap.containsKey(key)) { Map m = new HashMap<>(); m.put("engineeringId", engineerIdFinal); m.put("filmsId", filmsId); m.put("rawGlassWidth", width); m.put("rawGlassHeight", height); m.put("rawGlassThickness", thickness); m.put("rawSequence", rawGlassMap.size() + 1); m.put("usageRate", "0.95"); rawGlassMap.put(key, m); } } List> engineeringRawQueueList = rawGlassMap.values().stream().collect(Collectors.toList()); // 流程卡信息 Map> flowCardMap = new HashMap<>(); for (Map row : excelRows) { String glassId = str(row.get("glassId")); String flowCardId = str(row.get("flowCardId")); if (flowCardId.isEmpty()) { flowCardId = glassId; } double width = parseDouble(row.get("width"), 0d); double height = parseDouble(row.get("height"), 0d); double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal); String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal); String productName = str(row.get("productName")); String customerName = str(row.get("customerName")); Map exist = flowCardMap.get(flowCardId); if (exist == null) { Map m = new HashMap<>(); m.put("flowCardId", flowCardId); m.put("width", width); m.put("height", height); m.put("thickness", thickness); m.put("filmsId", filmsId); m.put("totalLayer", 0); m.put("layer", 0); m.put("glassTotal", 1); m.put("productName", productName); m.put("customerName", customerName); flowCardMap.put(flowCardId, m); } else { int count = (int) exist.getOrDefault("glassTotal", 1); exist.put("glassTotal", count + 1); } } List> flowCardInfoList = flowCardMap.values().stream().collect(Collectors.toList()); // 汇总 int glassTotal = glassInfolList.size(); double glassTotalArea = glassInfolList.stream() .mapToDouble(m -> parseDouble(m.get("width"), 0d) * parseDouble(m.get("height"), 0d) / 1_000_000d) .sum(); double patternArea = engineeringRawQueueList.stream() .mapToDouble(m -> parseDouble(m.get("rawGlassWidth"), 0d) * parseDouble(m.get("rawGlassHeight"), 0d) / 1_000_000d) .sum(); result.put("engineerId", engineerIdFinal); result.put("engineerName", "工程_" + engineerIdFinal); result.put("avgAvailability", "90"); result.put("validAvailability", "90"); result.put("lastAvailability", "90"); result.put("glassTotal", glassTotal); result.put("glassTotalArea", round2(glassTotalArea)); result.put("planPatternTotal", engineeringRawQueueList.size()); result.put("planPatternTotalArea", round2(patternArea)); result.put("realityPatternTotal", engineeringRawQueueList.size()); result.put("realityPatternTotalArea", round2(patternArea)); result.put("filmsId", filmsIdDefaultFinal); result.put("thickness", thicknessDefaultFinal); result.put("engineeringRawQueueList", engineeringRawQueueList); result.put("glassInfolList", glassInfolList); result.put("flowCardInfoList", flowCardInfoList); result.put("hollowFormulaDetailsList", null); result.put("temperingParameterList", null); return result; } // 日期格式化器(线程不安全,使用ThreadLocal保证线程安全) private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd"); // 数字匹配正则(预编译提升性能) private static final Pattern DIGIT_PATTERN = Pattern.compile("\\d+"); /** * 生成工程师ID * 格式规则:P + 年月日(yyMMdd) + 两位序号 * 序号优先从glassId中提取末尾两位数字,否则使用传入的index补零 * * @param glassId 玻璃ID(可为null,用于提取数字序号) * @param index 备用序号(当glassId无有效数字时使用) * @return 格式化的工程师ID(如:P25010801) */ private String generateEngineerId(Object glassId, int index) { // 1. 生成日期前缀(yyMMdd) String base = LocalDate.now().format(DATE_FORMATTER); // 2. 初始化序号(两位补零) String seq = String.format("%02d", index); // 3. 从glassId中提取末尾两位数字(覆盖默认序号) if (glassId != null) { String glassIdStr = glassId.toString(); Matcher matcher = DIGIT_PATTERN.matcher(glassIdStr); String lastDigitStr = null; // 遍历匹配所有数字段,取最后一个 while (matcher.find()) { lastDigitStr = matcher.group(); } // 若数字段长度≥2,取最后两位;否则保留原序号 if (lastDigitStr != null && lastDigitStr.length() >= 2) { seq = lastDigitStr.substring(lastDigitStr.length() - 2); } } return "P" + base + seq; } /** * 提取List中第一个Map的指定key值(默认空字符串) * * @param rows 数据行列表(可为null/空) * @param key 要提取的键 * @return 第一个Map的key对应值(空则返回"") */ private String firstValue(List> rows, String key) { return firstValue(rows, key, ""); } /** * 提取List中第一个Map的指定key值(自定义默认值) * * @param rows 数据行列表(可为null/空) * @param key 要提取的键 * @param defaultVal 空值时的默认返回值 * @return 第一个Map的key对应值(空则返回defaultVal) */ private String firstValue(List> rows, String key, String defaultVal) { if (rows == null || rows.isEmpty() || key == null) { return defaultVal; } Map firstRow = rows.get(0); Object value = firstRow.get(key); return value == null ? defaultVal : value.toString(); } /** * 对象转字符串(null转空串,自动去除首尾空格) * * @param v 待转换对象 * @return 处理后的字符串 */ private String str(Object v) { return v == null ? "" : v.toString().trim(); } /** * 对象转字符串(空串时返回默认值,自动去除首尾空格) * * @param v 待转换对象 * @param def 空值默认值 * @return 处理后的字符串 */ private String strOrDefault(Object v, String def) { String result = str(v); return result.isEmpty() ? def : result; } /** * 解析对象为double(失败/空值返回默认值) * * @param v 待解析对象(支持数字/字符串类型) * @param def 解析失败时的默认值 * @return 解析后的double值 */ private double parseDouble(Object v, double def) { if (v == null) { return def; } try { if (v instanceof Number) { return ((Number) v).doubleValue(); } return Double.parseDouble(v.toString().trim()); } catch (NumberFormatException e) { // 仅捕获数字格式化异常,避免吞掉其他异常 return def; } } /** * 保留两位小数(四舍五入) * * @param v 原始数值 * @return 保留两位小数后的数值 */ private double round2(double v) { return Math.round(v * 100.0) / 100.0; } }