本文介绍了27.博客系统项目开发之搜索页面制作求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。
对技术面试,学习经验等有一些体会,在此分享。
Spring Boot 博客系统项目开发之搜索页面制作
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
首页所有的功能基本都开发完成,首屏页面上大部分显示区域都实现了数据交互,当然,细心的同学也会发现我们还有一处搜索框没有做任何的更改也没有做任何的数据交互,搜索框即是首屏页面实现中漏掉的一个功能,也就是我们本节实验需要实现的搜索功能,这一节会实现博客页面所有的搜索功能,包括搜索框搜索(关键字搜索)、按照分类搜索、按照标签搜索。
知识点
- 关键字搜索功能
- 搜索页面制作
- 分类搜索功能
- 标签搜索功能
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
关键字搜索功能实现
页面设计
接下来就是我们本次实验的第一个部分–关键字搜索功能实现,首先来看一下最终的搜索页展现效果来确认该页面的布局设计,如下图所示:
该页面与首页极度相似,也包括顶部导航栏、文章列表区域、底部页脚区域,不同的是缺少了侧边工具栏区域,顶部区域和底部区域是公共区域,这里就不再赘述,重点关注一下图中标注的三个地方,如上图所示,分别标注了三个区域:
- 搜索页副标题区域:这个区域处于整个搜索页面功能区的顶部,用于显示搜索信息,因为后续根据分类搜索和根据标签搜索功能也会用到该页面,在显示搜索信息的同时也对搜索功能进行区分;
- 文章列表区域:用于展示文章列表,显示文章的概览信息,与首页不同的是该页面没有侧边工具栏区域所以文章列表区域会更大一些;
- 分页导航区域:放置分页按钮,用于分页跳转功能。
数据格式定义
如下图所示即为博客列表中需要渲染的内容,首先博客列表肯定是一个 List 对象,但是因为有分页功能,所以还需要返回分页字段,因此最终接收到的结果返回格式为 PageResult 对象,而列表中的单项对象中的字段则需要通过下图中的内容进行确认。
通过图片我们可以看到博客标题字段、预览图字段、分类名称字段、分类图片字段,当然这里通常会设计成可跳转的形式,即点击标题或者预览图后会跳转到对应的博客详情页面中,点击分类名称或者分类图片也会跳转到对应的博客分类页面,因此还需要一个博客实体的 id 字段和分类实体的 id 字段,因此返回数据的格式就得出来了,与首页的数据模型一样也是使用 BlogListVO 对象。
发起搜索请求
接下来就是讲解一下整个搜索功能的第一步–发起搜索请求,该操作通过提交搜索框中的内容来实现。
- 首先需要对搜索框所在的 form 表单进行代码修改,这部分内容在 header.html,修改如下:(注:完整代码位于 resources/templates/blog/header.html)
<form method="get" onsubmit="return false;" accept-charset="utf-8"> <input class="searchfield" id="searchbox" type="text" placeholder=" 搜索" /> <button class="searchbutton" id="searchbutton" onclick="search()"> <i class="fa fa-search"></i> </button> </form>
修改 form 表单的 onsubmit
事件,同时增加搜索按钮的 onclick()
事件,在用户点击搜索按钮后执行 search()
方法。
- 在 resources/static/blog/js 目录下新增 search.js,新增如下代码:
$(function () { $('#searchbox').keypress(function (e) { var key = e.which; //e.which是按键的值 if (key == 13) { var q = $(this).val(); if (q && q != '') { window.location.href = '/search/' + q; } } }); }); function search() { var q = $('#searchbox').val(); if (q && q != '') { window.location.href = '/search/' + q; } }
该 js 文件中定义了 search()
方法的实现,在用户点击搜索按钮后会将用户输入的字段取出来并放入搜索请求的路径中进行请求,同时也定义了输入框的 keypress()
事件,当用户在输入框中输入需要搜索的字段后敲下 Enter 回车键,也可以发起搜索请求。
- 最后修改 blog/index.html ,增加 search.js 的引用代码:
<script th:src="@{/blog/js/search.js}"></script>
数据查询实现
接下来是数据查询的功能实现,上述博客列表中的字段可以通过直接查询 tb_blog 文章表和 tb_blog_category 表来获取,同时需要注意分页功能实现,传参时需要传入关键字和页码,实现逻辑如下。
首先,定义 service 方法,业务层代码如下(注:完整代码位于 com.site.blog.my.core.service.impl.BlogServiceImpl.java):
/** * 根据搜索关键字获取首页文章列表 * * @param page * @return */ public PageResult getBlogsPageBySearch(String keyword, int page) { if (page > 0 && PatternUtil.validKeyword(keyword)) { Map param = new HashMap(); param.put("page", page); param.put("limit", 9); param.put("keyword", keyword); param.put("blogStatus", 1);//过滤发布状态下的数据 PageQueryUtil pageUtil = new PageQueryUtil(param); List<Blog> blogList = blogMapper.findBlogList(pageUtil); List<BlogListVO> blogListVOS = getBlogListVOsByBlogs(blogList); int total = blogMapper.getTotalBlogs(pageUtil); PageResult pageResult = new PageResult(blogListVOS, total, pageUtil.getLimit(), pageUtil.getPage()); return pageResult; } return null; } /** * 数据填充 */ private List<BlogListVO> getBlogListVOsByBlogs(List<Blog> blogList) { List<BlogListVO> blogListVOS = new ArrayList<>(); if (!CollectionUtils.isEmpty(blogList)) { List<Integer> categoryIds = blogList.stream().map(Blog::getBlogCategoryId).collect(Collectors.toList()); Map<Integer, String> blogCategoryMap = new HashMap<>(); if (!CollectionUtils.isEmpty(categoryIds)) { List<BlogCategory> blogCategories = categoryMapper.selectByCategoryIds(categoryIds); if (!CollectionUtils.isEmpty(blogCategories)) { blogCategoryMap = blogCategories.stream().collect(Collectors.toMap(BlogCategory::getCategoryId, BlogCategory::getCategoryIcon, (key1, key2) -> key2)); } } for (Blog blog : blogList) { BlogListVO blogListVO = new BlogListVO(); BeanUtils.copyProperties(blog, blogListVO); if (blogCategoryMap.containsKey(blog.getBlogCategoryId())) { blogListVO.setBlogCategoryIcon(blogCategoryMap.get(blog.getBlogCategoryId())); } else { blogListVO.setBlogCategoryId(0); blogListVO.setBlogCategoryName("默认分类"); blogListVO.setBlogCategoryIcon("/admin/dist/img/category/1.png"); } blogListVOS.add(blogListVO); } } return blogListVOS; }
我们定义了 getBlogsPageBySearch()
方法并传入 keyword 和 page 参数,keyword 用来过滤想要的文章列表,page 用于确定查询第几页的数据,之后通过 SQL 查询出对应的分页数据,再之后是填充数据,某些字段是 tb_blog 表中没有的,所以再去分类表中查询并设置到 BlogListVO 对象中,最终返回的数据是 PageResult 对象。
具体的 SQL 语句如下(注:完整代码位于 resources/mapper/BlogMapper.xml):
<select id="findBlogList" parameterType="Map" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from tb_blog where is_deleted=0 <if test="keyword!=null"> AND (blog_title like CONCAT('%','${keyword}','%' ) or blog_category_name like CONCAT('%','${keyword}','%' )) </if> <if test="blogStatus!=null"> AND blog_status = #{blogStatus} </if> order by blog_id desc <if test="start!=null and limit!=null"> limit #{start},#{limit} </if> </select> <select id="getTotalBlogs" parameterType="Map" resultType="int"> select count(*) from tb_blog where is_deleted=0 <if test="keyword!=null"> AND (blog_title like CONCAT('%','${keyword}','%' ) or blog_category_name like CONCAT('%','${keyword}','%' )) </if> <if test="blogStatus!=null"> AND blog_status = #{blogStatus} </if> </select>
在原来查询博客列表 SQL 的基础上增加了对于 blog_title 字段和 blog_category_name 的过滤,使用 LIKE 语法对博客记录进行检索。
数据渲染
想要将数据通过 Thymeleaf 语法渲染到前端页面上,首先需要将数据获取并转发到对应的模板页面中,需要在 Controller 方法中将查询到的数据放入 request 域中,新增 search() 方法,代码如下:(注:完整代码位于 com.site.blog.my.core.controller.blog.MyBlogController)
/** * 搜索列表页 * * @return */ @GetMapping({"/search/{keyword}"}) public String search(HttpServletRequest request, @PathVariable("keyword") String keyword) { return search(request, keyword, 1); } /** * 搜索列表页 * * @return */ @GetMapping({"/search/{keyword}/{page}"}) public String search(HttpServletRequest request, @PathVariable("keyword") String keyword, @PathVariable("page") Integer page) { PageResult blogPageResult = blogService.getBlogsPageBySearch(keyword, page); request.setAttribute("blogPageResult", blogPageResult); request.setAttribute("pageName", "搜索"); request.setAttribute("pageUrl", "search"); request.setAttribute("keyword", keyword); return "blog/list"; }
路径映射为 /search/{keyword}/{page},page 参数不传的话默认为第 1 页,根据页码和关键字查询出对应的分页数据 blogPageResult 并放入到 request 对象中,之后跳转到 list 模板页面进行数据渲染,需要注意的是 pageName 字段、 pageUrl 字段和 keyword 字段,这些字段主要是为了搜索页副标题区域的显示和翻页链接的拼接。
新增搜索页 list.html ,模板代码如下:(注:完整代码位于 resources/templates/blog/list.html)
“`html
<div class="blog-category" th:utext="${blog.blogCategoryName}"> </div> </a> </div> </header> </article> </div> <th:block th:if="${iterStat.last and iterStat.count%3==1}"> <div class="col-md-4 col-sm-4 blog-main-card"></div> <div class="col-md-4 col-sm-4 blog-main-card"></div> </th:block> <th:block th:if="${iterStat.last and iterStat.count%3==2}"> <div class="col-md-4 col-sm-4 blog-main-card"></div> </th:block> </th:block> </th:block> <th:block th:if="${null != blogPageResult}"> <ul class="blog-pagination"> <li th:class="${blogPageResult.currPage==1}?'disabled' : ''"><a th:href="@{${blogPageResult.currPage==1}?'##':'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-1}}">«</a> </li> <li th:if="${blogPageResult.currPage-3 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-3}}" th:text="${blogPageResult.currPage -3}">1</a></li> <li th:if="${blogPageResult.currPage-2 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-2}}" th:text="${blogPageResult.currPage -2}">1</a></li> <li th:if="${blogPageResult.currPage-1 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-1}}" th:text="${blogPageResult.currPage -1}">1</a></li> <li class="active"><a href="#" th:text="${blogPageResult.currPage}">1</a></li> <li th:if="${blogPageResult.currPage+1 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+1}}" th:text="${blogPageResult.currPage +1}">1</a></li> <li th:if="${blogPageResult.currPage+2 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+2}}" th:text="${blogPageResult.currPage +2}">1</a></li> <li th:if="${blogPageResult.currPage+3 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+3}}" th:text="${blogPageResult.currPage +3}">1</a></li> <li th:class="${blogPageResult.currPage==blogPageResult.totalPage}?'disabled' : ''"><a th:href="@{${blogPageResult.currPage==blogPageResult.totalPage}?'##' : '/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+1}}">»</a> </li> </ul>
Spring Boot 博客系统项目开发之搜索页面制作
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
首页所有的功能基本都开发完成,首屏页面上大部分显示区域都实现了数据交互,当然,细心的同学也会发现我们还有一处搜索框没有做任何的更改也没有做任何的数据交互,搜索框即是首屏页面实现中漏掉的一个功能,也就是我们本节实验需要实现的搜索功能,这一节会实现博客页面所有的搜索功能,包括搜索框搜索(关键字搜索)、按照分类搜索、按照标签搜索。
知识点
- 关键字搜索功能
- 搜索页面制作
- 分类搜索功能
- 标签搜索功能
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
关键字搜索功能实现
页面设计
接下来就是我们本次实验的第一个部分–关键字搜索功能实现,首先来看一下最终的搜索页展现效果来确认该页面的布局设计,如下图所示:
该页面与首页极度相似,也包括顶部导航栏、文章列表区域、底部页脚区域,不同的是缺少了侧边工具栏区域,顶部区域和底部区域是公共区域,这里就不再赘述,重点关注一下图中标注的三个地方,如上图所示,分别标注了三个区域:
- 搜索页副标题区域:这个区域处于整个搜索页面功能区的顶部,用于显示搜索信息,因为后续根据分类搜索和根据标签搜索功能也会用到该页面,在显示搜索信息的同时也对搜索功能进行区分;
- 文章列表区域:用于展示文章列表,显示文章的概览信息,与首页不同的是该页面没有侧边工具栏区域所以文章列表区域会更大一些;
- 分页导航区域:放置分页按钮,用于分页跳转功能。
数据格式定义
如下图所示即为博客列表中需要渲染的内容,首先博客列表肯定是一个 List 对象,但是因为有分页功能,所以还需要返回分页字段,因此最终接收到的结果返回格式为 PageResult 对象,而列表中的单项对象中的字段则需要通过下图中的内容进行确认。
通过图片我们可以看到博客标题字段、预览图字段、分类名称字段、分类图片字段,当然这里通常会设计成可跳转的形式,即点击标题或者预览图后会跳转到对应的博客详情页面中,点击分类名称或者分类图片也会跳转到对应的博客分类页面,因此还需要一个博客实体的 id 字段和分类实体的 id 字段,因此返回数据的格式就得出来了,与首页的数据模型一样也是使用 BlogListVO 对象。
发起搜索请求
接下来就是讲解一下整个搜索功能的第一步–发起搜索请求,该操作通过提交搜索框中的内容来实现。
- 首先需要对搜索框所在的 form 表单进行代码修改,这部分内容在 header.html,修改如下:(注:完整代码位于 resources/templates/blog/header.html)
<form method="get" onsubmit="return false;" accept-charset="utf-8"> <input class="searchfield" id="searchbox" type="text" placeholder=" 搜索" /> <button class="searchbutton" id="searchbutton" onclick="search()"> <i class="fa fa-search"></i> </button> </form>
修改 form 表单的 onsubmit
事件,同时增加搜索按钮的 onclick()
事件,在用户点击搜索按钮后执行 search()
方法。
- 在 resources/static/blog/js 目录下新增 search.js,新增如下代码:
$(function () { $('#searchbox').keypress(function (e) { var key = e.which; //e.which是按键的值 if (key == 13) { var q = $(this).val(); if (q && q != '') { window.location.href = '/search/' + q; } } }); }); function search() { var q = $('#searchbox').val(); if (q && q != '') { window.location.href = '/search/' + q; } }
该 js 文件中定义了 search()
方法的实现,在用户点击搜索按钮后会将用户输入的字段取出来并放入搜索请求的路径中进行请求,同时也定义了输入框的 keypress()
事件,当用户在输入框中输入需要搜索的字段后敲下 Enter 回车键,也可以发起搜索请求。
- 最后修改 blog/index.html ,增加 search.js 的引用代码:
<script th:src="@{/blog/js/search.js}"></script>
数据查询实现
接下来是数据查询的功能实现,上述博客列表中的字段可以通过直接查询 tb_blog 文章表和 tb_blog_category 表来获取,同时需要注意分页功能实现,传参时需要传入关键字和页码,实现逻辑如下。
首先,定义 service 方法,业务层代码如下(注:完整代码位于 com.site.blog.my.core.service.impl.BlogServiceImpl.java):
/** * 根据搜索关键字获取首页文章列表 * * @param page * @return */ public PageResult getBlogsPageBySearch(String keyword, int page) { if (page > 0 && PatternUtil.validKeyword(keyword)) { Map param = new HashMap(); param.put("page", page); param.put("limit", 9); param.put("keyword", keyword); param.put("blogStatus", 1);//过滤发布状态下的数据 PageQueryUtil pageUtil = new PageQueryUtil(param); List<Blog> blogList = blogMapper.findBlogList(pageUtil); List<BlogListVO> blogListVOS = getBlogListVOsByBlogs(blogList); int total = blogMapper.getTotalBlogs(pageUtil); PageResult pageResult = new PageResult(blogListVOS, total, pageUtil.getLimit(), pageUtil.getPage()); return pageResult; } return null; } /** * 数据填充 */ private List<BlogListVO> getBlogListVOsByBlogs(List<Blog> blogList) { List<BlogListVO> blogListVOS = new ArrayList<>(); if (!CollectionUtils.isEmpty(blogList)) { List<Integer> categoryIds = blogList.stream().map(Blog::getBlogCategoryId).collect(Collectors.toList()); Map<Integer, String> blogCategoryMap = new HashMap<>(); if (!CollectionUtils.isEmpty(categoryIds)) { List<BlogCategory> blogCategories = categoryMapper.selectByCategoryIds(categoryIds); if (!CollectionUtils.isEmpty(blogCategories)) { blogCategoryMap = blogCategories.stream().collect(Collectors.toMap(BlogCategory::getCategoryId, BlogCategory::getCategoryIcon, (key1, key2) -> key2)); } } for (Blog blog : blogList) { BlogListVO blogListVO = new BlogListVO(); BeanUtils.copyProperties(blog, blogListVO); if (blogCategoryMap.containsKey(blog.getBlogCategoryId())) { blogListVO.setBlogCategoryIcon(blogCategoryMap.get(blog.getBlogCategoryId())); } else { blogListVO.setBlogCategoryId(0); blogListVO.setBlogCategoryName("默认分类"); blogListVO.setBlogCategoryIcon("/admin/dist/img/category/1.png"); } blogListVOS.add(blogListVO); } } return blogListVOS; }
我们定义了 getBlogsPageBySearch()
方法并传入 keyword 和 page 参数,keyword 用来过滤想要的文章列表,page 用于确定查询第几页的数据,之后通过 SQL 查询出对应的分页数据,再之后是填充数据,某些字段是 tb_blog 表中没有的,所以再去分类表中查询并设置到 BlogListVO 对象中,最终返回的数据是 PageResult 对象。
具体的 SQL 语句如下(注:完整代码位于 resources/mapper/BlogMapper.xml):
<select id="findBlogList" parameterType="Map" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from tb_blog where is_deleted=0 <if test="keyword!=null"> AND (blog_title like CONCAT('%','${keyword}','%' ) or blog_category_name like CONCAT('%','${keyword}','%' )) </if> <if test="blogStatus!=null"> AND blog_status = #{blogStatus} </if> order by blog_id desc <if test="start!=null and limit!=null"> limit #{start},#{limit} </if> </select> <select id="getTotalBlogs" parameterType="Map" resultType="int"> select count(*) from tb_blog where is_deleted=0 <if test="keyword!=null"> AND (blog_title like CONCAT('%','${keyword}','%' ) or blog_category_name like CONCAT('%','${keyword}','%' )) </if> <if test="blogStatus!=null"> AND blog_status = #{blogStatus} </if> </select>
在原来查询博客列表 SQL 的基础上增加了对于 blog_title 字段和 blog_category_name 的过滤,使用 LIKE 语法对博客记录进行检索。
数据渲染
想要将数据通过 Thymeleaf 语法渲染到前端页面上,首先需要将数据获取并转发到对应的模板页面中,需要在 Controller 方法中将查询到的数据放入 request 域中,新增 search() 方法,代码如下:(注:完整代码位于 com.site.blog.my.core.controller.blog.MyBlogController)
/** * 搜索列表页 * * @return */ @GetMapping({"/search/{keyword}"}) public String search(HttpServletRequest request, @PathVariable("keyword") String keyword) { return search(request, keyword, 1); } /** * 搜索列表页 * * @return */ @GetMapping({"/search/{keyword}/{page}"}) public String search(HttpServletRequest request, @PathVariable("keyword") String keyword, @PathVariable("page") Integer page) { PageResult blogPageResult = blogService.getBlogsPageBySearch(keyword, page); request.setAttribute("blogPageResult", blogPageResult); request.setAttribute("pageName", "搜索"); request.setAttribute("pageUrl", "search"); request.setAttribute("keyword", keyword); return "blog/list"; }
路径映射为 /search/{keyword}/{page},page 参数不传的话默认为第 1 页,根据页码和关键字查询出对应的分页数据 blogPageResult 并放入到 request 对象中,之后跳转到 list 模板页面进行数据渲染,需要注意的是 pageName 字段、 pageUrl 字段和 keyword 字段,这些字段主要是为了搜索页副标题区域的显示和翻页链接的拼接。
新增搜索页 list.html ,模板代码如下:(注:完整代码位于 resources/templates/blog/list.html)
“`html
<div class="blog-category" th:utext="${blog.blogCategoryName}"> </div> </a> </div> </header> </article> </div> <th:block th:if="${iterStat.last and iterStat.count%3==1}"> <div class="col-md-4 col-sm-4 blog-main-card"></div> <div class="col-md-4 col-sm-4 blog-main-card"></div> </th:block> <th:block th:if="${iterStat.last and iterStat.count%3==2}"> <div class="col-md-4 col-sm-4 blog-main-card"></div> </th:block> </th:block> </th:block> <th:block th:if="${null != blogPageResult}"> <ul class="blog-pagination"> <li th:class="${blogPageResult.currPage==1}?'disabled' : ''"><a th:href="@{${blogPageResult.currPage==1}?'##':'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-1}}">«</a> </li> <li th:if="${blogPageResult.currPage-3 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-3}}" th:text="${blogPageResult.currPage -3}">1</a></li> <li th:if="${blogPageResult.currPage-2 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-2}}" th:text="${blogPageResult.currPage -2}">1</a></li> <li th:if="${blogPageResult.currPage-1 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-1}}" th:text="${blogPageResult.currPage -1}">1</a></li> <li class="active"><a href="#" th:text="${blogPageResult.currPage}">1</a></li> <li th:if="${blogPageResult.currPage+1 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+1}}" th:text="${blogPageResult.currPage +1}">1</a></li> <li th:if="${blogPageResult.currPage+2 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+2}}" th:text="${blogPageResult.currPage +2}">1</a></li> <li th:if="${blogPageResult.currPage+3 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+3}}" th:text="${blogPageResult.currPage +3}">1</a></li> <li th:class="${blogPageResult.currPage==blogPageResult.totalPage}?'disabled' : ''"><a th:href="@{${blogPageResult.currPage==blogPageResult.totalPage}?'##' : '/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+1}}">»</a> </li> </ul>
Spring Boot 博客系统项目开发之搜索页面制作
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
首页所有的功能基本都开发完成,首屏页面上大部分显示区域都实现了数据交互,当然,细心的同学也会发现我们还有一处搜索框没有做任何的更改也没有做任何的数据交互,搜索框即是首屏页面实现中漏掉的一个功能,也就是我们本节实验需要实现的搜索功能,这一节会实现博客页面所有的搜索功能,包括搜索框搜索(关键字搜索)、按照分类搜索、按照标签搜索。
知识点
- 关键字搜索功能
- 搜索页面制作
- 分类搜索功能
- 标签搜索功能
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
关键字搜索功能实现
页面设计
接下来就是我们本次实验的第一个部分–关键字搜索功能实现,首先来看一下最终的搜索页展现效果来确认该页面的布局设计,如下图所示:
该页面与首页极度相似,也包括顶部导航栏、文章列表区域、底部页脚区域,不同的是缺少了侧边工具栏区域,顶部区域和底部区域是公共区域,这里就不再赘述,重点关注一下图中标注的三个地方,如上图所示,分别标注了三个区域:
- 搜索页副标题区域:这个区域处于整个搜索页面功能区的顶部,用于显示搜索信息,因为后续根据分类搜索和根据标签搜索功能也会用到该页面,在显示搜索信息的同时也对搜索功能进行区分;
- 文章列表区域:用于展示文章列表,显示文章的概览信息,与首页不同的是该页面没有侧边工具栏区域所以文章列表区域会更大一些;
- 分页导航区域:放置分页按钮,用于分页跳转功能。
数据格式定义
如下图所示即为博客列表中需要渲染的内容,首先博客列表肯定是一个 List 对象,但是因为有分页功能,所以还需要返回分页字段,因此最终接收到的结果返回格式为 PageResult 对象,而列表中的单项对象中的字段则需要通过下图中的内容进行确认。
通过图片我们可以看到博客标题字段、预览图字段、分类名称字段、分类图片字段,当然这里通常会设计成可跳转的形式,即点击标题或者预览图后会跳转到对应的博客详情页面中,点击分类名称或者分类图片也会跳转到对应的博客分类页面,因此还需要一个博客实体的 id 字段和分类实体的 id 字段,因此返回数据的格式就得出来了,与首页的数据模型一样也是使用 BlogListVO 对象。
发起搜索请求
接下来就是讲解一下整个搜索功能的第一步–发起搜索请求,该操作通过提交搜索框中的内容来实现。
- 首先需要对搜索框所在的 form 表单进行代码修改,这部分内容在 header.html,修改如下:(注:完整代码位于 resources/templates/blog/header.html)
<form method="get" onsubmit="return false;" accept-charset="utf-8"> <input class="searchfield" id="searchbox" type="text" placeholder=" 搜索" /> <button class="searchbutton" id="searchbutton" onclick="search()"> <i class="fa fa-search"></i> </button> </form>
修改 form 表单的 onsubmit
事件,同时增加搜索按钮的 onclick()
事件,在用户点击搜索按钮后执行 search()
方法。
- 在 resources/static/blog/js 目录下新增 search.js,新增如下代码:
$(function () { $('#searchbox').keypress(function (e) { var key = e.which; //e.which是按键的值 if (key == 13) { var q = $(this).val(); if (q && q != '') { window.location.href = '/search/' + q; } } }); }); function search() { var q = $('#searchbox').val(); if (q && q != '') { window.location.href = '/search/' + q; } }
该 js 文件中定义了 search()
方法的实现,在用户点击搜索按钮后会将用户输入的字段取出来并放入搜索请求的路径中进行请求,同时也定义了输入框的 keypress()
事件,当用户在输入框中输入需要搜索的字段后敲下 Enter 回车键,也可以发起搜索请求。
- 最后修改 blog/index.html ,增加 search.js 的引用代码:
<script th:src="@{/blog/js/search.js}"></script>
数据查询实现
接下来是数据查询的功能实现,上述博客列表中的字段可以通过直接查询 tb_blog 文章表和 tb_blog_category 表来获取,同时需要注意分页功能实现,传参时需要传入关键字和页码,实现逻辑如下。
首先,定义 service 方法,业务层代码如下(注:完整代码位于 com.site.blog.my.core.service.impl.BlogServiceImpl.java):
/** * 根据搜索关键字获取首页文章列表 * * @param page * @return */ public PageResult getBlogsPageBySearch(String keyword, int page) { if (page > 0 && PatternUtil.validKeyword(keyword)) { Map param = new HashMap(); param.put("page", page); param.put("limit", 9); param.put("keyword", keyword); param.put("blogStatus", 1);//过滤发布状态下的数据 PageQueryUtil pageUtil = new PageQueryUtil(param); List<Blog> blogList = blogMapper.findBlogList(pageUtil); List<BlogListVO> blogListVOS = getBlogListVOsByBlogs(blogList); int total = blogMapper.getTotalBlogs(pageUtil); PageResult pageResult = new PageResult(blogListVOS, total, pageUtil.getLimit(), pageUtil.getPage()); return pageResult; } return null; } /** * 数据填充 */ private List<BlogListVO> getBlogListVOsByBlogs(List<Blog> blogList) { List<BlogListVO> blogListVOS = new ArrayList<>(); if (!CollectionUtils.isEmpty(blogList)) { List<Integer> categoryIds = blogList.stream().map(Blog::getBlogCategoryId).collect(Collectors.toList()); Map<Integer, String> blogCategoryMap = new HashMap<>(); if (!CollectionUtils.isEmpty(categoryIds)) { List<BlogCategory> blogCategories = categoryMapper.selectByCategoryIds(categoryIds); if (!CollectionUtils.isEmpty(blogCategories)) { blogCategoryMap = blogCategories.stream().collect(Collectors.toMap(BlogCategory::getCategoryId, BlogCategory::getCategoryIcon, (key1, key2) -> key2)); } } for (Blog blog : blogList) { BlogListVO blogListVO = new BlogListVO(); BeanUtils.copyProperties(blog, blogListVO); if (blogCategoryMap.containsKey(blog.getBlogCategoryId())) { blogListVO.setBlogCategoryIcon(blogCategoryMap.get(blog.getBlogCategoryId())); } else { blogListVO.setBlogCategoryId(0); blogListVO.setBlogCategoryName("默认分类"); blogListVO.setBlogCategoryIcon("/admin/dist/img/category/1.png"); } blogListVOS.add(blogListVO); } } return blogListVOS; }
我们定义了 getBlogsPageBySearch()
方法并传入 keyword 和 page 参数,keyword 用来过滤想要的文章列表,page 用于确定查询第几页的数据,之后通过 SQL 查询出对应的分页数据,再之后是填充数据,某些字段是 tb_blog 表中没有的,所以再去分类表中查询并设置到 BlogListVO 对象中,最终返回的数据是 PageResult 对象。
具体的 SQL 语句如下(注:完整代码位于 resources/mapper/BlogMapper.xml):
<select id="findBlogList" parameterType="Map" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from tb_blog where is_deleted=0 <if test="keyword!=null"> AND (blog_title like CONCAT('%','${keyword}','%' ) or blog_category_name like CONCAT('%','${keyword}','%' )) </if> <if test="blogStatus!=null"> AND blog_status = #{blogStatus} </if> order by blog_id desc <if test="start!=null and limit!=null"> limit #{start},#{limit} </if> </select> <select id="getTotalBlogs" parameterType="Map" resultType="int"> select count(*) from tb_blog where is_deleted=0 <if test="keyword!=null"> AND (blog_title like CONCAT('%','${keyword}','%' ) or blog_category_name like CONCAT('%','${keyword}','%' )) </if> <if test="blogStatus!=null"> AND blog_status = #{blogStatus} </if> </select>
在原来查询博客列表 SQL 的基础上增加了对于 blog_title 字段和 blog_category_name 的过滤,使用 LIKE 语法对博客记录进行检索。
数据渲染
想要将数据通过 Thymeleaf 语法渲染到前端页面上,首先需要将数据获取并转发到对应的模板页面中,需要在 Controller 方法中将查询到的数据放入 request 域中,新增 search() 方法,代码如下:(注:完整代码位于 com.site.blog.my.core.controller.blog.MyBlogController)
/** * 搜索列表页 * * @return */ @GetMapping({"/search/{keyword}"}) public String search(HttpServletRequest request, @PathVariable("keyword") String keyword) { return search(request, keyword, 1); } /** * 搜索列表页 * * @return */ @GetMapping({"/search/{keyword}/{page}"}) public String search(HttpServletRequest request, @PathVariable("keyword") String keyword, @PathVariable("page") Integer page) { PageResult blogPageResult = blogService.getBlogsPageBySearch(keyword, page); request.setAttribute("blogPageResult", blogPageResult); request.setAttribute("pageName", "搜索"); request.setAttribute("pageUrl", "search"); request.setAttribute("keyword", keyword); return "blog/list"; }
路径映射为 /search/{keyword}/{page},page 参数不传的话默认为第 1 页,根据页码和关键字查询出对应的分页数据 blogPageResult 并放入到 request 对象中,之后跳转到 list 模板页面进行数据渲染,需要注意的是 pageName 字段、 pageUrl 字段和 keyword 字段,这些字段主要是为了搜索页副标题区域的显示和翻页链接的拼接。
新增搜索页 list.html ,模板代码如下:(注:完整代码位于 resources/templates/blog/list.html)
“`html
<div class="blog-category" th:utext="${blog.blogCategoryName}"> </div> </a> </div> </header> </article> </div> <th:block th:if="${iterStat.last and iterStat.count%3==1}"> <div class="col-md-4 col-sm-4 blog-main-card"></div> <div class="col-md-4 col-sm-4 blog-main-card"></div> </th:block> <th:block th:if="${iterStat.last and iterStat.count%3==2}"> <div class="col-md-4 col-sm-4 blog-main-card"></div> </th:block> </th:block> </th:block> <th:block th:if="${null != blogPageResult}"> <ul class="blog-pagination"> <li th:class="${blogPageResult.currPage==1}?'disabled' : ''"><a th:href="@{${blogPageResult.currPage==1}?'##':'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-1}}">«</a> </li> <li th:if="${blogPageResult.currPage-3 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-3}}" th:text="${blogPageResult.currPage -3}">1</a></li> <li th:if="${blogPageResult.currPage-2 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-2}}" th:text="${blogPageResult.currPage -2}">1</a></li> <li th:if="${blogPageResult.currPage-1 >=1}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage-1}}" th:text="${blogPageResult.currPage -1}">1</a></li> <li class="active"><a href="#" th:text="${blogPageResult.currPage}">1</a></li> <li th:if="${blogPageResult.currPage+1 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+1}}" th:text="${blogPageResult.currPage +1}">1</a></li> <li th:if="${blogPageResult.currPage+2 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+2}}" th:text="${blogPageResult.currPage +2}">1</a></li> <li th:if="${blogPageResult.currPage+3 <=blogPageResult.totalPage}"><a th:href="@{'/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+3}}" th:text="${blogPageResult.currPage +3}">1</a></li> <li th:class="${blogPageResult.currPage==blogPageResult.totalPage}?'disabled' : ''"><a th:href="@{${blogPageResult.currPage==blogPageResult.totalPage}?'##' : '/'+${pageUrl}+'/'+${keyword}+'/' + ${blogPageResult.currPage+1}}">»</a> </li> </ul>
部分转自互联网,侵权删除联系
最新评论