代码编织梦想

文件文章分页展示

    1.数据访问层实现

        实现文章分类展示效果需要同时实现文章查询以及文章统计数据查询,这里先编写文章类Article和统计类Static对应的数据访问方法。

              (1)创建Dao层接口文件

                 在blog_system项目中创建名为com.itheima.dao的包,并在该包下使用MyBatis框架分别创建文章类Article和统计类Static对应的Mapper接口文件。内容如下所示。

ArticleMapper

package com.itheima.dao;

import com.itheima.model.domain.Article;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
 * @Classname ArticleMapper
 * @Description TODO
 * @Date 2019-3-14 9:44
 * @Created by CrazyStone
 */

@Mapper
public interface ArticleMapper {
    // 根据id查询文章信息
    @Select("SELECT * FROM t_article WHERE id=#{id}")
    public Article selectArticleWithId(Integer id);

    // 发表文章,同时使用@Options注解获取自动生成的主键id
    @Insert("INSERT INTO t_article (title,created,modified,tags,categories," +
            " allow_comment, thumbnail, content)" +
            " VALUES (#{title},#{created}, #{modified}, #{tags}, #{categories}," +
            " #{allowComment}, #{thumbnail}, #{content})")
    @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
    public Integer publishArticle(Article article);

    // 文章发分页查询
    @Select("SELECT * FROM t_article ORDER BY id DESC")
    public List<Article> selectArticleWithPage();

    // 通过id删除文章
    @Delete("DELETE FROM t_article WHERE id=#{id}")
    public void deleteArticleWithId(int id);

    // 站点服务统计,统计文章数量
    @Select("SELECT COUNT(1) FROM t_article")
    public Integer countArticle();

    // 通过id更新文章
    public Integer updateArticleWithId(Article article);
}

StaticMapper

package com.itheima.dao;

import com.itheima.model.domain.Article;
import com.itheima.model.domain.Statistic;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
 * @Classname StatisticMapper
 * @Description TODO
 * @Date 2019-3-14 9:45
 * @Created by CrazyStone
 */

@Mapper
public interface StatisticMapper {
    // 新增文章对应的统计信息
    @Insert("INSERT INTO t_statistic(article_id,hits,comments_num) values (#{id},0,0)")
    public void addStatistic(Article article);

    // 根据文章id查询点击量和评论量相关信息
    @Select("SELECT * FROM t_statistic WHERE article_id=#{articleId}")
    public Statistic selectStatisticWithArticleId(Integer articleId);

    // 通过文章id更新点击量
    @Update("UPDATE t_statistic SET hits=#{hits} " +
            "WHERE article_id=#{articleId}")
    public void updateArticleHitsWithId(Statistic statistic);

    // 通过文章id更新评论量
    @Update("UPDATE t_statistic SET comments_num=#{commentsNum} " +
            "WHERE article_id=#{articleId}")
    public void updateArticleCommentsWithId(Statistic statistic);

    // 根据文章id删除统计数据
    @Delete("DELETE FROM t_statistic WHERE article_id=#{aid}")
    public void deleteStatisticWithId(int aid);

    // 统计文章热度信息
    @Select("SELECT * FROM t_statistic WHERE hits !='0' " +
            "ORDER BY hits DESC, comments_num DESC")
    public List<Statistic> getStatistic();

    // 统计博客文章总访问量
    @Select("SELECT SUM(hits) FROM t_statistic")
    public long getTotalVisit();

    // 统计博客文章总评论量
    @Select("SELECT SUM(comments_num) FROM t_statistic")
    public long getTotalComment();
}

ArticleMapper中使用注解 的方式实现了文件的增删改查操作,其中,使用@Insert注解插入文章时,结合@Options注解将插入的文章主键id返回。

StaticMapper中同样使用注解的方式实现了文件的统计操作,包括统计信息的增删改查操作,文章排行榜统计,博客总访问量和评论量的统计。

     (2)创建MyBatis对应的XML映射文件

ArticleMapper中,updateArticleWithld()方法用于根据id修改对应文章内容。如果使用注解方式动态拼接SQL语句是十分不便的,这里,我们使用XML文件配置SQL语句。

    在blog_system项目中的resources目录下创建名为mapper的包,并在该包中创建Article文章类操作对应的XML映射文件,内容如文件10-7所示。

ArticleMapper.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.itheima.dao.ArticleMapper">
    <update id="updateArticleWithId" parameterType="Article">
        update t_article
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="created != null">
                created = #{created},
            </if>
            <if test="modified != null">
                modified = #{modified},
            </if>
            <if test="tags != null">
                tags = #{tags},
            </if>
            <if test="categories != null">
                categories = #{categories},
            </if>
            <if test="hits != null">
                hits = #{hits},
            </if>
            <if test="commentsNum != null">
                comments_num = #{commentsNum},
            </if>
            <if test="allowComment != null">
                allow_comment = #{allowComment},
            </if>
            <if test="thumbnail != null">
                thumbnail = #{thumbnail},
            </if>
            <if test="content != null">
                content = #{content},
            </if>
        </set>
        where id = #{id}
    </update>
</mapper>

文件10-7中,先使用namespace属性声明了文章类接口ArticleMapper对应的位置,然后根据需要编写了针对文章修改方法updateArticleWithld()的SQL语句。

      2.业务处理层实现

           (1)创建Service层接口文件

          在blog_system项目中创建名为com.ithema.service的包,在该包下创建用于文章操作的接口类,并编写文章相关的分页查询以及文章热度统计的方法,内容如文件10-8所示。

IArticleService.java

package com.itheima.service;

import com.github.pagehelper.PageInfo;
import com.itheima.model.domain.Article;
import java.util.List;
/**
 * @Classname IArticleService
 * @Description TODO
 * @Date 2019-3-14 9:46
 * @Created by CrazyStone
 */

public interface IArticleService {
    // 分页查询文章列表
    public PageInfo<Article> selectArticleWithPage(Integer page, Integer count);

    // 统计前10的热度文章信息
    public List<Article> getHeatArticles();

    // 根据文章id查询单个文章详情
    public Article selectArticleWithId(Integer id);

    // 发布文章
    public void publish(Article article);

    // 根据主键更新文章
    public void updateArticleWithId(Article article);

    // 根据主键删除文章
    public void deleteArticleWithId(int id);
}

 (2)创建Service层接口实现类文件

    在com.itheima.service包下创建一个impl包,在该包下创建IArticleService接口文件对应的实现类ArticleServieceImpl,并实现接口中方法,内容如文件10-9所示。

ArticleServiceimpl.java

package com.itheima.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.dao.ArticleMapper;
import com.itheima.dao.CommentMapper;
import com.itheima.dao.StatisticMapper;
import com.itheima.model.domain.Article;
import com.itheima.model.domain.Statistic;
import com.itheima.service.IArticleService;
import com.vdurmont.emoji.EmojiParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Classname ArticleServiceImpl
 * @Description TODO
 * @Date 2019-3-14 9:47
 * @Created by CrazyStone
 */
@Service
@Transactional
public class ArticleServiceImpl implements IArticleService {
    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private StatisticMapper statisticMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private CommentMapper commentMapper;

    // 分页查询文章列表
    @Override
    public PageInfo<Article> selectArticleWithPage(Integer page, Integer count) {
        PageHelper.startPage(page, count);
        List<Article> articleList = articleMapper.selectArticleWithPage();
        // 封装文章统计数
        for (int i = 0; i < articleList.size(); i++) {
            Article article = articleList.get(i);
            Statistic statistic = statisticMapper.selectStatisticWithArticleId(article.getId());
            article.setHits(statistic.getHits());
            article.setCommentsNum(statistic.getCommentsNum());
        }
        PageInfo<Article> pageInfo=new PageInfo<>(articleList);
        return pageInfo;
    }

    // 统计前10的热度文章信息
    @Override
    public List<Article> getHeatArticles( ) {
        List<Statistic> list = statisticMapper.getStatistic();
        List<Article> articlelist=new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            Article article = articleMapper.selectArticleWithId(list.get(i).getArticleId());
            article.setHits(list.get(i).getHits());
            article.setCommentsNum(list.get(i).getCommentsNum());
            articlelist.add(article);
            if(i>=9){
                break;
            }
        }
        return articlelist;
    }

    // 根据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;
    }

    // 发布文章
    @Override
    public void publish(Article article) {
        // 去除表情
        article.setContent(EmojiParser.parseToAliases(article.getContent()));
        article.setCreated(new Date());
        article.setHits(0);
        article.setCommentsNum(0);
        // 插入文章,同时插入文章统计数据
        articleMapper.publishArticle(article);
        statisticMapper.addStatistic(article);
    }

    // 更新文章
    @Override
    public void updateArticleWithId(Article article) {
        article.setModified(new Date());
        articleMapper.updateArticleWithId(article);
        redisTemplate.delete("article_" + article.getId());
    }

    // 删除文章
    @Override
    public void deleteArticleWithId(int id) {
        // 删除文章的同时,删除对应的缓存
        articleMapper.deleteArticleWithId(id);
        redisTemplate.delete("article_" + id);
        // 同时删除对应文章的统计数据
        statisticMapper.deleteStatisticWithId(id);
        // 同时删除对应文章的评论数据
        commentMapper.deleteCommentWithId(id);
    }

}

文件10-9中,selectArticleWithPage(Integer page,Integer count)方法用于分页查询出对应的文章信息,getHeatArticles()方法用于统计热度排名前十的文章信息。

3.请求处理层的实现

(1)实现Controller控制层处理类

在blog_system项目中创建名为com.ithema.web.client的包用于客户端文章统一管理。在client包下创建博客首页处理类IndexController,并编写文章分页查询和热度统计的方法,内容如文件10-10所示。

IndexController.java        

package com.itheima.web.client;

import com.github.pagehelper.PageInfo;
import com.itheima.model.domain.Article;
import com.itheima.model.domain.Comment;
import com.itheima.service.IArticleService;
import com.itheima.service.ICommentService;
import com.itheima.service.ISiteService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @Classname IndexController
 * @Description TODO
 * @Date 2019-3-14 9:49
 * @Created by CrazyStone
 */
@Controller
public class IndexController {
    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    @Autowired
    private IArticleService articleServiceImpl;
    @Autowired
    private ICommentService commentServiceImpl;
    @Autowired
    private ISiteService siteServiceImpl;

    // 博客首页,会自动跳转到文章页
    @GetMapping(value = "/")
    private String index(HttpServletRequest request) {
        return this.index(request, 1, 5);
    }

    // 文章页
    @GetMapping(value = "/page/{p}")
    public String index(HttpServletRequest request, @PathVariable("p") int page, @RequestParam(value = "count", defaultValue = "5") int count) {
        PageInfo<Article> articles = articleServiceImpl.selectArticleWithPage(page, count);
        // 获取文章热度统计信息
        List<Article> articleList = articleServiceImpl.getHeatArticles();
        request.setAttribute("articles", articles);
        request.setAttribute("articleList", articleList);
        logger.info("分页获取文章信息: 页码 "+page+",条数 "+count);
        return "client/index";
    }

    // 文章详情查询
    @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);
        }
    }

}

 文件10-10中,两个index()方法分别用于处理博客首页和文章首页的访问请求。其中,在处理文章首页请求的index()方法中,查询出的文章信息article和热度文章articleList都存储在Request域,便于后台通过request将数据发送给前端页面。

(2)实现自定义拦截器Interceptor

在本博系统前端页面进行数据展示的过程中,会涉及很多的数据、日期和图片等的转换,而前端使用的是Thymeleaf模板引擎,为了方便页面数据转换,在项目业务功能实现之前特意编写并提供了页面数据转换工具类Commons(com.itheima.utiles包下,具体代码实现读者可以自行查看)。因此,还必须将工具类也传递到前端页面中,这里选择使用自定义拦截器Interceptor的方式,将Commons工具类实例储存在Rquest域中返回页面使用。

   在web目录下新创建一个interceptor包,并在该包中通过实现HandlerInterceptor接口自定义一个拦截器类,内容如文件10-11所示

BaseInterceptor.java

package com.itheima.web.interceptor;

import com.itheima.utils.Commons;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 自定义的Interceptor拦截器类,用于封装请求后的数据类到request域中,供html页面使用
 * 注意:自定义Mvc的Interceptor拦截器类
 *  1、使用@Configuration注解声明
 *  2、自定义注册类将自定义的Interceptor拦截器类进行注册使用
 */
@Configuration
public class BaseInterceptor implements HandlerInterceptor {
    @Autowired
    private Commons commons;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 用户将封装的Commons工具返回页面
        request.setAttribute("commons",commons);
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

文件10-11中,自定义拦截器BaseInterceptor实现了HandlerInterceptor接口的核心方法,并在postHandler()方法中将Commons工具类的实例存储在Request域传递到前端页面。

    实现了自定义拦截器Interceptor后,还需要通过Spring框架提供的WebMvcConfigurer接口类进行注册,在interceptor包下编写一个WebMvcConfigurer接口实现类注册自定义拦截器,内容如文件10-12所示。

WebMvcConfig.java

package com.itheima.web.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * @Classname WebMvcConfig
 * @Description TODO
 * @Date 2019-3-14 10:01
 * @Created by CrazyStone
 */

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private BaseInterceptor baseInterceptor;

    @Override
    // 重写addInterceptors()方法,注册自定义拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(baseInterceptor);
    }
}

文件10-12中,WebMvcConfig类实现了WebMvcConfigurer接口,并重写了addinterceptors()方法注册自动以拦截器。

4.实现前端页面功能

    根据上一步指定的跳转页面,打开项目类目录下client目录中的项目首页index.html,进行具体的数据获取和展示,其核心代码如文件10-13所示。

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 载入文章头部页面,位置在/client文件夹下的header模板页面,模板名称th:fragment为header -->
<div th:replace="/client/header::header(null,null)" />
<body>
<div class="am-g am-g-fixed blog-fixed index-page">
    <div class="am-u-md-8 am-u-sm-12">
        <!-- 文章遍历并分页展示 -->
        <div th:each="article: ${articles.list}">
            <article class="am-g blog-entry-article">
                <div class="am-u-lg-6 am-u-md-12 am-u-sm-12 blog-entry-img">
                    <img width="100%" class="am-u-sm-12" th:src="@{${commons.show_thumb(article)}}"/>
                </div>
                <div class="am-u-lg-6 am-u-md-12 am-u-sm-12 blog-entry-text">
                    <!-- 文章分类 -->
                    <span class="blog-color"style="font-size: 15px;"><a>默认分类</a></span>
                    <span>&nbsp;&nbsp;&nbsp;</span>
                    <!-- 发布时间 -->
                    <span style="font-size: 15px;" th:text="'发布于 '+ ${commons.dateFormat(article.created)}" />
                    <h2>
                        <div><a style="color: #0f9ae0;font-size: 20px;" th:href="${commons.permalink(article.id)}" th:text="${article.title}" />
                        </div>
                    </h2>
                    <!-- 文章摘要-->
                    <div style="font-size: 16px;" th:utext="${commons.intro(article,75)}" />
                </div>
            </article>
        </div>
        <!-- 文章分页信息 -->
        <div class="am-pagination">
            <div th:replace="/comm/paging::pageNav(${articles},'上一页','下一页','page')" />
        </div>
    </div>
    <!-- 博主信息描述 -->
    <div class="am-u-md-4 am-u-sm-12 blog-sidebar">
        <div class="blog-sidebar-widget blog-bor">
            <h2 class="blog-text-center blog-title"><span>CrazyStone</span></h2>
            <img th:src="@{/assets/img/me.jpg}" alt="about me" class="blog-entry-img"/>
            <p>
                Java后台开发
            </p>
            <p>个人博客小站,主要发表关于Java、Spring、Docker等相关文章</p>
        </div>
        <div class="blog-sidebar-widget blog-bor">
            <h2 class="blog-text-center blog-title"><span>联系我</span></h2>
            <p>
                <a><span class="am-icon-github am-icon-fw blog-icon"></span></a>
                <a><span class="am-icon-weibo am-icon-fw blog-icon"></span></a>
            </p>
        </div>
    </div>
    <!-- 阅读排行榜 -->
    <div class="am-u-md-4 am-u-sm-12 blog-sidebar">
        <div class="blog-sidebar-widget blog-bor">
            <h2 class="blog-text-center blog-title"><span>阅读排行榜</span></h2>
            <div style="text-align: left">
                <th:block th:each="article :${articleList}">
                    <a  style="font-size: 15px;" th:href="@{'/article/'+${article.id}}"
                        th:text="${articleStat.index+1}+'、'+${article.title}+'('+${article.hits}+')'">
                    </a>
                    <hr style="margin-top: 0.6rem;margin-bottom: 0.4rem" />
                </th:block>
            </div>
        </div>
    </div>
</div>
</body>
<!-- 载入文章尾部页面,位置在/client文件夹下的footer模板页面,模板名称th:fragment为footer -->
<div th:replace="/client/footer::footer" />
</html>

文件10-13中,第8~35行代码用于显示文章分页列表,第36~40行代码用于控制分页,第45-57行代码用于显示阅读排行榜。其中,第38行和第61行代码使用th:replace属性引入了外部模板文件。第49~54行代码使用th:*属性遍历显示Request域中的文章信息,这里只展示Spring boot整合Thymeleaf展示页面数据。

5.效果展示

启动项目,访问项目首页,效果如图所示。

项目初次启动访问首页会自动拦截跳转到Security提供的登录页面,这是由于在项目pom.xml中预先添加了Security相关的依赖文件,所以会默认启动Security提供的默认用户名user,并输入项目启动时控制台打印的随机密码登录,会自动跳转到博客首页面如图10-13所示。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_52689367/article/details/129611152