Springboot整合搜索(Elasticsearch)页面渲染(thymeleaf)

前言:

  今天我们要做的是基于Elasticsearch搜索和thymeleaf渲染页面,在今天的任务中将简单的介绍thymeleaf的基本使用,以及结合项目如何使用thymeleaf渲染页面。
观看thymeleaf的基本使用我已经写了一篇博客,连接地址:https://www.jianshu.com/p/29afd801bcc4 通过上一篇《基于Elasticsearch商品搜索(模仿某东搜索页面的简单实现)》

基础数据渲染

(1)更新SearchController,定义跳转搜索结果页面方法

@GetMapping("list")
    public String list(@RequestParam Map<String, String> searchMap, Model model) {
        //对搜索入参带有特殊符号进行处理
        if (null != searchMap) {
            Set<Map.Entry<String, String>> entries = searchMap.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                if (entry.getKey().startsWith("spec_")) {
                    searchMap.put(entry.getKey(), entry.getValue().replace("+", "%2B"));
                }
            }
        }
       
        Map resultMap = searchService.search(searchMap);
       
    //设置视图数据
        //返回数据
        model.addAttribute("resultMap", resultMap);
        //查询条件数据
        model.addAttribute("searchMap", searchMap);
        return "search";
    }

(2) 搜索结果页面渲染

(2.1)用户选择条件回显

      <div class="bread">
            <ul class="fl sui-breadcrumb">
                <li>
                    <a href="#">全部结果</a>
                </li>
                <li class="active"><span th:text="${searchMap.keywords}"></span></li>
            </ul>
            <ul class="fl sui-tag">
                <!-- 品牌-->
                <li class="with-x" th:if="${#maps.containsKey(searchMap,'brand')}">品牌:
                    <span th:text="${searchMap.brand}"></span>
                   <i>×</i>
                </li>
                <!-- 价格-->
                <li class="with-x" th:if="${#maps.containsKey(searchMap,'price')}">价格:<span
                        th:text="${searchMap.price}"></span>
                        <i>×</i>
                </li>
                <!-- 规格-->
                <li class="with-x" th:each="sm:${searchMap}" th:if="${#strings.startsWith(sm.key,'spec_')}">
                    <span th:text="${#strings.replace(sm.key,'spec_','')}"></span>:
                    <span th:text="${#strings.replace(sm.value,'%2B','+')}"></span>
                    <i>×</i>
                </li>

            </ul>
            <form class="fl sui-form form-dark">
                <div class="input-control control-right">
                    <input type="text"/>
                    <i class="sui-icon icon-touch-magnifier"></i>
                </div>
            </form>
        </div>

(2.2)商品属性及规格显示

 <!--selector-->
        <div class="clearfix selector">
            <div class="type-wrap logo" th:unless="${#maps.containsKey(searchMap,'brand')}">
                <div class="fl key brand">品牌</div>
                <div class="value logos">
                    <ul class="logo-list">
                        <li th:each="brand,brandSate:${resultMap.brandList}">
                            <a th:text="${brand}" th:href="@{${url}(brand=${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:${resultMap.skuSpecList}"
                 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}" th:href="@{${url}('spec_'+${spec.key}=${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元" th:href="@{${url}(price='0-500')}"></a>
                        </li>
                        <li>
                            <a th:text="500-1000元" th:href="@{${url}(price='500-1000')}"></a>
                        </li>
                        <li>
                            <a th:text="1000-1500元" th:href="@{${url}(price='1000-1500')}"></a>
                        </li>
                        <li>
                            <a th:text="1500-2000元" th:href="@{${url}(price='1500-2000')}"></a>
                        </li>
                        <li>
                            <a th:text="2000-3000元" th:href="@{${url}(price='2000-3000')}"></a>
                        </li>
                        <li>
                            <a th:text="3000元以上" th:href="@{${url}(price='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>

(2.3)商品列表显示

<!--商品列表-->
            <div class="goods-list">
                <ul class="yui3-g">
                    <li class="yui3-u-1-5" th:each="sku,skuStat:${resultMap.rows}">
                        <div class="list-wrap">
                            <div class="p-img">
                               <a href="item.html" target="_blank"><img src="/img/_/mobile01.png" /></a>
                            </div>
                            <div class="price">
                                <strong>
                                    <em>¥</em>
                                    <i th:text="${sku.price}"></i>
                                </strong>
                            </div>
                            <div class="attr">
                                <a target="_blank" href="item.html" th:title="${sku.spec}" th:utext="${sku.name}"></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>

(3)关键字搜索

                        <div class="search">
                            <form th:action="@{/search/list}" class="sui-form form-inline">
                                <!--searchAutoComplete-->
                                <div class="input-append">
                                    <input th:type="text" id="autocomplete" name="keywords"
                                           th:value="${searchMap.keywords}" class="input-error input-xxlarge"/>
                                    <button class="sui-btn btn-xlarge btn-danger" th:type="submit">搜索</button>
                                </div>
                            </form>
                        </div>

(4) 条件搜索实现

用户每次点击搜索的时候,其实在上次搜索的基础之上加上了新的搜索条件,也就是在上一次请求的URL后面追加了新的搜索条件,我们可以在后台每次拼接组装出上次搜索的URL,然后每次将URL存入到Model中,页面每次点击不同条件的时候,从Model中取出上次请求的URL,然后再加上新点击的条件参数实现跳转即可。

(4.1)后台记录搜索URL

        StringBuilder url = new StringBuilder("/search/list");
        //拼接url
        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.addAttribute("url", urlString);
        } else {
            model.addAttribute("url", url);
        }

(4.2)页面搜索对接

        <div class="clearfix selector">
            <div class="type-wrap logo" th:unless="${#maps.containsKey(searchMap,'brand')}">
                <div class="fl key brand">品牌</div>
                <div class="value logos">
                    <ul class="logo-list">
                        <li th:each="brand,brandSate:${resultMap.brandList}">
                            <a th:text="${brand}" th:href="@{${url}(brand=${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:${resultMap.skuSpecList}"
                 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}" th:href="@{${url}('spec_'+${spec.key}=${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元" th:href="@{${url}(price='0-500')}"></a>
                        </li>
                        <li>
                            <a th:text="500-1000元" th:href="@{${url}(price='500-1000')}"></a>
                        </li>
                        <li>
                            <a th:text="1000-1500元" th:href="@{${url}(price='1000-1500')}"></a>
                        </li>
                        <li>
                            <a th:text="1500-2000元" th:href="@{${url}(price='1500-2000')}"></a>
                        </li>
                        <li>
                            <a th:text="2000-3000元" th:href="@{${url}(price='2000-3000')}"></a>
                        </li>
                        <li>
                            <a th:text="3000元以上" th:href="@{${url}(price='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>  

(5)移除搜索条件

用户点击条件搜索后,要将选中的条件显示出来,并提供移除条件的 x 按钮,显示条件我们可以从searchMap中获取,移除其实就是将之前的请求地址中的指定条件删除即可。

<ul class="fl sui-tag">
                <li class="with-x" th:if="${#maps.containsKey(searchMap,'brand')}">品牌:
                    <span th:text="${searchMap.brand}"></span>
                    <a th:href="@{${#strings.replace(url,'&brand='+searchMap.brand,'')}}">×</a>
                </li>

                <li class="with-x" th:if="${#maps.containsKey(searchMap,'price')}">价格:<span
                        th:text="${searchMap.price}"></span>
                    <a th:href="@{${#strings.replace(url,'&price='+searchMap.price,'')}}">×</a>
                </li>
                <!--规格-->
                <li class="with-x" th:each="sm:${searchMap}" th:if="${#strings.startsWith(sm.key,'spec_')}">
                    <span th:text="${#strings.replace(sm.key,'spec_','')}"></span>:
                    <span th:text="${#strings.replace(sm.value,'%2B','+')}"></span>
                    <a th:href="@{${#strings.replace(url,'&'+sm.key+'='+sm.value,'')}}">×</a>
                </li>
                
            </ul>

(6)排序

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

(7)分页

加入分页工具类——传送门:https://www.jianshu.com/p/acdffb8b3859
实现分页信息封装:

        //设置分页查询数据
        Page<Skuinfo> page = new Page<Skuinfo>(
                Long.parseLong(String.valueOf(resultMap.get("total"))),
                Integer.parseInt(String.valueOf(resultMap.get("pageNum"))),
                Page.pageSize
        );

看累了吧,来几张图片放松一下


86.jpg

87.jpg

88.jpg

89.jpg

90.jpg

91.jpg

92.jpg

商品详情页静态化

需求分析

  当系统审核完成商品,需要将商品详情页进行展示,那么采用静态页面生成的方能的web服务器中进行访问是比较合适的。所以,开发流程如下图所示:


流程图

执行步骤解释:
  系统管理员(商家运维人员)修改或者审核商品的时候, 会更改数据库中商品上架状态并发送商品id给rabbitMq中的上架交换器。
  上架交换器会将商品id发给静态页生成队列。
  静态页微服务设置监听器, 监听静态页生成队列, 根据商品id获取商品详细数据并使用thymeleaf的模板技术生成静态页。
静态页面生成服务中的具体代码实现:

1.创建PageListener监听类,监听page_create_queue队列,获取消息,并生成静态化页面

@Component
public class PageListener {

    @Autowired
    private PageService pageService;

    @RabbitListener(queues = RabbitmqConfig.PAGE_CREATE_QUEUE)
    public void receiveMessage(String spuId) {
        System.out.println("生成静态页面的spuId:" + spuId);
        pageService.generateHtml(spuId);
    }
}

2.PageServiceImpl实现:

@Service
public class PageServiceImpl implements PageService {

    @Value("${pagepath}")
    private String pagepath;

    @Autowired
    private TemplateEngine templateEngine;

    /**
     * @param spuId
     * @return void
     * @description: 生产静态化页面
     * @author 大佬味的小男孩
     * @date 2020/07/27 21:57
     */
    @Override
    public void generateHtml(String spuId) {

        //获取context对象
        Context context = new Context();
        //获取静态化页面的数据
        Map<String, Object> map = this.getItemData(spuId);
        context.setVariables(map);


        //获取商品存储位置(本地磁盘)
        File file = new File(pagepath);
        if (!file.exists()) {
            //判断存储静态化页面的文件夹是否存在  如果不存在 就创建
            file.mkdir();
        }
        //定义输出流  生产文件
        File file1 = new File(file + "/" + spuId + ".html");
        Writer out = null;
        try {
            out = new PrintWriter(file1);
            templateEngine.process("item", context, out);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭流
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Autowired
    private SpuFeign spuFeign;
    @Autowired
    private CategoryFeign categoryFeign;
    @Autowired
    private SkuFeign skuFeign;

    /**
     * @param spuId
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @description: //获取静态化页面的数据
     * @author 大佬味的小男孩
     * @date 2020/07/27 22:01
     */
    private Map<String, Object> getItemData(String spuId) {

        HashMap<String, Object> map = new HashMap<>();
        //获取spu数据
        Spu spu = spuFeign.findSpuById(spuId).getData();

        map.put("spu", spu);

        //获取图片数据
        if (spu != null) {
            if (!StringUtils.isEmpty(spu.getImages())) {
                map.put("ImageList", spu.getImages().split(","));
            }
        }

        //获取商品分类数据
        Category category1 = categoryFeign.findById(spu.getCategory1Id());
        Category category2 = categoryFeign.findById(spu.getCategory2Id());
        Category category3 = categoryFeign.findById(spu.getCategory3Id());
        map.put("category1", category1);
        map.put("category2", category2);
        map.put("category3", category3);

        //获取sku的数据
        List<Sku> skuList = skuFeign.findSkuListByspuid(spuId);
        map.put("skuList", skuList);

        //获取规格信息
        map.put("specifcationList", JSON.parseObject(spu.getSpecItems(), Map.class));

        return map;
    }
}

3. 数据监控服务我就不演示了

4 模板填充

(1)面包屑数据
        <div class="crumb-wrap">
            <ul class="sui-breadcrumb">
                <li>
                    <a href="#" th:text="${category1.name}"></a>
                </li>
                <li>
                    <a href="#" th:text="${category2.name}"></a>
                </li>
                <li>
                    <a href="#" th:text="${category3.name}"></a>
                </li>
            </ul>
        </div>
(2)商品图片
            <div class="fl preview-wrap">
                <!--放大镜效果-->
                <div class="zoom">
                    <!--默认第一个预览-->
                    <div id="preview" class="spec-preview">
                        <span class="jqzoom"><img th:jqimg="${ImageList[0]}" th:src="${ImageList[0]}"/></span>
                    </div>
                    <!--下方的缩略图-->
                    <div class="spec-scroll">
                        <a class="prev">&lt;</a>
                        <!--左右按钮-->
                        <div class="items">
                            <ul>
                                <li th:each="img:${ImageList}"><img th:src="${img}" th:bimg="${img}"  onmousemove="preview(this)"/></li>
                            </ul>
                        </div>
                        <a class="next">&gt;</a>
                    </div>
                </div>
            </div>
(3)规格输出
                    <div id="specification" class="summary-wrap clearfix">
                        <!--循环MAP-->
                        <dl th:each="spec,specStat:${specifcationList}">
                            <dt>
                                <div class="fl title">
                                    <i th:text="${spec.key}"></i>
                                </div>
                            </dt>
                            <dd th:each="arrValue:${specStat.current.value}">
                                <a href="javascript:;"
                                   th:v-bind:class="|{selected:sel('${spec.key}','${arrValue}')}|"
                                   th:@click="|selectSpecification('${spec.key}','${arrValue}')|">
                                    <i th:text="${arrValue}"></i>
                                    <span title="点击取消选择">&nbsp;</span>
                                </a>
                            </dd>
                        </dl>
                    </div>
(4)默认SKU显示

  静态页生成后,需要显示默认的Sku,我们这里默认显示第1个Sku即可,这里可以结合着Vue一起实现。可以先定义一个集合,再定义一个spec和sku,用来存储当前选中的Sku信息和Sku的规格,代码如下:

<script th:inline="javascript">
    var item = new Vue({
        el: '#itemArray',
        data: {
            skuList: [[${skuList}]],   //SKU集合
            sku: {},                     //当前选中的sku
            spec: {},                   //选中的sku的规格
            num: 1
        },
        created: function () {
            //默认选中第1个SKU,深克隆
            this.sku = JSON.parse(JSON.stringify(this.skuList[0]));
            //第1个SKU的规格,深克隆
            this.spec = JSON.parse(this.skuList[0].spec);
        }
    })
</script>

最后请原谅我不知道怎么总结前端页面!

记得一键三连

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343