智能化业务校验框架:动态设计与应用实践

news2024/12/27 1:10:04

目录

 一、业务背景

二、配置内容展示

三、商品动态配置内容展示

(一)商品spu校验信息数据

(二)商品sku校验信息数据

(三)组包商品校验信息数据

(四)商品数据校验数据持有者

(五)相关配置注解含义展示

动态配置内容

动态配置组信息

校验项内容

校验项内容校验结果

四、商品平台校验引擎加载

(一)初始化逻辑

(二)本地信息处理拦截器

(三)动态配置类型枚举

五、本地化实际动态校验接口

(一)商品动态校验内容底层接口

(二)校验处理整合函数

(三)数据整合模型展示

校验结果信息

校验结果详细

六、实际对外提供接口

(一)门店商品数据动态校验处理

(二)商品数据校验数据持有者查询整合模拟

(三)商品查询模接口

七、动态校验测试数据

(一)数据准备

(二)数据测试

(三)验证如下展示

商品spu基本信息验证

商品sku基本信息验证

组合商品基本信息验证


干货分享,感谢您的阅读!实现一套业务层级的动态校验框架。

 一、业务背景

假设实际业务上层(具体比方说商超业务方向、鲜花业务方向、医药业务方向、电商业务方向等)在实际的商品领域模型中共用同一套模型,但是各业务根据其自身特点对于商品相关信息校验内容存在差异,要求在商品保存、修改、上架等业务流程中对应的校验内容要求动态化实时校验。

我们要求假定业务相关方可依据当前现有的业务身份或在平台可自定义选择对应的业务标识,即可进行配置的生效业务配置,该业务配置会根据具体主要业务信息和其对应的生效范围生成唯一标识key进行业务管控。具体生效范围包括了具体的经营品类、品牌、门店、业务端、业务角色等。

与上述唯一业务标识绑定的内容可继续配置其对应业务白名单和动态商品校验配置。业务白名单主要包括全局白名单、品牌白名单和门店白名单,各业务对应的动态商品校验主要包含四方面的内容:商品基本信息+商品内容唯一性(商品名称在店内分类唯一、组包商品名称在店内唯一、商品upc码唯一等)+商品一致性(商品内部绑定关系一致等)+商品业务逻辑动态校验等。

配置侧主要内容配置后对线上业务需要近实时生效。

二、配置内容展示

配置侧主要配置内容梳理如下:

三、商品动态配置内容展示

(一)商品spu校验信息数据

package org.zyf.javabasic.dynamicbizvalidator.content;

import com.google.common.collect.Lists;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfig;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfigGroup;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyKey;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyMessage;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSpu;
import org.zyf.javabasic.dynamicbizvalidator.enums.DynamicConfigType;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 商品spu校验信息数据
 * @date 2023/3/8  23:45
 */
@Data
public class ProductSpuVerifyContent {
    protected long id;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.name.notnull", defaultConfigValue = "true", configType = DynamicConfigType.NOT_BLANK),
            @DynamicConfig(configName = "spu.name.length.max", defaultConfigValue = "10", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String name;

    @VerifyMessage("商品spu名称在该店内分类中已存在")
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.name.uniqueInTag", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE)
    })
    protected boolean nameUniqueInTag = true;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.skus.size.min", defaultConfigValue = "0", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "spu.skus.size.max", defaultConfigValue = "10", configType = DynamicConfigType.RANGE_MAX)
    })
    protected Integer skuSize;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.skus.attrSku.size.min", defaultConfigValue = "0", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "spu.skus.attrSku.size.max", defaultConfigValue = "10", configType = DynamicConfigType.RANGE_MAX)
    })
    protected Integer attrSkuSize;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.spCategory.isNotNull", defaultConfigValue = "false", configType = DynamicConfigType.NUMBER_NOT_NULL)
    })
    protected long categoryId;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.piccontent.content.length.max", defaultConfigValue = "4096", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String picContent;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.unit.length.max", defaultConfigValue = "20", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String unit;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.description.length.max", defaultConfigValue = "4096", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String description;

    @VerifyKey("thirdPartyPrimaryId(sourceFoodCode)")
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.thirdPartyPrimaryId.length.max", defaultConfigValue = "128", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String thirdPartyPrimaryId;

    @VerifyKey("组包使用场景")
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "spu.package.use.scene.notnull", defaultConfigValue = "true", configType = DynamicConfigType.NOT_BLANK),
            @DynamicConfig(configName = "spu.package.use.scene.length.max", defaultConfigValue = "8", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String packageUseScene;

    /**
     * ProductSpuVerifyContents
     */
    public static List<ProductSpuVerifyContent> toProductSpuVerifyContents(List<ProductPoiSpu> spuList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductSpuVerifyContent> productSpuValidEntityList = Lists.newArrayListWithCapacity(spuList.size());
        for (int i = 0; i < spuList.size(); i++) {
            productSpuValidEntityList.add(toProductSpuVerifyContent(spuList.get(i), poiProductValidatorDataHolder, i));
        }

        return productSpuValidEntityList;
    }

    /**
     * 转换成ProductSpuVerifyContent
     */
    public static ProductSpuVerifyContent toProductSpuVerifyContent(ProductPoiSpu poiSpu, PoiProductValidatorDataHolder poiProductValidatorDataHolder, int indexOfBatch) {
        /*用于模拟*/
        ProductSpuVerifyContent productSpuVerifyContent = new ProductSpuVerifyContent();
        productSpuVerifyContent.setName(poiSpu.getName());
        if (poiSpu.getHqSpuId() > 0 && StringUtils.isBlank(poiSpu.getName())) {
            /*总分模式下,商品名字是可以为空的,设置个任意不重复的值,防止非空校验*/
            productSpuVerifyContent.setName(poiSpu.getHqSpuId() + "_" + indexOfBatch);
        }

        productSpuVerifyContent.setPicContent(poiSpu.getPicContent());
        productSpuVerifyContent.setAttrSkuSize(23);
        productSpuVerifyContent.setDescription(poiSpu.getDescription());
        productSpuVerifyContent.setCategoryId(poiSpu.getCategoryId());
        productSpuVerifyContent.setNameUniqueInTag(true);
        productSpuVerifyContent.setSkuSize(poiSpu.getSkuList().size());
        productSpuVerifyContent.setThirdPartyPrimaryId(poiSpu.getSourceFoodCode());
        productSpuVerifyContent.setUnit(poiSpu.getUnit());
        productSpuVerifyContent.setPackageUseScene("热点商品售卖");

        return productSpuVerifyContent;
    }
}

(二)商品sku校验信息数据

package org.zyf.javabasic.dynamicbizvalidator.content;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.zyf.javabasic.common.utils.FormatUtils;
import org.zyf.javabasic.common.utils.FrequencyUtils;
import org.zyf.javabasic.common.utils.PackageProductUtils;
import org.zyf.javabasic.common.utils.PicUrlLegalCheckUtil;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfig;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfigGroup;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyKey;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyMessage;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSku;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSpu;
import org.zyf.javabasic.dynamicbizvalidator.dynamicdeal.ProductStaticValidator;
import org.zyf.javabasic.dynamicbizvalidator.enums.DynamicConfigType;

import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author yanfengzhang
 * @description 商品sku校验信息数据
 * @date 2023/3/8  23:42
 */
@Data
public class ProductSkuVerifyContent {
    protected long id;
    protected long poiId;
    protected long spuId;
    protected String picture;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.spec.length.min", defaultConfigValue = "0", configType = DynamicConfigType.LENGTH_MIN),
            @DynamicConfig(configName = "sku.spec.length.max", defaultConfigValue = "10", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String spec;

    @VerifyMessage("价格超过上限")
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.price.top", defaultConfigValue = "999", configType = DynamicConfigType.RANGE_MAX),
    })
    protected double price;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.stock.min", defaultConfigValue = "-9999999", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "sku.stock.max", defaultConfigValue = "9999999", configType = DynamicConfigType.RANGE_MAX),
    })
    protected int stock;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.boxNum.min", defaultConfigValue = "0", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "sku.boxNum.max", defaultConfigValue = "100", configType = DynamicConfigType.RANGE_MAX),
    })
    protected double boxNum;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.boxPrice.min", defaultConfigValue = "0", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "sku.boxPrice.max", defaultConfigValue = "100", configType = DynamicConfigType.RANGE_MAX),
    })
    protected double boxPrice;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.upc_code.isNotNull", defaultConfigValue = "false", configType = DynamicConfigType.NOT_BLANK),
    })
    protected String upc;

    @VerifyKey("thirdPartyPrimaryId(sourceFoodCode)")
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.thirdPartyPrimaryId.length.max", defaultConfigValue = "128", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String thirdPartyPrimaryId;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.locatorCode.length.max", defaultConfigValue = "50", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String locatorCode;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.tagname.length.max", defaultConfigValue = "255", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String tagName;

    @VerifyMessage("可售时间不合法")
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.shippingtimex.legal", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE),
    })
    protected boolean legalShippingTimeX = true;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.description.length.max", defaultConfigValue = "255", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String description;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.unit.length.max", defaultConfigValue = "20", configType = DynamicConfigType.LENGTH_MAX)
    })
    protected String unit;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.minOrderCount.min", defaultConfigValue = "0", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "sku.minOrderCount.max", defaultConfigValue = "50", configType = DynamicConfigType.RANGE_MAX),
    })
    protected int minOrderCount;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.weight.isNotNull", defaultConfigValue = "false", configType = DynamicConfigType.NUMBER_NOT_NULL)
    })
    protected long weight;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.upc_code.uniqueInPoi", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE),
    })
    @VerifyMessage("商品upc/ean编码在该店中已存在")
    protected boolean upcUniqueInPoi = true;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.price.range", defaultConfigValue = "100000", configType = DynamicConfigType.RANGE_MAX),
    })
    @VerifyMessage("价格修改幅度超过上限")
    protected double priceRange;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.price.frequency", defaultConfigValue = "100000", configType = DynamicConfigType.RANGE_MAX),
    })
    @VerifyMessage("价格修改频率超过上限")
    protected long priceFrequency;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.legalPicUrl", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE),
    })
    @VerifyMessage("图片地址不合法")
    protected boolean legalPicUrl = true;


    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.package.skus.size.min", defaultConfigValue = "2", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "sku.package.skus.size.max", defaultConfigValue = "100000", configType = DynamicConfigType.RANGE_MAX)
    })
    @VerifyKey("组包商品数量")
    @VerifyMessage("您设置的组包只包含一个商品,不支持设置")
    protected int packageSkuSize = 2;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.package.unique", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE),
    })
    @VerifyKey("组包店内唯一")
    @VerifyMessage("该组包中包含的商品及数量,与店铺中其他组包相同,不支持创建")
    protected boolean packageUniqueInPoi = true;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.package.legalPackagePrice", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE),
    })
    @VerifyKey("组包价格")
    @VerifyMessage("您设置的组包价与原价相同,不支持创建")
    protected boolean legalPackagePrice = true;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "sku.package.legalPackageStock", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE),
    })
    @VerifyKey("组包库存")
    @VerifyMessage("您设置的组包库存有误,不支持创建")
    protected boolean legalPackageStock = true;

    public static List<ProductSkuVerifyContent> toProductSkuVerifyContents(List<ProductPoiSku> poiSkuList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        if (CollectionUtils.isEmpty(poiSkuList)) {
            return Collections.emptyList();
        }

        List<ProductSkuVerifyContent> productSkuVerifyContents = Lists.newArrayListWithCapacity(poiSkuList.size());
        for (int i = 0; i < poiSkuList.size(); i++) {
            productSkuVerifyContents.add(toProductSkuVerifyContent(poiSkuList.get(i), i, poiProductValidatorDataHolder));
        }
        return productSkuVerifyContents;
    }

    public static ProductSkuVerifyContent toProductSkuVerifyContent(ProductPoiSku sku, int spuIndex, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        /*用于模拟*/
        ProductSkuVerifyContent productSkuVerifyContent = new ProductSkuVerifyContent();
        productSkuVerifyContent.setPoiId(sku.getPoiId());
        productSkuVerifyContent.setSpuId(sku.getPoiSpuId());

        /*总分商品特殊处理*/
        if (sku.getHqSkuId() > 0) {
            if (StringUtils.isBlank(sku.getUpcCode())) {
                productSkuVerifyContent.setUpc(sku.getHqSkuId() + "");
            }

            if (sku.getWeight() == 0) {
                productSkuVerifyContent.setWeight(1);
            }
        }

        /*upc唯一校验处理*/
        productSkuVerifyContent.setUpcUniqueInPoi(skuUpcUniqueInPoi(sku.getId(), sku.getHqSkuId(), sku.getUpcCode(), spuIndex, poiProductValidatorDataHolder));
        /*可售时间合法性校验*/
        productSkuVerifyContent.setLegalShippingTimeX(ProductStaticValidator.shippingTimeXCheck(sku.getShippingTime()));
        /*图片合法性校验*/
        productSkuVerifyContent.setLegalPicUrl(PicUrlLegalCheckUtil.checkLegaPicUrl(sku.getPicture()));
        /*验证价格*/
        processSkuPrice(productSkuVerifyContent, poiProductValidatorDataHolder.getSkuIdToSkuMap());
        /*组包商品关联单品的总数*/
        processPackageSkuSize(productSkuVerifyContent, sku);
        /*组包商品店内唯一*/
        productSkuVerifyContent.setPackageUniqueInPoi(packageSkuUniqueInPoi(sku, spuIndex, poiProductValidatorDataHolder));

        ImmutablePair<Boolean, Boolean> legalPackagePriceAndStockPair = legalPackagePriceAndStock(sku, poiProductValidatorDataHolder);
        /*组包价格设置的是否合法*/
        productSkuVerifyContent.setLegalPackagePrice(legalPackagePriceAndStockPair.getLeft());
        /*组包库存设置的是否合法*/
        productSkuVerifyContent.setLegalPackageStock(legalPackagePriceAndStockPair.getRight());

        return productSkuVerifyContent;
    }

    /**
     * 验证sku的upc码店内唯一
     */
    public static boolean skuUpcUniqueInPoi(long skuId, long hqSkuId, String upcCode, int indexOfSpu, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        /*upc唯一性校验,1、先检查数据库中是否有重复的upc 2、再校验本次批量插入的商品中有没有相同的upc*/
        boolean upcUniqueInPoi = CollectionUtils.isEmpty(existUpcInPoi(skuId, hqSkuId, upcCode, poiProductValidatorDataHolder));
        if (upcUniqueInPoi) {
            upcUniqueInPoi = CollectionUtils.isEmpty(existUpcInBatch(upcCode, indexOfSpu, poiProductValidatorDataHolder));
        }
        return upcUniqueInPoi;
    }

    /**
     * 校验upc是否重复
     */
    public static List<ProductPoiSku> existUpcInPoi(long skuId, long hqSkuId, String upcCode, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductPoiSku> resultList = Lists.newArrayList();
        if (StringUtils.isBlank(upcCode)) {
            return resultList;
        }

        /*查询当前upc是否已经存在*/
        Collection<ProductPoiSku> skuCollection = poiProductValidatorDataHolder.getUpcToSkuListMap().get(upcCode);
        /*为空表示upc在店内不存在*/
        if (CollectionUtils.isEmpty(skuCollection)) {
            return resultList;
        }

        for (ProductPoiSku existSku : skuCollection) {
            /*更新自己不算重复*/
            if (skuId > 0 && existSku.getId() == skuId) {
                continue;
            }
            /*hqSkuId相同也是更新自己, 总分的逻辑*/
            if (hqSkuId > 0 && existSku.getHqSkuId() == hqSkuId) {
                continue;
            }
            /*相同spu下的sku可以upc重复*/
            if (skuId == existSku.getPoiSpuId()) {
                continue;
            }
            /*其他情况就属于upc重复了*/
            resultList.add(existSku);
        }
        return resultList;
    }

    /**
     * 校验在同一次创建的sku中有没有重复的upc
     * 规则:当同时传多个相同UPC,且在不同SPU下时,则按照提交允许仅允许第一个创建成功
     */
    public static List<ProductPoiSku> existUpcInBatch(String upcCode, int indexOfSpu, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductPoiSku> resultList = Lists.newArrayList();
        if (StringUtils.isBlank(upcCode)) {
            return resultList;
        }

        Map<Long, List<ProductPoiSku>> spuIdToSkuListMap = poiProductValidatorDataHolder.getSpuIdToSkuListMap();
        if (MapUtils.isEmpty(spuIdToSkuListMap)) {
            return resultList;
        }

        int index = 0;
        for (Map.Entry<Long, List<ProductPoiSku>> longListEntry : spuIdToSkuListMap.entrySet()) {
            if (indexOfSpu <= index++) {
                break;
            }

            List<ProductPoiSku> skuList = longListEntry.getValue();
            for (ProductPoiSku poiSku : skuList) {
                if (upcCode.equals(poiSku.getUpcCode())) {
                    resultList.add(poiSku);
                }
            }
        }

        return resultList;
    }

    /**
     * 处理价格修复幅度和价格修改频率
     */
    public static void processSkuPrice(ProductSkuVerifyContent productSkuVerifyContent, Map<Long, ProductPoiSku> skuMap) {
        ProductPoiSku oldPoiSku = skuMap.get(productSkuVerifyContent.getId());
        if (oldPoiSku == null || oldPoiSku.getPrice() == productSkuVerifyContent.getPrice()) {
            return;
        }

        if (oldPoiSku.getPrice() == 0) {
            oldPoiSku.setPrice(0.01);
        }
        productSkuVerifyContent.setPriceRange(Math.abs((productSkuVerifyContent.getPrice() / skuMap.get(productSkuVerifyContent.getId()).getPrice() - 1) * 100));
        /*查询最后修改时间,先模拟吧*/
        /*long lastModifyTime = poiProductVerifyQueryService.getSkuPriceLastModifyTime(productSkuVerifyContent.getId());*/
        long lastModifyTime = 72L;
        if (lastModifyTime != 0) {
            productSkuVerifyContent.setPriceFrequency(FrequencyUtils.getPriceFrequency(FormatUtils.unixTime(), lastModifyTime));
        }
    }

    public static void processPackageSkuSize(ProductSkuVerifyContent productSkuVerifyContent, ProductPoiSku sku) {
        List<ProductPoiPackageSkuRel> poiPackageSkuRelList = sku.getPoiPackageSkuRelList();
        if (CollectionUtils.isEmpty(poiPackageSkuRelList)) {
            return;
        }

        int packageSkuSize = 0;
        for (ProductPoiPackageSkuRel poiPackageSkuRel : poiPackageSkuRelList) {
            String pricingRule = poiPackageSkuRel.getPricingRule();
            if (StringUtils.isBlank(pricingRule)) {
                continue;
            }

            ProductPackageSkuRel packageSkuRel = JSONObject.parseObject(pricingRule, ProductPackageSkuRel.class);
            packageSkuSize += packageSkuRel.getCount();
        }

        productSkuVerifyContent.setPackageSkuSize(packageSkuSize);
    }

    public static boolean packageSkuUniqueInPoi(ProductPoiSku poiSku, int indexOfSpu, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        /*组包唯一性校验,1、先检查数据库中是否有重复的组包 2、再校验本次批量插入的商品中有没有相同的组包*/
        boolean packageUniqueInPoi = CollectionUtils.isEmpty(existPackageSkuInPoi(poiSku, poiProductValidatorDataHolder));
        if (packageUniqueInPoi) {
            packageUniqueInPoi = CollectionUtils.isEmpty(existPackageSkuInBatch(poiSku, indexOfSpu, poiProductValidatorDataHolder));
        }
        return packageUniqueInPoi;
    }

    /**
     * 校验在门店内有没有重复的组包
     */
    public static List<Long> existPackageSkuInPoi(ProductPoiSku poiSku, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<Long> resultList = Lists.newArrayList();
        List<ProductPoiPackageSkuRel> poiPackageSkuRelList = poiSku.getPoiPackageSkuRelList();
        if (CollectionUtils.isEmpty(poiPackageSkuRelList)) {
            return Collections.emptyList();
        }

        /*转成唯一key*/
        String packageSkuIdStr = PackageProductUtils.toPackageSkuIdStr(poiPackageSkuRelList);

        Map<String, Long> packageSkuIdToSkuMap = poiProductValidatorDataHolder.getSkuIdListStrToPackageSkuMap();
        if (packageSkuIdToSkuMap.containsKey(packageSkuIdStr) && packageSkuIdToSkuMap.get(packageSkuIdStr) != poiSku.getId()) {
            resultList.add(packageSkuIdToSkuMap.get(packageSkuIdStr));
        }

        return resultList;
    }

    /**
     * 校验在同一次创建的sku中有没有重复的组包
     */
    public static List<ProductPoiSku> existPackageSkuInBatch(ProductPoiSku poiSku, int indexOfSpu, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductPoiSku> resultList = Lists.newArrayList();
        List<ProductPoiPackageSkuRel> poiPackageSkuRelList = poiSku.getPoiPackageSkuRelList();
        if (CollectionUtils.isEmpty(poiPackageSkuRelList)) {
            return Collections.emptyList();
        }

        String packageSkuIdStr = PackageProductUtils.toPackageSkuIdStr(poiPackageSkuRelList);
        List<ProductPoiSpu> spuList = poiProductValidatorDataHolder.getSpuList();
        for (int i = 0; i < indexOfSpu; i++) {
            ProductPoiSpu poiSpu = spuList.get(i);
            if (poiSpu.getIsPackage() == 0) {
                continue;
            }

            List<ProductPoiSku> skuList = poiSpu.getSkuList();
            if (CollectionUtils.isEmpty(skuList)) {
                continue;
            }

            for (ProductPoiSku sku : skuList) {
                List<ProductPoiPackageSkuRel> tempPoiPackageSkuRelList = sku.getPoiPackageSkuRelList();
                if (CollectionUtils.isEmpty(tempPoiPackageSkuRelList)) {
                    continue;
                }

                String tempPackageSkuIdStr = PackageProductUtils.toPackageSkuIdStr(tempPoiPackageSkuRelList);
                if (Objects.equals(packageSkuIdStr, tempPackageSkuIdStr)) {
                    resultList.add(sku);
                }
            }
        }

        return resultList;
    }

    public static ImmutablePair<Boolean, Boolean> legalPackagePriceAndStock(ProductPoiSku poiSku, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductPoiPackageSkuRel> poiPackageSkuRelList = poiSku.getPoiPackageSkuRelList();
        if (CollectionUtils.isEmpty(poiPackageSkuRelList)) {
            return ImmutablePair.of(true, true);
        }

        double originPrice = 0;
        double discountPrice = 0;
        int stock = Integer.MAX_VALUE;

        Map<Long, ProductPoiSku> skuIdToPackageRelSkuMap = poiProductValidatorDataHolder.getSkuIdToPackageRelSkuMap();
        for (ProductPoiPackageSkuRel poiPackageSkuRel : poiPackageSkuRelList) {
            ProductPoiSku originSku = skuIdToPackageRelSkuMap.get(poiPackageSkuRel.getPoiSkuId());
            if (originSku == null) {
                continue;
            }

            if (StringUtils.isBlank(poiPackageSkuRel.getPricingRule())) {
                continue;
            }
            ProductPackageSkuRel packageSkuRel = JSONObject.parseObject(poiPackageSkuRel.getPricingRule(), ProductPackageSkuRel.class);

            double skuPrice = originSku.getPrice();
            DecimalFormat df = new DecimalFormat("#.00");
            originPrice += skuPrice * packageSkuRel.getCount();
            discountPrice += (Double.parseDouble(df.format(skuPrice * packageSkuRel.getDiscount() / 10)) * packageSkuRel.getCount());

            //库存
            if (originSku.getStock() != -1) {
                int packageSkuStock = (int) Math.floor(originSku.getStock() / packageSkuRel.getCount());
                stock = Math.min(stock, packageSkuStock);
            }
        }

        boolean legalPackagePrice = true;
        if (originPrice <= discountPrice || Math.abs(poiSku.getPrice() - discountPrice) >= 0.1) {
            legalPackagePrice = false;
        }

        boolean legalPackageStock = true;
        //无限库存
        if (stock == Integer.MAX_VALUE) {
            if (poiSku.getStock() != -1) {
                legalPackageStock = false;
            }
        } else {
            if (stock != poiSku.getStock()) {
                legalPackageStock = false;
            }
        }

        return ImmutablePair.of(legalPackagePrice, legalPackageStock);
    }
}

(三)组包商品校验信息数据

package org.zyf.javabasic.dynamicbizvalidator.content;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfig;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfigGroup;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyKey;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyMessage;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.enums.DynamicConfigType;

import java.util.Collections;
import java.util.List;

/**
 * @author yanfengzhang
 * @description 组包商品校验信息数据
 * @date 2023/3/8  23:40
 */
@Data
public class ProductPackageVerifyContent {
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "package.sku.count.min", defaultConfigValue = "1", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "package.sku.count.max", defaultConfigValue = "10000", configType = DynamicConfigType.RANGE_MAX),

    })
    @VerifyKey("组包商品数量")
    private int count;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "package.sku.discount.min", defaultConfigValue = "0", configType = DynamicConfigType.RANGE_MIN),
            @DynamicConfig(configName = "package.sku.discount.max", defaultConfigValue = "10", configType = DynamicConfigType.RANGE_MAX)
    })
    @VerifyKey("组包商品的折扣")
    private double discount;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "package.name.uniqueInTag", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE)
    })
    @VerifyMessage("组包内有商品不存在,不支持创建")
    private boolean existSku = true;

    @VerifyMessage("您设置的组包包含组包商品,不支持创建")
    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "package.sku.notExistPackageSku", defaultConfigValue = "true", configType = DynamicConfigType.IS_TRUE)
    })
    private boolean notExistPackageSku = true;

    @DynamicConfigGroup(config = {
            @DynamicConfig(configName = "package.sku.pricingRule", defaultConfigValue = "true", configType = DynamicConfigType.NOT_BLANK)
    })
    private String pricingRule;

    public static List<ProductPackageVerifyContent> toProductPackageVerifyContents(List<ProductPoiPackageSkuRel> poiPackageSkuRelList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        if (CollectionUtils.isEmpty(poiPackageSkuRelList)) {
            return Collections.emptyList();
        }

        List<ProductPackageVerifyContent> productPackageVerifyContents = Lists.newArrayListWithCapacity(poiPackageSkuRelList.size());

        ProductPackageVerifyContent productPackageVerifyContent;
        for (ProductPoiPackageSkuRel poiPackageSkuRel : poiPackageSkuRelList) {
            productPackageVerifyContent = new ProductPackageVerifyContent();
            productPackageVerifyContents.add(productPackageVerifyContent);

            productPackageVerifyContent.setPricingRule(poiPackageSkuRel.getPricingRule());

            String pricingRule = poiPackageSkuRel.getPricingRule();
            if (StringUtils.isBlank(pricingRule)) {
                continue;
            }
            ProductPackageSkuRel packageSkuRel = JSONObject.parseObject(pricingRule, ProductPackageSkuRel.class);
            productPackageVerifyContent.setCount(packageSkuRel.getCount());
            productPackageVerifyContent.setDiscount(packageSkuRel.getDiscount());

            productPackageVerifyContent.setExistSku(poiProductValidatorDataHolder.getSkuIdToPackageRelSkuMap().containsKey(poiPackageSkuRel.getPoiSkuId()));

            productPackageVerifyContent.setNotExistPackageSku(!(poiProductValidatorDataHolder.getPackageSkuIdToPackageSkuMap().containsKey(poiPackageSkuRel.getPoiSkuId())));
        }

        return productPackageVerifyContents;
    }
}

(四)商品数据校验数据持有者

package org.zyf.javabasic.dynamicbizvalidator.content;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import lombok.Data;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSku;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSpu;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiTag;

import java.util.List;
import java.util.Map;

/**
 * @author yanfengzhang
 * @description 商品数据校验数据持有者
 * * 为尽量减少校验时查询过多数据,需要在校验之前,查询出所有的准备数据,放到这个类的实例中。
 * * 数据查询要尽量缩小范围,避免过多的查询与后续的循环对比。
 * @date 2023/3/9  23:08
 */
@Data
public class PoiProductValidatorDataHolder {
    /**
     * 描述:spu集合
     * 功能:验证spu名称本次保存列表唯一
     */
    private List<ProductPoiSpu> spuList = Lists.newArrayList();

    /**
     * 描述:店内分类id与商品名称集合的映射
     * 功能:验证商品名称店内分类下唯一
     */
    private Multimap<Long, ProductPoiSpu> tagIdToSpuMultimap = HashMultimap.create();

    /**
     * 描述:分类集合
     * 功能:验证店内分类名称唯一
     */
    private List<ProductPoiTag> tagList = Lists.newArrayList();

    /**
     * 描述:本次待保存的sku列表, 按spu的顺序组合
     * 功能:验证本次批量保存的sku是否有重复的数据,比如upc码
     */
    private Map<Long, List<ProductPoiSku>> spuIdToSkuListMap = Maps.newLinkedHashMap();

    /**
     * 描述:upc码对应的sku集合
     * 功能:做upc重复校验
     */
    private Multimap<String, ProductPoiSku> upcToSkuListMap = HashMultimap.create();

    /**
     * 描述:skuId对应的sku
     * 功能:对旧数据依赖的校验会用的,比如价格
     */
    private Map<Long, ProductPoiSku> skuIdToSkuMap = Maps.newHashMap();

    /**
     * 描述:packageSkuId对应的skuId
     * 功能:批量创建组包商品时,比较门店内有没有相同的组包商品
     */
    private Map<String, Long> skuIdListStrToPackageSkuMap = Maps.newHashMap();

    /**
     * 描述:skuId对应的组包关联的sku
     * 功能:校验组包关联的单品sku是否存在
     */
    private Map<Long, ProductPoiSku> skuIdToPackageRelSkuMap = Maps.newHashMap();

    /**
     * 描述:组包skuId对应的组包sku
     * 功能:校验组包关联的单品sku是否为组包商品
     */
    private Map<Long, ProductPoiSku> packageSkuIdToPackageSkuMap = Maps.newHashMap();
}

(五)相关配置注解含义展示

动态配置内容

package org.zyf.javabasic.dynamicbizvalidator.annotation;

import org.zyf.javabasic.dynamicbizvalidator.enums.DynamicConfigType;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description 动态配置内容
 * @date 2023/3/8  23:33
 */
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicConfig {
    /**
     * 配置名称信息
     */
    String configName() default "";

    /**
     * 配置值信息
     */
    String value() default "";

    /**
     * 配置值默认信息
     */
    String defaultConfigValue() default "";

    /**
     * 是否覆盖
     */
    boolean override() default false;

    /**
     * 配置类型要求
     */
    DynamicConfigType configType() default DynamicConfigType.NOT_BLANK;
}

动态配置组信息

package org.zyf.javabasic.dynamicbizvalidator.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description 动态配置组信息
 * @date 2023/3/8  23:34
 */
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicConfigGroup {
    /**
     * 动态配置内容集合
     */
    DynamicConfig[] config() default {};
}

校验项内容

package org.zyf.javabasic.dynamicbizvalidator.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description 校验项内容
 * @date 2023/3/8  23:35
 */
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface VerifyKey {
    /**
     * 校验项内容
     */
    String value() default "";
}

校验项内容校验结果

package org.zyf.javabasic.dynamicbizvalidator.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description 校验项内容校验结果
 * @date 2023/3/8  23:35
 */
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface VerifyMessage {
    /**
     * 校验内容结论
     */
    String value() default "";

    /**
     * 覆盖信息
     */
    boolean override() default false;
}

四、商品平台校验引擎加载

(一)初始化逻辑

对应以上配置内容结合前面的展述可以将各业务标识实际配置的动态业务校验信息在入库后,监听DB变更将其加载到对应业务校验服务器中,具体加载初始化jvm代码如下:

package org.zyf.javabasic.dynamicbizvalidator.dynamicdeal;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.TypeUtils;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.cfg.ConstraintMapping;
import org.hibernate.validator.cfg.context.PropertyConstraintMappingContext;
import org.hibernate.validator.cfg.context.PropertyTarget;
import org.hibernate.validator.cfg.defs.AssertTrueDef;
import org.hibernate.validator.cfg.defs.DecimalMaxDef;
import org.hibernate.validator.cfg.defs.DecimalMinDef;
import org.hibernate.validator.cfg.defs.LengthDef;
import org.hibernate.validator.cfg.defs.NotBlankDef;
import org.hibernate.validator.cfg.defs.RangeDef;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfig;
import org.zyf.javabasic.dynamicbizvalidator.annotation.DynamicConfigGroup;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductPackageVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductSkuVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductSpuVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.enums.DynamicConfigType;

import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

/**
 * @author yanfengzhang
 * @description 商品平台校验引擎
 * 规定:每个业务标识拥有一个改类的实例
 * @date 2023/3/9  23:03
 */
@Slf4j
public class DynamicValidatorEngine {
    /**
     * 各业务方标识所对应的Validator
     */
    public final static Map<String, Validator> bizValidatorMap = Maps.newConcurrentMap();
    /**
     * 各业务方标识是否有数据唯一性校验
     * 如果存在数据唯一性校验,是需要查询数据库的。
     */
    public final static Map<String, Boolean> bizUniqueFlagMap = Maps.newConcurrentMap();

    /**
     * 初始化业务动态配置数据校验内容信息
     *
     * @param bizInfo       业务详细内容:
     *                      比方说实际业务信息如:商超-水果-鲜切水果/果捞店、商超-宠物-宠物食品/用品店、商超-超市便利-小型超市、商超-超市便利-便利店、商超-超市便利-大型超市/卖场、
     *                      商超-数码家电-电脑数码店、医药-医药健康-综合药店、医药-医药健康-营养保健品店、医药-医药健康-跨境综合药店、外卖-美食-中式正餐、外卖-美食-全球美食、外卖-甜点-西式点心、
     *                      外卖-甜点-生日蛋糕、外卖-甜点-水果捞、外卖-饮品-咖啡、外卖-饮品-纯茶/凉茶、外卖-饮品-奶茶、外卖-饮品-果汁等
     *                      备注:业务信息可以不断扩展进行精细化管控等处理,加大灵活性,业务细化后按细化规则配置和提取差异化动态配置校验
     * @param configContent
     */
    public static void init(final String bizInfo, final JSONObject configContent) {
        HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class)
                .configure()
                .messageInterpolator(new LocaleMessageInterpolator(new ResourceBundleMessageInterpolator(), Locale.SIMPLIFIED_CHINESE));
        ConstraintMapping constraintMapping = configuration.createConstraintMapping();

        initBizConstraint(bizInfo, ProductSpuVerifyContent.class, constraintMapping, configContent);
        initBizConstraint(bizInfo, ProductSkuVerifyContent.class, constraintMapping, configContent);
        initBizConstraint(bizInfo, ProductPackageVerifyContent.class, constraintMapping, configContent);

        bizValidatorMap.put(bizInfo, configuration.addMapping(constraintMapping).buildValidatorFactory().getValidator());
    }

    /**
     * 初始化具体业务校验规则信息
     *
     * @param bizInfo           业务详细内容:
     *                          *                比方说实际业务信息如:商超-水果-鲜切水果/果捞店、商超-宠物-宠物食品/用品店、商超-超市便利-小型超市、商超-超市便利-便利店、商超-超市便利-大型超市/卖场、
     *                          *                商超-数码家电-电脑数码店、医药-医药健康-综合药店、医药-医药健康-营养保健品店、医药-医药健康-跨境综合药店、外卖-美食-中式正餐、外卖-美食-全球美食、外卖-甜点-西式点心、
     *                          *                外卖-甜点-生日蛋糕、外卖-甜点-水果捞、外卖-饮品-咖啡、外卖-饮品-纯茶/凉茶、外卖-饮品-奶茶、外卖-饮品-果汁等
     *                          *                备注:业务信息可以不断扩展进行精细化管控等处理,加大灵活性,业务细化后按细化规则配置和提取差异化动态配置校验
     * @param clazz             具体业务校验数据
     * @param constraintMapping 映射内容
     * @param configContent     配置内容
     */
    private static void initBizConstraint(final String bizInfo, final Class clazz, final ConstraintMapping constraintMapping,
                                          final JSONObject configContent) {
        PropertyTarget propertyTarget = constraintMapping.type(clazz);

        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            Annotation[] declaredAnnotations = field.getAnnotations();
            if (ArrayUtils.isEmpty(declaredAnnotations)) {
                continue;
            }

            for (Annotation declaredAnnotation : declaredAnnotations) {
                if (DynamicConfigGroup.class.equals(declaredAnnotation.annotationType())) {
                    DynamicConfigGroup dynamicConfigGroup = (DynamicConfigGroup) declaredAnnotation;
                    DynamicConfig[] config = dynamicConfigGroup.config();
                    Map<DynamicConfigType, DynamicConfig> dynamicConfigMap = Maps.newHashMap();
                    if (ArrayUtils.isNotEmpty(config)) {
                        for (DynamicConfig dynamicConfig : config) {
                            dynamicConfigMap.put(dynamicConfig.configType(), dynamicConfig);
                        }
                        setPropertyConstraint(propertyTarget, field.getName(), dynamicConfigMap, configContent);
                    }
                    break;
                }
            }

        }
        bizUniqueFlagMap.put(bizInfo, Boolean.TRUE);
    }

    /**
     * 设置单个属性的规则
     *
     * @param propertyTarget   具体设置规则
     * @param fieldName        具体作用字段
     * @param dynamicConfigMap 动态配置项
     * @param configContent    配置内容
     */
    private static void setPropertyConstraint(PropertyTarget propertyTarget, String fieldName,
                                              Map<DynamicConfigType, DynamicConfig> dynamicConfigMap,
                                              JSONObject configContent) {
        PropertyConstraintMappingContext property = propertyTarget.property(fieldName, ElementType.FIELD);

        /* 是否为空字符的校验 */
        if (dynamicConfigMap.containsKey(DynamicConfigType.NOT_BLANK)) {
            if (getConfigValue(dynamicConfigMap.get(DynamicConfigType.NOT_BLANK), configContent, Boolean.class)) {
                property.constraint(new NotBlankDef());
            }
        }

        /*字符长度的校验*/
        LengthDef lengthDef = null;
        if (dynamicConfigMap.containsKey(DynamicConfigType.LENGTH_MIN)) {
            lengthDef = new LengthDef().min(getConfigValue(dynamicConfigMap.get(DynamicConfigType.LENGTH_MIN), configContent, Integer.class));
        }
        if (dynamicConfigMap.containsKey(DynamicConfigType.LENGTH_MAX)) {
            if (lengthDef == null) {
                lengthDef = new LengthDef();
            }
            lengthDef.max(getConfigValue(dynamicConfigMap.get(DynamicConfigType.LENGTH_MAX), configContent, Integer.class));
        }
        if (lengthDef != null) {
            property.constraint(lengthDef);
        }

        /*数字范围的校验*/
        RangeDef rangeDef = null;
        if (dynamicConfigMap.containsKey(DynamicConfigType.RANGE_MIN)) {
            rangeDef = new RangeDef().min(getConfigValue(dynamicConfigMap.get(DynamicConfigType.RANGE_MIN), configContent, Integer.class));
        }
        if (dynamicConfigMap.containsKey(DynamicConfigType.RANGE_MAX)) {
            if (rangeDef == null) {
                rangeDef = new RangeDef();
            }
            rangeDef.max(getConfigValue(dynamicConfigMap.get(DynamicConfigType.RANGE_MAX), configContent, Integer.class));
        }
        if (rangeDef != null) {
            property.constraint(rangeDef);
        }

        /*是否是true的校验*/
        if (dynamicConfigMap.containsKey(DynamicConfigType.IS_TRUE)
                && getConfigValue(dynamicConfigMap.get(DynamicConfigType.IS_TRUE), configContent, Boolean.class)) {
            property.constraint(new AssertTrueDef());
        }

        /*是否是必填数值的校验*/
        if (dynamicConfigMap.containsKey(DynamicConfigType.NUMBER_NOT_NULL)
                && getConfigValue(dynamicConfigMap.get(DynamicConfigType.NUMBER_NOT_NULL), configContent, Boolean.class)) {
            property.constraint(new RangeDef().min(1));
        }

        /*是否为规定的含小数取值的校验*/
        if (dynamicConfigMap.containsKey(DynamicConfigType.DEECIMAL_MIN)) {
            property.constraint(new DecimalMinDef().value(getConfigValue(dynamicConfigMap.get(DynamicConfigType.DEECIMAL_MIN), configContent, String.class)));
        }
        if (dynamicConfigMap.containsKey(DynamicConfigType.DEECIMAL_MAX)) {
            property.constraint(new DecimalMaxDef().value(getConfigValue(dynamicConfigMap.get(DynamicConfigType.DEECIMAL_MIN), configContent, String.class)));
        }
    }

    /**
     * 解析配置值
     *
     * @param dynamicConfig 动态配置
     * @param configContent 配置内容
     * @param clazz         具体格式
     */
    private static <T> T getConfigValue(DynamicConfig dynamicConfig, JSONObject configContent, Class<T> clazz) {
        try {
            if (configContent.containsKey(dynamicConfig.configName())) {
                return configContent.getObject(dynamicConfig.configName(), clazz);
            }
            return TypeUtils.cast(dynamicConfig.defaultConfigValue(), clazz, ParserConfig.getGlobalInstance());
        } catch (Exception e) {
            log.error("DynamicValidatorEngine getConfigValue exception,dynamicConfigName={},configContent={}", dynamicConfig.configName(), configContent);
            return null;
        }
    }

    /**
     * 当前业务详细的动态配置校验
     *
     * @param currentBizKey 业务详细内容:
     *                      *                比方说实际业务信息如:商超-水果-鲜切水果/果捞店、商超-宠物-宠物食品/用品店、商超-超市便利-小型超市、商超-超市便利-便利店、商超-超市便利-大型超市/卖场、
     *                      *                商超-数码家电-电脑数码店、医药-医药健康-综合药店、医药-医药健康-营养保健品店、医药-医药健康-跨境综合药店、外卖-美食-中式正餐、外卖-美食-全球美食、外卖-甜点-西式点心、
     *                      *                外卖-甜点-生日蛋糕、外卖-甜点-水果捞、外卖-饮品-咖啡、外卖-饮品-纯茶/凉茶、外卖-饮品-奶茶、外卖-饮品-果汁等
     *                      *                备注:业务信息可以不断扩展进行精细化管控等处理,加大灵活性,业务细化后按细化规则配置和提取差异化动态配置校验
     * @return 当前业务详细的动态配置校验内容
     */
    public static Validator currentBizValidator(String currentBizKey) {
        Validator currentValidator = bizValidatorMap.get(currentBizKey);
        if (Objects.isNull(currentValidator)) {
            throw new IllegalStateException(String.format("currentBizKey[%s] can't get legal currentValidator", currentBizKey));
        }
        return currentValidator;
    }

    /**
     * 当前业务详细的动态唯一性配置校验
     *
     * @param currentBizKey 业务详细内容:
     *                      *                比方说实际业务信息如:商超-水果-鲜切水果/果捞店、商超-宠物-宠物食品/用品店、商超-超市便利-小型超市、商超-超市便利-便利店、商超-超市便利-大型超市/卖场、
     *                      *                商超-数码家电-电脑数码店、医药-医药健康-综合药店、医药-医药健康-营养保健品店、医药-医药健康-跨境综合药店、外卖-美食-中式正餐、外卖-美食-全球美食、外卖-甜点-西式点心、
     *                      *                外卖-甜点-生日蛋糕、外卖-甜点-水果捞、外卖-饮品-咖啡、外卖-饮品-纯茶/凉茶、外卖-饮品-奶茶、外卖-饮品-果汁等
     *                      *                备注:业务信息可以不断扩展进行精细化管控等处理,加大灵活性,业务细化后按细化规则配置和提取差异化动态配置校验
     * @return 当前业务详细的动态唯一性配置校验内容
     */
    public static boolean currentBizUniqueFlag(String currentBizKey) {
        Boolean currentUniqueFlag = bizUniqueFlagMap.get(currentBizKey);
        if (Objects.isNull(currentUniqueFlag)) {
            return false;
        }
        return currentUniqueFlag;
    }
}

(二)本地信息处理拦截器

package org.zyf.javabasic.dynamicbizvalidator.dynamicdeal;

import javax.validation.MessageInterpolator;
import java.util.Locale;

/**
 * @author yanfengzhang
 * @description 本地信息处理拦截器
 * @date 2023/3/9  23:15
 */
public class LocaleMessageInterpolator implements MessageInterpolator {
    private final MessageInterpolator defaultInterpolator;
    private final Locale defaultLocale;

    public LocaleMessageInterpolator(MessageInterpolator interpolator, Locale locale) {
        this.defaultLocale = locale;
        this.defaultInterpolator = interpolator;
    }

    @Override
    public String interpolate(String message, Context context) {
        return defaultInterpolator.interpolate(message,
                context,
                this.defaultLocale);
    }

    @Override
    public String interpolate(String message, Context context, Locale locale) {
        return defaultInterpolator.interpolate(message, context, this.defaultLocale);
    }
}

(三)动态配置类型枚举

package org.zyf.javabasic.dynamicbizvalidator.enums;

/**
 * @author yanfengzhang
 * @description 动态配置类型枚举
 * @date 2023/3/8  23:33
 */
public enum DynamicConfigType {
    NOT_BLANK,
    LENGTH_MIN,
    LENGTH_MAX,
    RANGE_MIN,
    RANGE_MAX,
    IS_TRUE,
    NUMBER_NOT_NULL,
    DEECIMAL_MIN,
    DEECIMAL_MAX;
}

五、本地化实际动态校验接口

(一)商品动态校验内容底层接口

package org.zyf.javabasic.dynamicbizvalidator.dynamicdeal;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductPackageVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductSkuVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductSpuVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.model.VerifyResult;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.List;
import java.util.Set;

/**
 * @author yanfengzhang
 * @description 商品动态校验内容
 * @date 2023/3/9  23:53
 */
@Service
@Log4j2
public class ProductDynamicValidator {

    /**
     * 具体商品spu校验
     *
     * @param currentBizKey            业务详细内容:
     *                                 *                比方说实际业务信息如:商超-水果-鲜切水果/果捞店、商超-宠物-宠物食品/用品店、商超-超市便利-小型超市、商超-超市便利-便利店、商超-超市便利-大型超市/卖场、
     *                                 *                商超-数码家电-电脑数码店、医药-医药健康-综合药店、医药-医药健康-营养保健品店、医药-医药健康-跨境综合药店、外卖-美食-中式正餐、外卖-美食-全球美食、外卖-甜点-西式点心、
     *                                 *                外卖-甜点-生日蛋糕、外卖-甜点-水果捞、外卖-饮品-咖啡、外卖-饮品-纯茶/凉茶、外卖-饮品-奶茶、外卖-饮品-果汁等
     *                                 *                备注:业务信息可以不断扩展进行精细化管控等处理,加大灵活性,业务细化后按细化规则配置和提取差异化动态配置校验
     * @param productSpuVerifyContents 商品spu校验信息数据
     * @return 校验结果
     */
    public List<VerifyResult> spuVerify(String currentBizKey, List<ProductSpuVerifyContent> productSpuVerifyContents) {
        /*1.校验内容为空则直接返回结果即可*/
        if (CollectionUtils.isEmpty(productSpuVerifyContents)) {
            return Lists.newArrayList();
        }

        List<VerifyResult> resultList = Lists.newArrayList();
        Validator validator = DynamicValidatorEngine.currentBizValidator(currentBizKey);
        /*门店下所有商品基本数据校验*/
        productSpuVerifyContents.forEach(productSpuVerifyContent -> {
            Set<ConstraintViolation<ProductSpuVerifyContent>> validateResult = validator.validate(productSpuVerifyContent);
            resultList.add(buildVerifyResult(validateResult, ProductSpuVerifyContent.class));
        });

        return resultList;
    }

    /**
     * 商品sku校验
     *
     * @param currentBizKey            业务详细内容:
     *                                 *                比方说实际业务信息如:商超-水果-鲜切水果/果捞店、商超-宠物-宠物食品/用品店、商超-超市便利-小型超市、商超-超市便利-便利店、商超-超市便利-大型超市/卖场、
     *                                 *                商超-数码家电-电脑数码店、医药-医药健康-综合药店、医药-医药健康-营养保健品店、医药-医药健康-跨境综合药店、外卖-美食-中式正餐、外卖-美食-全球美食、外卖-甜点-西式点心、
     *                                 *                外卖-甜点-生日蛋糕、外卖-甜点-水果捞、外卖-饮品-咖啡、外卖-饮品-纯茶/凉茶、外卖-饮品-奶茶、外卖-饮品-果汁等
     *                                 *                备注:业务信息可以不断扩展进行精细化管控等处理,加大灵活性,业务细化后按细化规则配置和提取差异化动态配置校验
     * @param productSkuVerifyContents 商品sku校验信息数据
     * @return 校验结果
     */
    public List<VerifyResult> skuVerify(String currentBizKey, List<ProductSkuVerifyContent> productSkuVerifyContents) {
        if (CollectionUtils.isEmpty(productSkuVerifyContents)) {
            return Lists.newArrayList();
        }

        List<VerifyResult> resultList = Lists.newArrayList();
        Validator validator = DynamicValidatorEngine.currentBizValidator(currentBizKey);
        productSkuVerifyContents.forEach(productSkuVerifyContent -> {
            Set<ConstraintViolation<ProductSkuVerifyContent>> validateResult = validator.validate(productSkuVerifyContent);
            resultList.add(buildVerifyResult(validateResult, ProductSkuVerifyContent.class));
        });

        return resultList;
    }

    /**
     * 组包商品校验
     *
     * @param currentBizKey                业务详细内容:
     *                                     *                比方说实际业务信息如:商超-水果-鲜切水果/果捞店、商超-宠物-宠物食品/用品店、商超-超市便利-小型超市、商超-超市便利-便利店、商超-超市便利-大型超市/卖场、
     *                                     *                商超-数码家电-电脑数码店、医药-医药健康-综合药店、医药-医药健康-营养保健品店、医药-医药健康-跨境综合药店、外卖-美食-中式正餐、外卖-美食-全球美食、外卖-甜点-西式点心、
     *                                     *                外卖-甜点-生日蛋糕、外卖-甜点-水果捞、外卖-饮品-咖啡、外卖-饮品-纯茶/凉茶、外卖-饮品-奶茶、外卖-饮品-果汁等
     *                                     *                备注:业务信息可以不断扩展进行精细化管控等处理,加大灵活性,业务细化后按细化规则配置和提取差异化动态配置校验
     * @param productPackageVerifyContents 组包商品校验信息数据
     * @return 校验结果
     */
    public List<VerifyResult> packageVerify(String currentBizKey, List<ProductPackageVerifyContent> productPackageVerifyContents) {
        if (CollectionUtils.isEmpty(productPackageVerifyContents)) {
            return Lists.newArrayList();
        }
        Validator validator = DynamicValidatorEngine.currentBizValidator(currentBizKey);
        List<Set<ConstraintViolation<ProductPackageVerifyContent>>> resultList = Lists.newArrayList();
        productPackageVerifyContents.forEach(productPackageVerifyContent -> {
            resultList.add(validator.validate(productPackageVerifyContent));
        });
        List<VerifyResult> verifyResultList = this.buildVerifyResult(resultList, ProductPackageVerifyContent.class);
        return verifyResultList;
    }

    /**
     * 批量构建标准的校验结果
     */
    private <T> List<VerifyResult> buildVerifyResult(List<Set<ConstraintViolation<T>>> constraintViolationList, Class<T> clazz) {
        List<VerifyResult> list = Lists.newArrayList();
        for (Set<ConstraintViolation<T>> set : constraintViolationList) {
            list.add(this.buildVerifyResult(set, clazz));
        }
        return list;
    }

    /**
     * 构建标准的校验结果
     */
    private <T> VerifyResult buildVerifyResult(Set<ConstraintViolation<T>> constraintViolationSet, Class<T> clazz) {
        if (CollectionUtils.isEmpty(constraintViolationSet)) {
            return VerifyResult.builder().illegal(false).propertyMessageMap(Maps.<String, String>newHashMap()).build();
        }

        VerifyResult verifyResult = VerifyResult.builder().illegal(true).propertyMessageMap(Maps.<String, String>newHashMap()).build();
        for (ConstraintViolation<T> constraintViolation : constraintViolationSet) {
            new ConstraintViolationTransformFunction<>(clazz, verifyResult).apply(constraintViolation);
        }

        log.debug(verifyResult.toString());
        return verifyResult;
    }
}

(二)校验处理整合函数

package org.zyf.javabasic.dynamicbizvalidator.dynamicdeal;

import com.google.common.base.Function;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyKey;
import org.zyf.javabasic.dynamicbizvalidator.annotation.VerifyMessage;
import org.zyf.javabasic.dynamicbizvalidator.model.VerifyResult;

import javax.validation.ConstraintViolation;
import javax.validation.Path;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Objects;

/**
 * @author yanfengzhang
 * @description 校验处理整合
 * @date 2023/3/9  23:08
 */
@AllArgsConstructor
@Slf4j
public class ConstraintViolationTransformFunction<T> implements Function<ConstraintViolation<T>, VerifyResult> {

    private Class<T> validClazz;
    private VerifyResult verifyResult;

    @Override
    public VerifyResult apply(ConstraintViolation<T> constraintViolation) {
        Iterator<Path.Node> it = constraintViolation.getPropertyPath().iterator();
        String fieldName = null;
        while (it.hasNext()) {
            fieldName = it.next().getName();
        }
        if (Objects.nonNull(fieldName)) {
            try {
                Field field = validClazz.getDeclaredField(fieldName);
                VerifyMessage verifyMessage = field.getAnnotation(VerifyMessage.class);
                String message = constraintViolation.getMessage();
                if (Objects.nonNull(verifyMessage) && StringUtils.isNotBlank(verifyMessage.value())) {
                    message = verifyMessage.value();
                }

                VerifyKey key = field.getAnnotation(VerifyKey.class);
                String keyValue = (key != null && key.value().length() != 0) ?
                        key.value() :
                        constraintViolation.getPropertyPath().toString();
                verifyResult.getPropertyMessageMap().put(keyValue, message);

                return verifyResult;
            } catch (NoSuchFieldException | SecurityException e) {
                log.warn("Cannot find field " + fieldName, e);
            }
        }
        verifyResult.getPropertyMessageMap().put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
        return verifyResult;
    }
}

(三)数据整合模型展示

校验结果信息

package org.zyf.javabasic.dynamicbizvalidator.model;

import lombok.Builder;
import lombok.Data;

import java.util.Map;

/**
 * @author yanfengzhang
 * @description 校验结果信息
 * @date 2023/3/9  23:55
 */
@Data
@Builder
public class VerifyResult {
    private boolean illegal;
    private Map<String, String> propertyMessageMap;

    public String getErrorMsg() {
        if (propertyMessageMap != null && propertyMessageMap.size() > 0) {
            return propertyMessageMap.values().toString();
        }
        return "";
    }
}

校验结果详细

package org.zyf.javabasic.dynamicbizvalidator.model;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.util.List;
import java.util.Map;

/**
 * @author yanfengzhang
 * @description 校验源集合sourceList与校验结果集合verifyResultList保证顺序,即校验源元素与校验结果元素哦在同一索引位置
 * @date 2023/3/9  23:40
 */
@Data
@Slf4j
public class VerifyResultDetail<T> {
    /**
     * 校验源集合
     */
    private List<T> sourceList;
    /**
     * 校验结果集合
     */
    private List<VerifyResult> verifyResultList;
    /**
     * T的对象域变量校验结果
     */
    private List<Map<SubEntity, VerifyResultDetail>> verifyResultMapList = Lists.newArrayList();
    /**
     * 校验合法的对象集合
     */
    private List<VerifyEntity<T>> legalList = Lists.newArrayList();
    /**
     * 校验非法的对象集合
     */
    private List<VerifyEntity<T>> illegalList = Lists.newArrayList();

    private KeyGetter keyGetter;

    public VerifyResultDetail(List<T> sourceList, List<VerifyResult> verifyResultList, KeyGetter keyGetter) {
        if (sourceList == null || verifyResultList == null || keyGetter == null) {
            throw new IllegalArgumentException("sourceList == null || verifyResultList == null || ");
        }

        this.sourceList = sourceList;
        this.verifyResultList = verifyResultList;
        this.keyGetter = keyGetter;
    }

    public VerifyResultDetail<T> aggregation() {
        if (sourceList.size() != verifyResultList.size()) {
            log.warn("被校验集合与校验结果集合长度不一致, sourceList.size[{}] verifyResultList.size[{}]", sourceList.size(), verifyResultList.size());
            return this;
        }

        int size = sourceList.size();
        VerifyResult verifyResult;
        for (int i = 0; i < size; i++) {
            verifyResult = verifyResultList.get(i);

            if (verifyResult == null) {
                continue;
            }

            (verifyResult.isIllegal() ? illegalList : legalList).add(new VerifyEntity(i, sourceList.get(i), verifyResultList.get(i)));
        }

        return this;
    }

    public List<T> legalEntityList() {
        if (CollectionUtils.isEmpty(legalList)) {
            return Lists.newArrayList();
        }
        return Lists.transform(legalList, new Function<VerifyEntity, T>() {
            @Override
            public T apply(VerifyEntity input) {
                return (T) input.getEntity();
            }
        });
    }

    public void addVerifyResultMapList(SubEntity subEntity, VerifyResultDetail verifyResultDetail, int verifyResultListIndex) {
        Map<SubEntity, VerifyResultDetail> tempMap;
        if (this.getVerifyResultMapList().size() >= verifyResultListIndex + 1) {
            tempMap = this.getVerifyResultMapList().get(verifyResultListIndex);
        } else {
            tempMap = Maps.newHashMap();
            this.getVerifyResultMapList().add(tempMap);
        }

        tempMap.put(subEntity, verifyResultDetail);

        if (this.getVerifyResultList().size() >= verifyResultListIndex + 1 && verifyResultDetail.hasIllegal()) {
            this.getVerifyResultList().get(verifyResultListIndex).setIllegal(true);
        }


    }

    public boolean hasIllegal() {
        return CollectionUtils.isNotEmpty(this.illegalList);
    }

    public Map<String, String> errorMessage() {
        if (!this.hasIllegal()) {
            return Maps.newLinkedHashMap();
        }

        Map<String, String> resultMap = Maps.newLinkedHashMap();
        Map<String, String> tempMap;
        Map<SubEntity, VerifyResultDetail> verifyResultDetailMap;
        for (VerifyEntity<T> verifyEntity : illegalList) {
            tempMap = Maps.newLinkedHashMap();
            tempMap.putAll(verifyEntity.getVerifyResult().getPropertyMessageMap());
            if (this.getVerifyResultMapList().size() >= verifyEntity.getIndex() + 1) {
                verifyResultDetailMap = this.getVerifyResultMapList().get(verifyEntity.getIndex());

                if (verifyResultDetailMap != null) {
                    for (SubEntity key : verifyResultDetailMap.keySet()) {
                        tempMap.put(key.getDesc(), verifyResultDetailMap.get(key).errorMessage().toString());
                    }
                }
            }

            resultMap.put(keyGetter.key(verifyEntity.getEntity()), tempMap.toString());
        }

        return resultMap;
    }

    @Data
    @AllArgsConstructor
    public static class VerifyEntity<T> {
        int index;
        T entity;
        VerifyResult verifyResult;
    }

    public interface KeyGetter<T> {
        String key(T keySource);
    }

    @AllArgsConstructor
    @Getter
    public enum SubEntity {
        SKU("sku"), PICTURE("图片"), ATTR("售卖属性"), SPU_EXTEND("SPU扩展属性"), SKU_EXTEND("SKU扩展属性"), LABEL("标签"), BRAND("品牌"),
        SPU_GRAPHICDESCRIPTION("图文详情"), SPU_VIDEO("商品视频"), SPU_INTELLIGENTPRODUCTTAG("spu智能分类"), SPU_MULTIPRODUCTTAG("多分类"), SKU_RELATEPRODUCTLIBREQUIRED("强制关联标品"),
        ALLOWCUSTOMPRODUCT("自建商品"), SKU_WEIHG("重量"), SKU_UPC("sku upc"), PACKAGE_SKU_REL("组包信息");

        private String desc;
    }
}

六、实际对外提供接口

(一)门店商品数据动态校验处理

package org.zyf.javabasic.dynamicbizvalidator.service;

import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSku;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSpu;
import org.zyf.javabasic.dynamicbizvalidator.content.PoiProductValidatorDataHolder;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductPackageVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductSkuVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.content.ProductSpuVerifyContent;
import org.zyf.javabasic.dynamicbizvalidator.dynamicdeal.ProductDynamicValidator;
import org.zyf.javabasic.dynamicbizvalidator.model.VerifyResult;
import org.zyf.javabasic.dynamicbizvalidator.model.VerifyResultDetail;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author yanfengzhang
 * @description 门店商品数据动态校验处理
 * @date 2023/3/9  23:37
 */
@Service
public class PoiProductDynamicValidatorService {

    @Resource
    private PoiProductValidatorDataHolderFactory poiProductValidatorDataHolderFactory;
    @Resource
    private ProductDynamicValidator productDynamicValidator;

    private final VerifyResultDetail.KeyGetter<ProductPoiSpu> spuKeyGetter = new VerifyResultDetail.KeyGetter<ProductPoiSpu>() {
        @Override
        public String key(ProductPoiSpu keySource) {
            return String.format("poiId[%d] spuId[%d] spuName[%s]", keySource.getPoiId(), keySource.getId(), keySource.getName());
        }
    };

    private final VerifyResultDetail.KeyGetter<ProductPoiSku> skuKeyGetter = new VerifyResultDetail.KeyGetter<ProductPoiSku>() {
        @Override
        public String key(ProductPoiSku keySource) {
            return String.format("poiId[%d] spuId[%d] skuId[%d] skuSpec[%s]", keySource.getPoiId(), keySource.getPoiSpuId(), keySource.getId(), keySource.getSpec());
        }
    };

    private final VerifyResultDetail.KeyGetter<ProductPoiPackageSkuRel> packageKeyGetter = new VerifyResultDetail.KeyGetter<ProductPoiPackageSkuRel>() {
        @Override
        public String key(ProductPoiPackageSkuRel keySource) {
            return String.format("packageSkuId[%d] skuId[%s]", keySource.getPoiPackageSkuId(), keySource.getPoiSkuId());
        }
    };

    /**
     * 门店商品spu动态校验处理
     */
    public VerifyResultDetail<ProductPoiSpu> spuVerify(long poiId, String currentBizKey, List<ProductPoiSpu> poiSpuList) {
        if (CollectionUtils.isEmpty(poiSpuList)) {
            return new VerifyResultDetail<>(poiSpuList, Lists.newArrayList(), spuKeyGetter).aggregation();
        }

        /*查询必要的数据*/
        PoiProductValidatorDataHolder poiProductValidatorDataHolder = poiProductValidatorDataHolderFactory.getInstanceBySpu(poiId, poiSpuList);
        /*spu的校验*/
        List<ProductSpuVerifyContent> productSpuVerifyContents = ProductSpuVerifyContent.toProductSpuVerifyContents(poiSpuList, poiProductValidatorDataHolder);
        List<VerifyResult> verifyResultList = productDynamicValidator.spuVerify(currentBizKey, productSpuVerifyContents);

        /*其他信息的校验*/
        VerifyResultDetail<ProductPoiSpu> verifyResultDetail = new VerifyResultDetail<>(poiSpuList, verifyResultList, spuKeyGetter);
        for (int i = 0; i < poiSpuList.size(); i++) {
            ProductPoiSpu poiSpu = poiSpuList.get(i);
            /*SKU*/
            if (CollectionUtils.isNotEmpty(poiSpu.getSkuList())) {
                VerifyResultDetail<ProductPoiSku> skuVerifyResultDetail = this.skuVerify(poiId, currentBizKey, poiSpu.getSkuList(), poiProductValidatorDataHolder, poiSpu.getIsPackage() == 1);
                verifyResultDetail.addVerifyResultMapList(VerifyResultDetail.SubEntity.SKU, skuVerifyResultDetail, i);
            }
        }
        return verifyResultDetail.aggregation();
    }

    /**
     * 门店商品sku动态校验处理
     */
    public VerifyResultDetail<ProductPoiSku> skuVerify(long poiId, String currentBizKey, List<ProductPoiSku> poiSkuList, PoiProductValidatorDataHolder poiProductValidatorDataHolder, boolean isPackage) {
        /*转成校验模型*/
        List<ProductSkuVerifyContent> productSkuVerifyContents = ProductSkuVerifyContent.toProductSkuVerifyContents(poiSkuList, poiProductValidatorDataHolder);
        /*sku的校验*/
        VerifyResultDetail<ProductPoiSku> poiSkuVerifyResultDetail = skuVerify(poiId, currentBizKey, poiSkuList, productSkuVerifyContents, isPackage);

        for (int i = 0; i < poiSkuList.size(); i++) {
            ProductPoiSku poiSku = poiSkuList.get(i);
            List<ProductPackageVerifyContent> productPackageVerifyContents = ProductPackageVerifyContent.toProductPackageVerifyContents(poiSku.getPoiPackageSkuRelList(), poiProductValidatorDataHolder);
            if (CollectionUtils.isEmpty(productPackageVerifyContents)) {
                continue;
            }

            VerifyResultDetail<ProductPoiPackageSkuRel> poiPackageSkuRelVerifyResultDetail = packageSkuRelVerify(poiId, currentBizKey, poiSku.getPoiPackageSkuRelList(), productPackageVerifyContents);
            poiSkuVerifyResultDetail.addVerifyResultMapList(VerifyResultDetail.SubEntity.PACKAGE_SKU_REL, poiPackageSkuRelVerifyResultDetail, i);
        }

        return poiSkuVerifyResultDetail.aggregation();
    }

    /**
     * 门店组包商品动态校验处理
     */
    public VerifyResultDetail<ProductPoiPackageSkuRel> packageSkuRelVerify(long poiId, String currentBizKey, List<ProductPoiPackageSkuRel> poiPackageSkuRelList, List<ProductPackageVerifyContent> productPackageVerifyContents) {
        if (CollectionUtils.isEmpty(productPackageVerifyContents)) {
            return new VerifyResultDetail<>(poiPackageSkuRelList, Lists.<VerifyResult>newArrayList(), packageKeyGetter);
        }

        List<VerifyResult> verifyResults = productDynamicValidator.packageVerify(currentBizKey, productPackageVerifyContents);
        return new VerifyResultDetail<>(poiPackageSkuRelList, verifyResults, packageKeyGetter).aggregation();
    }

    /**
     * sku校验
     */
    private VerifyResultDetail<ProductPoiSku> skuVerify(long poiId, String currentBizKey, List<ProductPoiSku> poiSkuList, List<ProductSkuVerifyContent> productSkuVerifyContents, boolean isPackage) {
        if (CollectionUtils.isEmpty(productSkuVerifyContents)) {
            return new VerifyResultDetail<>(poiSkuList, Lists.<VerifyResult>newArrayList(), skuKeyGetter);
        }

        List<VerifyResult> verifyResults = productDynamicValidator.skuVerify(currentBizKey, productSkuVerifyContents);
        VerifyResultDetail<ProductPoiSku> verifyResultDetail = new VerifyResultDetail<>(poiSkuList, verifyResults, skuKeyGetter);
        afterSkuVerify(verifyResultDetail, isPackage);
        return verifyResultDetail.aggregation();
    }

    public void afterSkuVerify(VerifyResultDetail<ProductPoiSku> skuVerifyResultDetail, boolean isPackage) {
        if (isPackage) {
            List<VerifyResult> verifyResultList = skuVerifyResultDetail.getVerifyResultList();
            for (VerifyResult verifyResult : verifyResultList) {
                verifyResult.getPropertyMessageMap().remove("upc");
                if (MapUtils.isEmpty(verifyResult.getPropertyMessageMap())) {
                    verifyResult.setIllegal(false);
                }
            }
        }
    }
}

(二)商品数据校验数据持有者查询整合模拟

package org.zyf.javabasic.dynamicbizvalidator.service;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSku;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSpu;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiTag;
import org.zyf.javabasic.dynamicbizvalidator.content.PoiProductValidatorDataHolder;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * @author yanfengzhang
 * @description 商品数据校验数据持有者查询整合
 * @date 2023/3/9  23:12
 */
@Service
public class PoiProductValidatorDataHolderFactory {

    @Resource
    private PoiProductVerifyQueryService poiProductVerifyQueryService;

    public PoiProductValidatorDataHolder getInstanceBySpu(long poiId, List<ProductPoiSpu> poiSpuList) {
        PoiProductValidatorDataHolder poiProductValidatorDataHolder = new PoiProductValidatorDataHolder();

        poiProductValidatorDataHolder.getSpuList().addAll(poiSpuList);
        /*构建spu依赖的数据*/
        buildSpuData(poiId, poiSpuList, poiProductValidatorDataHolder);

        /*构建sku依赖的数据*/
        List<ProductPoiSku> poiSkuList = poiSpuList.stream().map(ProductPoiSpu::getSkuList)
                .filter(CollectionUtils::isNotEmpty).flatMap(Collection::stream).collect(Collectors.toList());
        buildSkuData(poiId, poiSkuList, poiProductValidatorDataHolder);

        return poiProductValidatorDataHolder;
    }

    public void buildSpuData(long poiId, List<ProductPoiSpu> poiSpuList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        /*设置同名的spu->同名spu校验基础数据*/
        buildSpuDataForTagIdSpu(poiId, poiSpuList, poiProductValidatorDataHolder);
        /*本次待保存的sku列表*/
        buildSpuDataForSkuList(poiProductValidatorDataHolder, poiSpuList);
    }

    /**
     * 设置同名的spu
     */
    public void buildSpuDataForTagIdSpu(long poiId, List<ProductPoiSpu> poiSpuList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        Set<Long> tagIdSet = Sets.newHashSet();
        Set<String> spuNameSet = Sets.newHashSet();
        for (ProductPoiSpu poiSpu : poiSpuList) {
            if (CollectionUtils.isNotEmpty(poiSpu.getTagList())) {
                poiSpu.getTagList().forEach(tag -> tagIdSet.add(tag.getId()));
            }
            if (StringUtils.isNotBlank(poiSpu.getName())) {
                spuNameSet.add(poiSpu.getName());
            }
        }

        List<ProductPoiSpu> spuList = poiProductVerifyQueryService.getSameNamePoiSpuList(poiId, Lists.newArrayList(tagIdSet), Lists.newArrayList(spuNameSet));
        for (ProductPoiSpu spu : spuList) {
            if (CollectionUtils.isEmpty(spu.getTagList())) {
                continue;
            }

            for (ProductPoiTag poiTag : spu.getTagList()) {
                poiProductValidatorDataHolder.getTagIdToSpuMultimap().put(poiTag.getId(), spu);
            }
        }
    }

    /**
     * 设置sku
     */
    private void buildSpuDataForSkuList(PoiProductValidatorDataHolder poiProductValidatorDataHolder, List<ProductPoiSpu> poiSpuList) {
        for (ProductPoiSpu poiSpu : poiSpuList) {
            if (CollectionUtils.isEmpty(poiSpu.getSkuList())) {
                poiProductValidatorDataHolder.getSpuIdToSkuListMap().put(poiSpu.getId(), Collections.emptyList());
            }
            poiProductValidatorDataHolder.getSpuIdToSkuListMap().put(poiSpu.getId(), poiSpu.getSkuList());
        }
    }

    public void buildSkuData(long poiId, List<ProductPoiSku> poiSkuList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        Set<String> upcSet = Sets.newHashSet();
        Set<Long> idSet = Sets.newHashSet();
        Set<Long> packageIdSet = Sets.newHashSet();

        /*默认本次创建的商品中没有组包商品*/
        boolean hasPackage = false;
        for (ProductPoiSku poiSku : poiSkuList) {
            if (StringUtils.isNotEmpty(poiSku.getUpcCode())) {
                upcSet.add(poiSku.getUpcCode());
            }
            if (poiSku.getId() > 0) {
                idSet.add(poiSku.getId());
            }

            if (CollectionUtils.isNotEmpty(poiSku.getPoiPackageSkuRelList())) {
                hasPackage = true;
                for (ProductPoiPackageSkuRel poiPackageSkuRel : poiSku.getPoiPackageSkuRelList()) {
                    packageIdSet.add(poiPackageSkuRel.getPoiSkuId());
                }
            }
        }

        buildSkuDataForUpcList(poiId, Lists.newArrayList(upcSet), poiProductValidatorDataHolder);
        buildSkuDataForSkuIdList(poiId, Lists.newArrayList(idSet), poiProductValidatorDataHolder);
        if (hasPackage) {
            buildSkuDataForPackage(poiId, Lists.newArrayList(packageIdSet), poiProductValidatorDataHolder);
        }
    }

    public void buildSkuDataForUpcList(long poiId, List<String> upcList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductPoiSku> skuListByUpcs = poiProductVerifyQueryService.getSkuListByUpcList(poiId, upcList);
        if (CollectionUtils.isNotEmpty(skuListByUpcs)) {
            skuListByUpcs.forEach(sku -> poiProductValidatorDataHolder.getUpcToSkuListMap().put(sku.getUpcCode(), sku));
        }
    }

    public void buildSkuDataForSkuIdList(long poiId, List<Long> skuIdList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductPoiSku> skuListByIds = poiProductVerifyQueryService.getSkuListByIdList(poiId, skuIdList, false);
        if (CollectionUtils.isNotEmpty(skuListByIds)) {
            skuListByIds.forEach(sku -> poiProductValidatorDataHolder.getSkuIdToSkuMap().put(sku.getId(), sku));
        }
    }

    public void buildSkuDataForPackage(long poiId, List<Long> skuIdList, PoiProductValidatorDataHolder poiProductValidatorDataHolder) {
        List<ProductPoiPackageSkuRel> poiPackageSkuRelList = poiProductVerifyQueryService.getPackageSkuRelListBySkuIdList(poiId, skuIdList);
        if (CollectionUtils.isNotEmpty(poiPackageSkuRelList)) {
            List<Long> packageSkuIdList = poiPackageSkuRelList.stream().map(ProductPoiPackageSkuRel::getPoiPackageSkuId).distinct().collect(Collectors.toList());
            poiPackageSkuRelList = poiProductVerifyQueryService.getPackageSkuRelListByPackageSkuIdList(poiId, packageSkuIdList);

            Map<Long, List<ProductPoiPackageSkuRel>> packageSkuIdMap = poiPackageSkuRelList.stream()
                    .collect(Collectors.groupingBy(ProductPoiPackageSkuRel::getPoiPackageSkuId));

            for (Map.Entry<Long, List<ProductPoiPackageSkuRel>> entry : packageSkuIdMap.entrySet()) {
                String packageSkuIdStr = toPackageSkuIdStr(entry.getValue());
                poiProductValidatorDataHolder.getSkuIdListStrToPackageSkuMap().put(packageSkuIdStr, entry.getKey());
            }
        }

        List<ProductPoiPackageSkuRel> poiPackageSkuRelList2 = poiProductVerifyQueryService.getPackageSkuRelListByPackageSkuIdList(poiId, skuIdList);
        if (CollectionUtils.isNotEmpty(poiPackageSkuRelList2)) {
            List<Long> packageSkuIdList = poiPackageSkuRelList2.stream().map(ProductPoiPackageSkuRel::getPoiPackageSkuId).distinct().collect(Collectors.toList());
            List<ProductPoiSku> packageSkuList = poiProductVerifyQueryService.getSkuListByIdList(poiId, packageSkuIdList, false);
            if (CollectionUtils.isNotEmpty(packageSkuList)) {
                for (ProductPoiSku packageSku : packageSkuList) {
                    poiProductValidatorDataHolder.getPackageSkuIdToPackageSkuMap().put(packageSku.getId(), packageSku);
                }
            }
        }

        List<ProductPoiSku> skuListByIds = poiProductVerifyQueryService.getSkuListByIdList(poiId, skuIdList, true);
        if (CollectionUtils.isNotEmpty(skuListByIds)) {
            skuListByIds.forEach(sku -> poiProductValidatorDataHolder.getSkuIdToPackageRelSkuMap().put(sku.getId(), sku));
        }

    }

    public static String toPackageSkuIdStr(List<ProductPoiPackageSkuRel> poiPackageSkuRelList) {
        TreeMap<Long, Integer> skuIdToCount = new TreeMap<>();
        for (ProductPoiPackageSkuRel poiPackageSkuRel : poiPackageSkuRelList) {
            String pricingRule = poiPackageSkuRel.getPricingRule();
            if (StringUtils.isBlank(pricingRule)) {
                skuIdToCount.put(poiPackageSkuRel.getPoiSkuId(), 0);
                continue;
            }
            ProductPackageSkuRel packageSkuRel = JSON.parseObject(pricingRule, ProductPackageSkuRel.class);
            skuIdToCount.put(poiPackageSkuRel.getPoiSkuId(), packageSkuRel.getCount());
        }

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<Long, Integer> entry : skuIdToCount.entrySet()) {
            sb.append(entry.getKey()).append("[").append(entry.getValue()).append("]");
        }

        return sb.toString();
    }
}

(三)商品查询模接口

package org.zyf.javabasic.dynamicbizvalidator.service;

import com.google.common.collect.Lists;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSku;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSpu;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiTag;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 商品查询模拟
 * @date 2023/3/9  23:24
 */
@Service
public class PoiProductVerifyQueryService {

    /**
     * 模拟:通过门店和对应分类以及spu名称查询对应的商品spu数据
     * 店内分类id与商品名称集合的映射
     */
    public List<ProductPoiSpu> getSameNamePoiSpuList(long poiId, List<Long> tagIdList, List<String> spuNameList) {

        ProductPoiTag productPoiTag = ProductPoiTag.builder().poiId(poiId).id(11L).name("商家优选推荐").build();
        ProductPoiSpu productPoiSpu = ProductPoiSpu.builder().id(23L).poiId(poiId).name("张彦峰优选商品").tagList(Lists.newArrayList(productPoiTag)).build();
        return Lists.newArrayList(productPoiSpu);
    }

    /**
     * 模拟:通过门店和对应upc查询对应的商品sku数据
     */
    public List<ProductPoiSku> getSkuListByUpcList(long poiId, List<String> upcList) {
        ProductPoiSku productPoiSku1 = ProductPoiSku.builder().id(2L).name("张彦峰优选商品规格2").poiId(poiId).upcCode("2345676543212345432123").build();
        ProductPoiSku productPoiSku2 = ProductPoiSku.builder().id(1L).name("张彦峰优选商品规格1").poiId(poiId).upcCode("2345676535432455432123").build();
        return Lists.newArrayList(productPoiSku1, productPoiSku2);
    }

    /**
     * 模拟:通过门店和对应id查询对应的商品sku数据
     */
    public List<ProductPoiSku> getSkuListByIdList(long poiId, List<Long> idList, boolean needStock) {
        ProductPoiSku productPoiSku1 = ProductPoiSku.builder().id(2L).name("张彦峰优选商品规格2").poiId(poiId).upcCode("2345676543212345432123").build();
        ProductPoiSku productPoiSku2 = ProductPoiSku.builder().id(1L).name("张彦峰优选商品规格1").poiId(poiId).upcCode("2345676535432455432123").build();
        return Lists.newArrayList(productPoiSku1, productPoiSku2);
    }

    /**
     * 模拟:通过门店和对应id查询对应的组合商品数据
     */
    public List<ProductPoiPackageSkuRel> getPackageSkuRelListBySkuIdList(long poiId, List<Long> skuIdList) {
        ProductPoiPackageSkuRel productPoiPackageSkuRel1 = ProductPoiPackageSkuRel.builder().poiId(poiId).id(34L).poiSkuId(1L).poiPackageSkuId(1L).build();
        ProductPoiPackageSkuRel productPoiPackageSkuRel2 = ProductPoiPackageSkuRel.builder().poiId(poiId).id(36L).poiSkuId(2L).poiPackageSkuId(2L).build();
        return Lists.newArrayList(productPoiPackageSkuRel1, productPoiPackageSkuRel2);
    }

    public List<ProductPoiPackageSkuRel> getPackageSkuRelListByPackageSkuIdList(long poiId, List<Long> packageSkuIdList) {
        ProductPoiPackageSkuRel productPoiPackageSkuRel1 = ProductPoiPackageSkuRel.builder().poiId(poiId).id(34L).poiSkuId(1L).poiPackageSkuId(1L).build();
        ProductPoiPackageSkuRel productPoiPackageSkuRel2 = ProductPoiPackageSkuRel.builder().poiId(poiId).id(36L).poiSkuId(2L).poiPackageSkuId(2L).build();
        return Lists.newArrayList(productPoiPackageSkuRel1, productPoiPackageSkuRel2);
    }
}

七、动态校验测试数据

(一)数据准备

假设我们存在商超B端上单业务编码的业务标识为sdq1234rewsddsf,实际上这个应该是用户在配置侧填写完成实际触发到对应的应用方,假设业务唯一标识已生成,并动态配置相关数据如下展示(实际应该是页面配置入库后触达业务更新数据缓存),具体代码如下:

 @Before
    public void init() {
        /*假设闪购商超B端上单业务编码的业务标识为sdq1234rewsddsf,实际上这个应该是用户在配置侧填写完成实际触发到对应的应用方*/
        String bizInfo = "sdq1234rewsddsf";
        JSONObject configContent = new JSONObject();
        /*对应业务spu信息动态校验要求*/
        configContent.put("spu.name.notnull", true);
        configContent.put("spu.name.length.max", 10);
        configContent.put("spu.name.uniqueInTag", true);
        configContent.put("spu.skus.size.min", 1);
        configContent.put("spu.skus.size.max", 30);
        configContent.put("spu.skus.attrSku.size.min", 0);
        configContent.put("spu.skus.attrSku.size.max", 15);
        configContent.put("spu.spCategory.isNotNull", true);
        configContent.put("spu.piccontent.content.length.max", 12);
        configContent.put("spu.unit.length.max", 30);
        configContent.put("spu.description.length.max", 100);
        configContent.put("spu.thirdPartyPrimaryId.length.max", 100);
        configContent.put("spu.package.use.scene.notnull", true);
        configContent.put("spu.package.use.scene.length.max", 1);
        /*对应业务sku信息动态校验要求*/
        configContent.put("sku.spec.length.min", 1);
        configContent.put("sku.spec.length.max", 10);
        configContent.put("sku.price.top", 100);
        configContent.put("sku.stock.min", 0);
        configContent.put("sku.stock.max", 9999999);
        configContent.put("sku.boxNum.min", 1);
        configContent.put("sku.boxNum.max", 100);
        configContent.put("sku.boxPrice.min", 0);
        configContent.put("sku.boxPrice.max", 100);
        configContent.put("sku.upc_code.isNotNull", false);
        configContent.put("sku.thirdPartyPrimaryId.length.max", 100);
        configContent.put("sku.locatorCode.length.max", 10);
        configContent.put("sku.tagname.length.max", 20);
        configContent.put("sku.description.length.max", 300);
        configContent.put("sku.unit.length.max", 100);
        configContent.put("sku.minOrderCount.min", 1);
        configContent.put("sku.minOrderCount.max", 100);
        configContent.put("sku.weight.isNotNull", true);
        configContent.put("sku.shippingtimex.legal", true);
        configContent.put("sku.upc_code.uniqueInPoi", true);
        configContent.put("sku.price.range", 1000);
        configContent.put("sku.price.frequency", 100);
        configContent.put("sku.legalPicUrl", true);
        configContent.put("sku.package.skus.size.min", 2);
        configContent.put("sku.package.unique", true);
        configContent.put("sku.package.legalPackagePrice", true);
        configContent.put("sku.package.legalPackageStock", true);
        /*组包商品校验要求*/
        configContent.put("package.sku.count.min", 1);
        configContent.put("package.sku.count.max", 30);
        configContent.put("package.sku.discount.min", 0);
        configContent.put("package.sku.discount.max", 50);
        configContent.put("package.name.uniqueInTag", true);
        configContent.put("package.sku.notExistPackageSku", true);
        configContent.put("package.sku.pricingRule", true);

        DynamicValidatorEngine.init(bizInfo, configContent);
    }

(二)数据测试

package org.zyf.javabasic.dynamicbizvalidator;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.log4j.Log4j2;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.zyf.javabasic.ZYFApplication;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiPackageSkuRel;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSku;
import org.zyf.javabasic.dynamicbizvalidator.basedata.ProductPoiSpu;
import org.zyf.javabasic.dynamicbizvalidator.dynamicdeal.DynamicValidatorEngine;
import org.zyf.javabasic.dynamicbizvalidator.model.VerifyResultDetail;
import org.zyf.javabasic.dynamicbizvalidator.service.PoiProductDynamicValidatorService;

import java.util.List;

/**
 * @author yanfengzhang
 * @description
 * @date 2023/3/8  23:30
 */
@Log4j2
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BizDynamicValidatorTest {

    @Autowired
    private PoiProductDynamicValidatorService poiProductDynamicValidatorService;

    @Before
    public void init() {
        /*假设闪购商超B端上单业务编码的业务标识为sdq1234rewsddsf,实际上这个应该是用户在配置侧填写完成实际触发到对应的应用方*/
        String bizInfo = "sdq1234rewsddsf";
        JSONObject configContent = new JSONObject();
        /*对应业务spu信息动态校验要求*/
        configContent.put("spu.name.notnull", true);
        configContent.put("spu.name.length.max", 10);
        configContent.put("spu.name.uniqueInTag", true);
        configContent.put("spu.skus.size.min", 1);
        configContent.put("spu.skus.size.max", 30);
        configContent.put("spu.skus.attrSku.size.min", 0);
        configContent.put("spu.skus.attrSku.size.max", 15);
        configContent.put("spu.spCategory.isNotNull", true);
        configContent.put("spu.piccontent.content.length.max", 12);
        configContent.put("spu.unit.length.max", 30);
        configContent.put("spu.description.length.max", 100);
        configContent.put("spu.thirdPartyPrimaryId.length.max", 100);
        configContent.put("spu.package.use.scene.notnull", true);
        configContent.put("spu.package.use.scene.length.max", 1);
        /*对应业务sku信息动态校验要求*/
        configContent.put("sku.spec.length.min", 1);
        configContent.put("sku.spec.length.max", 10);
        configContent.put("sku.price.top", 100);
        configContent.put("sku.stock.min", 0);
        configContent.put("sku.stock.max", 9999999);
        configContent.put("sku.boxNum.min", 1);
        configContent.put("sku.boxNum.max", 100);
        configContent.put("sku.boxPrice.min", 0);
        configContent.put("sku.boxPrice.max", 100);
        configContent.put("sku.upc_code.isNotNull", false);
        configContent.put("sku.thirdPartyPrimaryId.length.max", 100);
        configContent.put("sku.locatorCode.length.max", 10);
        configContent.put("sku.tagname.length.max", 20);
        configContent.put("sku.description.length.max", 300);
        configContent.put("sku.unit.length.max", 100);
        configContent.put("sku.minOrderCount.min", 1);
        configContent.put("sku.minOrderCount.max", 100);
        configContent.put("sku.weight.isNotNull", true);
        configContent.put("sku.shippingtimex.legal", true);
        configContent.put("sku.upc_code.uniqueInPoi", true);
        configContent.put("sku.price.range", 1000);
        configContent.put("sku.price.frequency", 100);
        configContent.put("sku.legalPicUrl", true);
        configContent.put("sku.package.skus.size.min", 2);
        configContent.put("sku.package.unique", true);
        configContent.put("sku.package.legalPackagePrice", true);
        configContent.put("sku.package.legalPackageStock", true);
        /*组包商品校验要求*/
        configContent.put("package.sku.count.min", 1);
        configContent.put("package.sku.count.max", 30);
        configContent.put("package.sku.discount.min", 0);
        configContent.put("package.sku.discount.max", 50);
        configContent.put("package.name.uniqueInTag", true);
        configContent.put("package.sku.notExistPackageSku", true);
        configContent.put("package.sku.pricingRule", true);

        DynamicValidatorEngine.init(bizInfo, configContent);
    }

    /**
     * 商品spu
     */
    @Test
    public void spuVerify() {
        try {
            long poiId = 11L;
            String currentBizKey = "sdq1234rewsddsf";
            List<ProductPoiPackageSkuRel> poiPackageSkuRelList = Lists.newArrayList(ProductPoiPackageSkuRel.builder()
                            .poiId(poiId)
                            .poiPackageSkuId(11L)
                            .poiPackageSkuId(1L)
                            .poiPackageSpuId(23L)
                            .pricingRule("{\"count\":567,\"discount\":23.0,\"isMaster\":1,\"sequence\":34,\"skuPrice\":2345.0,\"spuId\":23}").build(),
                    ProductPoiPackageSkuRel.builder()
                            .poiId(poiId)
                            .poiPackageSkuId(12L)
                            .poiPackageSkuId(2L)
                            .poiPackageSpuId(23L)
                            .pricingRule("{\"count\":578,\"discount\":56.0,\"isMaster\":2,\"sequence\":38,\"skuPrice\":2565.0,\"spuId\":23}").build());

            List<ProductPoiSku> skuList = Lists.newArrayList(ProductPoiSku.builder()
                            .poiId(poiId)
                            .poiSpuId(23L)
                            .name("层层围珠玑,团团锦绣簇。")
                            .upcCode("rfgfrds87ytghjkjnbvfghjn")
                            .description("营销活动日志流量增长")
                            .shippingTime("[[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"]]")
                            .picture("https://zyfcodes.blog.csdn.vip.net/?type=blog")
                            .boxNum(5000)
                            .boxPrice(300)
                            .groupPrice(1000)
                            .limitStock(0)
                            .minOrderCount(0)
                            .oriPrice(7200)
                            .price(4500)
                            .sourceFoodCode("KJHBVFTYU89876TREDCVGHU")
                            .locatorCode("0OKJUYTFGBN")
                            .poiStandardId(45L)
                            .spec("QWERTYUIOP")
                            .weight(345)
                            .weightUnit("KG")
                            .poiPackageSkuRelList(poiPackageSkuRelList)
                            .build(),
                    ProductPoiSku.builder()
                            .poiId(poiId)
                            .poiSpuId(23L)
                            .name("桃花一簇开无主,可爱深红爱浅红?")
                            .upcCode("pokmnbvghjmnbv098u7ytghj")
                            .description("标签管理系统业务能力全景梳理")
                            .shippingTime("[[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"],[\"00:00-23:59\"]]")
                            .picture("https://zyfcodes.blog.csdn.net/article/details/105148032?spm=1001.2014.3001.5502")
                            .boxNum(5000)
                            .boxPrice(400)
                            .groupPrice(1200)
                            .limitStock(34)
                            .minOrderCount(0)
                            .oriPrice(7300)
                            .price(4700)
                            .sourceFoodCode("KJHBV654TREDCVGHU")
                            .locatorCode("0OKJUdfvcxBN")
                            .poiStandardId(45L)
                            .spec("QWERefrdIOP")
                            .weight(347)
                            .weightUnit("KG")
                            .poiPackageSkuRelList(poiPackageSkuRelList).build());

            ProductPoiSpu productPoiSpu = ProductPoiSpu.builder()
                    .id(23L)
                    .name("校验商品名称(要求最大长度是30000000)")
                    .description("金典相册收藏:不是相如怜赋客,争教容易见文君。洛阳亲友如相问,一片冰心在玉壶。越陌度阡,枉用相存。酒伴来相命,开尊共解酲。")
                    .categoryId(900003027L)
                    .unit("份")
                    .skuList(skuList)
                    .sourceFoodCode("43erijhgf")
                    .picContent("张彦峰相关相册分类").build();
            List<ProductPoiSpu> poiSpuList = Lists.newArrayList(productPoiSpu);
            log.info("poiSpuList={}", JSON.toJSONString(poiSpuList));
            VerifyResultDetail<ProductPoiSpu> getVerifyResultDetail = poiProductDynamicValidatorService.spuVerify(poiId, currentBizKey, poiSpuList);
            log.info("门店商品校验结果:{}", JSON.toJSONString(getVerifyResultDetail.aggregation()));
        } catch (Exception e) {
            log.error("门店商品校验结果失败,错误信息:", e);
        }

    }
}

(三)验证如下展示

商品spu基本信息验证

商品sku基本信息验证

组合商品基本信息验证

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2251812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯每日真题 - 第23天

题目&#xff1a;&#xff08;直线&#xff09; 题目描述&#xff08;12届 C&C B组C题&#xff09; 解题思路&#xff1a; 题目理解: 在平面直角坐标系中&#xff0c;从给定的点集中确定唯一的直线。 两点确定一条直线&#xff0c;判断两条直线是否相同&#xff0c;可通过…

设计模式---建造者模式

建造者模式 一种创建型设计模式&#xff0c;它允许你一步一步地构建复杂对象。通过使用建造者模式&#xff0c;你可以将对象的构建过程与其表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。说白点就是&#xff0c;解决了构造函数创建对象的问题。 适用于那种构造函…

【北京迅为】iTOP-4412全能版使用手册-第二十章 搭建和测试NFS服务器

iTOP-4412全能版采用四核Cortex-A9&#xff0c;主频为1.4GHz-1.6GHz&#xff0c;配备S5M8767 电源管理&#xff0c;集成USB HUB,选用高品质板对板连接器稳定可靠&#xff0c;大厂生产&#xff0c;做工精良。接口一应俱全&#xff0c;开发更简单,搭载全网通4G、支持WIFI、蓝牙、…

Python小黄人

文章目录 系列目录写在前面完整代码代码分析1. 初始化部分2. 身体部分3. 右眼睛部分4. 左眼睛部分5. 嘴巴部分6. 裤子部分7. 脚部部分8. 手部部分9. 头顶装饰部分10. 代码总结 写在后面 系列目录 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码…

操作系统 | 学习笔记 | 王道 | 2.2处理机调度

2.2 处理机调度 文章目录 2.2 处理机调度2.2.1 调度的概念2.2.2 调度的目标2.2.3 调度的实现2.2.4 典型的调度算法错题总结&#xff1a; 2.2.1 调度的概念 调度的基本概念 处理机调度是对处理机进行分配&#xff0c;即从就绪队列中按照一定的算法&#xff08;公平、高效的原则&…

算法与数据结构(1)

一&#xff1a;数据结构概论 数据结构分为初阶数据结构&#xff08;主要由C语言实现&#xff09;和高阶数据结构&#xff08;由C实现&#xff09; 初阶数据结构当中&#xff0c;我们会学到顺序表、链表、栈和队列、二叉树、常见排序算法等内容。 高阶数据结构当中&#xff0…

【Android】EventBus的使用及源码分析

文章目录 介绍优点基本用法线程模式POSTINGMAINMAIN_ORDEREDBACKGROUNDASYNC 黏性事件 源码注册getDefault()registerfindSubscriberMethods小结 postpostStickyunregister 介绍 优点 简化组件之间的通信 解耦事件发送者和接收者在 Activity、Fragment 和后台线程中表现良好避…

深度学习笔记——生成对抗网络GAN

本文详细介绍早期生成式AI的代表性模型&#xff1a;生成对抗网络GAN。 文章目录 一、基本结构生成器判别器 二、损失函数判别器生成器交替优化目标函数 三、GAN 的训练过程训练流程概述训练流程步骤1. 初始化参数和超参数2. 定义损失函数3. 训练过程的迭代判别器训练步骤生成器…

成都睿明智科技有限公司抖音电商服务的新引擎

在这个短视频风起云涌的时代&#xff0c;抖音不仅成为了人们休闲娱乐的首选&#xff0c;更是商家们竞相角逐的电商新蓝海。在这片充满机遇与挑战的海域中&#xff0c;成都睿明智科技有限公司如同一艘装备精良的航船&#xff0c;引领着众多企业向抖音电商的深水区进发。今天&…

51c视觉~YOLO~合集4

我自己的原文哦~ https://blog.51cto.com/whaosoft/12512597 1、Yolo8 1.1、检测PCB元件 技术世界正在以惊人的速度发展&#xff0c;而这种转变的核心是一个革命性的工具 — 计算机视觉。它最有趣的应用之一是电子印刷电路板 &#xff08;PCB&#xff09; 的检测和分析。本文…

Jenkins的使用

文章目录 一、Jenkins是什么\有什么用\与GitLab的对比二、Jenkins的安装与配置Jenkins的安装方式在Linux上安装Jenkins&#xff1a;在Windows上安装Jenkins&#xff1a;配置Jenkins&#xff1a; &#xff08;可选&#xff09;配置启动用户为root&#xff08;一定要是root吗??…

图论入门教程:GTM173 Graph Theory

这是本图论的入门教材&#xff0c;Graph Theory Fifth Edition&#xff0c;隶属于著名的GTM系列&#xff0c;作者是Reinhard Diestel。这是本对新人友好的教材&#xff0c;之前本科上离散数学的课时&#xff0c;因为涉及到图论&#xff0c;而学校的课堂又太水让我心生不满&…

QT5 Creator (Mingw编译器) 调用VS2019 (阿里云 oss C++库) 报错的解决方法

方法就是不要用VS2019编译&#xff0c;要用MINgw32编译。 编译命令如下&#xff1a; cmake -G "MinGW Makefiles" ^-DCMAKE_MAKE_PROGRAMD:\qt\Tools\mingw810_32\bin\mingw32-make.exe ^-DCMAKE_C_COMPILERD:\qt\Tools\mingw810_32\bin\gcc.exe ^-DCMAKE_CXX_COMP…

反向传播、梯度下降与学习率:深度学习中的优化艺术

目录 反向传播&#xff1a;神经网络的学习机制 梯度下降&#xff1a;优化算法的基石 学习率&#xff1a;平衡速度与稳定性的关键 学习率的调整策略 固定学习率 学习率衰减 自适应学习率 梯度消失与梯度爆炸 结语 在深度学习的领域中&#xff0c;构建一个有效的神经网络…

论文笔记(五十九)A survey of robot manipulation in contact

A survey of robot manipulation in contact 文章概括摘要1. 引言解释柔顺性控制的概念&#xff1a;应用实例&#xff1a; 2. 需要接触操控的任务2.1 环境塑造2.2 工件对齐2.3 关节运动2.4 双臂接触操控 3. 接触操控中的控制3.1 力控制3.2 阻抗控制3.3 顺应控制 4. 接触操控中的…

881.救生艇

目录 题目过程 题目 给定数组 people 。people[i]表示第 i 个人的体重 &#xff0c;船的数量不限&#xff0c;每艘船可以承载的最大重量为 limit。 每艘船最多可同时载两人&#xff0c;但条件是这些人的重量之和最多为 limit。 返回 承载所有人所需的最小船数 。 过程 cla…

【汇编】逻辑指令

文章目录 一、逻辑运算指令&#xff08;一&#xff09;各逻辑运算指令格式及操作&#xff08;1&#xff09;逻辑非指令 NOT&#xff08;2&#xff09;逻辑与指令 AND&#xff08;3&#xff09;逻辑或指令 OR&#xff08;4&#xff09;异或指令 XOR&#xff08;5&#xff09;测试…

网页开发的http基础知识

请求方式-GET&#xff1a;请求参数在请求行中&#xff0c;没有请求体&#xff0c;如&#xff1a;/brand/findAll?nameoPPo&status1。GET请求大小在浏览器中是有限制的请求方式-POST&#xff1a;请求参数在请求体中&#xff0c;POST请求大小是没有限制的 HTTP请求&#xf…

如何做好一份技术文档

如何做好一份技术文档 以下是本人的一些微不足道的经验&#xff0c;希望可以与大家互相交流学习 方向一&#xff1a;技术文档的规划布局 确定整体架构 创建一份优秀技术文档的第一步是规划其整体架构。一个好的架构应能引导读者理解文档的内容&#xff0c;同时提供一个逻辑清…

Springboot——SseEmitter流式输出

文章目录 前言SseEmitter 简介测试demo注意点异常一 ResponseBodyEmitter is already set complete 前言 最近做AI类的开发&#xff0c;看到各大AI模型的输出方式都是采取的一种EventStream的方式实现。 不是通常的等接口处理完成后&#xff0c;一次性返回。 而是片段式的处理…