乐优商城笔记六:商品详情页

使用模板引擎 Thymeleaf + nginx 完成商品详情页静态化

完成乐优商城商品详情页

搭建商品详情页微服务

创建子工程

  • GroupId:com.leyou.service
  • ArtifactId:ly-page

编写pom.xml


<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>parentartifactId>
        <groupId>com.leyougroupId>
        <version>1.0.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <groupId>com.leyou.servicegroupId>
    <artifactId>ly-pageartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>
        <dependency>
            <groupId>com.leyou.servicegroupId>
            <artifactId>ly-item-interfaceartifactId>
            <version>${leyou.latest.version}version>
        dependency>
    dependencies>
project>

编写application.yml

server:
  port: 8004
spring:
  application:
    name: page-service
  thymeleaf:
    cache: false # 关闭thymeleaf缓存
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:9999/eureka
  instance:
    lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
    lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期

编写启动类

package com.leyou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

导入thymeleaf模板

将下面的代码放入resources/templates下。

item.html

这个模板为最终模板,即内部逻辑已经全部完成,只需要放入数据即可。


<html xmlns:th="http://www.w3.org/1999/xhtml">

<head>
	<meta charset="utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE">
	<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
	<title>乐优商城--商品详情页title>
	 <link rel="icon" href="/assets/img/favicon.ico">

    <link rel="stylesheet" type="text/css" href="/css/webbase.css" />
    <link rel="stylesheet" type="text/css" href="/css/pages-item.css" />
    <link rel="stylesheet" type="text/css" href="/css/pages-zoom.css" />
    <link rel="stylesheet" type="text/css" href="/css/widget-cartPanelView.css" />

	<style type="text/css">
	.goods-intro-list li {
		display: inline-block;
		width: 300px;
	}
       .Ptable {
           margin: 10px 0;
       }
       .Ptable-item {
            padding: 12px 0;
            line-height: 220%;
            color: #999;
            font-size: 12px;
               border-bottom: 1px solid #eee;
        }
       .Ptable-item h3 {
           width: 110px;
           text-align: right;
       }
       .Ptable-item h3, .package-list h3 {
           font-weight: 400;
           font-size: 12px;
           float: left;
       }
       h3 {
           display: block;
           font-size: 1.17em;
           -webkit-margin-before: 1em;
           -webkit-margin-after: 1em;
           -webkit-margin-start: 0px;
           -webkit-margin-end: 0px;
           font-weight: bold;
       }
       .Ptable-item dl {
           margin-left: 110px;
       }
       dl {
           display: block;
           -webkit-margin-before: 1em;
           -webkit-margin-after: 1em;
           -webkit-margin-start: 0px;
           -webkit-margin-end: 0px;
       }
       .Ptable-item dt {
           width: 160px;
           float: left;
           text-align: right;
           padding-right: 5px;
       }
       .Ptable-item dd {
           margin-left: 210px;
       }
       dd {
           display: block;
           -webkit-margin-start: 40px;
       }
       .package-list {
           padding: 12px 0;
           line-height: 220%;
           color: #999;
           font-size: 12px;
           margin-top: -1px;
       }
       .package-list h3 {
           width: 130px;
           text-align: right;
       }
       .package-list p {
           margin-left: 155px;
           padding-right: 50px;
       }
style>

head>

<body>



<div id="itemApp">
	<div id="nav-bottom">
		<ly-top />
	div>
	<div class="py-container">
		<div id="item">
			<div class="crumb-wrap">
				<ul class="sui-breadcrumb">
					<li th:each="category : ${categories}">
						<a href="#" th:text="${category.name}">手机a>
					li>
					<li>
						<a href="#" th:text="${brand.name}">Applea>
					li>
					<li class="active" th:text="${spu.title}">Apple iPhone 6sli>
				ul>
			div>
			
			<div class="product-info">
				<div class="fl preview-wrap">
					
					<div class="zoom">
						
						<div id="preview" class="spec-preview">
      <span class="jqzoom">
         <img :jqimg="images[0]" :src="images[0]" width="400px" height="400px"/>
      span>
						div>
						
						<div class="spec-scroll">
							<a class="prev"><a>
							
							<div class="items">
								<ul>
									<li v-for="(image, i) in images" :key="i">
										<img :src="image" :bimg="image" onmousemove="preview(this)" />
									li>
								ul>
							div>
							<a class="next">>a>
						div>
					div>
				div>
				<div class="fr itemInfo-wrap">
					<div class="sku-name">
						<h4 v-text="sku.title">h4>
					div>
					<div class="news"><span th:utext="${spu.subTitle}">span>div>
					<div class="summary">
						<div class="summary-wrap">
							<div class="fl title"><i>价  格i>div>
							<div class="fl price">
								<i>¥i><em v-text="ly.formatPrice(sku.price)">em><span>降价通知span>
							div>
							<div class="fr remark"><i>累计评价i><em>612188em>div>
						div>
						<div class="summary-wrap">
							<div class="fl title">
								<i>促  销i>
							div>
							<div class="fl fix-width">
								<i class="red-bg">加价购i>
								<em class="t-gray">满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换
购热销商品em>
							div>
						div>
					div>
					<div class="support">
						<div class="summary-wrap">
							<div class="fl title">
								<i>支  持i>
							div>
							<div class="fl fix-width">
								<em class="t-gray">以旧换新,闲置手机回收  4G套餐超值抢  礼品购em>
							div>
						div>
						<div class="summary-wrap">
							<div class="fl title">
								<i>配 送 至i>
							div>
							<div class="fl fix-width">
								<em class="t-gray">上海 <span>有货span>em>
							div>
						div>
					div>
					<div class="clearfix choose">
						<div id="specification" class="summary-wrap clearfix">
							<dl v-for="(value,key) in specialSpec" :key="key">
								<dt>
									<div class="fl title">
										<i>{{key}}i>
									div>
								dt>
								<dd v-for="(str, index) in value" :key="index">
									<a href="javascript:;" :class="{selected: index===indexes[key]}" @click="indexes[key]=index">
										{{str}}<span v-if="index===indexes[key]" title="点击取消选择"> span>
									a>
								dd>
							dl>
						div>

						<div class="summary-wrap">
							<div class="fl title">
								<div class="control-group">
									<div class="controls">
										<input autocomplete="off" type="text" disabled value="1" minnum="1" class="itxt" />
										<a href="javascript:void(0)" class="increment plus">+a>
										<a href="javascript:void(0)" class="increment mins">-a>
									div>
								div>
							div>
							<div class="fl">
								<ul class="btn-choose unstyled">
									<li>
										<a href="success-cart.html" target="_blank" class="sui-btn  btn-danger addshopcar">加入购物车a>
									li>
								ul>
							div>
						div>
					div>
				div>
			div>
			
			<div class="clearfix product-detail">
				<div class="fl aside">
					<ul class="sui-nav nav-tabs tab-wraped">
						<li class="active">
							<a href="#index" data-toggle="tab">
								<span>相关分类span>
							a>
						li>
						<li>
							<a href="#profile" data-toggle="tab">
								<span>推荐品牌span>
							a>
						li>
					ul>
					<div class="tab-content tab-wraped">
						<div id="index" class="tab-pane active">
							<ul class="part-list unstyled">
								<li>手机li>
								<li>手机壳li>
								<li>内存卡li>
								<li>Iphone配件li>
								<li>贴膜li>
								<li>手机耳机li>
								<li>移动电源li>
								<li>平板电脑li>
							ul>
							<ul class="goods-list unstyled">
								<li>
									<div class="list-wrap">
										<div class="p-img">
											<img src="/img/_/part01.png" />
										div>
										<div class="attr">
											<em>Apple苹果iPhone 6s (A1699)em>
										div>
										<div class="price">
											<strong>
											<em>¥em>
											<i>6088.00i>
										strong>
										div>
										<div class="operate">
											<a href="javascript:void(0);" class="sui-btn btn-bordered">加入购物车a>
										div>
									div>
								li>
								<li>
									<div class="list-wrap">
										<div class="p-img">
											<img src="/img/_/part02.png" />
										div>
										<div class="attr">
											<em>Apple苹果iPhone 6s (A1699)em>
										div>
										<div class="price">
											<strong>
											<em>¥em>
											<i>6088.00i>
										strong>
										div>
										<div class="operate">
											<a href="javascript:void(0);" class="sui-btn btn-bordered">加入购物车a>
										div>
									div>
								li>
								<li>
									<div class="list-wrap">
										<div class="p-img">
											<img src="/img/_/part03.png" />
										div>
										<div class="attr">
											<em>Apple苹果iPhone 6s (A1699)em>
										div>
										<div class="price">
											<strong>
											<em>¥em>
											<i>6088.00i>
										strong>
										div>
										<div class="operate">
											<a href="javascript:void(0);" class="sui-btn btn-bordered">加入购物车a>
										div>
									div>
									<div class="list-wrap">
										<div class="p-img">
											<img src="/img/_/part02.png" />
										div>
										<div class="attr">
											<em>Apple苹果iPhone 6s (A1699)em>
										div>
										<div class="price">
											<strong>
											<em>¥em>
											<i>6088.00i>
										strong>
										div>
										<div class="operate">
											<a href="javascript:void(0);" class="sui-btn btn-bordered">加入购物车a>
										div>
									div>
									<div class="list-wrap">
										<div class="p-img">
											<img src="/img/_/part03.png" />
										div>
										<div class="attr">
											<em>Apple苹果iPhone 6s (A1699)em>
										div>
										<div class="price">
											<strong>
											<em>¥em>
											<i>6088.00i>
										strong>
										div>
										<div class="operate">
											<a href="javascript:void(0);" class="sui-btn btn-bordered">加入购物车a>
										div>
									div>
								li>
							ul>
						div>
						<div id="profile" class="tab-pane">
							<p>推荐品牌p>
						div>
					div>
				div>
				<div class="fr detail">
					<div class="clearfix fitting">
						<h4 class="kt">选择搭配h4>
						<div class="good-suits">
							<div class="fl master">
								<div class="list-wrap">
									<div class="p-img">
										<img src="/img/_/l-m01.png" />
									div>
									<em>¥5299em>
									<i>+i>
								div>
							div>
							<div class="fl suits">
								<ul class="suit-list">
									<li class="">
										<div id="">
											<img src="/img/_/dp01.png" />
										div>
										<i>Feless费勒斯VRi>
										<label data-toggle="checkbox" class="checkbox-pretty">
    <input type="checkbox"><span>39span>
  label>
									li>
									<li class="">
										<div id=""><img src="/img/_/dp02.png" /> div>
										<i>Feless费勒斯VRi>
										<label data-toggle="checkbox" class="checkbox-pretty">
    <input type="checkbox"><span>50span>
  label>
									li>
									<li class="">
										<div id=""><img src="/img/_/dp03.png" />div>
										<i>Feless费勒斯VRi>
										<label data-toggle="checkbox" class="checkbox-pretty">
    <input type="checkbox"><span>59span>
  label>
									li>
									<li class="">
										<div id=""><img src="/img/_/dp04.png" />div>
										<i>Feless费勒斯VRi>
										<label data-toggle="checkbox" class="checkbox-pretty">
    <input type="checkbox"><span>99span>
  label>
									li>
								ul>
							div>
							<div class="fr result">
								<div class="num">已选购0件商品div>
								<div class="price-tit"><strong>套餐价strong>div>
								<div class="price">¥5299div>
								<button class="sui-btn  btn-danger addshopcar">加入购物车button>
							div>
						div>
					div>
					<div class="tab-main intro">
						<ul class="sui-nav nav-tabs tab-wraped">
							<li class="active">
								<a href="#one" data-toggle="tab">
									<span>商品介绍span>
								a>
							li>
							<li>
								<a href="#two" data-toggle="tab">
									<span>规格与包装span>
								a>
							li>
							<li>
								<a href="#three" data-toggle="tab">
									<span>售后保障span>
								a>
							li>
							<li>
								<a href="#four" data-toggle="tab">
									<span>商品评价span>
								a>
							li>
							<li>
								<a href="#five" data-toggle="tab">
									<span>手机社区span>
								a>
							li>
						ul>
						<div class="clearfix">div>
						<div class="tab-content tab-wraped">
							<div id="one" class="tab-pane active">
								
								<div class="intro-detail">
									<div th:utext="${detail.description}">div>
								div>
							div>
							<div id="two" class="tab-pane">
								<div class="Ptable">
									<div class="Ptable-item" v-for="(group, i) in specs" :key="i">
										<h3 v-text="group.group">h3>
										<dl>
            <span v-for="(param,j) in group.params" :key="j">
				<dt v-text="param.k">dt>
				<dd v-text="param.v ? param.v + (param.unit || '') : JSON.parse(sku.ownSpec)[param.k]">dd>
			span>
										dl>
									div>
								div>
								<div class="package-list">
									<h3>包装清单h3>
									<p th:text="${detail.packingList}">p>
								div>

							div>
							<div id="three" class="tab-pane">
								<p>售后保障p>
								<p th:text="${detail.afterService}">p>
							div>
							<div id="four" class="tab-pane">
								<p>商品评价p>
							div>
							<div id="five" class="tab-pane">
								<p>手机社区p>
							div>
						div>
					div>
				div>
			div>
			
			<div class="clearfix">div>
			<div class="like">
				<h4 class="kt">猜你喜欢h4>
				<div class="like-list">
					<ul class="yui3-g">
						<li class="yui3-u-1-6">
							<div class="list-wrap">
								<div class="p-img">
									<img src="/img/_/itemlike01.png" />
								div>
								<div class="attr">
									<em>DELL戴尔Ins 15MR-7528SS 15英寸 银色 笔记本em>
								div>
								<div class="price">
									<strong>
											<em>¥em>
											<i>3699.00i>
										strong>
								div>
								<div class="commit">
									<i class="command">已有6人评价i>
								div>
							div>
						li>
						<li class="yui3-u-1-6">
							<div class="list-wrap">
								<div class="p-img">
									<img src="/img/_/itemlike02.png" />
								div>
								<div class="attr">
									<em>Apple苹果iPhone 6s/6s Plus 16G 64G 128Gem>
								div>
								<div class="price">
									<strong>
											<em>¥em>
											<i>4388.00i>
										strong>
								div>
								<div class="commit">
									<i class="command">已有700人评价i>
								div>
							div>
						li>
						<li class="yui3-u-1-6">
							<div class="list-wrap">
								<div class="p-img">
									<img src="/img/_/itemlike03.png" />
								div>
								<div class="attr">
									<em>DELL戴尔Ins 15MR-7528SS 15英寸 银色 笔记本em>
								div>
								<div class="price">
									<strong>
											<em>¥em>
											<i>4088.00i>
										strong>
								div>
								<div class="commit">
									<i class="command">已有700人评价i>
								div>
							div>
						li>
						<li class="yui3-u-1-6">
							<div class="list-wrap">
								<div class="p-img">
									<img src="/img/_/itemlike04.png" />
								div>
								<div class="attr">
									<em>DELL戴尔Ins 15MR-7528SS 15英寸 银色 笔记本em>
								div>
								<div class="price">
									<strong>
											<em>¥em>
											<i>4088.00i>
										strong>
								div>
								<div class="commit">
									<i class="command">已有700人评价i>
								div>
							div>
						li>
						<li class="yui3-u-1-6">
							<div class="list-wrap">
								<div class="p-img">
									<img src="/img/_/itemlike05.png" />
								div>
								<div class="attr">
									<em>DELL戴尔Ins 15MR-7528SS 15英寸 银色 笔记本em>
								div>
								<div class="price">
									<strong>
											<em>¥em>
											<i>4088.00i>
										strong>
								div>
								<div class="commit">
									<i class="command">已有700人评价i>
								div>
							div>
						li>
						<li class="yui3-u-1-6">
							<div class="list-wrap">
								<div class="p-img">
									<img src="/img/_/itemlike06.png" />
								div>
								<div class="attr">
									<em>DELL戴尔Ins 15MR-7528SS 15英寸 银色 笔记本em>
								div>
								<div class="price">
									<strong>
											<em>¥em>
											<i>4088.00i>
										strong>
								div>
								<div class="commit">
									<i class="command">已有700人评价i>
								div>
							div>
						li>
					ul>
				div>
			div>
		div>
	div>
	
div>
<script src="/js/vue/vue.js">script>
<script src="/js/axios.min.js">script>
<script src="/js/common.js">script>

<script th:inline="javascript">
	// sku集合
	const skus = /*[[${skus}]]*/ [];
	// 规格参数id与name对
	const paramMap = /*[[${specs}]]*/ {};
	// 特有规格参数集合
	const specialSpec = JSON.parse(/*[[${detail.specTemplate}]]*/ "");
	// 初始化特有规格参数默认选中一个
	const indexes = {};
	const initIndex = skus[0].indexes.split("_");

	Object.keys(specialSpec).forEach((key, i) => {
		indexes[key] = parseInt(initIndex[i]);
	});
	const indexArr = skus.map(s => s.indexes);

	const specs = JSON.parse(/*[[${detail.specifications}]]*/ "");

script>

<script>
    var itemVm = new Vue({
        el:"#itemApp",
        data:{
        	ly,
			skus,
			paramMap,
			specialSpec,
			indexes,
			specs
        },
        components:{
            lyTop: () => import('/js/pages/top.js')
        },
		computed: {
			sku(){
				const index = Object.values(this.indexes).join("_");
				return this.skus.find(s=>s.indexes==index);
			},
			images(){
				return this.sku.images ? this.sku.images.split(",") : [''];
			}
		},
    });
script>

<script type="text/javascript" src="/js/plugins/jquery/jquery.min.js">script>
<script type="text/javascript">
$(function(){
	$("#service").hover(function(){
		$(".service").show();
	},function(){
		$(".service").hide();
	});
	$("#shopcar").hover(function(){
		$("#shopcarlist").show();
	},function(){
		$("#shopcarlist").hide();
	});

})
script>
<script type="text/javascript" src="/js/model/cartModel.js">script>
<script type="text/javascript" src="/js/plugins/jquery.easing/jquery.easing.min.js">script>
<script type="text/javascript" src="/js/plugins/sui/sui.min.js">script>
<script type="text/javascript" src="/js/plugins/jquery.jqzoom/jquery.jqzoom.js">script>
<script type="text/javascript" src="/js/plugins/jquery.jqzoom/zoom.js">script>
<script type="text/javascript" src="index/index.js">script>
body>

html>

使用thymeleaf渲染页面

创建FeignClient

  • 分别创建 BrandClient,CategoryClient,GoodsClient和SpecificationClient接口并继承item-interface中对应的API接口类即可。

  • 提供api,基本上所有的api已经在之前就准备好了,只需要在GoodsController中新增一个api。

    
        /**
         * 查询spu信息
         *
         * @param spuId 商品ID
         * @return Spu
         */
        @GetMapping("/{spuId}")
        public ResponseEntity<SpuBO> queryGoodsById(@PathVariable("spuId") Long spuId) {
            return ResponseEntity.ok(goodsService.queryGoodsById(spuId));
        }
    

    goodsService.queryGoodsById(spuId)在之前已经实现,只需要将api对外提供即可。

PageServiceConstants

这是我的常量类,PageService用到的一些字符串常量,我都定义在这里面的

package com.leyou.page.util;

/**
 * page service 常量类
 */
public final class PageServiceConstants {

    /**
     * template name
     */
    public static final String TEMPLATE_NAME_ITEM = "item";

    /**
     * Model field
     */
    public static final String MODEL_CATEGORIES = "categories";
    public static final String MODEL_BRAND = "brand";
    public static final String MODEL_SPU = "spu";
    public static final String MODEL_DETAIL = "detail";
    public static final String MODEL_SKUS = "skus";
    public static final String MODEL_SPECS = "specs";

}

Controller代码

package com.leyou.page.controller;

import com.leyou.page.service.PageService;
import com.leyou.page.util.PageServiceConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.Map;

@Controller
public class PageController {

    @Autowired
    private PageService pageService;

    /**
     * 生成数据并返回到item视图
     */
    @GetMapping("item/{id}.html")
    public String spuPageInfo(@PathVariable("id") Long spuId, Model model) {
        // 获取数据
        Map<String, Object> attributes = pageService.loadModel(spuId);
        // 封装数据
        model.addAllAttributes(attributes);
        
        return PageServiceConstants.TEMPLATE_NAME_ITEM;
    }
}

Service代码

package com.leyou.page.service;

import com.alibaba.fastjson.JSON;
import com.leyou.BO.SpuBO;
import com.leyou.client.item.BrandClient;
import com.leyou.client.item.CategoryClient;
import com.leyou.client.item.GoodsClient;
import com.leyou.client.item.SpecificationClient;
import com.leyou.page.util.PageServiceConstants;
import com.leyou.pojo.Brand;
import com.leyou.pojo.Category;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class PageService {

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private SpecificationClient specificationClient;

    public Map<String, Object> loadModel(Long spuId) {
        Map<String, Object> model = new HashMap<>();
        // 获取spu信息
        SpuBO spuBO = goodsClient.queryGoodsById(spuId);
        // 分类数据
        List<Category> categories = categoryClient.queryCategoryListByIds(Arrays.asList(spuBO.getCid1(), spuBO.getCid2(), spuBO.getCid3()));
        // 品牌数据
        Brand brand = brandClient.queryBrandById(spuBO.getBrandId());

        model.put(PageServiceConstants.MODEL_BRAND, brand);
        model.put(PageServiceConstants.MODEL_CATEGORIES, categories);
        model.put(PageServiceConstants.MODEL_DETAIL, spuBO.getSpuDetail());
        model.put(PageServiceConstants.MODEL_SKUS, spuBO.getSkuList());
        model.put(PageServiceConstants.MODEL_SPECS, JSON.parseObject(spuBO.getSpuDetail().getSpecTemplate()));
        model.put(PageServiceConstants.MODEL_SPU, spuBO);

        return model;
    }

}

运行前的工作

修改搜索页的跳转

修改nginx配置

之前所有的www.leyou.com域名下的地址都会转发到9002端口,现在我们需要将/item的请求转发到8004端口,也就是我们的page-service的端口

启动nginx,即可测试啦。

网页静态化

什么是静态化

静态化是指把动态生成的HTML页面变为静态内容保存,以后用户的请求到来,直接访问静态页面,不再经过服务的渲染。

而静态的HTML页面可以部署在nginx中,从而大大提高并发能力,减小服务器访问压力。

如何实现静态化

目前,静态化页面都是通过模板引擎来生成,而后保存到nginx服务器来部署。常用的模板引擎比如:

  • Freemarker
  • Velocity
  • Thymeleaf

我们之前就使用的Thymeleaf,来渲染html返回给用户。Thymeleaf除了可以把渲染结果写入Response,也可以写到本地文件,从而实现静态化。

thymeleaf重要角色

Context:运行上下文

用来保存模型数据,当模板引擎渲染时,可以从Context上下文中获取数据用于渲染。

当与SpringBoot结合使用时,我们放入Model的数据就会被处理到Context,作为模板渲染的数据使用。

TemplateResolver:模板解析器

用来读取模板相关的配置,例如:模板存放的位置信息,模板文件名称,模板文件的类型等等。

当与SpringBoot结合时,TemplateResolver已经由其创建完成,并且各种配置也都有默认值,比如模板存放位置,其默认值就是:templates。比如模板文件类型,其默认值就是html。

TemplateEngine:模板引擎

模板引擎:用来解析模板的引擎,需要使用到上下文、模板解析器。分别从两者中获取模板中需要的数据,模板文件。然后利用内置的语法规则解析,从而输出解析后的文件。

通过templateEngine.process方法执行解析和页面生成过程

代码实现

Service代码

PageService中新增代码

    @Autowired
    private TemplateEngine templateEngine;

    @Value("ly.page.destPath")
    private String pagePath;

    /**
     * 生成商品详情页并保存到指定目录
     *
     * @param spuId 商品ID
     */
    public void createHtml(Long spuId) {
        Context context = new Context();
        context.setVariables(loadModel(spuId));

        // 输出流
        File file = new File(pagePath + "/" + spuId + ".html");
        try(PrintWriter printWriter = new PrintWriter(file)) {
            // 生成页面
            templateEngine.process(PageServiceConstants.TEMPLATE_NAME_ITEM, context, printWriter);
        } catch (FileNotFoundException e) {
            log.error("[生成商品详情页失败,商品ID = {}]", spuId, e);
        }
    }
Controller代码

修改PageController中的spuPageInfo方法

配置nginx代理静态资源

修改之前代理到8004端口的配置信息

采用静态页和tomcat加载两种方式,静态页目录未找到该静态页,就从数据库加载数据。

性能优化

生成商品详情页,使用线程池去执行生成静态页,进一步提高效率。

新增工具类

package com.leyou.page.util;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadUtils {

    private static final ExecutorService es = Executors.newFixedThreadPool(10);

    public static void execute(Runnable runnable) {
        es.submit(runnable);
    }
}

PageService新增方法

    /**
     * 新建线程处理页面静态化
     *
     * @param spuId 商品ID
     */
    public void asyncExcute(Long spuId) {
        ThreadUtils.execute(() -> createHtml(spuId));
    }

然后修改controller中的生成静态页方法的调用为 asyncExcute(spuId)即可。

遗留问题

做到这里,会想到,这个静态页生成了就一直不会变了,万一我的商品有了修改,或者下架了怎么办,怎么去完成商品信息的同步呢?使用服务间的调用?还是其他方式?

这个问题在下一个笔记中详细处理,会使用到消息中间件去完成商品信息的一个同步过程。

你可能感兴趣的:(乐优商城)