1. 程式人生 > 實用技巧 >暢購商城(七):Thymeleaf實現靜態頁

暢購商城(七):Thymeleaf實現靜態頁

好好學習,天天向上


本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star,更多文章請前往:目錄導航

Thymeleaf簡單入門

什麼是Thymeleaf

Thymeleaf是一個模板引擎,主要用於編寫動態頁面。

SpringBoot整合Thymeleaf

SpringBoot整合Thymeleaf的方式很簡單,共分為以下幾個步驟

  • 建立一個sprinboot專案
  • 新增thymeleaf和spring web的起步依賴
  • 在resources/templates/下編寫html(需要宣告使用thymeleaf標籤)
  • 在controller層編寫相應的程式碼

啟動類,配置檔案,依賴的程式碼下一節有,這裡就不貼了。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>SpringBoot整合Thymeleaf</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<!--輸出hello資料   ${變數名}  -->
<p th:text="${hello}"></p>
</body>
</html>
@Controller
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/hello")
    public String hello(Model model){
        model.addAttribute("hello","歡迎關注微信公眾號Robod");
        return "demo1";
    }
}

這樣將專案啟動起來,訪問http://localhost:8080/test/hello

就可以成功跳轉到demo1.html頁面的內容了。

Thymeleaf常用標籤

  • th:action 定義後臺控制器路徑

現在訪問http://localhost:8080/test/hello2,如果控制檯輸出“demo2”,頁面還跳轉到demo2的話說明是OK的。

  • th:each 物件遍歷

訪問http://localhost:8080/test/hello3就可以看到結果了。

  • 遍歷Map

訪問http://localhost:8080/test/hello4就可以看到輸出結果。

  • 陣列輸出

訪問http://localhost:8080/test/hello5就可以看到輸出結果。

  • Date輸出

訪問http://localhost:8080/test/hello6就可以看到輸出結果。

  • th:if條件

訪問http://localhost:8080/test/hello7就可以看到輸出結果。

  • th:fragment th:include 定義和引入模組

比如我們在footer.html中定義了一個模組:

<div th:fragment="foot">
    歡迎關注微信公眾號Robod
</div>

然後在demo7中引用:

<div th:include="footer::foot"></div>

這樣訪問http://localhost:8080/test/hello7就可以看到效果了。

  • |....| 字串拼接
<span th:text="|${str1}${str2}|"></span>
--------------------------------------------
@RequestMapping("/hello8")
public String hello8(Model model){
    model.addAttribute("str1","字串1");
    model.addAttribute("str2","字串2");
    return "demo8";
}

訪問http://localhost:8080/test/hello8就可以看到輸出結果。

想要完整程式碼的小夥伴請點選下載

搜尋頁面

微服務搭建

我們建立一個搜尋頁面渲染微服務用來展示搜尋頁面,在這個微服務中,使用者進行搜尋後,調用搜索微服務拿到資料,然後使用Thymeleaf將頁面渲染出來展示給使用者。在changgou-web下建立一個名為changgou-search-web的Module用作搜尋微服務的頁面渲染工程。因為有些依賴是所有頁面渲染微服務都要用到的,所以在changgou-web中新增依賴:

<dependencies>
    <!-- Thymeleaf-->
    <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>-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
    <!--amqp-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
</dependencies>

Feign的依賴這裡我發現了一個問題,因為我不是把SearchEntity根據下圖的流程通過Feign傳遞到changgou-service-search麼。如果新增我註釋的那個依賴就會出現HttpRequestMethodNotSupportedException: Request method 'POST' not supported異常。新增後面一個依賴就不會出現問題。我到網上查了一下,貌似是Feign的一個小Bug,就是如果在GET請求裡添加了請求體就會被轉換為POST請求。

因為我們需要使用到Feign在幾個微服務之間進行呼叫,所以在changgou-search-web新增對changgou-service-search-api的依賴。

<dependencies>
    <dependency>
        <groupId>com.robod</groupId>
        <artifactId>changgou-service-search-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

然後在changgou-service-search-api下編寫相應的Feign介面用來呼叫changgou-service-search:

@FeignClient(name="search")
@RequestMapping("/search")
public interface SkuEsFeign {

    /**
     * 搜尋
     * @param searchEntity
     * @return
     */
    @GetMapping
    Result<SearchEntity> searchByKeywords(@RequestBody(required = false) SearchEntity searchEntity);
}

然後在changgou-search-web下的resource目錄下將資料提供的靜態資源匯入進去。因為主要是做後端的功能,所以前端就不寫了,直接匯入:

最後將啟動類和配置檔案寫好:

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.robod.feign")
public class SearchWebApplication {
    public static void main(String[] args) {
        SpringApplication.run(SearchWebApplication.class,args);
    }
}
server:
  port: 18086

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true

spring:
  thymeleaf:
    #構建URL時預先檢視名稱的字首,預設就是這個,寫在這裡是怕忘了怎麼配置
    prefix: classpath:/templates/
    suffix: .html   #字尾
    cache: false    #禁止快取

feign:
  hystrix:
    enabled: true
  application:
    name: search-web
  main:
    allow-bean-definition-overriding: true

# 不配置下面兩個的話可能會報timed-out and no fallback available異常
ribbon:
  ReadTimeout: 500000   # Feign請求讀取資料超時時間

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 50000   # feign連線超時時間

這樣我們的搜尋頁面微服務工程就搭建好了。然後在建立一個SkuSearchWebController類,然後建立一個searchByKeywords方法作為搜尋功能的入口

@GetMapping("/list")
public String searchByKeywords(SearchEntity searchEntity
        , Model model) {
    if (searchEntity == null || StringUtils.isEmpty(searchEntity.getKeywords())) {
        searchEntity = new SearchEntity("小米");
    }
    if (searchEntity.getSearchSpec() == null) {
        searchEntity.setSearchSpec(new HashMap<>(8));
    }
    SearchEntity result = skuFeign.searchByKeywords(searchEntity).getData();
    model.addAttribute("result", result);
    return "search";
}

這裡我指定了一個預設的關鍵詞,因為我發現如果searchEntity為null的話Feign就會報出timed-out and no fallback available,指定預設關鍵詞就可以解決這個問題,而且也符合邏輯,淘寶上如果不在搜尋欄填入任何內容就會搜尋預設的關鍵詞。

這個時候如果去訪問http://localhost:18086/search/list是沒有圖片和css樣式的,因為現在的seearch.html中指定的相對路徑,也就是去訪問search/img/下的圖片,其實是在img/下,所以我們還需要把相對路徑改為絕對路徑。把search中的href="./改為href="/,把src="./改為src="/,這樣訪問的就是img/下的圖片了。頁面就可以正常顯示了。

這樣的話搜尋頁面渲染微服務就搭建成功了。

資料填充

現在頁面所展示的資料並不是我們從ES中搜索出來的真實資料,而是預先設定好的資料。所以現在我們需要把搜尋出來的資料填充到介面上。

頁面所展示的就是一堆的li標籤,我們所需要做的就是留一個li,然後使用Themeleaf標籤迴圈取出資料填入進去。

<div class="goods-list">
    <ul class="yui3-g">
        <li th:each="item:${result.rows}" class="yui3-u-1-5">
            <div class="list-wrap">
                <div class="p-img">
                    <a href="item.html" target="_blank"><img th:src="${item.getImage()}"/></a>
                </div>
                <div class="price">
                    <strong>
                        <em>¥</em>
                        <i th:text="${item.price}"></i>
                    </strong>
                </div>
                <div class="attr">
                    <!--th:utext可以識別標籤  strings.abbreviate控制長度-->
                    <a target="_blank" href="item.html" title="" 
                       th:utext="${#strings.abbreviate(item.name,150)}"></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>

頁面關鍵詞搜尋和回顯顯示

首先指定表單提交的路徑,然後指定name的值,將搜尋按鈕的type指定為“submit”就可以實現頁面關鍵詞搜尋;然後新增th:value="${result.keywords}"表示取出result.keywords的值,從而實現回顯顯示的功能。

搜尋條件回顯及條件過濾顯示

  • 分類和品牌

如果沒有指定分類和品牌資訊的話,後端會將分類和品牌進行統計然後傳到前端,當我們指定了分類和品牌之後就不用將分類和品牌進行分類統計了,這個在上一篇文章中說過,但是前端怎麼處理呢?使用th:each遍歷出資料顯示出來,當我們指定了分類或者品牌之後,頁面上就不去顯示分類或品牌選項。

th:unless 的意思是不滿足條件才輸出資料,所以判斷一下categotyList和brandList是不是空的,是空的就不輸出內容。不是空的就用th:each遍歷,然後用th:text輸出。

  • 規格

規格顯示和過濾和上面的類似。

searchSpec是傳到後端的規格Map<String,String>集合,sepcMap是後端傳到前端的規格Map<String,Set>集合。所以我們判斷sepcMap中是否包含searchSpec的key,包含則說明這個規格我們已經指定過了,就不去顯示,否則就遍歷顯示出來。

但是前端怎麼給後端的searchEntity.searchSpec賦值呢?我不知道,問了一下我哥,他說這樣寫:http://www.test.com/path?map[a]=1&map[b]=2,然後就報400錯誤了,控制檯顯示