Преглед изворни кода

Merge remote-tracking branch 'origin/sit-plantform' into sit-plantform

lyx пре 4 дана
родитељ
комит
7cd161a577

+ 2 - 2
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondUserViolationDetailVo.java

@@ -36,10 +36,10 @@ public class SecondUserViolationDetailVo extends SecondUserViolationVo{
 
     // 动态信息
     @ApiModelProperty(value = "动态信息")
-    List<LifeUserDynamics> dynamicsList;
+    LifeUserDynamics dynamicsInfo;
 
     // 评论信息
     @ApiModelProperty(value = "评论信息")
-    List<LifeComment> commentList;
+    LifeComment commentInfo;
 
 }

+ 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;
     }
 
     /**

+ 9 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java

@@ -6,6 +6,7 @@ import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.EssentialHolidayComparison;
 import shop.alien.entity.store.LifeCoupon;
@@ -181,4 +182,12 @@ public class LifeCouponController {
         }
         return R.fail("失败");
     }
+
+    @ApiOperation("导入假期管理")
+    //@ApiImplicitParams({@ApiImplicitParam(name = "file", value = "Excel文件", dataType = "MultipartFile", paramType = "form", required = true)})
+    @PostMapping("/importHoliday")
+    public R<String> importHoliday(MultipartFile file) {
+        log.info("LifeCouponController.importHoliday fileName={}", file.getOriginalFilename());
+        return lifeCouponService.importHolidayFromExcel(file);
+    }
 }

+ 8 - 0
alien-store/src/main/java/shop/alien/store/service/LifeCouponService.java

@@ -3,6 +3,7 @@ package shop.alien.store.service;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import org.springframework.web.bind.annotation.RequestParam;
 import shop.alien.entity.store.EssentialHolidayComparison;
@@ -63,4 +64,11 @@ public interface LifeCouponService extends IService<LifeCoupon> {
      * @return LifeCouponVo
      */
     shop.alien.entity.store.vo.LifeCouponVo getNewCouponDetail(String id);
+
+    /**
+     * 导入假期管理
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importHolidayFromExcel(MultipartFile file);
 }

+ 265 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java

@@ -8,10 +8,14 @@ import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.LifeDiscountCouponStoreFriendDto;
@@ -23,8 +27,10 @@ import shop.alien.store.service.LifeDiscountCouponStoreFriendService;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 import shop.alien.util.common.constant.OrderStatusEnum;
 
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.time.format.TextStyle;
@@ -38,6 +44,7 @@ import java.util.stream.Collectors;
  * @version 1.0
  * @date 2024/12/23 15:08
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCoupon> implements LifeCouponService {
@@ -694,4 +701,262 @@ public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCou
         return lifeCouponMapper.getNewCouponDetail(id);
     }
 
+    @Override
+    public R<String> importHolidayFromExcel(MultipartFile file) {
+        log.info("LifeCouponServiceImpl.importHolidayFromExcel fileName={}", file.getOriginalFilename());
+
+        if (file == null || file.isEmpty()) {
+            return R.fail("上传文件为空");
+        }
+
+        String fileName = file.getOriginalFilename();
+        if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+            return R.fail("文件格式不正确,请上传Excel文件");
+        }
+
+        List<String> errorMessages = new ArrayList<>();
+        int successCount = 0;
+        int totalCount = 0;
+
+        try (InputStream inputStream = file.getInputStream();
+             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
+            Sheet sheet = workbook.getSheetAt(0);
+
+            // 获取表头(第6行,索引为5)
+            Row headerRow = sheet.getRow(5);
+            if (headerRow == null) {
+                return R.fail("Excel文件格式不正确,缺少表头");
+            }
+
+            // 构建字段映射(表头名称 -> 列索引)
+            Map<String, Integer> headerMap = new HashMap<>();
+            for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                Cell cell = headerRow.getCell(i);
+                if (cell != null) {
+                    String headerName = getCellValueAsString(cell);
+                    if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(headerName)) {
+                        headerMap.put(headerName.trim(), i);
+                    }
+                }
+            }
+
+            // 验证表头
+            if (!headerMap.containsKey("年份") || !headerMap.containsKey("节日名称") 
+                    || !headerMap.containsKey("开始时间") || !headerMap.containsKey("结束时间")) {
+                return R.fail("Excel文件格式不正确,缺少必要的表头字段(年份、节日名称、开始时间、结束时间)");
+            }
+
+            // 读取数据行(从第7行开始,索引为6)
+            for (int rowIndex = 6; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                Row row = sheet.getRow(rowIndex);
+                if (row == null) {
+                    continue;
+                }
+
+                // 检查是否为空行
+                boolean isEmptyRow = true;
+                for (int i = 0; i < row.getLastCellNum(); i++) {
+                    Cell cell = row.getCell(i);
+                    if (cell != null && cell.getCellType() != CellType.BLANK) {
+                        String cellValue = getCellValueAsString(cell);
+                        if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(cellValue)) {
+                            isEmptyRow = false;
+                            break;
+                        }
+                    }
+                }
+                if (isEmptyRow) {
+                    continue;
+                }
+
+                totalCount++;
+                EssentialHolidayComparison holiday = new EssentialHolidayComparison();
+
+                try {
+                    // 读取年份(必填)
+                    Integer yearColIndex = headerMap.get("年份");
+                    if (yearColIndex == null) {
+                        throw new RuntimeException("缺少年份字段");
+                    }
+                    Cell yearCell = row.getCell(yearColIndex);
+                    String yearValue = getCellValueAsString(yearCell);
+                    if (StringUtils.isEmpty(yearValue)) {
+                        throw new RuntimeException("年份不能为空");
+                    }
+                    // 处理年份可能是数字的情况
+                    if (yearCell != null && yearCell.getCellType() == CellType.NUMERIC) {
+                        double numericValue = yearCell.getNumericCellValue();
+                        yearValue = String.valueOf((long) numericValue);
+                    }
+                    holiday.setParticularYear(yearValue.trim());
+
+                    // 读取节日名称(必填)
+                    Integer nameColIndex = headerMap.get("节日名称");
+                    if (nameColIndex == null) {
+                        throw new RuntimeException("缺少节日名称字段");
+                    }
+                    Cell nameCell = row.getCell(nameColIndex);
+                    String nameValue = getCellValueAsString(nameCell);
+                    if (StringUtils.isEmpty(nameValue)) {
+                        throw new RuntimeException("节日名称不能为空");
+                    }
+                    holiday.setFestivalName(nameValue.trim());
+
+                    // 读取开始时间(必填,格式:2026-01-01)
+                    Integer startTimeColIndex = headerMap.get("开始时间");
+                    if (startTimeColIndex == null) {
+                        throw new RuntimeException("缺少开始时间字段");
+                    }
+                    Cell startTimeCell = row.getCell(startTimeColIndex);
+                    String startTimeValue = getCellValueAsString(startTimeCell);
+                    if (StringUtils.isEmpty(startTimeValue)) {
+                        throw new RuntimeException("开始时间不能为空");
+                    }
+                    Date startTime = parseDate(startTimeValue.trim(), rowIndex + 1);
+                    holiday.setStartTime(startTime);
+
+                    // 读取结束时间(必填,格式:2026-01-01)
+                    Integer endTimeColIndex = headerMap.get("结束时间");
+                    if (endTimeColIndex == null) {
+                        throw new RuntimeException("缺少结束时间字段");
+                    }
+                    Cell endTimeCell = row.getCell(endTimeColIndex);
+                    String endTimeValue = getCellValueAsString(endTimeCell);
+                    if (StringUtils.isEmpty(endTimeValue)) {
+                        throw new RuntimeException("结束时间不能为空");
+                    }
+                    Date endTime = parseDate(endTimeValue.trim(), rowIndex + 1);
+                    holiday.setEndTime(endTime);
+
+                    // 验证结束时间必须大于等于开始时间
+                    if (endTime.before(startTime)) {
+                        throw new RuntimeException("结束时间必须大于等于开始时间");
+                    }
+
+                    // 读取状态(可选,默认启用)
+                    Integer statusColIndex = headerMap.get("状态");
+                    int openFlag = 1; // 默认启用
+                    if (statusColIndex != null) {
+                        Cell statusCell = row.getCell(statusColIndex);
+                        String statusValue = getCellValueAsString(statusCell);
+                        if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(statusValue)) {
+                            String status = statusValue.trim();
+                            if ("启用".equals(status) || "1".equals(status)) {
+                                openFlag = 1;
+                            } else if ("禁用".equals(status) || "0".equals(status)) {
+                                openFlag = 0;
+                            } else {
+                                throw new RuntimeException("状态格式错误,请输入'启用'或'禁用'");
+                            }
+                        }
+                    }
+                    holiday.setOpenFlag(openFlag);
+                    holiday.setDelFlag(0);
+
+                    // 设置节日日期为开始时间
+                    LocalDate startLocalDate = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                    holiday.setFestivalDate(startLocalDate);
+
+                    // 保存数据
+                    essentialHolidayComparisonMapper.insert(holiday);
+                    successCount++;
+                } catch (Exception e) {
+                    errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                    log.error("导入第{}行数据失败", rowIndex + 1, e);
+                }
+            }
+        } catch (Exception e) {
+            log.error("导入Excel失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+
+        // 构建返回消息
+        StringBuilder message = new StringBuilder();
+        message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+        if (!errorMessages.isEmpty()) {
+            message.append("\n失败详情:\n");
+            for (int i = 0; i < Math.min(errorMessages.size(), 10); i++) {
+                message.append(errorMessages.get(i)).append("\n");
+            }
+            if (errorMessages.size() > 10) {
+                message.append("...还有").append(errorMessages.size() - 10).append("条错误信息");
+            }
+        }
+
+        return R.success(message.toString());
+    }
+
+    /**
+     * 解析日期字符串
+     */
+    private Date parseDate(String dateStr, int rowNum) {
+        try {
+            // 尝试多种日期格式
+            SimpleDateFormat[] formats = {
+                new SimpleDateFormat("yyyy-MM-dd"),
+                new SimpleDateFormat("yyyy/MM/dd"),
+                new SimpleDateFormat("yyyy年MM月dd日")
+            };
+
+            for (SimpleDateFormat format : formats) {
+                try {
+                    return format.parse(dateStr);
+                } catch (Exception e) {
+                    // 继续尝试下一个格式
+                }
+            }
+
+            // 如果都失败,尝试解析数字日期(Excel日期格式)
+            try {
+                double numericValue = Double.parseDouble(dateStr);
+                return org.apache.poi.ss.usermodel.DateUtil.getJavaDate(numericValue);
+            } catch (Exception e) {
+                // 忽略
+            }
+
+            throw new RuntimeException("日期格式错误,请使用格式:2026-01-01");
+        } catch (Exception e) {
+            throw new RuntimeException("日期格式错误,请使用格式:2026-01-01");
+        }
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
+                    // 日期格式
+                    Date date = cell.getDateCellValue();
+                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+                    return sdf.format(date);
+                } else {
+                    // 处理数字,避免科学计数法
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                try {
+                    return cell.getStringCellValue();
+                } catch (Exception e) {
+                    return String.valueOf(cell.getNumericCellValue());
+                }
+            default:
+                return null;
+        }
+    }
+
 }

+ 8 - 6
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java

@@ -769,10 +769,11 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         List<String> videoFileType = Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv");
 
         if ("2".equals(item.getReportContextType())) {
-            LifeUserDynamics dynamicsList = lifeUserDynamicsMapper.selectById(id);
+            LifeUserDynamics dynamicsInfo = lifeUserDynamicsMapper.selectById(id);
+            item.setDynamicsInfo(dynamicsInfo);
             // 将逗号分隔的图片路径拆分成 List<String>
-            if (dynamicsList != null && StringUtils.isNotEmpty(dynamicsList.getImagePath())) {
-                List<String> imagePathList = Arrays.stream(dynamicsList.getImagePath().split(","))
+            if (dynamicsInfo != null && StringUtils.isNotEmpty(dynamicsInfo.getImagePath())) {
+                List<String> imagePathList = Arrays.stream(dynamicsInfo.getImagePath().split(","))
                         .map(String::trim)
                         .filter(StringUtils::isNotEmpty)
                         .collect(Collectors.toList());
@@ -782,10 +783,11 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
         if ("3".equals(item.getReportContextType())) {
             // TODO: 处理评论类型
-            LifeComment commentList = lifeCommentMapper.selectById(id);
+            LifeComment commentInfo = lifeCommentMapper.selectById(id);
+            item.setCommentInfo(commentInfo);
             // 将逗号分隔的图片路径拆分成 List<String>
-            if (commentList != null && StringUtils.isNotEmpty(commentList.getImagePath())) {
-                List<String> imagePathList = Arrays.stream(commentList.getImagePath().split(","))
+            if (commentInfo != null && StringUtils.isNotEmpty(commentInfo.getImagePath())) {
+                List<String> imagePathList = Arrays.stream(commentInfo.getImagePath().split(","))
                         .map(String::trim)
                         .filter(StringUtils::isNotEmpty)
                         .collect(Collectors.toList());