oying-system/src/main/java/com/oying/modules/pc/product/domain/dto/ProductAuditData.java
@@ -8,7 +8,7 @@ @Data public class ProductAuditData { private Product originalStore; private Product originalProduct; private Product product; private ProductRevisionRecord revisionRecord; oying-system/src/main/java/com/oying/modules/pc/product/domain/dto/ProductCreateRequest.java
New file @@ -0,0 +1,5 @@ package com.oying.modules.pc.product.domain.dto; public interface ProductCreateRequest { } oying-system/src/main/java/com/oying/modules/pc/product/domain/dto/ProductMerchantCreateRequest.java
@@ -10,7 +10,7 @@ import java.util.List; @Data public class ProductMerchantCreateRequest { public class ProductMerchantCreateRequest implements ProductCreateRequest { private Long storeId; oying-system/src/main/java/com/oying/modules/pc/product/domain/dto/ProductPriceUpdateRequest.java
New file @@ -0,0 +1,18 @@ package com.oying.modules.pc.product.domain.dto; import lombok.Data; import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotNull; import java.math.BigDecimal; @Data public class ProductPriceUpdateRequest { private Long productId; @NotNull(message = "价格不能为空") @DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0") private BigDecimal price; } oying-system/src/main/java/com/oying/modules/pc/product/domain/dto/ProductRevisionRecord.java
@@ -1,14 +1,59 @@ package com.oying.modules.pc.product.domain.dto; import com.oying.modules.pc.store.domain.StoreQualification; import com.oying.modules.pc.store.domain.dto.StoreQualificationUpdateRequest; import com.oying.modules.pc.product.domain.ProductLabel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @Data public class ProductRevisionRecord { private Long productId; private String barcode; private String name; private String title; private Long categoryId; private Long secondCategoryId; private BigDecimal price; private Integer stockQuantity; private Integer minPurchaseQuantity; private Integer warnStock; private Integer weight; private Integer length; private Integer width; private Integer height; @ApiModelProperty(value = "是否支持退货") private Integer returns; @ApiModelProperty(value = "是否支持自提") private Integer selfPickup; private List<Long> deletedImageIds = new ArrayList<>(); private List<ProductImageUpdateRequest> updatedImages = new ArrayList<>(); private List<ProductImageCreateRequest> newImages = new ArrayList<>(); private List<Long> deletedLabelIds = new ArrayList<>(); private List<ProductLabel> updatedLabels = new ArrayList<>(); private List<ProductLabel> newLabels = new ArrayList<>(); } oying-system/src/main/java/com/oying/modules/pc/product/domain/enums/ProductAuditTypeEnum.java
New file @@ -0,0 +1,11 @@ package com.oying.modules.pc.product.domain.enums; public enum ProductAuditTypeEnum { PUT_ON_SHELF, NEW_PRODUCT, FULL_UPDATE, IMAGE_UPDATE, LABEL_UPDATE } oying-system/src/main/java/com/oying/modules/pc/product/domain/enums/ProductCreationType.java
New file @@ -0,0 +1,6 @@ package com.oying.modules.pc.product.domain.enums; public enum ProductCreationType { DIRECT, WITH_AUDIT } oying-system/src/main/java/com/oying/modules/pc/product/events/handler/ProductAuditVerdictAfterHandlerFactory.java
New file @@ -0,0 +1,23 @@ package com.oying.modules.pc.product.events.handler; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; @Slf4j @Component @RequiredArgsConstructor public class ProductAuditVerdictAfterHandlerFactory { private final List<ProductAuditVerdictHandler> handlers; public ProductAuditVerdictHandler getHandler(ProductAuditTypeEnum auditType) { return handlers.stream() .filter(handler -> handler.supports(auditType)) .findFirst() .orElseThrow(() -> new UnsupportedOperationException("未找到商品审核处理类型为 [" + auditType + "] 的处理器")); } } oying-system/src/main/java/com/oying/modules/pc/product/events/handler/ProductAuditVerdictEventHandler.java
New file @@ -0,0 +1,37 @@ package com.oying.modules.pc.product.events.handler; import com.oying.modules.pc.product.domain.ProductAudit; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; import com.oying.modules.pc.product.events.ProductAuditVerdictEvent; import com.oying.modules.pc.product.service.ProductAuditService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @Slf4j @Component @RequiredArgsConstructor public class ProductAuditVerdictEventHandler { private final ProductAuditService productAuditService; private final ProductAuditVerdictAfterHandlerFactory handlerFactory; /** * 监听 ProductAuditVerdictEvent 事件 */ @EventListener public void handleProductAuditPassEvent(ProductAuditVerdictEvent event) { log.info("监听到商品审核事件: {}", event); try { ProductAudit productAudit = productAuditService.getById(event.getAuditId()); ProductAuditTypeEnum auditType = ProductAuditTypeEnum.valueOf(productAudit.getType()); ProductAuditVerdictHandler handler = handlerFactory.getHandler(auditType); handler.handle(productAudit); log.info("商品审核事件处理成功: auditId={}", event.getAuditId()); } catch (Exception e) { log.error("处理商品审核事件时发生异常: auditId={}", event.getAuditId(), e); throw e; } } } oying-system/src/main/java/com/oying/modules/pc/product/events/handler/ProductAuditVerdictHandler.java
New file @@ -0,0 +1,14 @@ package com.oying.modules.pc.product.events.handler; import com.oying.modules.pc.product.domain.ProductAudit; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; public interface ProductAuditVerdictHandler { // 判断当前处理器是否支持处理该类型的操作 boolean supports(ProductAuditTypeEnum auditType); // 处理审核后的逻辑 void handle(ProductAudit audit); } oying-system/src/main/java/com/oying/modules/pc/product/events/handler/ProductCreateAuditVerdictHandler.java
New file @@ -0,0 +1,35 @@ package com.oying.modules.pc.product.events.handler; import com.oying.modules.pc.common.core.constrant.AuditStatusEnum; import com.oying.modules.pc.product.domain.Product; import com.oying.modules.pc.product.domain.ProductAudit; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; import com.oying.modules.pc.product.domain.enums.ProductStatusEnum; import com.oying.modules.pc.product.service.ProductService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component @RequiredArgsConstructor public class ProductCreateAuditVerdictHandler implements ProductAuditVerdictHandler { private final ProductService productService; @Override public boolean supports(ProductAuditTypeEnum auditType) { return ProductAuditTypeEnum.NEW_PRODUCT.equals(auditType); } @Override public void handle(ProductAudit audit) { AuditStatusEnum auditStatus = AuditStatusEnum.get(audit.getStatus()); Product existingProduct = productService.getById(audit.getProductId()); existingProduct.setStatus(auditStatus.getValue()); if (AuditStatusEnum.APPROVED.equals(auditStatus)) { existingProduct.setShelfStatus(ProductStatusEnum.NO_AVAILABLE.getValue()); } productService.updateById(existingProduct); } } oying-system/src/main/java/com/oying/modules/pc/product/events/handler/ProductFullUpdateAuditVerdictHandler.java
New file @@ -0,0 +1,35 @@ package com.oying.modules.pc.product.events.handler; import com.oying.modules.pc.common.core.constrant.AuditStatusEnum; import com.oying.modules.pc.product.domain.Product; import com.oying.modules.pc.product.domain.ProductAudit; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; import com.oying.modules.pc.product.domain.enums.ProductStatusEnum; import com.oying.modules.pc.product.service.ProductService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component @RequiredArgsConstructor public class ProductFullUpdateAuditVerdictHandler implements ProductAuditVerdictHandler { private final ProductService productService; @Override public boolean supports(ProductAuditTypeEnum auditType) { return ProductAuditTypeEnum.FULL_UPDATE.equals(auditType); } @Override public void handle(ProductAudit audit) { AuditStatusEnum auditStatus = AuditStatusEnum.get(audit.getStatus()); Product existingProduct = productService.getById(audit.getProductId()); existingProduct.setStatus(auditStatus.getValue()); if (AuditStatusEnum.APPROVED.equals(auditStatus)) { existingProduct.setShelfStatus(ProductStatusEnum.NO_AVAILABLE.getValue()); } productService.updateById(existingProduct); } } oying-system/src/main/java/com/oying/modules/pc/product/rest/ProductMerchantController.java
@@ -7,10 +7,7 @@ import com.oying.annotation.Log; import com.oying.modules.pc.product.domain.Product; import com.oying.modules.pc.product.domain.ProductLabel; import com.oying.modules.pc.product.domain.dto.ProductImageCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantUpdateRequest; import com.oying.modules.pc.product.domain.dto.ProductQueryCriteria; import com.oying.modules.pc.product.domain.dto.*; import com.oying.modules.pc.product.service.ProductImageService; import com.oying.modules.pc.product.service.ProductLabelService; import com.oying.modules.pc.product.service.ProductMerchantService; @@ -41,9 +38,6 @@ @Api(tags = "商品(商户端)") @RequestMapping("/api/pc/merchant/store/{storeId}/product") public class ProductMerchantController { private final int MAX_IMAGES = 5; private final int MAX_LABELS = 10; private final ProductService productService; private final ProductMerchantService productMerchantService; @@ -98,7 +92,7 @@ public ResponseEntity<?> createProduct(@PathVariable Long storeId, @Validated @RequestBody ProductMerchantCreateRequest request) { request.setStoreId(ObjectUtils.defaultIfNull(request.getStoreId(), storeId)); productMerchantService.create(request); productMerchantService.createWithAudit(request); return ResponseEntity.status(HttpStatus.CREATED).build(); } @@ -114,6 +108,16 @@ return ResponseEntity.noContent().build(); } @PutMapping(value = "/{productId}/price") @Log("修改价格") @ApiOperation("修改价格") public ResponseEntity<?> updateProductPrice(@PathVariable Long productId, @Validated @RequestBody ProductPriceUpdateRequest request) { request.setProductId(ObjectUtils.defaultIfNull(request.getProductId(), productId)); productMerchantService.updatePrice(request); return ResponseEntity.noContent().build(); } @DeleteMapping @Log("批量删除商品") @ApiOperation("批量删除商品") oying-system/src/main/java/com/oying/modules/pc/product/service/ProductCreationStrategy.java
New file @@ -0,0 +1,14 @@ package com.oying.modules.pc.product.service; import com.oying.modules.pc.product.domain.dto.ProductCreateRequest; import com.oying.modules.pc.product.domain.enums.ProductCreationType; public interface ProductCreationStrategy { ProductCreationType getType(); // 每个策略知道自己处理的类型 void create(ProductCreateRequest request); boolean supports(ProductCreationType type); } oying-system/src/main/java/com/oying/modules/pc/product/service/ProductMerchantService.java
@@ -1,7 +1,10 @@ package com.oying.modules.pc.product.service; import com.oying.modules.pc.product.domain.ProductAudit; import com.oying.modules.pc.product.domain.dto.ProductMerchantCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantUpdateRequest; import com.oying.modules.pc.product.domain.dto.ProductPriceUpdateRequest; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; import com.oying.modules.pc.product.events.ProductAuditVerdictEvent; import java.util.List; @@ -9,12 +12,14 @@ public interface ProductMerchantService { void create(ProductMerchantCreateRequest request); void createWithAudit(ProductMerchantCreateRequest request); void submitToAudit(Long productId, ProductAuditTypeEnum productAuditType); void update(ProductMerchantUpdateRequest request); void updatePrice(ProductPriceUpdateRequest request); void updateImages(ProductMerchantUpdateRequest request); void updateLabels(ProductMerchantUpdateRequest request); void batchDelete(List<Long> ids); void putOnShelf(Long productId); void takeOffShelf(Long productId); void handleAuditVerdictEvent(ProductAuditVerdictEvent event); } oying-system/src/main/java/com/oying/modules/pc/product/service/ProductService.java
@@ -52,6 +52,12 @@ void update(Product resources); /** * 编辑 * @param resources / */ void update(Product resources, boolean isDirectUpdate); /** * 多选删除 * @param ids / */ oying-system/src/main/java/com/oying/modules/pc/product/service/impl/ProductAuditCreator.java
New file @@ -0,0 +1,92 @@ package com.oying.modules.pc.product.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson2.JSON; import com.oying.modules.pc.common.core.constrant.AuditStatusEnum; import com.oying.modules.pc.product.converter.ProductImageAssembler; import com.oying.modules.pc.product.converter.ProductLabelAssembler; import com.oying.modules.pc.product.domain.Product; import com.oying.modules.pc.product.domain.ProductAudit; import com.oying.modules.pc.product.domain.ProductImage; import com.oying.modules.pc.product.domain.ProductLabel; import com.oying.modules.pc.product.domain.dto.ProductAuditData; import com.oying.modules.pc.product.domain.dto.ProductCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductImageCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantCreateRequest; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; import com.oying.modules.pc.product.domain.enums.ProductCreationType; import com.oying.modules.pc.product.domain.enums.ProductStatusEnum; import com.oying.modules.pc.product.service.*; import com.oying.modules.pc.utils.ImageUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Slf4j @Component @RequiredArgsConstructor public class ProductAuditCreator implements ProductCreationStrategy { private final ProductService productService; private final ProductImageService productImageService; private final ProductLabelService productLabelService; private final ProductAuditService productAuditService; @Override public ProductCreationType getType() { return ProductCreationType.WITH_AUDIT; } @Override public void create(ProductCreateRequest resource) { ProductMerchantCreateRequest request = (ProductMerchantCreateRequest) resource; Product product = new Product(); BeanUtils.copyProperties(request, product); request.getImages().stream().findFirst().map(ProductImageCreateRequest::getUploadFileId) .ifPresent(id -> { product.setMainImageId(id.toString()); product.setMainImageUrl(ImageUtils.getPublicObjectUrl(id)); }); product.setStatus(ProductStatusEnum.PENDING.getValue()); productService.create(product); List<ProductImage> productImages = request.getImages().stream().map(i -> { i.setProductId(product.getProductId()); return ProductImageAssembler.to(i); }).collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(productImages)) { productImageService.saveBatch(productImages); } List<ProductLabel> productLabels = request.getLabels().stream().map(i -> { i.setProductId(product.getProductId()); return ProductLabelAssembler.to(i); }).collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(productLabels)) { productLabelService.saveBatch(productLabels); } Product auditProduct = new Product(); auditProduct.copy(product); auditProduct.setImages(productImages); auditProduct.setLabels(productLabels); ProductAudit audit = new ProductAudit(); audit.setProductId(product.getProductId()); audit.setType(ProductAuditTypeEnum.NEW_PRODUCT.name()); audit.setStatus(AuditStatusEnum.PENDING.getValue()); ProductAuditData auditData = new ProductAuditData(); auditData.setProduct(auditProduct); audit.setData(JSON.toJSONString(auditData)); productAuditService.create(audit); } @Override public boolean supports(ProductCreationType type) { return ProductCreationType.WITH_AUDIT.equals(type); } } oying-system/src/main/java/com/oying/modules/pc/product/service/impl/ProductCreationContext.java
New file @@ -0,0 +1,59 @@ package com.oying.modules.pc.product.service.impl; import com.oying.modules.pc.product.domain.dto.ProductCreateRequest; import com.oying.modules.pc.product.domain.enums.ProductCreationType; import com.oying.modules.pc.product.service.ProductCreationStrategy; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; @Component public class ProductCreationContext { private final Map<ProductCreationType, ProductCreationStrategy> strategies; // Spring会自动注入所有ProductCreationStrategy的实现 public ProductCreationContext(List<ProductCreationStrategy> strategyList) { this.strategies = strategyList.stream() .collect(Collectors.toMap( ProductCreationStrategy::getType, Function.identity() )); } @PostConstruct public void validateStrategies() { if (strategies.isEmpty()) { throw new IllegalStateException("未配置任何商品创建策略,应用无法正常启动"); } } public void createProduct(ProductCreateRequest request, ProductCreationType type) { ProductCreationStrategy strategy = strategies.get(type); if (strategy == null) { throw new IllegalArgumentException("不支持的创建类型或参数类型不匹配"); } strategy.create(request); // 使用反射调用,或者更好的方式... // invokeStrategy(strategy, request); } /* private void invokeStrategy(ProductCreationStrategy<?> strategy, ProductCreationRequest request) { try { Method createMethod = strategy.getClass().getMethod("create", request.getClass()); createMethod.invoke(strategy, request); } catch (Exception e) { throw new RuntimeException("执行商品创建策略失败", e); } }*/ } oying-system/src/main/java/com/oying/modules/pc/product/service/impl/ProductDirectCreator.java
New file @@ -0,0 +1,75 @@ package com.oying.modules.pc.product.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.oying.modules.pc.product.converter.ProductImageAssembler; import com.oying.modules.pc.product.converter.ProductLabelAssembler; import com.oying.modules.pc.product.domain.Product; import com.oying.modules.pc.product.domain.ProductImage; import com.oying.modules.pc.product.domain.ProductLabel; import com.oying.modules.pc.product.domain.dto.ProductCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductImageCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantCreateRequest; import com.oying.modules.pc.product.domain.enums.ProductCreationType; import com.oying.modules.pc.product.domain.enums.ProductStatusEnum; import com.oying.modules.pc.product.service.ProductCreationStrategy; import com.oying.modules.pc.product.service.ProductImageService; import com.oying.modules.pc.product.service.ProductLabelService; import com.oying.modules.pc.product.service.ProductService; import com.oying.modules.pc.utils.ImageUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Slf4j @Component @RequiredArgsConstructor public class ProductDirectCreator implements ProductCreationStrategy { private final ProductService productService; private final ProductImageService productImageService; private final ProductLabelService productLabelService; @Override public ProductCreationType getType() { return ProductCreationType.DIRECT; } @Override public void create(ProductCreateRequest resource) { ProductMerchantCreateRequest request = (ProductMerchantCreateRequest) resource; Product product = new Product(); BeanUtils.copyProperties(request, product); request.getImages().stream().findFirst().map(ProductImageCreateRequest::getUploadFileId) .ifPresent(id -> { product.setMainImageId(id.toString()); product.setMainImageUrl(ImageUtils.getPublicObjectUrl(id)); }); product.setStatus(ProductStatusEnum.DRAFT.getValue()); productService.create(product); List<ProductImage> productImages = request.getImages().stream().map(i -> { i.setProductId(product.getProductId()); return ProductImageAssembler.to(i); }).collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(productImages)) { productImageService.saveBatch(productImages); } List<ProductLabel> productLabels = request.getLabels().stream().map(i -> { i.setProductId(product.getProductId()); return ProductLabelAssembler.to(i); }).collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(productLabels)) { productLabelService.saveBatch(productLabels); } } @Override public boolean supports(ProductCreationType type) { return ProductCreationType.DIRECT.equals(type); } } oying-system/src/main/java/com/oying/modules/pc/product/service/impl/ProductMerchantImagesUpdateProcessor.java
New file @@ -0,0 +1,34 @@ package com.oying.modules.pc.product.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.oying.modules.pc.product.domain.dto.ProductImageCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantUpdateRequest; import com.oying.modules.pc.product.service.ProductImageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class ProductMerchantImagesUpdateProcessor { private final ProductImageService productImageService; public void processImagesUpdate(ProductMerchantUpdateRequest request) { if (CollectionUtil.isNotEmpty(request.getDeletedImageIds())) { productImageService.deleteAll(request.getDeletedImageIds()); } if (CollectionUtil.isNotEmpty(request.getUpdatedImages())) { productImageService.batchUpdate(request.getUpdatedImages()); } if (CollectionUtil.isNotEmpty(request.getNewImages())) { List<ProductImageCreateRequest> newImages = request.getNewImages().stream().peek(i-> i.setProductId(request.getProductId())).collect(Collectors.toList()); productImageService.batchCreate(newImages); } } } oying-system/src/main/java/com/oying/modules/pc/product/service/impl/ProductMerchantLabelUpdateProcessor.java
New file @@ -0,0 +1,36 @@ package com.oying.modules.pc.product.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.oying.modules.pc.product.domain.ProductLabel; import com.oying.modules.pc.product.domain.dto.ProductImageCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantUpdateRequest; import com.oying.modules.pc.product.service.ProductImageService; import com.oying.modules.pc.product.service.ProductLabelService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class ProductMerchantLabelUpdateProcessor { private final ProductLabelService productLabelService; public void processLabelsUpdate(ProductMerchantUpdateRequest request) { if (CollectionUtil.isNotEmpty(request.getDeletedLabelIds())) { productLabelService.deleteAll(request.getDeletedLabelIds()); } if (CollectionUtil.isNotEmpty(request.getUpdatedLabels())) { productLabelService.batchUpdate(request.getUpdatedLabels()); } if (CollectionUtil.isNotEmpty(request.getNewLabels())) { List<ProductLabel> newLabels = request.getNewLabels().stream().peek(i-> i.setProductId(request.getProductId())).collect(Collectors.toList()); productLabelService.batchCreate(newLabels); } } } oying-system/src/main/java/com/oying/modules/pc/product/service/impl/ProductMerchantServiceImpl.java
@@ -1,35 +1,29 @@ package com.oying.modules.pc.product.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.oying.exception.BadRequestException; import com.oying.modules.pc.common.core.constrant.AuditStatusEnum; import com.oying.modules.pc.product.converter.ProductImageAssembler; import com.oying.modules.pc.product.converter.ProductLabelAssembler; import com.oying.modules.pc.product.domain.Product; import com.oying.modules.pc.product.domain.ProductAudit; import com.oying.modules.pc.product.domain.ProductImage; import com.oying.modules.pc.product.domain.ProductLabel; import com.oying.modules.pc.product.domain.dto.ProductAuditData; import com.oying.modules.pc.product.domain.dto.ProductImageCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantCreateRequest; import com.oying.modules.pc.product.domain.dto.ProductMerchantUpdateRequest; import com.oying.modules.pc.product.domain.enums.ProductChangeTypeEnum; import com.oying.modules.pc.product.domain.dto.*; import com.oying.modules.pc.product.domain.enums.ProductAuditTypeEnum; import com.oying.modules.pc.product.domain.enums.ProductCreationType; import com.oying.modules.pc.product.domain.enums.ProductStatusEnum; import com.oying.modules.pc.product.events.ProductAuditVerdictEvent; import com.oying.modules.pc.product.service.*; import com.oying.modules.pc.utils.ImageUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Slf4j @Service @@ -40,62 +34,127 @@ private final ProductImageService productImageService; private final ProductLabelService productLabelService; private final ProductAuditService productAuditService; private final ProductCreationContext productCreationService; private final ProductMerchantImagesUpdateProcessor productMerchantImagesUpdateProcessor; private final ProductMerchantLabelUpdateProcessor productMerchantLabelUpdateProcessor; @Override @Transactional(rollbackFor = Exception.class) public void create(ProductMerchantCreateRequest request) { Product product = new Product(); BeanUtils.copyProperties(request, product); request.getImages().stream().findFirst().map(ProductImageCreateRequest::getUploadFileId) .ifPresent(id -> { product.setMainImageId(id.toString()); product.setMainImageUrl(ImageUtils.getPublicObjectUrl(id)); }); product.setStatus(ProductStatusEnum.DRAFT.getValue()); productService.create(product); productCreationService.createProduct(request, ProductCreationType.DIRECT); } List<ProductImage> productImages = request.getImages().stream().map(i -> { i.setProductId(product.getProductId()); return ProductImageAssembler.to(i); }).collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(productImages)) { productImageService.saveBatch(productImages); @Override @Transactional(rollbackFor = Exception.class) public void createWithAudit(ProductMerchantCreateRequest request) { productCreationService.createProduct(request, ProductCreationType.WITH_AUDIT); } @Override @Transactional(rollbackFor = Exception.class) public void submitToAudit(Long productId, ProductAuditTypeEnum productAuditType) { Product existingProduct = productService.getById(productId); if (!ProductStatusEnum.DRAFT.getValue().equals(existingProduct.getStatus())) { throw new BadRequestException("无法提交,该商品已提交审核或已上架"); } List<ProductLabel> productLabels = request.getLabels().stream().map(i -> { i.setProductId(product.getProductId()); return ProductLabelAssembler.to(i); }).collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(productLabels)) { productLabelService.saveBatch(productLabels); } List<ProductImage> productImages = productImageService.queryImagesByProductId(existingProduct.getProductId()); existingProduct.setImages(productImages); List<ProductLabel> productLabels = productLabelService.queryLabelsByProductId(existingProduct.getProductId()); existingProduct.setLabels(productLabels); ProductAudit audit = new ProductAudit(); audit.setProductId(existingProduct.getProductId()); audit.setType(productAuditType.name()); audit.setStatus(AuditStatusEnum.PENDING.getValue()); ProductAuditData auditData = new ProductAuditData(); auditData.setProduct(existingProduct); audit.setData(JSON.toJSONString(auditData)); productAuditService.create(audit); } @Override @Transactional(rollbackFor = Exception.class) public void update(ProductMerchantUpdateRequest request) { Product existingProduct = productService.getProduct(request.getProductId()); this.validateApprovedStatus(existingProduct.getShelfStatus()); this.processImagesUpdate(request); this.processLabelsUpdate(request); this.validateAuditStatus(existingProduct.getStatus()); productMerchantImagesUpdateProcessor.processImagesUpdate(request); productMerchantLabelUpdateProcessor.processLabelsUpdate(request); BeanUtils.copyProperties(request, existingProduct); existingProduct.setStatus(ProductStatusEnum.PENDING.getValue()); existingProduct.setShelfStatus(ProductStatusEnum.NO_AVAILABLE.getValue()); productService.update(existingProduct); ProductAudit audit = new ProductAudit(); audit.setProductId(existingProduct.getProductId()); audit.setType(ProductAuditTypeEnum.FULL_UPDATE.name()); audit.setStatus(AuditStatusEnum.PENDING.getValue()); ProductAuditData auditData = new ProductAuditData(); auditData.setOriginalProduct(existingProduct); Product newProduct = new Product(); BeanUtil.copyProperties(request, newProduct, CopyOptions.create().setIgnoreNullValue(true)); auditData.setProduct(existingProduct); ProductRevisionRecord revisionRecord = new ProductRevisionRecord(); BeanUtil.copyProperties(request, revisionRecord, CopyOptions.create().setIgnoreNullValue(true)); auditData.setRevisionRecord(revisionRecord); audit.setData(JSON.toJSONString(auditData)); productAuditService.create(audit); } @Override @Transactional(rollbackFor = Exception.class) public void updatePrice(ProductPriceUpdateRequest request) { Product existingProduct = productService.getProduct(request.getProductId()); existingProduct.setPrice(request.getPrice()); productService.update(existingProduct, true); } @Override @Transactional(rollbackFor = Exception.class) public void updateImages(ProductMerchantUpdateRequest request) { Product existingProduct = productService.getProduct(request.getProductId()); this.validateApprovedStatus(existingProduct.getShelfStatus()); this.processImagesUpdate(request); this.validateAuditStatus(existingProduct.getStatus()); productMerchantImagesUpdateProcessor.processImagesUpdate(request); ProductAudit audit = new ProductAudit(); audit.setProductId(existingProduct.getProductId()); audit.setType(ProductAuditTypeEnum.IMAGE_UPDATE.name()); audit.setStatus(AuditStatusEnum.PENDING.getValue()); ProductAuditData auditData = new ProductAuditData(); auditData.setOriginalProduct(existingProduct); Product newProduct = new Product(); BeanUtil.copyProperties(request, newProduct, CopyOptions.create().setIgnoreNullValue(true)); auditData.setProduct(existingProduct); ProductRevisionRecord revisionRecord = new ProductRevisionRecord(); BeanUtil.copyProperties(request, revisionRecord, CopyOptions.create().setIgnoreNullValue(true)); auditData.setRevisionRecord(revisionRecord); audit.setData(JSON.toJSONString(auditData)); productAuditService.create(audit); } @Override @Transactional(rollbackFor = Exception.class) public void updateLabels(ProductMerchantUpdateRequest request) { Product existingProduct = productService.getProduct(request.getProductId()); this.validateApprovedStatus(existingProduct.getShelfStatus()); this.processImagesUpdate(request); this.validateAuditStatus(existingProduct.getStatus()); productMerchantLabelUpdateProcessor.processLabelsUpdate(request); ProductAudit audit = new ProductAudit(); audit.setProductId(existingProduct.getProductId()); audit.setType(ProductAuditTypeEnum.LABEL_UPDATE.name()); audit.setStatus(AuditStatusEnum.PENDING.getValue()); ProductAuditData auditData = new ProductAuditData(); auditData.setOriginalProduct(existingProduct); Product newProduct = new Product(); BeanUtil.copyProperties(request, newProduct, CopyOptions.create().setIgnoreNullValue(true)); auditData.setProduct(existingProduct); ProductRevisionRecord revisionRecord = new ProductRevisionRecord(); BeanUtil.copyProperties(request, revisionRecord, CopyOptions.create().setIgnoreNullValue(true)); auditData.setRevisionRecord(revisionRecord); audit.setData(JSON.toJSONString(auditData)); productAuditService.create(audit); } @Transactional @@ -107,25 +166,17 @@ @Override public void putOnShelf(Long productId) { Product existingProduct = productService.getById(productId); if (ProductStatusEnum.AVAILABLE.getValue().equals(existingProduct.getShelfStatus())) { throw new BadRequestException("商品已上架"); if (ProductStatusEnum.AVAILABLE.getValue().equals(existingProduct.getStatus())) { return; } if (productAuditService.hasPendingByStoreId(productId)) { throw new BadRequestException("已在审核中"); if (!(ProductStatusEnum.APPROVED.getValue().equals(existingProduct.getStatus()) || ProductStatusEnum.NO_AVAILABLE.getValue().equals(existingProduct.getStatus()))) { throw new BadRequestException("无法上架,商品未审核"); } ProductAudit audit = new ProductAudit(); audit.setProductId(productId); audit.setType(ProductChangeTypeEnum.PUT_ON_SHELF.name()); audit.setStatus(AuditStatusEnum.PENDING.getValue()); ProductAuditData auditData = new ProductAuditData(); auditData.setOriginalStore(existingProduct); audit.setData(JSON.toJSONString(auditData)); productAuditService.create(audit); LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>() .eq(Product::getProductId, productId) .set(Product::getStatus, ProductStatusEnum.PENDING.getValue()); .set(Product::getStatus, ProductStatusEnum.AVAILABLE.getValue()) .set(Product::getShelfStatus, ProductStatusEnum.AVAILABLE.getValue()); productService.update(wrapper); } @@ -135,6 +186,9 @@ if (ProductStatusEnum.NO_AVAILABLE.getValue().equals(existingProduct.getShelfStatus())) { return; } if (!ProductStatusEnum.AVAILABLE.getValue().equals(existingProduct.getShelfStatus())) { return; } LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>() .eq(Product::getProductId, productId) .set(Product::getStatus, ProductStatusEnum.NO_AVAILABLE.getValue()) @@ -142,65 +196,12 @@ productService.update(wrapper); } @Override @EventListener @Transactional(rollbackFor = Exception.class) public void handleAuditVerdictEvent(ProductAuditVerdictEvent event) { try { ProductAudit audit = productAuditService.getById(event.getAuditId()); ProductChangeTypeEnum changeType = ProductChangeTypeEnum.valueOf(audit.getType()); if (changeType == ProductChangeTypeEnum.PUT_ON_SHELF) { this.handlePutOnShelfAuditEvent(audit); } } catch (Exception e) { log.error("处理商品审核结果异常", e); } } private void processImagesUpdate(ProductMerchantUpdateRequest request) { if (CollectionUtil.isNotEmpty(request.getDeletedImageIds())) { productImageService.deleteAll(request.getDeletedImageIds()); } if (CollectionUtil.isNotEmpty(request.getUpdatedImages())) { productImageService.batchUpdate(request.getUpdatedImages()); } if (CollectionUtil.isNotEmpty(request.getNewImages())) { List<ProductImageCreateRequest> newImages = request.getNewImages().stream().peek(i-> i.setProductId(request.getProductId())).collect(Collectors.toList()); productImageService.batchCreate(newImages); } } private void processLabelsUpdate(ProductMerchantUpdateRequest request) { if (CollectionUtil.isNotEmpty(request.getDeletedLabelIds())) { productLabelService.deleteAll(request.getDeletedLabelIds()); } if (CollectionUtil.isNotEmpty(request.getUpdatedLabels())) { productLabelService.batchUpdate(request.getUpdatedLabels()); } if (CollectionUtil.isNotEmpty(request.getNewLabels())) { List<ProductLabel> newLabels = request.getNewLabels().stream().peek(i-> i.setProductId(request.getProductId())).collect(Collectors.toList()); productLabelService.batchCreate(newLabels); } } private void handlePutOnShelfAuditEvent(ProductAudit audit) { AuditStatusEnum auditStatus = AuditStatusEnum.get(audit.getStatus()); Product existingProduct = productService.getById(audit.getProductId()); if (AuditStatusEnum.APPROVED.equals(auditStatus)) { existingProduct.setStatus(ProductStatusEnum.AVAILABLE.getValue()); existingProduct.setShelfStatus(ProductStatusEnum.AVAILABLE.getValue()); } else { existingProduct.setStatus(auditStatus.getValue()); } productService.updateById(existingProduct); } private void validateApprovedStatus(Integer status) { private void validateAuditStatus(Integer status) { Set<ProductStatusEnum> statusEnumSet = CollectionUtil.newHashSet(ProductStatusEnum.PENDING, ProductStatusEnum.UNDER_REVIEW, ProductStatusEnum.AVAILABLE); ProductStatusEnum.UNDER_REVIEW); ProductStatusEnum existingStatus = ProductStatusEnum.getOrDefault(status, ProductStatusEnum.DRAFT); if (statusEnumSet.contains(existingStatus)) { throw new BadRequestException("在售商品无法修改"); throw new BadRequestException("商品已在审核流程中无法修改"); } } } oying-system/src/main/java/com/oying/modules/pc/product/service/impl/ProductServiceImpl.java
@@ -66,6 +66,18 @@ @Override @Transactional(rollbackFor = Exception.class) public void update(Product resources, boolean isDirectUpdate) { if (isDirectUpdate) { productMapper.updateById(resources); } else { Product product = getById(resources.getProductId()); product.copy(resources); productMapper.updateById(product); } } @Override @Transactional(rollbackFor = Exception.class) public void deleteAll(List<Long> ids) { for (Long id : ids) { LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>()