package com.klp.wms.aspect; import cn.hutool.core.bean.BeanUtil; import cn.hutool.json.JSONUtil; import com.klp.domain.WmsCoilChangeLog; import com.klp.domain.WmsMaterialCoil; import com.klp.mapper.WmsCoilChangeLogMapper; import com.klp.mapper.WmsMaterialCoilMapper; import com.klp.wms.annotation.CoilChangeLog; import com.klp.wms.enums.CoilChangeType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.*; /** * 钢卷变更日志切面 *

* 拦截标注了 @CoilChangeLog 的方法,记录变更前后的差异。 * * @author klp * @date 2026-07-11 */ @Slf4j @Aspect @Component @RequiredArgsConstructor public class CoilChangeLogAspect { private final WmsMaterialCoilMapper coilMapper; private final WmsCoilChangeLogMapper changeLogMapper; /** 不需要参与差异比较的字段 */ private static final Set EXCLUDE_DIFF_FIELDS = new HashSet<>(Arrays.asList( "coilId", "createBy", "createTime", "updateBy", "updateTime", "delFlag", "searchValue", "params" )); @Around("@annotation(coilChangeLog)") public Object around(ProceedingJoinPoint joinPoint, CoilChangeLog coilChangeLog) throws Throwable { CoilChangeType changeType = coilChangeLog.changeType(); // 1. 提取旧卷 ID 列表(异常不影响主流程) List oldCoilIds; try { oldCoilIds = extractOldCoilIds(joinPoint, coilChangeLog); } catch (Exception e) { log.warn("提取旧卷ID失败,跳过变更日志: {}", e.getMessage()); return joinPoint.proceed(); } // 2. 查询旧卷快照(异常不影响主流程) Map> oldSnapshots = new LinkedHashMap<>(); try { for (Long coilId : oldCoilIds) { if (coilId != null) { WmsMaterialCoil coil = coilMapper.selectById(coilId); if (coil != null) { oldSnapshots.put(coilId, beanToSnapshot(coil)); } } } } catch (Exception e) { log.warn("查询旧卷快照失败,跳过变更日志: {}", e.getMessage()); return joinPoint.proceed(); } // 3. 执行目标方法 Object result = joinPoint.proceed(); // 4. 确定新卷 ID + 写入日志(整块 try-catch,不影响主流程) try { List newCoilIds = resolveNewCoilIds(result, changeType, oldCoilIds); if (changeType == CoilChangeType.DELETE) { // 删除:只有旧快照,无新快照 for (Long oldId : oldCoilIds) { Map oldSnapshot = oldSnapshots.get(oldId); insertLog(changeType, oldId, null, oldSnapshot, null, coilChangeLog.value()); } } else if (changeType == CoilChangeType.INSERT) { // 新建:无旧快照,只有新快照 for (Long newId : newCoilIds) { Map newSnapshot = queryCoilSnapshot(newId); insertLog(changeType, null, newId, null, newSnapshot, coilChangeLog.value()); } } else if (changeType.isProcessing()) { // 加工变换:旧卷 → 新卷 Long commonNewId = newCoilIds.isEmpty() ? null : newCoilIds.get(0); if (changeType == CoilChangeType.MERGE) { // N旧→1新:每个旧卷都指向同一个新卷 for (int i = 0; i < oldCoilIds.size(); i++) { Long oldId = oldCoilIds.get(i); Map oldSnapshot = oldSnapshots.get(oldId); Map newSnapshot = commonNewId != null ? queryCoilSnapshot(commonNewId) : null; insertLog(changeType, oldId, commonNewId, oldSnapshot, newSnapshot, coilChangeLog.value()); } } else if (oldCoilIds.size() == 1 && newCoilIds.size() > 1) { // 1旧→N新(分卷):每个新卷一条日志,都指向同一个旧母卷 Long oldId = oldCoilIds.get(0); Map oldSnapshot = oldSnapshots.get(oldId); String label = coilChangeLog.value() + "-分卷"; for (Long newId : newCoilIds) { Map newSnapshot = queryCoilSnapshot(newId); insertLog(changeType, oldId, newId, oldSnapshot, newSnapshot, label); } } else { // 一一对应:单步加工/回滚/单子卷等 for (int i = 0; i < oldCoilIds.size(); i++) { Long oldId = oldCoilIds.get(i); Long newId = (i < newCoilIds.size()) ? newCoilIds.get(i) : null; Map oldSnapshot = oldSnapshots.get(oldId); Map newSnapshot = (newId != null) ? queryCoilSnapshot(newId) : null; insertLog(changeType, oldId, newId, oldSnapshot, newSnapshot, coilChangeLog.value()); } } } else { // 原地修改:相同 ID for (Long coilId : oldCoilIds) { Map oldSnapshot = oldSnapshots.get(coilId); Map newSnapshot = queryCoilSnapshot(coilId); insertLog(changeType, coilId, coilId, oldSnapshot, newSnapshot, coilChangeLog.value()); } } } catch (Exception e) { log.warn("记录钢卷变更日志失败: {}", e.getMessage()); } return result; } // ==================== 参数提取 ==================== /** * 从方法参数中提取旧卷 ID 列表 */ private List extractOldCoilIds(ProceedingJoinPoint joinPoint, CoilChangeLog annotation) { Object[] args = joinPoint.getArgs(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String[] paramNames = signature.getParameterNames(); List ids = new ArrayList<>(); // 优先从 BO 字段提取 String boField = annotation.coilIdBoField(); if (boField != null && !boField.isEmpty()) { for (Object arg : args) { if (arg != null && !isBasicType(arg)) { Long id = getFieldValue(arg, boField); if (id != null) { // bo.getCoilId() 非空 → 分卷场景:外层就是旧母卷 ids.add(id); } else { // bo.getCoilId() 为空 → 合卷场景:newCoils 里的才是旧卷 List newCoils = getFieldValue(arg, "newCoils", List.class); if (newCoils != null) { for (Object item : newCoils) { Long itemId = getFieldValue(item, boField); if (itemId != null) { ids.add(itemId); } } } } } } if (!ids.isEmpty()) { return ids; } } // 按参数名提取 String paramName = annotation.coilIdParam(); for (int i = 0; i < paramNames.length; i++) { if (paramName.equals(paramNames[i])) { Object val = args[i]; if (val instanceof Long) { ids.add((Long) val); } else if (val instanceof Collection) { for (Object item : (Collection) val) { if (item instanceof Long) { ids.add((Long) item); } } } } } return ids; } // ==================== 新卷 ID 解析 ==================== /** * 从方法返回值中解析新卷 ID */ private List resolveNewCoilIds(Object result, CoilChangeType changeType, List oldCoilIds) { List newIds = new ArrayList<>(); if (result == null) { return newIds; } if (result instanceof Long) { newIds.add((Long) result); } else if (result instanceof Integer) { newIds.add(((Integer) result).longValue()); } else if (result instanceof String) { // 可能是单个 ID 或逗号分隔的 ID 列表 String str = (String) result; if (str.contains(",")) { for (String part : str.split(",")) { try { newIds.add(Long.parseLong(part.trim())); } catch (NumberFormatException ignored) { } } } else { try { newIds.add(Long.parseLong(str.trim())); } catch (NumberFormatException ignored) { } } } else if (result instanceof Map) { // 尝试从 Map 中提取 coilId 或 newCoilId Map map = (Map) result; Object id = map.get("coilId"); if (id == null) { id = map.get("newCoilId"); } if (id instanceof Long) { newIds.add((Long) id); } } else { // 尝试反射获取 coilId Long id = getFieldValue(result, "coilId"); if (id != null) { newIds.add(id); } } // 如果无法解析新 ID,对于非加工类型,使用旧 ID if (newIds.isEmpty() && !changeType.isProcessing() && changeType != CoilChangeType.DELETE) { return oldCoilIds; } return newIds; } // ==================== 快照与差异 ==================== /** * 将钢卷实体转为快照 Map(排除无关字段) */ private Map beanToSnapshot(WmsMaterialCoil coil) { Map map = BeanUtil.beanToMap(coil, false, true); // 转换为字符串值以便 JSON 序列化 Map snapshot = new LinkedHashMap<>(); for (Map.Entry entry : map.entrySet()) { if (!EXCLUDE_DIFF_FIELDS.contains(entry.getKey())) { snapshot.put(entry.getKey(), entry.getValue()); } } return snapshot; } /** * 按 ID 查询钢卷并转为快照 */ private Map queryCoilSnapshot(Long coilId) { if (coilId == null) { return Collections.emptyMap(); } WmsMaterialCoil coil = coilMapper.selectById(coilId); return coil != null ? beanToSnapshot(coil) : Collections.emptyMap(); } /** * 对比新旧快照,返回变更字段差异 Map */ private Map> computeDiff(Map oldSnapshot, Map newSnapshot) { Map> diff = new LinkedHashMap<>(); // 检查旧值变更或新增 Set allFields = new LinkedHashSet<>(); if (oldSnapshot != null) allFields.addAll(oldSnapshot.keySet()); if (newSnapshot != null) allFields.addAll(newSnapshot.keySet()); for (String field : allFields) { if (EXCLUDE_DIFF_FIELDS.contains(field)) { continue; } Object oldVal = (oldSnapshot != null) ? oldSnapshot.get(field) : null; Object newVal = (newSnapshot != null) ? newSnapshot.get(field) : null; if (!Objects.equals(oldVal, newVal)) { Map change = new LinkedHashMap<>(); change.put("old", oldVal); change.put("new", newVal); diff.put(field, change); } } return diff; } // ==================== 日志写入 ==================== private void insertLog(CoilChangeType changeType, Long oldCoilId, Long newCoilId, Map oldSnapshot, Map newSnapshot, String operationDesc) { Map> diff = computeDiff(oldSnapshot, newSnapshot); WmsCoilChangeLog logRecord = new WmsCoilChangeLog(); logRecord.setChangeType(changeType.name()); logRecord.setOldCoilId(oldCoilId); logRecord.setNewCoilId(newCoilId); logRecord.setOldCoilNo(snapshotValue(oldSnapshot, "currentCoilNo")); logRecord.setNewCoilNo(snapshotValue(newSnapshot, "currentCoilNo")); logRecord.setChangedFields(diff.isEmpty() ? null : JSONUtil.toJsonStr(diff)); logRecord.setOperationDesc(operationDesc); changeLogMapper.insert(logRecord); } private String snapshotValue(Map snapshot, String key) { if (snapshot == null) return null; Object val = snapshot.get(key); return val != null ? String.valueOf(val) : null; } // ==================== 反射工具 ==================== private Long getFieldValue(Object obj, String fieldName) { try { String getter = "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); Method method = obj.getClass().getMethod(getter); Object val = method.invoke(obj); if (val instanceof Long) { return (Long) val; } } catch (Exception ignored) { } return null; } @SuppressWarnings("unchecked") private T getFieldValue(Object obj, String fieldName, Class type) { try { String getter = "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); Method method = obj.getClass().getMethod(getter); Object val = method.invoke(obj); if (type.isInstance(val)) { return (T) val; } } catch (Exception ignored) { } return null; } private boolean isBasicType(Object obj) { return obj instanceof String || obj instanceof Number || obj instanceof Boolean || obj instanceof Date; } }