From 2d15315b7901ce48782668d8ef9802a3233126d8 Mon Sep 17 00:00:00 2001 From: gong <1157756119@qq.com> Date: Thu, 29 Jan 2026 09:42:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=95=86=E5=93=81=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/ProductController.java | 49 ++--- .../com/czg/product/dto/ProductExportDTO.java | 173 ++++++++++++++++++ .../czg/product/service/ProductService.java | 4 +- .../java/com/czg/excel/ExcelExportUtil.java | 56 ++++++ .../com/czg/excel/LocalTimeConverter.java | 47 +++++ .../service/impl/ProductServiceImpl.java | 58 +++++- 6 files changed, 354 insertions(+), 33 deletions(-) create mode 100644 cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductExportDTO.java create mode 100644 cash-common/cash-common-tools/src/main/java/com/czg/excel/LocalTimeConverter.java diff --git a/cash-api/product-server/src/main/java/com/czg/controller/admin/ProductController.java b/cash-api/product-server/src/main/java/com/czg/controller/admin/ProductController.java index 85bab5667..82ee8aed2 100644 --- a/cash-api/product-server/src/main/java/com/czg/controller/admin/ProductController.java +++ b/cash-api/product-server/src/main/java/com/czg/controller/admin/ProductController.java @@ -27,6 +27,7 @@ import com.czg.validator.group.InsertGroup; import com.czg.validator.group.UpdateGroup; import com.mybatisflex.core.paginate.Page; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -80,6 +81,14 @@ public class ProductController { return CzgResult.success(map); } + /** + * 导出商品 + */ + @GetMapping("export") + public void exportProduct(ProductDTO param, HttpServletResponse response) { + productService.exportProductList(param, response); + } + /** * 商品-列表 */ @@ -91,9 +100,7 @@ public class ProductController { param.setShopId(shopId); List productList = productService.getProductCacheList(param); productService.refreshProductStock(param, productList); - productList.forEach(prod -> { - prod.setIsSaleTime(uProductService.calcIsSaleTime(prod.getDays(), prod.getStartTime(), prod.getEndTime())); - }); + productList.forEach(prod -> prod.setIsSaleTime(uProductService.calcIsSaleTime(prod.getDays(), prod.getStartTime(), prod.getEndTime()))); return CzgResult.success(productList); } @@ -124,9 +131,7 @@ public class ProductController { dto.setShopId(shopId); productService.addProduct(dto); asyncProductToShop(dto.getId()); - ThreadUtil.execAsync(() -> { - rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId)); - }); + ThreadUtil.execAsync(() -> rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId))); return CzgResult.success(); } @@ -153,9 +158,7 @@ public class ProductController { dto.setShopId(shopId); productService.updateProduct(dto); asyncProductToShop(dto.getId()); - ThreadUtil.execAsync(() -> { - rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId)); - }); + ThreadUtil.execAsync(() -> rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId))); return CzgResult.success(); } @@ -167,9 +170,7 @@ public class ProductController { Long shopId = StpKit.USER.getShopId(); param.setShopId(shopId); productService.updateProductStock(param); - ThreadUtil.execAsync(() -> { - rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId)); - }); + ThreadUtil.execAsync(() -> rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId))); return CzgResult.success(); } @@ -188,9 +189,7 @@ public class ProductController { Long shopId = StpKit.USER.getShopId(); productService.deleteProduct(shopId, id); asyncProductToShop(id); - ThreadUtil.execAsync(() -> { - rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId)); - }); + ThreadUtil.execAsync(() -> rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId))); return CzgResult.success(); } @@ -205,9 +204,7 @@ public class ProductController { Long shopId = StpKit.USER.getShopId(); param.setShopId(shopId); productService.onOffProduct(param); - ThreadUtil.execAsync(() -> { - rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId)); - }); + ThreadUtil.execAsync(() -> rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId))); return CzgResult.success(); } @@ -222,9 +219,7 @@ public class ProductController { Long shopId = StpKit.USER.getShopId(); param.setShopId(shopId); productService.markProductIsSoldOut(param); - ThreadUtil.execAsync(() -> { - rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId)); - }); + ThreadUtil.execAsync(() -> rabbitPublisher.sendProductInfoChangeMsg(Convert.toStr(shopId))); return CzgResult.success(); } @@ -321,9 +316,7 @@ public class ProductController { if (shopInfo.getMainId() == null || shopId == shopInfo.getMainId()) { throw new CzgException("不存在主子店铺关系,无需同步商品信息"); } - ThreadUtil.execAsync(() -> { - shopSyncService.sync(shopInfo.getMainId(), shopId, shopId); - }); + ThreadUtil.execAsync(() -> shopSyncService.sync(shopInfo.getMainId(), shopId, shopId)); CzgResult ret = CzgResult.success(); ret.setMsg("操作成功,数据正在后台同步中..."); return ret; @@ -331,15 +324,11 @@ public class ProductController { private void asyncProductToShop(Long id) { long shopId = StpKit.USER.getShopId(0L); - ThreadUtil.execAsync(() -> { - shopSyncService.syncProductBySourceShop(shopId, id, shopId); - }); + ThreadUtil.execAsync(() -> shopSyncService.syncProductBySourceShop(shopId, id, shopId)); } private void asyncConsProToShop(Long id) { long shopId = StpKit.USER.getShopId(0L); - ThreadUtil.execAsync(() -> { - shopSyncService.syncConsProBySourceShop(shopId, id, shopId); - }); + ThreadUtil.execAsync(() -> shopSyncService.syncConsProBySourceShop(shopId, id, shopId)); } } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductExportDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductExportDTO.java new file mode 100644 index 000000000..94563c686 --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/dto/ProductExportDTO.java @@ -0,0 +1,173 @@ +package com.czg.product.dto; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 商品导出 + * @author yjjie + * @date 2026/1/28 14:30 + */ +@Data +public class ProductExportDTO { + + @Data + public static class ProductSkuExportDTO { + + @ExcelProperty("条形码") + private String barCode; + + @ExcelProperty("原价") + private BigDecimal originPrice; + + @ExcelProperty("成本价") + private BigDecimal costPrice; + + @ExcelProperty("会员价") + private BigDecimal memberPrice; + + @ExcelProperty("售价") + private BigDecimal salePrice; + + @ExcelProperty("起售数量") + private Integer suitNum; + + @ExcelProperty("规格详情") + private String specInfo; + + @ExcelProperty("是否上架") + private Integer isGrounding; + + + @ExcelProperty("规格名称") + private String name; + } + + @Data + public static class ProductGroupExportDTO { + @ExcelProperty("套餐内商品总数") + private Integer count; + + @ExcelProperty("套餐选几") + private Integer number; + + @ExcelProperty("套餐名称") + private String title; + + @ExcelProperty("套餐内商品列表") + private List goods = new ArrayList<>(); + + @Data + public static class FoodExportDTO { + + @ExcelProperty("商品名称") + private String proName; + + @ExcelProperty("规格名称") + private String skuName; + } + } + + @ExcelProperty("商品名称") + private String name; + + @ExcelProperty("商品分类名称") + private String categoryName; + + @ExcelProperty("条码") + private String barCode; + + @ExcelProperty("商品规格名称") + private String specFullName; + + @ExcelProperty("售价") + private BigDecimal price; + @ExcelProperty("会员价") + private BigDecimal memberPrice; + + @ExcelProperty("商品单位名称") + private String unitName; + + /** + * 商品类型 single-单规格商品 sku-多规格商品 package-套餐商品 weight-称重商品 coupon-团购券 + */ + @ExcelProperty("商品类型") + private String type; + + /** + * 套餐类型 0 固定套餐 1可选套餐 + */ + @ExcelIgnore + private Integer groupType; + @ExcelProperty("套餐类型") + private String groupTypeRemark; + + /** + * 可用开始时间 + */ + @ExcelProperty("可用开始时间") + private LocalTime startTime; + /** + * 可用结束时间 + */ + @ExcelProperty("可用结束时间") + private LocalTime endTime; + + /** + * 商品级库存数量 + */ + @ExcelProperty("库存数量") + private Integer stockNumber; + + /** + * 是否上架 + */ + @ExcelIgnore + private Integer isSale; + @ExcelProperty("是否上架") + private String isSaleRemark; + + + @ExcelIgnore + private List skuList; + + @ExcelIgnore + private List proGroupVo; + + + public String getType() { + return switch (type) { + case "single" -> "单规格商品"; + case "sku" -> "多规格商品"; + case "package" -> "套餐商品"; + case "weight" -> "称重商品"; + case "coupon" -> "团购券"; + case null, default -> "未知类型"; + }; + } + + public String getGroupTypeRemark() { + if (!"package".equals(type)) { + return ""; + } + return switch (groupType) { + case 0 -> "固定套餐"; + case 1 -> "可选套餐"; + case null, default -> "未知类型"; + }; + } + + public String getIsSaleRemark() { + return switch (isSale) { + case 0 -> "下架"; + case 1 -> "上架"; + case null, default -> "未知状态"; + }; + } +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/product/service/ProductService.java b/cash-common/cash-common-service/src/main/java/com/czg/product/service/ProductService.java index f24801f20..420b7a7e0 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/product/service/ProductService.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/product/service/ProductService.java @@ -1,13 +1,13 @@ package com.czg.product.service; import com.czg.product.dto.ProductDTO; -import com.czg.product.dto.RelatedProductDTO; import com.czg.product.entity.Product; import com.czg.product.entity.ProductStockFlow; import com.czg.product.param.*; import com.czg.product.vo.ProductStatisticsVo; import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.service.IService; +import jakarta.servlet.http.HttpServletResponse; import java.util.List; @@ -34,6 +34,8 @@ public interface ProductService extends IService { */ List getProductList(ProductDTO param); + void exportProductList(ProductDTO param, HttpServletResponse response); + /** * 从缓存里面获取商品列表 * diff --git a/cash-common/cash-common-tools/src/main/java/com/czg/excel/ExcelExportUtil.java b/cash-common/cash-common-tools/src/main/java/com/czg/excel/ExcelExportUtil.java index ba1d6d570..b5030ddd4 100644 --- a/cash-common/cash-common-tools/src/main/java/com/czg/excel/ExcelExportUtil.java +++ b/cash-common/cash-common-tools/src/main/java/com/czg/excel/ExcelExportUtil.java @@ -3,6 +3,8 @@ package com.czg.excel; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.write.builder.ExcelWriterBuilder; +import com.alibaba.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; @@ -244,4 +246,58 @@ public class ExcelExportUtil { } return count; } + + /** + * 带合并单元格的商品导出到Response + * + * @param data 数据列表 + * @param fileName 文件名 + * @param response HttpServletResponse + * @param 数据类型 + */ + public static void exportProductWithMergeToResponse(List data, Class clazz, + String fileName, + HttpServletResponse response, List handlers) { + if (data == null) { + data = Collections.emptyList(); + } + + setResponseHeader(response, fileName, DEFAULT_CONFIG); + + try (OutputStream outputStream = response.getOutputStream()) { + // 创建样式策略 - 设置表头和内容都居中 + WriteCellStyle headStyle = new WriteCellStyle(); + headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + + WriteCellStyle contentStyle = new WriteCellStyle(); + contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + + HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(headStyle, contentStyle); + + // 创建写入器 - 必须指定clazz + ExcelWriterBuilder builder = EasyExcel.write(outputStream, clazz) + .autoCloseStream(true) + .registerConverter(new LocalTimeConverter()) + .registerWriteHandler(styleStrategy); + + if (handlers != null && !handlers.isEmpty()) { + for (SheetWriteHandler h : handlers) { + builder.registerWriteHandler(h); + } + } + + ExcelWriter excelWriter = builder.build(); + + WriteSheet writeSheet = EasyExcel.writerSheet(DEFAULT_CONFIG.getDefaultSheetName()).build(); + + excelWriter.write(data, writeSheet); + excelWriter.finish(); + + log.info("带合并单元格的商品Excel导出成功,文件名:{},数据量:{}", fileName, data.size()); + } catch (IOException e) { + log.error("带合并单元格的商品Excel导出失败", e); + throw new CzgException("Excel导出失败", e); + } + } + } diff --git a/cash-common/cash-common-tools/src/main/java/com/czg/excel/LocalTimeConverter.java b/cash-common/cash-common-tools/src/main/java/com/czg/excel/LocalTimeConverter.java new file mode 100644 index 000000000..3672f9af8 --- /dev/null +++ b/cash-common/cash-common-tools/src/main/java/com/czg/excel/LocalTimeConverter.java @@ -0,0 +1,47 @@ +package com.czg.excel; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * @author yjjie + * @date 2026/1/28 16:16 + */ +public class LocalTimeConverter implements Converter { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); + + @Override + public Class supportJavaTypeKey() { + return LocalTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public LocalTime convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + String stringValue = cellData.getStringValue(); + if (stringValue == null || stringValue.trim().isEmpty()) { + return null; + } + return LocalTime.parse(stringValue, FORMATTER); + } + + @Override + public WriteCellData convertToExcelData(LocalTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (value == null) { + return new WriteCellData<>(""); + } + return new WriteCellData<>(value.format(FORMATTER)); + } +} diff --git a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java index 3d85750b7..fe58280ae 100644 --- a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java +++ b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java @@ -5,11 +5,14 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONWriter; import com.czg.constant.CacheConstant; import com.czg.constants.SystemConstants; +import com.czg.excel.ExcelExportUtil; import com.czg.exception.CzgException; import com.czg.product.dto.*; import com.czg.product.entity.*; @@ -30,6 +33,7 @@ import com.mybatisflex.core.update.UpdateChain; import com.mybatisflex.spring.service.impl.ServiceImpl; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboService; @@ -65,7 +69,6 @@ public class ProductServiceImpl extends ServiceImpl impl private final ProdSkuMapper prodSkuMapper; private final ProdConsRelationMapper prodConsRelationMapper; private final ConsInfoMapper consInfoMapper; - private final ConsStockFlowMapper consStockFlowMapper; private final ProductStockFlowMapper productStockFlowMapper; private final ProductStockFlowService productStockFlowService; private final ConsStockFlowService consStockFlowService; @@ -175,6 +178,57 @@ public class ProductServiceImpl extends ServiceImpl impl return records; } + @Override + public void exportProductList(ProductDTO param, HttpServletResponse response) { + QueryWrapper queryWrapper = buildFullQueryWrapper(param); + List records = super.listAs(queryWrapper, ProductDTO.class); + buildProductExtInfo(records); + + List handlers = new ArrayList<>(); + + List dataList = new ArrayList<>(); + records.forEach(exportDTO -> { + int first = dataList.size() + 1; + if (exportDTO.getSkuList() != null && !exportDTO.getSkuList().isEmpty()) { + exportDTO.getSkuList().forEach(sku -> { + ProductExportDTO dto = new ProductExportDTO(); + BeanUtil.copyProperties(exportDTO, dto); + dto.setSpecFullName(sku.getSpecInfo()); + dto.setPrice(sku.getSalePrice()); + dto.setMemberPrice(sku.getMemberPrice()); + dto.setIsSale(sku.getIsGrounding()); + dto.setBarCode(sku.getBarCode()); + dataList.add(dto); + }); + + if (exportDTO.getSkuList().size() > 1) { + OnceAbsoluteMergeStrategy name = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 0, 0); + handlers.add(name); + OnceAbsoluteMergeStrategy category = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 1, 1); + handlers.add(category); + OnceAbsoluteMergeStrategy unit = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 6, 6); + handlers.add(unit); + OnceAbsoluteMergeStrategy type = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 7, 7); + handlers.add(type); + OnceAbsoluteMergeStrategy groupType = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 8, 8); + handlers.add(groupType); + OnceAbsoluteMergeStrategy startTime = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 9, 9); + handlers.add(startTime); + OnceAbsoluteMergeStrategy endTime = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 10, 10); + handlers.add(endTime); + OnceAbsoluteMergeStrategy stockNumber = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 11, 11); + handlers.add(stockNumber); + OnceAbsoluteMergeStrategy createTime = new OnceAbsoluteMergeStrategy(first, first + exportDTO.getSkuList().size() - 1, 12, 12); + handlers.add(createTime); + } + } else { + dataList.add(BeanUtil.copyProperties(exportDTO, ProductExportDTO.class)); + } + }); + + ExcelExportUtil.exportProductWithMergeToResponse(dataList, ProductExportDTO.class, "商品列表", response, handlers); + } + @Override public List getProductCacheList(ProductDTO param) { Long shopId = param.getShopId(); @@ -555,7 +609,7 @@ public class ProductServiceImpl extends ServiceImpl impl String type = param.getType(); Long id = param.getId(); Integer isSale = param.getIsSale(); - String sensitiveOperation = ""; + String sensitiveOperation; if (isSale == 1) { sensitiveOperation = "上架"; } else {