谷粒商城项目之高级篇笔记(一)

本文详细介绍了谷粒商城项目中的商品上架流程,包括商品Mapping、建立商品索引、上架细节、数据一致性及代码实现。同时,讲解了商城系统首页的Thymeleaf整合、DevTools使用,以及通过Nginx实现域名访问环境。此外,还涉及了缓存管理,包括Redis的使用、分布式锁和缓存一致性问题。最后,讨论了商品检索的业务分析、DSL语句、服务构建和页面渲染等关键步骤。
摘要由CSDN通过智能技术生成

目录

1 商城业务

1.1 商品上架

需求:

  • 上架的商品才可以在网站展示。
  • 上架的商品需要可以被检索。

1.1.1 商品Mapping

商品mapping

分析:商品上架在es中是存sku还是spu?

1)、检索的时候输入名字,是需要按照sku的title进行全文检索的
2)、检索使用商品规格,规格是spu的公共属性,每个spu是一样的
3)、按照分类id进去的都是直接列出spu的,还可以切换。
4〕、我们如果将sku的全量信息保存到es中(包括spu属性〕就太多字段了

选取如下方案:

方案1:方便检索   
{
   
    skuId:1
    spuId:11
    skyTitile:华为xx
    price:999
    saleCount:99
    attr:[
        {
   尺寸:5},
        {
   CPU:高通945},
        {
   分辨率:全高清}
	]
}
缺点:如果每个sku都存储规格参数(如尺寸),会有冗余存储,因为每个spu对应的sku的规格参数都一样
冗余:
举例:100*20=2000MB=2G

方案2:分布式   
sku索引
{
   
    spuId:1
    skuId:11
    xxx
}
attr索引
{
   
    skuId:11
    attr:[
        {
   尺寸:5},
        {
   CPU:高通945},
        {
   分辨率:全高清}
	]
}

举例:
先找到4000个符合要求的spu,再根据4000个spu查询对应的属性,封装了4000个id,long 8B*4000=32000B=32KB
1K个人检索,就是32MB

结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络拥堵
因此选用方案1,以空间换时间

1.1.2 建立product索引

  • { “type”: “keyword” }, # 保持数据精度问题,可以检索,但不分词
  • “analyzer”: “ik_smart” # 中文分词器
  • “index”: false, # 不可被检索,不生成index
  • “doc_values”: false # 默认为true,设置为false,表示不可以做排序、聚合以及脚本操作,这样更节省磁盘空间。还可以通过设定doc_values为true,index为false来让字段不能被搜索但可以用于排序、聚合以及脚本操作
PUT 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,  # 不可被检索,不生成index
                "doc_values": false # 不可被聚合
            },
            "catalogName": {
   "type": "keyword" },
            "attrs": {
    # attrs:当前sku的属性规格
                "type": "nested",
                "properties": {
   
                    "attrId": {
   "type": "long"  },
                    "attrName": {
   
                        "type": "keyword",
                        "index": false,
                        "doc_values": false
                    },
                    "attrValue": {
   "type": "keyword" }
                }
            }
        }
    }
}

1669197427060

nested嵌入式对象

属性是"type": “nested”,因为是内部的属性进行检索

数组类型的对象会被扁平化处理(对象的每个属性会分别存储到一起)

user.name=["aaa","bbb"]
user.addr=["ccc","ddd"]

这种存储方式,可能会发生如下错误:
错误检索到{
   aaa,ddd},这个组合是不存在的

数组的扁平化处理会使检索能检索到本身不存在的,为了解决这个问题,就采用了嵌入式属性,数组里是对象时用嵌入式属性(不是对象无需用嵌入式属性)

nested阅读:https://blog.csdn.net/weixin_40341116/article/details/80778599

使用聚合:https://blog.csdn.net/kabike/article/details/101460578

1669204852792

1.1.3 上架细节

上架是将后台的商品放在es 中可以提供检索和查询功能。
1)、hasStock:代表是否有库存。默认上架的商品都有库存。如果库存无货的时候才需要
更新一下es
2)、库存补上以后,也需要重新更新一下es
3)、hotScore 是热度值,我们只模拟使用点击率更新热度。点击率增加到一定程度才更新
热度值。
4)、下架就是从es 中移除检索项,以及修改mysql 状态

商品上架步骤:
1)、先在es 中按照之前的mapping 信息,建立product 索引。
2)、点击上架,查询出所有sku 的信息,保存到es 中
3)、es 保存成功返回,更新数据库的上架状态信息。

1.1.4 数据一致性

1)、商品无库存的时候需要更新es 的库存信息
2)、商品有库存也要更新es 的信息

1.1.5 代码实现

  • 接口文档

image-20221123222707831

  • SpuInfoController:
 /**
     * /product/spuinfo/{spuId}/up
     * 商品上架功能
     */
    @PostMapping("/{spuId}/up")
    public R spuUp(@PathVariable("spuId") Long spuId){
   
        spuInfoService.up(spuId);

        return R.ok();
    }

product微服务里组装好,search微服务里保存到es中,进行商品上架

  • 商品上架entity SkuEsMode

商品上架需要在es中保存spu信息并更新spu的状态信息,由于SpuInfoEntity与索引的数据模型并不对应,所以我们要建立专门的vo进行数据传输。

package com.atguigu.common.to.es;

//商品在 es中保存的数据模型
@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<Attrs> attrs; 


    @Data
    public static class Attrs {
   

        private Long attrId;
        private String attrName;
        private String attrValue;
    }

}
  • 商品上架service
    • spu下的sku的规格参数相同,因此我们要将查询规格参数提前,只查询一次
    • spu和sku:spu是款,sku是件。spu > sku.
1)先查库存(商品微服务远程调用库存微服务)
  1. 在ware库存微服务里添加"查询sku是否有库存"的controller
  • WareSkuController
package com.atguigu.gulimall.ware.controller;

/**
     * 查询sku是否有库存
     */
    @PostMapping("/hasstock")
    public R getSkuHasStock(@RequestBody List<Long> skuIds){
   
        //sku_id,stock --- 引出创建SkuHasStockVo
        List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(skuIds);
        return R.ok().setData(vos);
    }
  • 在ware微服务中的vo包下面新建 SkuHasStockVo (方便查询库存) (后面product微服务也会用到,所以在后面会将其复制到common微服务中)
package com.atguigu.gulimall.ware.vo;

@Data
public class SkuHasStockVo {
   

    private Long skuId;

    private Boolean hasStock;

}
  • WareSkuServiceImpl (查出当前商城下面的所有以sku_id和库存量为集合的数据)
package com.atguigu.gulimall.ware.service.impl;

@Override
    public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {
   

        List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
   
            SkuHasStockVo vo = new SkuHasStockVo();

            //查询当前 sku的总库存量
            //SELECT SUM(stock-stock_locked) FROM `wms_ware_sku` WHERE sku_id = 1
            Long count = baseMapper.getSkuStock(skuId); 

            vo.setSkuId(skuId);
            vo.setHasStock(count==null?false:count>0);
            return vo;
        }).collect(Collectors.toList());
        return collect;
    }
  • SELECT SUM(stock-stock_locked) FROM `wms_ware_sku` WHERE sku_id = 1查询华为这个sku下的总库存量

image-20221126145854208

image-20221126145925505

  • WareSkuDao (查库存)
package com.atguigu.gulimall.ware.dao;


@Mapper
public interface WareSkuDao extends BaseMapper<WareSkuEntity> {
   


    Long getSkuStock(Long skuId);//一个参数的话,可以不用写@Param,多个参数一定要写,方便区分
}
  • WareSkuDao.xml
 <select id="getSkuStock" resultType="java.lang.Long">
        SELECT SUM(stock-stock_locked) FROM `wms_ware_sku` WHERE  sku_id=#{skuId}     #动态获取
    </select>
  • 接着我们在商品微服务调用库存微服务的查询库存总量的方法来查询商品的库存总量

在 package com.atguigu.gulimall.product.feign下:

package com.atguigu.gulimall.product.feign;

@FeignClient("gulimall-ware") //调用库存微服务
public interface WareFeignService {
   

    /**
     * 1、R设计的时候可以加上泛型
     * 2、直接返回我们想要的结果
     * 3、自己封装解析结果
     * @param skuIds
     * @return
     */
    @PostMapping("/ware/waresku/hasstock")//注意路径复制完全
    R getSkuHasStock(@RequestBody List<Long> skuIds);
}
  1. 将 R 工具类进行改装,查完库存后直接返回封装好的数据(debug检查错误后最终选择自己封装解析结果) — 解决 return R.ok().setData(vos)
package com.atguigu.common.utils;

public class R extends HashMap<String, Object> {
   
	private static final long serialVersionUID = 1L;


    /**
    		加入以下代码
    */
    
	//利用 阿里巴巴提供的fastjson 进行逆转
	public <T> T getData(TypeReference<T> typeReference){
   
        /**
        public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    	}
    	
    	//默认是HashMap.Node<k,v> e
        */
		Object data = get("data");
        
		String s = JSON.toJSONString(data);
        
        /**
        
           String jsonStr = "[{\"id\":1001,\"name\":\"Jobs\"}]";
       		List<Model> models = JSON.parseObject(jsonStr, new TypeReference<List<Model>>() {});
       
            Params:
            text – json string
            type – type refernce
            features –
            Returns:

        */
		T t = JSON.parseObject(s, typeReference);
		return t;
	}

	public R setData(Object data){
   
        //将数据data和data进行映射  
		put("data",data);
		return this;
	}
...

image-20221126153824044

2)商品上架,保存到es中(商品微服务远程调用搜索微服务)
  1. 搜索微服务下创建controller.ElasticSaveController
package com.atguigu.gulimall.search.controller;


@Slf4j
@RequestMapping("/search/save")
@RestController
public class ElasticSaveController {
   

    @Autowired
    ProductSaveService productSaveService;

    //上架商品
    // 添加@RequestBody 将 请求体中的 List<SkuEsModel> 集合转换为json数据,因此请求方式必须为  @PostMapping   
    // GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。
    @PostMapping("/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels){
   


        // 如果返回的是 boolean 类型的false,说明我们的 sku数据有问题
        //如果返回的是 catch里面的内容,可能是 es 客户端连接不上问题
        boolean b = false;
        try {
   
            b = productSaveService.productStatusUp(skuEsModels);
        }catch (Exception e){
   
            log.error("ElasticSaveController商品上架错误: {}",e);
            return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
        }

        if (!b){
   
            return R.ok();
        }else {
   
            return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
        }

    }
}

  • 搜索微服务下创建service和impl
  • ProductSaveService
package com.atguigu.gulimall.search.service;

public interface ProductSaveService {
   
    boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException;
}

  • ProductSaveServiceImpl
package com.atguigu.gulimall.search.service.impl;

@Slf4j
@Service("productSaveService")
public class ProductSaveServiceImpl implements ProductSaveService {
   

    @Autowired
    RestHighLevelClient restHighLevelClient;    //eslaticsearch和springboot整合中我们就使用的这个

    @Override
    public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
   

        //保存到es
        //1.给 es 中建立索引。product,建立好映射关系。 (提前使用kibana为商品建立索引及映射)

        //2.给 es 中保存这些数据
        //bulkRequest   用来批量处理请求
        BulkRequest bulkRequest = new BulkRequest();
        for (SkuEsModel model : skuEsModels) {
   
            //1.构造保存请求   index在eslaticsearch中是保存操作
            /**
             public IndexRequest(String index) {
        		super(NO_SHARD_ID);
        		this.index = index;
    		}
            */
            IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
			//设置索引文档的id
            indexRequest.id(model.getSkuId().toString());
            String s = JSON.toJSONString(model);
			/**
			public IndexRequest source(String source, XContentType xContentType) {
        			return source(new BytesArray(source), xContentType);
    		}
			*/
            indexRequest.source(s, XContentType.JSON);

			//批量操作中添加index请求
            bulkRequest.add(indexRequest);
        }
        //BulkRequest bulkRequest, RequestOptions options
		//批量执行的响应
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        //TODO 1、如果批量错误
        boolean b = bulk.hasFailures();
        List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
   
            return item.getId();
        }).collect(Collectors.toList());

        log.info("商品上架完成:{},返回数据:{}",collect,bulk.toString());

        return b;
    }
}
  • image-20221126155317098

  • **RestHighLevelClient ** eslaticsearch和springboot整合中我们就使用的这个

image-20221126154959263

  • 搜索微服务下创建constant.EsConstant
package com.atguigu.gulimall.search.constant;

public class EsConstant {
   

    public static final String PRODUCT_INDEX = "product";  //sku数据在 es中的索引

}

  1. fenign 调用: gulimall-product 调用 gulimall-search
  • 商品微服务下SearchFeignService
package com.atguigu.gulimall.product.feign;

@FeignClient("gulimall-search")
public interface SearchFeignService {
   

    @PostMapping("/search/save/product")
    R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}
  1. 上架失败返回R.error(错误码,消息)

此时再定义一个错误码枚举。在接收端获取他返回的状态码

  • BizCodeEnume(common微服务下的exception:专门存放设置错误码)
  • 注意枚举类如果新增一个,必须要将改为
 package com.atguigu.common.exception;

PRODUCT_UP_EXCEPTION(11000,"商品上架异常");
  1. 点击上架后再让数据库中状态变为上架状态
  • 这里在 gulimall-common包下的constant.ProductConstant 类中创建一个新的枚举类(复制里面有的类,稍作修改即可)
package com.atguigu.common.constant;

public class ProductConstant {
   
    ...
	public enum StatusEnum {
   
        NEW_SPU(0,"新建"), SPU_UP(1,"商品上架"),SPU_DOWN(2,"商品下架");
        private int code;
        private String msg;

        StatusEnum(int code, String msg) {
   
            this.code = code;
            this.msg = msg;
        }

        public int getCode() {
   
            return code;
        }

        public String getMsg() {
   
            return msg;
        }
    }
}
3) 商品微服务中商品上架总代码
  • SpuInfoController
package com.atguigu.gulimall.product.controller;

/**
     * /product/spuinfo/{spuId}/up
     * 商品上架功能
     */
    @PostMapping("/{spuId}/up")
    public R spuUp(@PathVariable("spuId") Long spuId){
   
        spuInfoService.up(spuId);

        return R.ok();
    }
  • SpuInfoServiceImpl
 package com.atguigu.gulimall.product.service.impl;

@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的所有可以用来被检索的规格属性,
        //SkuInfoEntity    --- pms_product_attr_value   spu属性值表
        List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);
        List<Long> attrIds = baseAttrs.stream().map(attr -> {
    //返回所有属性的id
            return attr.getAttrId();
        }).collect(Collectors.toList());

        List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);

        Set<Long> idSet = new HashSet<>(searchAttrIds);//因为是kv 键值对,转换成 set 集合比较方便

        // 从  baseAttrs 集合中 过滤 出  attrValueEntities 集合
        List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {
   
            return idSet.contains(item.getAttrId());
        }).map(item -> {
    
            //将 set集合 映射 成  map集合
            SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
            BeanUtils.copyProperties(item, attrs);//属性对拷:item 是数据库中查出来的数据
            return attrs;
        }).collect(Collectors.toList());

        //TODO 1、发送远程调用,库存系统查询是否有库存
        //由于远程调用可能出现网络问题,所以需要进行try  - catch处理一下
        Map<Long, Boolean> stockMap = null;
        try {
   
            R r = wareFeignService.getSkuHasStock(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);
        }


        //2.封装每个sku的信息
        Map<Long, Boolean> finalStockMap = stockMap;
        List<SkuEsModel> upProducts = skus.stream().map(sku -> {
      //通过  stream API 将 skus中的 数据遍历
            //组装我们需要的数据
            SkuEsModel esModel = new SkuEsModel();
            BeanUtils.copyProperties(sku, esModel);//属性对拷,将 sku中的属性 拷贝到 esmodel中

            //需要单独处理的数据 ,SkuInfoEntity SkuEsModel中相比SkuInfoEntity少的数据。
            //skuPrice,skuImg

            esModel.setSkuPrice(sku.getPrice());
            esModel.setSkuImg(sku.getSkuDefaultImg());

            //hotScore(热度评分)  hasStock(库存)

            //设置库存信息
            //如果远程调用出现问题,默认给 true值;如果没有问题,那就赋真正的值  为了不影响正常调用
            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(esModel.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的状态
            baseMapper.updataSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
        }else {
   
            //远程调用失败
            //TODO  7、重复调用?接口幂等性;重试机制? xxx
            
            //Feign 调用流程原理
            /**
             * 1.构造请求数据,将对象转为json;
             * RequestTemplate template = buildTemplateFromArgs.create(argv);
             * 2.发送请求进行执行(执行成功会解码响应数据);
             * executeAndDecode(template)'
             * 3.执行请求会有重试机制
             * while(true){
             *    try{
             *      executeAndDecode(template);
             *    }catch(){
             *       try{ retryer.continueOrPropagate(e);}catch(){throw ex;
             *       continue;
             *          }
             *    }
             *
             */

        }

    }
  • AttrService
 package com.atguigu.gulimall.product.service;

List<Long> selectSearchAttrIds(@Param("attrIds") List<Long> attrIds);   //加这个@Param,否则会出现
  • AttrServiceImpl
package com.atguigu.gulimall.product.service;

@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(查询出哪些规格属性可以被检索)
package com.atguigu.gulimall.product.dao;

 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>
  • SpuInfoDao (更新库存状态)
package com.atguigu.gulimall.product.dao;

@Mapper
public interface SpuInfoDao extends BaseMapper<SpuInfoEntity> {
   

void updataSpuStatus(@Param("spuId") Long spuId, @Param("code") int code);
}
  • SpuInfoDao.xml
<update id="updataSpuStatus">
        UPDATE `pms_spu_info` SET publish_status =#{code},update_time=NOW() WHERE id =#{spuId}
    </update>

1669257801032

4)上架中调用的两个远程微服务

gulimall-product 调用 gulimall-search 将 商品上架内容保存在 ElasticSearch中,方便全文检索:

SearchFeignService

@FeignClient("gulimall-search")
public interface SearchFeignService {
   

    @PostMapping("/search/save/product")
    R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}

gulimall-product 调用 gulimall-ware 将 查询 商品库存:

WareFeignService

@FeignClient("gulimall-ware") //说明调用哪一个 远程服务
public interface WareFeignService {
   

    /**
     * 1、R设计的时候可以加上泛型
     * 2、直接返回我们想要的结果
     * 3、自己封装解析结果
     * @param skuIds
     * @return
     */
    @PostMapping("/ware/waresku/hasstock")//注意路径复制完全
    R getSkuHasStock(@RequestBody List<Long> skuIds);
}
5)踩坑

报错:

nested exception is org.apache.ibatis.binding.BindingException: Parameter ‘attrIds’ not found. Avail …

  • 解决办法:(来自网络)

image-20221125225717486

image-20221125225745388

我的错误就是没有在dao或者接口中加入@Param这个注解。找不到对应的属性名。 加入注解即可。

image-20221125225845075

6)效果展示

商品成功上架,显示状态 为 已上架

1669277105482


1.2 商城系统首页

image-20221127102330789

前面的分布式基础我们使用的是前后端分离,即前端使用vue进行开发,后端就只做后端代码。但是从分布式高级开始,我们开始使用动静分离的方式。

  • 对于以前的代码,我们可以将分布式基础中关于rest风格的,对接app操作的,前后端分离的包从controller改为app.将对接页面的controller,我们放到web中

image-20221126191400799

1.2.1 整合thymeleaf渲染首页

1)、导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

将thymeleaf模板引擎导入商品微服务中

2)、资源存放

将老师给的课件中有关首页的资源放到gulimall-product包下的resources下。index文件夹放到static静态资源文件夹下,index.html放到templates文件夹下。

image-20221127135102126

3)、关闭thymeleaf缓存

我们在application.yml中关闭thymeleaf缓存,方便我们在更新网页资源的时候可以实时更新。

image-20221127135450142

4)、访问首页

localhost:11000

image-20221126194306527

5)、踩坑

这里记录一个坑:

按照老师的设置之后启动访问首页,一直显示访问不了。也不是什么端口问题,后面发现是缓存的问题。

解决办法:删除 target目录,重启商品微服务。

image-20221126194122099

1.2.2 整合dev-tools渲染一级分类

1)、编写IndexController
  1. 我们现在访问的首页的数据都是写死的,我们对于一级分类数据需要从数据库中查出。

    在web包下编写一个IndexController类,实现我们访问localhost:11000/ 和localhost:11000/index.html都可以跳转到首页。

package com.atguigu.gulimall.product.web;
/**
 * @author hxld
 * @create 2022-11-26 20:55
 */
@Controller
public class IndexController {
   

    @Autowired
    CategoryService categoryService;

    @GetMapping({
   "/","/index.html"})
    public String indexPage(Model model){
   
        //TODO 1.查出所有的1级分类
        List<CategoryEntity> categoryEntities = categoryService.getLevel1Categorys();

        model.addAttribute("categorys",categoryEntities);
        return "index";
    }
}

2)、service和serviceImpl
  • CategoryService

    package com.atguigu.gulimall.product.service;
    
    List<CategoryEntity> getLevel1Categorys();
    
  • CategoryServiceImpl

package com.atguigu.gulimall.product.service.impl;

@Override
    public List<CategoryEntity> getLevel1Categorys() {
   
        //parent_cid=0 或者 cat_level=1 都表示是一级分类。这里我们选用parent_cid=0
        List<CategoryEntity> entities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return entities;
    }
3) 、引入dev-tools

对于商城首页的编写,我们每次修改之后都希望不重启微服务就可以实时查看是否修改成功。所以我们可以引入热部署工具。

当然对于简单的页面修改我们可以直接ctrl+shift+f9。但是对于一些类或者方法的修改我们还是建议重启微服务,因为可以避免一些不必要的错误。

 <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
           <optional>true</optional>    <!--这个才相当于将工具真正的导入进来了-->
        </dependency>
模板引擎
 1)、thymeleaf-stater:关闭缓存
 2)、静态资源都放在static文件夹下就可以按照路径直接访问
 3)、页面放在templates下,直接访问
 4)、页面修改不重启服务器实时更新
     1)、引入dev-tools
     2)、修改网页面  ctrl+shift+f9重新自动编译下页面,代码配置,推荐重启微服务
4) 、修改index.html
    <!--轮播主体内容-->
    <div class="header_main">
      <div class="header_banner">
        <div class="header_main_left">
          <ul>
            <li th:each="category : ${categorys}">
              <!--th:attr="ctg-data=${category.catId}"  自定义属性写法-->
              <a href="#" class="header_main_left_a"  th:attr="ctg-data=${category.catId}"><b th:text="${category.name}">家用电器 </b></a>
            </li>
          </ul>

将写死的内容给删除掉,使用thymeleaf语法进行编写,动态获取数据。

1.2.3 渲染二、三级分类数据

1)、删除静态资源json数据

前面的二、三级分类的数据都是写在index.json文件夹下的catalog.json中,是写死的。我们将这些json数据进行格式化,如下图。

image-20221127143339918

image-20221126213250929

  • 删除json,我们自己编写

image-20221126214211576

2)、代码实现
  1. 修改js文件夹下的catalogLoader.js,改成如下图所示,到时候我们向index/catalog.json发送请求查数据

image-20221127143642938

  1. 根据上面的json解析的数据新建Catelog2Vo
package com.atguigu.gulimall.product.vo;


/**
 * @author hxld
 * @create 2022-11-26 21:36
 */

//2级分类vo
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Catelog2Vo {
   
    private String catalog1Id;//1级分类id
    private List<Catelog3Vo> catalog3List; //三级子分类
    private String id;
    private String name;


    /**
     * 三级分类 vo
     * "catalog2Id":"61",
     * "id":"610",
     * "name":"商务休闲鞋"
     */
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public static class Catelog3Vo {
   
        private String catalog2Id; //父分类,2级分类 id
        private String id;
        private String name;

    }


}
  1. indexcontroller中编写
//index/catalog.json
    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
   

        Map<String, List<Catelog2Vo>> catalogJson = categoryService.getCatalogJson();

        return catalogJson;
    }

  1. CategoryService
Map<String, List<Catelog2Vo>> getCatalogJson();
  1. CategoryServiceImpl
@Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
   

        //1.查出所有1级分类
        List<CategoryEntity> level1Categorys = getLevel1Categorys();

        //2.封装数据
        Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
   
            //1.每一个的一级分类,查到这个一级分类的二级分类
            List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getParentCid()));
            //2.封装上面的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
   
                catelog2Vos = categoryEntities.stream()
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值