WHCSRL 技术网

SpringBoot从零开始的问答社区 (3)首页-Thymeleaf模板引擎

代码仓库

Github->https://github.com/noahlin27/Astera

Gitee->Astera: Astera project for Spring Boot

首页主要分为两个部分,一个是网站的导航栏,一个是问题帖的动态列表。我们先把较为简单的动态列表实现,常见问答社区的首页主要是一些热门的帖子,这里先用数据库里最新的帖子来代替。

开发新功能模块

数据库

通常每次开发一个新的模块,都是按照一定的顺序的,而从数据库的设计开始几乎是毋庸置疑的。一般来说,一个问题帖子会包含:标题,内容,发帖作者,发帖时间,点赞,评论,收藏等,所以我设计的字段有:id, title, content, user_id, create_time, comment_count。作者的信息都存在 User 表里了,只需要用 user_id 去查;点赞功能准备以后用redis缓存实现,所以选择不存到数据库。

model

在 model 包下添加 Question 类,成员变量和数据库字段一一对应:id, title, content, createTime, userId, commentCount,然后给每个变量加上 get、set 方法,一个 POJO类就这样写好了。

dao

在 dao 包下添加 QuestionDAO 接口,目前我们需要能够添加问题和读取最新的若干条问题,也就是一个 insert 方法和一个 select 方法,insert 方法按照之前写 UserDAO 时的 @Insert 注解去写就好,在写 select 方法时,考虑到后面需要用到某个用户发表问题的列表,我们可以在 SQL 语句中加个判断,这就要用到 mybatis 的 xml 配置文件。依然是在 QuestionDAO 中先定义好接口方法。

List<Question> selectLatest(@Param("userId") int userId, @Param("offset") int offset, @Param("limit") int limit);

mapper

这部分在第(1)篇中写到过,其实 mapper 包的命名和 QuestionMapper.xml 的命名取成 *DAO.xml 之类的也都行,注意跟 mybatis 配置的 mapper-locations 参数匹配就ok,xml 中的 <mapper namespace="..."><select id="..." resultType="..."> 的 "..." 部分也是同理。

当需要获取所有人发的问题时,userId 的值为0;当需要获取某一位用户发表的问题时,userId 的值为用户的 id。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4. <mapper namespace="top.noahlin.astera.dao.QuestionDAO">
  5. <sql id="TABLE_NAME">question</sql>
  6. <sql id="SELECT_FIELDS">id, title, content, create_time, user_id, comment_count</sql>
  7. <select id="selectLatest" resultType="top.noahlin.astera.model.Question">
  8. SELECT
  9. <include refid="SELECT_FIELDS"></include>
  10. FROM
  11. <include refid="TABLE_NAME"></include>
  12. <if test="userId!=0">
  13. WHERE user_id = #{userId}
  14. </if>
  15. ORDER BY id DESC
  16. LIMIT #{offset}, #{limit}
  17. </select>
  18. </mapper>

service

在 service 包下添加 QuestionService 接口,创建一个名为 impl 的子目录,将 QuestionServiceImpl 实现类放在 impl 包下。先在 QuestionService 中定义好需要的方法,包括它的参数和返回值,然后在实现类中把具体功能实现。

为了方便给视图模板传各种参数,在 model 中定义一个 ViewObject 类,它的成员变量类型为 Map<String, Object>,get 方法为返回一个 key 的值,set 方法为 put 一个键值对。

  1. public class ViewObject {
  2. private final Map<String, Object> objs = new HashMap<>();
  3. public void set(String key, Object value) {
  4. objs.put(key, value);
  5. }
  6. public Object get(String key) {
  7. return objs.get(key);
  8. }
  9. }

接下来,要完成首页的问题动态列表,需要 service 提供的方法也很简单:增加问题、获取问题列表。要注意一下的是获取问题列表的方法,这里用到了 List<View Object>。

理论上来说,直接把一个实体类对象返回给前端是不正确的,这样做会把不必要的数据、私密的数据传到前端,但是这整个项目都由我一个人写,并且是前后端不分离的,再加上此项目仅用于“练手”这一条,就简单的这样子写一下了。比较正规的写法当然是根据前端需要的数据,设计包含对应字段的 dto 或者 vo。

  1. public List<ViewObject> getQuestionList(int userId) {
  2. List<Question> questions = questionDAO.selectLatest(userId, 0, 10);
  3. List<ViewObject> vos = new ArrayList<>();
  4. for (Question question : questions) {
  5. ViewObject vo = new ViewObject();
  6. vo.set("user", userDAO.selectById(question.getUserId()));
  7. vo.set("question", question);
  8. vos.add(vo);
  9. }
  10. return vos;
  11. }

controller

在 controller 包下创建 IndexController ,处理 / 和 /index 请求,返回 index.html 模板,其中 “questionListVO” 是模板需要的参数。

  1. @GetMapping({"/", "/index"})
  2. public String index(HttpServletRequest request) {
  3. request.setAttribute("questionListVO", questionService.getQuestionList(0));
  4. return "index";
  5. }

Thymeleaf 使用

Thymeleaf 是 spring 推荐的模板引擎,因为没有用过其他的模板,也只能说它用着感觉还行。第一次写前端的代码,在原来的 HTML、CSS、JS 文件基础上去改,是可以实现需求的,入门比较简单,这里简单介绍一下用法。

添加 thymeleaf 的依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  4. </dependency>

代码测试阶段,application 配置文件关闭 thymeleaf 缓存

  1. spring:
  2. thymeleaf:
  3. cache: false

在<html>标签中加属性 xmlns:th="http://www.thymeleaf.org" 就可以使用 thymeleaf 的 th 指令了。常用的指令:

th:fragment="片段名(参数)"        供反复使用的代码片段

th:replace="文件路径::片段名(参数)"        调用代码片段

带参数值的属性都可以用到 th 指令,包括但不仅限于:

th:text="${参数}"        转义文本(防 html 注入)

th:utext="${参数}"        不作处理的文本

th:href="@{${参数}}"        链接

控制流程:

th:if="${参数}"  th:if="${参数} == 0"      逻辑判断,可以只传 boolean 参数,也可以在 HTML 中增加逻辑运算

th:each="i:${参数}"        循环

自定义变量:

th:object="${参数1}" th:text="*{参数2}"        保存对象,方便多次调用属性

相当于th:text="${参数1.参数2}"

内置对象:

#strings、#dates、#arrays 等,可直接调用内置的函数

想了解更多可以参考官方文档或者其它优质的博客

推荐阅读