【項目學習】穀粒商城學習記錄3 - 高級篇
一、ElasticSearch
1、基本概念
- 官方文檔
- ES可以實現快速存儲、搜索和分析海量數據
2、準備工作
(1) ES安裝(docker)
- 看我的另一個博客
(2) ES學習
- 我的感覺是和學習mysql一樣,使用並不難,難的是做一些高進階的東西。現在學的就是查看信息,查詢,保存,修改,刪除,批量操作這類,都是固定指令請求,熟悉就行。檢索很重要,query DSL
- 推薦博客
- 用
postman
去學習很方便
(3) ik分詞安裝 & nginx安裝
(4) springboot整合high-level-client
- 創建新模塊
gulimall-search
- 添加common依賴,配置註冊中心
- 導入相關依賴
<!--導入es的rest high level client--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.4.2</version> </dependency>
- 編寫配置,給容器注入一個
RestHighLevelClient
(以下實現在GulimallElasticSearchConfig配置類中)@Bean public RestHighLevelClient esRestClient() { RestHighLevelClient client = new RestHighLevelClient( RestClient.builder(new HttpHost("101.201.39.44", 20004, "http")) ); return client; }
- 最後參照api使用就行
(5) 增刪改查測試
a. 新增
@Test
public void indexData() throws IOException {
IndexRequest indexRequest = new IndexRequest("users");
indexRequest.id("1");
indexRequest.source("userName", "zhangsan", "age", 18, "gender", "男");
User user = new User();
user.setUserName("zhangsan");
user.setAge(18);
user.setGender("男");
String jsonString = JSON.toJSONString(user);
indexRequest.source(jsonString, XContentType.JSON);
//執行操作
IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//提取有用的響應數據
System.out.println(index);
}
b.查詢
- 所有es的
_search
中的命令,比如match, from, size這些都有接口方法 - 下面例子也使用了聚合
@Test
public void searchData() throws IOException {
//1、創建檢索請求
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices("bank");
//指定DSL,檢索條件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//1.1)、構造檢索條件
sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
System.out.println(sourceBuilder.toString());
searchRequest.source(sourceBuilder);
//2、執行檢索
SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//3、分析結果 searchResponse
System.out.println(searchResponse.toString());
}
3、商城業務-商品上架
(1) sku在es中存儲模型分析
-
我們想要將sku信息保存在es中,首先要考慮如何建索引。有以下兩種考慮方式
- 索引庫設計方案1(推薦,空間換時間):將所有sku可能會用到的信息都放在一個索引內
優點:空間換時間,減少請求次數
缺點:面對一些場景下,如每個sku都有規格參數,會存在大量冗餘存儲。當每個spu下面sku規格都相同時,會存儲多次。{ skuId:1 spuId:11 skyTitile:華爲xx price:999 saleCount:99 attr:[ {尺寸:5}, {CPU:高通945}, {分辨率:全高清} ] }
- 索引庫設計方案2(不推薦,請求太多,傳輸數據量大) :和mysql分表一樣,將冗餘數據分開單獨建立索引
優點:減少空間使用
缺點:由於規格標籤都是動態變化的,每次每個用戶都會發送請求去查詢各種不同數據,導致請求訪問量太大,可能引起網絡阻塞
- 索引庫設計方案1(推薦,空間換時間):將所有sku可能會用到的信息都放在一個索引內
-
最終使用方案一,如下是所使用的建立索引請求:
說明:{"type": "keyword"}, 保持數據精度問題,可以檢索,但不能分詞
{"analyzer", "ik_smart"}, 使用ik中文分詞器
{"index", false}, 不可被檢索,不生成index
{"doc_values", false}, 不可被聚合,es就不會維護一些聚合信息
爲了防止數組扁平化,商品屬性字段設爲nested類型PUT product { "mappings":{ "properties": { "skuId":{ "type": "long" }, #商品sku "spuId":{ "type": "keyword" }, #當前sku所屬的spu。 "skuTitle": { "type": "text", "analyzer": "ik_smart" #只有sku的標題需要被分詞 }, "skuPrice": { "type": "keyword" }, "skuImg" : { "type": "keyword" }, "saleCount":{ "type":"long" }, "hasStock": { "type": "boolean" }, #是否有庫存。在庫存模塊添加此商品庫存後,此字段更爲true "hotScore": { "type": "long" }, "brandId": { "type": "long" }, "catalogId": { "type": "long" }, "brandName": {"type": "keyword"}, "brandImg":{ "type": "keyword", "index": false, #不可被檢索 "doc_values": false #不可被聚合。doc_values默認爲true }, "catalogName": {"type": "keyword" }, "attrs": { "type": "nested", #對象數組防止扁平化,不能用object類型 "properties": { "attrId": {"type": "long" }, "attrName": { "type": "keyword", "index": false, #在後面“商城業務-檢索服務”開發時這裏要去掉 "doc_values": false #在後面“商城業務-檢索服務”開發時這裏要去掉 }, "attrValue": {"type": "keyword" } } } } } }
(2) 實現商品上架服務
實現接口:/product/spuinfo/{spuId}/up
- 整體業務流程:
- 1、查詢當前spu下的sku列表;
- 2、將這個sku列表封裝成sku_es模型類列表;
- 1.給每個sku加上屬性規格列表
- 2.查詢每個sku是否有庫存,要遠程調用庫存模塊
- 3.給每個sku加上熱度、所屬品牌、所屬分類名、所有可被檢索的規格等屬性
- 3、將收集的sku_es模型類列表發給es保存,要遠程調用查詢模塊。
- SpuInfoController
@RequestMapping("/{spuId}/up") public R spuUp(@PathVariable("spuId") Long spuId) { spuInfoService.up(spuId); return R.ok(); }
- 在common模塊中的to包下新建
es
包,保存用於es的相關數據模型,並創建SkuEsModel@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 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; } }
- 接着在SpuInfoServiceImpl的
up
方法中封裝數據,首先查出當前spuid對應的所有sku信息,品牌的名字,並通過遍歷skus將數據封裝成SkuEsModel類型。通過對比SkuInfoEntity和SkuEsModel兩個類,會發現有的屬性兩個同名,這些直接通過BeanUtils.copyProperties賦值。還有一些信息雖然沒有同名,但可以通過單獨set賦值。最後處理其他需要處理的步驟,這裏產生了TODO1 ~ TODO4。 最後TODO5實現上傳。List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId); // TODO 4、查詢當前sku的所有可以被用來檢索的規格屬性 //遍歷封裝sku信息 List<Object> upProducts = skus.stream().map(sku -> { SkuEsModel esModel = new SkuEsModel(); BeanUtils.copyProperties(sku, esModel); esModel.setSkuPrice(sku.getPrice()); esModel.setSkuImg(sku.getSkuDefaultImg()); // TODO 1、發送遠程調用,庫存系統查詢是否有庫存 // TODO 2、熱度評分。0 // TODO 3、查詢品牌和分類的名字信息 return esModel; }).collect(Collectors.toList()); // TODO 5、將數據發送給es進行保存; gulimall-search;
- 接着先實現最容易的TODO3:
// TODO 3、查詢品牌和分類的名字信息 BrandEntity brand = brandService.getById(esModel.getBreandId()); esModel.setBrandName(brand.getName()); esModel.setBrandImg(brand.getLogo()); CategoryEntity category = categoryService.getById(esModel.getCatalogId()); esModel.setCatalogName(category.getName());
- 接着實現TODO4, 首先由於我們要查詢當前sku的所有可以被用來檢索的規格屬性,所以在ProductAttrValueService中實現一個根據spuId獲得所有該spu商品屬性的方法
baseAttrlistForSpu
, 它將返回所有商品屬性類型的數據list。我們還要遍歷封裝成ids//ProductAttrValueServiceImpl中實現 @Override public List<ProductAttrValueEntity> baseAttrlistForSpu(Long spuId) { List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id",spuId)); return entities; }
//up()方法內實現TODO4 // TODO 4、查詢當前sku的所有可以被用來檢索的規格屬性 List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrlistForSpu(spuId); List<Object> attrIds = baseAttrs.stream().map(attr -> { return attr.getAttrId(); }).collect(Collectors.toList());
- 由於上面ids包含了許多不用於search的冗餘屬性id, 所以我們還要在AttrService中實現一個方法
selectSearchAttrIds
,來實現對傳入ids中留下search_type爲1的id。這個方法將在mapping中實現特定的sql語句。//AttrService中 /** * 在指定的所有屬性集合中,調出檢索屬性 * @param attrIds * @return */ List<Long> selectSearchAttrIds(List<Long> attrIds);
//AttrServiceImpl中 @Override public List<Long> selectSearchAttrIds(List<Long> attrIds) { // select attr_id from `pms_attr` where attr_id in(?) and search_type = 1 return baseMapper.selectSearchAttrIds(attrIds); }
//AttrDao List<Long> selectSearchAttrIds(@Param("attrIds") List<Long> attrIds);
//AttrDao.xml <select id="selectSearchAttrIds" resultType="java.lang.Long"> select attr_id from `pms_attr` where attr_id in <foreach collection="attrIds" item="id" separator="," open="(" close=")"> #{id} </foreach> and search_type = 1 </select>
- 注意過濾後,還要轉成內部類SkuEsModel.Attr。完整的TODO4實現:
// TODO 4、查詢當前sku的所有可以被用來檢索的規格屬性 List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrlistForSpu(spuId); List<Long> attrIds = baseAttrs.stream().map(attr -> { return attr.getAttrId(); }).collect(Collectors.toList()); //查出attrIds中用於search的屬性id List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds); Set<Long> idSet = new HashSet<>(searchAttrIds); //留下所有滿足條件的商品屬性list List<SkuEsModel.Attr> attrsList = 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());
- TODO2的熱度可以先設置爲0
// TODO 2、熱度評分。0 esModel.setHotScore(0L);
- 接着實現遠程查詢庫存的功能
- 創建vo,接收方法返回數據 SkugHasStockVo
@Data public class SkugHasStockVo { private Long skuId; private Boolean hasStock; }
- 在庫存模塊的WareSkuController中實現
@PostMapping("/hasstock") public R getSkusHasStock(@RequestBody List<Long> skuIds) { List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds); return R.ok().put("data", vos); }
- 在WareSkuServiceImpl 中具體實現:
@Override public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) { List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> { SkuHasStockVo vo = new SkuHasStockVo(); //查詢當前sku的總庫存量 long count = baseMapper.getSkuStock(skuId); vo.setSkuId(skuId); vo.setHasStock(count > 0); return vo; }).collect(Collectors.toList()); return collect; }
- 其中,getSkuStock()方法是是現在baseMapper的,也就是自己實現的特定sql語句,用於查詢庫存總量-已鎖定量
<select id="getSkuStock" resultType="java.lang.Long"> select sum(stock - stock_locked) from `wms_ware_sku` where sku_id = #{skuId} </select>
- 創建vo,接收方法返回數據 SkugHasStockVo
- 上面在庫存模塊中實現了方法,但是要想在商品模塊使用,得通過遠程調用。在商品模塊下的Feign包下新建
WareFeignService
接口。@FeignClient("gulimall-waer") public interface WareFeignService { @PostMapping("/ware/waresku/hasstock") public R getSkusHasStock(@RequestBody List<Long> skuIds); }
- up()中調用遠程接口查詢庫存量信息,注意這裏修改了公共類中的R,添加了getData()方法。
下面是R的修改// TODO 1、發送遠程調用,庫存系統查詢是否有庫存 Map<Long, Boolean> stockMap = null; try { R skuHasStock = wareFeignService.getSkusHasStock(skuIdList); TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {}; stockMap = skuHasStock.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item-> item.getHasStock())); } catch (Exception e) { log.error("庫存服務查詢異常: 原因{}", e); }
public <T> T getData(String key, TypeReference<T> typeReference) { Object data = get(key);// 默認是map類型,springmvc做的 String jsonStr = JSON.toJSONString(data); T t = JSON.parseObject(jsonStr, (Type) typeReference); return t; } // 利用fastJson進行逆轉 // 這裏要聲明泛型<T>,這個泛型只跟方法有關,跟類無關。 // 例如類上有個泛型,這裏可以使用類上的泛型,就不用聲明 public <T> T getData(TypeReference<T> typeReference) { Object data = get("data");// 默認是map類型,springmvc做的 String jsonStr = JSON.toJSONString(data); T t = JSON.parseObject(jsonStr, typeReference); return t; } public R setData(Object data) { put("data", data); return this; }
- 最後實現遠程上架接口,完成商品上架
- 使用postman 工具,創建請求:
http://es服務ip地址:端口/product
{ "mappings":{ "properties": { "skuId":{ "type": "long" }, "spuId":{ "type": "keyword" }, "skuTitle": { "type": "text", "analyzer": "ik_smart" }, "skuPrice": { "type": "keyword" }, "skuImg" : { "type": "keyword" }, "saleCount":{ "type":"long" }, "hasStock": { "type": "boolean" }, "hotScore": { "type": "long" }, "brandId": { "type": "long" }, "catalogId": { "type": "long" }, "brandName": {"type": "keyword"}, "brandImg":{ "type": "keyword", "index": false, "doc_values": false }, "catalogName": {"type": "keyword" }, "attrs": { "type": "nested", "properties": { "attrId": {"type": "long" }, "attrName": { "type": "keyword", "index": false, "doc_values": false }, "attrValue": {"type": "keyword" } } } } } }
- 執行結果:
- 在common.exception.BizCodeEnume中添加錯誤碼:
PRODUCT_UP_EXCEPTION(11000, "商品上架異常")
- 在search模塊中實現了商品保存
ProductSaveController中:
ProductSaveServiceImpl中:@PostMapping("/product") public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) { boolean status = false; try{ status = productSaveService.productStatusUp(skuEsModels); } catch (IOException e) { return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg()); } if(status) return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg()); else return R.ok(); }
@Autowired RestHighLevelClient restHighLevelClient; @Override public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException { //1.在es中建立索引,建立映射關係, 使用postman工具發送請求實現,請求json在/resources/product-mapping.txt中 //2、在ES中保存數據 //BulkRequest bulkRequest, RequestOptions options BulkRequest bulkRequest = new BulkRequest(); for (SkuEsModel skuEsModel : skuEsModels) { //構造保存請求 IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX); indexRequest.id(skuEsModel.getSkuId().toString()); String jsonString = JSON.toJSONString(skuEsModel); indexRequest.source(jsonString, XContentType.JSON); bulkRequest.add(indexRequest); } BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS); //TODO 如果批量報錯 boolean hasFailures = bulk.hasFailures(); List<String> collect = Arrays.asList(bulk.getItems()).stream().map(item -> { return item.getId(); }).collect(Collectors.toList()); log.info("商品上架完成:{}",collect); return hasFailures; }
- 使用postman 工具,創建請求:
- 爲了能讓product模塊使用上面的接口,需要在Feign包中創建接口
- 創建請求接口
@FeignClient("gulimall-search") public interface SearchFeignService { @PostMapping("/search/save/product") public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels); }
- 由於完成上架後要修改spu狀態,所以在common.constant包下修改ProductConstant。添加關於商品狀態的枚舉。
public enum ProductStatusEnum{ NEW_SPU(0, "新建"), SPU_UP(1, "商品上架"), SPU_DOWN(2, "商品下架"); private int code; private String msg; ProductStatusEnum(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } }
- 在dao中實現
updateSpuStatus
: 修改上架產品的狀態void updateSpuStatus(@Param("spuId") Long spuId, @Param("code") int code);
<update id="updateSpuStatus"> update `pms_spu_info` set publish_status=#{code}, update_time=NOW() where id=#{spuId} </update>
- 創建請求接口
- up方法的全部:
@Override public void up(Long spuId) { //組裝需要的數據 //1、查出當前spuid對應的所有sku信息,品牌的名字 List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId); List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList()); // TODO 4、查詢當前sku的所有可以被用來檢索的規格屬性 List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrlistForSpu(spuId); List<Long> attrIds = baseAttrs.stream().map(attr -> { return attr.getAttrId(); }).collect(Collectors.toList()); //查出attrIds中用於search的屬性id List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds); Set<Long> idSet = new HashSet<>(searchAttrIds); //留下所有滿足條件的商品屬性list List<SkuEsModel.Attr> attrsList = 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()); // TODO 1、發送遠程調用,庫存系統查詢是否有庫存 Map<Long, Boolean> stockMap = null; try { R skuHasStock = wareFeignService.getSkusHasStock(skuIdList); TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {}; stockMap = skuHasStock.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item-> item.getHasStock())); } catch (Exception e) { log.error("庫存服務查詢異常: 原因{}", e); } Map<Long, Boolean> finalStockMap = stockMap; //遍歷封裝sku信息 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())); } // TODO 2、熱度評分。0 esModel.setHotScore(0L); // TODO 3、查詢品牌和分類的名字信息 BrandEntity brand = brandService.getById(sku.getBrandId()); esModel.setBrandName(brand.getName()); esModel.setBrandImg(brand.getLogo()); CategoryEntity category = categoryService.getById(esModel.getCatalogId()); esModel.setCatalogName(category.getName()); // 設置檢索屬性 esModel.setAttrs(attrsList); return esModel; }).collect(Collectors.toList()); // TODO 5、將數據發送給es進行保存; gulimall-search; R r = searchFeignService.productStatusUp(upProducts); if(r.getCode() == 0) { //遠程調用成功 // TODO 6、修改當前spu的狀態,具體實現代碼在SpuInfoDao.xml this.baseMapper.updateSpuStatus(spuId, ProductConstant.ProductStatusEnum.SPU_UP.getCode()); } else { //遠程調用失敗 // TODO 7、重複調用? 接口冪等性? 重試機制? } }
(3) 測試商品上架服務
4、商城業務-首頁
(1) 整合thymeleaf渲染首頁
- 在product模塊的pom.xml文件中導入thymeleaf依賴
<!-- 模板引擎 thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
- 導入前端頁面
- 將index文件夾放在product模塊resources/static目錄下面,將index.html頁面放在resources/templates目錄下面
- control包改名app,並新增web包
- 以後所有rest接口從app包提供,所有controller從web包提供
- web包下的controller是返回給thymeleaf的,它默認的前綴和後綴分別是:
classpath:/templates/
和.html
,springboot訪問項目的時候自動會找index.html
- 進行thymeleaf配置, yml關閉thymeleaf緩存
# 關閉thymeleaf緩存,以便修改代碼後刷新頁面能看出修改 spring: thymeleaf: cache: false
- 啓動服務:
- 注意這裏的端口號是你的product服務端口號
- 注意這裏的端口號是你的product服務端口號
(4) 開發首頁跳轉功能
- 新建gulimall/product/web/IndexController.java
- 創建頁面跳轉方法
indexPage
@Autowired CategoryService categoryService; @GetMapping({"/", "index.html"}) public String indexPage(Model model) { // TODO 1、查出所有一級分類 List<CategoryEntity> categoryEntities = categoryService.getLevel1Categorys(); model.addAttribute("categories", categoryEntities); //視圖解析器進行拼串,前綴classpath:/templates/返回值.html return "index"; //相當於return "classpath:/templates/index.html"; 攔截GetMapping路徑後轉到首頁 }
- getLevel1Categorys()方法具體實現
@Override public List<CategoryEntity> getLevel1Categorys() { return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0)); }
(5) thymeleaf基礎語法,渲染商城首頁一級分類
- 首先,html標籤加上thymeleaf命名空間xmlns:th="http://www.thymeleaf.org", 加上命名空間後就行使用thymeleaf的語法
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 添加熱部署
<!-- 熱部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
- index.html中獲得數據,並通過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> ....
- 測試結果:
(6) 渲染二三級分類數據
- 創建接收數據vo: Catalog2Vo
@NoArgsConstructor @AllArgsConstructor @Data public class Catalog2Vo { private String catalog1Id; // 1級父分類ID private List<Catalog3Vo> catalog3List;// 3級子分類集合 private String id; // 2級分類ID private String name; // 2級分類name /** * 三級分類Vo */ @NoArgsConstructor @AllArgsConstructor @Data public static class Catalog3Vo { private String catalog2Id; // 2級父分類ID private String id; // 3級分類ID private String name; // 3級分類name } }
- 新增接口:
getCatalogJson
- 接口:
/** * 查出三級分類 * 1級分類作爲key, 2級引用List * @return */ @ResponseBody @GetMapping("/index/catelog.json") public Map<String, List<Catalog2Vo>> getCatalogJson() { Map<String, List<Catalog2Vo>> map = categoryService.getCatalogJson(); return map; }
- 具體實現:
private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) { return selectList.stream().filter(item-> item.getParentCid() == parent_cid).collect(Collectors.toList()); } @Override public Map<String, List<Catalog2Vo>> getCatalogJson() { //獲得所有數據 List<CategoryEntity> selectList = baseMapper.selectList(null); //1、 獲得所有一級分類數據 List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L); //2、封裝數據 Map<String, List<Catalog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), level1 -> { //查到當前1級分類的2級分類 List<CategoryEntity> category2level = getParent_cid(selectList, level1.getCatId()); List<Catalog2Vo> catalog2Vos = null; if (category2level != null) { catalog2Vos = category2level.stream().map(level12 -> { //查詢當前二級分類的三級分類 List<CategoryEntity> category3level = getParent_cid(selectList, level12.getCatId()); List<Catalog2Vo.Catalog3Vo> catalog3Vos = null; if (category3level != null) { catalog3Vos = category3level.stream().map(level13 -> { return new Catalog2Vo.Catalog3Vo(level12.getCatId().toString(), level13.getCatId().toString(), level13.getName()); }).collect(Collectors.toList()); } return new Catalog2Vo(level1.getCatId().toString(), catalog3Vos, level12.getCatId().toString(), level12.getName()); }).collect(Collectors.toList()); } return catalog2Vos; })); return collect; }
- 測試結果:
- 接口:
(7) nginx通過域名訪問
- 沒有解決阿里雲服務器內網穿透,老老實實用本地nginx了
- 本地nginx的nginx.conf:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; client_max_body_size 1024m; sendfile on; keepalive_timeout 65; upstream gulimall { server 192.168.56.1:88; } include ./servers/*; }
- severs/gulumall.conf
問題:nginx轉發到網關時會丟掉許多信息,如host信息,但我們網關需要host來判斷轉發到什麼微服務,所以需要一些設置避免host信息丟失
server { listen 80; #監聽此端口 server_name gulimall.com; #監聽此域名 #charset koi8-r; #access_log /var/log/nginx/log/host.access.log main; location / { #配置請求的路由 proxy_set_header Host $host; #坑點:Nginx代理給網關時會丟失請求的host等信息 proxy_pass http://gulimall; #因爲主配置文件配置了上游服務器爲網關地址,所以可以請求路由到http://192.168.xxx.xxx:10000/ } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} }
- 配置網關跳轉信息
- id: gulimall_host_route uri: lb://gulimall-product predicates: - Host=**.gulimall.com
- 本地nginx的nginx.conf:
- 重啓網關,nginx進行測試: