penghao 4 днів тому
батько
коміт
2439b1db5d

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