目录
- 1 商城业务
-
- 1.1 商品上架
- 1.2 商城系统首页
- 1.3 nginx 搭建域名访问环境
- 1.4 缓存
- 1.5 商品检索
- 1.6 商品详情
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" }
}
}
}
}
}
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
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 代码实现
- 接口文档
- 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)先查库存(商品微服务远程调用库存微服务)
- 在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下的总库存量
- 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);
}
- 将 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;
}
...
2)商品上架,保存到es中(商品微服务远程调用搜索微服务)
- 搜索微服务下创建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;
}
}
-
-
**RestHighLevelClient ** eslaticsearch和springboot整合中我们就使用的这个
- 搜索微服务下创建constant.EsConstant
package com.atguigu.gulimall.search.constant;
public class EsConstant {
public static final String PRODUCT_INDEX = "product"; //sku数据在 es中的索引
}
- 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);
}
- 上架失败返回R.error(错误码,消息)
此时再定义一个错误码枚举。在接收端获取他返回的状态码
- BizCodeEnume(common微服务下的exception:专门存放设置错误码)
- 注意枚举类如果新增一个,必须要将
;
改为,
package com.atguigu.common.exception;
PRODUCT_UP_EXCEPTION(11000,"商品上架异常");
- 点击上架后再让数据库中状态变为上架状态
- 这里在 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>
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 …
- 解决办法:(来自网络)
我的错误就是没有在dao或者接口中加入@Param这个注解。找不到对应的属性名。 加入注解即可。
6)效果展示
商品成功上架,显示状态 为 已上架
1.2 商城系统首页
前面的分布式基础我们使用的是前后端分离,即前端使用vue进行开发,后端就只做后端代码。但是从分布式高级开始,我们开始使用动静分离的方式。
- 对于以前的代码,我们可以将分布式基础中关于rest风格的,对接app操作的,前后端分离的包从controller改为app.将对接页面的controller,我们放到web中
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文件夹下。
3)、关闭thymeleaf缓存
我们在application.yml中关闭thymeleaf缓存,方便我们在更新网页资源的时候可以实时更新。
4)、访问首页
localhost:11000
5)、踩坑
这里记录一个坑:
按照老师的设置之后启动访问首页,一直显示访问不了。也不是什么端口问题,后面发现是缓存的问题。
解决办法:删除 target目录,重启商品微服务。
1.2.2 整合dev-tools渲染一级分类
1)、编写IndexController
-
我们现在访问的首页的数据都是写死的,我们对于一级分类数据需要从数据库中查出。
在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数据进行格式化,如下图。
- 删除json,我们自己编写
2)、代码实现
- 修改js文件夹下的catalogLoader.js,改成如下图所示,到时候我们向
index/catalog.json
发送请求查数据
- 根据上面的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;
}
}
- indexcontroller中编写
//index/catalog.json
@ResponseBody
@GetMapping("/index/catalog.json")
public Map<String, List<Catelog2Vo>> getCatalogJson() {
Map<String, List<Catelog2Vo>> catalogJson = categoryService.getCatalogJson();
return catalogJson;
}
- CategoryService
Map<String, List<Catelog2Vo>> getCatalogJson();
- 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()