• Spring Boot+MyBatis+MySQL+Spring MVC之论坛首页开发


    1.项目概述

    开发仿牛客网论坛首页。

    2. Entity

    ① 表user包含字段:username, password, salt, email, type, status, activation_code, header_url, create_time
    ② 表discuss_post包含字段:
    id, user_id, title, content, type, status, create_time, comment_count, score

    实体类:DiscussPost.java

    package com.nowcoder.community.entity;
    
    import java.util.Date;
    
    public class DiscussPost  {
    
        private int id;
        private int userId;
        private String title;
        private String content;
        private int type;
        private int status;
        private Date createTime;
        private int commentCount;
        private double score;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public int getUserId() {
            return userId;
        }
    
        public void setUserId(int userId) {
            this.userId = userId;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public int getType() {
            return type;
        }
    
        public void setType(int type) {
            this.type = type;
        }
    
        public int getStatus() {
            return status;
        }
    
        public void setStatus(int status) {
            this.status = status;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        public int getCommentCount() {
            return commentCount;
        }
    
        public void setCommentCount(int commentCount) {
            this.commentCount = commentCount;
        }
    
        public double getScore() {
            return score;
        }
    
        public void setScore(double score) {
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "DiscussPost{" +
                    "id=" + id +
                    ", userId=" + userId +
                    ", title='" + title + '\'' +
                    ", content='" + content + '\'' +
                    ", type=" + type +
                    ", status=" + status +
                    ", createTime=" + createTime +
                    ", commentCount=" + commentCount +
                    ", score=" + score +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    实体类:Page.java
    该类用于封装分页相关的信息。

    package com.nowcoder.community.entity;
    
    /**
     * 封装分页相关的信息.
     */
    public class Page {
    
        // 当前页码
        private int current = 1;
        // 显示上限
        private int limit = 10;
        // 数据总数(用于计算总页数)
        private int rows;
        // 查询路径(用于复用分页链接)
        private String path;
    
        public int getCurrent() {
            return current;
        }
    	
    	// 当前页页码必须不小于1
        public void setCurrent(int current) {
            if (current >= 1) {
                this.current = current;
            }
        }
    
        public int getLimit() {
            return limit;
        }
    	
    	// 显示数据的下限为1,上限为100
        public void setLimit(int limit) {
            if (limit >= 1 && limit <= 100) {
                this.limit = limit;
            }
        }
    
        public int getRows() {
            return rows;
        }
    
    	// 数据总数不能小于零
        public void setRows(int rows) {
            if (rows >= 0) {
                this.rows = rows;
            }
        }
    
        public String getPath() {
            return path;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    
        /**
         * 获取当前页的起始行
         *
         * @return
         */
        public int getOffset() {
            // current * limit - limit
            return (current - 1) * limit;
        }
    
        /**
         * 获取总页数
         *
         * @return
         */
        public int getTotal() {
            // rows / limit [+1]
            if (rows % limit == 0) {
                return rows / limit;
            } else {
                return rows / limit + 1;
            }
        }
    
        /**
         * 获取起始页码
         *
         * @return
         */
        public int getFrom() {
        	// 显示前两页
            int from = current - 2;
            return from < 1 ? 1 : from;
        }
    
        /**
         * 获取结束页码
         *
         * @return
         */
        public int getTo() {
        	// 显示后两页
            int to = current + 2;
            int total = getTotal();
            return to > total ? total : to;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    3. DAO

    接口:DiscussPostMapper.java

    package com.nowcoder.community.dao;
    
    import com.nowcoder.community.entity.DiscussPost;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    @Mapper
    public interface DiscussPostMapper {
    
        List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
    
        // @Param注解用于给参数取别名
        // 如果只有一个参数,并且在里使用,则必须加别名
        int selectDiscussPostRows(@Param("userId") int userId);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    @Param注解用于给参数取别名,如果只有一个参数,并且在里使用,则必须加别名。

    4. XML

    discusspost-mapper.xml实现SQL与实体类DiscussPost的映射。

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.nowcoder.community.dao.DiscussPostMapper">
    
        <sql id="selectFields">
            id, user_id, title, content, type, status, create_time, comment_count, score
        sql>
    
        <select id="selectDiscussPosts" resultType="DiscussPost">
            select <include refid="selectFields">include>
            from discuss_post
            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="selectDiscussPostRows" resultType="int">
            select count(id)
            from discuss_post
            where status != 2
            <if test="userId != 0">
                and user_id = #{userId}
            if>
        select>
    
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    其中,第一个 select 语句相当于:

    select id, user_id, title, content, type, status, create_time, comment_count, score 
    from discuss_post 
    where status != 2 
    order by type desc, create_time desc 
    limit ?, ?
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第二个 select 语句相当于:

    select count(id) 
    from discuss_post 
    where status != 2 
    
    • 1
    • 2
    • 3

    5. Service

    业务组件:DiscussPortService.java

    package com.nowcoder.community.service;
    
    import com.nowcoder.community.dao.DiscussPostMapper;
    import com.nowcoder.community.entity.DiscussPost;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class DiscussPostService {
    
        @Autowired
        private DiscussPostMapper discussPostMapper;
    
        public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
            return discussPostMapper.selectDiscussPosts(userId, offset, limit);
        }
    
        public int findDiscussPostRows(int userId) {
            return discussPostMapper.selectDiscussPostRows(userId);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    6. Controller

    控制组件:HomeController.java
    注入Service组件:DiscussPostServiceUserService
    方法getIndexPage()中的参数 model 和 page均由前端控制器DispatcherServlet 自动创建。

    package com.nowcoder.community.controller;
    
    import com.nowcoder.community.entity.DiscussPost;
    import com.nowcoder.community.entity.Page;
    import com.nowcoder.community.entity.User;
    import com.nowcoder.community.service.DiscussPostService;
    import com.nowcoder.community.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Controller
    public class HomeController {
    
        @Autowired
        private DiscussPostService discussPostService;
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/index")
        public String getIndexPage(Model model, Page page) {
            // 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model
            // 所以,在thymeleaf中可以直接访问Page对象中的数据
            page.setRows(discussPostService.findDiscussPostRows(0));
            page.setPath("/index");
    
            List<DiscussPost> list = discussPostService.findDiscussPosts(0,page.getOffset(),page.getLimit());
            List<Map<String, Object>> discussPosts = new ArrayList<>();
            if (list != null) {
                for (DiscussPost post : list) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("post", post);
                    User user = userService.findUserById(post.getUserId());
                    map.put("user", user);
                    discussPosts.add(map);
                }
            }
            model.addAttribute("discussPosts", discussPosts);
            return "/index";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    ① list 是封装了帖子 DiscussPost (包含多个字段的实体类)的List
    ② discussPosts 是封装了 map 的 List
    ③ map 中封装了 帖子 post 和对应的 用户实体类对象 user(通过 post 根据user_id 查询得到)

    7. 前端网页

    论坛首页index.html
    ① 帖子列表部分:

    <!-- 帖子列表 -->
    <ul class="list-unstyled">
    	<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
    		<a href="site/profile.html">
    			<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
    		</a>
    		<div class="media-body">
    			<h6 class="mt-0 mb-3">
    				<a href="#" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
    				<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>
    			</h6>
    			<div class="text-muted font-size-12">
    				<u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
    				<ul class="d-inline float-right">
    					<li class="d-inline ml-2">11</li>
    					<li class="d-inline ml-2">|</li>
    					<li class="d-inline ml-2">回帖 7</li>
    				</ul>
    			</div>
    		</div>
    	</li>
    </ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • th:each="map:${discussPosts}"使用变量 map 遍历封装了帖子和对应用户实体类对象的集合构成的列表 discussPosts。
    • th:src="${map.user.headerUrl}"用于动态替换 map 中的 user 的用户头像存储链接。
    • th:utext="${map.post.title}"用于动态替换 map 中的 post 的帖子标题。
    • th:if="${map.post.type==1}"用于条件判断,动态替换帖子类型为 1,即置顶的帖子。
      -th:if="${map.post.status==1}"用于条件判断,动态替换帖子状态为 1,即精华帖。
    • th:utext="${map.user.username}"用于动态替换 map 中的用户的用户名。
    • th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}"用于按照指定格式动态生成日期。

    ② 分页部分:

    <!-- 分页 -->
    			<nav class="mt-5" th:if="${page.rows>0}">
    				<ul class="pagination justify-content-center">
    					<li class="page-item">
    						<a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
    					</li>
    					<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 ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
    						<a class="page-link" th:href="@{${page.path}(current=${i})}" th:text="${i}">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>
    					<li class="page-item">
    						<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
    					</li>
    				</ul>
    			</nav>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • th:if="${page.rows>0}用于条件判断该页数据条数是否为正数。
    • th:href="@{${page.path}(current=1)}"首页
    • th:class="|page-item ${page.current==1?'disabled':''}|"
      th:href="@{${page.path}(current=${page.current-1})}"上一页
    • th:class="|page-item ${i==page.current?'active':''}|"
      th:each="i:${#numbers.sequence(page.from,page.to)}"
      th:href="@{${page.path}(current=${i})}"
      th:text="${i}"页码1
    • th:class="|page-item ${page.current==page.total?'disabled':''}|"
      th:href="@{${page.path}(current=${page.current+1})}"下一页
    • th:href="@{${page.path}(current=${page.total})}"末页
  • 相关阅读:
    el-input有时候添加不了有时候删不了
    DDD - 六边形架构和CQRS架构
    调试记录 单片机GD32F103C8T6(兆易创新) 程序烧写完成但是没有现象 (自己做的板子)
    Mybatis-plus学习笔记
    Taurus.MVC 性能压力测试(ap 压测 和 linux 下wrk 压测):.NET Core 版本
    麒麟系统开发笔记(八):在国产麒麟系统上使用linuxdeployqt发布qt程序
    LC 239.滑动窗口最大值
    Linux学习-31-Linux权限位及修改文件和目录的权限
    请问如何在proteus里画出电压器两边的箭头?
    2019Android高级面试题总结
  • 原文地址:https://blog.csdn.net/hutianle/article/details/125891765