文章详情包括评论,之前写的ArticleMapper已经实现根据id查询文章详情,现在补充评论功能。
一、创建评论的数据访问层接口CommentMapper
在该接口上添加@Mapper,接口内添加如下方法:
// 分页展示某个文章的评论
@Select("SELECT * FROM t_comment WHERE article_id=#{aid} ORDER BY id DESC")
public List<Comment> selectCommentWithPage(Integer aid);
// 后台查询最新几条评论
@Select("SELECT * FROM t_comment ORDER BY id DESC")
public List<Comment> selectNewComment();
// 发表评论
@Insert("INSERT INTO t_comment (article_id,created,author,ip,content)" +
" VALUES (#{articleId}, #{created},#{author},#{ip},#{content})")
public void pushComment(Comment comment);
// 站点服务统计,统计评论数量
@Select("SELECT COUNT(1) FROM t_comment")
public Integer countComment();
// 通过文章id删除评论信息
@Delete("DELETE FROM t_comment WHERE article_id=#{aid}")
public void deleteCommentWithId(Integer aid);
二、业务处理层实现
1、在IArticleService添加查询文章详情的方法
// 根据文章id查询单个文章详情
public Article selectArticleWithId(Integer id);
2.创建评论业务处理接口ICommentService
//文章评论业务处理接口
public interface ICommentService {
// 获取文章下的评论
public PageInfo<Comment> getComments(Integer aid, int page, int count);
}
3.创建博客站点业务处理接口 ISiteService
//博客站点统计服务
public interface ISiteService {
// 最新收到的评论
public List<Comment> recentComments(int count);
// 最新发表的文章
public List<Article> recentArticles(int count);
// 获取后台统计数据
public StaticticsBo getStatistics();
// 更新某个文章的统计数据
public void updateStatistics(Article article);
}
4.打开ArticleServiceImpl,实现查询文章详情,嵌入Redis缓存管理
先使用@Autowired注入定制的RedisTemplate类(在config包下定制的配置类),
然后在文章查询过程中先从Redis缓存中进行查询,如果为空再进行数据库查询,并将结果进行缓存处理。
其中,此处缓存管理的文章详情数据的key为“article_” + id的形式。
@Autowired
private RedisTemplate redisTemplate;
// 根据id查询单个文章详情,并使用Redis进行缓存管理
public Article selectArticleWithId(Integer id){
Article article = null;
Object o = redisTemplate.opsForValue().get("article_" + id);
if(o!=null){
article=(Article)o;
}else{
article = articleMapper.selectArticleWithId(id);
if(article!=null){
redisTemplate.opsForValue().set("article_" + id,article);
}
}
return article;
}
5.实现ICommentService和ISiteService接口
@Service
@Transactional
public class CommentServiceImpl implements ICommentService {
@Autowired
private CommentMapper commentMapper;
// 根据文章id分页查询评论
@Override
public PageInfo<Comment> getComments(Integer aid, int page, int count) {
PageHelper.startPage(page,count);
List<Comment> commentList = commentMapper.selectCommentWithPage(aid);
PageInfo<Comment> commentInfo = new PageInfo<>(commentList);
return commentInfo;
}
}
@Service
@Transactional
public class SiteServiceImpl implements ISiteService {
@Autowired
private CommentMapper commentMapper;
@Autowired
private ArticleMapper articleMapper;
@Autowired
private StatisticMapper statisticMapper;
@Override
public void updateStatistics(Article article) {
Statistic statistic =
statisticMapper.selectStatisticWithArticleId(article.getId());
statistic.setHits(statistic.getHits()+1);
statisticMapper.updateArticleHitsWithId(statistic);
}
@Override
public List<Comment> recentComments(int limit) {
PageHelper.startPage(1, limit>10 || limit<1 ? 10:limit);
List<Comment> byPage = commentMapper.selectNewComment();
return byPage;
}
@Override
public List<Article> recentArticles(int limit) {
PageHelper.startPage(1, limit>10 || limit<1 ? 10:limit);
List<Article> list = articleMapper.selectArticleWithPage();
// 封装文章统计数据
for (int i = 0; i < list.size(); i++) {
Article article = list.get(i);
Statistic statistic =
statisticMapper.selectStatisticWithArticleId(article.getId());
article.setHits(statistic.getHits());
article.setCommentsNum(statistic.getCommentsNum());
}
return list;
}
@Override
public StaticticsBo getStatistics() {
StaticticsBo staticticsBo = new StaticticsBo();
Integer articles = articleMapper.countArticle();
Integer comments = commentMapper.countComment();
staticticsBo.setArticles(articles);
staticticsBo.setComments(comments);
return staticticsBo;
}
}
三、请求处理层实现
打开用户首页请求处理类IndexController,注入评论服务对象和站点服务对象,并添加文章详情查询方法,用来处理请求路径为“/article/{id}”的请求。
方法实现过程:
先查询出对应的文章信息,然后对文章的评论信息进行查询封装,
同时更新了文章的点击量统计信息。
在完成文章数据的查询后,如果文章不为空,跳转到client文件夹下的articleDetails.html文件;
如果文件为空,跳转到自定义的comm文件夹下的error_404.html错误页面。
@Autowired
private ICommentService commentServiceImpl;
@Autowired
private ISiteService siteServiceImpl;
// 文章详情查询
@GetMapping(value = "/article/{id}")
public String getArticleById(@PathVariable("id") Integer id, HttpServletRequest request){
Article article = articleServiceImpl.selectArticleWithId(id);
if(article!=null){
// 查询封装评论相关数据
getArticleComments(request, article);
// 更新文章点击量
siteServiceImpl.updateStatistics(article);
request.setAttribute("article",article);
return "client/articleDetails";
}else {
logger.warn("查询文章详情结果为空,查询文章id: "+id);
// 未找到对应文章页面,跳转到提示页
return "comm/error_404";
}
}
// 查询文章的评论信息,并补充到文章详情里面
private void getArticleComments(HttpServletRequest request, Article article) {
if (article.getAllowComment()) {
// cp表示评论页码,commentPage
String cp = request.getParameter("cp");
cp = StringUtils.isBlank(cp) ? "1" : cp;
request.setAttribute("cp", cp);
PageInfo<Comment> comments = commentServiceImpl.getComments(article.getId(),Integer.parseInt(cp),3);
request.setAttribute("cp", cp);
request.setAttribute("comments", comments);
}
}
四、实现前端页面功能
项目类目录下client目录中的文章详情页面articleDetails.html
通过th:*属性获取并展示了后台查询得到的文章及评论详情数据,同时在页面底部的<script>标签中实现了一个图片缩放功能。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<script th:src="@{/assets/js/jquery.min.js}"></script>
<script th:src="@{/assets/js/layer.js}"></script>
</head>
<div th:replace="client/header::header(${article.title},null)"></div>
<body>
<article class="main-content post-page">
<div class="post-header">
<h1 class="post-title" itemprop="name headline" th:text="${article.title}"></h1>
<div class="post-data">
<time th:datetime="${commons.dateFormat(article.created)}" itemprop="datePublished" th:text="'发布于 '+ ${commons.dateFormat(article.created)}"></time>
</div>
</div>
<br />
<div id="post-content" class="post-content content" th:utext="${commons.article(article.content)}"></div>
</article>
<div th:replace="client/comments::comments"></div>
<div th:replace="client/footer::footer"></div>
<!-- 使用layer.js实现图片缩放功能 -->
<script type="text/JavaScript">
$('.post-content img').on('click', function(){
var imgurl=$(this).attr('src');
var w=this.width*2;
var h=this.height*2+50;
layer.open({
type: 1,
title: false, //不显示标题栏
area: [w+"px", h+"px"],
shadeClose: true, //点击遮罩关闭
content: '\<\div style="padding:20px;">' +
'\<\img style="width:'+(w-50)+'px;" src='+imgurl+'\>\<\/div>'
});
});
</script>
</body>
</html>
五、Redis服务启动与配置
之前的环节中已经引入了自定义的Redis配置类,同时还引入了Redis服务连接需要的配置文件application-redis.properties,最后需要做的就是启动Redis服务即可。
六、效果展示
启动项目,登录并进入项目首页,在项目首页随机选择一篇文章单击文章名查看文章详情,或者选择阅读排行榜中的一篇文章进行详情查看。
通过Redis客户端可视化管理工具查看缓存的文章详情数据