【摘要】商城业务 shop-business~
商城业务-商品上架-sku在es中存储模型分析
上架的商品才可以在网站展示。
上架的商品需要可以被检索。
商品Mapping
分析:商品上架在es中是否存sku还是spu?
(1).检索的时候输入名字,是需要按照sku的title进行全文检索的。
(2).检索使用商品规格,规格是spu的公共属性,每个spu是一样的。
(3).按照分类id进去的都是直接列出spu的,还可以切换。
(4).我们如果将spu的全量信息保存到es中(包括spu属性)就太多字段了。
(5).我们如果将spu以及其他包含的sku信息保存到es中,也可以方便检索。但是sku属于spu的级联对象,在es中需要nested模型,这种性能差点。
(6).但是储存和检索我们必须性能折中。
(7).如果我们分拆存储,spu和attr一个索引,sku单独一个索引可能涉及的问题。
检索商品的名字,如”手机”,对应的spu有很多,我们要分析出这些spu的所有关联属性,再做一次查询,就必须将所有spu_id都发出去。假设有1万个数据,数据传输一次就10000*4=4MB;
并发情况下假设1000检索请求,那就是4GB的传输数据,传输阻塞时长会很长,业务更无法继续。
所以,我们如下设计,这样才时文档区别于关系型数据库的地方,宽表设计,不能去考虑数据库范式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| 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" } } } } } }
|
商城业务-商品上架-nested数据类型场景
https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html
商城业务-商品上架-构造基本数据
创建数据传输对象SkuEsModel
。这边的数据结构是根据所需要存在es中的数据结构定义的。这部分业务逻辑主要在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.aiz.common.to.es;
import lombok.Data; import java.math.BigDecimal; import java.util.List;
@Data public class SkuEsModel { private Long skuId; private Long spuId; private String skuTitle; private BigDecimal skuPrice; 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<Attr> attrs; @Data public static class Attr{ private Long attrId; private String attrName; private String attrValue; } }
|
商城业务-商品上架-构造sku检索属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| @Service("spuInfoService") public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService { @Override public void up(Long spuId) { List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId); List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList()); List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId); List<Long> attrIds = baseAttrs.stream().map(attr -> { return attr.getAttrId(); }).collect(Collectors.toList()); List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds); Set<Long> idSet = new HashSet<>(searchAttrIds);
List<SkuEsModel.Attr> attrs = new ArrayList<>(); List<SkuEsModel.Attr> attrList = baseAttrs.stream().filter(item -> { return idSet.contains(item.getAttrId()); }).map(item -> { SkuEsModel.Attr attrs1 = new SkuEsModel.Attr(); BeanUtils.copyProperties(item, attrs1); return attrs1; }).collect(Collectors.toList());
Map<Long, Boolean> stockMap = null; try{ R<List<SkuHasStockVo>> r = wareFeignService.getSkusHasStock(skuIdList); TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {}; stockMap = r.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock())); }catch (Exception e){ log.error("库存服务查询异常:原因{}",e); }
Map<Long, Boolean> finalStockMap = stockMap; List<SkuEsModel> upProducts = skus.stream().map(sku->{ SkuEsModel esModel = new SkuEsModel(); BeanUtils.copyProperties(sku,esModel); esModel.setSkuPrice(sku.getPrice()); esModel.setSkuImg(sku.getSkuDefaultImg()); if(finalStockMap == null){ esModel.setHasStock(true); }else{ esModel.setHasStock(finalStockMap.get(sku.getSkuId())); }
esModel.setHotScore(0L); BrandEntity brand = brandService.getById(esModel.getBrandId()); esModel.setBrandName(brand.getName()); esModel.setBrandImg(brand.getLogo()); CategoryEntity category = categoryService.getById(esModel.getCatalogId()); esModel.setCatalogName(category.getName());
esModel.setAttrs(attrList);
return esModel; }).collect(Collectors.toList());
R r = searchFeignService.productStatusUp(upProducts); if(r.getCode() == 0){ baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode()); }else{
} }
|
商城业务-商品上架-远程查询库存&泛型结果封装
商城业务-商品上架-远程上架接口
商城业务-商品上架-上架接口调试&feign源码
商城业务-商品上架-抽取响应结果&上架测试完成
分布式应用经常会因为服务不稳定,导致服务不可用。在服务启动第一次的时候,使用商品上架功能后台接口容易超时,多试几次就好了。
关于商品上架这一块业务比较复杂,我在后续会整理一张流程图。
商城业务-首页-整合thymeleaf渲染首页
服务端的页面渲染式开发。
pom.xml(zheli-product)
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
|
application.yml(zheli-product)
1 2 3
| spring: thymeleaf: cache: false
|
目录结构:静态资源放static、html页面放templates。
1 2 3
| resources/ static/ templates/
|
创建用来存放页面跳转的包com.aiz.zhelimall.product.web
。
把controller包改名为app。app包下面存放RESTful接口。web存放所有的controller。
使用模板引擎总结
(一).thymeleaf-starter:关闭缓存
(二).静态资源都放在static文件下就可以按照路径直接访问
(三).页面都放在templates下,直接访问
SpringBoot 访问项目的时候,默认会找index
可以参考WebMvcAutoConfiguration.welcomePageHandlerMapping()这个方法看。就可以理解为什么访问localhost:8000
会转到resources/templates/index.html
IndexController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Controller public class IndexController { @Autowired CategoryService categoryService; @GetMapping({"/","/index.html"}) public String indexPage(Model model){ List<CategoryEntity> categoryEntityList = categoryService.getLeve1Categorys(); model.addAttribute("categorys", categoryEntityList);
return "index"; } }
|
由于我们使用的是thymeleaf模板引擎进行解析,这个类似于JSP,它也有自己的语法,比如${}取值变量等。想了解更多肯定得去官网了解一下。官网
首先给html文件加上thymeleaf的名称空间。
1
| <html lang="en" xmlns:th="http://www.thymeleaf.org">
|
自定义属性和原生属性显示方式。
1 2 3 4 5
| <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>
|
为了页面修改不重启服务器实时更新。引入dev-tools。
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
|
资源编译Build Project (Ctrl+F9)
只对当前资源编译 (Ctrl+Shift+F9)
商城业务-首页-渲染二级三级分类数据
商城业务-nginx-搭建域名访问环境一(反向代理配置)
商城业务-nginx-搭建域名访问环境二(负载均衡到网关)