【項目學習】穀粒商城學習記錄3 - 高級篇

【項目學習】穀粒商城學習記錄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分表一樣,將冗餘數據分開單獨建立索引
      優點:減少空間使用
      缺點:由於規格標籤都是動態變化的,每次每個用戶都會發送請求去查詢各種不同數據,導致請求訪問量太大,可能引起網絡阻塞
  • 最終使用方案一,如下是所使用的建立索引請求:

    說明:{"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>
      
  • 上面在庫存模塊中實現了方法,但是要想在商品模塊使用,得通過遠程調用。在商品模塊下的Feign包下新建WareFeignService接口。
    @FeignClient("gulimall-waer")
    public interface WareFeignService {
        @PostMapping("/ware/waresku/hasstock")
        public R getSkusHasStock(@RequestBody List<Long> skuIds);
    }
    
  • up()中調用遠程接口查詢庫存量信息,注意這裏修改了公共類中的R,添加了getData()方法。
    // 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);
    }
    
    下面是R的修改
    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中:
      @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();
      }
      
      ProductSaveServiceImpl中:
      @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;
      
      }
      
  • 爲了能讓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服務端口號

(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進行測試:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章