【摘要】商城业务 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-搭建域名访问环境二(负载均衡到网关)