当前位置:网站首页>4.发布帖子,评论帖子
4.发布帖子,评论帖子
2022-08-02 14:10:00 【鱼子酱:P】
目录
一:发布帖子
用到的表:DiscussPost
方法:用到AJAX,网页能将增量更新呈现在页面上,而不需要刷新整个页面
异步通信技术,虽然X代表XML,但目前JSON使用的比XML更加普遍
思路
- 在首页点击 “我要发布”,填写标题和正文,点击发布,会执行index.js中的publish()方法,触发ajax异步发送帖子请求,映射到DiscussPostController(/discuss)层的 /add 路径。执行控制器类 DiscussPostController 的 addDiscussPost()方法。里面调用 discussPostService.addDiscussPost 方法,将post 中的 title,content 转义HTML标记,,然后调用 discussPostMapper.insertDiscussPost(post) 存入数据。通过其对应的SQL语句将帖子内容插进 discuss_post表中。
- 简单来说就是 用post方式提交接json格式数据给目标URL,controller层接收到请求后,处理/add请求,前端根据返回的状态码以及提示信息判断是否添加成功。
开发流程
1.我们从最简单的工具类开始,在里面写上了我们需要的一些工具方法;
在util.CommunityUtil类中添加新的工具方法,用于转换json字符串:返回状态码,在贴子发布后,显示发布成功。
//得到JSON格式的字符串
//输入为:编号、提示、业务数据
public static String getJSONString(int code, String msg, Map<String, Object> 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 id="insertDiscussPost" parameterType="DiscussPost">
insert into discuss_post(<include refid="insertFields"></include>)
values (#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
</insert>
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语句
<select id="selectDiscussPostById" resultType="DiscussPost">
select <include refid="selectFields"></include>
from discuss_post
where id = #{id}
</select>
③在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<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
int selectCountByEntity(int entityType, int entityId);
}
在resources.mapper下创建comment-mapper.xml,用来实现上面接口中的SQL语句。
<?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.mycommunity.dao.CommentMapper">
<sql id="selectFields">
id, user_id, entity_type, entity_id, target_id, content, status, create_time
</sql>
<select id="selectCommentsByEntity" resultType="Comment">
select <include refid="selectFields"></include>
from comment
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
order by create_time asc
limit #{offset}, #{limit}
</select>
<select id="selectCountByEntity" resultType="int">
select count(id)
from comment
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
</select>
</mapper>
③在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<Comment> 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方法。
- 注入CommentService
- 借助分页,参数添加page
- 设置评论分页信息,每页显示5条,设置page的路径和总的评论数。
- 得到当前帖子的所有评论
- 遍历评论集合,创建评论VO列表,并放入map封装数据
整个逻辑就是查出当前帖子下的所有评论,遍历所有评论,处理用户名等信息,查询评论下的回复,遍历每个回复,处理用户名等信息。
@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<Comment> commentList =
commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
//评论VO列表(显示列表)
List<Map<String, Object>> commentVoList = new ArrayList<>();
if(commentList != null){
for (Comment comment : commentList){
//评论VO
Map<String, Object> commentVo = new HashMap<>();
commentVo.put("comment", comment); //向VO中添加评论
commentVo.put("user", userService.findUserById(comment.getUserId())); //向VO中添加评论作者
//评论的回复:不分页
List<Comment> replyList = commentService.findCommentsByEntity(
ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
//回复的VO列表
List<Map<String, Object>> replyVoList = new ArrayList<>();
if(replyList != null){
for (Comment reply : replyList){
Map<String, Object> replyVo = new HashMap<>();
//存回复
replyVo.put("reply", reply);
//作者
replyVo.put("user", userService.findUserById(reply.getUserId()));
User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
replyVo.put("target", target);
replyVoList.add(replyVo);
}
}
//目前包含楼主的评论,以及回复楼主的评论
commentVo.put("replys", replyVoList);
//楼中的评论数量
int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("replyCount", replyCount);
//里面装有所有楼层,以及楼层中的回复、数量信息
commentVoList.add(commentVo);
}
}
model.addAttribute("comments", commentVoList);
return "/site/discuss-detail";
}
注:由于用到新的常量,在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 id="updateCommentCount">
update discuss_post set comment_count = #{commentCount} where id = #{id}
</update>
②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<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
int selectCountByEntity(int entityType, int entityId);
//增加评论
int insertComment(Comment comment);
}
在resources.mapper创建对应的comment-mapper.xml:
<?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.mycommunity.dao.CommentMapper">
<!--查询字段-->
<sql id="selectFields">
id, user_id, entity_type, entity_id, target_id, content, status, create_time
</sql>
<!--插入字段:比查询少一个id(主键自增)-->
<sql id="insertFields">
user_id, entity_type, entity_id, target_id, content, status, create_time
</sql>
<select id="selectCommentsByEntity" resultType="Comment">
select <include refid="selectFields"></include>
from comment
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
order by create_time asc
limit #{offset}, #{limit}
</select>
<select id="selectCountByEntity" resultType="int">
select count(id)
from comment
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
</select>
<!--添加评论-->
<insert id="insertComment" parameterType="Comment">
insert into comment (<include refid="insertFields"></include>)
values (#{userId}, #{entityType}, #{entityId}, #{targetId}, #{content}, #{status}, #{createTime})
</insert>
</mapper>
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<Comment> 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;
}
}
边栏推荐
- win10任务栏不合并图标如何设置
- STM32LL library use - SPI communication
- 2021-10-14
- Win10 Settings screen out from lack of sleep?Win10 set the method that never sleep
- pygame draw arc
- jest测试,组件测试
- Detailed explanation of Golang garbage collection mechanism
- Please make sure you have the correct access rights and the repository exists.问题解决
- win10系统更新错误代码0x80244022怎么办
- Yolov5 official code reading - prior to transmission
猜你喜欢
pygame draw arc
13.56MHZ刷卡芯片CI521兼容cv520/ci520支持A卡B卡MIFARE协议
刷卡芯片CI520可直接PIN对PIN替换CV520支持SPI通讯接口
为vscode配置clangd
用U盘怎么重装Win7系统?如何使用u盘重装系统win7?
轻量化AlphaPose
镜像法求解接地导体空腔电势分布问题
【系统设计与实现】基于flink的分心驾驶预测与数据分析系统
What should I do if Windows 10 cannot connect to the printer?Solutions for not using the printer
FP7126降压恒流65536级高辉无频闪调光共阳极舞台灯RGB驱动方案
随机推荐
How to simulate 1/3 probability with coins, and arbitrary probability?
Spark及相关生态组件安装配置——快速回忆
Mysql的锁
用U盘怎么重装Win7系统?如何使用u盘重装系统win7?
Flink + sklearn - use JPMML implement flink deployment on machine learning model
Publish module to NPM should be how to operate?Solutions to problems and mistake
DP4301无线收发SUB-1G芯片兼容CC1101智能家居
单端K总线收发器DP9637兼容L9637
Compilation error D8021: Invalid numeric argument '/Wextra' cl command line error d8021 invalid numeric argument '/Wextra'
CI24R1小模块2.4G收发模块无线通信低成本兼容si24r1/XN297超低功耗
pytorch模型转libtorch和onnx格式的通用代码
LORA芯片ASR6601支持M4内核的远距离传输芯片
FP6293电池升压5V-12V大电流2APWM模式升压方案
网络安全抓包
FP7195转模拟调光技术解决智能家居调光频闪和电感噪音的原理
7. How to add the Click to RecyclerView and LongClick events
专硕与学硕
Win10安装了固态硬盘还是有明显卡顿怎么办?
In-depth understanding of Golang's Map
Binder ServiceManager解析