ソースを参照

feat(annotation): 增强ChangeRecordLog注解功能

- 新增idParam属性,支持指定ID参数名称
- 新增mapper属性,支持指定Mapper类
- 优化ID值提取逻辑,支持从基本类型参数直接获取
- 改进查询修改前数据的逻辑,支持通过指定Mapper查询
- 增加从Mapper泛型参数中自动提取实体类的功能
- 完善日志记录,提高调试信息的详细程度
jyc 5 日 前
コミット
7ee0b1f367

+ 12 - 0
alien-store/src/main/java/shop/alien/store/annotation/ChangeRecordLog.java

@@ -1,5 +1,6 @@
 package shop.alien.store.annotation;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import shop.alien.store.enums.OperationType;
 
 import java.lang.annotation.*;
@@ -41,8 +42,19 @@ public @interface ChangeRecordLog  {
     String idField() default "id";
 
     /**
+     * ID參數名稱(默認為空,如果方法參數中ID參數的名稱不是"id",可指定,如:"holidayId"、"userId"等)
+     * 如果指定了此參數,會優先從方法參數中按名稱查找ID值
+     */
+    String idParam() default "";
+
+    /**
      * 是否记录修改后的数据
      */
     boolean recordAfter() default true;
+
+    /**
+     * Mapper类(用于查询修改前的数据,如果不指定则从Spring容器中查找)
+     */
+    Class<? extends BaseMapper> mapper() default BaseMapper.class;
 }
 

+ 223 - 81
alien-store/src/main/java/shop/alien/store/aspect/OperationLogAspect.java

@@ -98,25 +98,57 @@ public class OperationLogAspect implements ApplicationContextAware {
                 Object paramObj = null;
                 Object idValue = null;
                 
-                // 查找包含ID的參數對象
-                if (args != null && parameterNames != null) {
+                // 優先使用注解中指定的ID參數名
+                if (annotation != null && StringUtils.isNotBlank(annotation.idParam()) && args != null && parameterNames != null) {
+                    for (int i = 0; i < parameterNames.length; i++) {
+                        if (annotation.idParam().equals(parameterNames[i]) && args[i] != null) {
+                            idValue = args[i];
+                            log.info("從注解指定的參數名中獲取ID: 參數名={}, ID值={}, 類型: {}", 
+                                    annotation.idParam(), idValue, args[i].getClass().getSimpleName());
+                            break;
+                        }
+                    }
+                }
+                
+                // 如果沒有通過指定參數名找到ID,則使用原來的邏輯
+                if (idValue == null && args != null && parameterNames != null) {
                     for (int i = 0; i < args.length; i++) {
                         Object arg = args[i];
-                        if (arg != null && !isPrimitiveOrWrapper(arg.getClass()) && !isString(arg.getClass())) {
-                            // 嘗試從對象中提取ID
-                            idValue = extractIdFromObject(arg, annotation.idField());
-                            if (idValue != null) {
-                                paramObj = arg;
-                                log.info("從參數中提取到ID: {}, 對象類型: {}", idValue, arg.getClass().getSimpleName());
-                                break;
+                        if (arg != null) {
+                            // 如果參數本身就是ID(Integer、Long、String等基本類型)
+                            if (isPrimitiveOrWrapper(arg.getClass()) || isString(arg.getClass())) {
+                                // 如果指定了mapper,可以直接使用這個參數作為ID
+                                if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                                    // 如果沒有指定idParam,或者參數名是"id",則使用
+                                    if (StringUtils.isBlank(annotation.idParam()) || "id".equals(parameterNames[i])) {
+                                        idValue = arg;
+                                        log.info("參數本身就是ID: {}, 參數名: {}, 類型: {}", 
+                                                idValue, parameterNames[i], arg.getClass().getSimpleName());
+                                        break;
+                                    }
+                                }
+                            } else {
+                                // 嘗試從對象中提取ID
+                                idValue = extractIdFromObject(arg, annotation.idField());
+                                if (idValue != null) {
+                                    paramObj = arg;
+                                    log.info("從參數中提取到ID: {}, 對象類型: {}", idValue, arg.getClass().getSimpleName());
+                                    break;
+                                }
                             }
                         }
                     }
                 }
                 
                 // 如果找到了ID,通過Mapper查詢原始數據(方法執行前)
-                if (idValue != null && paramObj != null) {
-                    beforeDataObj = queryBeforeDataById(paramObj.getClass(), idValue);
+                // 如果注解中指定了mapper,可以直接使用mapper查詢,不需要paramObj
+                if (idValue != null) {
+                    Class<?> entityClass = paramObj != null ? paramObj.getClass() : null;
+                    // 如果指定了mapper,實體類會從mapper中提取,所以entityClass可以為null
+                    if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                        entityClass = null; // 實體類將從mapper中提取
+                    }
+                    beforeDataObj = queryBeforeDataById(entityClass, idValue, annotation);
                     if (beforeDataObj != null) {
                         log.info("✓ 成功查詢到修改前的數據,類型: {}", beforeDataObj.getClass().getSimpleName());
                         log.info("修改前的數據JSON: {}", JSON.toJSONString(beforeDataObj));
@@ -447,42 +479,71 @@ public class OperationLogAspect implements ApplicationContextAware {
                     Object paramObj = null;
                     Object idValue = null;
                     
-                    // 查找包含ID的參數對象
-                    if (args != null && parameterNames != null) {
+                    // 優先使用注解中指定的ID參數名
+                    if (annotation != null && StringUtils.isNotBlank(annotation.idParam()) && args != null && parameterNames != null) {
+                        for (int i = 0; i < parameterNames.length; i++) {
+                            if (annotation.idParam().equals(parameterNames[i]) && args[i] != null) {
+                                idValue = args[i];
+                                log.info("從注解指定的參數名中獲取ID: 參數名={}, ID值={}, 類型: {}", 
+                                        annotation.idParam(), idValue, args[i].getClass().getSimpleName());
+                                break;
+                            }
+                        }
+                    }
+                    
+                    // 如果沒有通過指定參數名找到ID,則使用原來的邏輯
+                    if (idValue == null && args != null && parameterNames != null) {
                         for (int i = 0; i < args.length; i++) {
                             Object arg = args[i];
-                            if (arg != null && !isPrimitiveOrWrapper(arg.getClass()) && !isString(arg.getClass())) {
-                                // 嘗試從對象中提取ID
-                                idValue = extractIdFromObject(arg, annotation.idField());
-                                if (idValue != null) {
-                                    paramObj = arg;
-                                    log.info("從參數中提取到ID: {}, 對象類型: {}", idValue, arg.getClass().getSimpleName());
-                                    break;
-                                }
-                            } else if (arg != null && (arg instanceof Integer || arg instanceof Long || arg instanceof String)) {
-                                // 如果參數本身就是ID
-                                idValue = arg;
-                                log.info("參數本身就是ID: {}", idValue);
-                                // 需要找到對應的實體類,這裡先嘗試從其他參數中找
-                                for (int j = 0; j < args.length; j++) {
-                                    if (j != i && args[j] != null && 
-                                        !isPrimitiveOrWrapper(args[j].getClass()) && 
-                                        !isString(args[j].getClass())) {
-                                        paramObj = args[j];
+                            if (arg != null) {
+                                // 如果參數本身就是ID(Integer、Long、String等基本類型)
+                                if (isPrimitiveOrWrapper(arg.getClass()) || isString(arg.getClass())) {
+                                    // 如果指定了mapper,可以直接使用這個參數作為ID
+                                    if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                                        // 如果沒有指定idParam,或者參數名是"id",則使用
+                                        if (StringUtils.isBlank(annotation.idParam()) || "id".equals(parameterNames[i])) {
+                                            idValue = arg;
+                                            log.info("參數本身就是ID: {}, 參數名: {}, 類型: {}", 
+                                                    idValue, parameterNames[i], arg.getClass().getSimpleName());
+                                            break;
+                                        }
+                                    } else {
+                                        // 沒有指定mapper,需要找到對應的實體類
+                                        idValue = arg;
+                                        log.info("參數本身就是ID: {}", idValue);
+                                        // 嘗試從其他參數中找實體對象
+                                        for (int j = 0; j < args.length; j++) {
+                                            if (j != i && args[j] != null && 
+                                                !isPrimitiveOrWrapper(args[j].getClass()) && 
+                                                !isString(args[j].getClass())) {
+                                                paramObj = args[j];
+                                                break;
+                                            }
+                                        }
+                                        break;
+                                    }
+                                } else {
+                                    // 嘗試從對象中提取ID
+                                    idValue = extractIdFromObject(arg, annotation.idField());
+                                    if (idValue != null) {
+                                        paramObj = arg;
+                                        log.info("從參數中提取到ID: {}, 對象類型: {}", idValue, arg.getClass().getSimpleName());
                                         break;
                                     }
                                 }
-                                break;
                             }
                         }
                     }
                     
                     // 如果找到了ID,通過Mapper查詢原始數據
-                    if (idValue != null && paramObj != null) {
-                        beforeDataObj = queryBeforeDataById(paramObj.getClass(), idValue);
-                    } else if (idValue != null) {
-                        // 只有ID,沒有對象,嘗試通過ID類型推斷實體類
-                        log.warn("只有ID沒有對象,無法自動查詢原始數據。ID: {}", idValue);
+                    // 如果注解中指定了mapper,可以直接使用mapper查詢,不需要paramObj
+                    if (idValue != null) {
+                        Class<?> entityClass = paramObj != null ? paramObj.getClass() : null;
+                        // 如果指定了mapper,實體類會從mapper中提取,所以entityClass可以為null
+                        if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                            entityClass = null; // 實體類將從mapper中提取
+                        }
+                        beforeDataObj = queryBeforeDataById(entityClass, idValue, annotation);
                     } else {
                         log.warn("無法從參數中提取ID,跳過查詢修改前的數據");
                     }
@@ -727,78 +788,159 @@ public class OperationLogAspect implements ApplicationContextAware {
     /**
      * 通過ID查詢修改前的數據
      */
-    private Object queryBeforeDataById(Class<?> entityClass, Object id) {
-        if (id == null || entityClass == null) {
+    private Object queryBeforeDataById(Class<?> entityClass, Object id, shop.alien.store.annotation.ChangeRecordLog annotation) {
+        if (id == null) {
             return null;
         }
         
         try {
-            // 根據實體類名推斷Mapper名稱
-            // 例如:EssentialHolidayComparison -> EssentialHolidayComparisonMapper
-            String entityName = entityClass.getSimpleName();
-            String mapperBeanName = entityName.substring(0, 1).toLowerCase() + entityName.substring(1) + "Mapper";
+            BaseMapper<?> mapper = null;
+            Class<?> actualEntityClass = entityClass;
             
-            // 嘗試從Spring容器中獲取Mapper
-            if (applicationContext != null) {
+            // 優先使用注解中指定的Mapper
+            if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                try {
+                    if (applicationContext != null) {
+                        // 從Spring容器中獲取指定類型的Mapper實例
+                        mapper = applicationContext.getBean(annotation.mapper());
+                        log.info("使用注解中指定的Mapper: {}", annotation.mapper().getSimpleName());
+                        
+                        // 從Mapper的泛型參數中提取實體類
+                        actualEntityClass = extractEntityClassFromMapper(annotation.mapper());
+                        if (actualEntityClass != null) {
+                            log.info("從Mapper泛型中提取到實體類: {}", actualEntityClass.getSimpleName());
+                        } else {
+                            log.warn("無法從Mapper泛型中提取實體類,使用參數中的實體類");
+                            actualEntityClass = entityClass;
+                        }
+                    }
+                } catch (Exception e) {
+                    log.warn("無法從容器中獲取注解指定的Mapper: {}", annotation.mapper().getSimpleName(), e);
+                }
+            }
+            
+            // 如果注解中沒有指定Mapper或獲取失敗,則從容器中查找
+            if (mapper == null && applicationContext != null) {
+                if (entityClass == null) {
+                    log.warn("未指定Mapper且無法從參數中獲取實體類,無法查詢數據");
+                    return null;
+                }
+                
+                // 根據實體類名推斷Mapper名稱
+                // 例如:EssentialHolidayComparison -> EssentialHolidayComparisonMapper
+                String entityName = entityClass.getSimpleName();
+                String mapperBeanName = entityName.substring(0, 1).toLowerCase() + entityName.substring(1) + "Mapper";
+                
                 try {
                     // 先嘗試標準命名
-                    BaseMapper<?> mapper = (BaseMapper<?>) applicationContext.getBean(mapperBeanName);
+                    mapper = (BaseMapper<?>) applicationContext.getBean(mapperBeanName);
                     if (mapper != null) {
-                        // selectById 需要 Serializable 類型
-                        Serializable serializableId = convertToSerializable(id);
-                        if (serializableId != null) {
-                            Object result = mapper.selectById(serializableId);
-                            log.info("通過Mapper查詢成功,Mapper: {}, ID: {}", mapperBeanName, id);
-                            return result;
-                        }
+                        log.info("通過標準命名找到Mapper: {}", mapperBeanName);
                     }
                 } catch (Exception e) {
                     log.debug("無法獲取Mapper: {}", mapperBeanName, e);
                 }
                 
-                // 嘗試查找所有BaseMapper類型的Bean
-                try {
-                    String[] beanNames = applicationContext.getBeanNamesForType(BaseMapper.class);
-                    for (String beanName : beanNames) {
-                        try {
-                            BaseMapper<?> mapper = applicationContext.getBean(beanName, BaseMapper.class);
-                            // 檢查Mapper的泛型類型是否匹配
-                            java.lang.reflect.Type[] types = mapper.getClass().getGenericInterfaces();
-                            for (java.lang.reflect.Type type : types) {
-                                if (type instanceof java.lang.reflect.ParameterizedType) {
-                                    java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) type;
-                                    if (pt.getRawType().equals(BaseMapper.class)) {
-                                        java.lang.reflect.Type[] actualTypes = pt.getActualTypeArguments();
-                                        if (actualTypes.length > 0 && actualTypes[0] instanceof Class) {
-                                            Class<?> mapperEntityClass = (Class<?>) actualTypes[0];
-                                            if (mapperEntityClass.equals(entityClass)) {
-                                                // selectById 需要 Serializable 類型
-                                                Serializable serializableId = convertToSerializable(id);
-                                                if (serializableId != null) {
-                                                    Object result = mapper.selectById(serializableId);
-                                                    log.info("通過Mapper查詢成功,Mapper: {}, ID: {}", beanName, id);
-                                                    return result;
+                // 如果標準命名失敗,嘗試查找所有BaseMapper類型的Bean
+                if (mapper == null) {
+                    try {
+                        String[] beanNames = applicationContext.getBeanNamesForType(BaseMapper.class);
+                        for (String beanName : beanNames) {
+                            try {
+                                BaseMapper<?> candidateMapper = applicationContext.getBean(beanName, BaseMapper.class);
+                                // 檢查Mapper的泛型類型是否匹配
+                                java.lang.reflect.Type[] types = candidateMapper.getClass().getGenericInterfaces();
+                                for (java.lang.reflect.Type type : types) {
+                                    if (type instanceof java.lang.reflect.ParameterizedType) {
+                                        java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) type;
+                                        if (pt.getRawType().equals(BaseMapper.class)) {
+                                            java.lang.reflect.Type[] actualTypes = pt.getActualTypeArguments();
+                                            if (actualTypes.length > 0 && actualTypes[0] instanceof Class) {
+                                                Class<?> mapperEntityClass = (Class<?>) actualTypes[0];
+                                                if (mapperEntityClass.equals(entityClass)) {
+                                                    mapper = candidateMapper;
+                                                    log.info("通過泛型匹配找到Mapper: {}", beanName);
+                                                    break;
                                                 }
                                             }
                                         }
                                     }
                                 }
+                                if (mapper != null) {
+                                    break;
+                                }
+                            } catch (Exception e) {
+                                // 繼續查找下一個
                             }
-                        } catch (Exception e) {
-                            // 繼續查找下一個
                         }
+                    } catch (Exception e) {
+                        log.debug("查找Mapper失敗", e);
                     }
-                } catch (Exception e) {
-                    log.debug("查找Mapper失敗", e);
                 }
             }
             
-            log.warn("無法找到對應的Mapper,實體類: {}, ID: {}", entityClass.getSimpleName(), id);
+            // 使用找到的Mapper查詢數據
+            if (mapper != null) {
+                Serializable serializableId = convertToSerializable(id);
+                if (serializableId != null) {
+                    Object result = mapper.selectById(serializableId);
+                    log.info("通過Mapper查詢成功,Mapper: {}, 實體類: {}, ID: {}", 
+                            mapper.getClass().getSimpleName(), 
+                            actualEntityClass != null ? actualEntityClass.getSimpleName() : "未知", 
+                            id);
+                    return result;
+                }
+            }
+            
+            log.warn("無法找到對應的Mapper,實體類: {}, ID: {}", 
+                    actualEntityClass != null ? actualEntityClass.getSimpleName() : "未知", id);
             return null;
         } catch (Exception e) {
-            log.error("查詢修改前的數據失敗,實體類: {}, ID: {}", entityClass.getSimpleName(), id, e);
+            log.error("查詢修改前的數據失敗,實體類: {}, ID: {}", 
+                    entityClass != null ? entityClass.getSimpleName() : "未知", id, e);
+            return null;
+        }
+    }
+    
+    /**
+     * 從Mapper類的泛型參數中提取實體類
+     */
+    private Class<?> extractEntityClassFromMapper(Class<? extends BaseMapper> mapperClass) {
+        if (mapperClass == null || mapperClass == BaseMapper.class) {
             return null;
         }
+        
+        try {
+            // 獲取Mapper接口的泛型參數
+            java.lang.reflect.Type[] types = mapperClass.getGenericInterfaces();
+            for (java.lang.reflect.Type type : types) {
+                if (type instanceof java.lang.reflect.ParameterizedType) {
+                    java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) type;
+                    if (pt.getRawType().equals(BaseMapper.class)) {
+                        java.lang.reflect.Type[] actualTypes = pt.getActualTypeArguments();
+                        if (actualTypes.length > 0 && actualTypes[0] instanceof Class) {
+                            return (Class<?>) actualTypes[0];
+                        }
+                    }
+                }
+            }
+            
+            // 如果直接繼承的接口沒有泛型,嘗試從父類獲取
+            java.lang.reflect.Type superclass = mapperClass.getGenericSuperclass();
+            if (superclass instanceof java.lang.reflect.ParameterizedType) {
+                java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) superclass;
+                if (pt.getRawType().equals(BaseMapper.class)) {
+                    java.lang.reflect.Type[] actualTypes = pt.getActualTypeArguments();
+                    if (actualTypes.length > 0 && actualTypes[0] instanceof Class) {
+                        return (Class<?>) actualTypes[0];
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.warn("從Mapper類中提取實體類失敗: {}", mapperClass.getSimpleName(), e);
+        }
+        
+        return null;
     }
 
     /**