Files
klp-oa/klp-wms/src/main/java/com/klp/wms/aspect/CoilChangeLogAspect.java
Joshi 2dbbb942ef refactor(wms): 优化卷材变更日志记录逻辑
- 重构加工变换日志记录,支持合并、分卷等不同场景
- 修改快照对比逻辑,合并新旧键值以避免遗漏空值字段
- 调整仓库层级排序算法,移除四级库位的序号修正
- 注释掉卷材合同关系中的日志记录代码
- 在撤回导出时清空操作人员信息
2026-07-11 16:49:20 +08:00

363 lines
14 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 = extractOldCoilIds(joinPoint, coilChangeLog);
// 2. 查询旧卷快照
Map<Long, Map<String, Object>> oldSnapshots = new LinkedHashMap<>();
for (Long coilId : oldCoilIds) {
if (coilId != null) {
WmsMaterialCoil coil = coilMapper.selectById(coilId);
if (coil != null) {
oldSnapshots.put(coilId, beanToSnapshot(coil));
}
}
}
// 3. 执行目标方法
Object result = joinPoint.proceed();
// 4. 确定新卷 ID 列表
List<Long> newCoilIds = resolveNewCoilIds(result, changeType, oldCoilIds);
// 5. 查询新卷快照并写入日志try-catch 包裹,不影响主流程)
try {
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;
}
}