SpringCloud商城day08 商品详情页-2021-10-14

本文介绍了如何在SpringBoot中整合Thymeleaf以提高页面访问效率,并详细阐述了商品详情页的静态化过程,包括使用RabbitMQ监听数据变更,生成静态页面,以及部署到Nginx服务器进行加载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. Thymeleaf入门

1. thymeleaf: 页面静态化技术 -> 提前生成静态资源 -> 用户访问时加载到浏览器-> 提高访问效率减少占用资源

Thymeleaf is a modern server-side Java template engine for both web and standalone environments. 

2. SpringMVC整合thymeleaf模块 -> 六种模板

3. 创建springboot工程 -> 依赖starter-web/thymeleaf -> 启动类ThymeleafApplication.java

(1) yml配置: spring.thymeleaf.cache: false //页面加载时关闭缓存
(2) 新建页面 resourece/templates/*.html 

 4. thymeleaf语法:

(1) <html xmlns:th="http://www.thymeleaf.org">
    //表单提交
    <p th:text="${hello}"></p>
    <form th:action="@{/demo/test}">
        <input th:type="text" th:name="id">  //输入框属性名
        <button>提交</button>
    </form>
(2) 遍历对象: 
    <tr th:each="user, userStat:${userList}" >
        <td>
            <span th:text="${userStat.index}"></span>  // 0 1 2
        </td>
        <td th:text="${user.id}"></td>  // 1 2 3
    </tr>
(3) Map输出:
    <div th:each="map, mapStat:${dataMap}">
        <div th:text="${map}"></div>
        <span th:text="${mapStat.current.key}"></span>   //取键
        <span th:text="${mapStat.current.value}"></span> //取值
    </div>
(4) 数组输出:
    <div th:each="array, arrayStat:${names}">
        <span th:text="{arrayStat.count}"></span> //count属性 从1开始
    </div>
(5) 日期输出:
    <div th:text="${#dates.format(now, 'yyyy-MM-dd HH:mm:ss')}">
      
    </div>
(6) 条件判断:
    <div>
        <span th:if="${(age>=18)}">符合条件则输出</span>
    </div>
(7) 创建页面模块footer.html
 -> 引入命名空间 <html xmlns:th="http://www.thymeleaf.org">
 -> fragment模块:  <div id="C" th:fragment="copy"></div>
 -> demo.html引入模块: <div id="F" th:include="footer::copy"></div>

(8) th:utext="直接解析自带html标签"
      

二. 搜索页面渲染

1. 需求: 显示 用户勾选的搜索条件+商品属性规格+商品搜索结果集合

2. 实现: 客户端搜索 -> 搜索微服务查询索引 -> thymeleaf渲染静态化页面 -> 返回页面;

3. 步骤:

1. search微服务
(1) 添加thymeleaf依赖: spring-boot-starter-thymeleaf
(2) yml配置文件: thymeleaf.cache: false
(3) 添加页面静态化资源: 
    1) resources/static/js/img/css
    2) resources/templates/html
(4) 更新SearchController.java
    1)  类注解:@Controller   //不用@RestController, 新增方法返回值不是JSON
    2) 添加方法
    @GetMapping()
    public String list(){
        //特殊符号处理
        this. handleSearchMap(searchMap);
        //获取查询结果
        Map resultMap = searchService.search(searchMap);
        //携带数据
        model.addAttribute("searchMap", searchMap);
        model.addAttributes("resultMap", resultMap);
        //跳转搜索页面
        return "search";   
    }
(1) 修改SearchServiceImpl.java
 -> 转换规格字符串的集合[{"颜色":"黑", "尺码":"小",...},{"颜色":"白", "尺码":"中"},....]
 -> 转换成Map {颜色=[黑,白,...], 尺码=[小,中],...}
 -> thymeleaf遍历map得到数据

(2)实现规格转换:
    public Map<String, Set<String>> formatSpec(List<String> specList){
        //定义map
        Map<String, Set<String>> resultMap = new HashMap<>();
        if(specList !=null&&specList.size()>0){
            //遍历
            for(String specJasonString : specList){
                //jason转换为map
                Map<String, String> specMap = JASON.parseObject(specJasonString, Map.class);
                //遍历key 颜色/尺码
                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放入map
                    resultMap.put(specKey, specSet);
                }
            }
        }
        return resultMap;
    }
(1) 搜索关键字页面渲染 -> 搜索框条件 -> 数据回显-> 表单提交 -> 商品列表查询
    thymeleaf表单提交:
    <form th:action="@{/search/list/}'>
    <input th:type="text" id="autocomplete" name="keywords" th:value="${searchMap.keywords}">
    <button th:type="submit">

(2) 条件搜索实现: 搜索 -> 添加条件 -> URL+条件 -> 存入Model -> 取出URL -> 拼接条件 ->跳转
    //修改Controller, 拼装URL
    StringBuilder 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("&");
            }
        }
    String urlString = url.toString();
    //取出路径多余的&号
    urlString = urlString.subString(0,urlString.length()-1);
    model.addAttributes("url",urlString);
    }
    

1. 前端url拼接: 

<a th:text="${brand}" th:href="@{${url}(brand=${brand})}"></a>

2. 去除搜索条件: X 

<a th:href="@{${#strings.replace(url, '&price='+searchMap.price,'')}}">X</a>

3. 产品价格排序:  价格↑  价格↓

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

4. 分页查询实现: 

(1) 后端封装分页数据的实体类: Page.java -> 初始化分页数据
    public class Page<T> implements Serializable{
        //当前页为第一页
        public static final Integer pageNum=1;
        //默认每页显示
        public static final Integer pageSize=10;
        //判断当前页是否为空或<1
        public static Integer currenPagegNumber(Integer pageNum){
            if(num==pageNum||pageNum<1){
                pageNum=1;
            }
        return pageNum;
        }

        //设置当前页
        public voidsetCurrentPage(long currenpage, long total, long pagesize){
            //能整除正好分N页, 不能则N+1
            int totalPages = (int)(total%pagesize=0?total/pagesize:(total/pagesize)+1);

            //总页数
            this.last=totalPage;
            //判断当前页是否大于总页数, 越界则查最后一页
            if(currentpage>totalpages){
                this.currentpage=totalpages;
            }else{
                this.currentpage=currentpage;
           }
            //计算起始页
            this.start=(this.currentpage-1)*pagesize;
        }

        //计算起始页
        public void initPage(long total, int currenpage, 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;  //判断是否大于最大页数

            //页差<0, 起点页=起点页+页差
            //页差>=0, 起点和终点判断
            this.lpage=topdiv<0?this.lpage+topdiv:this.lpage;

            //起点页<=0, 结束页=|起点页|+1
            //起点页>0, 结束页
            this.rpage=this.lpage<=0?this.rpage+(this.lpage*-1)+1:this.rpage;
            
            //起点页<=0, 起点页成为第一页
            this.lpage=this.lpage<=?1:this.lpage;
            //结束页>总页数, 结束页=总页数
            this.rpage=this.rpage>last?this.last:this.rpage;
        }
    } 
    

       (2). SearchControler中封装分页数据:

//封装分页数据并返回
Page<SkuInfo> page = new Page<SkuInfo>{
    //总记录数
    Long.parseLong(String.valueOf(resultMap.get("total")));
    //设置当前页
    Integer.parseInt(String.valueOf(resultMap.get("pageNum")));
    Page.pageSize
}

        (3) 分页前端实现:

<a th:href="@{${url}(pageNum=${page.upper})}">上一页</a>
<a th:href="@{${url}(pageNum=${page.next})}">下一页</a>
<!--动态页码-->
<li th:each="i:${#numbers.sequence(page.lpage, page,rpage)}" th:class="${i}==${page.currentpage}?'active':''></li>

三. 商品详情页静态化

1. 提前生成商品详情页 -> 部署到高性能web服务器 -> 静态化加载 -> 无需请求后端服务

流程: 
->商品状态已审核
-> 发送商品spuId到rabbitMQ 
-> MQ发送消息到changgou_web渲染微服务 
-> 静态页生成渲染微服务监听消息 
-> 渲染微服务发送spuId远程调用商品微服务
-> 通过spuId查询goods
-> 使用thymeleaf生成商品详情页面

2. 静态页生成服务: changgou_service_page

service_page微服务:
(1) 添加依赖: common / starter-thymeleaf / amqp / goods-api
(2) 配置文件: pagepath: D:\items   #自定义属性, 静态页存储路径
(3) 启动类: com.changgou.page.PageApplication.java
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients(basePackages= {"com.changgou.goods.feign"})
    public class PageApplication{
        SpringApplication.run(PageApplication.class, args);
    }
(4) feign接口: changgou_service_goods_api
    @FeignClient(name="goods")
    public interface CategoryFeign{
        @GetMapping("/category/{id}")
        public Result<Category> findById(@PathVariable("id") Integer id);
    }

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

(5) 静态页生成代码:
    监听商品审核状态 -> 上架后发送消息spuId -> page服务监听消息 -> 接收spuId -> 调用goods服务查询数据 -> page服务生成静态页
@Service
public class PageServiceImpl implements PageService{
    //获取配置文件中的自定义属性
    @Value("${pagepath}")
    private String pagepath;       
    @Autowired
    private TemplateEngine templateEngine;
    @Override
    public void generateHtml(String spuId) {

        //1.获取context对象, 用于存储商品的相关数据
        Context context = new Context();
        //定义方法, 获取静态化页面数据
        Map<String, Object> itemData = this.getItemData(spuId);
        //存储静态化页面数据, 参数map
        context.setVariables(itemData);


        //2.获取商品详情页面存储位置
        File dir = new File(pagepath);

        //3.判断当前存储位置的文件夹是否存在, 不存在则新建
        if (!dir.exists()){
            dir.mkdirs();}

        //4.定义输出流, 完成文件生成
        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 (FileNotFoundException e) {
            e.printStackTrace();}
        //5. 关闭流
        try {out.close();} catch (IOException e) {
            e.printStackTrace();}
    }

    //获取静态化页面的相关数据
    private Map<String,Object> getItemData(String spuId) {
        Map<String, Object> resultMap = new HashMap<>();
        //1. 获取spu
        Result<Spu> spuById = spuFeign.findSpuById(spuId);
        Spu spu = spuById.getData();
        resultMap.put("spu", spu);

        //2. 获取图片信息
        if (spu != null){
            if (StringUtils.isNotEmpty(spu.getImages())){
                resultMap.put("imageList", spu.getImages().split(","));
            }
        }

        //3. 获取商品的分类信息
        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);

        //4. 获取sku信息
        List<Sku> skuList = skuFeign.findSkuListBySpuId(spuId);
        resultMap.put("skuList",skuList);

        //5. 获取商品规格信息
        resultMap.put("specificationList", JSON.parseObject(spu.getSpecItems(),Map.class));

        return resultMap;
    }
}

(1) com.changgou.page.config.RabbitMQConfig.java配置类

//定义队列
public static final String PAGE_CREATE_QUEUE="page_create_queue";

//声明队里
@Bean(PAGE_CREATE_QUEUE)
Publiv Queue newQueue(){ 
    return new Queue(PAGE_CREATE_QUEUE);
}

//绑定
@Bean
public Binding PAGE_CREATE_QUEUE_BINDING(@Qualifier(PAGE_CREATE_QUEUE)Queue queue, @Qualifier(GOODS_UP_EXCHAGNE)Exchange exchange){
    return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}

(2) 监听类: com.changgou.page.listener.PageListener.java

@Component
public class PageListener {
    @Autowired
    private PageService pageService;
    @RabbitListener(queues = RabbitMQConfig.PAGE_CREATE_QUEUE)
    public void receiveMessage(String spuId) {
        System.out.println("获取静态化页面的商品Id值为: " + spuId);
        //调用业务层生成静态化页面
        pageService.generateHtml(spuId);
    }
}

(3) 修改canal监控微服务的 spuListener 

SpuListener监听tb_spu表:
@CanalEventListener: 事件监听
@ListenPoint(schema="changgou_goods", table="tb_spu"): 监听点
//获取表数据改变前的数据
Map<String,String> oldData = CanalEntry.RowData rowData.getBeforeColumnsList() 
//获取表数据改变后的数据
Map<String,String> newData = CanalEntry.RowData rowData.getAfterColumnsList() 

//获取最新被审核通过的商品 status 0->1
if ("0".equals(oldData.get("status")) && "1".equals(newData.get("status"))){
    //将商品的spuid发送到mq
    rabbitTemplate.convertAndSend(RabbitMQConfig.GOODS_UP_EXCHANGE,"",newData.get("id"));
        }

(4) nginx静态资源服务器 -> 存储商品静态页

        1) nginx目录: usr/local/openresty/nginx/html -> 放入商品静态页

        2) 刷新nginx: systemctl restart openresty 

                或进入cd usr/local/openresty/nginx/sbin ->  输入命令./nginx -s reload

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值