跳到主要内容

商品核心逻辑说明

一、商品sku

1、表结构设计

商品表(es_goods)

字段名类型备注是否索引
goods_idbigint(20)商品ID
goods_namevarchar(255)商品名称
snvarchar(255)商品编号
brand_idbigint(20)品牌id
category_idbigint(20)分类id
goods_typevarchar(50)商品类型
weightdecimal(10)重量
market_enableint(1)上架状态
introlongtext详情
pricedecimal(20)商品价格
costdecimal(20)成本价格
mktpricedecimal(20)市场价格
have_specint(1)是否有规格
create_timebigint(20)创建时间
last_modifybigint(20)最后修改时间
view_countint(10)浏览数量
buy_countint(10)购买数量
disabledint(10)是否被删除
quantityint(10)实际库存
enable_quantityint(10)可用库存
pointint(10)积分
page_titlevarchar(255)seo标题
meta_keywordsvarchar(255)seo关键字
meta_descriptionvarchar(255)seo描述
gradedecimal(20)商品好评率
thumbnailvarchar(255)缩略图路径
bigvarchar(255)大图路径
smallvarchar(255)小图路径
originalvarchar(255)原图路径
seller_idbigint(20)卖家id
shop_cat_idbigint(20)店铺分类id
comment_numint(10)评论数量
template_idbigint(20)运费模板id
goods_transfee_chargeint(1)谁承担运费
seller_namevarchar(255)卖家名字
is_authint(1)审核状态
auth_messagevarchar(255)审核信息
self_operatedint(1)是否自营
under_messagevarchar(500)下架原因
priorityint(1)搜索优先级
mobile_introlongtext商品移动端详情
goods_videovarchar(255)商品视频

商品sku表(es_goods_sku)

字段名类型备注是否索引
sku_idbigint(20)主键ID
goods_idbigint(20)商品ID
goods_namevarchar(255)商品名称
snvarchar(50)商品编号
quantityint(10)库存
enable_quantityint(10)可用库存
pricedecimal(20)商品价格
specslongtext规格信息json
costdecimal(20)成本价格
weightdecimal(20)重量
seller_idbigint(20)卖家id
seller_namevarchar(255)卖家名称
category_idbigint(20)分类id
thumbnailvarchar(255)缩略图
hash_codeint(10) 哈希码

2、商品结构

二、商品规格值

1、表结构设计

规格项(es_specification) 所有规格名称和描述的表

列名类型描述是否索引
spec_idbigint(20)主键
spec_namevarchar(100)规格项名称
disabledint(1)是否被删除 0 删除 1 没有删除
spec_memovarchar(50)规格描述
seller_idbigint(20)所属卖家

规格值(es_spec_values) 规格值的表

列名类型描述是否索引
spec_value_idbigint(20)主键
spec_idbigint(20)规格项id
spec_valuevarchar(100)规格值名字
seller_idbigint(20)所属卖家
spec_namevarchar(100)规格名字

2、规格值结构

当前商城平台商品规格结构如下:

3、核心代码展示

商家添加规格值实现类(SpecValuesManagerImpl)—以添加规格为例:

        @Override
@Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public List<SpecValuesDO> saveSpecValue(Long specId, String[] valueList) {

//查询规格是否存在
SpecificationDO spec = specificationManager.getModel(specId);
if (spec == null) {
throw new ServiceException(GoodsErrorCode.E306.code(), "所属规格不存在");
}
this.specValuesMapper.delete(new QueryWrapper<SpecValuesDO>()
.eq("spec_id",specId)
.eq("seller_id",0));

List<SpecValuesDO> res = new ArrayList<>();
for (String value : valueList) {
if (value.length() > 50) {
throw new ServiceException(GoodsErrorCode.E305.code(), "规格值为1到50个字符之间");
}
SpecValuesDO specValue = new SpecValuesDO(specId, value, 0L);
specValue.setSpecName(spec.getSpecName());
this.specValuesMapper.insert(specValue);
res.add(specValue);
}
return res;
}

三、 商品参数组

1、表结构设计

商品关联参数表(es_goods_params)   关联了商品表(es_goods),商品参数组表(es_parameter)

字段名类型备注是否索引
idbigint(20) 主键ID
goods_idbigint(20) 商品id
param_idbigint(20) 参数id     
param_namevarchar(100) 参数名字    
param_valuevarchar(255) 参数值     

参数表(es_parameters)

字段名类型备注是否索引
param_idint(10)主键
param_namevarchar(100)参数名称
param_typeint(1)参数类型1 输入项 2 选择项
optionsvarchar(255)选择值,当参数类型是选择项2时,必填,逗号分隔
is_indexint(1)是否可索引,0 不显示 1 显示
requiredint(1)是否必填是 1 否 0
group_idint(10)参数分组id
category_idint(10)分类id
sortint(2)排序

参数组(es_parameter_group)

字段名类型备注是否索引
group_idint(10)主键
group_namevarchar(100)参数组名称
category_idint(10)关联分类id
sortint(2)排序

2、商品参数结构

四、 商品缓存

1、设计说明

商品为高频率读取数据,频繁读取数据库,会导致数据库压力过大,所以使用缓存机制,常用数据存入redis 中。

2、数据结构设计

商品缓存类:CacheGoods


/** 缓存商品对象 */
@ApiModel
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class CacheGoods implements Serializable {

private static final long serialVersionUID = -3642358108471082387L;
@ApiModelProperty(name = "goods_id", value = "商品id")
@Column(name = "goods_id")
private Long goodsId;

@ApiModelProperty(name = "category_id", value = "分类id")
private Long categoryId;

@ApiModelProperty(name = "goods_name", value = "商品名称")
@Column(name = "goods_name")
private String goodsName;

@ApiModelProperty(name = "sn", value = "商品编号")
@Column(name = "sn")
private String sn;

@ApiModelProperty(name = "price", value = "商品价格")
@Column(name = "price")
private Double price;

@ApiModelProperty(name = "weight", value = "重量")
@Column(name = "weight")
private Double weight;

@ApiModelProperty(name = "intro", value = "详情")
private String intro;

@ApiModelProperty(name = "goods_transfee_charge", value = "谁承担运费0:买家承担,1:卖家承担")
@Column(name = "goods_transfee_charge")
private Integer goodsTransfeeCharge;

@ApiModelProperty(name = "template_id", value = "运费模板id,不需要运费模板时值是0")
@Column(name = "template_id")
private Long templateId;

@ApiModelProperty(name = "market_enable", value = "是否上架,1上架 0下架")
@Column(name = "market_enable")
private Integer marketEnable;

@ApiModelProperty(name = "disabled", value = "是否放入回收站 0 删除 1未删除")
@Column(name = "disabled")
private Integer disabled;

@ApiModelProperty(name = "is_auth", value = "是否审核通过 0 未审核 1 通过 2 不通过")
@Column(name = "is_auth")
private Integer isAuth;

@ApiModelProperty(value = "可用库存")
@Column(name = "enable_quantity")
private Integer enableQuantity;

@ApiModelProperty(name = "quantity", value = "库存")
private Integer quantity;

@ApiModelProperty(name = "seller_id", value = "卖家")
private Long sellerId;

@ApiModelProperty(name = "seller_name", value = "卖家名称")
private String sellerName;

@ApiModelProperty(name = "sku_list", value = "sku列表")
private List<GoodsSkuVO> skuList;

@ApiModelProperty(name = "thumbnail", value = "商品缩略图")
private String thumbnail;

@ApiModelProperty(name = "last_modify", value = "商品最后修改时间")
private Long lastModify;

@ApiModelProperty(name = "comment_num", value = "评论数量")
private Integer commentNum;

@ApiModelProperty(name = "grade", value = "商品好评率")
private Double grade;

@ApiModelProperty(name = "mobile_intro", value = "商品移动端详情")
private String mobileIntro;

@ApiModelProperty(name = "goods_video", value = "商品视频")
private String goodsVideo;
.....

SKU缓存类:GoodsSkuVO

@ApiModel
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class GoodsSkuVO extends GoodsSkuDO {

private static final long serialVersionUID = -666090547834195127L;

@ApiModelProperty(name = "spec_list", value = "规格列表", required = false)
private List<SpecValueVO> specList;

@ApiModelProperty(name = "goods_transfee_charge", value = "谁承担运费0:买家承担,1:卖家承担", hidden = true)
private Integer goodsTransfeeCharge;

@ApiModelProperty(value = "是否被删除 0 删除 1 未删除", hidden = true)
private Integer disabled;

@ApiModelProperty(value = "上架状态 0下架 1上架", hidden = true)
private Integer marketEnable;

@ApiModelProperty(name = "goods_type", value = "商品类型NORMAL普通POINT积分")
private String goodsType;

@ApiModelProperty(value = "最后修改时间", hidden = true)
private Long lastModify;

@ApiModelProperty(value = "运费脚本", hidden = true)
private List<String> scripts;

GoodsSkuDO

@TableName("es_goods_sku")
@ApiModel
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class GoodsSkuDO implements Serializable {

private static final long serialVersionUID = 5102510694003249L;

/**
* 主键
*/
@TableId(type= IdType.ASSIGN_ID)
@ApiModelProperty(hidden = true)
private Long skuId;
/**
* 商品id
*/
@ApiModelProperty(name = "goods_id", value = "商品id", hidden = true)
private Long goodsId;
/**
* 商品名称
*/
@ApiModelProperty(name = "goods_name", value = "商品名称", hidden = true)
private String goodsName;
/**
* 商品编号
*/
@ApiModelProperty(name = "sn", value = "商品编号", required = false)
@Length(max = 30,message = "商品规格编号太长,不能超过30个字符")
private String sn;
/**
* 库存
*/
@ApiModelProperty(name = "quantity", value = "库存", required = false)
@Max(value = 99999999, message = "库存不能超过99999999")
private Integer quantity;
/**
* 可用库存
*/
@ApiModelProperty(name = "enable_quantity", value = "可用库存")
private Integer enableQuantity;
/**
* 商品价格
*/
@ApiModelProperty(name = "price", value = "商品价格", required = false)
@Max(value = 99999999, message = "价格不能超过99999999")
private Double price;
/**
* 规格信息json
*/
@ApiModelProperty(name = "specs", value = "规格信息json", hidden = true)
private String specs;
/**
* 成本价格
*/
@ApiModelProperty(name = "cost", value = "成本价格", required = true)
@Max(value = 99999999, message = "成本价格不能超过99999999")
private Double cost;
/**
* 重量
*/
@ApiModelProperty(name = "weight", value = "重量", required = true)
@Max(value = 99999999, message = "重量不能超过99999999")
private Double weight;
/**
* 卖家id
*/
@ApiModelProperty(name = "seller_id", value = "卖家id", hidden = true)
private Long sellerId;
/**
* 卖家名称
*/
@ApiModelProperty(name = "seller_name", value = "卖家名称", hidden = true)
private String sellerName;
/**
* 分类id
*/
@ApiModelProperty(name = "category_id", value = "分类id", hidden = true)
private Long categoryId;
/**
* 缩略图
*/
@ApiModelProperty(name = "thumbnail", value = "缩略图", hidden = true)
private String thumbnail;


@ApiModelProperty(name = "hash_code", value = "hash_code", hidden = true)
private Integer hashCode;

/**
* 运费模板ID(非数据库字段)
*/
@TableField(exist = false)
private Long templateId;

3、核心代码展示

缓存商品的查询和写入

    /**
* 缓存中查询商品的信息
*
* @param goodsId 商品主键
* @return
*/
@Override
public CacheGoods getFromCache(Long goodsId) {
CacheGoods goods = (CacheGoods) cache.get(CachePrefix.GOODS.getPrefix() + goodsId);
logger.debug("由缓存中读出商品:");
logger.debug(goods);
if (goods == null) {
GoodsDO goodsDB = this.getModel(goodsId);
if (goodsDB == null) {
throw new ServiceException(GoodsErrorCode.E301.code(), "该商品已被彻底删除");
}
// GoodsVo的对象返回,GoodsVo中的skuList是要必须填充好的
List<GoodsSkuVO> skuList = goodsSkuManager.listByGoodsId(goodsId);

goods = new CacheGoods();
BeanUtils.copyProperties(goodsDB, goods);
goods.setSkuList(skuList);

//填充库存数据
fillStock(goods);
cache.put(CachePrefix.GOODS.getPrefix() + goodsId, goods);
logger.debug("由缓存中读出商品为空,由数据库中返回商品:");
logger.debug(goods);
return goods;
} else {
//填充库存数据
fillStock(goods);
}
return goods;
}

五、 商品详情页

1、设计说明

商品详情页分为服务器端渲染和前端异步渲染部分,服务器端渲染通过静态页功能静态化展示
服务器端渲染:商品名称、商品价格、分类名称、规格参数、商品详情、商品相册、商品好评率、店铺的信息等
浏览器异步渲染:商品的库存,商品的规格,商品的参与的活动、商品评论、商品咨询、销售记录等

2、表结构设计

商品相册表(es_goods_gallery)   存储商品图片的链接地址

字段名类型备注是否索引
img_idbigint(20)主键ID
goods_idbigint(20)商品主键
thumbnailvarchar(255)缩略图路径
smallvarchar(255)小图路径
bigvarchar(255)大图路径
originalvarchar(255)原图路径
tinyvarchar(255)极小图路径
isdefaultint(1)是否是默认图片
sortint(2)排序

交易快照表(es_goods_snapshot)   订单生成时,把下单的商品数据写入此表,留作凭证。

字段名类型备注是否索引
snapshot_idbigint(20)主键ID
goods_idbigint(20)商品id
namevarchar(255)商品名称
snvarchar(255)商品编号
brand_namevarchar(255)品牌名称
category_namevarchar(255)分类名称
goods_typevarchar(20)商品类型
weightdecimal(10)重量
introlongtext商品详情
mobile_introlongtext商品移动端详情
pricedecimal(10)商品价格
costdecimal(10)商品成本价
mktpricedecimal(10)商品市场价
have_specsmallint(1)商品是否开启规格
params_jsonlongtext参数json
img_jsonlongtext图片json
create_timebigint(20)快照时间
pointint(10)商品使用积分
seller_idbigint(20)所属卖家
promotion_jsonlongtext促销json
coupon_jsonlongtext优惠券json
member_idbigint(20)所属会员

3、核心代码展示

静态部分

    @ApiOperation(value = "浏览商品的详情,静态部分使用")
@ApiImplicitParam(name = "goods_id", value = "分类id,顶级为0", required = true, dataType = "long", paramType = "path")
@GetMapping("/{goods_id}")
public GoodsShowDetail getGoodsDetail(@PathVariable("goods_id") Long goodsId) {

GoodsDO goods = goodsQueryManager.getModel(goodsId);
GoodsShowDetail detail = new GoodsShowDetail();
if (goods == null){
throw new ResourceNotFoundException("不存在此商品");
}
BeanUtils.copyProperties(goods,detail);
Integer goodsOff = 0;
//商品不存在,直接返回
if(goods == null){
detail.setGoodsOff(goodsOff);
return detail;
}
//分类
CategoryDO category = categoryManager.getModel(goods.getCategoryId());
detail.setCategoryName(category == null ? "":category.getName());
//上架状态
if(goods.getMarketEnable()==1){
goodsOff = 1;
}
detail.setGoodsOff(goodsOff);
//参数
List<GoodsParamsGroupVO> list = this.goodsParamsManager.queryGoodsParams(goods.getCategoryId(),goodsId);
detail.setParamList(list);
//相册
List<GoodsGalleryDO> galleryList = goodsGalleryManager.list(goodsId);
detail.setGalleryList(galleryList);
//商品好平率
detail.setGrade(goodsQueryManager.getGoodsGrade(goodsId));
return detail;
}

动态部分

    @ApiOperation(value = "获取sku信息,商品详情页动态部分")
@ApiImplicitParam(name = "goods_id", value = "商品id", required = true, dataType = "long", paramType = "path")
@GetMapping("/{goods_id}/skus")
public List<GoodsSkuVO> getGoodsSkus(@PathVariable("goods_id") Long goodsId) {
CacheGoods goods = goodsQueryManager.getFromCache(goodsId);
return goods.getSkuList();
}


@Override
CacheGoods goods = (CacheGoods) cache.get(CachePrefix.GOODS.getPrefix() + goodsId);
if (goods == null) {
GoodsDO goodsDB = this.getModel(goodsId);
if (goodsDB == null) {
throw new ServiceException(GoodsErrorCode.E301.code(), "该商品已被彻底删除");
}
// GoodsVo的对象返回,GoodsVo中的skuList是要必须填充好的
List<GoodsSkuVO> skuList = goodsSkuManager.listByGoodsId(goodsId);
//类型转换
goods = new CacheGoods();
BeanUtils.copyProperties(goodsDB, goods);
goods.setSkuList(skuList);

//填充库存数据
fillStock(goods);
cache.put(CachePrefix.GOODS.getPrefix() + goodsId, goods);
return goods;
} else {
//填充库存数据
fillStock(goods);
}

六、 库存设计

1、设计思路

添加库存:goods表的库存字段,人员不能进行编辑,而是编辑对应的SKU库存,再通过SKU总库存,写入goods表的库存字段
扣减库存:扣减reids 的库存,一定状态后统一同步到数据库。

2、实现方法

采用lua脚本执行redis中的库存扣减 数据库的更新采用非时时同步,而是建立了一个缓冲池,当达到一定条件时再同步数据库。 这样条件有:缓冲区大小,缓冲次数,缓冲时间 上述条件在配置中心可以配置,如果没有配置采用 ${@link UpdatePool} 默认值

在配置项说明:

    缓冲区大小:shoptnt.pool.stock.max-pool-size     
缓冲次数:shoptnt.pool.stock.max-update-time
缓冲时间(秒数):shoptnt.pool.stock.max-lazy-second

3、核心代码展示

GoodsQuantityVO:

    //商品ID
private Long goodsId;
//skuID
private Long skuId;
//库存值
private Integer quantity;
//库存类型 actual: 实际的库存,包含了待发货的
enable: 可以售的库存,不包含待发货的
private QuantityType quantityType;

GoodsQueryManagerImpl

 /**
* 为商品填充库存信息<br/>
* 库存的信息存储在单独的缓存key中<br/>
* 由缓存中读取出所有sku的库存,并分别为goods.skuList中的sku设置库存,以保证库存的时时正确性<br/>
* 同时还会将所有的sku库存累加设置为商品的库存
*
* @param goods
*/
protected void (CacheGoods goods) {
List<GoodsSkuVO> skuList = goods.getSkuList();
//由缓存中获取sku的可用库存和实际库存
//此操作为批量操作,因为是高频操作,要尽量减少和redis的交互次数
List keys = createKeys(skuList);
//将商品的可用库存和实际库存一起读
keys.add(StockCacheKeyUtil.goodsEnableKey(goods.getGoodsId()));
keys.add(StockCacheKeyUtil.goodsActualKey(goods.getGoodsId()));
List<String> quantityList = stringRedisTemplate.opsForValue().multiGet(keys);
int enableTotal = 0;
int actualTotal = 0;
int i = 0;
for (GoodsSkuVO skuVO : skuList) {
//第一个是可用库存
Integer enable = StringUtil.toInt(quantityList.get(i), null);
i++;
//第二个是真实库存
Integer actual = StringUtil.toInt(quantityList.get(i), null);
//缓存被击穿,由数据库中读取
if (enable == null || actual == null) {
Map<String, Integer> map = goodsQuantityManager.fillCacheFromDB(skuVO.getSkuId());
enable = map.get("enable_quantity");
actual = map.get("quantity");
//重置缓存中的库存
stringRedisTemplate.opsForValue().set(StockCacheKeyUtil.skuEnableKey(skuVO.getSkuId()), "" + enable);
stringRedisTemplate.opsForValue().set(StockCacheKeyUtil.skuActualKey(skuVO.getSkuId()), "" + actual);
}
skuVO.setEnableQuantity(enable);
skuVO.setQuantity(actual);
if (enable == null) {
enable = 0;
}
if (actual == null) {
actual = 0;
}
//累计商品的库存
enableTotal += enable;
actualTotal += actual;
i++;
}

//设置商品的库存
goods.setEnableQuantity(enableTotal);
goods.setQuantity(actualTotal);

//读取缓存中商品的库存,看是否被击穿了
//第一个是可用库存
Integer goodsEnable = StringUtil.toInt(quantityList.get(i), null);
i++;

//第二个是真实库存
Integer goodsActual = StringUtil.toInt(quantityList.get(i), null);

//商品的库存被击穿了
if (goodsEnable == null || goodsActual == null) {
//重置缓存中的库存
stringRedisTemplate.opsForValue().set(StockCacheKeyUtil.goodsEnableKey(goods.getGoodsId()), "" + enableTotal);
stringRedisTemplate.opsForValue().set(StockCacheKeyUtil.goodsActualKey(goods.getGoodsId()), "" + actualTotal);
}
}

七、 搜索设计

1、设计思路

商品是高频率搜索功能,数据库查询压力大,所以由 elasticsearch 进行创建映射,创建索引,进行搜索。。

2、表结构设计

商品分词表(es_goods_words)   为商品搜索之前,提供一个模糊查询商品的数量

字段名类型备注是否索引
idbigint(20)主键ID
wordsvarchar(255)分词
goods_numbigint(20)数量
quanpinvarchar(255)全拼字母
szmvarchar(255)首字母
typevarchar(255)类型
sortint(10)排序字段

3、代码示例

1.创建映射(goodsMapping) EsDeployExecutor#createGoodsMapping方法中新增所需字段

创建商品mapping 类型为HashMap

            /**
* 创建商品mapping
*/
private Map createGoodsMapping() {
Map goodsMap = new HashMap();
//放入商品信息到创建的map中
goodsMap.put("goodsId", new MyMap().put("type", "long").getMap());
goodsMap.put("goodsName", new MyMap().put("type", "text").put("analyzer", "ik_max_word").getMap());
goodsMap.put("thumbnail", new MyMap().put("type", "text").getMap());
goodsMap.put("small", new MyMap().put("type", "text").getMap());
goodsMap.put("buyCount", new MyMap().put("type", "integer").getMap());
goodsMap.put("sellerId", new MyMap().put("type", "long").getMap());
goodsMap.put("sellerName", new MyMap().put("type", "text").getMap());
goodsMap.put("shopCatId", new MyMap().put("type", "long").getMap());
goodsMap.put("shopCatPath", new MyMap().put("type", "text").getMap());
goodsMap.put("commentNum", new MyMap().put("type", "integer").getMap());
goodsMap.put("grade", new MyMap().put("type", "double").getMap());
goodsMap.put("price", new MyMap().put("type", "double").getMap());
goodsMap.put("brand", new MyMap().put("type", "long").getMap());
goodsMap.put("categoryId", new MyMap().put("type", "long").getMap());
goodsMap.put("categoryPath", new MyMap().put("type", "text").getMap());
goodsMap.put("disabled", new MyMap().put("type", "integer").getMap());
goodsMap.put("marketEnable", new MyMap().put("type", "integer").getMap());
goodsMap.put("priority", new MyMap().put("type", "integer").getMap());
goodsMap.put("isAuth", new MyMap().put("type", "integer").getMap());
goodsMap.put("intro", new MyMap().put("type", "text").getMap());
goodsMap.put("selfOperated", new MyMap().put("type", "integer").getMap());
            //参数有多种,所以创建多个map
Map paramPro = new MyMap().put("name", new MyMap().put("type", "keyword").getMap()).put("value", new MyMap().put("type", "keyword").getMap()).getMap();
goodsMap.put("params", new MyMap().put("type", "nested").put("properties", paramPro).getMap());

return new MyMap().put("properties", goodsMap).getMap();
}
   2.索引类增加对应属性
cn.shoptnt.core.goodssearch.model.GoodsIndex 这个类中也需要新增对应字段

```java
@Document(indexName = "#{esConfig.indexName}_"+ EsSettings.GOODS_INDEX_NAME, type = EsSettings.GOODS_TYPE_NAME)
public class GoodsIndex {
public GoodsIndex() {
}

@Id
private Long goodsId;

@Field(type = FieldType.text, analyzer = "ik_max_word")
private String goodsName;

@Field(type = FieldType.Long)
private Long sellerId;

....
}

3.填充商品信息        GoodsIndexManagerImpl#getSource方法中添加对应字段,将商品对应字段值信息存储到ES中

把HashMap封装成 GoodsIndex 类

    /**
* 封装成内存需要格式数据
* @param goods
*/
protected GoodsIndex getSource(Map goods) {
GoodsIndex goodsIndex = new GoodsIndex();
goodsIndex.setGoodsId(StringUtil.toLong(goods.get("goods_id").toString(), 0));
goodsIndex.setGoodsName(goods.get("goods_name").toString());
goodsIndex.setThumbnail(goods.get("thumbnail") == null ? "" : goods.get("thumbnail").toString());
goodsIndex.setSmall(goods.get("small") == null ? "" : goods.get("small").toString());
Double p = goods.get("price") == null ? 0d : StringUtil.toDouble(goods.get("price").toString(), 0d);
goodsIndex.setPrice(p);
Double discountPrice = goods.get("discount_price") == null ? 0d : StringUtil.toDouble(goods.get("discount_price").toString(), 0d);
goodsIndex.setDiscountPrice(discountPrice);
goodsIndex.setBuyCount(goods.get("buy_count") == null ? 0 : StringUtil.toInt(goods.get("buy_count").toString(), 0));
goodsIndex.setSellerId(StringUtil.toLong(goods.get("seller_id").toString(), 0));
//店铺分组
goodsIndex.setShopCatId(goods.get("shop_cat_id") == null ? 0 : StringUtil.toLong(goods.get("shop_cat_id").toString(), 0));
goodsIndex.setShopCatPath("");
if (goodsIndex.getShopCatId() != 0) {
ShopCatDO shopCat = shopCatClient.getModel(goodsIndex.getShopCatId());
if (shopCat != null) {
goodsIndex.setShopCatPath(HexUtils.encode(shopCat.getCatPath()));
}
}
goodsIndex.setSellerName(goods.get("seller_name").toString());
goodsIndex.setCommentNum(goods.get("comment_num") == null ? 0 : StringUtil.toInt(goods.get("comment_num").toString(), 0));
goodsIndex.setGrade(goods.get("grade") == null ? 100 : StringUtil.toDouble(goods.get("grade").toString(), 100d));
goodsIndex.setBrand(goods.get("brand_id") == null ? 0 : StringUtil.toLong(goods.get("brand_id").toString(), 0));
goodsIndex.setCategoryId(goods.get("category_id") == null ? 0 : StringUtil.toLong(goods.get("category_id").toString(), 0));
CategoryDO cat = categoryManager.getModel(Long.valueOf(goods.get("category_id").toString()));
if (cat!=null){
goodsIndex.setCategoryPath(HexUtils.encode(cat.getCategoryPath()));
}
goodsIndex.setDisabled(StringUtil.toInt(goods.get("disabled").toString(), 0));
goodsIndex.setMarketEnable(StringUtil.toInt(goods.get("market_enable").toString(), 0));
goodsIndex.setIsAuth(StringUtil.toInt(goods.get("is_auth").toString(), 0));
goodsIndex.setIntro(goods.get("intro") == null ? "" : goods.get("intro").toString());
goodsIndex.setSelfOperated(goods.get("self_operated") == null ? 0 : StringUtil.toInt(goods.get("self_operated").toString(), 0));
//添加商品优先级维度
goodsIndex.setPriority(goods.get("priority") == null ? 1 : StringUtil.toInt(goods.get("priority").toString(), 1));
//参数维度,已填写参数
List<Map> params = (List<Map>) goods.get("params");
List<Param> paramsList = this.convertParam(params);
goodsIndex.setParams(paramsList);
return goodsIndex;
}

4.封装然后存入es

先把HashMap封装成 GoodsIndex 类,然后在封装到 indexQuery 类,最后写入 elasticsearch

 /**
* 将某个商品加入索引<br>
* @param goods 商品数据
*/
@Override
@Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void addIndex(Map goods) {
String goodsName = goods.get("goods_name").toString();
try {
//配置文件中定义的索引名字
String indexName = esConfig.getIndexName() + "_" + EsSettings.GOODS_INDEX_NAME;
GoodsIndex goodsIndex = this.getSource(goods);
IndexQuery indexQuery = new IndexQuery();
indexQuery.setIndexName(indexName);
indexQuery.setType(EsSettings.GOODS_TYPE_NAME);
indexQuery.setId(goodsIndex.getGoodsId().toString());
indexQuery.setObject(goodsIndex);
        //审核通过且没有下架且没有删除
boolean flag = goodsIndex.getDisabled() == 1 && goodsIndex.getMarketEnable() == 1 && goodsIndex.getIsAuth() == 1;
if (flag) {
List<String> wordsList = toWordsList(goodsName);
// 分词入库
this.wordsToDb(wordsList);
}
elasticsearchOperations.index(indexQuery);
} catch (Exception e) {
e.printStackTrace();
logger.error("为商品["+goodsName+"]生成索引异常",e);
debugger.log("为商品["+goodsName+"]生成索引异常",StringUtil.getStackTrace(e));
throw new RuntimeException("为商品["+goodsName+"]生成索引异常", e);
}
}

5.商品搜索参数
>如果新增字段不做为搜索参数可忽略此步骤

&nbsp;&nbsp;
&nbsp;&nbsp;GoodsSearchManagerImpl#createQuery方法中,添加对应字段的搜索表达式

```java
/**
* 构建查询条件
* @throws Exception
*/
protected SearchRequestBuilder createQuery(GoodsSearchDTO goodsSearch) throws Exception {

String keyword = goodsSearch.getKeyword();
Long cat = goodsSearch.getCategory();
Long brand = goodsSearch.getBrand();
String price = goodsSearch.getPrice();
Long sellerId = goodsSearch.getSellerId();
Long shopCatId = goodsSearch.getShopCatId();
SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(esConfig.getIndexName() + "_" + EsSettings.GOODS_INDEX_NAME);
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

// 关键字检索
if (!StringUtil.isEmpty(keyword)) {
QueryStringQueryBuilder queryString = new QueryStringQueryBuilder(keyword).field("goodsName");
queryString.defaultOperator(Operator.AND);
//按照关键字检索 关键字无需按照最细粒度分词 update by liuyulei 2019-12-12
queryString.analyzer("ik_smart");
queryString.useDisMax(false);
boolQueryBuilder.must(queryString);
}
// 品牌搜素
if (brand != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("brand", brand));
}
// 分类检索
if (cat != null) {
CategoryDO category = categoryManager.getModel(cat);
if (category == null) {
throw new ServiceException("", "该分类不存在");
}
boolQueryBuilder.must(QueryBuilders.wildcardQuery("categoryPath", HexUtils.encode(category.getCategoryPath()) + "*"));
}
//卖家搜索
if (sellerId != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("sellerId", sellerId));
}
// 卖家分组搜索
if (shopCatId != null) {
ShopCatDO shopCat = shopCatClient.getModel(shopCatId);
if (shopCat == null) {
throw new ServiceException("", "该分组不存在");
}
boolQueryBuilder.must(QueryBuilders.wildcardQuery("shopCatPath", HexUtils.encode(shopCat.getCatPath()) + "*"));
}

// 参数检索
String prop = goodsSearch.getProp();
if (!StringUtil.isEmpty(prop)) {
String[] propArray = prop.split(Separator.SEPARATOR_PROP);
for (String p : propArray) {
String[] onpropAr = p.split(Separator.SEPARATOR_PROP_VLAUE);
String name = onpropAr[0];
String value = onpropAr[1];
boolQueryBuilder.must(QueryBuilders.nestedQuery("params", QueryBuilders.termQuery("params.name", name), ScoreMode.None));
boolQueryBuilder.must(QueryBuilders.nestedQuery("params", QueryBuilders.termQuery("params.value", value), ScoreMode.None));
}
}

//价格搜索
if (!StringUtil.isEmpty(price)) {
String[] pricear = price.split(Separator.SEPARATOR_PROP_VLAUE);
double min = StringUtil.toDouble(pricear[0], 0.0);
double max = Integer.MAX_VALUE;
if (pricear.length == 2) {
max = StringUtil.toDouble(pricear[1], Double.MAX_VALUE);
}
boolQueryBuilder.must(QueryBuilders.rangeQuery("price").from(min).to(max).includeLower(true).includeUpper(true));
}

// 删除的商品不显示
boolQueryBuilder.must(QueryBuilders.termQuery("disabled", "1"));
// 未上架的商品不显示
boolQueryBuilder.must(QueryBuilders.termQuery("marketEnable", "1"));
// 待审核和审核不通过的商品不显示
boolQueryBuilder.must(QueryBuilders.termQuery("isAuth", "1"));
searchRequestBuilder.setQuery(boolQueryBuilder);


//排序
String sortField = goodsSearch.getSort();
String sortId = "priority";
SortOrder sort = SortOrder.DESC;
if (!StringUtil.isEmpty(sortField)) {
Map<String, String> sortMap = SortContainer.getSort(sortField);
sortId = sortMap.get("id");
// 如果是默认排序 --默认排序根据 商品优先级排序 add by liuyulei _2019-07-01
if ("def".equals(sortId)) {
sortId = "priority";
}
if ("buynum".equals(sortId)) {
sortId = "buyCount";
}
if ("grade".equals(sortId)) {
sortId = "commentNum";
}
if ("desc".equals(sortMap.get("def_sort"))) {
sort = SortOrder.DESC;
} else {
sort = SortOrder.ASC;
}
}
searchRequestBuilder.addSort(sortId, sort);
//好平率
if ("commentNum".equals(sortId)) {
searchRequestBuilder.addSort("buyCount", sort);
// boolQueryBuilder.must(QueryBuilders.rangeQuery("buyCount").from(1).includeLower(true));
}
//如果不是默认排序 则在原有搜索结果基础上加上商品优先级排序 add by liuyulei 2019-07-01
if (!"priority".equals(sortId)) {
//商品优先级
searchRequestBuilder.addSort("priority", SortOrder.DESC);
}
return searchRequestBuilder;
}

ES搜索新增字段之后,需要执行部署程序重新初始化ES,然后管理端手动点击生成索引,否则搜索不到新增字段数据