avatar

目录
牛客网后端项目实战(六):开发社区首页

开发社区首页

  • 开发流程
    • 1次请求的执行过程
  • 分步实现
    • 开发社区首页,显示前10个帖子
    • 开发分页组件,分页显示所有的帖子

我们首先开发社区首页,显示帖子,先查看一下存帖子的表,在navicat右边可以查看DDL(Data Definition Language),也就是建表语句,workbench查看的方法自行百度。根据DDL了解一下表的结构。

然后我们就可以进行开发,根据上图的依赖关系,我们从下往上进行,也就是entity-dao-service-controller的顺序。

实体类

首先是实体类,和上一节的操作一样,比较简单。根据数据库字段写好就行。其实有插件可以自动生成实体类和xml,但是在学习阶段,建议自己敲一遍熟悉一下。

java
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
package com.neu.langsam.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 +
'}';
}
}

DAO层

实体类写完后,写dao层接口,这里定义了两个方法,一个是查询帖子,另一个是查询帖子数量,可以用来实现后面的分页。需要注意的点在注释里写了。

然后需要写接口对应的mapper配置文件,和上一节内容差不多,有几个需要注意的点

  • namespace要修改成对应的接口
  • 使用了标签动态拼接sql语句,当满足if的条件时,拼接上if标签里的内容,这样我们就可以根据传入的userId的值,来判断是查询首页帖子还是查询我的帖子。
  • 另外使用了order by来排序,首先按照帖子类型倒序,再按照时间倒叙,这样就实现了精华帖置顶并且新帖在前的功能,后面还会实现按热度排序。
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.neu.langsam.community.dao;

import com.neu.langsam.community.entity.DiscussPost;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface DiscussPostMapper {

//首页帖子实际不用传入userId,但考虑到之后需要实现我的帖子功能,这里就整合到一起,offset偏移量表示当前是第几行,limit表示每页数量

List selectDiscussPosts(int userId,int offset,int limit);


//动态拼接条件,方法有且只有一个条件时,需要用@Param注解给参数取别名,也可以在属性名太长的时候取别名来简化
int selectDiscussPostRows(@Param("userId") int userId);

}
xml
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

mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.neu.langsam.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>

然后测试一下,可以看到控制台输出没问题。

Service层

我们这个业务比较简单,但是也必须严格遵守层次去编写,便于我们后面的一些维护和安全性的实现。这里还有另一个问题,discusspost表里有userId字段,但是我们不可能直接给用户显示id,需要显示用户名。有两种方案:

  • 写sql的时候用id再查询一下用户名拼接到一起
  • 用之前写好的查询user的方法,和我查询到的discusspost再做一个组合

看起来第二种方法更麻烦一些,但是当我们后面用到redis缓存数据库后,这样的方法就可能效率更高了。那么我们再写一个UserService提供查询用户方法。

java
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
package com.neu.langsam.community.service;

import com.neu.langsam.community.dao.DiscussPostMapper;
import com.neu.langsam.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 findDiscussPosts(int userId,int offset,int limit){
return discussPostMapper.selectDiscussPosts(userId,offset,limit);
}



public int findDiscussPostRows(int userId){
return discussPostMapper.selectDiscussPostRows(userId);
}

}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.neu.langsam.community.service;


import com.neu.langsam.community.dao.UserMapper;
import com.neu.langsam.community.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

@Autowired
private UserMapper userMapper;

public User findUserById(int id){
return userMapper.selectById(id);
}
}

导入网页

前端的网页我们使用教程模板,在我的gayhub可以找到,把template下的文件复制到项目就行。

gayhub

Controller层

首先在controller包下新建HomeController,使用注解注入Service

java
1
2
3
4
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;

第一个任务

首页上显示10个帖子。这里先写固定值,后续再进行改进。前面提到的显示用户名的方式,采用第二种,利用post里的id去查询对应的user,然后用HashMap装到一起,再用list处理map。

最终我们传给模板引擎的就是一个list,list里是每个帖子对应的map,每个map里有post对象和user对象,每个对象有自己的属性和get、set方法。那么利用thymeleaf的语法,去遍历这些对象,将值赋给相应的标签就行。html已经写好了,有兴趣的可以学一学。我们启动项目,在浏览器里访问。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RequestMapping(path = "/index",method = RequestMethod.GET)
public String getIndexPage(Model model){
//查询10条数据
List list=discussPostService.findDiscussPosts(0,0,10);
//用Map把post和user装到一起
List> discussPosts=new ArrayList<>();
//遍历post,用post里的id查询user
if(list!=null){
for (DiscussPost post:list){
Map map=new HashMap<>();
map.put("post",post);
User user=userService.findUserById(post.getUserId());
map.put("user",user);
discussPosts.add(map);
}
}
//把我们准备好的数据传给model
model.addAttribute("discussPosts",discussPosts);
//返回模板
return "/index";
}

第二个任务

完成第一个任务后,在这基础上完成分页的功能,我们可以自己封装一个分页类,提高代码的复用。在entity包下创建Page类。使用idea生成get和set方法,这里我们需要修改一下生成的set方法,判断一下传入的数据是否合理。比如setLimit方法,我规定limit必须大于1小于100。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//当前页码
private int current=1;
//显示上限
private int limit=10;
//数据的总数,用于计算总页数
private int rows;
//查询路径,用于复用分页链接
private String path;


public void setLimit(int limit) {
if (limit>=1&&limit<=100){
this.limit = limit;}
}

另外,还需要自定义一些方法。

java
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
/*
*获取当前页的起始行
*/
public int getOffset(){
//current*limit-limit
return (current-1)*limit;
}

/*
*获取总页数
*/
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;
}

然后对controller进行一些改造。

java
1
2
3
4
5
6
7
8
9
10
@RequestMapping(path = "/index",method = RequestMethod.GET)
public String getIndexPage(Model model, Page page){

//方法调用之前,Spring MVC会自动实例化Model和Page,并讲Page注入Model
//所以在thymeleaf中可以直接访问page对象中的数据
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");


List list=discussPostService.findDiscussPosts(0,page.getOffset(),page.getLimit());

那么至此,首页完成了。

文章作者: langsam
文章链接: https://langsam1998.github.io/2020/03/10/20200310-%E7%89%9B%E5%AE%A2%E7%BD%91%E5%90%8E%E7%AB%AF%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98%EF%BC%88%E5%85%AD%EF%BC%89%EF%BC%9A%E5%BC%80%E5%8F%91%E7%A4%BE%E5%8C%BA%E9%A6%96%E9%A1%B5/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 平儿的博客
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论