商品导出

This commit is contained in:
gong
2026-01-29 09:42:50 +08:00
parent 66d3c8ad0b
commit 2d15315b79
6 changed files with 354 additions and 33 deletions

View File

@@ -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<ProductDTO> 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<Void> 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));
}
}

View File

@@ -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<FoodExportDTO> 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<ProductSkuExportDTO> skuList;
@ExcelIgnore
private List<ProductGroupExportDTO> 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 -> "未知状态";
};
}
}

View File

@@ -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<Product> {
*/
List<ProductDTO> getProductList(ProductDTO param);
void exportProductList(ProductDTO param, HttpServletResponse response);
/**
* 从缓存里面获取商品列表
*

View File

@@ -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 <T> 数据类型
*/
public static <T> void exportProductWithMergeToResponse(List<T> data, Class<T> clazz,
String fileName,
HttpServletResponse response, List<SheetWriteHandler> 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);
}
}
}

View File

@@ -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<LocalTime> {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
@Override
public Class<LocalTime> 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));
}
}

View File

@@ -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<ProductMapper, Product> 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<ProductMapper, Product> impl
return records;
}
@Override
public void exportProductList(ProductDTO param, HttpServletResponse response) {
QueryWrapper queryWrapper = buildFullQueryWrapper(param);
List<ProductDTO> records = super.listAs(queryWrapper, ProductDTO.class);
buildProductExtInfo(records);
List<SheetWriteHandler> handlers = new ArrayList<>();
List<ProductExportDTO> 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<ProductDTO> getProductCacheList(ProductDTO param) {
Long shopId = param.getShopId();
@@ -555,7 +609,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
String type = param.getType();
Long id = param.getId();
Integer isSale = param.getIsSale();
String sensitiveOperation = "";
String sensitiveOperation;
if (isSale == 1) {
sensitiveOperation = "上架";
} else {