云商城-商品详情页实战
第5章 搜索+商品详情页实战
课程目标
1、搜索页面Thymeleaf渲染实战
1)商品搜索页模板渲染
2)搜索页条件搜索实现
2、商品详情页静态化处理
1)商品详情页静态化
2)Vue+Thymeleaf静态页属性切换
3、静态页实时更新
1)Canal实时监听数据库变更
2)实时更新静态页
1 搜索页面Thymeleaf渲染实战
1.1 Thymeleaf模板引擎
Thymeleaf 是一种模板语言,它包含数据模型(Data)、模板(Template)、模板引擎(Template Engine)和结果文档(Result Documents)。
- 数据模型
数据是信息的表现形式和载体,可以是符号、文字、数字、语音、图像、视频等。数据和信息是不可分离的,数据是信息的表达,信息是数据的内涵。数据本身没有意义,数据只有对实体行为产生影响时才成为信息。 - 模板
模板,是一个蓝图,即一个与类型无关的类。编译器在使用模板时,会根据模板实参对模板进行实例化,得到一个与类型相关的类。 - 模板引擎
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。 - 结果文档
一种特定格式的文档,比如用于网站的模板引擎就会生成一个标准的HTML文档。
Thymeleaf 是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMaker等, 它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比, Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。
1.2 Springboot整合thymeleaf
1)工程创建
在mall-web下创建搜索页面渲染工程mall-search-web坐标如下:
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-search-web</artifactId>
在mall-web中引入如下依赖:
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--Nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
bootstrap.yml:
server:
port: 8085
spring:
application:
name: mall-websearch
cloud:
nacos:
config:
file-extension: yaml
server-addr: 192.168.100.130:8848
discovery:
#Nacos的注册地址
server-addr: 192.168.100.130:8848
thymeleaf:
cache: false
suffix: .html
encoding: UTF-8
prefix: classpath:/templates/
#日志配置
logging:
pattern:
console: "%msg%n"
启动类创建com.gupaoedu.vip.mall.WebSearchApplication:
@SpringBootApplication
public class WebSearchApplication {
public static void main(String[] args) {
SpringApplication.run(WebSearchApplication.class,args);
}
}
2)访问案例
将参考资料中的前台\frant下的所有样式、图片、js拷贝到工程的resources/static目录下,search.html拷贝到resources/templates目录下,如下图:

将search.html中所有相对路径换成绝对路径,也就是将"./"换成"/",把search.html中头部的<html>换成<html xmlns:th="http://www.thymeleaf.org">
创建控制器com.gupaoedu.vip.mall.search.controller.SearchController,代码如下:
@Controller
@RequestMapping(value = "/web/search")
public class SearchController {
/****
* 搜索页面跳转
* @return
*/
@GetMapping
public String search(){
return "search";
}
}
访问http://localhost:8085/web/search效果如下:

1.3 搜索渲染
搜索数据渲染我们需要在mall-search-web中调用mall-search-service,因此需要在mall-search-web引入mall-search-api依赖,并且在com.gupaoedu.vip.mall.search.feign.SkuSearchFeign添加搜索调用方法,代码如下:
/****
* 商品搜索
*/
@GetMapping
RespResult<Map<String,Object>> search(@RequestParam Map<String,Object> searchMap);
mall-search-web依赖引入:
<dependencies>
<!--mall-search-api-->
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-search-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
在启动类上添加@EnableFeignClients注解
@EnableFeignClients(basePackages = "com.gupaoedu.vip.mall.search.feign")
1.3.1 列表数据加载
Thymeleaf语法库:
th:abbr | th:accept | th:accept-charset |
|---|---|---|
th:accesskey | th:action | th:align |
th:alt | th:archive | th:audio |
th:autocomplete | th:axis | th:background |
th:bgcolor | th:border | th:cellpadding |
th:cellspacing | th:challenge | th:charset |
th:cite | th:class | th:classid |
th:codebase | th:codetype | th:cols |
th:colspan | th:compact | th:content |
th:contenteditable | th:contextmenu | th:data |
th:datetime | th:dir | th:draggable |
th:dropzone | th:enctype | th:for |
th:form | th:formaction | th:formenctype |
th:formmethod | th:formtarget | th:fragment |
th:frame | th:frameborder | th:headers |
th:height | th:high | th:href |
th:hreflang | th:hspace | th:http-equiv |
th:icon | th:id | th:inline |
th:keytype | th:kind | th:label |
th:lang | th:list | th:longdesc |
th:low | th:manifest | th:marginheight |
th:marginwidth | th:max | th:maxlength |
th:media | th:method | th:min |
th:name | th:onabort | th:onafterprint |
th:onbeforeprint | th:onbeforeunload | th:onblur |
th:oncanplay | th:oncanplaythrough | th:onchange |
th:onclick | th:oncontextmenu | th:ondblclick |
th:ondrag | th:ondragend | th:ondragenter |
th:ondragleave | th:ondragover | th:ondragstart |
th:ondrop | th:ondurationchange | th:onemptied |
th:onended | th:onerror | th:onfocus |
th:onformchange | th:onforminput | th:onhashchange |
th:oninput | th:oninvalid | th:onkeydown |
th:onkeypress | th:onkeyup | th:onload |
th:onloadeddata | th:onloadedmetadata | th:onloadstart |
th:onmessage | th:onmousedown | th:onmousemove |
th:onmouseout | th:onmouseover | th:onmouseup |
th:onmousewheel | th:onoffline | th:ononline |
th:onpause | th:onplay | th:onplaying |
th:onpopstate | th:onprogress | th:onratechange |
th:onreadystatechange | th:onredo | th:onreset |
th:onresize | th:onscroll | th:onseeked |
th:onseeking | th:onselect | th:onshow |
th:onstalled | th:onstorage | th:onsubmit |
th:onsuspend | th:ontimeupdate | th:onundo |
th:onunload | th:onvolumechange | th:onwaiting |
th:optimum | th:pattern | th:placeholder |
th:poster | th:preload | th:radiogroup |
th:rel | th:rev | th:rows |
th:rowspan | th:rules | th:sandbox |
th:scheme | th:scope | th:scrolling |
th:size | th:sizes | th:span |
th:spellcheck | th:src | th:srclang |
th:standby | th:start | th:step |
th:style | th:summary | th:tabindex |
th:target | th:title | th:type |
th:usemap | th:value | th:valuetype |
th:vspace | th:width | th:wrap |
th:xmlbase | th:xmllang | th:xmlspace |
1.3.1.1 语法说明
数据结果集循环语法:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#using-theach
th:each对象遍历,功能类似jstl中的<c:forEach>标签。
<tr th:each="user,userStat:${users}">
<td>
下标:<span th:text="${userStat.index}"></span>,
</td>
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.address}"></td>
</tr>
th:text:输出指定数据,例如th:text="${user.address}"表示输出user对象中的address属性。
th:src:加载指定图片
1.3.1.2 列表加载
修改search.html商品部分,输出商品列表信息,代码如下:

效果如下:

1.3.2 关键词搜索回显
1.3.2.1 语法说明
th:value给指定表单赋值。
#maps.containsKey(searchMap,'keywords'):判断map对象searchMap中是否包含keywords的key。
三元表达式:th:value="${#maps.containsKey(searchMap,'keywords')}? ${searchMap.keywords}:''"
1.3.2.2 搜索实现
用户输入关键词,后台会用searchMap接收,接收后,前台需要显示,我们可以把searchMap再存入到model中,在页面搜索框中回显搜索条件。
1)搜索条件存储:
修改com.gupaoedu.vip.mall.search.controller.SearchController的search方法,将搜索条件存储到model中:

2)页面搜索框配置
修改search.html的搜索框:

3)展示优化
关键词搜索后,效果如下:

我们只需要把之前商品名字展示标签换成th:utext即可,这样就能识别html标签了。
<a target="_blank" href="item.html" th:utext="${item.name}"></a>
1.3.3 搜索条件回显
1.3.3.1 语法说明
th:if:条件成立,则显示
th:unless:条件不成立则显示
th:each:循环(前面用过)
th:text:文本显示
1.3.3.2 条件回显
1)分类条件回显
分类条件在result.categoryList中,可以直接在页面回显,如果没有该对象,则不回显。

2)品牌回显
品牌条件在result.brandList中,可以直接在页面回显,如果没有该对象,则不回显。

3)规格回显
属性回显需要注意,如果用户没有输入该属性,才回显,如果输入了该属性,则不回显,属性是以attr_开始传入后台。

4)价格回显

页面效果如下:

1.3.4 搜索条件记录

这些条件其实都已经存在在searchMap中了,只需要取出显示即可,但如果是属性回显,就需要把attr_去掉,回显如下:

1.3.5 动态搜索实现
1.5.3.1 语法说明
${#strings.replace(str,x,y)字符串替换成指定的y
th:href:a标签的超链接,th:href="@{${#strings.replace(url,'price='+searchMap.price,'')}}"
1.3.5.2 动态搜索分析

进行搜索的时候,我们可以发现一个规律,选择搜索条件的时候,其实就是将搜索条件作为参数追加到搜索地址后面,移除某个搜索条件的时候,其实就是把搜索参数从搜索路径上移除就可以了。
我们可以在后台定义一个基础的搜索地址/web/search,每次执行搜索的时候,搜索参数会存入到searchMap中,我们可以将searchMap中的参数拼接到基础搜索地址后面作为参数,如果下次增加搜索条件,直接在它后面追加搜索条件即可,如果是减少搜索条件,在它后面移除指定条件即可。
1.3.5.3 动态搜索
1)当前URL生成
用户每次请求,我们需要根据当前提交的搜索条件生成当前的URL地址,在参考资料中提供了UrlUtils,该工具类中有三个方法①将Map参数转成URL的参数②提供baseUrl和Map,组装一个完整的Url③去掉Url中指定参数,代码如下:
public class UrlUtils {
/**
* 去掉URL中指定的参数
*/
public static String replateUrlParameter(String url,String... names){
for (String name : names) {
url = url.replaceAll("(&"+name+"=([0-9\\w]+))|("+name+"=([0-9\\w]+)&)|("+name+"=([0-9\\w]+))", "");
}
return url;
}
/***
* 当前请求地址组装
*/
public static String map2url(String baseUrl,Map<String,Object> searchMap){
//参数获取
String parm = map2parm(searchMap);
if(!StringUtils.isEmpty(parm)){
baseUrl+="?"+parm;
}
return baseUrl;
}
/**
* 将map转换成url参数
* @param map
* @return
*/
public static String map2parm(Map<String, Object> map) {
if (map == null) {
return "";
}
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, Object> entry : map.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue());
sb.append("&");
}
String parameters = sb.toString();
if (parameters.endsWith("&")) {
parameters = StringUtils.substringBeforeLast(parameters ,"&");
}
return parameters;
}
}
我们把该类导入到mall-common中com.gupaoedu.mall.util.UrlUtils,同时添加commons-lang3依赖:
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
修改com.gupaoedu.vip.mall.search.controller.SearchController#search添加当前地址生成调用:

2)页面动态调用
每次动态调用其实就是在当前url上添加指定条件,修改search.html的搜索条件,给每个搜索条件添加url,代码如下:

3)条件移除

条件移除是指每次搜索的条件中如果想移除掉某个条件,我们可以把指定参数从当前url中替换成空即可。修改search.html当前已选中的条件,代码如下:

1.3.5.4 排序实现

无论是哪种排序方式,我们可以直接传递2个参数sfield和sm到后台即可:
综合:不带这2个参数的url
新品:sfield=updateTime,sm=DESC
价格+:sfield=price,sm=ASC
价格-:sfield=price,sm=DESC
我们需要一个不带sfield和sm这两个参数的url,可以在后台生成一个,修改com.gupaoedu.vip.mall.search.controller.SearchController#search,添加一个没有排序参数的url:

页面排序修改:

1.3.5.5 分页
每次分页携带的分页参数是page,而page每次必须和上次不同,所以我们需要将url中上一次搜索的page参数去掉,而且每次需要计算分页,我们可以将参考资料中的PageInfo拷贝到mall-common中,实现对分页的封装。
1)分页封装
在mall-search-service的com.gupaoedu.vip.mall.search.service.impl.SkuSearchServiceImpl#search实现分页,代码如下红色部分:

2)分页url处理
修改com.gupaoedu.vip.mall.search.controller.SearchController#search,代码如下:

3)页面分页处理

2 商品详情页静态化处理
在做网站的时候,为了提升网站数据加载效率同时降低数据库负载,一般会将变更频率较低的数据做特殊处理,比如做成静态页、添加缓存,通常网站门户会这么做。商品详情页访问频率非常高,而且数据变更频率非常低,所以完全可以做成静态页。
静态页生成流程如下图:

2.1 商品详情页静态化
2.1.1 工程搭建
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mall-web</artifactId>
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-page-web</artifactId>
<description>
商品详情页生成
</description>
<dependencies>
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-goods-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
bootstrap.yml
server:
port: 8086
spring:
application:
name: mall-web-page
cloud:
nacos:
config:
file-extension: yaml
server-addr: 192.168.100.130:8848
discovery:
#Nacos的注册地址
server-addr: 192.168.100.130:8848
thymeleaf:
cache: false
encoding: UTF-8
main:
allow-bean-definition-overriding: true
#页面生成存储路径
pagepath: D:/pages/items/
#日志配置
logging:
pattern:
console: "%msg%n"
启动类:com.gupaoedu.vip.mall.MallPageApplication
@SpringBootApplication
public class MallPageApplication {
public static void main(String[] args) {
SpringApplication.run(MallPageApplication.class,args);
}
}
将参考资料中的前台\frant\item.html拷贝到工程templates路径下。
2.1.2 页面数据分析

页面的图片是Spu中的图片集合,标题是当前选中的Sku标题,属性数据来源于Spu中的attribute_list属性,它记录了当前Spu所有Sku的属性集合,如:{"就业薪资":["6K起","10K起"],"学习费用":["1万","2万"]}
我们要想生成静态页,需要同时查询Spu和对应Sku集合以及三级分类。
2.1.3 详情页数据加载
1)分类查询
在mall-goods-service的com.gupaoedu.vip.mall.goods.controller.CategoryController中添加根据ID查询分类信息:
/****
* 根据ID查询
*/
@GetMapping(value = "/{id}")
public RespResult<Category> one(@PathVariable(value = "id")Integer id){
Category category = categoryService.getById(id);
return RespResult.ok(category);
}
在mall-goods-api中创建com.gupaoedu.vip.mall.goods.feign.CategoryFeign:
@FeignClient(value = "mall-goods")
public interface CategoryFeign {
/****
* 根据ID查询
*/
@GetMapping(value = "/category/{id}")
RespResult<Category> one(@PathVariable(value = "id")Integer id);
}
2)Product查询
mall-goods-service的com.gupaoedu.vip.mall.goods.service.SpuService中添加根据SpuId查询Product
//查询商品详情
Product findBySpuId(String id);
mall-goods-service中com.gupaoedu.vip.mall.goods.service.impl.SpuServiceImpl实现查询Product
/***
* 查询商品详情
* @param id
* @return
*/
@Override
public Product findBySpuId(String id) {
//查询Spu
Spu spu = spuMapper.selectById(id);
//查询Sku集合
List<Sku> skus = skuMapper.selectList(new QueryWrapper<Sku>().eq("spu_id",id));
Product product = new Product();
product.setSpu(spu);
product.setSkus(skus);
return product;
}
mall-goods-service的com.gupaoedu.vip.mall.goods.controller.SpuController中调用查询Product
/***
* 根据ID查询
*/
@GetMapping(value = "/product/{id}")
public RespResult<Product> one(@PathVariable(value = "id")String id){
Product product = spuService.findBySpuId(id);
return RespResult.ok(product);
}
在mall-goods-api中创建com.gupaoedu.vip.mall.goods.feign.SpuFeign:
@FeignClient(value = "mall-goods")
public interface SpuFeign {
/***
* 根据ID查询
*/
@GetMapping(value = "/spu/product/{id}")
RespResult<Product> one(@PathVariable(value = "id")String id);
}
2.1.4 静态页生成
1)Service
在mall-page-web创建com.gupaoedu.vip.mall.page.service.PageService,代码如下:
public interface PageService {
//生成静态页
void html(String id) throws FileNotFoundException, UnsupportedEncodingException;
}
在mall-page-web中创建com.gupaoedu.vip.mall.page.service.impl.PageServiceImpl,代码如下:
@Service
public class PageServiceImpl implements PageService {
@Autowired
private SpuFeign spuFeign;
@Autowired
private CategoryFeign categoryFeign;
@Autowired
private TemplateEngine templateEngine;
@Value("${pagepath}")
private String pagepah;
/****
* 生成静态页
* @param id
*/
@Override
public void html(String id) throws FileNotFoundException, UnsupportedEncodingException {
//加载数据
Map<String,Object> dataMap = dataLoad(id);
//创建Thymeleaf容器对象
Context context = new Context();
//设置页面数据模型
context.setVariables(dataMap);
//文件名字 id.html
File dest = new File(pagepah, id + ".html");
PrintWriter writer = new PrintWriter(dest, "UTF-8");
//生成页面
templateEngine.process("item", context, writer);
}
/***
* 数据加载
* @param id
* @return
*/
public Map<String, Object> dataLoad(String id) {
//查询商品数据
RespResult<Product> respProduct = spuFeign.one(id);
Product product = respProduct.getData();
if (product != null) {
//数据模型
Map<String, Object> dataMap = new HashMap<String, Object>();
//Spu
dataMap.put("spu", product.getSpu());
//图片
dataMap.put("images", product.getSpu().getImages().split(","));
//属性
dataMap.put("attrs",JSON.parseObject(product.getSpu().getAttributeList()));
//三级分类查询
RespResult<Category> one = categoryFeign.one(product.getSpu().getCategoryOneId());
RespResult<Category> two = categoryFeign.one(product.getSpu().getCategoryTwoId());
RespResult<Category> three = categoryFeign.one(product.getSpu().getCategoryThreeId());
dataMap.put("one", one.getData());
dataMap.put("two", two.getData());
dataMap.put("three", three.getData());
//Sku集合转JSON
List<Sku> skus = product.getSkus();
List<Map<String, Object>> skuList = new ArrayList<Map<String, Object>>();
for (Sku sku : skus) {
Map<String, Object> skuMap = new HashMap<String, Object>();
skuMap.put("id", sku.getId());
skuMap.put("name", sku.getName());
skuMap.put("price", sku.getPrice());
skuMap.put("attr", sku.getSkuAttribute());
//添加到集合中
skuList.add(skuMap);
}
dataMap.put("skulist", skuList);
return dataMap;
}
return null;
}
}
2)Controller
在mall-page-web中创建com.gupaoedu.vip.mall.page.controller.PageController:
@RestController
@RequestMapping(value = "/page")
public class PageController {
@Autowired
private PageService pageService;
/***
* 生成静态页
*/
@GetMapping(value = "/{id}")
public RespResult html(@PathVariable(value = "id")String id) throws Exception{
pageService.html(id);
return RespResult.ok();
}
}
2.1.5 数据绑定
1)分类面包屑

代码如下:

2)图片列表

3)属性列表

生成静态页效果如下:

2.2 Vue+Thymeleaf静态页属性切换
2.2.1 数据动态切换分析

我们可以把华为商城打开,商品详情如上图,每次点击不同属性时,页面根本没有跳动,其实是静态页已经把静态数据加载好了,每次选择不同产品时,直接从页面指定对象中找对应的数据即可,那么每次是怎么匹配的呢?
我们目前已经加载了Sku集合,Sku集合中有一个属性sku_attribute,它记录了每个Sku的属性集合,用户在页面选择不同Sku组合的时候,其实最终组合起来一定是某个Sku的sku_attribute的值,而且该值不可能重复,所以我们可以利用这个特性来实现对应Sku的查找。

2.2.2 Vue+Thymeleaf数据绑定
1)默认数据
我们将所有Sku集合中第一个商品作为默认Sku,可以点定义一个集合skulist接收所有skulist,再定义一个sku存储当前选中的Sku,定义一个cattr存储当前选中的sku的属性。

上图代码如下:
<script th:inline="javascript">
new Vue({
el: '#app',
data() {
return {
//Sku集合
skulist: [[${skulist}]],
//当前Sku
sku:{},
//当前选中的属性
cattr:{}
}
},
created:function () {
//默认选中第1个sku
this.sku=JSON.parse(JSON.stringify(this.skulist[0]))
//选中的属性
this.cattr =JSON.parse(this.skulist[0].attr)
}
})
</script>
2)选中Sku属性匹配
选中某一个Sku后,我们需要根据用户选择的属性从skulist中所有Sku的attr进行匹配,如果匹配上了,则表示用户选择是该商品,如果匹配失败,表示不是该商品,继续匹配。
我们需要先编写一个方法,实现2个Map对象匹配:

上图代码如下:
//匹配2个map是否相同
sameMap(map1,map2){
//循环第1个map
for(var key in map1){
//匹配当前相同key的值是否相同
if(map1[key]!=map2[key]){
return false;
}
}
return true;
}
3)选中Sku匹配
用户每次选择不同属性,我们把属性存入到当前选中sku的属性cattr中,然后从skulist中进行匹配:
//sku匹配
choosesku(key,value){
//将key和value填充到cattr中
this.$set(this.cattr,key,value)
//循环匹配
for(var i=0;i<this.skulist.length;i++){
//匹配,则返回true
if(this.sameMap(JSON.parse(this.skulist[i].attr),this.cattr)){
this.sku=JSON.parse(JSON.stringify(this.skulist[i]))
return;
}
}
//没有找到,给默认值
this.sku.id=0;
this.sku.name="该商品已下架";
this.sku.price=0;
},
choosesku方法调用:

4)样式切换
样式为class="selected",我们可以写一个方法,将当前的属性名和属性值传入到方法中,在cattr中判断是否存在,如果存在,则表示要选中它,否则不选中。
//样式匹配
ischoose(key,value){
if(this.cattr!=undefined && this.cattr[key]==value){
return true;
}
return false;
},
页面选中样式:

生成静态页,效果如下

注意:如果没有该样式,记得把参考资料中前台\frant的样式、图片、js拷贝到静态页目录下即可。
3 静态页实时同步
3.1 静态页同步分析
静态页同步,我们可以在监听sku表变化,一旦发生变更,可以直接通过feign调用实现静态页生成和删除。
3.2 静态页实时更新
1)Feign
创建mall-page-api工程,并创建Feign接口com.gupaoedu.vip.mall.page.feign.PageFeign实现生成静态页调用,代码如下:
@FeignClient(value = "mall-web-page")
public interface PageFeign {
/***
* 生成静态页
*/
@GetMapping(value = "/page/{id}")
RespResult html(@PathVariable(value = "id")String id) throws Exception;
}
2)Canal监听调用
在mall-canal-service中监听调用,修改com.gupaoedu.vip.canal.listener.SkuHandler的add和update方法,增加feign接口调用,代码如下:

上图代码如下:
@Component
public class SkuHandler implements EntryHandler<Sku>{
@Autowired
private SkuSearchFeign skuSearchFeign;
@Autowired
private PageFeign pageFeign;
/***
* 增加产品
* @param sku
*/
@SneakyThrows
@Override
public void insert(Sku sku) {
if(sku.getStatus().intValue()==1){
//导入索引
skuSearchFeign.add(JSON.parseObject(JSON.toJSONString(sku), SkuEs.class));
}
//生成静态页
pageFeign.html(sku.getSpuId());
}
/***
* 修改
* @param before
* @param after
*/
@SneakyThrows
@Override
public void update(Sku before, Sku after) {
if(after.getStatus().intValue()==2){
//导入索引
skuSearchFeign.del(after.getId());
}else{
skuSearchFeign.add(JSON.parseObject(JSON.toJSONString(after), SkuEs.class));
}
//生成静态页
pageFeign.html(after.getSpuId());
}
/***
*
* @param sku
*/
@Override
public void delete(Sku sku) {
skuSearchFeign.del(sku.getId());
}
}
不要忘记在主方法上添加@EnableFeignClients
@EnableFeignClients(basePackages = {"com.gupaoedu.vip.mall.goods.feign","com.gupaoedu.vip.mall.search.feign","com.gupaoedu.vip.mall.page.feign"})
以后但凡出现如下错误,都不要过度担心,这种情况都是对象名字的重复,整个工程里即便null也是一个重复对象,这种情况我们允许覆盖重复名字即可。

在bootstrap.yml中添加如下配置:
main:
allow-bean-definition-overriding: true