|
@@ -8,10 +8,14 @@ import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
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.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
import org.springframework.util.CollectionUtils;
|
|
|
import org.springframework.util.StringUtils;
|
|
import org.springframework.util.StringUtils;
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
import shop.alien.entity.result.R;
|
|
import shop.alien.entity.result.R;
|
|
|
import shop.alien.entity.store.*;
|
|
import shop.alien.entity.store.*;
|
|
|
import shop.alien.entity.store.dto.LifeDiscountCouponStoreFriendDto;
|
|
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.UniqueRandomNumGenerator;
|
|
|
import shop.alien.util.common.constant.OrderStatusEnum;
|
|
import shop.alien.util.common.constant.OrderStatusEnum;
|
|
|
|
|
|
|
|
|
|
+import java.io.InputStream;
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
import java.math.RoundingMode;
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
import java.time.*;
|
|
import java.time.*;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.time.format.TextStyle;
|
|
import java.time.format.TextStyle;
|
|
@@ -38,6 +44,7 @@ import java.util.stream.Collectors;
|
|
|
* @version 1.0
|
|
* @version 1.0
|
|
|
* @date 2024/12/23 15:08
|
|
* @date 2024/12/23 15:08
|
|
|
*/
|
|
*/
|
|
|
|
|
+@Slf4j
|
|
|
@Service
|
|
@Service
|
|
|
@RequiredArgsConstructor
|
|
@RequiredArgsConstructor
|
|
|
public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCoupon> implements LifeCouponService {
|
|
public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCoupon> implements LifeCouponService {
|
|
@@ -694,4 +701,262 @@ public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCou
|
|
|
return lifeCouponMapper.getNewCouponDetail(id);
|
|
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;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
}
|
|
}
|