第7章 Thymeleaf、Rabbitmq實現靜態頁
學習目標
-
Thymeleaf的介紹
-
Thymeleaf的入門
-
Thymeleaf的語法及標籤
-
商品詳情頁靜態化工程搭建
-
商品詳情頁靜態化功能實現
1.詳情頁靜態化操作 2.填充基礎數據 Spu、List<Sku> 3.規格切換
搜索頁面渲染
1.數據展示 2.搜索條件展示 3.實現條件搜索控制
- 用戶修改商品信息,同步更新創建商品詳情頁
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-parent工程中。
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)創建html
在resources中創建templates目錄,在templates目錄創建 demo1.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表達式。
(3)修改application.yml配置
創建application.yml,並設置thymeleaf的緩存設置,設置爲false。默認加緩存的,用於測試。
spring:
thymeleaf:
cache: false
在這裏,其實還有一些默認配置,比如視圖前綴:classpath:/templates/,視圖後綴:.html
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties
部分源碼如下:
(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 "demo1";
}
}
(5)測試
創建啓動類com.itheima.ThymeleafApplication
,代碼如下:
@SpringBootApplication
public class ThymeleafApplication {
public static void main(String[] args) {
SpringApplication.run(ThymeleafApplication.class,args);
}
}
啓動系統,並在瀏覽器訪問
http://localhost:8080/test/hello
3 Thymeleaf基本語法
(1)th:action
定義後臺控制器路徑,類似<form>
標籤的action屬性。
例如:
<form id="login-form" th:action="@{/test/hello}">
<button>提交</button>
</form>
表示提交的請求地址爲/test/hello
(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>
測試效果
(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>
測試效果
(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>
測試效果
(5)Date輸出
後臺添加日期
//日期
model.addAttribute("now",new Date());
頁面輸出
<div>
<span th:text="${#dates.format(now,'yyyy-MM-dd hh:ss:mm')}"></span>
</div>
測試效果
(6)th:if條件
後臺添加年齡
//if條件
model.addAttribute("age",22);
頁面輸出
<div>
<span th:if="${(age>=18)}">終於長大了!</span>
</div>
測試效果
(7)使用javascript
java代碼爲:
(8) 字符拼接 使用||
後臺代碼:
模板:
4 搜索頁面渲染
4.1 搜索分析
搜索頁面要顯示的內容主要分爲3塊。
1)搜索的數據結果
2)篩選出的數據搜索條件
3)用戶已經勾選的數據條件
4.2 搜索實現
搜索的業務流程如上圖,用戶每次搜索的時候,先經過搜索業務工程,搜索業務工程調用搜索微服務工程,這裏搜索業務工程單獨挪出來的原因是它這裏涉及到了模板渲染以及其他綜合業務處理,以後很有可能會有移動端的搜索和PC端的搜索,後端渲染如果直接在搜索微服務中進行,會對微服務造成一定的侵入,不推薦這麼做,推薦微服務獨立,只提供服務,如果有其他頁面渲染操作,可以搭建一個獨立的消費工程調用微服務達到目的。
4.2.1 搜索工程搭建
(1)工程創建
在changgou-web工程中創建changgou-web-search工程,並在changgou-web的pom.xml中引入如下依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--amqp-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
(2)靜態資源導入
將資源中的頁面/前端頁面/search.html
拷貝到工程的resources/templates
目錄下,js、css等拷貝到static
目錄下,如下圖:
(3)Feign創建
修改changgou-service-search-api,添加com.changgou.search.feign.SkuFeign
,實現調用搜索,代碼如下:
@FeignClient(name="search")
@RequestMapping("/search")
public interface SkuFeign {
/**
* 搜索
* @param searchMap
* @return
*/
@GetMapping
Map search(@RequestParam(required = false) Map searchMap);
}
由於以後做搜索都是基於GET請求,所以我們需要將之前的搜索改成GET請求操作,修改changgou-service-search
微服務的com.changgou.search.controller.SkuController
裏面的search
方法,代碼如下:
(4)changgou-web-search的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>changgou-web</artifactId>
<groupId>com.changgou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>changgou-web-search</artifactId>
<dependencies>
<!--search API依賴-->
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou-service-search-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
(5)搜索調用
在changgou-web-search中創建com.changgou.search.controller.SkuController,實現調用搜索,代碼如下:
@Controller
@RequestMapping(value = "/search")
public class SkuController {
@Autowired
private SkuFeign skuFeign;
/**
* 搜索
* @param searchMap
* @return
*/
@GetMapping(value = "/list")
public String search(@RequestParam(required = false) Map searchMap, Model model){
//調用changgou-service-search微服務
Map resultMap = skuFeign.search(searchMap);
model.addAttribute("result",resultMap);
return "search";
}
}
(6)啓動類創建
修改changgou-web-search,添加啓動類com.changgou.SearchWebApplication,代碼如下:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.changgou.search.feign")
public class SearchWebApplication {
public static void main(String[] args) {
SpringApplication.run(SearchWebApplication.class,args);
}
}
(7)application.yml配置文件
server:
port: 18088
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
spring:
thymeleaf:
cache: false
application:
name: search-web
main:
allow-bean-definition-overriding: true
(8)項目完整結構
在search.html的頭部引入thymeleaf標籤
<html xmlns:th="http://www.thymeleaf.org">
測試:http://localhost:18088/search
,效果如下:
4.2.2 搜索數據填充
後端搜索到數據後,前端頁面進行數據顯示,顯示的數據分爲3部分
1)搜索的數據結果
2)篩選出的數據搜索條件
3)用戶已經勾選的數據條件
4.2.3 關鍵字搜索
用戶每次輸入關鍵字的時候,直接根據關鍵字搜索,關鍵字搜索的數據會存儲到result.rows
中,頁面每次根據result獲取rows,然後循環輸出即可,同時頁面的搜索框每次需要回顯搜索的關鍵詞。
實現思路
1.前端表單提交搜索的關鍵詞
2.後端根據關鍵詞進行搜索
3.將搜索條件存儲到Model中
4.頁面循環迭代輸出數據
5.搜索表單回顯搜索的關鍵詞
(1)後臺搜索實現
修改SkuController的search方法,代碼如下:
/**
* 搜索
* @param searchMap
* @return
*/
@GetMapping(value = "/list")
public String search(@RequestParam(required = false) Map<String,String> searchMap, Model model){
//調用changgou-service-search微服務
Map<String,Object> resultMap = skuFeign.search(searchMap);
//搜索數據結果
model.addAttribute("result",resultMap);
//搜索條件
model.addAttribute("searchMap",searchMap);
return "search";
}
(2)頁面搜索實現
修改search.html
注意:搜索按鈕爲submit提交。
(3)頁面結果輸出
修改search.html,代碼如下:
(4)測試
搜索華爲
關鍵字,效果如下:
4.3 搜索條件回顯
搜索條件除了關鍵字外,還有分類、品牌、以及規格,這些在我們前面已經將數據存入到了Map中,我們可以直接從Map中將數據取出,然後在頁面輸出即可。
分類:result.categoryList
品牌:result.brandList
規格:result.specList
修改search.html的條件顯示部分,代碼如下:
上圖代碼如下:
<!--selector-->
<div class="clearfix selector">
<div class="type-wrap" th:if="${#maps.containsKey(result, 'categoryList')}">
<div class="fl key">分類</div>
<div class="fl value">
<span th:each="category,categoryStat:${result.categoryList}">
<a th:text="${category}"></a>
</span>
</div>
<div class="fl ext"></div>
</div>
<div class="type-wrap logo" th:if="${#maps.containsKey(result, 'brandList')}">
<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>
解釋:
th:unless:條件不滿足時,才顯示
${#maps.containsKey(result,'brandList')}:map中包含某個key
4.4 條件搜索實現
用戶每次點擊搜索的時候,其實在上次搜索的基礎之上加上了新的搜索條件,也就是在上一次請求的URL後面追加了新的搜索條件,我們可以在後臺每次拼接組裝出上次搜索的URL,然後每次將URL存入到Model中,頁面每次點擊不同條件的時候,從Model中取出上次請求的URL,然後再加上新點擊的條件參數實現跳轉即可。
(1)後臺記錄搜索URL
修改SkuController,添加組裝URL的方法,並將組裝好的URL存儲起來,代碼如下:
(2)頁面搜索對接
th:href 這裏是超鏈接的語法,例如:th:href="@{${url}(price='500-1000')}"
表示請求地址是取url
參數的值,同時向後臺傳遞參數price的值爲500-100元。
4.5 移除搜索條件
如上圖,用戶點擊條件搜索後,要將選中的條件顯示出來,並提供移除條件的x
按鈕,顯示條件我們可以從searchMap中獲取,移除其實就是將之前的請求地址中的指定條件刪除即可。
(1)條件顯示
修改search.html,代碼如下:
解釋:
${#strings.startsWith(sm.key,'spec_')}:表示以spec_開始的key
${#strings.replace(sm.key,'spec_','')}:表示將sm.key中的spec_替換成空
(2)移除搜素條件
修改search.html,移除分類、品牌、價格、規格搜索條件,代碼如下:
4.6 排序(作業)
上圖代碼是排序代碼,需要2個屬性,sortRule
:排序規則,ASC或者DESC,sortField
:排序的域,前端每次只需要將這2個域的值傳入到後臺即可實現排序。
(1)後臺組裝排序URL
每次排序的時候恢復第1頁查詢,所以url地址我們需要重新拼接,每次切換排序的時候,不需要之前的排序信息,修改SkuController,代碼如下:
代碼如下:
private String url(Map<String, String> searchMap) {// { spec_網絡:"移動4G","keywords":"華爲"}
String url = "/search/list"; // a/b?id=1&
if (searchMap != null) {
url += "?";
for (Map.Entry<String, String> stringStringEntry : searchMap.entrySet()) {
//如果是排序 則 跳過 拼接排序的地址 因爲有數據
if(stringStringEntry.getKey().equals("sortField") || stringStringEntry.getKey().equals("sortRule")){
continue;
}
url += stringStringEntry.getKey() + "=" + stringStringEntry.getValue() + "&";
}
if(url.lastIndexOf("&")!=-1)
url = url.substring(0, url.lastIndexOf("&"));
}
return url;
}
(2)前端排序實現
修改search.html,實現排序,代碼如下:
這一塊我們實現了價格排序,同學們課後去實現以下銷量和新品排序。
4.7 分頁
真實的分頁應該像百度那樣,如下圖:
(1)分頁工具類定義
在comm工程中添加Page分頁對象,代碼如下:
public class Page <T> implements Serializable{
// 頁數(第幾頁)
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;
}
//計算start
this.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;
}
}
(2)分頁實現
由於這裏需要獲取分頁信息,我們可以在changgou-service-search
服務中修改搜索方法實現獲取分頁數據,修改com.changgou.search.service.impl.SkuServiceImpl
的search方法,在return之前添加如下方法獲取份額與數據:
//分頁數據保存
//設置當前頁碼
resultMap.put("pageNum", pageNum);
resultMap.put("pageSize", 30);
修改SkuController,實現分頁信息封裝,代碼如下:
(3)頁面分頁實現
修改search.html,實現分頁查詢,代碼如下:
注意:每次如果搜條件發生變化都要從第1頁查詢,而點擊下一頁的時候,分頁數據在頁面給出,不需要在後臺拼接的url中給出,所以在拼接url的時候,需要過濾掉分頁參數,修改changgou-web-search
的控制層com.changgou.search.controller.SkuController
的url拼接方法,代碼如下:
5.暢購商品詳情頁
5.1 需求分析
當系統審覈完成商品,需要將商品詳情頁進行展示,那麼採用靜態頁面生成的方式生成,並部署到高性能的web服務器中進行訪問是比較合適的。所以,開發流程如下圖所示:
執行步驟解釋:
- 系統管理員(商家運維人員)修改或者審覈商品的時候,會觸發canal監控數據
- canal微服務獲取修改數據後,調用靜態頁微服務的方法進行生成靜態頁
- 靜態頁微服務只負責使用thymeleaf的模板技術生成靜態頁
5.2 商品靜態化微服務創建
5.2.1 需求分析
該微服務只用於生成商品靜態頁,不做其他事情。
5.2.2 搭建項目
(1)在changgou-web下創建一個名稱爲changgou-web-item的模塊,如圖:
(2)changgou-web-item中添加起步依賴,如下
<?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>changgou-web</artifactId>
<groupId>com.changgou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>changgou-web-item</artifactId>
<dependencies>
<!--api 模塊-->
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou-service-goods-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
(3)修改application.yml的配置
server:
port: 18085
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
spring:
thymeleaf:
cache: false
application:
name: item
main:
allow-bean-definition-overriding: true
#rabbitmq:
# host: 192.168.25.138
# 生成靜態頁的位置
pagepath: D:/project/workspace_changgou/changgou/changgou-parent/changgou-web/changgou-web-item/src/main/resources/templates/items
(4)創建系統啓動類
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.changgou.goods.feign")
public class ItemApplication {
public static void main(String[] args) {
SpringApplication.run(ItemApplication.class,args);
}
}
5.3 生成靜態頁
5.3.1 需求分析
頁面發送請求,傳遞要生成的靜態頁的的商品的SpuID.後臺controller 接收請求,調用thyemleaf的原生API生成商品靜態頁。
上圖是要生成的商品詳情頁,從圖片上可以看出需要查詢SPU的3個分類作爲麪包屑顯示,同時還需要查詢SKU和SPU信息。
5.3.2 Feign創建
一會兒需要查詢SPU和SKU以及Category,所以我們需要先創建Feign,修改changgou-service-goods-api,添加CategoryFeign,並在CategoryFeign中添加根據ID查詢分類數據,代碼如下:
/**
* 獲取分類的對象信息
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<Category> findById(@PathVariable(name = "id") Integer id);
在changgou-service-goods-api,添加SkuFeign,並添加根據SpuID查詢Sku集合,代碼如下:
/**
* 根據條件搜索
* @param sku
* @return
*/
@PostMapping(value = "/search" )
public Result<List<Sku>> findList(@RequestBody(required = false) Sku sku);
在changgou-service-goods-api,添加SpuFeign,並添加根據SpuID查詢Spu信息,代碼如下:
/***
* 根據SpuID查詢Spu信息
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<Spu> findById(@PathVariable(name = "id") Long id);
5.3.3 靜態頁生成代碼
(1)創建Controller
在changgou-web-item中創建com.changgou.item.controller.PageController用於接收請求,測試生成靜態頁
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired
private PageService pageService;
/**
* 生成靜態頁面
* @param id
* @return
*/
@RequestMapping("/createHtml/{id}")
public Result createHtml(@PathVariable(name="id") Long id){
pageService.createPageHtml(id);
return new Result(true, StatusCode.OK,"ok");
}
}
(2)創建service
接口:
public interface PageService {
/**
* 根據商品的ID 生成靜態頁
* @param spuId
*/
public void createPageHtml(Long spuId) ;
}
實現類:
上圖代碼如下:
@Service
public class PageServiceImpl implements PageService {
@Autowired
private SpuFeign spuFeign;
@Autowired
private CategoryFeign categoryFeign;
@Autowired
private SkuFeign skuFeign;
@Autowired
private TemplateEngine templateEngine;
//生成靜態文件路徑
@Value("${pagepath}")
private String pagepath;
/**
* 構建數據模型
* @param spuId
* @return
*/
private Map<String,Object> buildDataModel(Long spuId){
//構建數據模型
Map<String,Object> dataMap = new HashMap<>();
//獲取spu 和SKU列表
Result<Spu> result = spuFeign.findById(spuId);
Spu spu = result.getData();
//獲取分類信息
dataMap.put("category1",categoryFeign.findById(spu.getCategory1Id()).getData());
dataMap.put("category2",categoryFeign.findById(spu.getCategory2Id()).getData());
dataMap.put("category3",categoryFeign.findById(spu.getCategory3Id()).getData());
if(spu.getImages()!=null) {
dataMap.put("imageList", spu.getImages().split(","));
}
dataMap.put("specificationList",JSON.parseObject(spu.getSpecItems(),Map.class));
dataMap.put("spu",spu);
//根據spuId查詢Sku集合
Sku skuCondition = new Sku();
skuCondition.setSpuId(spu.getId());
Result<List<Sku>> resultSku = skuFeign.findList(skuCondition);
dataMap.put("skuList",resultSku.getData());
return dataMap;
}
/***
* 生成靜態頁
* @param spuId
*/
@Override
public void createPageHtml(Long spuId) {
// 1.上下文
Context context = new Context();
Map<String, Object> dataModel = buildDataModel(spuId);
context.setVariables(dataModel);
// 2.準備文件
File dir = new File(pagepath);
if (!dir.exists()) {
dir.mkdirs();
}
File dest = new File(dir, spuId + ".html");
// 3.生成頁面
try (PrintWriter writer = new PrintWriter(dest, "UTF-8")) {
templateEngine.process("item", context, writer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.2.4 模板填充
(1)麪包屑數據
修改item.html,填充三個分類數據作爲麪包屑,代碼如下:
(2)商品圖片
修改item.html,將商品圖片信息輸出,在真實工作中需要做空判斷,代碼如下:
(3)規格輸出
(4)默認SKU顯示
靜態頁生成後,需要顯示默認的Sku,我們這裏默認顯示第1個Sku即可,這裏可以結合着Vue一起實現。可以先定義一個集合,再定義一個spec和sku,用來存儲當前選中的Sku信息和Sku的規格,代碼如下:
頁面顯示默認的Sku信息
(5)記錄選中的Sku
在當前Spu的所有Sku中spec值是唯一的,我們可以根據spec來判斷用戶選中的是哪個Sku,我們可以在Vue中添加代碼來實現,代碼如下:
添加規格點擊事件
(6)樣式切換
點擊不同規格後,實現樣式選中,我們可以根據每個規格判斷該規格是否在當前選中的Sku規格中,如果在,則返回true添加selected樣式,否則返回false不添加selected樣式。
Vue添加代碼:
頁面添加樣式綁定,代碼如下:
5.2.5 靜態資源過濾
生成的靜態頁我們可以先放到changgou-web-item工程中,後面項目實戰的時候可以挪出來放到Nginx指定發佈目錄。一會兒我們將生成的靜態頁放到resources/templates/items目錄下,所以請求該目錄下的靜態頁需要直接到該目錄查找即可。
我們創建一個EnableMvcConfig類,開啓靜態資源過濾,代碼如下:
@ControllerAdvice
@Configuration
public class EnableMvcConfig implements WebMvcConfigurer{
/***
* 靜態資源放行
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/items/**").addResourceLocations("classpath:/templates/items/");
}
}
5.4.6 啓動測試
啓動eurekea服務端
啓動商品微服務
啓動靜態化微服務 changgou-web-item
將靜態資源導入到changgou-web-item中,如下圖:
生成靜態頁地址 http://localhost:18085/page/createHtml/1087918019151269888
靜態頁生成後訪問地址 http://localhost:18085/items/1087918019151269888.html
6 canal監聽生成靜態頁
監聽到數據的變化,直接調用feign 生成靜態頁即可.
6.1 需求分析
當商品微服務審覈商品之後,應當發送消息,這裏採用了Canal監控數據變化,數據變化後,調用feign實現生成靜態頁
6.2 Feign創建
在changgou-service-api中創建changgou-web-item-api,該工程中主要創建changgou-web-item的對外依賴抽取信息。
(1)Feign創建
在changgou-web-item-api中創建com.changgou.item.feign.PageFeign,代碼如下:
@FeignClient(name="item")
@RequestMapping("/page")
public interface PageFeign {
/***
* 根據SpuID生成靜態頁
* @param id
* @return
*/
@RequestMapping("/createHtml/{id}")
Result createHtml(@PathVariable(name="id") Long id);
}
(2)pom.xml依賴
修改changgou-service-canal工程的pom.xml,引入如下依賴:
<!--靜態頁API 服務-->
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou-web-item-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
(3)修改changgou-service-canal工程中的啓動類
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient
@EnableCanalClient // 啓用canal
@EnableFeignClients(basePackages = {"com.changgou.content.feign","com.changgou.item.feign"})
public class CanalApplication {
public static void main(String[] args) {
SpringApplication.run(CanalApplication.class, args);
}
}
6.3 canal監聽數據變化
監聽類中,監聽商品數據庫的tb_spu的數據變化,當數據變化的時候生成靜態頁或者刪除靜態頁
在原來的監聽類中添加如下代碼即可,
@Autowired
private PageFeign pageFeign;
@ListenPoint(destination = "example",
schema = "changgou_goods",
table = {"tb_spu"},
eventType = {CanalEntry.EventType.UPDATE, CanalEntry.EventType.INSERT, CanalEntry.EventType.DELETE})
public void onEventCustomSpu(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
//判斷操作類型
if (eventType == CanalEntry.EventType.DELETE) {
String spuId = "";
List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
for (CanalEntry.Column column : beforeColumnsList) {
if (column.getName().equals("id")) {
spuId = column.getValue();//spuid
break;
}
}
//todo 刪除靜態頁
}else{
//新增 或者 更新
List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
String spuId = "";
for (CanalEntry.Column column : afterColumnsList) {
if (column.getName().equals("id")) {
spuId = column.getValue();
break;
}
}
//更新 生成靜態頁
pageFeign.createHtml(Long.valueOf(spuId));
}
}