[Lilishop Mall] No4-7. Code development of business logic, involving: commodity module

  Only the backend is involved, see the top column for all directories, codes, documents, and interface paths are: 

[Lilishop Mall] Record the study notes of the B2B2C mall system~


The whole article will combine the business introduction to focus on the design logic, which includes the interface class and business class, and the specific source code analysis, the source code is not complicated to read~

Caution: Some comments in the source code are wrong, some comments have completely opposite meanings, and some comments are not correct. I updated them during the reading process and added new comments where I did not know. So be careful when reading the source code!

Table of contents

A1. Commodity module

B0. Preface

C1. Commodity association table and sub-table analysis

C2. Operational analysis of commodity association tables and sub-tables (skipable)

B1. New product

C1. Commodity DTO class analysis GoodsOperationDTO

C2. Business logic

        D1. Core business logic

        D2. Actual operation of business logic

C3. Code logic

        D1. Constructor new a commodity PO object

        D2. According to the business, check and set the product information

        D3. Get the thumbnail of the set [commodity thumbnails are unified and explained here]

        D4.set commodity parameters

        D5. Constructor new a commodity skuPO object

        D6. Sales model rendering

        D7. Modify the commodity inventory to be the sum of the inventory of the commodity sku

        D8. Send the rocketMq message that generates the es commodity sku index 

        D9. Construct the es commodity sku index EsGoodsIndex list information

        D10. Word segmentation storage

        D11.save es commodity sku index

C3. Summary

A2. Third-party tool records (can be skipped)

B1.hutool

Use of C1.JSONUtil tool


The commodity module is the core of the mall system. It is not a simple addition, deletion, modification and query. After all, it will involve many business tables, so it looks simple but the business is still relatively complicated. Each interface (which can be understood as a business operation) may involve Many business operations.

The data structure design and interface have been analyzed in the No3 detailed design before, and then the interface will be analyzed first, and the data structure must be combined~

We only analyze key interfaces, and record similar ones together~

A1. Commodity module

B0. Preface

Before starting, first record the information of the product module.

C1. Commodity association table and sub-table analysis

First of all, the product table will be associated with seven tables: product classification, product brand, product specification, product parameter, product unit, shipping cost template, and store classification, but only product classification, product brand, shipping cost template, and store classification are related to the product table. of! The remaining product specifications, product parameters, and product units are weakly related, and are not related to the product table. They are only optional, and the data is directly saved in the product table.

Commodity category: must be selected to the last level, it is multiple choice, so you can store the commodity category id together and separate them with commas;

Commodity brand: It is a single choice, and it can be stored directly;

Shipping cost template: it is single-selected and can be stored directly;

Store category: it is multi-choice, so store category ids can be stored together and separated by commas;

Commodity specification: The specification corresponds to the commodity sku, and is stored in the commodity sku table, and the saved data is not the identification;

Commodity parameters: Since the parameters are customized by the M terminal and are multi-item, it is most appropriate to store them in the json type

Commodity unit: the data content stored directly will not be associated;

Secondly, after the product table is created, sub-tables will be generated: product sku table, product atlas table, wholesale rule table,

Part of the data in the product sku table is the same as the information in the product table, and part of it comes from the specification table.

C2. Operational analysis of commodity association tables and sub-tables (skipable)

Let me briefly explain here, because the seven association tables of the product may affect each other and the product table, for example, the product brand and product category, the product category will be associated with the product brand, if the associated product category is disabled The brand of the product will be reminded at this time: the category has been bound to the brand, please unlink it first: [\"mobile phone\",\"earphone/headset\"].

So once it comes to the relationship, you must consider various data operations in comparison with the business design. If the logic is not given in the design, you must ask clearly! ! ! Very important! ! !

I won’t describe it in detail here. When analyzing the above seven tables later, I will analyze it in detail~~~~

B1. New product

C1. Commodity DTO class analysis GoodsOperationDTO

This class is the interface input parameter type. Because the product information is more complicated, a DTO class needs to be added. This DTO class contains the interface input parameter type analyzed in the previous article~~~

Focus on the reference type,

Because commodity parameter attributes and wholesale attributes are complex and numerous, and there are field attribute rules, a class can also be abstracted in time, so the custom reference object is directly used to receive; among them are the parameter group class GoodsParamsDTO and the commodity parameters in the group Class GoodsParamsItemDTO, WholesaleDTO;

The commodity specification attributes are also complex and numerous, but there are no field attribute rules, and a specific field class cannot be abstracted, so the map collection is used to receive~~~

Below are some fields~~~

public class GoodsOperationDTO implements Serializable {

    //与其他表无关联的,基本数据类型的业务基本信息;
    @ApiModelProperty(hidden = true)
    private String goodsId;

    @ApiModelProperty(value = "商品名称", required = true)
    @NotEmpty(message = "商品名称不能为空")
    @Length(max = 50, message = "商品名称不能超过50个字符")
    private String goodsName;
。。。
    //与其他表关联的业务基本信息,可理解为外键;
    @ApiModelProperty(value = "商品分类path,逗号隔开")
    private String categoryPath;

    @ApiModelProperty(value = "店铺分类id,逗号隔开", required = true)
    @Size(max = 200, message = "选择了太多店铺分类")
    private String storeCategoryPath;
。。。
    //引用类型的业务基本信息;
    @ApiModelProperty(value = "商品参数")
    private List<GoodsParamsDTO> goodsParamsDTOList;

    @ApiModelProperty(value = "sku列表,因为无法匹配pojo类,所以是用map接收")
    @Valid
    private List<Map<String, Object>> skuList;

。。。
    //业务校验/判断的字段;
    @ApiModelProperty(value = "是否有规格", hidden = true)
    private String haveSpec;

    @ApiModelProperty(value = "是否重新生成sku数据")
    private Boolean regeneratorSkuFlag = true;
。。。

}

C2. Business logic

The core business logic refers to the main business operations of the interface, not combined with other reuse situations or business situations! ! !

The actual operation business logic is an operation that combines reuse code and other business conditions , so the actual operation will definitely have some judgment or other code logic.

So the core business logic is mainly to explain what the interface does specifically, which is for front-end understanding, design understanding, and back-end understanding, while the actual operation business logic is to explain how the interface is implemented with business logic, only for back-end understanding .

D1. Core business logic

  1. Generate the basic information of the product and product skulist according to the product DTO, save the product and skulist, and then generate and save the corresponding sub-table information;
  2. After the product is successfully added, if the product has passed the review and is on the shelf, you need to generate the es product sku index list based on the product information, and save the es product sku index list after word segmentation and storage.

D2. Actual operation of business logic

Next, we need to further implement the core business logic!

When introducing business logic, some other code structures will be involved. If there is any need to explain, it will be marked with green shading , and then it will be introduced in detail in the following code logic.

GoodsStoreController#save:

  1. Get the input parameter DTO, call the service method to add products;
  2. Return ResultUtil.success(); if there is an exception, enter the exception interception and return the exception;

GoodsServiceImpl#addGoods:

  1. The constructor creates a product PO object , and sets the basic information in the DTO to the product PO, which can verify whether the DTO information is valid;
  2. According to the business, check and set product information : 1. Determine whether the product type is virtual or physical, and then configure the delivery template; 2. Determine whether the product id exists, whether it is added or modified; 3. Determine whether the product needs to be reviewed; 4. .Judge whether the current user is a store, and set the store information;
  3. Set the first picture in the product album list as the default picture of the product, and get the set thumbnail, etc.;
  4. Set commodity parameters , the parameters are converted to JSON type and saved;
  5. save product information; [The basic information of the product has been set up to this step];
  6. Determine whether there is GoodsGalleryList product catalog information in the product DTO, save product catalog information; [sub-table association]
  7. Determine whether there is skuList product specification information in the product DTO, use the constructor new a product skuPO object in the for loop , and set the basic information of the product to the product skuPO, and then set the product specification information to the product skuPO;
  8. Render according to the sales mode . If it is a wholesale mode, you need to render sku information and save wholesale information; [sub-table association]
  9. The first one in the set album list is the default picture of the product album, and get the set thumbnail, etc.;
  10. Loop the product sku, set the first set in the image list in the specification json as the default image of the product sku, and get the set thumbnail, etc.;
  11. Batch save commodity sku information; [sub-table association]
  12. Modify the commodity inventory to be the sum of the inventory of the commodity sku ;
  13. If the product has been approved and is on the shelf, send the rocketMq message to generate the es product sku index , and only need to pass the product id.
  14. When MQ is executed, the product information and product skulist information are obtained through the product ID, and then the es product sku index EsGoodsIndex list information is constructed based on these two information , and the word segmentation is stored according to the product parameters and product names in the list , and the es product is saved sku index ;

Below is a screenshot of the service’s method of adding products. For the specific code logic, see the code logic analysis below~~ 

C3. Code logic

D1. Constructor new a commodity PO object

First of all, we know that the product PO table contains the basic information of the product, so it can be assigned directly through the constructor of the PO class. Because the product DTO table is relatively complicated, it needs to be verified before using the DTO object to construct the PO object, and other businesses will also Reuse to DTO objects to construct PO objects, so manually add this type of construction method to the PO class~

@EqualsAndHashCode(callSuper = true)
@Data
@TableName("li_goods")
@ApiModel(value = "商品")
public class Goods extends BaseEntity {
。。。
    public Goods(GoodsOperationDTO goodsOperationDTO) {
        //基本信息赋值
        this.goodsName = goodsOperationDTO.getGoodsName();
        this.categoryPath = goodsOperationDTO.getCategoryPath();
        this.storeCategoryPath = goodsOperationDTO.getStoreCategoryPath();
        this.brandId = goodsOperationDTO.getBrandId();
        this.templateId = goodsOperationDTO.getTemplateId();
        this.recommend = goodsOperationDTO.getRecommend();
        this.sellingPoint = goodsOperationDTO.getSellingPoint();
        this.salesModel = goodsOperationDTO.getSalesModel();
        this.goodsUnit = goodsOperationDTO.getGoodsUnit();
        this.intro = goodsOperationDTO.getIntro();
        this.mobileIntro = goodsOperationDTO.getMobileIntro();
        this.goodsVideo = goodsOperationDTO.getGoodsVideo();
        this.price = goodsOperationDTO.getPrice();
        if (goodsOperationDTO.getGoodsParamsDTOList() != null && goodsOperationDTO.getGoodsParamsDTOList().isEmpty()) {
            this.params = JSONUtil.toJsonStr(goodsOperationDTO.getGoodsParamsDTOList());
        }
        //判断是否立即上架
        this.marketEnable = Boolean.TRUE.equals(goodsOperationDTO.getRelease()) ? GoodsStatusEnum.UPPER.name() : GoodsStatusEnum.DOWN.name();
        this.goodsType = goodsOperationDTO.getGoodsType();
        //商品评分,初始100
        this.grade = 100D;

        //循环sku,判定sku是否有效,根据销售模式、商品类型
        /*
            sn 、quantity:是任何销售模式下、任何商品类型下都有的
            price、cost:是零售销售模式下、任何商品类型下有的
            weight:是任何销售模式下、商品实物类型下有的
         */
        for (Map<String, Object> sku : goodsOperationDTO.getSkuList()) {
            //判定参数不能为空
            if (!sku.containsKey("sn") || sku.get("sn") == null) {
                throw new ServiceException(ResultCode.GOODS_SKU_SN_ERROR);
            }
            if (!sku.containsKey("quantity") || StringUtil.isEmpty(sku.get("quantity").toString()) || Convert.toInt(sku.get("quantity").toString()) < 0) {
                throw new ServiceException(ResultCode.GOODS_SKU_QUANTITY_ERROR);
            }
            //判断参数是否有效,并且是非批发销售模式下的
            if ((!sku.containsKey("price") || StringUtil.isEmpty(sku.get("price").toString()) || Convert.toDouble(sku.get("price")) <= 0)
                    //非批发销售模式。添加此判断是因为成本和价格仅针对零售销售模式而言,但是前端有可能会先填写过零售模式的规格后又修改为批发模式,就会导致price参数不对,如果是批发销售模式此参数就无所谓了
                    && !goodsOperationDTO.getSalesModel().equals(GoodsSalesModeEnum.WHOLESALE.name())) {
                throw new ServiceException(ResultCode.GOODS_SKU_PRICE_ERROR);
            }
            if ((!sku.containsKey("cost") || StringUtil.isEmpty(sku.get("cost").toString()) || Convert.toDouble(sku.get("cost")) <= 0)
                    //非批发销售模式
                    && !goodsOperationDTO.getSalesModel().equals(GoodsSalesModeEnum.WHOLESALE.name())) {
                throw new ServiceException(ResultCode.GOODS_SKU_COST_ERROR);
            }
            //虚拟商品没有重量字段
            if (this.goodsType.equals(GoodsTypeEnum.PHYSICAL_GOODS.name()) &&
                    (!sku.containsKey("weight") || sku.containsKey("weight") && (StringUtil.isEmpty(sku.get("weight").toString()) || Convert.toDouble(sku.get("weight").toString()) < 0))) {
                throw new ServiceException(ResultCode.GOODS_SKU_WEIGHT_ERROR);
            }
            sku.values().forEach(i -> {
                if (CharSequenceUtil.isBlank(i.toString())) {
                    throw new ServiceException(ResultCode.MUST_HAVE_GOODS_SKU_VALUE);
                }
            });
        }
    }
。。。
}

D2. According to the business, check and set the product information

Only relying on the new PO object of the constructor is not the most complete, and there may be special services that need to be modified again, so add a special precaution to deal with special services.

For example, the following business needs to be added here:

//cn.lili.modules.goods.serviceimpl.GoodsServiceImpl
    /**
     * 根据业务,检查商品信息
     * 如果商品是虚拟商品则无需配置配送模板
     * 如果商品是实物商品需要配置配送模板
     * 判断商品id是否存在。修改商品时会复用此方法
     * 判断商品是否需要审核。系统配置里面设置的
     * 判断当前用户是否为店铺,并设置店铺信息
     *
     * @param goods 商品
     */
    private void checkGoods(Goods goods) {
        //判断商品类型,是虚拟的还是实物的
        switch (goods.getGoodsType()) {
            case "PHYSICAL_GOODS":
                if ("0".equals(goods.getTemplateId())) {
                    throw new ServiceException(ResultCode.PHYSICAL_GOODS_NEED_TEMP);
                }
                break;
            case "VIRTUAL_GOODS":
                if (!"0".equals(goods.getTemplateId())) {
                    goods.setTemplateId("0");
                }
                break;
            default:
                throw new ServiceException(ResultCode.GOODS_TYPE_ERROR);
        }
        //检查商品是否存在--修改商品时使用
        if (goods.getId() != null) {
            this.checkExist(goods.getId());
        } else {
            //评论次数
            goods.setCommentNum(0);
            //购买次数
            goods.setBuyCount(0);
            //购买次数
            goods.setQuantity(0);
            //商品评分
            goods.setGrade(100.0);
        }

        //获取商品系统配置决定是否审核
        Setting setting = settingService.get(SettingEnum.GOODS_SETTING.name());
        GoodsSetting goodsSetting = JSONUtil.toBean(setting.getSettingValue(), GoodsSetting.class);
        //set审核状态
        goods.setAuthFlag(Boolean.TRUE.equals(goodsSetting.getGoodsCheck()) ? GoodsAuthEnum.TOBEAUDITED.name() : GoodsAuthEnum.PASS.name());
        //判断当前用户是否为店铺
        if (Objects.requireNonNull(UserContext.getCurrentUser()).getRole().equals(UserEnums.STORE)) {
            StoreVO storeDetail = this.storeService.getStoreDetail();
            if (storeDetail.getSelfOperated() != null) {
                goods.setSelfOperated(storeDetail.getSelfOperated());
            }
            goods.setStoreId(storeDetail.getId());
            goods.setStoreName(storeDetail.getStoreName());
            goods.setSelfOperated(storeDetail.getSelfOperated());
        } else {
            throw new ServiceException(ResultCode.STORE_NOT_LOGIN_ERROR);
        }
    }

D3. Get the thumbnail of the set [commodity thumbnails are unified and explained here]

Each product and product sku has its own separate default image, as well as the thumbnail and small image of the default image.

There is a difference between this default image and the album list, and generally the first image in the album list will be used as the default image.

  • The default picture of the product, which can be used for the picture displayed in the list of the S-end product list;
  • The default image of the product sku, which can be used for the image displayed in the list when searching for products on the B-side;
  • I don't know where it is used for the product album, but the product sku, that is, the picture of the product specification, is by default the [when adding/modifying products on the S side] of the product album taken, but the sku album can be modified;
  • The product sku album, which can be used for the product picture displayed on the left when the product details are opened on the B-side;

When users add/modify products, they will upload pictures, and then get the picture storage url. [For the upload method, see cn.lili.controller.common.UploadController#upload, which is uploaded to Alibaba Cloud’s OSS]

Then when adding a product, it will get its thumbnail and other information according to the URL of the original image above. Since it can be reused, it is abstracted into a method.

 The same is true for the default image of the product sku, so I won’t repeat it here

D4.set commodity parameters

This is just to record, the product parameters are stored as JSON, and will be converted into the type of product parameters in the subsequent functions such as obtaining product information, without complicated business. The key point is that the front-end value passing format must be consistent with the corresponding pojo object type at the back-end~~ Otherwise, the json format conversion will fail!

D5. Constructor new a commodity skuPO object

Products will have product skus with different specifications. For example, XX mobile phone has four product skus: yellow memory 60G, white memory 60G, yellow memory 120G, and white memory 120G. Just like when we want to buy a mobile phone when we visit Taobao, we must select the specification and model to determine the SKU of the product before we can place an order.

Therefore, the product sku is also relatively complicated, and the sku specification information is added based on part of the product information, which is equivalent to a sub-table of the product. Since many business functions, such as the function of adding seckill products, are operated according to the product sku, the shop system directly stores part of the product information in the sku table, so that when querying, it is enough to directly query the sku table. If the product information is modified , will also modify the corresponding information in the product sku, and then add or update~

It's simple to say, but the details are a bit complicated.

First of all, since there are multiple skus, a for loop is required to create GoodsSku and set its information. The specification information in each GoodsSku is divided into two types, one is original and fixed ("sn", "cost", " price", "quantity", "weight"), one is user-defined ("color", "memory", etc.), for fixed, we can use fixed fields, for custom, we can only use json The format is stored and easy to access.

Since the construction of skulist will be reused in many places, it is more convenient to directly abstract it into Builder~

 

 D6. Sales model rendering

 First of all, we need to know why the sales mode rendering is placed here?

There are two sales models: retail and wholesale. Retail is very simple, just add normal product specifications, while wholesale will be a special product sku.

First look at the page design:

 

 So we can know that under the wholesale mode, the wholesale specifications are determined according to the wholesale mode, and the prices of all commodity sku are the same~~~

Note: In the wholesale mode, you need to fill in the individual weight of the product. This weight is the weight of all product skus. The front end will set the sku weight as a unified weight~

After understanding the relationship, we can directly operate in wholesale mode when editing sku information, which can be understood as the rendering of sku data.

The shop system provides a separate sales mode rendering abstract class, and then implements a wholesale mode rendering class, through which the commodity sku is associated with the wholesale business class.

 

D7. Modify the commodity inventory to be the sum of the inventory of the commodity sku

In fact, there is no logic in this, but I am wondering why the sum of the inventory is placed in the newly added sku list and the database modification is called? 

Because there is no way to get the total inventory directly when saving the product before, and the sku storage after getting the sku list in this GoodsSkuServiceImpl#add method requires the product ID, so either get the product inventory and save it in a loop before saving the product, and perform pre-processing Processing, or post-processing after getting the inventory through skulist here.

D8. Send the rocketMq message that generates the es commodity sku index 

For the S-side of the store, the main thing is to add successful products and product sku information. Since the member B needs to browse and search for products, and the amount of data is large, we have introduced the es search engine. Then when the product and other information is stored in mysql, it is necessary to judge whether to add it to the es search engine according to the business, that is, to generate the es product sku index in es! ! !

Since the business of generating indexes by es is mainly aimed at the member B side, it does not affect the business of adding products on the S side of the store, so this operation can be added to mp, which is equivalent to an asynchronous operation~~~

We directly use the product id as the message to be transmitted. After mq monitors it, we first get the product information and product sku information through the product id, and then perform other operations. The product information will not be directly transmitted here. First, the amount of data is large. Second, The real-time data of the product cannot be guaranteed.

 ​​​

D9. Construct the es commodity sku index EsGoodsIndex list information

This step is troublesome and not complicated. We first get the product information and product sku list information through the product id, and then set the value of each es product sku object.

It should be noted here that the es product sku object field must conform to the front-end display. And some status values ​​must not be missing! ! !

D10. Word segmentation storage

Record it here, and then describe the word segmentation in detail when analyzing the B-side to obtain products.

D11.save es commodity sku index

Record it here, since it is saved in ordinary es, so just use the ElasticsearchRepository interface directly!

 


C3. Summary

In the newly added product interface logic analyzed above, the key logic is described, and the status judgment involved (such as the setting of the product review status) will not be described in detail, and it must follow the design.

Another thing is that the logic of adding a product interface and editing a product interface is similar, but there are some differences. So there will be some reuse methods~~~, we will focus on introducing the reuse methods in the editing logic~~~

A2. Third-party tool records (can be skipped)

B1.hutool

Use of C1.JSONUtil tool

  1. String JSONUtil#toJsonStr(java.lang.Object) Convert object to json
  2. T JSONUtil#toBean(java.lang.String, java.lang.Class<T>) convert json to object
  3. List<T> JSONUtil#toList(java.lang.String, java.lang.Class<T>) Convert json to object list

Guess you like

Origin blog.csdn.net/vaevaevae233/article/details/128628548