商品上架
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;
}