Files
klp-oa/klp-wms/src/main/java/com/klp/wms/aspect/CoilChangeLogAspect.java
Joshi cd3afc634c feat(anneal): 添加退火计划钢卷查询功能并优化变更日志处理
- 在IWmsAnnealPerformanceService中添加queryPlanCoils方法用于查询退火计划中的加工前钢卷
- 在WmsAnnealPerformanceController中添加/rawCoils端点暴露钢卷查询功能
- 在WmsAnnealPerformanceServiceImpl中实现完整的钢卷查询逻辑,包括按计划条件筛选和关联查询
- 修改CoilChangeLogAspect切面,在旧卷ID提取和快照查询中添加异常处理,确保异常不影响主流程
- 将变更日志的确定新卷ID和写入日志整体包装在try-catch中,提升系统稳定性
2026-07-11 16:50:44 +08:00

372 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.*;
/**
* 钢卷变更日志切面
* <p>
* 拦截标注了 @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<String> 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<Long> oldCoilIds;
try {
oldCoilIds = extractOldCoilIds(joinPoint, coilChangeLog);
} catch (Exception e) {
log.warn("提取旧卷ID失败跳过变更日志: {}", e.getMessage());
return joinPoint.proceed();
}
// 2. 查询旧卷快照(异常不影响主流程)
Map<Long, Map<String, Object>> 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<Long> newCoilIds = resolveNewCoilIds(result, changeType, oldCoilIds);
if (changeType == CoilChangeType.DELETE) {
// 删除:只有旧快照,无新快照
for (Long oldId : oldCoilIds) {
Map<String, Object> oldSnapshot = oldSnapshots.get(oldId);
insertLog(changeType, oldId, null, oldSnapshot, null, coilChangeLog.value());
}
} else if (changeType == CoilChangeType.INSERT) {
// 新建:无旧快照,只有新快照
for (Long newId : newCoilIds) {
Map<String, Object> 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<String, Object> oldSnapshot = oldSnapshots.get(oldId);
Map<String, Object> 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<String, Object> oldSnapshot = oldSnapshots.get(oldId);
String label = coilChangeLog.value() + "-分卷";
for (Long newId : newCoilIds) {
Map<String, Object> 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<String, Object> oldSnapshot = oldSnapshots.get(oldId);
Map<String, Object> newSnapshot = (newId != null) ? queryCoilSnapshot(newId) : null;
insertLog(changeType, oldId, newId, oldSnapshot, newSnapshot, coilChangeLog.value());
}
}
} else {
// 原地修改:相同 ID
for (Long coilId : oldCoilIds) {
Map<String, Object> oldSnapshot = oldSnapshots.get(coilId);
Map<String, Object> newSnapshot = queryCoilSnapshot(coilId);
insertLog(changeType, coilId, coilId, oldSnapshot, newSnapshot, coilChangeLog.value());
}
}
} catch (Exception e) {
log.warn("记录钢卷变更日志失败: {}", e.getMessage());
}
return result;
}
// ==================== 参数提取 ====================
/**
* 从方法参数中提取旧卷 ID 列表
*/
private List<Long> extractOldCoilIds(ProceedingJoinPoint joinPoint, CoilChangeLog annotation) {
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] paramNames = signature.getParameterNames();
List<Long> 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<Long> resolveNewCoilIds(Object result, CoilChangeType changeType, List<Long> oldCoilIds) {
List<Long> 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<String, Object> beanToSnapshot(WmsMaterialCoil coil) {
Map<String, Object> map = BeanUtil.beanToMap(coil, false, true);
// 转换为字符串值以便 JSON 序列化
Map<String, Object> snapshot = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!EXCLUDE_DIFF_FIELDS.contains(entry.getKey())) {
snapshot.put(entry.getKey(), entry.getValue());
}
}
return snapshot;
}
/**
* 按 ID 查询钢卷并转为快照
*/
private Map<String, Object> queryCoilSnapshot(Long coilId) {
if (coilId == null) {
return Collections.emptyMap();
}
WmsMaterialCoil coil = coilMapper.selectById(coilId);
return coil != null ? beanToSnapshot(coil) : Collections.emptyMap();
}
/**
* 对比新旧快照,返回变更字段差异 Map
*/
private Map<String, Map<String, Object>> computeDiff(Map<String, Object> oldSnapshot,
Map<String, Object> newSnapshot) {
Map<String, Map<String, Object>> diff = new LinkedHashMap<>();
// 检查旧值变更或新增
Set<String> 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<String, Object> 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<String, Object> oldSnapshot, Map<String, Object> newSnapshot,
String operationDesc) {
Map<String, Map<String, Object>> 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<String, Object> 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> T getFieldValue(Object obj, String fieldName, Class<T> 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;
}
}