第8章 Thymeleaf

学习目标

  • Thymeleaf的介绍
  • Thymeleaf的入门
  • Thymeleaf的语法及标签
  • 搜索页面渲染
  • 商品详情页静态化功能实现

1.Thymeleaf介绍

​ thymeleaf是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández创建,该作者还是Java加密库Jasypt的作者。

Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。

它的特点便是:开箱即用,Thymeleaf允许您处理六种模板,每种模板称为模板模式:

  • XML
  • 有效的XML
  • XHTML
  • 有效的XHTML
  • HTML5
  • 旧版HTML5

所有这些模式都指的是格式良好的XML文件,但Legacy HTML5模式除外,它允许您处理HTML5文件,其中包含独立(非关闭)标记,没有值的标记属性或不在引号之间写入的标记属性。为了在这种特定模式下处理文件,Thymeleaf将首先执行转换,将您的文件转换为格式良好的XML文件,这些文件仍然是完全有效的HTML5(实际上是创建HTML5代码的推荐方法)1。

另请注意,验证仅适用于XML和XHTML模板。

然而,这些并不是Thymeleaf可以处理的唯一模板类型,并且用户始终能够通过指定在此模式下解析模板的方法和编写结果的方式来定义他/她自己的模式。这样,任何可以建模为DOM树(无论是否为XML)的东西都可以被Thymeleaf有效地作为模板处理。

2.Springboot整合thymeleaf

使用springboot 来集成使用Thymeleaf可以大大减少单纯使用thymleaf的代码量,所以我们接下来使用springboot集成使用thymeleaf.

实现的步骤为:

  • 创建一个sprinboot项目
  • 添加thymeleaf的起步依赖
  • 添加spring web的起步依赖
  • 编写html 使用thymleaf的语法获取变量对应后台传递的值
  • 编写controller 设置变量的值到model中

(1)创建工程

创建一个独立的工程springboot-thymeleaf,该工程为案例工程,不需要放到changgou工程中。

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"><modelVersion>4.0.0</modelVersion><groupId>com.itheima</groupId><artifactId>springboot-thymeleaf</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.4.RELEASE</version></parent><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></dependencies>
</project>

(2)创建包com.itheima.thymeleaf.并创建启动类ThymeleafApplication

@SpringBootApplication
public class ThymeleafApplication {public static void main(String[] args) {SpringApplication.run(ThymeleafApplication.class,args);}
}

(3)创建application.yml

设置thymeleaf的缓存设置,设置为false。默认加缓存的,用于测试。

spring:thymeleaf:cache: false

(4)控制层

创建controller用于测试后台 设置数据到model中。

创建com.itheima.controller.TestController,代码如下:

@Controller
@RequestMapping("/test")
public class TestController {/**** 访问/test/hello  跳转到demo1页面* @param model* @return*/@RequestMapping("/hello")public String hello(Model model){model.addAttribute("hello","hello welcome");return "demo";}
}

(2)创建html

在resources中创建templates目录,在templates目录创建 demo.html,代码如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Thymeleaf的入门</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<!--输出hello数据-->
<p th:text="${hello}"></p>
</body>
</html>

解释:

<html xmlns:th="http://www.thymeleaf.org">:这句声明使用thymeleaf标签

<p th:text="${hello}"></p>:这句使用 th:text="${变量名}" 表示 使用thymeleaf获取文本数据,类似于EL表达式。

(5)测试

启动系统,并在浏览器访问

http://localhost:8080/test/hello

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GjTRM9qS-1620310478694)(images\1560936996326.png)]

3 Thymeleaf基本语法

(1)th:action

定义后台控制器路径,类似<form>标签的action属性。

例如:

<form th:action="@{/test/hello}" ><input th:type="text"  th:name="id"><button>提交</button>
</form>

(2)th:each

对象遍历,功能类似jstl中的<c:forEach>标签。

创建com.itheima.model.User,代码如下:

public class User {private Integer id;private String name;private String address;//..get..set
}

Controller添加数据

/**** 访问/test/hello  跳转到demo1页面* @param model* @return*/
@RequestMapping("/hello")
public String hello(Model model){model.addAttribute("hello","hello welcome");//集合数据List<User> users = new ArrayList<User>();users.add(new User(1,"张三","深圳"));users.add(new User(2,"李四","北京"));users.add(new User(3,"王五","武汉"));model.addAttribute("users",users);return "demo1";
}

页面输出

<table><tr><td>下标</td><td>编号</td><td>姓名</td><td>住址</td></tr><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>
</table>

测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQ09F2ZA-1620310478697)(images\1560941932553.png)]

(3)Map输出

后台添加Map

//Map定义
Map<String,Object> dataMap = new HashMap<String,Object>();
dataMap.put("No","123");
dataMap.put("address","深圳");
model.addAttribute("dataMap",dataMap);

页面输出

<div th:each="map,mapStat:${dataMap}"><div th:text="${map}"></div>key:<span th:text="${mapStat.current.key}"></span><br/>value:<span th:text="${mapStat.current.value}"></span><br/>==============================================
</div>

测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4VoOsNT-1620310478698)(images\1560942024009.png)]

(4)数组输出

后台添加数组

//存储一个数组
String[] names = {"张三","李四","王五"};
model.addAttribute("names",names);

页面输出

<div th:each="nm,nmStat:${names}"><span th:text="${nmStat.count}"></span><span th:text="${nm}"></span>==============================================
</div>

测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBDkuQca-1620310478701)(images\1560942589016.png)]

(5)Date输出

后台添加日期

//日期
model.addAttribute("now",new Date());

页面输出

<div><span th:text="${#dates.format(now,'yyyy-MM-dd hh:ss:mm')}"></span>
</div>

测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kt4uKPzw-1620310478702)(images\1560942631925.png)]

(6)th:if条件

后台添加年龄

//if条件
model.addAttribute("age",22);

页面输出

<div><span th:if="${(age>=18)}">终于长大了!</span>
</div>

测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OhlT74cP-1620310478704)(images\1560942782470.png)]

(7)th:fragment 定义一个模块

可以定义一个独立的模块,创建一个footer.html代码如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta http-equiv="Content-Type" content="text/html;charset=charset=utf-8"><title>fragment</title>
</head>
<body>
<div id="C" th:fragment="copy" >关于我们<br/>
</div>
</body>

(8)th:include

可以直接引入th:fragment,在demo1.html中引入如下代码:

<div id="A" th:include="footer::copy"></div>

效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4bP9oiN-1620310478705)(images\1560943006665.png)]

4 搜索页面渲染

4.1 搜索分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75HiXC5M-1620310478706)(images\1560982334645.png)]

搜索页面要显示的内容主要分为3块。

1)搜索的数据结果

2)筛选出的数据搜索条件

3)用户已经勾选的数据条件

4.2 搜索实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3njKCLc-1620310478706)(images\1560982635931.png)]

搜索的业务流程如上图,用户每次搜索的时候,先经过搜索业务工程,搜索业务工程调用搜索微服务工程。

4.2.1 搜索工程搭建

(1)引入依赖

在changgou-service_search工程中的pom.xml中引入如下依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

(2)静态资源导入

将资源中的页面资源/所有内容拷贝到工程的resources目录下如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hCEBd0AY-1620310478708)(images/1561019035144.png)]

(3) 更改配置文件,在spring下添加内容

thymeleaf:cache: false

4.2.1 基础数据渲染

(1)更新SearchController,定义跳转搜索结果页面方法

代码如下:

 //搜索页面   http://localhost:9009/search/list?keywords=手机&brand=三星&spec_颜色=粉色&//入参:Map//返回值 Map//由于页面是thymeleaf 完成的 属于服务器内页面渲染 跳转页面@GetMapping("/list")public String search(@RequestParam Map<String, String> searchMap, Model model) throws Exception {//特殊符号处理handlerSearchMap(searchMap);//执行查询返回值Map<String, Object> resultMap = searchService.search(searchMap);model.addAttribute("searchMap", searchMap);model.addAttribute("result", resultMap);return "search";}}

(2) 搜索结果页面渲染

(2.1)用户选择条件回显

<div class="bread"><ul class="fl sui-breadcrumb"><li><a href="#">全部结果</a></li><li class="active"><span th:text="${searchMap.keywords}"></span></li></ul><ul class="fl sui-tag"><!-- 品牌--><li class="with-x" th:if="${#maps.containsKey(searchMap,'brand')}">品牌:<span th:text="${searchMap.brand}"></span><i>×</i></li><!-- 价格--><li class="with-x" th:if="${#maps.containsKey(searchMap,'price')}">价格:<span th:text="${searchMap.price}"></span><i>×</i></li><!-- 规格--><li class="with-x" th:each="sm:${searchMap}" th:if="${#strings.startsWith(sm.key,'spec_')}"><span th:text="${#strings.replace(sm.key,'spec_','')}"></span>:<span th:text="${#strings.replace(sm.value,'%2B','+')}"></span><i>×</i></li></ul><form class="fl sui-form form-dark"><div class="input-control control-right"><input type="text" /><i class="sui-icon icon-touch-magnifier"></i></div></form></div>

(2.2)商品属性及规格显示

更新搜索业务层实现

 public Map<String, Set<String>> formartSpec(List<String> specList){Map<String,Set<String>> resultMap = new HashMap<>();if (specList!=null && specList.size()>0){for (String specJsonString : specList) {  //"{'颜色': '黑色', '尺码': '250度'}"//将获取到的json转换为mapMap<String,String> specMap = JSON.parseObject(specJsonString, Map.class);for (String specKey : specMap.keySet()) {Set<String> specSet = resultMap.get(specKey);if (specSet == null){specSet = new HashSet<String>();}//将规格信息存入set中specSet.add(specMap.get(specKey));//将set存入mapresultMap.put(specKey,specSet);}}}return resultMap;}

更新页面

<div class="clearfix selector"><div class="type-wrap logo" th:unless="${#maps.containsKey(searchMap,'brand')}"><div class="fl key brand">品牌</div><div class="value logos"><ul class="logo-list"><li th:each="brand,brandStat:${result.brandList}"><a th:text="${brand}"></a></li></ul></div><div class="ext"><a href="javascript:void(0);" class="sui-btn">多选</a><a href="javascript:void(0);">更多</a></div></div><div class="type-wrap" th:each="spec,specStat:${result.specList}" th:unless="${#maps.containsKey(searchMap,'spec_'+spec.key)}"><div class="fl key" th:text="${spec.key}"></div><div class="fl value"><ul class="type-list"><li th:each="op,opStat:${spec.value}"><a th:text="${op}"></a></li></ul></div><div class="fl ext"></div></div><div class="type-wrap" th:unless="${#maps.containsKey(searchMap,'price')}"><div class="fl key">价格</div><div class="fl value"><ul class="type-list"><li><a th:text="0-500元"></a></li><li><a th:text="500-1000元"></a></li><li><a th:text="1000-1500元"></a></li><li><a th:text="1500-2000元"></a></li><li><a th:text="2000-3000元"></a></li><li><a th:text="3000元以上"></a></li></ul></div><div class="fl ext"></div></div><div class="type-wrap"><div class="fl key">更多筛选项</div><div class="fl value"><ul class="type-list"><li><a>特点</a></li><li><a>系统</a></li><li><a>手机内存 </a></li><li><a>单卡双卡</a></li><li><a>其他</a></li></ul></div><div class="fl ext"></div></div></div>

(2.3)商品列表显示

<div class="goods-list"><ul class="yui3-g"><li class="yui3-u-1-5" th:each="sku,skuStat:${result.rows}"><div class="list-wrap"><div class="p-img"><a href="item.html"  target="_blank"><img src="/img/_/mobile01.png" /></a></div><div class="price"><strong><em>¥</em><i th:text="${sku.price}"></i></strong></div><div class="attr"><a target="_blank" href="item.html"  th:title="${sku.spec}" th:utext="${sku.name}">Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)</a></div><div class="commit"><i class="command">已有<span>2000</span>人评价</i></div><div class="operate"><a href="success-cart.html" target="_blank" class="sui-btn btn-bordered btn-danger">加入购物车</a><a href="javascript:void(0);" class="sui-btn btn-bordered">收藏</a></div></div></li></ul></div>

4.3 关键字搜索

修改search.html

<form  th:action="@{/search/list}" class="sui-form form-inline"><!--searchAutoComplete--><div class="input-append"><input type="text" id="autocomplete" name="keywords" th:value="${searchMap.keywords}"   class="input-error input-xxlarge" /><button class="sui-btn btn-xlarge btn-danger"  th:type="submit">搜索</button></div>
</form>

测试

搜索华为关键字,效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-do09eBRh-1620310478709)(images\1561807148761.png)]

4.4 条件搜索实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BXhd1vav-1620310478710)(images\1561809380407.png)]

用户每次点击搜索的时候,其实在上次搜索的基础之上加上了新的搜索条件,也就是在上一次请求的URL后面追加了新的搜索条件,我们可以在后台每次拼接组装出上次搜索的URL,然后每次将URL存入到Model中,页面每次点击不同条件的时候,从Model中取出上次请求的URL,然后再加上新点击的条件参数实现跳转即可。

(1)后台记录搜索URL

修改SkuController,添加组装URL的方法,并将组装好的URL存储起来,代码如下:

 //拼装urlStringBuilder url = new StringBuilder("/search/list");if (searchMap != null && searchMap.size()>0){//是由查询条件url.append("?");for (String paramKey : searchMap.keySet()) {if (!"sortRule".equals(paramKey) && !"sortField".equals(paramKey) && !"pageNum".equals(paramKey)){url.append(paramKey).append("=").append(searchMap.get(paramKey)).append("&");}}//http://localhost:9009/search/list?keywords=手机&spec_网络制式=4G&String urlString = url.toString();//去除路径上的最后一个&urlString=urlString.substring(0,urlString.length()-1);model.addAttribute("url",urlString);}else{model.addAttribute("url",url);}

(2)页面搜索对接

<div class="clearfix selector"><div class="type-wrap logo" th:unless="${#maps.containsKey(searchMap,'brand')}"><div class="fl key brand">品牌</div><div class="value logos"><ul class="logo-list"><li th:each="brand,brandSate:${result.brandList}"><a th:text="${brand}" th:href="@{${url}(brand=${brand})}"></a></li></ul></div><div class="ext"><a href="javascript:void(0);" class="sui-btn">多选</a><a href="javascript:void(0);">更多</a></div></div><div class="type-wrap" th:each="spec,specStat:${result.specList}" th:unless="${#maps.containsKey(searchMap,'spec_'+spec.key)}"><div class="fl key" th:text="${spec.key}"></div><div class="fl value"><ul class="type-list"><li th:each="op,opstat:${spec.value}"><a th:text="${op}" th:href="@{${url}('spec_'+${spec.key}=${op})}"></a></li></ul></div><div class="fl ext"></div></div><div class="type-wrap" th:unless="${#maps.containsKey(searchMap,'price')}"><div class="fl key">价格</div><div class="fl value"><ul class="type-list"><li><a th:text="0-500元" th:href="@{${url}(price='0-500')}"></a></li><li><a th:text="500-1000元" th:href="@{${url}(price='500-1000')}"></a></li><li><a th:text="1000-1500元" th:href="@{${url}(price='1000-1500')}"></a></li><li><a th:text="1500-2000元" th:href="@{${url}(price='1500-2000')}"></a></li><li><a th:text="2000-3000元" th:href="@{${url}(price='2000-3000')}"></a></li><li><a th:text="3000元以上" th:href="@{${url}(price='3000')}"></a></li></ul></div><div class="fl ext"></div></div><div class="type-wrap"><div class="fl key">更多筛选项</div><div class="fl value"><ul class="type-list"><li><a>特点</a></li><li><a>系统</a></li><li><a>手机内存 </a></li><li><a>单卡双卡</a></li><li><a>其他</a></li></ul></div><div class="fl ext"></div></div></div>

4.5 移除搜索条件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Win1ntn9-1620310478711)(images\1561812006303.png)]

如上图,用户点击条件搜索后,要将选中的条件显示出来,并提供移除条件的x按钮,显示条件我们可以从searchMap中获取,移除其实就是将之前的请求地址中的指定条件删除即可。

修改search.html,移除分类、品牌、价格、规格搜索条件,代码如下:

<ul class="fl sui-tag"><li class="with-x" th:if="${#maps.containsKey(searchMap,'brand')}">品牌:<span th:text="${searchMap.brand}"></span><a th:href="@{${#strings.replace(url,'&brand='+searchMap.brand,'')}}">×</a></li><li class="with-x" th:if="${#maps.containsKey(searchMap,'price')}">价格:<span th:text="${searchMap.price}"></span><a th:href="@{${#strings.replace(url,'&price='+searchMap.price,'')}}">×</a></li><!--规格--><li class="with-x" th:each="sm:${searchMap}" th:if="${#strings.startsWith(sm.key,'spec_')}"><span th:text="${#strings.replace(sm.key,'spec_','')}"></span> : <span th:text="${#strings.replace(sm.value,'%2B','+')}"></span><a th:href="@{${#strings.replace(url,'&'+sm.key+'='+sm.value,'')}}">×</a></li></ul>

4.6 排序

修改search.html,实现排序,代码如下:

<li><a th:href="@{${url}(sortRule='ASC',sortField='price')}">价格↑</a>
</li>
<li><a th:href="@{${url}(sortRule='DESC',sortField='price')}">价格↓</a>
</li>

4.7 分页

真实的分页应该像百度那样,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eiRluE5H-1620310478712)(images\1561834387880.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o2FssCe9-1620310478713)(images\1561834414207.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-stH7gMDl-1620310478714)(images\1561834458274.png)]

(1)分页工具类定义

在comm工程中添加Page分页对象,代码如下:

package com.changgou.entity;import java.io.Serializable;
import java.util.List;/*** 分页对象* @param <T>*/
public class Page <T> implements Serializable{//当前默认为第一页public static final Integer pageNum = 1;//默认每页显示条件public static final Integer pageSize = 20;//判断当前页是否为空或是小于1public static Integer cpn(Integer pageNum){if(null == pageNum || pageNum < 1){pageNum = 1;}return pageNum;}// 页数(第几页)private long currentpage;// 查询数据库里面对应的数据有多少条private long total;// 从数据库查处的总记录数// 每页查5条private int size;// 下页private int next;private List<T> list;// 最后一页private int last;private int lpage;private int rpage;//从哪条开始查private long start;//全局偏移量public int offsize = 2;public Page() {super();}/****** @param currentpage* @param total* @param pagesize*/public void setCurrentpage(long currentpage,long total,long pagesize) {//可以整除的情况下long pagecount =  total/pagesize;//如果整除表示正好分N页,如果不能整除在N页的基础上+1页int totalPages = (int) (total%pagesize==0? total/pagesize : (total/pagesize)+1);//总页数this.last = totalPages;//判断当前页是否越界,如果越界,我们就查最后一页if(currentpage>totalPages){this.currentpage = totalPages;}else{this.currentpage=currentpage;}//计算startthis.start = (this.currentpage-1)*pagesize;}//上一页public long getUpper() {return currentpage>1? currentpage-1: currentpage;}//总共有多少页,即末页public void setLast(int last) {this.last = (int) (total%size==0? total/size : (total/size)+1);}/***** 带有偏移量设置的分页* @param total* @param currentpage* @param pagesize* @param offsize*/public Page(long total,int currentpage,int pagesize,int offsize) {this.offsize = offsize;initPage(total, currentpage, pagesize);}/****** @param total   总记录数* @param currentpage 当前页* @param pagesize   每页显示多少条*/public Page(long total,int currentpage,int pagesize) {initPage(total,currentpage,pagesize);}/***** 初始化分页* @param total* @param currentpage* @param pagesize*/public void initPage(long total,int currentpage,int pagesize){//总记录数this.total = total;//每页显示多少条this.size=pagesize;//计算当前页和数据库查询起始值以及总页数setCurrentpage(currentpage, total, pagesize);//分页计算int leftcount =this.offsize, //需要向上一页执行多少次rightcount =this.offsize;//起点页this.lpage =currentpage;//结束页this.rpage =currentpage;//2点判断this.lpage = currentpage-leftcount;           //正常情况下的起点this.rpage = currentpage+rightcount;        //正常情况下的终点//页差=总页数和结束页的差int topdiv = this.last-rpage;             //判断是否大于最大页数/**** 起点页* 1、页差<0  起点页=起点页+页差值* 2、页差>=0 起点和终点判断*/this.lpage=topdiv<0? this.lpage+topdiv:this.lpage;/**** 结束页* 1、起点页<=0   结束页=|起点页|+1* 2、起点页>0    结束页*/this.rpage=this.lpage<=0? this.rpage+(this.lpage*-1)+1: this.rpage;/**** 当起点页<=0  让起点页为第一页* 否则不管*/this.lpage=this.lpage<=0? 1:this.lpage;/**** 如果结束页>总页数   结束页=总页数* 否则不管*/this.rpage=this.rpage>last? this.last:this.rpage;}public long getNext() {return  currentpage<last? currentpage+1: last;}public void setNext(int next) {this.next = next;}public long getCurrentpage() {return currentpage;}public long getTotal() {return total;}public void setTotal(long total) {this.total = total;}public long getSize() {return size;}public void setSize(int size) {this.size = size;}public long getLast() {return last;}public long getLpage() {return lpage;}public void setLpage(int lpage) {this.lpage = lpage;}public long getRpage() {return rpage;}public void setRpage(int rpage) {this.rpage = rpage;}public long getStart() {return start;}public void setStart(long start) {this.start = start;}public void setCurrentpage(long currentpage) {this.currentpage = currentpage;}/*** @return the list*/public List<T> getList() {return list;}/*** @param list the list to set*/public void setList(List<T> list) {this.list = list;}public static void main(String[] args) {//总记录数//当前页//每页显示多少条int cpage =17;Page page = new Page(1001,cpage,50,7);System.out.println("开始页:"+page.getLpage()+"__当前页:"+page.getCurrentpage()+"__结束页"+page.getRpage()+"____总页数:"+page.getLast());}
}

(2)分页实现

修改SkuController,实现分页信息封装,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3pELC6e-1620310478715)(images\1561834827481.png)]

(3)页面分页实现

修改search.html,实现分页查询,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-006gWTKW-1620310478716)(images\1561836674825.png)]

5.畅购商品详情页

5.1 需求分析

当系统审核完成商品,需要将商品详情页进行展示,那么采用静态页面生成的方式生成,并部署到高性能的web服务器中进行访问是比较合适的。所以,开发流程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jz4uJD8D-1620310478717)(images\Snipaste_2019-09-26_10-13-20.png)]

此处MQ我们使用Rabbitmq即可。

执行步骤解释:

  • 系统管理员(商家运维人员)修改或者审核商品的时候, 会更改数据库中商品上架状态并发送商品id给rabbitMq中的上架交换器
  • 上架交换器会将商品id发给静态页生成队列
  • 静态页微服务设置监听器, 监听静态页生成队列, 根据商品id获取商品详细数据并使用thymeleaf的模板技术生成静态页

5.2 商品静态化微服务创建

5.2.1 需求分析

该微服务只用于生成商品静态页,不做其他事情。

5.2.2 搭建项目

(1)在changgou-service下创建一个名称为changgou_service_page的项目,作为静态化页面生成服务

(2)changgou-service-page中添加起步依赖,如下

<dependencies><dependency><groupId>com.changgou</groupId><artifactId>changgou_common</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>com.changgou</groupId><artifactId>changgou_service_goods_api</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

(3)修改application.yml的配置

server:port: 9011
spring:application:name: pagerabbitmq:host: 192.168.200.128main:allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:client:service-url:defaultZone: http://127.0.0.1:6868/eurekainstance:prefer-ip-address: true
feign:hystrix:enabled: falseclient:config:default:   #配置全局的feign的调用超时时间  如果 有指定的服务配置 默认的配置不会生效connectTimeout: 600000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接  单位是毫秒readTimeout: 600000  # 指定的是调用服务提供者的 服务 的超时时间()  单位是毫秒
#hystrix 配置
hystrix:command:default:execution:timeout:#如果enabled设置为false,则请求超时交给ribbon控制enabled: trueisolation:strategy: SEMAPHORE
# 生成静态页的位置
pagepath: D:\items

(4)创建系统启动类

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.changgou.goods.feign")
public class PageApplication {public static void main(String[] args) {SpringApplication.run(PageApplication.class);}
}

5.3 生成静态页

5.3.1 需求分析

页面发送请求,传递要生成的静态页的商品的SpuID.后台controller 接收请求,调用thyemleaf的原生API生成商品静态页。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-04U5jiZA-1620310478718)(images\1561856657363.png)]

上图是要生成的商品详情页,从图片上可以看出需要查询SPU的3个分类作为面包屑显示,同时还需要查询SKU和SPU信息。

5.3.2 Feign创建

一会儿需要查询SPU和SKU以及Category,所以我们需要先创建Feign,修改changgou-service-goods-api,添加CategoryFeign,并在CategoryFeign中添加根据ID查询分类数据,代码如下:

@FeignClient(name = "goods")
public interface CategoryFeign {@GetMapping("/category/{id}")public Result<Category> findById(@PathVariable("id") Integer id);
}

在changgou-service-goods-api,添加SkuFeign,并添加根据SpuID查询Sku集合,代码如下:

@FeignClient(name = "goods")
public interface SkuFeign {@GetMapping("/sku/spu/{spuId}")public List<Sku> findSkuListBySpuId(@PathVariable("spuId") String spuId);
}

在changgou-service-goods-api,添加SpuFeign,并添加根据SpuID查询Spu信息,代码如下:

@FeignClient(name = "goods")
public interface SpuFeign {@GetMapping("/spu/findSpuById/{id}")public Result<Spu> findSpuById(@PathVariable("id") String id);
}

5.3.3 静态页生成代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6hCULfk-1620310478718)(images\Snipaste_2019-09-26_10-13-20.png)]

(1)创建PageService

public interface PageService {/*** 生成静态化页面* @param spuId*/void generateItemPage(String spuId);
}

(2)创建PageServiceImpl

@Service
public class PageServiceImpl implements PageService {@Autowiredprivate SpuFeign spuFeign;@Autowiredprivate CategoryFeign categoryFeign;@Autowiredprivate SkuFeign skuFeign;@Value("${pagepath}")private String pagepath;@Autowiredprivate TemplateEngine templateEngine;@Overridepublic void generateItemPage(String spuId) {//获取context对象,用于存放商品详情数据Context context = new Context();Map<String, Object> itemData = this.findItemData(spuId);context.setVariables(itemData);//获取商品详情页生成的指定位置File dir = new File(pagepath);//判断商品详情页文件夹是否存在,不存在则创建if (!dir.exists()){dir.mkdirs();}//定义输出流,进行文件生成File file = new File(dir+"/"+spuId+".html");Writer out = null;try{out = new PrintWriter(file);//生成文件/*** 1.模板名称* 2.context对象,包含了模板需要的数据* 3.输出流,指定文件输出位置*/templateEngine.process("item",context,out);}catch (Exception e){e.printStackTrace();}finally {//关闭流try {out.close();} catch (IOException e) {e.printStackTrace();}}}//获取静态化页面数据private Map<String, Object> findItemData(String spuId) {Map<String,Object> resultMap = new HashMap<>();//获取spu信息Result<Spu> spuResult = spuFeign.findSpuById(spuId);Spu spu = spuResult.getData();resultMap.put("spu",spu);//获取图片信息if (spu != null){if(!StringUtils.isEmpty(spu.getImages())){resultMap.put("imageList",spu.getImages().split(","));}}//获取分类信息Category category1 = categoryFeign.findById(spu.getCategory1Id()).getData();resultMap.put("category1",category1);Category category2 = categoryFeign.findById(spu.getCategory2Id()).getData();resultMap.put("category2",category2);Category category3 = categoryFeign.findById(spu.getCategory3Id()).getData();resultMap.put("category3",category3);//获取sku集合信息List<Sku> skuList = skuFeign.findSkuListBySpuId(spuId);resultMap.put("skuList",skuList);resultMap.put("specificationList",JSON.parseObject(spu.getSpecItems(), Map.class));return resultMap;}
}

(3)声明page_create_queue队列,并绑定到商品上架交换机

@Configuration
public class RabbitMQConfig {//定义交换机名称public static final String GOODS_UP_EXCHANGE="goods_up_exchange";public static final String GOODS_DOWN_EXCHANGE="goods_down_exchange";//定义队列名称public static final String AD_UPDATE_QUEUE="ad_update_queue";public static final String SEARCH_ADD_QUEUE="search_add_queue";public static final String SEARCH_DEL_QUEUE="search_del_queue";public static final String PAGE_CREATE_QUEUE="page_create_queue";//声明队列@Beanpublic Queue queue(){return new Queue(AD_UPDATE_QUEUE);}@Bean(SEARCH_ADD_QUEUE)public Queue SEARCH_ADD_QUEUE(){return new Queue(SEARCH_ADD_QUEUE);}@Bean(SEARCH_DEL_QUEUE)public Queue SEARCH_DEL_QUEUE(){return new Queue(SEARCH_DEL_QUEUE);}@Bean(PAGE_CREATE_QUEUE)public Queue PAGE_CREATE_QUEUE(){return new Queue(PAGE_CREATE_QUEUE);}//声明交换机@Bean(GOODS_UP_EXCHANGE)public Exchange GOODS_UP_EXCHANGE(){return ExchangeBuilder.fanoutExchange(GOODS_UP_EXCHANGE).durable(true).build();}@Bean(GOODS_DOWN_EXCHANGE)public Exchange GOODS_DOWN_EXCHANGE(){return ExchangeBuilder.fanoutExchange(GOODS_DOWN_EXCHANGE).durable(true).build();}//队列与交换机的绑定@Beanpublic Binding GOODS_UP_EXCHANGE_BINDING(@Qualifier(SEARCH_ADD_QUEUE)Queue queue,@Qualifier(GOODS_UP_EXCHANGE)Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with("").noargs();}@Beanpublic Binding PAGE_CREATE_QUEUE_BINDING(@Qualifier(PAGE_CREATE_QUEUE)Queue queue,@Qualifier(GOODS_UP_EXCHANGE)Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with("").noargs();}@Beanpublic Binding GOODS_DOWN_EXCHANGE_BINDING(@Qualifier(SEARCH_DEL_QUEUE)Queue queue,@Qualifier(GOODS_DOWN_EXCHANGE)Exchange exchange){return BindingBuilder.bind(queue).to(exchange).with("").noargs();}}

(4)创建PageListener监听类,监听page_create_queue队列,获取消息,并生成静态化页面

@Component
public class PageListener {@Autowiredprivate PageService pageService;@RabbitListener(queues = RabbitMQConfig.PAGE_CREATE_QUEUE)public void receiveMessage(String spuId){System.out.println("生成商品详情页面,商品id为: "+spuId);//生成静态化页面pageService.generateItemPage(spuId);}
}

(5)更新canal中消息队列配置类与Page服务一致

(6)更新canal中对于spu表的监听类,当商品审核状态从0变1,则将当前spuId发送到消息队列

//获取最新审核商品
if ("0".equals(oldData.get("status")) && "1".equals(newData.get("status"))){//发送商品spuIdrabbitTemplate.convertAndSend(RabbitMQConfig.GOODS_UP_EXCHANGE,"",newData.get("id"));
}

5.3.4 模板填充

(1)面包屑数据

修改item.html,填充三个分类数据作为面包屑,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4GBViAUa-1620310478720)(images\1561858073684.png)]

(2)商品图片

修改item.html,将商品图片信息输出,在真实工作中需要做空判断,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N34zaboo-1620310478721)(images\1561858651061.png)]

(3)规格输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RwFaWbzb-1620310478722)(images\1561859448044.png)]

(4)默认SKU显示

静态页生成后,需要显示默认的Sku,我们这里默认显示第1个Sku即可,这里可以结合着Vue一起实现。可以先定义一个集合,再定义一个spec和sku,用来存储当前选中的Sku信息和Sku的规格,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KSANRzFK-1620310478724)(images\1561858986838.png)]

页面显示默认的Sku信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6uuSemJ0-1620310478725)(images\1561859241651.png)]

(5)记录选中的Sku

在当前Spu的所有Sku中spec值是唯一的,我们可以根据spec来判断用户选中的是哪个Sku,我们可以在Vue中添加代码来实现,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WwLN8r1I-1620310478726)(images\1561860041421.png)]

添加规格点击事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YYhRxXz7-1620310478727)(images\1561860156033.png)]

(6)样式切换

点击不同规格后,实现样式选中,我们可以根据每个规格判断该规格是否在当前选中的Sku规格中,如果在,则返回true添加selected样式,否则返回false不添加selected样式。

Vue添加代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fE34lRxU-1620310478729)(images\1561860749790.png)]

页面添加样式绑定,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTIVEEtD-1620310478731)(images\1561860653870.png)]

5.3.5 启动测试

启动eurekea服务端,数据监控服务,商品服务,静态页生成服务. 将spu表中status字段从0更新为1.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arVsi4WH-1620310478732)(images\1561862084834.png)]

5.3.6 基于nginx完成静态页访问

生成的静态页我们可以先放到changgou-service-page工程中,后面项目实战的时候可以挪出来放到Nginx指定发布目录。一会儿我们将生成的静态页放到resources/templates/items目录下,所以请求该目录下的静态页需要直接到该目录查找即可。

,商品id为: "+spuId);
//生成静态化页面
pageService.generateItemPage(spuId);
}
}

(5)更新canal中消息队列配置类与Page服务一致(6)更新canal中对于spu表的监听类,当商品审核状态从0变1,则将当前spuId发送到消息队列```java
//获取最新审核商品
if ("0".equals(oldData.get("status")) && "1".equals(newData.get("status"))){//发送商品spuIdrabbitTemplate.convertAndSend(RabbitMQConfig.GOODS_UP_EXCHANGE,"",newData.get("id"));
}

5.3.4 模板填充

(1)面包屑数据

修改item.html,填充三个分类数据作为面包屑,代码如下:

[外链图片转存中…(img-4GBViAUa-1620310478720)]

(2)商品图片

修改item.html,将商品图片信息输出,在真实工作中需要做空判断,代码如下:

[外链图片转存中…(img-N34zaboo-1620310478721)]

(3)规格输出

[外链图片转存中…(img-RwFaWbzb-1620310478722)]

(4)默认SKU显示

静态页生成后,需要显示默认的Sku,我们这里默认显示第1个Sku即可,这里可以结合着Vue一起实现。可以先定义一个集合,再定义一个spec和sku,用来存储当前选中的Sku信息和Sku的规格,代码如下:

[外链图片转存中…(img-KSANRzFK-1620310478724)]

页面显示默认的Sku信息

[外链图片转存中…(img-6uuSemJ0-1620310478725)]

(5)记录选中的Sku

在当前Spu的所有Sku中spec值是唯一的,我们可以根据spec来判断用户选中的是哪个Sku,我们可以在Vue中添加代码来实现,代码如下:

[外链图片转存中…(img-WwLN8r1I-1620310478726)]

添加规格点击事件

[外链图片转存中…(img-YYhRxXz7-1620310478727)]

(6)样式切换

点击不同规格后,实现样式选中,我们可以根据每个规格判断该规格是否在当前选中的Sku规格中,如果在,则返回true添加selected样式,否则返回false不添加selected样式。

Vue添加代码:

[外链图片转存中…(img-fE34lRxU-1620310478729)]

页面添加样式绑定,代码如下:

[外链图片转存中…(img-jTIVEEtD-1620310478731)]

5.3.5 启动测试

启动eurekea服务端,数据监控服务,商品服务,静态页生成服务. 将spu表中status字段从0更新为1.

[外链图片转存中…(img-arVsi4WH-1620310478732)]

5.3.6 基于nginx完成静态页访问

生成的静态页我们可以先放到changgou-service-page工程中,后面项目实战的时候可以挪出来放到Nginx指定发布目录。一会儿我们将生成的静态页放到resources/templates/items目录下,所以请求该目录下的静态页需要直接到该目录查找即可。

第8章.商品详情页面之thymeleaf相关推荐

  1. 第七章 Web开发实战2——商品详情页

    本章以京东商品详情页为例,京东商品详情页虽然仅是单个页面,但是其数据聚合源是非常多的,除了一些实时性要求比较高的如价格.库存.服务支持等通过AJAX异步加载加载之外,其他的数据都是在后端做数据聚合然后 ...

  2. OpenResty学习——第七章 Web开发实战2——商品详情页

    本文转自https://blog.csdn.net/jinnianshilongnian/article/details/84704211,好文要顶,感谢博主分享! 本章以京东商品详情页为例,京东商品 ...

  3. Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十六(商品排序,Thymeleaf快速入门,商品详情页的展示)

    Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十六(商品详情页的展示) 一.商品排序 1.完善页面信息 这是用来做排序的,默认按照综合排序 ...

  4. 乐优商城 Day 09(thymeleaf,Rabbitmq,商品详情页,非教程)

    乐优商城学习Day09: 注意:此次代码都是在第八天的基础上 第八天的链接如下: https://blog.csdn.net/zcylxzyh/article/details/100859210 此次 ...

  5. 商品详细信息的代码html_电商网站的商品详情页系统架构

    小型电商网站的商品详情页系统架构 小型电商网站的页面展示采用页面全量静态化的思想.数据库中存放了所有的商品信息,页面静态化系统,将数据填充进静态模板中,形成静态化页面,推入 Nginx 服务器.用户浏 ...

  6. 尚硅谷谷粒商城第十二天 商品详情页及异步编排

    1. 商品详情 当用户搜索到商品,肯定会点击查看,就会进入商品详情页,接下来我们完成商品详情页的展示. 商品详情浏览量比较大,并发高,我们会独立开启一个微服务,用来展示商品详情. 1.1. 创建mod ...

  7. 京东商品详情页前端开发宝典

    声明:本位来自京东张开涛的微信公众号(kaitao-1234567),授权CSDN转载,如需转载请联系作者. 作者:周琪力,前端工程师,网络常用昵称「keelii」.在过去的4年里主要负责京东网站商品 ...

  8. 高并发处理之商品详情页

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 高并发处理之商品详情页 卜大伟 2019-01-18 11:13:47  2488  收藏  ...

  9. 乐优商城day13(商品详情页,rabbitMQ安装)

    所有代码发布在 [https://github.com/hades0525/leyou] Day13(rabbitmq) 2019年2月13日 14:45 使用thymeleaf thymeleaf基 ...

最新文章

  1. SAP MM 事务代码MI31之思考之续集
  2. 也说_T、_TEXT、TEXT、L
  3. 团购网站的兴衰与启示
  4. 机器学习算法源码全解析(二)-范数规则化之L0、L1与L2范数
  5. boost::test模块测试从数据集对主测试套件的访问
  6. JS模拟的Ping程序 (Web Ping)
  7. 前端学习(2872):Vue路由权限『前后端全解析』3
  8. python每个字符后添加空格_python实现指定字符串补全空格的方法
  9. android模拟器太卡,安卓模拟器安装之后太卡怎么解决
  10. k3服务器端的虚拟,k3服务器 客户端配置
  11. MySQL配置文件简单解析
  12. java fx 重绘_如何重绘JAVA FX 2.2中的窗口(舞台)
  13. chrome老版本_技术周刊 2019-08-06:Chrome 又隐藏了 www
  14. NVIDIA携大型台湾服务器制造商:为推AI数据中心设计方案
  15. python3安装教程
  16. android 生成 kml代码,android 导入KML文件
  17. Adobe Reader 下载
  18. gst-inspect-1.0 命令详解
  19. so easy(并查集)
  20. 蓝桥杯 灭鼠先锋 博弈

热门文章

  1. css3 - 图标元素动画效果5 - 弹性动画效果
  2. 【电脑技巧】设置电脑永不休眠
  3. Python中zip函数的用法
  4. 图像mnf正变换_PCA和MNF变换
  5. Procmon.exe —— 强大的系统监视工具
  6. 曾做erp开发工程师,谈下自己的经验
  7. PC usb蓝牙发射器
  8. kafka flush
  9. 躁!DJ 风格 Java 桌面音乐播放器
  10. 龙ol一键端服务器维护,龙OL一键端纯一键点两下OK问题及解答汇总贴.doc