一:发布帖子
二:帖子详情
三:查看评论
四:评论帖子
用到的表:DiscussPost
方法:用到AJAX,网页能将增量更新呈现在页面上,而不需要刷新整个页面
异步通信技术,虽然X代表XML,但目前JSON使用的比XML更加普遍
思路
开发流程
1.我们从最简单的工具类开始,在里面写上了我们需要的一些工具方法;
在util.CommunityUtil类中添加新的工具方法,用于转换json字符串:返回状态码,在贴子发布后,显示发布成功。
//得到JSON格式的字符串
//输入为:编号、提示、业务数据
public static String getJSONString(int code, String msg, Map map){
JSONObject json = new JSONObject();
json.put("code",code);
json.put("msg",msg);
if (map!=null){
for (String key: map.keySet()) {
json.put(key, map.get(key));
}
}
return json.toJSONString();
}
//得到JSON格式的字符串(重载1:无业务数据)
public static String getJSONString(int code, String msg){
return getJSONString(code, msg, null);
}
//得到JSON格式的字符串(重载2:无提示、业务数据)
public static String getJSONString(int code){
return getJSONString(code, null, null);
}
2. 在数据层dao中的DiscussPostMapper接口新添加方法,并在对应的discusspost-mapper添加对应的SQL语句
//添加帖子
int insertDiscussPost(DiscussPost discussPost);
//SQL语句
insert into discuss_post( )
values (#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
3. 业务的核心逻辑都在Service层,在service类中编写了一些需要的业务逻辑,业务层需要定义一个对帖子进行保存的方法,最后调用dao里的方法,实现对数据层的更新。
在service.DiscussPostService类下新建方法:addDiscussPost()。
@Autowired
private SensitiveFilter sensitiveFilter;
public int addDiscussPost(DiscussPost post){
if(post==null){
throw new IllegalArgumentException("参数不能为空!");
}
//转义HTML标记:防止人家发布的内容中包含html的标签,导致破坏页面
//只用对主题、评论进行转义、过滤操作
post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
post.setContent(HtmlUtils.htmlEscape(post.getContent()));
//过滤敏感词
post.setTitle(sensitiveFilter.filter(post.getTitle()));
post.setContent(sensitiveFilter.filter(post.getContent()));
return discussPostMapper.insertDiscussPost(post);
}
4. Service之后,最后就是视图层的编写,分为两个部分:控制器 + 页面。
在controller目录下新建:DiscussPostController,实现增加帖子的功能,以后所有与发帖相关的请求都在这里处理。
package com.nowcoder.mycommunity.controller;
//处理所有与发帖相关的请求
@Controller
@RequestMapping("/discuss")
public class DiscussPostController {
@Autowired
private DiscussPostService discussPostService;
@Autowired //获取当前用户
private HostHolder hostHolder;
@RequestMapping(path = "/add", method = RequestMethod.POST)
@ResponseBody
public String addDiscussPost(String title, String content) {
User user = hostHolder.getUser();
if (user == null){
// 403表示没有权限
return CommunityUtil.getJSONString(403, "你还没有登录哦!");
}
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle(title);
post.setContent(content);
post.setCreateTime(new Date());
discussPostService.addDiscussPost(post);
return CommunityUtil.getJSONString(0, "发布成功");
}
}
再完成JS部分的编写,index.html中101行的【发布按钮】绑定了一个函数publish(),可以在Index.js中查看(双击shift搜索)。
$(function(){
$("#publishBtn").click(publish);
});
function publish() {
$("#publishModal").modal("hide");
// 获取标题和内容
var title = $("#recipient-name").val();
var content = $("#message-text").val();
// 发送异步请求(POST)
$.post(
CONTEXT_PATH + "/discuss/add",
{"title":title,"content":content},
function(data) {
data = $.parseJSON(data);
// 在提示框中显示返回消息
$("#hintBody").text(data.msg);
// 显示提示框
$("#hintModal").modal("show");
// 2秒后,自动隐藏提示框
setTimeout(function(){
$("#hintModal").modal("hide");
// 刷新页面
if(data.code == 0) {
window.location.reload();
}
}, 2000);
}
);
}
比较简单,就是标准的开发流程
1. 大体思路
点击帖子,会打开一个链接,把帖子的内容显示完整。
按照正常的开发流程:数据层 - 服务层 - 页面。
① 在 index 页面点击帖子,映射到DiscussPostController(/discuss)层的 /detail/{discussPostId} 路径,并将帖子的 id 传入。
② 根据 id 利用 discussPostService.findDiscussPostById 方法查出 post。根据userService.findUserById 方法查出 User。将 post帖子,user发帖人 加入model。
(③ 后序新增:将点赞数量和状态查询处理加入model。)
④ 返回 /site/discuss-detail 页面。在 /site/discuss-detail 页面显示帖子和发布帖子的用户信息。
2. 过程
①在 dao.DiscussPostMapper类下添加增删改查方法:
selectDiscussPostById 根据主键查询帖子。
//根据主键查询帖子
DiscussPost selectDiscussPostById(int id);
②在 resources.mapper.discusspoat-mapper.xml 下添加上面Mapper对应的SQL语句
③在service.DiscussPostService 添加方法:
//根据ID查询帖子
public DiscussPost findDiscussPostById(int id){
return discussPostMapper.selectDiscussPostById(id);
}
④在controller.DiscussPostController 控制器类中添加控制器方法,用来处理点击帖子查看详情时的页面跳转。
@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model){
//查询得到帖子
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
//将帖子传给模板
model.addAttribute("post",post);
//查询出发帖人
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
return "/site/discuss-detail";
}
继 帖子详情后显示帖子的评论。同样在 DiscussPostController(/discuss)层的 /detail/{discussPostId} 映射中。我们将 comment 表中的 entity_type 属性在 CommunityConstant 工具类中设置了两个实体常量,(帖子1,评论2),便于分页查询时使用。
新表:评论表Comment,至此已经出现DiscussPost帖子表,User用户表
1.开发思路
这个功能也是比较常规的功能,按照常规流程三层进行开发。
①数据层:
- 根据实体查询一页评论数据;
- 根据实体查询评论的数量。
②业务层:
- 处理查询评论的业务;
- 处理查询评论数量的业务。
③表现层:
- 显示帖子详情数据时,同时显示该帖子所有的评论数据。
2.具体过程
评论是显示在帖子详情页面,所以改造DiscussPostController的getDiscussPost方法。同时还给comment帖子表设置了两个常量。(帖子1,评论2),便于分页查询时使用。
1)首先设置评论分页信息。设置为每页显示5条,设置page的路径(/disscuss/detail/ + )和总的评论数。
2)设置完后就可以进行分页查询了。根据该帖子的 id 和类型利用 commentService.findCommentsByEntity 方法查询得到当前帖子的所有评论放到一个集合中。还要呈现其它信息,将该帖子的评论和该评论的作者加入到Map中,当然不仅帖子有评论,评论也有回复的评论,所以也要把它的评论查到显示,这里比较绕,我们称普通评论为评论,评论的评论为回复。3)刚刚Map封装的是一个评论,这会继续查回复,这里就不用分页了,从第一行开始查,还是在该Map中嵌套插入一个包括该评论的所有回复reply,回复作者和回复目标(target)。再commentService.findCommentCount将回复数量插入Map(因为前端也会展示总的评论数)。完成以后最后将每个评论的Map传入模板Model。(难点)
3)整个逻辑就是查出当前帖子下的所有评论,遍历所有评论,处理用户名等信息,查询评论下的回复,遍历每个回复,处理用户名等信息。最后返回 /site/discuss-detail 页面。
①在entity包下新建实体类:Comment,对应评论的表。
package com.nowcoder.mycommunity.entity;
import java.util.Date;
//对应于【评论】表
public class Comment {
private int id;
private int userId;
private int entityType; //评论类型,1代表帖子,2代表评论
private int entityId; //对应回复实体的ID,如帖子id 228,或帖子id 229
private int targetId; //回复的对象,即向某个人的评论
private String content;
private int status;
private Date createTime;
// get()、set()
// toString()
}
②数据层:在dao包下,创建接口CommentMapper,主要有两个方法,一个是查询评论,一个是查询评论数量,这里也需要用到分页查询,用了offeset和limit.
由于涉及到分页,需要用到两个方法:查询某页有多少数据、查询一共有多少条数据
package com.nowcoder.mycommunity.dao;
import com.nowcoder.mycommunity.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CommentMapper {
//查询评论时,进行分页的两个方法
List selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
int selectCountByEntity(int entityType, int entityId);
}
在resources.mapper下创建comment-mapper.xml,用来实现上面接口中的SQL语句。
id, user_id, entity_type, entity_id, target_id, content, status, create_time
③在service目录下创建:CommentService,业务层也比较简单,没有什么需要处理的,直接返回查询结果
package com.nowcoder.mycommunity.service;
import com.nowcoder.mycommunity.dao.CommentMapper;
import com.nowcoder.mycommunity.entity.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommentService {
@Autowired
private CommentMapper commentMapper;
public List findCommentsByEntity(int entityType, int entityId, int offset, int limit){
return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
}
public int findCommentCount(int entityType, int entityId){
return commentMapper.selectCountByEntity(entityType, entityId);
}
}
④在controller.DiscussPostController中添加方法
评论是显示在帖子详情页面,所以改造DiscussPostController的getDiscussPost方法。
整个逻辑就是查出当前帖子下的所有评论,遍历所有评论,处理用户名等信息,查询评论下的回复,遍历每个回复,处理用户名等信息。
@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page){
//查询得到帖子
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
//将帖子传给模板
model.addAttribute("post",post);
//查询出发帖人
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
//评论的分页信息
page.setLimit(5);
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount()); //帖子中的评论数量
//评论:给帖子的评论(楼主)
//回复:给评论的评论(楼内的互相评论)
//评论列表
List commentList =
commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
//评论VO列表(显示列表)
List
注:由于用到新的常量,在util.CommunityConstant中添加
//帖子的实体类型
int ENTITY_TYPE_POST = 1;
//评论的实体类型
int ENTITY_TYPE_COMMENT = 2;
添加评论的功能也是比较基础的,按照数据层业务层和表现层进行开发,比较特别的就是会用到前面提到的事务管理。
为了效率,在帖子的字段里设计了一个评论数量,那么我们添加评论的时候就要同时更新评论数量。
添加 评论分为三种:①回帖 ②回评论 ③回某人评论
- 在 discuss-detail 页面点击最下方的回帖,映射到 CommentController 层的 /add/{discussPostId}。addComment方法里声明一个实体来知道给帖子评论的还是给某人回复的,① 传入了 entityType = 1 和 entityId = post.id 。② 在评论下方回复,与①映射相同,传入entityType = 2 和 entityId = comment.id。③ 对某人的评论回复,与①映射相同,传入entityType = 2 和 entityId = comment.id 和 targetId。
- 对传入的 comment 进一步设置userId,Status,CreateTime。然后调用commentService.addComment(comment) 方法插入数据库。commentService.addComment 使用了事务注解。该方法对 comment 的 content 进行转义HTML标记。然后使用 commentMapper.insertComment 插入评论。然后通过 commentMapper.selectCountByEntity 查询帖子的评论数量,再使用 discussPostService.updateCommentCount 方法将评论数量插入帖子详情表。
- 重定向 return “redirect:/discuss/detail/” + 帖子discussPostId。 刷新帖子详情页面。
①DiscussPostMapper中添加方法UpdatecommentCount:
帖子表因为有一个冗余的显示帖子数量的一个字段,所以帖子更新后,帖子表也要进行一个更新。
//插入评论后,更新对应帖子的评论数量
int updateCommentCount(int id, int commentCount);
对应的discusspost-mapper.xml的SQL语句:
update discuss_post set comment_count = #{commentCount} where id = #{id}
②CommentMapper里新增 添加评论的方法insertcomment
package com.nowcoder.mycommunity.dao;
import com.nowcoder.mycommunity.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CommentMapper {
//查询评论时,进行分页的两个方法
List selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
int selectCountByEntity(int entityType, int entityId);
//增加评论
int insertComment(Comment comment);
}
在resources.mapper创建对应的comment-mapper.xml:
id, user_id, entity_type, entity_id, target_id, content, status, create_time
user_id, entity_type, entity_id, target_id, content, status, create_time
insert into comment ( )
values (#{userId}, #{entityType}, #{entityId}, #{targetId}, #{content}, #{status}, #{createTime})
3. 业务层
①DiscussPostService中添加刚才增删改查的业务方法updateCommentCount
//业务:插入评论后,更新对应帖子的评论数量
public int updateCommentCount(int id, int commentCount){
return discussPostMapper.updateCommentCount(id, commentCount);
}
②创建CommentService,处理增加评论的核心操作,当前都在一个事务之内所以用声明式事务,加上 @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)注解,隔离级别,传播机制;只有更新为帖子的评论时,才更新其数量。
@Service
public class CommentService implements CommunityConstant {
@Autowired
private CommentMapper commentMapper;
@Autowired
private SensitiveFilter sensitiveFilter;
@Autowired
private DiscussPostService discussPostService;
public List findCommentsByEntity(int entityType, int entityId, int offset, int limit){
return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
}
public int findCommentCount(int entityType, int entityId){
return commentMapper.selectCountByEntity(entityType, entityId);
}
//增加评论
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public int addComment(Comment comment){
if(comment == null){
throw new IllegalArgumentException("参数不能为空!");
}
//评论的敏感词过滤
comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
comment.setContent(sensitiveFilter.filter(comment.getContent()));
int rows = commentMapper.insertComment(comment);
//更新对应帖子的评论数量
if(comment.getEntityType() == ENTITY_TYPE_POST){
//
int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId());
discussPostService.updateCommentCount(comment.getEntityId(), count);
}
return rows;
}
}
4. 表现层
新建一个CommentController,从地址里获取当前帖子的id,方便评论完重定向,前端传来的评论需要完善数据。
@Controller
@RequestMapping("/comment")
public class CommentController {
@Autowired
private CommentService commentService;
@Autowired
private HostHolder hostHolder;
//插入评论
@RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST)
public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment){
comment.setUserId(hostHolder.getUser().getId());
comment.setStatus(0);
comment.setCreateTime(new Date());
commentService.addComment(comment);
return "redirect:/discuss/detail/" + discussPostId;
}
}