电商项目高级篇-03 商品上架

1、商品上架

上架的商品才可以在网站展示。
上架的商品需要可以被检索。

1.1、设计:宽表设计

优点:方便检索
缺点:数据冗余

商品数据模型设计:

PUT product
{
  "mappings": {
    "properties": {
      "skuId": {
        "type": "long"
      },
      "spuId": {
        "type": "keyword"
      },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "saleCount": {
        "type": "long"
      },
      "hasStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "brandId": {
        "type": "long"
      },
      "catalogId": {
        "type": "long"
      },
      "brandName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "brandImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "catalogName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

在这里插入图片描述
index:false 不允许检索
doc_valuses:false 不允许聚合

在这里插入图片描述
库存字段设计:true or false
无需存储具体库存。便于维护。只用在库存没有或者有了的情况下维护两次

###先不执行这个。后面上架时再执行这个

PUT 请求

1.2、es数组会扁平化处理

PUT my_index/_doc/1
{
  "group": "fans",
  "user": [
    {
      "first": "John",
      "last": "smith"
    },
    {
      "first": "Alice",
      "last": "White"
    }
  ]
}
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "user.first": "Alice"
          }
        },
        {
          "match": {
            "user.last": "Smith"
          }
        }
      ]
    }
  }
}

先插入一个索引。再根据条件搜索。发现不在一个对象里也能搜索到
在这里插入图片描述

##解决这种问题可以使用嵌入式属性
1、先查询映射关系

GET my_index/_mapping

user不是嵌入式类型

2、设置嵌入式

DELETE my_index
PUT my_index
{
  "mappings": {
    "properties": {
      "user":{
        "type": "nested"
      }
    }
  }
}

3、再执行

PUT my_index/_doc/1
{
  "group": "fans",
  "user": [
    {
      "first": "John",
      "last": "smith"
    },
    {
      "first": "Alice",
      "last": "White"
    }
  ]
}

4、再查询

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "user.first": "Alice"
          }
        },
        {
          "match": {
            "user.last": "Smith"
          }
        }
      ]
    }
  }
}

在这里插入图片描述
就查不出数据了。这是正确的

1.3、服务编写

1.3.1、商品上架服务

在这里插入图片描述
SkuEsModel

package com.ljs.gulimall.common.es;

import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

@Data
public class SkuEsModel {
    /**
     * skuId
     */
    private Long skuId;

    /**
     * skuTitle
     */
    private String skuTitle;

    /**
     * skuPrice
     */
    private BigDecimal skuPrice;

    /**
     * skuImg
     */
    private String skuImg;

    private Long saleCount;

    private Boolean hasStock;

    private Long hotScore;

    private Long brandId;

    private Long catalogId;

    private String brandName;

    private String brandImg;

    private String catalogName;

    private List<Attrs> attrs;

    @Data
    public static class Attrs {
        private Long attrId;

        private String attrName;

        private String attrValue;
    }


}


HasStockTo

package com.ljs.gulimall.common.to;

import lombok.Data;

@Data
public class HasStockTo {
    private Long skuId;

    private Boolean hasStock;
}

R

/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有,侵权必究!
 */

package com.ljs.gulimall.common.utils;

import org.apache.http.HttpStatus;

import java.util.HashMap;
import java.util.Map;

/**
 * 返回数据
 *
 * @author Mark sunlightcs@gmail.com
 */
public class R<T> extends HashMap<String, Object> {
	private static final long serialVersionUID = 1L;

	public T data;

	public static long getSerialVersionUID() {
		return serialVersionUID;
	}

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}

	public R() {
		put("code", 0);
		put("msg", "success");
	}
	
	public static R error() {
		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
	}
	
	public static R error(String msg) {
		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
	}
	
	public static R error(int code, String msg) {
		R r = new R();
		r.put("code", code);
		r.put("msg", msg);
		return r;
	}

	public static R ok(String msg) {
		R r = new R();
		r.put("msg", msg);
		return r;
	}
	
	public static R ok(Map<String, Object> map) {
		R r = new R();
		r.putAll(map);
		return r;
	}
	
	public static R ok() {
		return new R();
	}

	public R put(String key, Object value) {
		super.put(key, value);
		return this;
	}

	/**
	 * getCode
	 *
	 * @return Integer
	 */
	public Integer getCode(){
		return Integer.parseInt(this.get("code") != null ? String.valueOf(this.get("code")) : "500");
	}
}

SpuInfoController

/**
     * //product/spuinfo/{spuId}/up
     * 商品上架
     */
    @PostMapping("/{spuId}/up")
    public R up(@PathVariable("spuId") Long spuId){
        spuInfoService.up(spuId);
        return R.ok();
    }

AttrDao

List<Long> selectSearchByAttrIdList(@Param("attrIdList") List<Long> attrIdList);

WareFeignService

package com.ljs.gulimall.product.feign;

import com.ljs.gulimall.common.to.HasStockTo;
import com.ljs.gulimall.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

@FeignClient("gulimall-ware")
public interface WareFeignService {

    /**
     * 是否有库存
     *
     * @param skuIds skuIds
     * @return
     */
    @PostMapping("/ware/waresku/hasstock")
    R<List<HasStockTo>> hasstock(@RequestBody List<Long> skuIds);
}

AttrServiceImpl

 @Override
    public List<Long> selectSearchByAttrIdList(List<Long> attrIdList) {
        return baseMapper.selectSearchByAttrIdList(attrIdList);
    }

ProductAttrValueServiceImpl

@Override
    public List<ProductAttrValueEntity> baseAttrListforSpu(Long spuId) {
        return baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id",spuId));
    }

SpuInfoServiceImpl

package com.ljs.gulimall.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ljs.gulimall.common.es.SkuEsModel;
import com.ljs.gulimall.common.to.HasStockTo;
import com.ljs.gulimall.common.to.SkuFullReductionTo;
import com.ljs.gulimall.common.to.SpuBoundsTo;
import com.ljs.gulimall.common.utils.PageUtils;
import com.ljs.gulimall.common.utils.Query;
import com.ljs.gulimall.common.utils.R;
import com.ljs.gulimall.product.dao.SpuInfoDao;
import com.ljs.gulimall.product.entity.*;
import com.ljs.gulimall.product.feign.CouponFeignService;
import com.ljs.gulimall.product.feign.WareFeignService;
import com.ljs.gulimall.product.service.*;
import com.ljs.gulimall.product.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {

    @Autowired
    private SpuInfoDescService spuInfoDescService;

    @Autowired
    private SpuImagesService imagesService;

    @Autowired
    private ProductAttrValueService attrValueService;

    @Autowired
    private AttrService attrService;

    @Autowired
    private SkuInfoService skuInfoService;

    @Autowired
    private SkuImagesService skuImagesService;

    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    private CouponFeignService couponFeignService;

    @Autowired
    private BrandService brandService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private WareFeignService wareFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                new QueryWrapper<SpuInfoEntity>()
        );

        return new PageUtils(page);
    }

    @Transactional
    @Override
    public void saveSpuInfo(SpuSaveVo spuInfo) {
        // 1、保存spu基本信息 pms_spu_info
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(spuInfo,spuInfoEntity);
        spuInfoEntity.setCreateTime(new Date());
        this.saveSpu(spuInfoEntity);
        // 2、保存spu描述图片 pms_spu_info_desc
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
        spuInfoDescEntity.setDecript(String.join(",",spuInfo.getDecript()));
        spuInfoDescService.save(spuInfoDescEntity);
        // 3、保存spu图片集 pms_spu_images
        List<String> images = spuInfo.getImages();
        imagesService.saveImages(spuInfoEntity.getId(),images);
        // 4、保存spu规格参数 pms_product_attr_value
        List<BaseAttrs> baseAttrs = spuInfo.getBaseAttrs();
        if (baseAttrs != null && baseAttrs.size() > 0){
            List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
                ProductAttrValueEntity entity = new ProductAttrValueEntity();
                entity.setSpuId(spuInfoEntity.getId());
                entity.setAttrId(attr.getAttrId());
                // 根据属性id查询属性名
                AttrEntity attrEntity = attrService.getBaseMapper().selectById(attr.getAttrId());
                if (attrEntity != null){
                    entity.setAttrName(attrEntity.getAttrName());
                }
                entity.setAttrValue(attr.getAttrValues());
                entity.setQuickShow(attr.getShowDesc());
                return entity;
            }).collect(Collectors.toList());
            attrValueService.saveBatch(collect);
        }
        // 5、保存spu积分信息 gulimall_sms--> sms_spu_bounds
        Bounds bounds = spuInfo.getBounds();
        SpuBoundsTo spuBoundsTo = new SpuBoundsTo();
        BeanUtils.copyProperties(bounds,spuBoundsTo);
        spuBoundsTo.setSpuId(spuInfoEntity.getId());
        R r = couponFeignService.saveSpuBounds(spuBoundsTo);
        if (r.getCode() != 0){
            log.error("远程调用保存spu积分信息保存失败。。。");
        }

        // 6、保存当前spu的所有sku信息:
        List<Skus> skus = spuInfo.getSkus();
        if (skus != null && skus.size() > 0) {
            for (Skus sku : skus) {
                List<Images> image = sku.getImages();
                String defaultImage = "";
                for (Images img : image) {
                    if (img.getDefaultImg() == 1) {
                        // 是否为默认图片为是的
                        defaultImage = img.getImgUrl();
                    }
                }
                SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
                BeanUtils.copyProperties(sku, skuInfoEntity);
                // 保存其他基本信息
                skuInfoEntity.setSkuDefaultImg(defaultImage);
                skuInfoEntity.setBrandId(spuInfo.getBrandId());
                skuInfoEntity.setCatalogId(spuInfo.getCatalogId());
                skuInfoEntity.setSaleCount(0L);
                skuInfoEntity.setSpuId(spuInfoEntity.getId());
                // 6.1)、sku基本信息 pms_sku_info
                skuInfoService.insert(skuInfoEntity);
                Long skuId = skuInfoEntity.getSkuId();
                // 6.2)、sku图片信息(默认图片) pms_sku_images
                List<SkuImagesEntity> skuImagesEntities = image.stream().map(
                        img -> {
                            SkuImagesEntity entity = new SkuImagesEntity();
                            entity.setSkuId(skuId);
                            entity.setDefaultImg(img.getDefaultImg());
                            entity.setImgUrl(img.getImgUrl());
                            return entity;
                        }
                ).filter(
                        entity -> {
                            return !StringUtils.isEmpty(entity.getImgUrl());
                        }
                ).collect(Collectors.toList());
                skuImagesService.saveBatch(skuImagesEntities);
                // 6.3)、sku的销售属性信息 pms_sku_sale_attr_value
                List<Attr> attr = sku.getAttr();
                List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(
                        a -> {
                            SkuSaleAttrValueEntity entity = new SkuSaleAttrValueEntity();
                            BeanUtils.copyProperties(a, entity);
                            entity.setSkuId(skuId);
                            return entity;
                        }
                ).collect(Collectors.toList());
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
                // 6.4)、sku的优惠、满减信息 gulimall_sms--> sms_sku_ladder\sms_sku_full_reduction\sms_member_price
                SkuFullReductionTo skuFullReductionTo = new SkuFullReductionTo();
                BeanUtils.copyProperties(sku,skuFullReductionTo);
                skuFullReductionTo.setSkuId(skuId);
                // 满减数量和满减价格不能为0
                if (skuFullReductionTo.getFullCount() > 0 && skuFullReductionTo.getFullPrice().compareTo(new BigDecimal("0")) > 0){
                    R r1 = couponFeignService.saveSkuReduction(skuFullReductionTo);
                    if (r1.getCode() != 0){
                        log.error("远程调用sku的优惠保存失败。。。");
                    }
                }
            }
        }
    }

    @Override
    public PageUtils queryPageCondition(Map<String, Object> params) {
        QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<>();
        // 关键字检索
        Object key = params.get("key");
        // 三级分类id
        Object catalogId = params.get("catelogId");
        // 品牌id
        Object brandId = params.get("brandId");
        // 状态 0-新建 1-上架 2-下架
        Object status = params.get("status");
        if (!StringUtils.isEmpty(key)){
            wrapper.and((w) -> {
                w.eq("id",key).or().like("spu_name",key);
            });
        }
        if (!StringUtils.isEmpty(catalogId) && !"0".equals(catalogId)){
            wrapper.eq("catalog_id",catalogId);
        }
        if (!StringUtils.isEmpty(brandId) && !"0".equals(brandId)){
            wrapper.eq("brand_id",brandId);
        }
        if (!StringUtils.isEmpty(status)){
            wrapper.eq("publish_status",status);
        }

        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                wrapper
        );

        return new PageUtils(page);
    }

    @Override
    public void up(Long spuId) {
        // 1、根据spuId查询对应的sku信息
        List<SkuInfoEntity> skuInfoEntities = this.getSkuInfoBySpuId(spuId);
        List<Long> skuIds = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
        Map<Long,BrandEntity> brandMap = this.getGrandMap();
        Map<Long,CategoryEntity> categoryMap = this.getCategoryMap();
        // 查询sku可检索的属性attrs
        List<ProductAttrValueEntity> productAttrValueEntities = attrValueService.baseAttrListforSpu(spuId);
        // 封装所有attrId
        List<Long> attrIdList = productAttrValueEntities.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());
        // 查询attrId下所有能被搜索的attrId
        List<Long> searchIds = attrService.selectSearchByAttrIdList(attrIdList);
        Set<Long> attrIdSet = new HashSet<>(searchIds);
        List<SkuEsModel.Attrs> attrsList = productAttrValueEntities.stream().filter(item -> {
            return attrIdSet.contains(item.getAttrId());
        }).map(item -> {
            SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
            BeanUtils.copyProperties(item, attrs);
            return attrs;
        }).collect(Collectors.toList());
        // 发送远程调用查询库存系统是否有库存
        Map<Long, Boolean> hasStockMap = new HashMap<>();
        try{
            R<List<HasStockTo>> hasstock = wareFeignService.hasstock(skuIds);
            List<LinkedHashMap> hasStockToList = (List<LinkedHashMap>)hasstock.get("data");
            hasStockToList.forEach(
                    item -> {
                       hasStockMap.put(Long.valueOf(item.get("skuId").toString()),Boolean.valueOf(item.get("hasStock").toString()));
                    }
            );
            log.error("");
        } catch (Exception ex) {
            log.error("远程调用查询库存失败:信息:{}", ex);
        }
        // 2、组装sku数据
        Map<Long, Boolean> finalHasStockMap = hasStockMap;
        List<SkuInfoEntity> collect = skuInfoEntities.stream().map(item -> {
            SkuEsModel esModel = new SkuEsModel();
            BeanUtils.copyProperties(item,esModel);
            esModel.setSkuPrice(item.getPrice());
            esModel.setSkuImg(item.getSkuDefaultImg());
            // 热度评分 0
            esModel.setHotScore(0L);
            // 设置库存量
            esModel.setHasStock(CollectionUtils.isEmpty(finalHasStockMap) ? true : finalHasStockMap.get(item.getSkuId()));
            // 查询品牌分类的名字信息
            BrandEntity brandEntity = brandMap.get(item.getBrandId());
            CategoryEntity categoryEntity = categoryMap.get(item.getCatalogId());
            if (Objects.nonNull(brandEntity) && Objects.nonNull(categoryEntity)) {
                esModel.setBrandName(brandEntity.getName());
                esModel.setBrandImg(brandEntity.getLogo());
                esModel.setCatalogName(categoryEntity.getName());
            }
            // 设置attrs属性
            esModel.setAttrs(attrsList);
            return item;
        }).collect(Collectors.toList());

        // 5、发送es进行保存

    }

    private Map<Long, CategoryEntity> getCategoryMap() {
        List<CategoryEntity> list = categoryService.list();
        return list.stream().collect(Collectors.toMap(CategoryEntity::getCatId, categoryEntity -> categoryEntity));
    }

    private Map<Long, BrandEntity> getGrandMap() {
        List<BrandEntity> list = brandService.list();
        return list.stream().collect(Collectors.toMap(BrandEntity :: getBrandId, brandEntity -> brandEntity));
    }

    private List<SkuInfoEntity> getSkuInfoBySpuId(Long spuId) {
        return skuInfoService.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id",spuId));
    }

    private void saveSpu(SpuInfoEntity spuInfoEntity) {
        this.baseMapper.insert(spuInfoEntity);
    }

}

AttrService

List<Long> selectSearchByAttrIdList(List<Long> attrIdList);
}

ProductAttrValueService

List<ProductAttrValueEntity> baseAttrListforSpu(Long spuId);

SpuInfoService

void up(Long spuId);

AttrDao.xml

<select id="selectSearchByAttrIdList" resultType="java.lang.Long">
        select attr_id from
        pms_attr
        where attr_id in
        <foreach collection="attrIdList" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
        and search_type = 1
    </select>

WareSkuController

  /**
     * 获取skuId下是否有库存
     */
    @PostMapping("/hasstock")
    public R<List<HasStockEntity>> hasstock(@RequestBody List<Long> skuIds){
        List<HasStockEntity> wareSkuEntities = wareSkuService.hasstock(skuIds);

        return R.ok().put("data", wareSkuEntities);
    }

WareSkuDao

Integer selectStoveStock(@Param("skuId") Long skuId);

HasStockEntity

package com.ljs.gulimall.ware.entity;

import lombok.Data;

@Data
public class HasStockEntity {
    private Long skuId;

    private Boolean hasStock;
}

WareSkuServiceImpl

 @Override
    public List<HasStockEntity> hasstock(List<Long> skuIds) {
        return skuIds.stream().map(item -> {
            Integer stock = this.baseMapper.selectStoveStock(item);
            if (stock != null) {
                HasStockEntity hasStockEntity = new HasStockEntity();
                hasStockEntity.setSkuId(item);
                hasStockEntity.setHasStock(stock > 0);
                return hasStockEntity;
            }
          return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

WareSkuService

List<HasStockEntity> hasstock(List<Long> skuIds);

WareSkuDao.xml

   <select id="selectStoveStock" resultType="java.lang.Integer">
         select sum(stock-stock_locked) stock from wms_ware_sku where sku_id = #{skuId};
    </select>

1.3.2 es保存商品上架

search微服务提供远程接口
EsConstant

package com.ljs.gulimall.search.constant;

public class EsConstant {

    /**
     * sku数据在es中的索引
     */
    public static final String PRODUCT_INDEX = "product";
}

ElasticSearchController

package com.ljs.gulimall.search.controller;

import com.ljs.gulimall.common.Enum.BizCodeEnum;
import com.ljs.gulimall.common.es.SkuEsModel;
import com.ljs.gulimall.common.utils.R;
import com.ljs.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.List;

@RestController
@Slf4j
@RequestMapping("/search/save")
public class ElasticSearchController {

    @Autowired
    private ProductSaveService productSaveService;

    @PostMapping("/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModelList)  {
        boolean b = false;
        try {
            b = productSaveService.productStatusUp(skuEsModelList);
        } catch (IOException e) {
            log.error("error===>{}",e);
            return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
        }
        if (b) {
            return R.ok();
        }
        return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
    }

}

application.properties

server.port=13000

product-mapping.txt

PUT product
{
  "mappings": {
    "properties": {
      "skuId": {
        "type": "long"
      },
      "spuId": {
        "type": "keyword"
      },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "saleCount": {
        "type": "long"
      },
      "hasStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "brandId": {
        "type": "long"
      },
      "catalogId": {
        "type": "long"
      },
      "brandName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "brandImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "catalogName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

BizCodeEnum.java

PRODUCT_UP_EXCEPTION(11000,"商品上架异常")
    ;

StatusEnum.java

package com.ljs.gulimall.common.Enum;

public enum StatusEnum {
    NEW_SPU(0,"新建"),
    SPU_UP(1,"商品上架"),
    SPU_DOWN(2,"商品下架")
    ;


    private int code;

    private String msg;

    StatusEnum(int code,String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

ProductSaveService.java

package com.ljs.gulimall.search.service;

import com.ljs.gulimall.common.es.SkuEsModel;

import java.io.IOException;
import java.util.List;

public interface ProductSaveService {

    boolean productStatusUp(List<SkuEsModel> skuEsModelList) throws IOException;
}

ProductSaveServiceImpl.java

package com.ljs.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.ljs.gulimall.common.es.SkuEsModel;
import com.ljs.gulimall.search.config.GulimallElasticsearchConfig;
import com.ljs.gulimall.search.constant.EsConstant;
import com.ljs.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ProductSaveServiceImpl implements ProductSaveService {

    @Autowired
    RestHighLevelClient restHighLevelClient;

    @Override
    public boolean productStatusUp(List<SkuEsModel> skuEsModelList) throws IOException {
        // 1、在es中建立索引。保存映射关系。在resources文件夹下product-mapping.txt
        // 2、保存索引信息
        BulkRequest bulkRequest = new BulkRequest();
        for (SkuEsModel skuEsModel : skuEsModelList) {
            // 构造保存请求
            IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
            indexRequest.id(skuEsModel.getSkuId().toString());
            String str = JSON.toJSONString(skuEsModel);
            indexRequest.source(str, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticsearchConfig.COMMON_OPTIONS);
        boolean b = bulk.hasFailures();
        List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
            return item.getId();
        }).collect(Collectors.toList());
        log.info("商品上架完成:{}",collect);
        return !b;
    }
}

商品微服务调用search服务远程接口:
SpuInfoDao.java

package com.ljs.gulimall.product.dao;

import com.ljs.gulimall.product.entity.SpuInfoEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * spu信息
 * 
 * @author liangjiansong
 * @email liangjiansong@gmail.com
 * @date 2022-10-22 21:21:08
 */
@Mapper
public interface SpuInfoDao extends BaseMapper<SpuInfoEntity> {

    void updateStatusById(@Param("spuId") Long spuId, @Param("code") int code);
}

SearchFeighService.java

package com.ljs.gulimall.product.feign;

import com.ljs.gulimall.common.es.SkuEsModel;
import com.ljs.gulimall.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

@FeignClient("gulimall-search")
public interface SearchFeighService {

    @PostMapping("/search/save/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModelList);

}

SpuInfoServiceImpl.java

  // 5、发送es进行保存
        R r = searchFeighService.productStatusUp(collect);
        if (r.getCode() == 0) {
            // 远程调用成功
            // 修改当前spu上架状态
            this.baseMapper.updateStatusById(spuId, StatusEnum.SPU_UP.getCode());
        } else {
            // 远程调用失败
            // 接口幂等性,重试机制?
        }

SpuInfoDao.xml

  <update id="updateStatusById">
        update pms_spu_info set publish_status = #{code},update_time = now() where id = #{spuId}
    </update>

在这里插入图片描述
skuId为刚才新增的。es保存成功

1.4、整合thymeleaf模版引擎

在这里插入图片描述

1、resources下新增static文件夹
放置所有静态资源
在这里插入图片描述
2、resources下新增templates文件夹
放置首页

3、pom文件引入thymeleaf依赖

 <!--thymeleaf模版引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

4、springboot关闭模版引擎缓存
application.yml

在这里插入图片描述
5、将product微服务的controller文件夹改成app方便以后开发。并新建一个web的文件夹

6、重启项目。访问http://localhost:12000/

在这里插入图片描述

1.5、商城业务:整合首页

1.5.1、渲染一级分类数据

IndexController

package com.ljs.gulimall.product.web;

import com.ljs.gulimall.product.entity.CategoryEntity;
import com.ljs.gulimall.product.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class IndexController {

    @Autowired
    private CategoryService categoryService;

    @GetMapping({"/","/index.html"})
    public String indexPage(Model model) {
        // 1、查出所有的一级分类
        List<CategoryEntity> categoryEntities = categoryService.getLevel1Categorys();
        model.addAttribute("categorys",categoryEntities);
        return "index";
    }
}

CategoryService

List<CategoryEntity> getLevel1Categorys();

CategoryServiceImpl

@Override
    public List<CategoryEntity> getLevel1Categorys() {
        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

整合dev-tools实现服务器不重启页面更新

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

index.html
1、增加thymeleaf名称空间

<html lang="en" xmlns:th="http://www.thymeleaf.org">

2、访问接口使用thymeleaf语法

  <!--轮播主体内容-->
    <div class="header_main">
      <div class="header_banner">
        <div class="header_main_left">
          <ul>
            <li th:each="category: ${categorys}">
              <a href="#" class="header_main_left_a"  th:attr="ctg-data= ${category.catId}"><b th:text="${category.name}"></b></a>
            </li>
          </ul>
        </div>

1级分类
在这里插入图片描述

1.5.2、渲染二三级分类

js通过接口获取json数据
catalogLoader.js

$.getJSON("index/catalog.json",function (data) {

Catelog2Vo

package com.ljs.gulimall.product.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Catelog2Vo {
    /**
     * 1级父分类id
     */
    private String catalog1Id;

    /**
     * 3级子分类
     */
    private List<Catelog3Vo> catalog3List;

    private String id;

    private String name;

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public static class Catelog3Vo{
        /**
         * 2级父分类id
         */
        private String catalog2Id;
        private String id;
        private String name;
    }
}

IndexController


    @ResponseBody
    @GetMapping("index/catalog.json")
    public Map<String,List<Catelog2Vo>> getCatalog(){
        return categoryService.getCatalog();
    }

CategoryService

Map<String, List<Catelog2Vo>> getCatalog();

CategoryServiceImpl

    @Override
    public Map<String, List<Catelog2Vo>> getCatalog() {
        // 1、获取当前所有分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        if (CollectionUtils.isEmpty(categoryEntities)) {
            return new HashMap<>();
        }
        // 2、对所有1级分类下的2级分类进行分组
        Map<Long, List<CategoryEntity>> categoryLv1Map = categoryEntities.stream().filter(item -> {
            return item.getCatLevel() == LEVEL_TWO;
        }).collect(Collectors.groupingBy(CategoryEntity::getParentCid));
        // 3、对所有2级分类下的3级分类进行分组
        Map<Long, List<CategoryEntity>> categoryLv2Map = categoryEntities.stream().filter(item -> {
            return item.getCatLevel() == LEVEL_THREE;
        }).collect(Collectors.groupingBy(CategoryEntity::getParentCid));
        // 4、封装返回出参
        Map<String,List<Catelog2Vo>> catelog2Map = new HashMap<>();
        categoryLv1Map.forEach((key, value) -> {
            List<Catelog2Vo> catelog2VoList = new ArrayList<>();
            for (CategoryEntity categoryEntity : value) {
                Catelog2Vo catelog2Vo = new Catelog2Vo();
                catelog2Vo.setCatalog1Id(String.valueOf(key));
                catelog2Vo.setId(String.valueOf(categoryEntity.getCatId()));
                catelog2Vo.setName(categoryEntity.getName());
                List<CategoryEntity> categoryLv3List = categoryLv2Map.get(categoryEntity.getCatId());
                if (!CollectionUtils.isEmpty(categoryLv3List)) {
                    List<Catelog2Vo.Catelog3Vo> lv3List = categoryLv3List.stream().map(item -> {
                        Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
                        catelog3Vo.setCatalog2Id(String.valueOf(categoryEntity.getCatId()));
                        catelog3Vo.setId(String.valueOf(item.getCatId()));
                        catelog3Vo.setName(item.getName());
                        return catelog3Vo;
                    }).collect(Collectors.toList());
                    catelog2Vo.setCatalog3List(lv3List);
                }
                catelog2VoList.add(catelog2Vo);
                catelog2Map.put(String.valueOf(key),catelog2VoList);
            }
        });
        return catelog2Map;
    }

在这里插入图片描述

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值