项目1在线交流平台-1.SpringBoot整合SSM环境准备-6.项目首页开发

参考牛客网高级项目教程

1. 功能需求与dao层数据准备

1)功能需求

  • 1.社区网页首页的显示,服务器响应一次浏览器的请求
  • 2.每页显示的内容有:
    • 显示没有拉黑的帖子的基本信息:
      • 帖子标题
      • 发布时间
      • 点赞数
      • 回帖数
    • 显示帖子关联的发帖人用户信息:
      • 用户名
      • 用户头像
  • 3.分页显示
    • 页码的显示:
      • 每页显示多少页码
      • 高亮选中的页码
      • 能够跳转到鼠标点到的页码
        • 首页、上一页、下一页、末页的跳转设定
    • 每页显示多少条没有拉黑的帖子信息

在这里插入图片描述

2)数据的准备

帖子数据表设计
  • 设计comment_count冗余字段
    • 因为这个字段更新查询频繁,免得每次从comment表查,增加效率
CREATE TABLE `discuss_post` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(45) DEFAULT NULL,
  `title` varchar(100) DEFAULT NULL,
  `content` text,
  `type` int(11) DEFAULT NULL COMMENT '0-普通; 1-置顶;',
  `status` int(11) DEFAULT NULL COMMENT '0-正常; 1-精华; 2-拉黑;',
  `create_time` timestamp NULL DEFAULT NULL,
  `comment_count` int(11) DEFAULT NULL,
  `score` double DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=289 DEFAULT CHARSET=utf8
dao层操作-动态sql的拼接
定义好DiscussPost帖子实体类
public class DiscussPost {
    private int id;
    private int userId;
    private String title;
    private String content;
    private int type;   // '0-普通; 1-置顶;',
    private int status; // '0-正常; 1-精华; 2-拉黑;',
    private Date createTime;
    private int commentCount; // 评论数
    private double score;
    // get、set...
}
声明好有关帖子接口的查询操作方法:
  • 查询指定页面信息的帖子列表

    • 因为,今后有需要查询每个用户的帖子信息功能,所有对传入的user参数动态拼接
      • user为null,查询指定页面的所有帖子信息
      • user不为null,查询指定用户的每页帖子信息
  • 查询一共有多少条帖子

    • 用以根据每页的limit,算出一共需要多少页,进一步计算出每页的offset起始条
    • @Param注解用于给参数取别名,指定具体参数,
      如果只有一个参数,并且在里使用,则必须加@Param注解
    @Mapper
    public interface DiscussPost {
        // 查询指定页面信息的帖子列表
        List<DiscussPost> getPost(int userId, int offset, int limit);
    
        // 查询一共有多少条帖子
        // 如果只有一个参数,并且在<if>里使用,则必须加@Param注解
        int getPostRows(@Param("userId") int userId);
    }
    
动态sql的编写
  • 动态条件的sql拼接,使用if标签

  • 查询顺序,先按照帖子类型倒序(从大到小)、帖子类型倒序排列

  • 要剔除拉黑帖子的条件过滤

  • 分页查询,使用limit条件设定:起始条数设定,每页显示多少条

     <!--    定义复用代码块-->
        <sql id="selectFields">
            select id, user_id, title, content, type, status, create_time, comment_count, score from discuss_post
        </sql>
    
    <!--    查询指定页面信息的帖子列表-->
        <select id="getPost" resultType="discussPost">
            <include refid="selectFields"></include>
            where status != 2
            <if test="userId != 0">
                and user_id = #{userId}
            </if>
            order by type desc, create_time desc
            limit #{offset}, #{limit}
        </select>
    
    <!--    查询一共有多少条帖子-->
    <!--    返回类型必须要写-->
        <select id="getPostRows" resultType="int">
            select count(id)
            from discuss_post
            where status != 2
            <if test="userId != 0">
                and user_id = #{userId}
            </if>
        </select>
    
    测试,sql编写是否有误
     @Test
        public void testGetPost() {
            List<DiscussPost> list = discussPostMapper.getPost(149, 0, 10);
            for(DiscussPost post : list) {
                System.out.println(post);
            }
    
            int rows = discussPostMapper.getPostRows(149);
            System.out.println(rows);
        }
    

2. Service层业务处理

要处理的业务

  • 帖子的业务层,将dao层查询的逻辑封装,返给视图层

    • 虽然此次封装信息简单,但不能省,分工不同,也利于今后安全处理,过滤器的处理
  • 由于既要显示帖子信息,也要显示关联的用户信息,涉及到多表查询

    • 但本次处理是将两种信息封装成一个map传给model,而不是在sql层进行多表查询工作
    • 这样做的目的有两个
      • 今后将数据库迁移成redis数据库,比较方便
      • 业务层操作更加直观方便,无需每次改动动将实体类和sql进行更改
    • 即,查询到帖子列表后,遍历每个帖子,根据帖子的userId查询uesr
      • 将每个帖子和对应的user封装成一个map,包装到model里,交给视图层MVC处理

Service方法定义

  • 处理帖子业务

    @Service
    public class DiscussPostService {
        @Autowired
        private DiscussPostMapper discussPostMapper;
        // 查询指定页面信息的帖子列表
        public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
            return discussPostMapper.getPosts(userId, offset, limit);
        }
    
        // 查询一共有多少条帖子
        public int findDiscussPostRows(int userId) {
            return discussPostMapper.getPostRows(userId);
        }
    }
    
  • 处理用户信息业务

    @Service
    public class UserService {
        @Autowired
        private UserMapper userMapper;
        
        // 根据id查询用户
        public User findUserById(int id) {
            return userMapper.selectById(id);
        }
    }
    

3. 视图层处理

静态资源和动态模板页面的准备

  • 视图层中的view相关静态资源和动态模板放在resource目录下

    • 将静态资源css,img, js资源放到静态目录下,

    • 动态模板:社区index.html首页,其他页面site,邮件发送页面mail放到模板目录下

      • springMVC中自动配置好视图解析器,将前缀后缀拼接好,controller只需返回文件的逻辑名index即可
      在这里插入图片描述

controller对model的处理

整体流程
  1. 将service层注入进来,

  2. 设定mapper映射的访问路径url

  3. 封装model信息,并model数据和视图逻辑名"index"返回给dispatchServlet

    @Controller
    public class HomeController {
        @Autowired
        private DiscussPostService discussPostService;
        @Autowired
        private UserService userService;
        
        @RequestMapping(path = "/index", method = RequestMethod.GET)
        public String getIndexPage(Model model) {
            ...
            return "index";
        }
    }
    
model封装的逻辑:
1.将帖子列表信息和用户信息一起封装成map
  • 先拿到指定页面的帖子列表信息,

  • 再遍历每条帖子,拿到对应用户信息,一块封装成map,再将每个map放进list集合中

  • 将要封装好的所有组合信息列表list装进model里

    	@RequestMapping(path = "/index", method = RequestMethod.GET)
        public String getIndexPage(Model model) {
            List<DiscussPost> posts = discussPostService.findDiscussPosts(0, 0, 10);
            List<Map<String, Object>> mapList = new ArrayList<>();
            for(DiscussPost post : posts) {
                Map<String, Object> map = new HashMap<>();
                map.put("post", post);
                User user = userService.findUserById(post.getUserId());
                map.put("user", user);
                mapList.add(map);
            }
            model.addAttribute("postUser", mapList);
            return "index";
        }
    
2.分页显示,Page类设定
  • 页面信息比较多,因此封装成一个实体类Page进行处理,Page类包含的信息有:

    • 1.接受用户传入的信息:
      • 当前页码
      • 每页显示的数量
    • 2.服务器自己查找处理的信息:
      • 帖子总条数,用于计算出一共需要的页码数
      • 查询的路径:复用分页的链接,直接在同一个path后面拼接数字即可,减少拼接url的操作
    • 3.为了显示需要,需要算出的信息
      • 起始页码
      • 结束页码
  • 注意,由于页码的非负特殊性,在get,set方法中注意边界条件

    public class Page {
    
        // 当前页码
        private int current = 1;
        // 显示上限
        private int limit = 10;
        // 数据总数(用于计算总页数)
        private int rows;
        // 查询路径(用于复用分页链接)
        private String path;
        
    	//需要添加边界条件的set方法和增加的方法
        public void setCurrent(int current) {
            if (current >= 1) {
                this.current = current;
            }
        }
    
        public void setLimit(int limit) {
            if (limit >= 1 && limit <= 100) {
                this.limit = limit;
            }
        }
    
        public void setRows(int rows) {
            if (rows >= 0) {
                this.rows = rows;
            }
        }
    
        // 获取当前页的起始行
        public int getOffset() {
            // current * limit - limit
            return (current - 1) * limit;
        }
    
        // 获取总页数
        public int getTotal() {
            if (rows % limit == 0) {
                return rows / limit;
            } else {
                return rows / limit + 1;
            }
        }
    
        // 获取起始页码,确保每次显示5个页码选择框
        public int getFrom() {
            int total = getTotal();
            int from = 1;
            if(current == total - 1) {
                from = current - 3;
            } else if(current == total) {
                from = current - 4;
            } else {
                from = current - 2;
            }
            return from < 1 ? 1 : from;
        }
    
        // 获取结束页码 确保每次显示5个页码选择框
        public int getTo() {
            int total = getTotal();
            int to = 1;
            if(current == 1) {
                to = current + 4;
            } else if(current == 2){
                to = current + 3;
            } else {
                to = current + 2;
            }
            return to > total ? total : to;
        }
    }
    
  • controller中,传入Page类,

    • 方法调用前,SpringMVC会自动自动实例化model,Page,并将page组装进model里
    • 故,无需手动装进model,thymeleaf模板可以获取model中的Page信息
    @RequestMapping(path = "/index", method = RequestMethod.GET)
        public String getIndexPage(Model model, Page page) {
            // 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model.
            // 无需手动装进model,thymeleaf模板可以获取model中的Page信息
            page.setRows(discussPostService.findDiscussPostRows(0));
            page.setPath("/index");
            
            List<DiscussPost> posts = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
            List<Map<String, Object>> mapList = new ArrayList<>();
            if(posts != null) {
                for (DiscussPost post : posts) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("post", post);
                    User user = userService.findUserById(post.getUserId());
                    map.put("user", user);
                    mapList.add(map);
                }
            }
            model.addAttribute("postUser", mapList);
            return "index";
        }
    

处理模板页面view

1.先显示一个页面的10条帖子用户信息
xmlns:th="http://www.thymeleaf.org

1.先配置模板引擎xmlns:th="http://www.thymeleaf.org"

<html lang="en" xmlns:th="http://www.thymeleaf.org">
th:href="@{/…/…}"

2.对于静态资源的相对路径,要用th:href="@{/…/…}"修饰

  • 或是链接到指定路径链接
  • 让thymeleaf模板去相对路径下找动态页面
<head>
	...
	<link rel="stylesheet" th:href="@{/css/global.css}" />
	<title>牛客网-首页</title>
</head>
<body>
    ...
	<script th:src="@{/js/global.js}"></script>
	<script th:src="@{js/index.js}"></script>
</body>

3.帖子列表内容动态映射

th:each=" ${ }”
  • 在一个li标签中进行遍历输出 th:each=" ${ }”:

  • 使用EL表达式: ${ }获取数据

    • 获取数据
    • 执行运算
    • 获取web开发的常用对象
  • 似于java中的for(int a :b),

    • map:${postUser}将 获取的每条数据赋值给别名map
  • ul 标签是无序列表

    • li 标签是列表项
    <!-- 将${}EL表达式中获取的每条数据赋值给别名map-->
    <li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${postUser}">
    
th修饰的${对象.方法名}
th:if
  • 条件判断,如果条件满足就显示,否则不显示

  • span标签,它的长度是封装数据的长度

    <span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
    <span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
    
th:utext
  • 用th:text="${}"书写文本内容

  • 用th:utext="${}",将转义字符自动识别正确显示,例如,&lt 为 <

    <u class="mr-3" th:utext="${map.user.username}">寒江雪</u>
    
#dates.format(数据,格式)
  • 用#引用thymeleaf模板工具

  • {#dates.format(数据,‘yyyy-MM-dd HH:mm:ss’}" 模板工具将时间格式设置

    <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}"></b>
    
2.显示分页信息
url的拼接
  • ${page.path}(current=1) 等价与 /index?current = 1–>

    <nav class="mt-5" th:if="${page.rows>0}">
    	<ul class="pagination justify-content-center">
    		<li class="page-item">
    <!--${page.path}(current=1) 等价与 /index?current = 1-->
    			<a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
    		</li>
            ...
            <li class="page-item">
    			<a class="page-link" th:href="@{${page.path}(current=${page.total})}">             末页</a>
    		</li>
        </ul>
    </nav>
    
“|静态 ${}|”
disabled
  • 链接到上一页,如果是首页,不能点,‘disabled’,

  • 添加动态判断==,动态与静态组合用|…|==

    <!--链接到上一页,如果是首页,不能点,'disabled',添加动态判断,动态与静态组合用|...|-->
    <li th:class="|page-item ${page.current==1?'disabled':''}|">
    	<a class="page-link" th:href="@{${page.path}(current=${(page.current)-1})}">上一页</a></li>
    ...
    <li th:class="|page-item ${page.current==page.total?'disabled':''}|">
    	<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a></li>
    
#numbers.sequence(page.from,page.to)
点亮与否,用active
  • 显示页码,i:${#numbers.sequence(page.from,page.to)},自动生成连续的数组,用i引用每个数组元素

  • 点亮与否,用active

  • href="#",链接到当前页面

    <li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
    	<a class="page-link" href="#" th:text="${i}">1</a>
    </li>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值