总结-1 首页内容

功能

首页的功能主要是展现帖子,需要做到能分页展示和导航栏根据用户是否登录给出不同的显示

Thymeleaf

配置

spring.thymeleaf.cache=false

表示关闭thymeleaf的缓存,开发过程中要关闭,上线之后可以开启

头部

在标签中使用xmlns申明启用thymeleaf模板引擎

<html lang="en" xmlns:th="http://www.thymeleaf.org">

使用模板申明静态资源
默认情况下,Spring Boot从classpath下一个叫/static的文件夹下查找

<link rel="stylesheet" th:href="@{/css/global.css}"/>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/index.js}"></script>

可以通过 th:if 确定该标签是否要显示,下面表示如果loginUser为空的话才显示标签内容,loginUser在哪里引入会在登录注册模块解释
th:href="@{/register}"表示会导向127.0.0.1:8080/community/register链接,其中community在application.properties中配置了表示项目的context路径,类似前缀
thymeleaf中用$表示引用变量,用@表示链接

<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}">
	<a class="nav-link" th:href="@{/register}">注册</a>
</li>

在头部标签通过设置th:fragment设置名字,方便其他模板复用

<header class="bg-dark sticky-top" th:fragment="header">

分页

我们需要定义一个Page实体类,需要当前页码current,每页最多几个条目limit,数据的总数rows和查询路径path(用于复用)。

public class Page {
    // 当前页码
    private int current = 1;
    // 显示上限
    private int limit = 10;
    // 数据的总数(用于计算总的页数)
    private int rows;
    // 查询路径(用于复用分页链接)
    private String path;
    
    /**
     * 获取当前页的起始行
     * @return
     */
    public int getOffset(){
        return (current-1)*limit;
    }

    /**
     * 获取总的页数
     * @return
     */
    public int getTotal(){
        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;
    }

    public int getTo(){
        int to = current+2;
        int total = getTotal();
        return to > total ? total : to;
    }
}

在controller中,我们需要传入一个Page类型的变量page和一个Model,SpringMVC会自动实例化Model和Page,并将page注入model中。
每次使用分页我们都要设置总数和路径。

public String getIndexPage(Model model, Page page){
        // 方法调用之前SpringMVC会自动实例化Model和Page,并将Page注入Model
        // 所以在thymeleaf中可以直接访问Page对象中的数据
        page.setRows(discussPostService.findDiscussPostRows(0));
        page.setPath("/index");
        return "/index";
    }

在模板引擎中,同样需要通过总数判断是否需要分页,通过th:fragment设置名字方便复用。分页主要有五个成分,分别是首页、上一页按钮、具体页码下一页和末页。
页码的跳转主要通过th:href="@{ p a g e . p a t h ( c u r r e n t = {page.path}(current= page.path(current={page.current-1})}“实现,其中path设置了当前路径,(current=…)表示向url路径里传入?current=…的参数,SpringBoot会自动将page的current属性设置为目标值。首页设为1,末页设为最后一页,上一页-1,后一页+1
th:class=”|page-item ${page.current==1?‘disabled’:’’}|"中的| |表示既有静态参数,也有动态参数,这句话就是根据当前页是否为1来设置disabled属性

<nav class="mt-5" th:if="${page.rows>0}" th:fragment="pagination">
	<ul class="pagination justify-content-center">
		<li class="page-item">
<!--							(current=1)表示参数为current=1,即...?current=1-->
			<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" href="#" 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>

Mybatis

配置

首先需要和数据库进行连接,使用jdbc进行连接,设置好连接url,用户名,密码,数据源种类、最大连接池、超时时间等

# DataSourceProperties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000

然后要配置Mybatis相关内容,文件存放位置为classpath:mapper文件夹下所有.xml文件,即resource/mapper之下
设置com.nowcoder.community.entity为实体类位置
当mapUnderscoreToCamelCase设置为 true 时,表示如果插入的表以自增列为主键,则允许 JDBC 支持自动生成主键,并可将自动生成的主键返回
最后使下划线命名和驼峰命名相匹配

# MybatisProperties
# 映射文件的存放位置
mybatis.mapper-locations=classpath:mapper/*.xml
# 实体类的位置
mybatis.type-aliases-package=com.nowcoder.community.entity
mybatis.configuration.useGeneratedKeys=true
# 是下划线命名和驼峰命名相匹配
mybatis.configuration.mapUnderscoreToCamelCase=true

帖子

我们按照数据库->entity->dao->mapper->service->controller->html的方法开发
首先需要设计数据库,在这里我们对一个帖子字段设计为
id,userId(作者),title,content,type(0表示普通,1表示置顶),status(0表示正常,1表示精华,2表示拉黑,一般不删除数据),create_time,comment_count(表示评论总数,在之后会用到),score
数据库图片
在项目中我们需要定义相关的实体类

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;
	//get set...
	// toString()...
}

接下来我们需要在dao中申明相关接口,其中@Mapper注解表示该接口是一个Mapper,分别申明了查询帖子的方法(userId为0时表示获得所有帖子,在mapper中会解释),查询帖子数量的方法(@Param表示取别名),添加帖子方法,根据帖子id查询帖子,更新帖子。这些方法都是在之后是有需求才产生的,不应该是直接产生的

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);
    // 方法只有一个参数并且在<if>里使用就必须要取别名
    int selectDiscussPostRows(@Param("userId") int userId);

    int insertDiscussPost(DiscussPost discussPost);

    DiscussPost selectDiscussPostById(int id);

    int updateCommentCount(int id,int commentCount);
}

然后我们需要在.xml文件中具体实现这些方法,在namespace中对应相应的dao中的接口。<sql>中的内容是对字段的封装,必须注明id,在后面中通过<include refild="">的方式引用。<select>表示查询,通过id和接口中的具体方法对应,<if>表示只有条件满足才拼接该条件,limit表示查询到的结果数目,offset表示第一个与全部数据开始的偏移。
通过#{…}来引用方法中的参数,resultType表示返回类型,可以直接引用实体类,parameterType表示参数类型

<?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.ceterommunity.dao.DiscussPostMapper">

<sql id="selectFields">
        id, user_id, title, content, type, status, create_time, comment_count,score
    </sql>

<sql id="insertFields">
        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>

<insert id="insertDiscussPost" parameterType="DiscussPost">
    insert into discuss_post(<include refid="insertFields"></include>)
    values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
</insert>

<select id="selectDiscussPostById" resultType="DiscussPost">
    select <include refid="selectFields"></include>
    from discuss_post
    where id = #{id}
</select>

<update id="updateCommentCount">
        update discuss_post set comment_count = #{commentCount} where id = #{id}
    </update>
</mapper>

然后我们要在service中实现具体的业务逻辑代码,首先我们需要用@Service注解来表示该类是service
这里很简单,就是通过@Autowired自动注入DiscussPostMapper,再调用即可

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 org.springframework.web.util.HtmlUtils;

import java.util.List;
@Service
public class DiscussPostService {

    @Autowired
    private DiscussPostMapper discussPostMapper;
    
    //userId是外键
    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);
    }
    public DiscussPost findDiscussPostByid(int id){
        return discussPostMapper.selectDiscussPostById(id);
    }

    public int updateCommentCount(int id,int commentCount){
        return discussPostMapper.updateCommentCount(id,commentCount);
    }
}

然后是Controller,在这里我们查询所有帖子(设置userId=0),然后遍历每一个帖子,将帖子实体和用户实体分别加入一个map中,然后将map加入到一个list中,最后将该list加入到model中

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.hibernate.validator.constraints.EAN;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

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;
    // /templates不用写
    @RequestMapping(path = "/index",method = RequestMethod.GET)
    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());
        // 将帖子和用户一起放入一个map里
        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";
    }
}

最后我们在模板中设置好这些数据。使用th:each对discussPosts列表进行遍历,同时会在显示的时候重复n次,每一个元素命名为map,map.user其实是map.get(“user”),通过th:utext来覆盖内容,utext和text的区别在于utext不会对标签进行转换

<ul class="list-unstyled">
	<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
		<a href="site/profile.html">
<!--							map.user其实是map.get("user"),headerUrl同理-->
			<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 th:href="@{|/discuss/detail/${map.post.id}|}" 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">回帖 <span th:text="${map.post.commentCount}"></span></li>
				</ul>
			</div>
		</div>						
	</li>
</ul>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值