当前位置:   article > 正文

web应用程序开发案例分析实验十 Spring Boot综合项目实战_spring综合实验

spring综合实验

实验十 Spring Boot综合项目实战

一、实验目的

1. 了解博客系统的系统功能和文件组织结构

2. 掌握前后台管理功能的实现

3. 掌握用户登录、定时邮件发送的功能实现

4. 熟悉博客系统数据库相关表及字段的设计、环境搭建的步骤及相关配置

二、实验内容

1、数据库实现     

2、系统环境搭建

3、文章分页展示

4、文章详情查看

5、文章评论管理

6、数据展示

7、文章发布

8、文章修改

9、文章删除

10、用户登录控制

11、定时邮件发送

三、实验步骤

1、准备项目环境

(1)创建项目,引入依赖文件

创建一个名称为blog_system的Spring Boot项目,选择Web模块

在pom.xml中添加

  1. <dependencies>
  2. <!-- 阿里巴巴的Druid数据源依赖启动器 -->
  3. <dependency>
  4. <groupId>com.alibaba</groupId>
  5. <artifactId>druid-spring-boot-starter</artifactId>
  6. <version>1.1.10</version>
  7. </dependency>
  8. <!-- MyBatis依赖启动器 -->
  9. <dependency>
  10. <groupId>org.mybatis.spring.boot</groupId>
  11. <artifactId>mybatis-spring-boot-starter</artifactId>
  12. <version>2.0.0</version>
  13. </dependency>
  14. <!-- MySQL数据库连接驱动 -->
  15. <dependency>
  16. <groupId>mysql</groupId>
  17. <artifactId>mysql-connector-j</artifactId>
  18. <scope>runtime</scope>
  19. </dependency>
  20. <!-- Redis服务启动器 -->
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-data-redis</artifactId>
  24. </dependency>
  25. <!-- mail邮件服务启动器 -->
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-mail</artifactId>
  29. </dependency>
  30. <!-- thymeleaf模板整合security控制页面安全访问依赖 -->
  31. <dependency>
  32. <groupId>org.thymeleaf.extras</groupId>
  33. <artifactId>thymeleaf-extras-springsecurity5</artifactId>
  34. </dependency>
  35. <!-- Spring Security依赖启动器 -->
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-security</artifactId>
  39. </dependency>
  40. <!-- Thymeleaf模板引擎启动器 -->
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  44. </dependency>
  45. <!-- Web服务启动器 -->
  46. <dependency>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-starter-web</artifactId>
  49. </dependency>
  50. <!-- MyBatis分页插件 -->
  51. <dependency>
  52. <groupId>com.github.pagehelper</groupId>
  53. <artifactId>pagehelper-spring-boot-starter</artifactId>
  54. <version>1.2.8</version>
  55. </dependency>
  56. <!-- String工具类包-->
  57. <dependency>
  58. <groupId>org.apache.commons</groupId>
  59. <artifactId>commons-lang3</artifactId>
  60. <version>3.5</version>
  61. </dependency>
  62. <!-- Markdown处理html -->
  63. <dependency>
  64. <groupId>com.atlassian.commonmark</groupId>
  65. <artifactId>commonmark</artifactId>
  66. <version>0.11.0</version>
  67. </dependency>
  68. <!-- Markdown处理表格 -->
  69. <dependency>
  70. <groupId>com.atlassian.commonmark</groupId>
  71. <artifactId>commonmark-ext-gfm-tables</artifactId>
  72. <version>0.11.0</version>
  73. </dependency>
  74. <!-- 过滤emoji表情字符 -->
  75. <dependency>
  76. <groupId>com.vdurmont</groupId>
  77. <artifactId>emoji-java</artifactId>
  78. <version>4.0.0</version>
  79. </dependency>
  80. <!-- devtools热部署工具 -->
  81. <dependency>
  82. <groupId>org.springframework.boot</groupId>
  83. <artifactId>spring-boot-devtools</artifactId>
  84. <scope>runtime</scope>
  85. </dependency>
  86. <!-- Spring Boot测试服务启动器 -->
  87. <dependency>
  88. <groupId>org.springframework.boot</groupId>
  89. <artifactId>spring-boot-starter-test</artifactId>
  90. <scope>test</scope>
  91. </dependency>
  92. </dependencies>

(2)编写配置文件

将application.properties全局配置文件更名为application.yml

  1. server:
  2. port: 80
  3. spring:
  4. profiles:
  5. # 外置jdbc、redis和mail配置文件
  6. active: jdbc,redis,mail
  7. # 关闭thymeleaf页面缓存
  8. thymeleaf:
  9. cache: false
  10. main:
  11. allow-circular-references: true
  12. # 配置国际化资源文件
  13. messages:
  14. basename: i18n.logo
  15. # MyBatis配置
  16. mybatis:
  17. configuration:
  18. #开启驼峰命名匹配映射
  19. map-underscore-to-camel-case: true
  20. #配置MyBatis的xml映射文件路径
  21. mapper-locations: classpath:mapper/*.xml
  22. #配置XML映射文件中指定的实体类别名路径
  23. type-aliases-package: com.lg.ch10.model.domain
  24. #pagehelper分页设置
  25. pagehelper:
  26. helper-dialect: mysql
  27. reasonable: true
  28. support-methods-arguments: true
  29. params: count=countSql
  30. #浏览器cookie相关设置
  31. COOKIE:
  32. # 设置cookie默认时长为30分钟
  33. VALIDITY: 1800

(3)前端资源引入

(4)后端基础代码导入

(5)在resources文件夹下创建application-jdbc.properties配置文件

  1. #添加并配置第三方数据库连接池druid
  2. spring.datasource.type = com.alibaba.druid.pool.DruidDataSource
  3. spring.datasource.initialSize=20
  4. spring.datasource.minIdle=10
  5. spring.datasource.maxActive=100
  6. #数据源连接配置
  7. spring.datasource.url = jdbc:mysql://localhost:3306/blog_system?serverTimezone=UTC&useSSL=false
  8. spring.datasource.username = root
  9. spring.datasource.password = 1234

(6)编写redis配置文件

在resources文件夹下application-redis.properties

  1. # Redis服务器地址,另外注意要开启Redis服务
  2. spring.redis.host=127.0.0.1
  3. # Redis服务器连接端口
  4. spring.redis.port=6379
  5. # Redis服务器连接密码(默认为空)
  6. spring.redis.password=
  7. # 连接池最大连接数(使用负值表示没有限制)
  8. spring.redis.jedis.pool.max-active=8
  9. # 连接池最大阻塞等待时间(使用负值表示没有限制)
  10. spring.redis.jedis.pool.max-wait=-1
  11. # 连接池中的最大空闲连接
  12. spring.redis.jedis.pool.max-idle=8

(7)编写mail配置文件

  1. 在resources文件夹下application-mail.properties
  2. #QQ邮箱邮件发送服务配置
  3. spring.mail.host=smtp.qq.com
  4. spring.mail.port=587
  5. # 配置个人QQ账户和密码(密码是加密后的授权码)
  6. spring.mail.username=1847147914@qq.com
  7. spring.mail.password=xwfsocuuhxqhiaib

(8)数据资源引入

在MySQL数据库中新建blog_system数据库并将数据库文件导入

2.文章分页展示

(1)数据访问层实现

① 在java\com.lg.ch10中创建dao包并在其中创建Dao层接口文件

ArticleMApper.java和StatisticMapper.java

ArticleMApper.java

  1. import com.lg.ch10.model.domain.Article;
  2. import org.apache.ibatis.annotations.*;
  3. import java.util.List;
  4. @Mapper
  5. public interface ArticleMApper {
  6. // 根据id查询文章信息
  7. @Select("SELECT * FROM t_article WHERE id=#{id}")
  8. public Article selectArticleWithId(Integer id);
  9. // 发表文章,同时使用@Options注解获取自动生成的主键id
  10. @Insert("INSERT INTO t_article (title,created,modified,tags,categories," +
  11. " allow_comment, thumbnail, content)" +
  12. " VALUES (#{title},#{created}, #{modified}, #{tags}, #{categories}," +
  13. " #{allowComment}, #{thumbnail}, #{content})")
  14. @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
  15. public Integer publishArticle(Article article);
  16. // 文章发分页查询
  17. @Select("SELECT * FROM t_article ORDER BY id DESC")
  18. public List<Article> selectArticleWithPage();
  19. // 通过id删除文章
  20. @Delete("DELETE FROM t_article WHERE id=#{id}")
  21. public void deleteArticleWithId(int id);
  22. // 站点服务统计,统计文章数量
  23. @Select("SELECT COUNT(1) FROM t_article")
  24. public Integer countArticle();
  25. // 通过id更新文章
  26. public Integer updateArticleWithId(Article article);
  27. }

StatisticMapper.java

  1. import com.lg.ch10.model.domain.Article;
  2. import com.lg.ch10.model.domain.Statistic;
  3. import org.apache.ibatis.annotations.*;
  4. import java.util.List;
  5. @Mapper
  6. public interface StatisticMapper {
  7. // 新增文章对应的统计信息
  8. @Insert("INSERT INTO t_statistic(article_id,hits,comments_num) values (#{id},0,0)")
  9. public void addStatistic(Article article);
  10. // 根据文章id查询点击量和评论量相关信息
  11. @Select("SELECT * FROM t_statistic WHERE article_id=#{articleId}")
  12. public Statistic selectStatisticWithArticleId(Integer articleId);
  13. // 通过文章id更新点击量
  14. @Update("UPDATE t_statistic SET hits=#{hits} " +
  15. "WHERE article_id=#{articleId}")
  16. public void updateArticleHitsWithId(Statistic statistic);
  17. // 通过文章id更新评论量
  18. @Update("UPDATE t_statistic SET comments_num=#{commentsNum} " +
  19. "WHERE article_id=#{articleId}")
  20. public void updateArticleCommentsWithId(Statistic statistic);
  21. // 根据文章id删除统计数据
  22. @Delete("DELETE FROM t_statistic WHERE article_id=#{aid}")
  23. public void deleteStatisticWithId(int aid);
  24. // 统计文章热度信息
  25. @Select("SELECT * FROM t_statistic WHERE hits !='0' " +
  26. "ORDER BY hits DESC, comments_num DESC")
  27. public List<Statistic> getStatistic();
  28. // 统计博客文章总访问量
  29. @Select("SELECT SUM(hits) FROM t_statistic")
  30. public long getTotalVisit();
  31. // 统计博客文章总评论量
  32. @Select("SELECT SUM(comments_num) FROM t_statistic")
  33. public long getTotalComment();
  34. }

② 在resources中创建名为mapper的包并在该包中创建ArticleMapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.lg.ch10.dao.ArticleMapper">
  4. <update id="updateArticleWithId" parameterType="Article">
  5. update t_article
  6. <set>
  7. <if test="title != null">
  8. title = #{title},
  9. </if>
  10. <if test="created != null">
  11. created = #{created},
  12. </if>
  13. <if test="modified != null">
  14. modified = #{modified},
  15. </if>
  16. <if test="tags != null">
  17. tags = #{tags},
  18. </if>
  19. <if test="categories != null">
  20. categories = #{categories},
  21. </if>
  22. <if test="hits != null">
  23. hits = #{hits},
  24. </if>
  25. <if test="commentsNum != null">
  26. comments_num = #{commentsNum},
  27. </if>
  28. <if test="allowComment != null">
  29. allow_comment = #{allowComment},
  30. </if>
  31. <if test="thumbnail != null">
  32. thumbnail = #{thumbnail},
  33. </if>
  34. <if test="content != null">
  35. content = #{content},
  36. </if>
  37. </set>
  38. where id = #{id}
  39. </update>
  40. </mapper>

(2)业务处理层实现

① 创建Service层接口文件

在com.lg.ch10中创建service包并在其中创建IArticleService.java

  1. import com.github.pagehelper.PageInfo;
  2. import java.util.List;
  3. public interface IArticleService {
  4. // 分页查询文章列表
  5. public PageInfo<com.lg.ch10.model.domain.Article> selectArticleWithPage(Integer page, Integer count);
  6. // 统计前10的热度文章信息
  7. public List<com.lg.ch10.model.domain.Article> getHeatArticles();
  8. }

② 创建Service下impl包层接口实现类文件ArticleServiceImpl.java

  1. import com.github.pagehelper.PageHelper;
  2. import com.github.pagehelper.PageInfo;
  3. import com.lg.ch10.dao.ArticleMapper;
  4. import com.lg.ch10.dao.StatisticMapper;
  5. import com.lg.ch10.model.domain.Article;
  6. import com.lg.ch10.model.domain.Statistic;
  7. import com.lg.ch10.service.IArticleService;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.transaction.annotation.Transactional;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. @Service
  14. @Transactional
  15. public class ArticleServiceImpl implements IArticleService {
  16. @Autowired
  17. private ArticleMapper articleMapper;
  18. @Autowired
  19. private StatisticMapper statisticMapper;
  20. // 分页查询文章列表
  21. @Override
  22. public PageInfo<Article> selectArticleWithPage(Integer page, Integer count) {
  23. PageHelper.startPage(page, count);
  24. List<Article> articleList = articleMapper.selectArticleWithPage();
  25. // 封装文章统计数据
  26. for (int i = 0; i < articleList.size(); i++) {
  27. Article article = articleList.get(i);
  28. Statistic statistic = statisticMapper.selectStatisticWithArticleId(article.getId());
  29. article.setHits(statistic.getHits());
  30. article.setCommentsNum(statistic.getCommentsNum());
  31. }
  32. PageInfo<Article> pageInfo=new PageInfo<>(articleList);
  33. return pageInfo;
  34. }
  35. // 统计前10的热度文章信息
  36. @Override
  37. public List<Article> getHeatArticles( ) {
  38. List<Statistic> list = statisticMapper.getStatistic();
  39. List<Article> articlelist=new ArrayList<>();
  40. for (int i = 0; i < list.size(); i++) {
  41. Article article = articleMapper.selectArticleWithId(list.get(i).getArticleId());
  42. article.setHits(list.get(i).getHits());
  43. article.setCommentsNum(list.get(i).getCommentsNum());
  44. articlelist.add(article);
  45. if(i>=9){
  46. break;
  47. }
  48. }
  49. return articlelist;
  50. }
  51. }

(3)请求处理层实现

① 在com.lg.ch10文件夹下创建web文件夹并在下面创建client文件夹后创建IndexController.java

  1. import com.github.pagehelper.PageInfo;
  2. import com.lg.ch10.model.domain.Article;
  3. import com.lg.ch10.service.IArticleService;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Controller;
  8. import org.springframework.web.bind.annotation.GetMapping;
  9. import org.springframework.web.bind.annotation.PathVariable;
  10. import org.springframework.web.bind.annotation.RequestParam;
  11. import javax.servlet.http.HttpServletRequest;
  12. import java.util.List;
  13. @Controller
  14. public class IndexController {
  15. private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
  16. @Autowired
  17. private IArticleService articleServiceImpl;
  18. // 博客首页,会自动跳转到文章页
  19. @GetMapping(value = "/")
  20. private String index(HttpServletRequest request) {
  21. return this.index(request, 1, 5);
  22. }
  23. // 文章页
  24. @GetMapping(value = "/page/{p}")
  25. public String index(HttpServletRequest request, @PathVariable("p") int page, @RequestParam(value = "count", defaultValue = "5") int count) {
  26. PageInfo<Article> articles = articleServiceImpl.selectArticleWithPage(page, count);
  27. // 获取文章热度统计信息
  28. List<Article> articleList = articleServiceImpl.getHeatArticles();
  29. request.setAttribute("articles", articles);
  30. request.setAttribute("articleList", articleList);
  31. logger.info("分页获取文章信息: 页码 "+page+",条数 "+count);
  32. return "client/index";
  33. }
  34. }

② web文件夹并在下面创建interceptor文件夹后创建BaseInterceptor.java

  1. import com.lg.ch10.utils.Commons;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.servlet.HandlerInterceptor;
  5. import org.springframework.web.servlet.ModelAndView;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. @Configuration
  9. public class BaseInterceptor implements HandlerInterceptor {
  10. @Autowired
  11. private Commons commons;
  12. @Override
  13. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  14. return true;
  15. }
  16. @Override
  17. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  18. // 用户将封装的Commons工具返回页面
  19. request.setAttribute("commons",commons);
  20. }
  21. @Override
  22. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  23. }
  24. }

③ 在interceptor文件夹中创建WebMvcConfig.java

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  5. @Configuration
  6. public class WebMvcConfig implements WebMvcConfigurer {
  7. @Autowired
  8. private BaseInterceptor baseInterceptor;
  9. @Override
  10. // 重写addInterceptors()方法,注册自定义拦截器
  11. public void addInterceptors(InterceptorRegistry registry) {
  12. registry.addInterceptor(baseInterceptor);
  13. }
  14. }

(4)实现前端页面功能

在resources\templates\client下创建index.html文件

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <!-- 载入文章头部页面,位置在/client文件夹下的header模板页面,模板名称th:fragment为header -->
  4. <div th:replace="/client/header::header(null,null)" />
  5. <body>
  6. <div class="am-g am-g-fixed blog-fixed index-page">
  7. <div class="am-u-md-8 am-u-sm-12">
  8. <!-- 文章遍历并分页展示 -->
  9. <div th:each="article: ${articles.list}">
  10. <article class="am-g blog-entry-article">
  11. <div class="am-u-lg-6 am-u-md-12 am-u-sm-12 blog-entry-img">
  12. <img width="100%" class="am-u-sm-12" th:src="@{${commons.show_thumb(article)}}"/>
  13. </div>
  14. <div class="am-u-lg-6 am-u-md-12 am-u-sm-12 blog-entry-text">
  15. <!-- 文章分类 -->
  16. <span class="blog-color"style="font-size: 15px;"><a>默认分类</a></span>
  17. <span>&nbsp;&nbsp;&nbsp;</span>
  18. <!-- 发布时间 -->
  19. <span style="font-size: 15px;" th:text="'发布于 '+ ${commons.dateFormat(article.created)}" />
  20. <h2>
  21. <div><a style="color: #0f9ae0;font-size: 20px;" th:href="${commons.permalink(article.id)}" th:text="${article.title}" />
  22. </div>
  23. </h2>
  24. <!-- 文章摘要-->
  25. <div style="font-size: 16px;" th:utext="${commons.intro(article,75)}" />
  26. </div>
  27. </article>
  28. </div>
  29. <!-- 文章分页信息 -->
  30. <div class="am-pagination">
  31. <div th:replace="/comm/paging::pageNav(${articles},'上一页','下一页','page')" />
  32. </div>
  33. </div>
  34. <!-- 博主信息描述 -->
  35. <div class="am-u-md-4 am-u-sm-12 blog-sidebar">
  36. <div class="blog-sidebar-widget blog-bor">
  37. <h2 class="blog-text-center blog-title"><span>CrazyStone</span></h2>
  38. <img th:src="@{/assets/img/me.jpg}" alt="about me" class="blog-entry-img"/>
  39. <p>
  40. Java后台开发
  41. </p>
  42. <p>个人博客小站,主要发表关于Java、Spring、Docker等相关文章</p>
  43. </div>
  44. <div class="blog-sidebar-widget blog-bor">
  45. <h2 class="blog-text-center blog-title"><span>联系我</span></h2>
  46. <p>
  47. <a><span class="am-icon-github am-icon-fw blog-icon"></span></a>
  48. <a><span class="am-icon-weibo am-icon-fw blog-icon"></span></a>
  49. </p>
  50. </div>
  51. </div>
  52. <!-- 阅读排行榜 -->
  53. <div class="am-u-md-4 am-u-sm-12 blog-sidebar">
  54. <div class="blog-sidebar-widget blog-bor">
  55. <h2 class="blog-text-center blog-title"><span>阅读排行榜</span></h2>
  56. <div style="text-align: left">
  57. <th:block th:each="article :${articleList}">
  58. <a style="font-size: 15px;" th:href="@{'/article/'+${article.id}}"
  59. th:text="${articleStat.index+1}+'、'+${article.title}+'('+${article.hits}+')'">
  60. </a>
  61. <hr style="margin-top: 0.6rem;margin-bottom: 0.4rem" />
  62. </th:block>
  63. </div>
  64. </div>
  65. </div>
  66. </div>
  67. </body>
  68. <!-- 载入文章尾部页面,位置在/client文件夹下的footer模板页面,模板名称th:fragment为footer -->
  69. <div th:replace="/client/footer::footer" />
  70. </html>

(5)效果展示

在浏览器输入:localhost/login

输入账号:user和控制台随机生成的密码

3.文章分页展示

(1)数据访问层实现

在dao文件夹下创建CommentMapper.java

  1. import com.lg.ch10.model.domain.Comment;
  2. import org.apache.ibatis.annotations.*;
  3. import java.util.List;
  4. @Mapper
  5. public interface CommentMapper {
  6. // 分页展示某个文章的评论
  7. @Select("SELECT * FROM t_comment WHERE article_id=#{aid} ORDER BY id DESC")
  8. public List<Comment> selectCommentWithPage(Integer aid);
  9. // 后台查询最新几条评论
  10. @Select("SELECT * FROM t_comment ORDER BY id DESC")
  11. public List<Comment> selectNewComment();
  12. // 发表评论
  13. @Insert("INSERT INTO t_comment (article_id,created,author,ip,content)" +
  14. " VALUES (#{articleId}, #{created},#{author},#{ip},#{content})")
  15. public void pushComment(Comment comment);
  16. // 站点服务统计,统计评论数量
  17. @Select("SELECT COUNT(1) FROM t_comment")
  18. public Integer countComment();
  19. // 通过文章id删除评论信息
  20. @Delete("DELETE FROM t_comment WHERE article_id=#{aid}")
  21. public void deleteCommentWithId(Integer aid);
  22. }

(2)业务处理层实现

① 创建Service层接口文件

在service文件夹下的IArticleService中编写id接口方法

  1. // 根据文章id查询单个文章详情
  2. public Article selectArticleWithId(Integer id);
  3. 在service中创建ICommentService.java
  4. import com.github.pagehelper.PageInfo;
  5. import com.lg.ch10.model.domain.Comment;
  6. public interface ICommentService {
  7. // 获取文章下的评论
  8. public PageInfo<Comment> getComments(Integer aid, int page, int count);
  9. }

在service中创建ISiteService.java

  1. import com.lg.ch10.model.ResponseData.StaticticsBo;
  2. import com.lg.ch10.model.domain.Article;
  3. import com.lg.ch10.model.domain.Comment;
  4. import java.util.List;
  5. public interface ISiteService {
  6. // 最新收到的评论
  7. public List<Comment> recentComments(int count);
  8. // 最新发表的文章
  9. public List<Article> recentArticles(int count);
  10. // 获取后台统计数据
  11. public StaticticsBo getStatistics();
  12. // 更新某个文章的统计数据
  13. public void updateStatistics(Article article);
  14. }

② 创建Service层接口实现类文件

在service\impl文件夹下创建ArticleServiceImpl中添加

  1. @Autowired
  2. private RedisTemplate redisTemplate;
  3. // 根据id查询单个文章详情,并使用Redis进行缓存管理
  4. public Article selectArticleWithId(Integer id){
  5. Article article = null;
  6. Object o = redisTemplate.opsForValue().get("article_" + id);
  7. if(o!=null){
  8. article=(Article)o;
  9. }else{
  10. article = articleMapper.selectArticleWithId(id);
  11. if(article!=null){
  12. redisTemplate.opsForValue().set("article_" + id,article);
  13. }
  14. }
  15. return article;
  16. }

在service\impl文件夹下创建CommentServiceImpl.java

  1. import com.github.pagehelper.PageHelper;
  2. import com.github.pagehelper.PageInfo;
  3. import com.lg.ch10.dao.CommentMapper;
  4. import com.lg.ch10.model.domain.Comment;
  5. import com.lg.ch10.service.ICommentService;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import java.util.List;
  10. @Service
  11. @Transactional
  12. public class CommentServiceImpl implements ICommentService {
  13. @Autowired
  14. private CommentMapper commentMapper;
  15. // 根据文章id分页查询评论
  16. @Override
  17. public PageInfo<Comment> getComments(Integer aid, int page, int count) {
  18. PageHelper.startPage(page,count);
  19. List<Comment> commentList = commentMapper.selectCommentWithPage(aid);
  20. PageInfo<Comment> commentInfo = new PageInfo<>(commentList);
  21. return commentInfo;
  22. }
  23. }

在service\impl文件夹下创建SiteServiceImpl.java

  1. import com.github.pagehelper.PageHelper;
  2. import com.lg.ch10.dao.ArticleMapper;
  3. import com.lg.ch10.dao.CommentMapper;
  4. import com.lg.ch10.dao.StatisticMapper;
  5. import com.lg.ch10.model.ResponseData.StaticticsBo;
  6. import com.lg.ch10.model.domain.Article;
  7. import com.lg.ch10.model.domain.Comment;
  8. import com.lg.ch10.model.domain.Statistic;
  9. import com.lg.ch10.service.ISiteService;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.stereotype.Service;
  12. import org.springframework.transaction.annotation.Transactional;
  13. import java.util.List;
  14. @Service
  15. @Transactional
  16. public class SiteServiceImpl implements ISiteService {
  17. @Autowired
  18. private CommentMapper commentMapper;
  19. @Autowired
  20. private ArticleMapper articleMapper;
  21. @Autowired
  22. private StatisticMapper statisticMapper;
  23. @Override
  24. public void updateStatistics(Article article) {
  25. Statistic statistic = statisticMapper.selectStatisticWithArticleId(article.getId());
  26. statistic.setHits(statistic.getHits()+1);
  27. statisticMapper.updateArticleHitsWithId(statistic);
  28. }
  29. @Override
  30. public List<Comment> recentComments(int limit) {
  31. PageHelper.startPage(1, limit>10 || limit<1 ? 10:limit);
  32. List<Comment> byPage = commentMapper.selectNewComment();
  33. return byPage;
  34. }
  35. @Override
  36. public List<Article> recentArticles(int limit) {
  37. PageHelper.startPage(1, limit>10 || limit<1 ? 10:limit);
  38. List<Article> list = articleMapper.selectArticleWithPage();
  39. // 封装文章统计数据
  40. for (int i = 0; i < list.size(); i++) {
  41. Article article = list.get(i);
  42. Statistic statistic = statisticMapper.selectStatisticWithArticleId(article.getId());
  43. article.setHits(statistic.getHits());
  44. article.setCommentsNum(statistic.getCommentsNum());
  45. }
  46. return list;
  47. }
  48. @Override
  49. public StaticticsBo getStatistics() {
  50. StaticticsBo staticticsBo = new StaticticsBo();
  51. Integer articles = articleMapper.countArticle();
  52. Integer comments = commentMapper.countComment();
  53. staticticsBo.setArticles(articles);
  54. staticticsBo.setComments(comments);
  55. return staticticsBo;
  56. }
  57. }

(3)请求处理层实现

在web\client文件夹下的IndexController.java中添加

  1. @Autowired
  2. private ICommentService commentServiceImpl;
  3. @Autowired
  4. private ISiteService siteServiceImpl;
  5. // 文章详情查询
  6. @GetMapping(value = "/article/{id}")
  7. public String getArticleById(@PathVariable("id") Integer id, HttpServletRequest request){
  8. Article article = articleServiceImpl.selectArticleWithId(id);
  9. if(article!=null){
  10. // 查询封装评论相关数据
  11. getArticleComments(request, article);
  12. // 更新文章点击量
  13. siteServiceImpl.updateStatistics(article);
  14. request.setAttribute("article",article);
  15. return "client/articleDetails";
  16. }else {
  17. logger.warn("查询文章详情结果为空,查询文章id: "+id);
  18. // 未找到对应文章页面,跳转到提示页
  19. return "comm/error_404";
  20. }
  21. }
  22. // 查询文章的评论信息,并补充到文章详情里面
  23. private void getArticleComments(HttpServletRequest request, Article article) {
  24. if (article.getAllowComment()) {
  25. // cp表示评论页码,commentPage
  26. String cp = request.getParameter("cp");
  27. cp = StringUtils.isBlank(cp) ? "1" : cp;
  28. request.setAttribute("cp", cp);
  29. PageInfo<Comment> comments = commentServiceImpl.getComments(article.getId(),Integer.parseInt(cp),3);
  30. request.setAttribute("cp", cp);
  31. request.setAttribute("comments", comments);
  32. }
  33. }

(4)实现前端页面功能

在templates\client中创建acticleDetails.html

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <script th:src="@{/assets/js/jquery.min.js}"></script>
  5. <script th:src="@{/assets/js/layer.js}"></script>
  6. </head>
  7. <div th:replace="client/header::header(${article.title},null)"></div>
  8. <body>
  9. <article class="main-content post-page">
  10. <div class="post-header">
  11. <h1 class="post-title" itemprop="name headline" th:text="${article.title}"></h1>
  12. <div class="post-data">
  13. <time th:datetime="${commons.dateFormat(article.created)}" itemprop="datePublished" th:text="'发布于 '+ ${commons.dateFormat(article.created)}"></time>
  14. </div>
  15. </div>
  16. <br />
  17. <div id="post-content" class="post-content content" th:utext="${commons.article(article.content)}"></div>
  18. </article>
  19. <div th:replace="client/comments::comments"></div>
  20. <div th:replace="client/footer::footer"></div>
  21. <!-- 使用layer.js实现图片缩放功能 -->
  22. <script type="text/JavaScript">
  23. $('.post-content img').on('click', function(){
  24. var imgurl=$(this).attr('src');
  25. var w=this.width*2;
  26. var h=this.height*2+50;
  27. layer.open({
  28. type: 1,
  29. title: false, //不显示标题栏
  30. area: [w+"px", h+"px"],
  31. shadeClose: true, //点击遮罩关闭
  32. content: '\<\div style="padding:20px;">' +
  33. '\<\img style="width:'+(w-50)+'px;" src='+imgurl+'\>\<\/div>'
  34. });
  35. });
  36. </script>
  37. </body>
  38. </html>

(5)Redis服务启动与配置

(6)效果展示

4.文章评论管理

(1)业务处理层实现

① 编写service层接口方法

在ICommentService中编写发布文章评论的方法

  1. // 用户发表评论
  2. public void pushComment(Comment comment);

② 编写service层接口实现类方法

在CommentServiceImpl中增加评论发布方法

  1. @Autowired
  2. private StatisticMapper statisticMapper;
  3. @Override
  4. public void pushComment(Comment comment){
  5. commentMapper.pushComment(comment);
  6. // 更新文章评论数据量
  7. Statistic statistic = statisticMapper.selectStatisticWithArticleId(comment.getArticleId());
  8. statistic.setCommentsNum(statistic.getCommentsNum()+1);
  9. statisticMapper.updateArticleCommentsWithId(statistic);
  10. }

(2)请求处理层实现

在com.lg.ch10/web/client下创建用户评论管理的控制类CommentController.java文件

  1. import com.lg.ch10.model.ResponseData.ArticleResponseData;
  2. import com.lg.ch10.model.domain.Comment;
  3. import com.lg.ch10.service.ICommentService;
  4. import com.lg.ch10.utils.MyUtils;
  5. import com.vdurmont.emoji.EmojiParser;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.security.core.context.SecurityContextHolder;
  10. import org.springframework.security.core.userdetails.User;
  11. import org.springframework.stereotype.Controller;
  12. import org.springframework.web.bind.annotation.PostMapping;
  13. import org.springframework.web.bind.annotation.RequestMapping;
  14. import org.springframework.web.bind.annotation.RequestParam;
  15. import org.springframework.web.bind.annotation.ResponseBody;
  16. import javax.servlet.http.HttpServletRequest;
  17. import java.util.Date;
  18. @Controller
  19. @RequestMapping("/comments")
  20. public class CommentController {
  21. private static final Logger logger = LoggerFactory.getLogger(CommentController.class);
  22. @Autowired
  23. private ICommentService commentServcieImpl;
  24. // 发表评论操作
  25. @PostMapping(value = "/publish")
  26. @ResponseBody
  27. public ArticleResponseData publishComment(HttpServletRequest request, @RequestParam Integer aid, @RequestParam String text) {
  28. // 去除js脚本
  29. text = MyUtils.cleanXSS(text);
  30. text = EmojiParser.parseToAliases(text);
  31. // 获取当前登录用户
  32. User user=(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  33. // 封装评论信息
  34. Comment comments = new Comment();
  35. comments.setArticleId(aid);
  36. comments.setIp(request.getRemoteAddr());
  37. comments.setCreated(new Date());
  38. comments.setAuthor(user.getUsername());
  39. comments.setContent(text);
  40. try {
  41. commentServcieImpl.pushComment(comments);
  42. logger.info("发布评论成功,对应文章id: "+aid);
  43. return ArticleResponseData.ok();
  44. } catch (Exception e) {
  45. logger.error("发布评论失败,对应文章id: "+aid +";错误描述: "+e.getMessage());
  46. return ArticleResponseData.fail();
  47. }
  48. }
  49. }

(3)实现前端页面功能

打开client文件夹中的comments.html,实现前端页面评论展示的实现。

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org" th:fragment="comments"
  3. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
  4. <body>
  5. <div th:if="${article}!=null">
  6. <div th:id="${article.id ?: 0}" class="comment-container">
  7. <div id="comments" class="clearfix">
  8. <div th:if="${article.allowComment}">
  9. <span class="response">
  10. <form name="logoutform" th:action="@{/logout}" method="post"></form>
  11. <th:block sec:authorize="isAuthenticated()">
  12. Hello,<a data-no-instant="" sec:authentication="name"></a>
  13. 如果你想 <a href="javascript:document.logoutform.submit();">注销</a> ?
  14. </th:block>
  15. <th:block sec:authorize="isAnonymous()">
  16. 用户想要评论,请先<a th:href="@{/login}" title="登录" data-no-instant="">登录</a>!
  17. </th:block>
  18. </span>
  19. <div sec:authorize="isAuthenticated()">
  20. <form id="comment-form" class="comment-form" role="form" onsubmit="return TaleComment.subComment();">
  21. <input type="hidden" name="aid" id="aid" th:value="${article.id}"/>
  22. <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
  23. <textarea name="text" id="textarea" class="form-control" placeholder="以上信息可以为空,评论不能为空哦!"
  24. required="required" minlength="5" maxlength="2000"></textarea>
  25. <button type="submit" class="submit" id="misubmit">提交</button>
  26. </form>
  27. </div>
  28. </div>
  29. <!-- 分页显示其他评论内容 -->
  30. <div th:if="${comments}">
  31. <ol class="comment-list">
  32. <th:block th:each="comment :${comments.list}">
  33. <li th:id="'li-comment-'+${comment.id}" class="comment-body comment-parent comment-odd">
  34. <div th:id="'comment-'+${comment.id}">
  35. <div class="comment-view" onclick="">
  36. <div class="comment-header">
  37. <!--设置人物头像和名称-->
  38. <img class="avatar" th:src="@{/assets/img/avatars.jpg}" height="50"/>
  39. <a class="comment-author" rel="external nofollow" th:text="${comment.author}" />
  40. </div>
  41. <!-- 评论内容 -->
  42. <div class="comment-content">
  43. <span class="comment-author-at"></span>
  44. <p th:utext="${commons.article(comment.content)}"></p>
  45. </div>
  46. <!-- 评论日期 -->
  47. <div class="comment-meta">
  48. <time class="comment-time" th:text="${commons.dateFormat(comment.created)}"></time>
  49. <a sec:authorize="isAuthenticated()" th:if="${comment.author}!= ${session.SPRING_SECURITY_CONTEXT.authentication.principal.username}" href="javascript:void(0)" style="color: #1b961b">
  50. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;回复
  51. </a>
  52. </div>
  53. </div>
  54. </div>
  55. </li>
  56. </th:block>
  57. </ol>
  58. <!-- 进行评论分页 -->
  59. <div class="lists-navigator clearfix">
  60. <ol class="page-navigator">
  61. <!-- 判断并展示上一页 -->
  62. <th:block th:if="${comments.hasPreviousPage}">
  63. <li class="prev"><a th:href="'?cp='+${comments.prePage}+'#comments'">上一页</a></li>
  64. </th:block>
  65. <!-- 判断并展示中间页 -->
  66. <th:block th:each="navIndex : ${comments.navigatepageNums}">
  67. <th:block th:if="${comments.pages} <= 5">
  68. <li th:class="${comments.pageNum}==${navIndex}?'current':''">
  69. <a th:href="'?cp='+${navIndex}+'#comments'" th:text="${navIndex}"></a>
  70. </li>
  71. </th:block>
  72. <th:block th:if="${comments.pages} > 5">
  73. <li th:if="${comments.pageNum <=3 && navIndex <= 5}" th:class="${comments.pageNum}==${navIndex}?'current':''">
  74. <a th:href="'?cp='+${navIndex}+'#comments'" th:text="${navIndex}"></a>
  75. </li>
  76. <li th:if="${comments.pageNum >= comments.pages-2 && navIndex > comments.pages-5}" th:class="${comments.pageNum}==${navIndex}?'current':''">
  77. <a th:href="'?cp='+${navIndex}+'#comments'" th:text="${navIndex}"></a>
  78. </li>
  79. <li th:if="${comments.pageNum >=4 && comments.pageNum <= comments.pages-3 && navIndex >= comments.pageNum-2 && navIndex <= comments.pageNum+2}" th:class="${comments.pageNum}==${navIndex}?'current':''">
  80. <a th:href="'?cp='+${navIndex}+'#comments'" th:text="${navIndex}"></a>
  81. </li>
  82. </th:block>
  83. </th:block>
  84. <!-- 判断并展示下一页 -->
  85. <th:block th:if="${comments.hasNextPage}">
  86. <li class="next"><a th:href="'?cp='+${comments.nextPage}+'#comments'">下一页</a></li>
  87. </th:block>
  88. </ol>
  89. </div>
  90. </div>
  91. </div>
  92. </div>
  93. </div>
  94. </body>
  95. <div th:replace="comm/tale_comment::tale_comment"></div>
  96. </html>

打开comm文件夹下的table_comment.html进行查看

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org" th:fragment="tale_comment" >
  3. <body>
  4. <script type="text/javascript">
  5. /*<![CDATA[*/
  6. (function () {
  7. window.TaleComment = {
  8. subComment: function () {
  9. $.ajax({
  10. type: 'post',
  11. url: '/comments/publish',
  12. data: $('#comment-form').serialize(),
  13. async: false,
  14. dataType: 'json',
  15. success: function (result) {
  16. if (result && result.success) {
  17. window.alert("评论提交成功!");
  18. window.location.reload();
  19. } else {
  20. window.alert("发送失败")
  21. if (result.msg) {
  22. alert(result.msg);
  23. }
  24. }
  25. }
  26. });
  27. return false;
  28. }
  29. };
  30. })();
  31. </script>
  32. </body>
  33. </html>

(4)效果展示

5.数据展示

(1)请求处理层实现

在com.lg.ch10/web文件夹下创建admin文件夹,并在其中创建后台管理控制类AdminController

  1. import com.lg.ch10.model.ResponseData.StaticticsBo;
  2. import com.lg.ch10.model.domain.Article;
  3. import com.lg.ch10.model.domain.Comment;
  4. import com.lg.ch10.service.ISiteService;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Controller;
  9. import org.springframework.web.bind.annotation.GetMapping;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import javax.servlet.http.HttpServletRequest;
  12. import java.util.List;
  13. @Controller
  14. @RequestMapping("/admin")
  15. public class AdminController {
  16. private static final Logger logger = LoggerFactory.getLogger(AdminController.class);
  17. @Autowired
  18. private ISiteService siteServiceImpl;
  19. // 管理中心起始页
  20. @GetMapping(value = {"", "/index"})
  21. public String index(HttpServletRequest request) {
  22. // 获取最新的5篇博客、评论以及统计数据
  23. List<Article> articles = siteServiceImpl.recentArticles(5);
  24. List<Comment> comments = siteServiceImpl.recentComments(5);
  25. StaticticsBo staticticsBo = siteServiceImpl.getStatistics();
  26. // 向Request域中存储数据
  27. request.setAttribute("comments", comments);
  28. request.setAttribute("articles", articles);
  29. request.setAttribute("statistics", staticticsBo);
  30. return "back/index";
  31. }
  32. }

(2)实现后台前端页面功能

back文件夹中index.html是后台系统的首页

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org" th:with="title='管理中心',active='home'">
  3. <header th:replace="back/header::headerFragment(${title},${active})"></header>
  4. <body class="fixed-left">
  5. <div id="wrapper">
  6. <div th:replace="back/header::header-body"></div>
  7. <div class="content-page">
  8. <div class="content">
  9. <div class="container">
  10. <div class="row">
  11. <div class="col-sm-12">
  12. <h4 class="page-title">仪表盘</h4>
  13. </div>
  14. <div class="row">
  15. <div class="col-sm-6 col-lg-3">
  16. <div class="mini-stat clearfix bx-shadow bg-info">
  17. <span class="mini-stat-icon"><i class="fa fa-quote-right" aria-hidden="true"></i></span>
  18. <div class="mini-stat-info text-right">
  19. 发表了<span class="counter" th:text="${statistics.articles}"></span>篇文章
  20. </div>
  21. </div>
  22. </div>
  23. <div class="col-sm-6 col-lg-3">
  24. <div class="mini-stat clearfix bg-purple bx-shadow">
  25. <span class="mini-stat-icon"><i class="fa fa-comments-o" aria-hidden="true"></i></span>
  26. <div class="mini-stat-info text-right">
  27. 收到了<span class="counter" th:text="${statistics.comments}"></span>条留言
  28. </div>
  29. </div>
  30. </div>
  31. </div>
  32. <div class="row">
  33. <div class="col-md-4">
  34. <div class="panel panel-default">
  35. <div class="panel-heading">
  36. <h4 class="panel-title">最新文章</h4>
  37. </div>
  38. <div class="panel-body">
  39. <ul class="list-group">
  40. <li class="list-group-item" th:each="article : ${articles}">
  41. <span class="badge badge-primary" th:text="${article.commentsNum}">
  42. </span>
  43. <a target="_blank" th:href="${commons.site_url('/article/')}+${article.id}"
  44. th:text="${article.title}"></a>
  45. </li>
  46. </ul>
  47. </div>
  48. </div>
  49. </div>
  50. <div class="col-md-4">
  51. <div class="panel panel-default">
  52. <div class="panel-heading">
  53. <h4 class="panel-title">最新留言</h4>
  54. </div>
  55. <div class="panel-body">
  56. <div th:if="${comments.size()}==0">
  57. <div class="alert alert-warning">
  58. 还没有收到留言.
  59. </div>
  60. </div>
  61. <ul class="list-group" th:unless="${comments}==null and ${comments.size()}==0">
  62. <li class="list-group-item" th:each="comment : ${comments}">
  63. <th:block th:text="${comment.author}"/><th:block th:text="${commons.dateFormat(comment.created)}"/>
  64. <a th:href="${commons.site_url('/article/')}+${comment.articleId}+'#comments'"
  65. target="_blank" th:utext="${commons.article(comment.content)}"></a>
  66. </li>
  67. </ul>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. </div>
  73. <div th:replace="back/footer :: footer-content"></div>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. <div th:replace="back/footer :: footer"></div>
  79. </body>
  80. </html>

(3)效果展示

6.文章发布

(1)业务处理层实现

① 编写service层接口方法

在IArticleService中添加

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

② 编写service层接口实现类方法

在ArticleServiceImpl中添加

  1. // 发布文章
  2. @Override
  3. public void publish(Article article) {
  4. // 去除表情
  5. article.setContent(EmojiParser.parseToAliases(article.getContent()));
  6. article.setCreated(new Date());
  7. article.setHits(0);
  8. article.setCommentsNum(0);
  9. // 插入文章,同时插入文章统计数据
  10. articleMapper.publishArticle(article);
  11. statisticMapper.addStatistic(article);
  12. }

(2)请求处理层实现

在后台管理控制类AdminController中添加页面跳转请求的方法

  1. @Autowired
  2. private IArticleService articleServiceImpl;
  3. // 向文章发表页面跳转
  4. @GetMapping(value = "/article/toEditPage")
  5. public String newArticle( ) {
  6. return "back/article_edit";
  7. }
  8. // 发表文章
  9. @PostMapping(value = "/article/publish")
  10. @ResponseBody
  11. public ArticleResponseData publishArticle(Article article) {
  12. if (StringUtils.isBlank(article.getCategories())) {
  13. article.setCategories("默认分类");
  14. }
  15. try {
  16. articleServiceImpl.publish(article);
  17. logger.info("文章发布成功");
  18. return ArticleResponseData.ok();
  19. } catch (Exception e) {
  20. logger.error("文章发布失败,错误信息: "+e.getMessage());
  21. return ArticleResponseData.fail();
  22. }
  23. }
  24. // 跳转到后台文章列表页面
  25. @GetMapping(value = "/article")
  26. public String index(@RequestParam(value = "page", defaultValue = "1") int page,
  27. @RequestParam(value = "count", defaultValue = "10") int count,
  28. HttpServletRequest request) {
  29. PageInfo<Article> pageInfo = articleServiceImpl.selectArticleWithPage(page, count);
  30. request.setAttribute("articles", pageInfo);
  31. return "back/article_list";
  32. }

(3)实现前端页面功能

打开back文件夹中的article_edit.html文件,该文件用于实现文章编辑和发布

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org" th:with="title='发布文章',active='publish'">
  3. <header th:replace="back/header::headerFragment(${title},${active})"></header>
  4. <link th:href="@{/back/plugins/tagsinput/jquery.tagsinput.css}" rel="stylesheet"/>
  5. <link th:href="@{/back/plugins/select2.dist.css/select2-bootstrap.css}" rel="stylesheet"/>
  6. <link th:href="@{/back/plugins/toggles/toggles.css}" rel="stylesheet"/>
  7. <link th:href="@{/back/plugins/mditor/css/mditor.min.css}" rel="stylesheet"/>
  8. <link th:href="@{/back/plugins/dropzone/4.3.0/min/dropzone.min.css}" rel="stylesheet">
  9. <style>
  10. #tags_tagsinput {
  11. background-color: #fafafa;
  12. border: 1px solid #eeeeee;
  13. }
  14. #tags_addTag input {
  15. width: 100%;
  16. }
  17. #tags_addTag {
  18. margin-top: -5px;
  19. }
  20. .mditor .editor{
  21. font-size: 14px;
  22. font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
  23. }
  24. .mditor .backdrop, .mditor .textarea, .mditor .viewer{
  25. font-size: 14px;
  26. }
  27. .markdown-body{
  28. font-size: 14px;
  29. }
  30. .note-toolbar {
  31. text-align: center;
  32. }
  33. .note-editor.note-frame {
  34. border: none;
  35. }
  36. .note-editor .note-toolbar {
  37. background-color: #f5f5f5;
  38. padding-bottom: 10px;
  39. }
  40. .note-toolbar .note-btn-group {
  41. margin: 0;
  42. }
  43. .note-toolbar .note-btn {
  44. border: none;
  45. }
  46. #articleForm #dropzone {
  47. min-height: 200px;
  48. background-color: #dbdde0;
  49. line-height:200px;
  50. margin-bottom: 10px;
  51. }
  52. #articleForm .dropzone {
  53. border: 1px dashed #8662c6;
  54. border-radius: 5px;
  55. background: white;
  56. }
  57. #articleForm .dropzone .dz-message {
  58. font-weight: 400;
  59. }
  60. #articleForm .dropzone .dz-message .note {
  61. font-size: 0.8em;
  62. font-weight: 200;
  63. display: block;
  64. margin-top: 1.4rem;
  65. }
  66. </style>
  67. <body class="fixed-left">
  68. <div id="wrapper">
  69. <div th:replace="back/header::header-body"></div>
  70. <div class="content-page">
  71. <div class="content">
  72. <div class="container">
  73. <div class="row">
  74. <div class="col-sm-12">
  75. <h4 class="page-title">
  76. <th:block th:if="${null != contents}">
  77. 编辑文章
  78. </th:block>
  79. <th:block th:unless="${null != contents}">
  80. 发布文章
  81. </th:block>
  82. </h4>
  83. </div>
  84. <div class="col-md-12">
  85. <form id="articleForm">
  86. <input type="hidden" name="id"
  87. th:value="${contents!=null and contents.id!=null}?${contents.id}: ''" id="id"/>
  88. <input type="hidden" name="allowComment"
  89. th:value="${contents!=null and contents.allowComment !=null}
  90. ?${contents.allowComment}: true" id="allow_comment"/>
  91. <input type="hidden" name="content" id="content-editor"/>
  92. <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
  93. <div class="form-group col-md-6" style="padding: 0 10px 0 0;">
  94. <th:block th:if="${null != contents}">
  95. <input type="text" class="form-control" name="title"
  96. required="required" aria-required="true" th:value="${contents.title}"/>
  97. </th:block>
  98. <th:block th:unless="${null != contents}">
  99. <input type="text" class="form-control" placeholder="请输入文章标题(必须)" name="title"
  100. required="required" aria-required="true"/>
  101. </th:block>
  102. </div>
  103. <div class="form-group col-md-6" style="padding: 0 10px 0 0;">
  104. <th:block th:if="${null != contents}">
  105. <input name="tags" id="tags" type="text" class="form-control" th:value="${contents.tags}" />
  106. </th:block>
  107. <th:block th:unless="${null != contents}">
  108. <input name="tags" id="tags" type="text" class="form-control" placeholder="请输入文章标签" />
  109. </th:block>
  110. </div>
  111. <div class="clearfix"></div>
  112. <div id="md-container" class="form-group">
  113. <textarea id="md-editor" th:utext="${contents!=null and contents.content !=null}?${contents.content}: ''"></textarea>
  114. </div>
  115. <div class="clearfix"></div>
  116. <div class="text-right">
  117. <a class="btn btn-default waves-effect waves-light" th:href="@{/admin/article}">返回列表</a>
  118. <button type="button" class="btn btn-primary waves-effect waves-light" onclick="subArticle('publish');">
  119. 保存文章
  120. </button>
  121. </div>
  122. </form>
  123. </div>
  124. </div>
  125. </div>
  126. </div>
  127. </div>
  128. </div>
  129. </body>
  130. </html>
  131. 当点击保存文章按钮后会触发执行提交方法
  132. function subArticle(status) {
  133. var title = $('#articleForm input[name=title]').val();
  134. var content = mditor.value;
  135. if (title == '') {
  136. tale.alertWarn('请输入文章标题');
  137. return;
  138. }
  139. if (title .length>25) {
  140. tale.alertWarn('文章标题不能超过25个字符!');
  141. return;
  142. }
  143. if (content == '') {
  144. tale.alertWarn('请输入文章内容');
  145. return;
  146. }
  147. $('#content-editor').val(content);
  148. $("#articleForm #status").val(status);
  149. $("#articleForm #categories").val($('#multiple-sel').val());
  150. var params = $("#articleForm").serialize();
  151. var url = $('#articleForm #id').val() != '' ? '/admin/article/modify' : '/admin/article/publish';
  152. tale.post({
  153. url:url,
  154. data:params,
  155. success: function (result) {
  156. if (result && result.success) {
  157. tale.alertOk({
  158. text:'文章保存成功',
  159. then: function () {
  160. setTimeout(function () {
  161. window.location.href = '/admin/article';
  162. }, 500);
  163. }
  164. });
  165. } else {
  166. tale.alertError(result.msg || '保存文章失败!');
  167. }
  168. }
  169. });
  170. }

(4)效果展示

7.文章修改

(1)业务处理层实现

① 编写service层接口方法

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

② 编写service层接口实现类方法

在ArticleServiceImpl中添加

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

(2)请求处理层实现

在后台管理控制类AdminController中定义分别用于处理跳转到文章修改页面和保存修改文章的操作

  1. // 向文章修改页面跳转
  2. @GetMapping(value = "/article/{id}")
  3. public String editArticle(@PathVariable("id") String id, HttpServletRequest request) {
  4. Article article = articleServiceImpl.selectArticleWithId(Integer.parseInt(id));
  5. request.setAttribute("contents", article);
  6. request.setAttribute("categories", article.getCategories());
  7. return "back/article_edit";
  8. }
  9. // 文章修改处理
  10. @PostMapping(value = "/article/modify")
  11. @ResponseBody
  12. public ArticleResponseData modifyArticle(Article article) {
  13. try {
  14. articleServiceImpl.updateArticleWithId(article);
  15. logger.info("文章更新成功");
  16. return ArticleResponseData.ok();
  17. } catch (Exception e) {
  18. logger.error("文章更新失败,错误信息: "+e.getMessage());
  19. return ArticleResponseData.fail();
  20. }
  21. }

(3)效果展示

8.文章删除

(1)业务处理层实现

① 编写service层接口方法

在IArticleService中添加

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

② 编写service层接口实现类方法

在ArticleServiceImpl中添加

  1. @Autowired
  2. private CommentMapper commentMapper;
  3. // 删除文章
  4. @Override
  5. public void deleteArticleWithId(int id) {
  6. // 删除文章的同时,删除对应的缓存
  7. articleMapper.deleteArticleWithId(id);
  8. redisTemplate.delete("article_" + id);
  9. // 同时删除对应文章的统计数据
  10. statisticMapper.deleteStatisticWithId(id);
  11. // 同时删除对应文章的评论数据
  12. commentMapper.deleteCommentWithId(id);
  13. }

(2)请求处理层实现

  1. // 文章删除
  2. @PostMapping(value = "/article/delete")
  3. @ResponseBody
  4. public ArticleResponseData delete(@RequestParam int id) {
  5. try {
  6. articleServiceImpl.deleteArticleWithId(id);
  7. logger.info("文章删除成功");
  8. return ArticleResponseData.ok();
  9. } catch (Exception e) {
  10. logger.error("文章删除失败,错误信息: "+e.getMessage());
  11. return ArticleResponseData.fail();
  12. }
  13. }

(3)实现前端页面功能

在back文件夹下的artice_list.html文件中处理展示文章列表和删除文章请求的前端页面功能

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org" th:with="title='文章管理',active='article'">
  3. <header th:replace="back/header::headerFragment(${title},${active})"></header>
  4. <head>
  5. <meta th:name="_csrf" th:content="${_csrf.token}"/>
  6. <!-- 默认的header name是X-CSRF-TOKEN -->
  7. <meta th:name="_csrf_header" th:content="${_csrf.headerName}"/>
  8. </head>
  9. <body class="fixed-left">
  10. <div id="wrapper">
  11. <div th:replace="back/header::header-body"></div>
  12. <div class="content-page">
  13. <div class="content">
  14. <div class="container">
  15. <div class="row">
  16. <div class="col-sm-12">
  17. <h4 class="page-title">文章管理</h4>
  18. </div>
  19. <div class="col-md-12">
  20. <table class="table table-striped table-bordered">
  21. <thead>
  22. <tr>
  23. <th width="35%">文章标题</th>
  24. <th width="15%">发布时间</th>
  25. <th>浏览量</th>
  26. <th>所属分类</th>
  27. <th>操作</th>
  28. </tr>
  29. </thead>
  30. <tbody>
  31. <th:block th:each="article : ${articles.list}">
  32. <tr th:id="${article.id}">
  33. <td>
  34. <a th:href="@{'/article/'+${article.id}}" th:text="${article.title}" target="_blank"></a>
  35. </td>
  36. <td><th:block th:text="${commons.dateFormat(article.created)}"/></td>
  37. <td><th:block th:text="${article.hits}"/></td>
  38. <td><th:block th:text="${article.categories}"/></td>
  39. <td>
  40. <a th:href="@{'/admin/article/'+${article.id}}"
  41. class="btn btn-primary btn-sm waves-effect waves-light m-b-5">
  42. <i class="fa fa-edit"></i> <span>编辑</span></a>
  43. <a href="javascript:void(0)" th:onclick="'delArticle('+${article.id}+');'"
  44. class="btn btn-danger btn-sm waves-effect waves-light m-b-5">
  45. <i class="fa fa-trash-o"></i> <span>删除</span></a>
  46. <a class="btn btn-warning btn-sm waves-effect waves-light m-b-5" href="javascript:void(0)">
  47. <i class="fa fa-rocket"></i> <span>预览</span></a>
  48. </td>
  49. </tr>
  50. </th:block>
  51. </tbody>
  52. </table>
  53. <div th:replace="comm/paging :: pageAdminNav(${articles})"></div>
  54. </div>
  55. </div>
  56. <div th:replace="back/footer :: footer-content"></div>
  57. </div>
  58. </div>
  59. </div>
  60. </div>
  61. <div th:replace="back/footer :: footer"></div>
  62. <script type="text/javascript">
  63. function delArticle(id) {
  64. // 获取<meta>标签中封装的_csrf信息
  65. var token = $("meta[name='_csrf']").attr("content");
  66. var header = $("meta[name='_csrf_header']").attr("content");
  67. if(confirm('确定删除该文章吗?')){
  68. $.ajax({
  69. type:'post',
  70. url : '/admin/article/delete',
  71. data: {id:id},
  72. dataType: 'json',
  73. beforeSend : function(xhr) {
  74. xhr.setRequestHeader(header, token);
  75. },
  76. success: function (result) {
  77. if (result && result.success) {
  78. window.alert("文章删除成功");
  79. window.location.reload();
  80. } else {
  81. window.alert(result.msg || '文章删除失败')
  82. }
  83. }
  84. });
  85. }
  86. }
  87. </script>
  88. </body>
  89. </html>

(4)效果展示

9.用户登录控制

(1)请求处理层实现

在com.lg.ch10/web/client文件夹下创建用户登录管理的控制类文件LoginController

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.PathVariable;
  4. import javax.servlet.http.HttpServletRequest;
  5. import java.util.Map;
  6. @Controller
  7. public class LoginController {
  8. // 向登录页面跳转,同时封装原始页面地址
  9. @GetMapping(value = "/login")
  10. public String login(HttpServletRequest request, Map map) {
  11. // 分别获取请求头和参数url中的原始访问路径
  12. String referer = request.getHeader("Referer");
  13. String url = request.getParameter("url");
  14. System.out.println("referer= "+referer);
  15. System.out.println("url= "+url);
  16. // 如果参数url中已经封装了原始页面路径,直接返回该路径
  17. if (url!=null && !url.equals("")){
  18. map.put("url",url);
  19. // 如果请求头本身包含登录,将重定向url设为空,让后台通过用户角色进行选择跳转
  20. }else if (referer!=null && referer.contains("/login")){
  21. map.put("url", "");
  22. }else {
  23. // 否则的话,就记住请求头中的原始访问路径
  24. map.put("url", referer);
  25. }
  26. return "comm/login";
  27. }
  28. // 对Security拦截的无权限访问异常处理路径映射
  29. @GetMapping(value = "/errorPage/{page}/{code}")
  30. public String AccessExecptionHandler(@PathVariable("page") String page, @PathVariable("code") String code) {
  31. return page+"/"+code;
  32. }
  33. }

(2)实现前端页面功能

打开comm文件下的自定义用户登录页面login.html进行自定义用户登录功能的查看和实现

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport"
  7. content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  8. <title>登录博客后台</title>
  9. <meta http-equiv="Cache-Control" content="no-siteapp"/>
  10. <link rel="shortcut icon" th:href="@{/user/img/bloglogo.jpg}"/>
  11. <script th:src="@{/assets/js/jquery.min.js}"></script>
  12. <script th:src="@{/assets/js/amazeui.min.js}"></script>
  13. <link rel="stylesheet" th:href="@{/assets/css/amazeui.min.css}"/>
  14. <link rel="stylesheet" th:href="@{/assets/css/app.css}"/>
  15. </head>
  16. <body>
  17. <div class="log">
  18. <div class="am-g">
  19. <div class="am-u-lg-3 am-u-md-6 am-u-sm-8 am-u-sm-centered log-content">
  20. <h1 class="log-title am-animation-slide-top" style="color: black;" th:text="#{login.welcomeTitle}">~欢迎登录博客~</h1>
  21. <br>
  22. <div th:if="${param.error}" style="color: red" th:text="#{login.error}">用户名或密码错误!</div>
  23. <form class="am-form" id="loginForm" th:action="@{/login}" method="post">
  24. <div>
  25. <input type="hidden" name="url" th:value="${url}">
  26. </div>
  27. <div class="am-input-group am-radius am-animation-slide-left">
  28. <input type="text" class="am-radius" th:placeholder="#{login.username}" name="username" />
  29. <span class="am-input-group-label log-icon am-radius">
  30. <i class="am-icon-user am-icon-sm am-icon-fw"></i>
  31. </span>
  32. </div>
  33. <br>
  34. <div class="am-input-group am-animation-slide-left log-animation-delay">
  35. <input type="password" class="am-form-field am-radius log-input" th:placeholder="#{login.password}" name="password" />
  36. <span class="am-input-group-label log-icon am-radius">
  37. <i class="am-icon-lock am-icon-sm am-icon-fw"></i>
  38. </span>
  39. </div>
  40. <div style="padding-top: 10px;">
  41. <input type="submit" th:value="#{login.sub}"
  42. class="am-btn am-btn-primary am-btn-block am-btn-lg am-radius am-animation-slide-bottom log-animation-delay" />
  43. </div>
  44. </form>
  45. </div>
  46. </div>
  47. <footer class="log-footer">
  48. <p style="margin: 30px; color: #2E2D3C"><time class="comment-time" th:text="${#dates.format(new java.util.Date().getTime(), 'yyyy')}"></time> &copy; Powered By <a style="color: #0e90d2" rel="nofollow">CrazyStone</a></p>
  49. </footer>
  50. </div>
  51. </body>
  52. </html>

(3)编写Security认证授权配置类

在com.lg.ch10/web/config文件夹下创建SecurityConfig

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.beans.factory.annotation.Value;
  3. import org.springframework.security.access.AccessDeniedException;
  4. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  6. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  7. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  8. import org.springframework.security.core.Authentication;
  9. import org.springframework.security.core.AuthenticationException;
  10. import org.springframework.security.core.GrantedAuthority;
  11. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  12. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  13. import org.springframework.security.web.access.AccessDeniedHandler;
  14. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  15. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  16. import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
  17. import org.springframework.security.web.savedrequest.RequestCache;
  18. import org.springframework.security.web.savedrequest.SavedRequest;
  19. import javax.servlet.RequestDispatcher;
  20. import javax.servlet.ServletException;
  21. import javax.servlet.http.HttpServletRequest;
  22. import javax.servlet.http.HttpServletResponse;
  23. import javax.sql.DataSource;
  24. import java.io.IOException;
  25. import java.net.URL;
  26. import java.util.Collection;
  27. @EnableWebSecurity // 开启MVC security安全支持
  28. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  29. @Autowired
  30. private DataSource dataSource;
  31. @Value("${COOKIE.VALIDITY}")
  32. private Integer COOKIE_VALIDITY;
  33. @Override
  34. protected void configure(HttpSecurity http) throws Exception {
  35. // 1、自定义用户访问控制
  36. http.authorizeRequests()
  37. .antMatchers("/","/page/**","/article/**","/login").permitAll()
  38. .antMatchers("/back/**","/assets/**","/user/**","/article_img/**").permitAll()
  39. .antMatchers("/admin/**").hasRole("admin")
  40. .anyRequest().authenticated();
  41. // 2、自定义用户登录控制
  42. http.formLogin()
  43. .loginPage("/login")
  44. .usernameParameter("username").passwordParameter("password")
  45. .successHandler(new AuthenticationSuccessHandler() {
  46. @Override
  47. public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
  48. String url = httpServletRequest.getParameter("url");
  49. // 获取被拦截的原始访问路径
  50. RequestCache requestCache = new HttpSessionRequestCache();
  51. SavedRequest savedRequest = requestCache.getRequest(httpServletRequest,httpServletResponse);
  52. if(savedRequest !=null){
  53. // 如果存在原始拦截路径,登录成功后重定向到原始访问路径
  54. httpServletResponse.sendRedirect(savedRequest.getRedirectUrl());
  55. } else if(url != null && !url.equals("")){
  56. // 跳转到之前所在页面
  57. URL fullURL = new URL(url);
  58. httpServletResponse.sendRedirect(fullURL.getPath());
  59. }else {
  60. // 直接登录的用户,根据用户角色分别重定向到后台首页和前台首页
  61. Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  62. boolean isAdmin = authorities.contains(new SimpleGrantedAuthority("ROLE_admin"));
  63. if(isAdmin){
  64. httpServletResponse.sendRedirect("/admin");
  65. }else {
  66. httpServletResponse.sendRedirect("/");
  67. }
  68. }
  69. }
  70. })
  71. // 用户登录失败处理
  72. .failureHandler(new AuthenticationFailureHandler() {
  73. @Override
  74. public void onAuthenticationFailure(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  75. // 登录失败后,取出原始页面url并追加在重定向路径上
  76. String url = httpServletRequest.getParameter("url");
  77. httpServletResponse.sendRedirect("/login?error&url="+url);
  78. }
  79. });
  80. // 3、设置用户登录后cookie有效期,默认值
  81. http.rememberMe().alwaysRemember(true).tokenValiditySeconds(COOKIE_VALIDITY);
  82. // 4、自定义用户退出控制
  83. http.logout().logoutUrl("/logout").logoutSuccessUrl("/");
  84. // 5、针对访问无权限页面出现的403页面进行定制处理
  85. http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
  86. @Override
  87. public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
  88. // 如果是权限访问异常,则进行拦截到指定错误页面
  89. RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher("/errorPage/comm/error_403");
  90. dispatcher.forward(httpServletRequest, httpServletResponse);
  91. }
  92. });
  93. }
  94. }

(4)效果展示

在浏览器输入http://localhost

10.定时邮件发送

(1)邮件发送工具类实现

在com.lg.ch10/utils包下创建工具类MailUtils.java

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.beans.factory.annotation.Value;
  3. import org.springframework.mail.SimpleMailMessage;
  4. import org.springframework.mail.javamail.JavaMailSenderImpl;
  5. import org.springframework.stereotype.Component;
  6. @Component
  7. public class MailUtils {
  8. @Autowired
  9. private JavaMailSenderImpl mailSender;
  10. @Value("${spring.mail.username}")
  11. private String mailfrom;
  12. // 发送简单邮件
  13. public void sendSimpleEmail(String mailto, String title, String content) {
  14. // 定制邮件发送内容
  15. SimpleMailMessage message = new SimpleMailMessage();
  16. message.setFrom(mailfrom);
  17. message.setTo(mailto);
  18. message.setSubject(title);
  19. message.setText(content);
  20. // 发送邮件
  21. mailSender.send(message);
  22. }
  23. }

(2)邮件定时发送调度实现

在com.lg.ch10/web包下创建scheduletask包,并在该包内创建一个管理类ScheduleTask

  1. import com.lg.ch10.dao.StatisticMapper;
  2. import com.lg.ch10.utils.MailUtils;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.scheduling.annotation.Scheduled;
  6. import org.springframework.stereotype.Component;
  7. @Component
  8. public class ScheduleTask {
  9. @Autowired
  10. private StatisticMapper statisticMapper;
  11. @Autowired
  12. private MailUtils mailUtils;
  13. @Value("${spring.mail.username}")
  14. private String mailto;
  15. @Scheduled(cron = "0 */3 * * * ? ")
  16. public void sendEmail(){
  17. // 定制邮件内容
  18. long totalvisit = statisticMapper.getTotalVisit();
  19. long totalComment = statisticMapper.getTotalComment();
  20. StringBuffer content = new StringBuffer();
  21. content.append("博客系统总访问量为:"+totalvisit+"人次").append("\n");
  22. content.append("博客系统总评论量为:"+totalComment+"人次").append("\n");
  23. mailUtils.sendSimpleEmail(mailto,"个人博客系统流量统计情况",content.toString());
  24. }
  25. }

(3)开启基于注解的定时任务

在启动类中添加

@EnableScheduling  // 开启定时任务注解功能支持

(4)效果展示

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/919899
推荐阅读
相关标签
  

闽ICP备14008679号