商品详情页静态化工程搭建
商品详情页静态化功能实现
1.详情页静态化操作
2.填充基础数据 Spu、List
3.规格切换
搜索页面渲染
1.数据展示
2.搜索条件展示
3.实现条件搜索控制
thymeleaf是一个XML/XHTML/HTML5模板引擎,可于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández创建,该作者还是Java加密库Jasypt的作者。
模板:将一些重复内容写好,其中某些可能发生变化的内容,采用占位符方式动态加载(JSP)
模板引擎技术:可以基于写好的模板,动态给写好的模板加载数据。
Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。
使用springboot 来集成使用Thymeleaf可以大大减少单纯使用thymleaf的代码量,所以我们接下来使用springboot集成使用thymeleaf.
实现的步骤为:
SpringMVC回顾:
1.创建工程
2.引入依赖包
3.web.xml-核心前端控制器、POST请求乱码过滤器
4.SpringMVC核心配置文件
视图解析器 前缀(/WEB-INF/pages/)、后缀(.jsp)
包扫描
静态资源过滤
注解驱动
(1)创建工程
创建一个独立的工程springboot-thymeleaf,该工程为案例工程,不需要放到changgou-parent工程中。
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">
<modelVersion>4.0.0modelVersion>
<groupId>com.itheimagroupId>
<artifactId>springboot-thymeleafartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.4.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
dependencies>
project>
(2)测试
创建启动类com.itheima.ThymeleafApplication
,代码如下:
@SpringBootApplication
public class ThymeleafApplication {
public static void main(String[] args) {
SpringApplication.run(ThymeleafApplication.class,args);
}
}
(3)控制层
创建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";
}
}
(4)修改application.yml配置
在这里,其实还有一些默认配置,比如视图前缀:classpath:/templates/,视图后缀:.html
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties
部分源码如下:
(5)创建html
在resources中创建templates目录,在templates目录创建 demo1.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>
<p th:text="${hello}">p>
body>
html>
解释:
:这句声明使用thymeleaf标签
:这句使用 th:text="${变量名}" 表示 使用thymeleaf获取文本数据,类似于EL表达式。
(6)关闭缓存
创建application.yml,并设置thymeleaf的缓存设置,设置为false。默认加缓存的,用于测试。
spring:
thymeleaf:
cache: false
启动系统,并在浏览器访问
http://localhost:8080/test/hello
(1)文本输出
普通文本输出
<p th:text="${description}">
上面的输出会将数据全部以文本输出,无法显示html标签效果,效果如下:
th:utext
输出文本可以识别html标签:
<p th:utext="${description}">p>
效果如下:
(2)th:each
对象遍历,功能类似jstl中的
标签。
创建com.itheima.model.User,代码如下:
public class User {
private Integer id;
private String name;
private String address;
public User() {
}
public User(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = 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}">//userStat表示被循环对象de
<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)Date输出
后台添加日期
//日期
model.addAttribute("now",new Date());
页面输出
测试效果
(4)条件判断
th:if条件判断
后台添加年龄
//if条件
model.addAttribute("age",22);
页面输出
<div>
<span th:if="${(age>=18)}">终于长大了!span>
div>
测试效果
反向判断th:unless
,代码如下:
<p>th:unless 反向判断 age小于18岁的条件不成立的时候输出成年人p>
<div>
<span th:unless="${age<18}">成年人span>
div>
效果如下:
(4)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>
判断Map是否存在某个Key
<p>if判断Map中是否包含某个keyp>
<div th:if="${#maps.containsKey(dataMap,'address')}">
<span th:text="${dataMap.address}">span>
div>
(5)字符处理
在后台存储一个name数据:
//字符判断和处理
model.addAttribute("name","spec_小红");
以指定字符开始判断:
<p>判断是否以spec_开始,如果是,则输出name值p>
<div th:if="${#strings.startsWith(name,'spec_')}">
<span th:text="${name}">span>
div>
去掉字符中指定字符串:
<p>将name中的spec_替换成空数据p>
<div th:text="${#strings.replace(name,'spec_','')}">div>
(6)超链接处理
超链接使用th:href
语法是th:href="@{url(name=xx,age=xx)}"
,案例如下:
在后台添加一个url参数
//添加一个地址
model.addAttribute("url", "/test/add");
在后台添加一个/test/add
的跳转地址方法
/**
* 接收前端数据
* @return
*/
@RequestMapping("/add")
public String add(String name,String address,Model model) {
System.out.println(name+" 住址是 "+address);
return "redirect:http://www.itheima.com";
}
前端跳转配置:
<p>跳转地址p>
<a th:href="@{${url}(name='王五',address='深圳')}">跳转到/test/add方法a>
(7)图片
<p>图片显示p>
<img th:src="'http://www.itheima.com/images/logo.png'">
(8)递增输出
输出1-10的数据
<p>输出1-10的数据p>
<div th:each="i:${#numbers.sequence(1,10,1)}">
<span th:text="${i}">span>
div>
搜索页面要显示的内容主要分为3块。
1)搜索的数据结果
2)筛选出的数据搜索条件
3)用户已经勾选的数据条件
搜索的业务流程如上图,用户每次搜索的时候,先经过搜索业务工程,搜索业务工程调用搜索微服务工程,这里搜索业务工程单独挪出来的原因是它这里涉及到了模板渲染以及其他综合业务处理,以后很有可能会有移动端的搜索和PC端的搜索,后端渲染如果直接在搜索微服务中进行,会对微服务造成一定的侵入,不推荐这么做,推荐微服务独立,只提供服务,如果有其他页面渲染操作,可以搭建一个独立的消费工程调用微服务达到目的。
(1)工程创建
在changgou-web工程中创建changgou-web-search工程,并在changgou-web的pom.xml中引入如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<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-amqpartifactId>
dependency>
dependencies>
(2)静态资源导入
将资源中的页面/前端页面/search.html
拷贝到工程的resources/templates
目录下,js、css等拷贝到static
目录下,如下图:
记住将search.html中的相对路径全部改成绝对路径,可以把search.html中路径配置的 ./
全部换成/
即可。这里如果不改的话,页面显示图片和其他静态资源会报404。
(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);
}
(4)changgou-web-search的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>changgou-webartifactId>
<groupId>com.changgougroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>changgou-web-searchartifactId>
<dependencies>
<dependency>
<groupId>com.changgougroupId>
<artifactId>changgou-service-search-apiartifactId>
<version>1.0-SNAPSHOTversion>
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: 18086
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
spring:
thymeleaf:
cache: false
application:
name: search-web
main:
allow-bean-definition-overriding: true
# 修改服务地址轮询策略,默认是轮询,配置之后变随机
ribbon:
ConnectTimeout: 10000 # 连接超时时间
ReadTimeout: 10000 # 数据读取超时时间
(8)项目完整结构
在search.html的头部引入thymeleaf标签
<html xmlns:th="http://www.thymeleaf.org">
测试:http://localhost:18086/search
,效果如下:
后端搜索到数据后,前端页面进行数据显示,显示的数据分为3部分
1)搜索的数据结果
2)筛选出的数据搜索条件
3)用户已经勾选的数据条件
用户每次输入关键字的时候,直接根据关键字搜索,关键字搜索的数据会存储到result.rows
中,页面每次根据result获取rows,然后循环输出即可,同时页面的搜索框每次需要回显搜索的关键词。
实现思路
1.前端表单提交搜索的关键词
2.后端根据关键词进行搜索
3.将搜索条件存储到Model中
4.页面循环迭代输出数据
5.搜索表单回显搜索的关键词
(1)后台搜索实现
修改SkuController的search方法,代码如下:
代码如下:
/***
* 搜索
*/
@GetMapping(value = "/list")
public String search(@RequestParam(required = false) Map searchMap, Model model){
//调用搜索
Map resultMap = skuFeign.search(searchMap);
model.addAttribute("result",resultMap);
//搜索条件存储用于页面回显
model.addAttribute("searchMap",searchMap);
return "search";
}
(2)页面搜索实现
修改search.html
注意:搜索按钮为submit提交。
(3)页面结果输出
修改search.html,代码如下:
(4)测试
搜索华为
关键字,效果如下:
搜索条件除了关键字外,还有分类、品牌、以及规格,这些在我们前面已经将数据存入到了Map中,我们可以直接从Map中将数据取出,然后在页面输出即可。
分类:result.categoryList
品牌:result.brandList
规格:result.specList
修改search.html的条件显示部分,代码如下:
上图代码如下:
<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>0-500元a>
li>
<li>
<a>500-1000元a>
li>
<li>
<a>1000-1500元a>
li>
<li>
<a>1500-2000元a>
li>
<li>
<a>2000-3000元a>
li>
<li>
<a>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
用户每次点击搜索的时候,其实在上次搜索的基础之上加上了新的搜索条件,也就是在上一次请求的URL后面追加了新的搜索条件,我们可以在后台每次拼接组装出上次搜索的URL,然后每次将URL存入到Model中,页面每次点击不同条件的时候,从Model中取出上次请求的URL,然后再加上新点击的条件参数实现跳转即可。
(1)后台记录搜索URL
修改SkuController,添加组装URL的方法,并将组装好的URL存储起来,代码如下:
(2)页面搜索对接
th:href 这里是超链接的语法,例如:th:href="@{${url}(price='500-1000元')}"
表示请求地址是取url
参数的值,同时向后台传递参数price的值为500-100元。
如上图,用户点击条件搜索后,要将选中的条件显示出来,并提供移除条件的x
按钮,显示条件我们可以从searchMap中获取,移除其实就是将之前的请求地址中的指定条件删除即可。
(1)条件显示
修改search.html,代码如下:
解释:
${#strings.startsWith(sm.key,'spec_')}:表示以spec_开始的key
${#strings.replace(sm.key,'spec_','')}:表示将sm.key中的spec_替换成空
(2)移除搜素条件
修改search.html,移除分类、品牌、价格、规格搜索条件,代码如下:
上图代码是排序代码,需要2个属性,sortRule
:排序规则,ASC或者DESC,sortField
:排序的域,前端每次只需要将这2个域的值传入到后台即可实现排序。
(1)排序URL处理
在今天课件中有一个工具包UrlUtils.java
可以把它拷贝到changgou-common
工程中,使用该工具类可以去掉URL地址中的指定参数。
修改changgou-web-search
的SkuController
类中的搜索方法,添加排序路径的处理,代码如下:
(2)前端排序实现
修改search.html,实现排序,代码如下:
这一块我们实现了价格排序,同学们课后去实现以下销量和新品排序。
真实的分页应该像百度那样,如下图:
(1)分页工具类定义
在今天的课件中有一个Page.java
分页工具,可以用该分页工具处理翻页数据,我们将该工具拷贝到changgou-common
工程中,可以直接使用,该工具类的代码如下:
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",builder.build().getPageable().getPageNumber()+1);
resultMap.put("pageSize",builder.build().getPageable().getPageSize());
修改SkuController,实现分页信息封装,代码如下:
(3)页面分页实现
修改search.html,实现分页查询,代码如下:
注意:每次如果搜条件发生变化都要从第1页查询,而点击下一页的时候,分页数据在页面给出,不需要在后台拼接的url中给出,每次url都需要去掉pageNum参数,代码如下:
该微服务只用于生成商品静态页,不做其他事情。
(1)在changgou-web下创建一个名称为changgou-web-item的模块,并且将item.html静态页拷贝到工程的templates目录下,如图:
(2)changgou-web-item中添加起步依赖,如下
<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-webartifactId>
<groupId>com.changgougroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>changgou-web-itemartifactId>
<description>
商品详情静态页生成微服务
description>
<dependencies>
<dependency>
<groupId>com.changgougroupId>
<artifactId>changgou-service-goods-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
(3)修改application.yml的配置
server:
port: 18087
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
spring:
thymeleaf:
cache: false
application:
name: item
main:
allow-bean-definition-overriding: true
# 生成静态页的位置
pagepath: D:/items
(4)创建系统启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.changgou.goods.feign")
public class ItemApplication {
public static void main(String[] args) {
SpringApplication.run(ItemApplication.class,args);
}
}
页面发送请求,传递要生成的静态页的的商品的SpuID.后台controller 接收请求,调用thyemleaf的原生API生成商品静态页。
上图是要生成的商品详情页,从图片上可以看出需要查询SPU的3个分类作为面包屑显示,同时还需要查询SKU和SPU信息。
(1)在changgou-web-item
中创建com.changgou.item.service.PageService
接口,并在接口中添加生成静态页的方法,代码如下:
public interface PageService {
/**
* 根据SPUID生成静态页
* @param id : SpuId
*/
void createPageHtml(String id);
}
(2)在changgou-web-item
中创建com.changgou.item.service.impl.PageServiceImpl
接口,并在接口中添加生成静态页的方法,代码如下:
@Service
public class PageServiceImpl implements PageService {
//静态文件所存储的位置
@Value("${pagepath}")
private String pagepath;
//模板引擎对象
@Autowired
private TemplateEngine templateEngine;
/**
* 根据SPUID生成静态页
* @param id : SpuId
*/
@Override
public void createPageHtml(String id) {
try {
//创建上下文对象
Context context = new Context();
//数据模型,用于存储页面需要填充的数据
Map<String,Object> dataModel = new HashMap<String,Object>();
context.setVariables(dataModel);
//生成的静态页的位置
File dest = new File(pagepath,id+".html");
//生成静态页
PrintWriter printWriter = new PrintWriter(dest,"UTF-8");
/**
* 1:模板名字
* 2:上下文对象
* 3:文件输出对象
*/
templateEngine.process("item",context,printWriter);
} catch (Exception e) {
e.printStackTrace();
}
}
}
(3)在changgou-web-item
中创建com.changgou.item.controller.PageController
,代码如下:
@RestController
@RequestMapping(value = "/page")
public class PageController {
@Autowired
private PageService pageService;
/***
* 创建静态页
*/
@GetMapping(value = "/createHtml/{id}")
public Result createHtml(@PathVariable(value = "id")String id){
//生成静态页
pageService.createPageHtml(id);
return new Result(true, StatusCode.OK,"生成成功!");
}
}
启动服务测试生成静态页:
在D盘的items目录下会生成静态文件,我们可以把静态文件所需的样式一起拷贝到该目录中来,让页面正常显示。
页面效果如下:
我们需要调用changgou-service-goods
微服务获取商品详情页数据,这里会用到feign调用,我们分别要获取分类数据、Spu数据、Sku数据。
一会儿需要查询SPU和SKU以及Category,所以我们需要先创建Feign,修改changgou-service-goods-api,添加CategoryFeign,并在CategoryFeign中添加根据ID查询分类数据,代码如下:
@FeignClient(name="goods")
@RequestMapping(value = "/category")
public interface CategoryFeign {
/**
* 获取分类的对象信息
* @param id
* @return
*/
@GetMapping("/{id}")
Result<Category> findById(@PathVariable(name = "id") Integer id);
}
在changgou-service-goods-api,添加SkuFeign,并添加根据SpuID查询Sku集合,代码如下:
/**
* 根据条件搜索
* @param sku
* @return
*/
@PostMapping(value = "/search" )
Result<List<Sku>> findList(@RequestBody(required = false) Sku sku);
在changgou-service-goods-api,添加SpuFeign,并添加根据SpuID查询Spu信息,代码如下:
@FeignClient(name="goods")
@RequestMapping(value = "/spu")
public interface SpuFeign {
/***
* 根据SpuID查询Spu信息
* @param id
* @return
*/
@GetMapping("/{id}")
Result<Spu> findById(@PathVariable(name = "id") String id);
}
实现类:
上图代码如下:
@Service
public class PageServiceImpl implements PageService {
//静态文件所存储的位置
@Value("${pagepath}")
private String pagepath;
//模板引擎对象
@Autowired
private TemplateEngine templateEngine;
@Autowired
private SpuFeign spuFeign;
@Autowired
private CategoryFeign categoryFeign;
@Autowired
private SkuFeign skuFeign;
/***
* 加载静态页所需的数据
*/
public Map<String,Object> buildDataModel(String spuid){
//创建一个Map用于存储静态页所有数据
Map<String,Object> dataMap = new HashMap<String,Object>();
//查询Spu
Result<Spu> spuResult = spuFeign.findById(spuid);
Spu spu = spuResult.getData();
//List查询
Sku sku = new Sku();
sku.setSpuId(spuid);
Result<List<Sku>> skuResult = skuFeign.findList(sku);
List<Sku> skuList = skuResult.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());
//商品图片集合
dataMap.put("imageList",spu.getImages().split(","));
//Spu存储
dataMap.put("spu",spu);
//List存储
dataMap.put("skuList",skuList);
//specList
dataMap.put("specList", JSON.parseObject(spu.getSpecItems(),Map.class));
return dataMap;
}
/**
* 根据SPUID生成静态页
* @param id : SpuId
*/
@Override
public void createPageHtml(String id) {
try {
//创建上下文对象
Context context = new Context();
//数据模型,用于存储页面需要填充的数据
Map<String,Object> dataModel = buildDataModel(id);
context.setVariables(dataModel);
//生成的静态页的位置
File dest = new File(pagepath,id+".html");
//生成静态页
PrintWriter printWriter = new PrintWriter(dest,"UTF-8");
/**
* 1:模板名字
* 2:上下文对象
* 3:文件输出对象
*/
templateEngine.process("item",context,printWriter);
} catch (Exception e) {
e.printStackTrace();
}
}
}
首先将页面的html换成
(1)面包屑数据
修改item.html,填充三个分类数据作为面包屑,代码如下:
(2)商品图片
修改item.html,将商品图片信息输出,在真实工作中需要做空判断,代码如下:
(3)规格输出
生成静态页后效果如下:
(4)默认SKU显示
静态页生成后,需要显示默认的Sku,我们这里默认显示第1个Sku即可,这里可以结合着Vue一起实现。可以先定义一个集合,再定义一个spec和sku,用来存储当前选中的Sku信息和Sku的规格,代码如下:
页面显示默认的Sku信息
选中当前的Sku
(5)记录选中的Sku
在当前Spu的所有Sku中spec值是唯一的,我们可以根据spec来判断用户选中的是哪个Sku,我们可以在Vue中添加代码来实现,代码如下:
添加规格点击事件
如何判断两个list相等
页面效果如下:
添加一个js方法用于添加购买数量,代码如下:
调用:
点击加入购物车的时候,我们这里暂时只提示加入购物车的信息即可,等到了后面再实现购物车的统一操作。
添加购物车的JS方法:
页面调用: